Repository navigation
feat(devtools): inspector refinement — correctness, token system, dirty-first defaults - #52
Conversation
…change the shortcut to Alt+Shift+E Phase 5a of the inspector refinement (correctness). - Diff table: body cells are <code> but every cell rule targeted `.pem-diff-row > span`, so body rows rendered with no padding, mono font, wrapping or change colour. Rules now target any cell element. A test proves the markup/stylesheet contract since jsdom never applies the Shadow DOM stylesheet. - Value tabs (original/patch/live/diff): extracted EntityValueTabs with a roving tabindex, ArrowLeft/Right/Home/End, ids, aria-controls, and the panel labelled by the active tab, mirroring the workspace tabs. - Shortcut: the Mod+Shift+G default was the browser's "Find previous" on macOS and fired while typing. Default is now Alt+Shift+E (new `alt` modifier; apps that configured the old shortcut keep it), listened on window, and ignored when the event comes from an editable element. React suite 83/83, package typecheck clean. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…bership list, no dangling aria-controls Phase 5a of the inspector refinement, second slice. - Command feedback: one element swapped role between "status" and "alert" (with aria-live="polite" on both). Assistive technology does not reliably announce a role change, so CommandFeedback now keeps a polite status region and an alert mounted side by side. - InspectorVirtualList passes the row index to renderItem; the View detail membership rows used membership.indexOf(member), O(n²) per render. - The membership scroll list had no bounded height (its flex:1 parent is not a flex container), so the virtualizer had no viewport and rendered every row; it is now capped at min(420px, 50vh) and falls back to one column when virtualized, where the 2-column grid cannot apply. - Graph Pulse: aria-controls pointed at a list that is removed while collapsed; it is now set only while expanded. Devtools tests 16/16, React package typecheck clean. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…es, container-query layout
Phase 5b of the inspector refinement (tokens & theme). Every class name,
rule structure, copy and behaviour is kept; only values change.
- One :host token block (3 surface depths, text/muted/code, amber accent,
add/mod/del, line, focus, radius, fonts), each overridable through
--pem-devtools-* with the seven pre-existing override names still honoured.
Hex literals: 120 (58 unique) → 12, all inside the token block; the
duplicated --pem-line/--pem-muted/--pem-accent declarations are gone.
- Dirty state is --pem-mod and errors are --pem-del instead of one shared
"attention" orange; diff rows colour by kind (changed/added/removed).
Status markup now emits data-tone="dirty" | "error".
- Type scale {11,12,13,15,18,24}px (8–10px data text removed), weights
{500,600,700}; spacing on a {2,4,6,8,12,16,20,24} scale.
- Every interactive control ≥ 32px on desktop and 44px under the touch
breakpoint, including the pills, rail buttons, dismiss, trace buttons and
pulse segments that the old breakpoint skipped.
- Layout breakpoints are @container queries on .pem-panel-content (rail
collapses < 900px, navigator/detail stack < 640px) so docked panels adapt
to their own width; the viewport query keeps only frame/safe-area rules.
- Contrast: launcher badge 3.30 → 9.09, pulse underline 2.88 → 6.15, and
every token on every surface ≥ 4.5:1 (tightest: del on elevated, 4.68).
- Motion: the per-segment Pulse animation is removed; the one animation is a
200ms ease-out panel reveal, disabled under prefers-reduced-motion.
::selection and scrollbars take palette colours; tabular numerals for data.
styles.test.ts proves all of this against the stylesheet string (78 cases).
React package: 167/167 tests, typecheck clean.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…event copy, visible rewound state
Phase 5c of the inspector refinement (defaults & flow).
- Landing: with any dirty entity the inspector opens on Entities with the
dirty filter and the first dirty entity selected; a dirty entity's value
view defaults to the diff ("Changes"). Explicit user choices still win.
- Status is actionable: header dirty/error chips and the Overview Dirty and
Errors metrics are buttons that open Entities filtered to that state with
the first match selected; the errors chip disappears at 0.
- Entity detail shows a one-line summary ("2 fields locally patched ·
status, +1 more") so the answer reads without opening a tab.
- Event copy: mutations are titled by their first affected identity and
operation ("Order/o-1042 · patch status", "+N more"), the correlation
column shows the sequence segment instead of the store prefix, and the
duplicate "Observed" timestamp row is gone.
- Rewound state is global: the inspector frame takes a --pem-mod outline and a
sticky "Viewing snapshot N (event M) · Return to live" bar sits under the
tabs in every workspace.
- Pause reads "Resume · N new" while paused.
+24 tests (inspector-shell.test.tsx harness, event-format.test.ts);
React package 191/191, typecheck clean. Stylesheet contract tests extended
for the new controls.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…ector gate Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Codex Review SummaryThis comment shows the latest Codex review activity on this pull request.
ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings. |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: a6fa3ab30c
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| import { cleanup, fireEvent, render, screen } from "@testing-library/react"; | ||
| import { afterEach, describe, expect, it, vi } from "vitest"; |
There was a problem hiding this comment.
Remove the newly added isolated test suites
This commit adds nine Vitest unit/component suites and cites them as development-gate evidence, but the repository explicitly forbids creating or expanding unit, component, isolated, snapshot, or mock-backed tests and requires the assembled integration/acceptance suite instead. Remove these suites and retain coverage in the packed browser/integration flow.
AGENTS.md reference: AGENTS.md:L92-L102
Useful? React with 👍 / 👎.
| const onKeyDown = (event: KeyboardEvent) => { | ||
| if (isEditableShortcutTarget(event.target)) return; | ||
| if (!matchesEntityGraphDevtoolsShortcut(event, resolvedShortcut)) return; |
There was a problem hiding this comment.
Check the composed path before firing the shortcut
When the shortcut is pressed in an inspector search input or preview textarea, this window-level listener sees the event retargeted to the DevTools shadow host because the inspector is portaled into a ShadowRoot. Consequently isEditableShortcutTarget(event.target) returns false and Alt+Shift+E still closes the panel while editing; inspect event.composedPath() for editable elements before matching the shortcut.
Useful? React with 👍 / 👎.
| const pausedNewCount = useMemo(() => { | ||
| if (!paused) return 0; | ||
| const lastSeen = pausedEvents[pausedEvents.length - 1]?.sequence ?? 0; | ||
| return liveEvents.filter((event) => event.sequence > lastSeen).length; | ||
| }, [liveEvents, paused, pausedEvents]); |
There was a problem hiding this comment.
Count paused events beyond the retention window
If the feed remains paused for more events than the bounded history retains, earlier post-pause events are evicted from liveEvents, so this filter can never report more than the retained subset (for example, 501 arrivals with a 500-event limit display 500 new). Track the sequence at the moment of pausing and compare it with the latest sequence so the Resume badge reflects all events received since pause.
Useful? React with 👍 / 👎.
…o has stable deps react-hooks/exhaustive-deps flagged the `?? []` fallback feeding useMemo. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
The Documentation site workflow's packed-API input hash drifted after #52 changed the React dist and #54 bumped the packages to 4.1.1: the inventory regenerated for 4.1.0 no longer described the built declarations. Same refresh as #48, for the 4.1.1 candidate (c5a3a10): - release/npm-registry-status.json: 13 packages at 4.1.1 on latest and next, candidate source c5a3a10, release URL v4.1.1. - release/v3-release-contract.json release.version and npm fixed version, examples/coverage.json + schema: 4.1.1. - Regenerated: packed API reference and inventory (13 packages), README and release-guide generated blocks, search index. - Hand-written release prose (README, RELEASING, announcement, website docs) states 4.1.1; the 4.1.0 GC fix stays as history. Website doc-contract tests 19/19, readme parity, ci:validate, test:release-contract 17/17 and the website build all pass. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Summary
An impeccable critique + audit of the React DevTools inspector (which the Chrome extension also renders) scored it 19/40 on Nielsen's heuristics and 10/20 on the technical audit: a competent but generic dark dashboard whose one real idea, the causal chain event → entity → field → view, was the smallest text on screen, with a broken diff table, a shortcut that collided with the browser's Find-previous, fixed-pixel columns that collapsed in docked layouts, 120 hard-coded colours, and a four-click path from "something is dirty" to "what changed". This is a refinement of the incumbent, not a redesign: class names, structure, copy style and data model are kept.
Five commits, one per phase:
spanselectors vscodecells); value tabs are a real ARIA tablist (roving tabindex, arrow keys,aria-controls); shortcut defaults to Alt+Shift+E (newaltmodifier; configured legacy shortcuts keep working), listens onwindow, and ignores editable targets.indexOf); the membership list is height-bounded so the virtualizer has a viewport; no danglingaria-controlson the collapsed Pulse.:hosttoken block (3 surface depths, text/muted/code, amber accent, add/mod/del, line, focus), every value overridable via--pem-devtools-*with the seven existing override names still honoured; hex literals 120 → 12 (all in the token block); dirty = mod, error = del instead of one shared orange; type scale {11…24}px, weights {500,600,700}, spacing on a 4-step scale; every control ≥ 32px desktop / 44px touch; layout via@containerqueries so docked panels adapt to their own width; contrast failures fixed (badge 3.30 → 9.09, pulse underline 2.88 → 6.15); one authored motion (200ms panel reveal) and none per Pulse segment.styles.test.tsenforces all of it (78 cases).Follow-ups deliberately not built: time travel as a Pulse scrubber, a light theme, replacing Overview with an attention landing.
Test plan
ci:lint,ci:typecheckpass.ci:integration(packed Vite/Next/browser inspector gate): 6/6 Chromium scenarios pass with the refined UI, including keyboard navigation, axe (0 serious/critical), docked layouts and the 5,000-event stress run.🤖 Generated with Claude Code