Skip to content

Cozier dev tool: hero panels, a real data browser, and profiling - #252

Merged
crs48 merged 8 commits into
mainfrom
claude/devtools-overhaul
Jun 24, 2026
Merged

Cozier dev tool: hero panels, a real data browser, and profiling#252
crs48 merged 8 commits into
mainfrom
claude/devtools-overhaul

Conversation

@crs48

@crs48 crs48 commented Jun 24, 2026

Copy link
Copy Markdown
Owner

Implements docs/explorations/0217. The in-app dev tool (@xnetjs/devtools, Ctrl/Cmd+Shift+D) went from a 20-tab horizontally-scrolling strip to a cozy, high-signal surface — and the genuinely useful panels got rebuilt on infrastructure that already existed.

What changed

Information architecture — four hero panels (Data · Changes · Logs · Performance) in a compact primary row; everything else tucked into a grouped More ▾ menu and a ⌘⇧P command palette (fuzzy by label + keywords). Driven by a typed panel registry ({ id, label, icon, group, tier, keywords }) instead of a flat array + 18-case switch. Persisted state now includes panel height (was lost on reload) and the persisted-panel validation derives from the registry — no more stale allowlist — and migrates the old nodes id → data.

Data panel (rebuilt) — the old NodeExplorer polled store.list() every 2s and synthesized a fake text-only schema. It's now the real database grid (GridSurface, read-only) driven by store.query(): schema picker, schema-aware typed columns, live store.subscribe updates (no polling), a query-plan inspector (strategy / indexes / duration / SQL), a row detail pane, and a PanelErrorBoundary so malformed node data can never take down the surface.

Logs panel (new) — toggle the five debug channels (sync/sqlite/query/boot/trace) that were previously console-only localStorage flags, plus a searchable, level/channel-filterable console capture.

Performance panel (new) — unifies signals that were scattered across four places: cold-start boot-timeline waterfall (read from performance marks), live FPS + JS heap, storage stats (nodes / lamport / OPFS quota), active queries, and recent slow traces.

Quality

  • New + existing tests green (110 in the dom project), repo typecheck green, pnpm --filter xnet-web build green (prod still tree-shakes @xnetjs/devtools to the no-op — zero bytes shipped to users).
  • Verified live in the browser: hero tabs, More menu, palette navigation + keyword search, the Data grid rendering real nodes with the query-plan inspector, log capture, and the boot-timeline waterfall — no console errors.
  • A 10-agent adversarial review of the diff caught 5 real issues (date/dateRange/file/relation cells rendering wrong, a perpetual loading spinner when the store is null, count:'estimate' not populating the row total, and mis-tagged log channels) — all fixed with regression tests.

🤖 Generated with Claude Code

xNet Test and others added 6 commits June 24, 2026 10:39
Condense the 20-tab bottom strip into 4 hero panels (Data, Changes, Logs,
Performance) with the rest grouped in a 'More' menu + a ⌘⇧P command palette.

- Typed panel registry (tier/group/icon/keywords) replaces the flat array;
  persisted-panel validation now derives from the registry (no stale allowlist)
  and migrates the old 'nodes' id -> 'data'. Panel height now persists too.
- Data panel: rebuilt on the real database grid (GridSurface, read-only) +
  store.query() with a query-plan inspector, schema-aware columns, live
  store.subscribe updates (no more 2s poll), and a row detail pane.
- Logs panel (new): toggle the five debug channels + a searchable console
  capture ring buffer.
- Performance panel (new): boot-timeline waterfall (from perf marks), live
  FPS/heap, storage stats, active queries, and recent traces.
- PanelErrorBoundary guards the data grid against malformed node data.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The read-only data grid crashed ("str.replace is not a function") when a
node property whose value wasn't a string landed in a text-family cell —
text/select/date/etc. renderers call string methods. Coerce each cell to its
column's FieldType: numbers->number, checkbox->boolean, multiSelect->string[],
everything else stringified. Caught live by the PanelErrorBoundary fallback.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…t, log tags)

From a 10-agent adversarial review of the diff:
- Data grid: coerce cells to each renderer's native shape — date/created/updated
  keep an epoch number (was "Invalid Date"), dateRange a {start,end} object,
  relation/person/multiSelect an array, file a FileRef; only text-family types
  are stringified. Fixes broken rendering of common schema column types.
- Data panel: land on a terminal empty state when the store is null instead of a
  perpetual "Loading nodes…" spinner.
- Data query: request count:'exact' so the "X / Y rows" total is populated under
  storage pushdown ('estimate' left it undefined).
- Logs: classifyChannel now buckets the real emitters correctly (the SQLite
  adapter's 'query plan' line -> Query; '[WSSyncProvider]' -> Sync).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@crs48
crs48 temporarily deployed to pr-252 June 24, 2026 18:41 — with GitHub Actions Inactive
The schema registry exposes each schema under both a versioned IRI
(.../Task@1.0.0) and a bare alias (.../Task); schemaLabel strips the version,
so every schema showed up twice in the picker (154 entries). Group options by
base IRI and emit one per schema — preferring the versioned IRI to query with,
and only splitting out genuinely-distinct versions (with a version suffix).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@crs48
crs48 temporarily deployed to pr-252 June 24, 2026 18:46 — with GitHub Actions Inactive
@github-actions

github-actions Bot commented Jun 24, 2026

Copy link
Copy Markdown
Contributor

Preview removed for PR #252.

github-actions Bot added a commit that referenced this pull request Jun 24, 2026
} from './grid-adapter'
import { useDataExplorer } from './useDataExplorer'

