Skip to content

ui: fixes and improvements for the panel shell, popup and accessibility #353

Description

@erkamyaman

Context

Fixes and improvements from a code audit of the overlay, popup and panel shell, panel ux and accessibility, done in October 2026. Roadmap work lives in #340, #341, #342 and #343. Items marked (to confirm) were not reproduced; check them before fixing.

The overlay, popup and panel shell

Problems

Medium

  • No retry after a failed first connection: connectDevframe runs once, and on rejection the panel sets connectionFailed and asks the user to reload. A dev server that starts slowly or restarts needs a manual reload. Retry with backoff (1, 2, 5, 10 s) while connectionFailed is set and add a "Retry now" button. The popup's findTarget also only retries on the next open (app/src/app.ts:913-947, app/src/app.ts:372-375, packages/devtools/src/popup.ts:114-121).

Low

  • Popup accepts theme messages from any window: onThemeMessage only rejects e.source === window, so any iframe or script on the host page can flip the popup theme, and the value is saved to localStorage. Accept the message only from iframe.contentWindow (or a frame inside it) with a matching origin (packages/devtools/src/popup.ts:827-832).
  • Theme posted to every ancestor with '*': ThemeService walks window.parent and posts pangular:theme-change with target origin '*', which leaks to unknown origins when the panel is embedded cross-origin. Post to the parent's origin (from document.referrer or ancestorOrigins), or rely on the BroadcastChannel for same-origin cases (app/src/theme.service.ts:36-47).
  • Tab strip is not a tablist: the inspector tabs are buttons with aria-current="page", with no roving tabindex or arrow keys, so ten tabs are ten tab stops before the content. Use role=tablist/tab/tabpanel with aria-selected and roving tabindex, or keep the nav pattern and add arrow-key handling (app/src/app.ts:346-358).
  • Opening the panel leaves focus on the FAB: togglePanel only moves focus on close. When opened from the keyboard (click with detail === 0), focus the iframe or its main once loaded (packages/devtools/src/popup.ts:670-675).
  • Hub views listed twice: HUB_VIEWS is defined in both the panel and the package, and the pangular: dock prefix is repeated too. Adding a dock in one place breaks followHubDocks and view titles silently. Export HUB_VIEWS and dockId from one module and import them in the app (app/src/app.ts:43-50, packages/devtools/src/hub-docks.ts:9-16, packages/devtools/src/hub-docks.ts:48, app/src/hub-dock-sync.ts:2, app/src/app.ts:1088).
  • Overlay and popup probe different bases: the overlay tries ['./', '/__pangular/', '/__devframes/pangular/'] and the popup tries ['/__pangular/', '/__devframes/pangular/', '/__devframe/', '/'], so one can find a server the other misses. Keep one exported list in panel-frame.ts and use it in both (packages/devtools/src/overlay.ts:177, packages/devtools/src/popup.ts:61).
  • Untyped hub:docks:activate call: the fallback dock switch casts the RPC client as unknown and calls hub:docks:activate. If the client has no top-level call, the panel quietly switches to the fallback tab instead of the dock. Use the typed hub client API and add a test that the hub path activates the dock (app/src/app.ts:1073-1094) (to confirm).
  • Short page id: the page id is 4 base36 characters from Math.random, and the BroadcastChannel claim only catches collisions inside one browser. Pages from other devices (NativeScript, Angular Native, a phone) reporting to one hub can collide. Use crypto.randomUUID().slice(0, 8) or longer, staying under the server's 50-character limit (packages/devtools/src/overlay.ts:96, packages/devtools/src/devframe.ts:272).

Improvements

  • Move limits, redaction, refreshMs and the action toggles from config-file-only to ctx.settings, with a Settings view in the panel (packages/devtools/src/config.ts, packages/devtools/src/devframe.ts, app/src/app.ts).
  • Export a programmatic buildReport() next to the CLI build, so CI and Analog/Vite builds can produce the static report without the CLI wrapper (packages/devtools/src/cli.ts, packages/devtools/package.json).
  • Remember scroll position and selection per tab, not only the tab id, in sessionStorage keyed by pageId, so a popup or extension reload returns to the same item (app/src/tab-memory.ts, app/src/app.ts).
  • A minimized "pill" launcher that shows the render count and error count on the FAB (packages/devtools/src/popup.ts).

Already tracked elsewhere

Panel UX and accessibility

Problems

