@jacare/devtools
v0.1.17
Published
Jacaré DevTools — State inspector (overlay + Chrome extension hook)
Readme
@jacare/devtools
Development tools for Jacaré — a Pulse Graph inspector with Graph, Mesh, and Scope tabs (each of Mesh/Scope can pop out to its own window).
Use this package during development to visualize reactive dependencies, inspect live pulse values, watch createBag mesh cells, and register Scope values — without cluttering the page with three floating panels by default.
Table of contents
- Install
- Quick start
- Pulse Graph panel
- Scope panel
- Registering scope values
- Low-level API
- Options
- When to use
- Production builds
- Links
Install
npm install @jacare/devtoolsRequires @jacare/core as a peer dependency.
npm: https://www.npmjs.com/package/@jacare/devtools
Quick start
Add to your app entry (e.g. boot.js) and guard with a dev check:
import { connectJacareDevtools } from '@jacare/devtools'
let disposeDevtools = null
if (import.meta.env.DEV) {
disposeDevtools = connectJacareDevtools()
}
if (import.meta.hot) {
import.meta.hot.dispose(() => {
disposeDevtools?.()
disposeDevtools = null
})
}One floating panel appears by default (dev only):
- Pulse Graph — tabs Graph | Mesh | Scope (↗ pops Mesh or Scope into a separate window)
- (optional) detached Mesh / Scope windows when popped out
Show / hide Pulse Graph
| Action | How |
|--------|-----|
| Config | ⚙ — corners, clear highlight/selection/Scope, reset layout |
| Move | Drag the header, or pick a corner in Config |
| Minimize | − in the panel header (or click the collapsed header to expand) |
| Hide | × — collapses to a dark Pulse Graph chip |
| Show again | Click the chip |
| Remember | Mode + corners stored in sessionStorage |
| Remove entirely | Call the dispose function returned by connectJacareDevtools() |
| Never load | Omit connectJacareDevtools() or guard with import.meta.env.DEV (production builds tree-shake it) |
connectJacareDevtools({
position: 'bottom-right',
scopePosition: 'bottom-left',
})Call the returned dispose function to remove panels:
const dispose = connectJacareDevtools()
// later
dispose()Pulse Graph panel
The Pulse Graph shows every reactive node in your app:
| Node kind | Label | Description |
|-----------|-------|-------------|
| signal | Pulse | Writable reactive state |
| computed | Derive | Values computed from other nodes |
| effect | Watch | Side effects that track dependencies |
In DEV, the Jacaré compiler injects { name, file, line } on pulse / derive / effect declarations and registers DOM bindings via devtoolsBind. The panel then shows:
| Instead of | You see |
|------------|---------|
| Pulse #3 | count · Counter.jcr:4 |
| Derive #2 | total · Cart.jcr:12 |
| Action | Feedback |
|--------|----------|
| Hover a node | Outline bound DOM elements |
| Click ◎ | Pick a page element → select pulses that feed it |
| Value change | List flash + value panel flash in the Pulse Graph |
For each node you can see:
- Name / source — variable name and
file:linewhen known - Value preview — shown in the list and detail pane; flashes green when it changes
- DOM bindings — kind and location of template bindings
- Dependencies — which nodes this one reads from
- Dependents — which nodes read from this one
Pulses created before connectJacareDevtools() are still tracked (registration is always on; the panel only becomes visible after connect).
How it works
connectJacareDevtools() calls enableDevtools() from @jacare/core, which instruments the pulse graph. The panel subscribes via subscribePulseGraph() and re-renders when the graph changes (notifications are coalesced to a microtask so large mounts do not re-render the overlay on every effect). createNav calls beginDevtoolsPage() on each screen swap so the graph shows the current page plus live shell pulses, not every visited route. Disposing the overlay calls disableDevtools() so the write ledger and graph UI stop until you connect again.
Scope panel
Scope is a manual watch list — nothing appears until you call registerScope(). Useful for cart totals, form drafts, filters, etc.
Values refresh ~every 120ms. Clear with clearScope(), the Scope ⌫ button, or Pulse Graph Clear Scope. Default position: bottom-left.
Registering scope values
In your .jcr or .js files:
import { pulse, registerScope } from '@jacare/core'
const draft = pulse('')
const filter = pulse('')
registerScope('draft', 'Draft', () => draft())
registerScope('filter', 'Filter', () => filter())The Scope panel will show:
DRAFT "hello world"
FILTER ""Register as many values as you need. Use descriptive names — they appear as labels in the panel.
Form fields
import { createForm, registerScope } from '@jacare/core'
const form = createForm({
initial: { email: '', message: '' },
})
registerScope('email', 'Email', () => form.fields.email())
registerScope('email.error', 'Email error', () => form.fields.email.error())Low-level API
You can build custom tooling without the built-in panels:
import {
enableDevtools,
getPulseGraph,
subscribePulseGraph,
} from '@jacare/core'
enableDevtools()
const snapshot = getPulseGraph()
console.log(snapshot.nodes) // all reactive nodes
console.log(snapshot.edges) // dependency edges
const unsubscribe = subscribePulseGraph(() => {
const latest = getPulseGraph()
// render your own UI
})Snapshot shape
interface PulseGraphSnapshot {
nodes: PulseNode[]
edges: PulseEdge[]
}
interface PulseNode {
id: number
kind: 'signal' | 'computed' | 'effect'
name?: string
file?: string
line?: number
value?: unknown
stale?: boolean
subscribers: number
disposed: boolean
}name, file, and line are populated when compiler metadata is available.
interface PulseEdge {
from: number
to: number
}Options
connectJacareDevtools
connectJacareDevtools({
target: document.body, // where to mount panels
scope: true, // set false to hide Scope tab
mesh: true, // set false to hide Mesh tab
position: 'bottom-right',
scopePosition: 'bottom-left', // used when Scope is popped out
meshPosition: 'top-left', // used when Mesh is popped out
meshDetached: false, // true = start Mesh as its own window
scopeDetached: false, // true = start Scope as its own window
})| Option | Type | Default | Description |
|--------|------|---------|-------------|
| target | HTMLElement | document.body | DOM element to append panels to |
| scope | boolean | true | Enable Scope tab (and optional pop-out window) |
| mesh | boolean | true | Enable Mesh tab (and optional pop-out window) |
| position | corner | bottom-right | Pulse Graph corner |
| scopePosition | corner | bottom-left | Scope window corner when detached |
| meshPosition | corner | top-left | Mesh window corner when detached |
| meshDetached | boolean | false | Start with Mesh in a separate window |
| scopeDetached | boolean | false | Start with Scope in a separate window |
connectJacareScope
Use the Scope panel independently:
import { connectJacareScope } from '@jacare/devtools'
const dispose = connectJacareScope({
target: document.body,
pulseMs: 120, // refresh interval in ms
})| Option | Type | Default | Description |
|--------|------|---------|-------------|
| target | HTMLElement | document.body | Where to mount the panel |
| pulseMs | number | 120 | How often to poll scope values |
connectJacareMesh
Inspect createBag cells in a standalone floating window (same UI as ↗ Pop out from the Mesh tab):
import { connectJacareMesh } from '@jacare/devtools'
const dispose = connectJacareMesh({
target: document.body,
pulseMs: 120,
})Shows bag ids, @id/key addresses, intent ports, DOM bind sources, and the last ripple flash. Use ↙ to dock back into the Pulse Graph Mesh tab when connected via connectJacareDevtools.
When to use
| Scenario | Tool |
|----------|------|
| Debug why a template isn't updating | Pulse Graph — check dependencies |
| See which pulses an effect tracks | Pulse Graph — inspect edges |
| Watch form state during typing | Scope panel + registerScope() |
| Understand reactivity in a new screen | Both panels |
| CI / production | Do not include — dev only |
Production builds
Always guard devtools behind a dev check:
if (import.meta.env.DEV) {
const { connectJacareDevtools } = await import('@jacare/devtools')
connectJacareDevtools()
}The todo scaffold template from @jacare/cli includes this pattern by default.
Tree-shaking removes @jacare/devtools from production bundles when the import is behind import.meta.env.DEV.
Links
- npm — @jacare/devtools
- Jacaré Devtools — Chrome Web Store
- Repository
- DevTools docs
- @jacare/core
- Live demo
- Jacaré Lab
- Related: @jacare/cli · @jacare/vite-plugin
License
MIT