export function DataExplorer() {
return type as FieldType
}

function optionsFor(prop: PropertyDefinition): GridFieldOption[] | undefined {
'general'
]

export function LogsPanel() {
setLogs([])
}, [])

const filtered = logs.filter((entry) => {
)
}

function StorageSection() {
)
}

function PlanInspector({ plan }: { plan: PlanMeta }) {
}
}, [selectedSchema])

const runQuery = useCallback(async () => {
)
}

function ActiveQueryRow({ query }: { query: ActiveQuery }) {
]

/** Read the boot marks back off the Performance timeline. */
export function readBootMarks(): BootMarks {

// ⌘/Ctrl+Shift+P opens the panel palette (only while devtools is mounted).
useEffect(() => {
const handler = (e: KeyboardEvent) => {
Add an opt-in 'edit' toggle to the Data panel (shown only when a specific
schema is selected). When on, inline-editable property columns (text/number/
checkbox/select/multiSelect/date/dateRange/url/email/phone) become editable and
write back via store.update; system columns, computed/auto types, relation/
person/file, and option-less selects stay locked. Edits coerce to the property
type (date accepts ISO strings) and failures surface inline. The 'All schemas'
view stays read-only (synthesized columns).

- GridSurface now honors a column's structural `readonly` flag (it was set by
  schema-to-grid-fields but never enforced) — both the keyboard and double-click
  edit paths skip locked columns, including checkbox toggles.
- Schema resolution is now robust to the registry's versioned-vs-bare keying
  (try the selected IRI and its base, sync then lazy get()), so editing
  activates for built-in schemas.

Verified live: edited a Database title in-grid and it persisted via
store.update + live re-query. 153 views grid tests + devtools suite green.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@crs48
crs48 temporarily deployed to pr-252 June 24, 2026 19:08 — with GitHub Actions Inactive
@github-actions

Copy link
Copy Markdown
Contributor

🖼️ UI changes in this PR

No visual differences detected in the changed UI.

CI run

github-actions Bot added a commit that referenced this pull request Jun 24, 2026
github-actions Bot added a commit that referenced this pull request Jun 24, 2026
return
}
const candidates = Array.from(new Set([selectedSchema, baseSchemaIri(selectedSchema)]))
void (async () => {
// Write a single edited cell back to the store. The live subscribe above
// refreshes the grid; system columns (@@…) are never editable.
const updateCell = useCallback(
async (rowId: string, fieldId: string, fieldType: FieldType, value: CellValue) => {
@crs48
crs48 merged commit 2ee55e1 into main Jun 24, 2026
18 of 22 checks passed
@crs48
crs48 deleted the claude/devtools-overhaul branch June 24, 2026 19:21
github-actions Bot added a commit that referenced this pull request Jun 24, 2026
crs48 added a commit that referenced this pull request Jun 25, 2026
## What

The dev tool's **Data** panel (Ctrl/Cmd+Shift+D) now reflects your real
authorization per cell. When editing is on, cells you're not allowed to
write are **locked** instead of failing on save, and a row's detail pane
shows exactly how your read/write permissions are derived. Implements
exploration **0219**.

## Why

Inline editing (added in #252) let you double-click any cell and call
`store.update` — but the grid had no idea what authorization said.
Editing a cell you couldn't write surfaced an avoidable
`PermissionError` only *after* you tried to save. This wires the
existing `store.auth` policy engine into the grid so the UI tells the
truth up front.

## How

- **`@xnetjs/views` — `GridSurface`/`GridCell`:** new optional
`cellLockReasons?: ReadonlyMap<string, string>` (key `rowId:fieldId` →
human reason). A shared `isCellLocked` predicate guards **every** write
path — edit-start, double-click, paste, fill-down, cut/clear
(`refsInRect`), and file-drop — not just the editor. Locked cells render
a subtle `Lock` glyph with the reason as the cell `title`.
Default-absent ⇒ unchanged behavior, so `DatabaseView` in the app is
unaffected.
- **`@xnetjs/devtools` — `useCellPermissions`:** lazy, edit-mode-only
per-node `write` decisions over the visible window (guarded on
`store.auth`, optimistic until resolved). Locks a cell when the node
isn't writable *or* a restricted `fieldRule` denies the field. Pure
`deriveCellLocks` extracted for testing.
- **`AuthTraceView`** extracted from `AuthZPanel` and reused in a
`NodePermissions` section of the node detail pane (`store.auth.explain`
for read + write → roles, grants, reasons, steps).
- **No-authz fallback:** stores without an `authEvaluator` keep
type-based editing and show a "permissions not enforced in this store"
note — no spurious locks.
- **Authz cache fix:** `DefaultPolicyEvaluator.can` no longer
reads/writes the decision cache when a `patch` is present. The cache is
keyed only by subject/action/node, so a prior node-level write check
could otherwise mask a field rule for the rest of the TTL — silently
bypassing field-level restrictions.

## Tests

- `GridSurface`: a locked cell blocks Enter + double-click editing
(shows title); sibling row still edits.
- `deriveCellLocks` / `restrictedFieldNames`: node-deny locks all
editable fields, writable unlocked, field-rule lock,
optimistic-undecided.
- `evaluator`: regression — a cached node-level decision no longer masks
a field rule.

Verified live in the browser preview (the dev store has no
`authEvaluator`, so the preview exercises the graceful "not enforced"
fallback; the enforced lock path is covered by the unit tests above).

🤖 Generated with [Claude Code](https://claude.com/claude-code)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants