Skip to content

feat(devtools): inspector refinement — correctness, token system, dirty-first defaults - #52

Merged
GQAdonis merged 6 commits into
mainfrom
fix/devtools-inspector-refinement
Oct 8, 2026
Merged

GQAdonis merged 6 commits into
mainfrom
fix/devtools-inspector-refinement

Conversation

@GQAdonis

@GQAdonis GQAdonis commented Oct 8, 2026

Copy link
Copy Markdown
Contributor

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:

  1. Correctness I — diff-table body rows were unstyled (span selectors vs code cells); value tabs are a real ARIA tablist (roving tabindex, arrow keys, aria-controls); shortcut defaults to Alt+Shift+E (new alt modifier; configured legacy shortcuts keep working), listens on window, and ignores editable targets.
  2. Correctness II — stable live regions (status + alert, no role swapping); virtual rows receive their index (removes an O(n²) indexOf); the membership list is height-bounded so the virtualizer has a viewport; no dangling aria-controls on the collapsed Pulse.
  3. Token system — one :host token 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 @container queries 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.ts enforces all of it (78 cases).
  4. Dirty-first defaults — with any dirty entity the inspector lands on Entities, dirty filter, first dirty entity selected, value view on the diff; header chips and Overview metrics are buttons that apply the filter; a one-line "N fields locally patched · path" summary; event titles name the affected identity and operation; the correlation column shows the sequence, not the store prefix; rewound state is a frame outline plus a sticky "Viewing snapshot N · Return to live" bar in every workspace; Pause reads "Resume · N new".
  5. The packed inspector gate presses the new shortcut.

Follow-ups deliberately not built: time travel as a Pulse scrubber, a light theme, replacing Overview with an attention landing.

Test plan

  • TDD per item; devtools tests 7 → 118 (whole React package 191/191), package typecheck clean.
  • ci:lint, ci:typecheck pass.
  • 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.
  • CI green once rebased on test(devtools): CI-aware long-task ceiling and one CI retry for the packed inspector gate #51 (CI long-task ceiling).

🤖 Generated with Claude Code

GQAdonis and others added 5 commits October 8, 2026 06:29
…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>
@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Oct 8, 2026 •

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review ✅ Completed 2026-10-08T12:30:19.130724Z a6fa3ab PR opened
ℹ️ 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" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 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".

Comment on lines +1 to +2
import { cleanup, fireEvent, render, screen } from "@testing-library/react";
import { afterEach, describe, expect, it, vi } from "vitest";

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge 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 👍 / 👎.

Comment on lines 193 to 195
const onKeyDown = (event: KeyboardEvent) => {
if (isEditableShortcutTarget(event.target)) return;
if (!matchesEntityGraphDevtoolsShortcut(event, resolvedShortcut)) return;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge 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 👍 / 👎.

Comment on lines +325 to +329
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]);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge 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>
@GQAdonis
GQAdonis merged commit 901c37d into main Oct 8, 2026
5 of 6 checks passed
@GQAdonis
GQAdonis deleted the fix/devtools-inspector-refinement branch October 8, 2026 14:55
GQAdonis added a commit that referenced this pull request Oct 8, 2026
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>
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.

1 participant