Medium

  • Error states without Retry: several error states tell the user to reload or reopen the panel and offer no button. Add a Retry button that calls the existing refresh() or load function, as pipes-inspector.ts:188-192 already does. In the shell, retry the devframe connection with backoff and show a "Reconnect" button (app/src/pages/network-inspector.ts:296-303, app/src/pages/dashboard.ts:234-241, app/src/app.ts:372-375).
  • Store has no loading state: before the first shared state arrives, the store page shows "No page is reporting NgRx state yet.", so NgRx users see a wrong message for a moment. Set a loaded flag on the first shared state value and show a role="status" "Loading…" state until then (app/src/pages/store-inspector.ts:130-143).
  • No arrow-key navigation in long lists: the network timeline (up to 2,000 rows), the store change and event lists and the signals node list need one Tab per row. Add roving tabindex with ArrowUp/Down and Home/End, like onListKey in pipes-inspector.ts:1341. A shared appRovingList directive would cover all of them (app/src/pages/network-inspector.ts:595-616, app/src/pages/store-inspector.ts:362, 499).

Low

  • Search fields differ from page to page: the guidelines ask for a search field with an icon, Escape to clear and a count. Only six pages have the search icon. It is missing on signal-inspector.ts:159, store-inspector.ts:100, forms-inspector.ts:358, route-tree.ts:115, route-timeline.ts:37 and route-inspector.ts:55. Placeholders mix "Filter" and "Find", and only some end with "…". Extract an <app-search-field> in app/src/ui with the icon, Escape, autocomplete="off", spellcheck="false" and an optional count, and pick one verb.
  • Route tree has no tree semantics: depth is shown only by padding-left, with no aria-level, aria-expanded or treegrid role, so screen readers lose the hierarchy. Use role="treegrid" with aria-level and aria-expanded, and support Left/Right to collapse and expand (app/src/pages/route-tree.ts:176-177).
  • Filtered lists tracked by $index: when the filter changes, rows are reused by position, so focus and selection jump. Track by a stable key such as route.file + route.fullPath, a finding's code and location, or a component id (app/src/pages/route-inspector.ts:118, route-current.ts:95, 167, route-lint.ts:65, forms-report.ts:162, cd-recording.ts:89).
  • Hex colors in the Analog view: the info and kind colors are hard-coded hex values outside the token system and the tint-contrast test. Move them to app/src/styles/_palette.scss or app/src/view-accents.ts with dark and light pairs, and add them to app/src/__tests__/tint-contrast.test.ts (app/src/pages/analog-inspector.ts:1133-1138, 1693-1725).
  • "Inspector turned off" gives no next step: the message names the inspectors key but has no docs link and no snippet. Add a link (as LimitNote does with LIMITS_DOCS_URL) and the exact option to turn it on, for example inspectors: { store: true } (app/src/app.ts:380-384).
  • No shortcut to focus search or switch tabs: there is no / to focus the current page's search and no key to switch tabs. Add a host listener in app.ts for / (expose each page's search through a small service) and Alt+1..9 for tabs, skipped while focus is in an input. The Cmd/Ctrl+K palette is tracked in phase 3 of 4: a shell you live in #342 (app/src/app.ts:149-153).

Improvements

  • One shared <app-state kind="loading|error|empty|no-match"> component with Retry and "Clear filters" slots, plus a spec that checks every page renders all four states (app/src/ui, app/src/pages/*.ts).
  • A spec that lists the pages and checks each one uses <app-search-field> and the roving-list directive (app/src/ui, app/src/pages/*.ts).
  • Resizable list and detail panes with a keyboard-accessible splitter (role="separator", aria-valuenow, arrow keys), width saved per view (app/src/pages/network-inspector.ts, store-inspector.ts, di-inspector.ts, app/src/tab-memory.ts).
  • "Pause live updates" and "Auto-scroll to newest" toggles on the network timeline, store change log and route timeline (app/src/pages/network-inspector.ts, store-inspector.ts, route-timeline.ts).
  • Sortable column headers with aria-sort on the network table (time, status, duration) and the route config table (app/src/pages/network-inspector.ts:580-592, app/src/pages/route-inspector.ts).
  • Saved filter chips per view, for example "Failed only" and "SSR" on the network page, stored in localStorage inside try/catch (app/src/pages/network-inspector.ts, app/src/tab-memory.ts).
  • Run pnpm test:axe on the loading, error and no-match states too, not only the populated static report.

Already tracked elsewhere


Generated by Claude Code

Activity

  1. added
    bugSomething is broken or shows wrong data
    featureA feature request or a pull request that adds one
    on Oct 11, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething is broken or shows wrong datafeatureA feature request or a pull request that adds one

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions