Commit Graph
781 Commits
Author SHA1 Message Date
Kai (Tam Nhu) TranandGitHub 923683bf30 feat(cliproxy): route plus dashboard to maintained fork (#1173) 2026-05-03 13:01:04 -04:00
Kai (Tam Nhu) TranandGitHub 50b0ffba75 fix(cliproxy): refresh upstream model and quota surfaces (#1158)
* fix(cliproxy): refresh upstream model and quota surfaces

* test(cliproxy): cover codex legacy model fallback
2026-05-01 02:19:31 -04:00
Tam Nhu Tran 7f086c8685 fix(ui): drop dedicated stage column; render stage chip inline in message
Adding a separate STAGE column squeezed MESSAGE to 0px at the
default 22/52/26 panel split — fixed columns + gaps totalled more
than the list panel's available width.

Render the stage chip inline at the start of the MESSAGE column when
present, restoring the original 7-column grid. The bot's earlier
"extra cell mismatch" complaint is still satisfied because there's
no longer a row-only cell missing from the header.

Also tighten three fixed columns to give MESSAGE more breathing room
even on narrower saved panel layouts:
- Module 140 -> 120
- Latency 72 -> 64
- Request 112 -> 100
2026-04-30 16:59:02 -04:00
Tam Nhu Tran e7058b6b6d fix(ui): include stage in leaf coalesce key
PR-Agent flagged that the leaf coalesceKey omitted `stage`, so two
adjacent no-requestId logs differing only by stage (e.g. one logging
`route` and the next `dispatch`) would merge into a single `×2`
row, hiding a distinct entry.

Add `stage` to the coalesce tuple. New unit test asserts that two
leaves identical except for stage stay as separate rows.

Refs #1138, #1151
2026-04-30 16:31:31 -04:00
Tam Nhu Tran 2ddc5d5afd fix(ui): remove intra-trace coalescing — preserve every stage for inspection
PR-Agent flagged that the trace-child coalesce was hiding legitimate
repeated stage emissions: a request that retries an upstream call or
emits the same stage twice for any reason would show only the first
occurrence behind a `×N` badge, with subsequent attempts no longer
selectable or inspectable.

The original noise problem motivating intra-trace coalesce was the
149-stage `web-server:http` self-polling trace, but that's already
hidden by default via the `hideDashboardInternals` filter. Users who
opt in to see internals are deliberately debugging the dashboard and
SHOULD see all 149 stages — collapsing them defeats the opt-in.

Drop `coalesceChildren` entirely. Trace children render uncoalesced
so retries, duplicated stages, and multi-attempt traces stay
individually inspectable. Standalone-leaf coalesce stays — that's
the actual user-facing dedup the PR was about.

Tests: drop the 7 `coalesceChildren` cases; keep 16 covering
`deriveStageHint` and `deriveTraceGroups`.

Refs #1138, #1151
2026-04-30 16:28:42 -04:00
Tam Nhu Tran 6ada2b243e test(ui): cover trace-coalesce, leaf-coalesce, and stage-hint helpers
PR-Agent flagged "No relevant tests" each round. Add 23 unit tests
covering the pure-function surface introduced by this PR:

- deriveStageHint — explicit stage wins, fallback to last .-segment
  of event, 12-char cap, undefined when nothing meaningful.
- coalesceChildren — empty input, single child, adjacent identical
  collapse, distinct messages stay separate (round-1 fix), distinct
  sources stay separate (round-2 fix), distinct stages stay separate,
  interleaving breaks the run.
- deriveTraceGroups — empty input, lone leaf, requestId grouping,
  ts-asc child sort, group ts pinned to oldest child, adjacent leaf
  coalesce with collapsedRange, leaves split by trace stay distinct
  (round-3 fix), distinct messages stay distinct, reverse-chronological
  display sort, max-level + total-latency aggregates, original-input
  adjacency preserved despite display sort.

Refactor: coalesceChildren extracted from logs-trace-row.tsx into
derive-trace-groups.ts so it's testable as a pure function without
React rendering.

Refs #1138, #1151
2026-04-30 16:24:19 -04:00
Tam Nhu Tran 40cc62a67f fix(ui): reserve stage column on every row for consistent alignment
PR-Agent flagged that trace child rows rendered an extra stage-hint
cell while the header grid template only had 7 columns. Result: every
expanded child shifted module/message/latency/request rightward, and
the stage chip overlapped its neighbour in the trace timeline.

Add a dedicated 72px "Stage" column to the header grid template and
to every row variant. Standalone leaves and trace heads render an
empty span at the same width; trace children with stage hints render
the chip in that slot. Column edges now match across all row types.

Refs #1138, #1151
2026-04-30 16:09:50 -04:00
Tam Nhu Tran 04ddc33518 fix(ui): make trace chevron actually collapse auto-expanded traces
The first click on a trace's chevron did nothing visible when the
trace's child was the currently-selected entry. Root cause: `toggle`
only checked the `expanded` set; auto-expand kept re-injecting the
requestId so the row stayed open even after the user added it to
`expanded` (and oscillating subsequent clicks).

Replace the single `expanded` set with a tristate driven by two
sets — `userExpanded` (explicitly opened) and `userCollapsed`
(explicitly closed). Auto-expand only fires when the id is in
neither, so a click on an auto-expanded chevron now writes the
collapse intent to `userCollapsed` and the row collapses on the
first click.

Refs #1138, #1151
2026-04-30 15:58:48 -04:00
Tam Nhu Tran 871b6826f7 fix(ui): coalesce leaves only when truly adjacent in original stream
PR-Agent flagged that the previous derive-trace-groups split entries
into trace and leaf buckets *before* coalescing leaves. That meant two
identical no-requestId entries separated by other rows in the real
stream would still merge into a single `×N` row, hiding signal.

Walk `entries` in input order with single-pass coalescing: a leaf
only extends the previous leaf when no other entry (trace child or
otherwise) appeared between them. Trace children still gather across
interleaving since they're identified by `requestId`. Group children
get sorted ts-ascending for trace-timeline display, with the group's
positional ts pinned to the oldest child for the reverse-chrono
display sort.

Refs #1138, #1151
2026-04-30 15:45:02 -04:00
Tam Nhu Tran 6f1396cf1e fix(ui): import MouseEvent type explicitly in logs-row
PR-Agent flagged that `React.MouseEvent` referenced an unimported
`React` namespace; in stricter TypeScript builds this would fail
typecheck. Import the type alias from `react` directly.

Refs #1138, #1151
2026-04-30 15:44:36 -04:00
Tam Nhu Tran 39ec84a8a9 fix(ui): only show "Copied" feedback when clipboard write succeeds
PR-Agent re-review flagged that `copyText()` swallowed clipboard
errors but the row's `setJustCopied(true)` always fired regardless,
so users in insecure contexts (or with denied clipboard permission)
saw a fake success indicator while nothing was actually copied.

Make `copyText()` return a boolean and gate the "Copied" UI state
on it. When the clipboard write fails, the button stays in its idle
state and the user can retry rather than being misled.

Refs #1138, #1151
2026-04-30 15:37:50 -04:00
Tam Nhu Tran 75cdc02c1f fix(ui): include source in trace child coalesce key
PR-Agent re-review flagged that adjacent trace rows from different
services with the same event/message/stage/level/module would still
merge because `source` was missing from the coalesce key. A request
that fans out across multiple services would lose the second service's
row behind a `× 2` badge.

Add `source` to the trace child coalesce key so each participating
service stays visible in the trace timeline.

Refs #1138, #1151
2026-04-30 15:37:21 -04:00
Tam Nhu Tran b7cfbd14e2 fix(ui): make logs row keyboard-accessible without nested interactive markup
PR-Agent flagged that the row's copy-requestId widget was a
`<span role="button" tabIndex={-1}>` nested inside a row `<button>`.
Two issues:
1. Nested interactive elements inside a `<button>` is invalid HTML.
2. `tabIndex={-1}` made the copy widget unreachable via keyboard, so
   the new affordance was effectively mouse-only.

Convert the row's outer element from `<button>` to `<div role="row"
tabIndex={0}>` with `onKeyDown` for Enter/Space → select. Now the row
itself is keyboard-focusable and the copy widget can be a real
`<button>` with focus-visible styling — keyboard users can Tab to it
and Enter to copy the requestId.

Refs #1138, #1151
2026-04-30 15:26:29 -04:00
Tam Nhu Tran a465e3fec1 fix(ui): include message in coalesce key so distinct entries stay visible
PR-Agent flagged that the leaf and trace-child coalesce keys collapsed
on `(event, module, level, requestId, source)` which merges adjacent
entries that share that tuple but report different content (e.g.
`User logged in: alice` vs `User logged in: bob` would appear as a
single `× 2` row, hiding bob).

Add `message` to both coalesce keys so distinct content stays
distinct. Skip `latencyMs` and `metadata` — those drift per request
even on truly redundant polls and would defeat the dedup the feature
exists for.

Refs #1138, #1151
2026-04-30 15:26:06 -04:00
Tam Nhu Tran 0dfb7b8583 fix(ui): reserve leading chevron column so trace and leaf rows align
Trace rows pushed the TIME column ~30px right because the expand chevron
was an inline flex child while leaf rows had nothing in that position.
Result: every column drifted right whenever a trace row was visible.

Reserve a 16px slot at the start of every row (header + leaf + trace).
Leaf rows render an empty span; trace rows render the chevron centered
inside the slot. All columns now share the same x-coordinates regardless
of row type.

Refs #1138
2026-04-30 14:56:32 -04:00
Tam Nhu Tran 4a77251021 feat(ui): hide dashboard self-polling internals via default filter
On real backend, the logs page was drowning in dashboard self-polling
noise — every `/api/logs` poll emitted a 149-stage `web-server:http`
trace, burying real provider activity.

- New workspace state `hideDashboardInternals` defaults ON. Entries
  whose `source` matches `/^web-server:/i` are filtered client-side
  before they reach the list.
- Toggle exposed in advanced filters as a labelled checkbox/switch with
  a one-line description, so users can opt in to see internals when
  debugging the dashboard itself.
- `clearAdvancedFilters` resets the toggle back to ON to keep the
  default signal-clean experience.
- Header stat strip (entries / traces / errors) wired through the shell.

Backend-side `web-server:*` instrumentation stays intact (still useful
for ops debugging) — just hidden from the user-facing log feed by
default.

Refs #1138, #1141, #1142
2026-04-30 14:31:32 -04:00
Tam Nhu Tran e04598eef7 feat(ui): align logs page visual language with dashboard markers
Logs page was visually disconnected from the rest of the dashboard. Health
uses ornamental `HEALTH.ATTENTIONREQUIRED` markers, Home uses `LIVE
Account Monitor` + stat cards. Logs shipped with a 14px h1 + clinical
table that felt like a different product.

- Header now opens with a `LOGS.STREAM` mono-uppercase marker matching
  the dashboard's `HEALTH.X` style, plus a 16px "Live activity" title
  and a status pill.
- Below the header, a stat strip mirrors the home page's monitor layout:
  ENTRIES / TRACES / ERRORS counters, errors highlighted red when >0.
- Trace row + child row font sizes lifted from 11px to 12-13px;
  request-id column widened to 112px to match the standalone-row table.
- Stage-hint fallback derived from event names so the trace timeline
  still renders meaningful chips when backend entries lack an explicit
  `stage` field (e.g. dashboard self-polling).
- Intra-trace coalesce: identical consecutive child rows collapse to a
  single row with `× N` badge so a 149-stage self-poll trace renders as
  3 rows of signal instead of 149 rows of noise.

Refs #1138, #1141, #1142
2026-04-30 14:30:54 -04:00
Tam Nhu Tran d5b14bec1c fix(ui): align logs row and detail panel via shared accessors
Both surfaces now consume `getDisplayModule / getDisplayStage /
getDisplayRequestId / getDisplayLatency` from `utils.ts`, replacing the
previous divergence where the list row fell back to `source` while the
detail panel showed em-dash placeholders for the same entry.

Also widens the request-id column in the row table from 88px to 112px
and adds a hover-revealed clipboard icon for one-click copy of the full
requestId. List header bumped to 12px sans-serif uppercase + 9h height
for legibility.

Refs #1138, #1141, #1142
2026-04-30 13:37:02 -04:00
Tam Nhu Tran a10e570310 test(ui): cover logs page redesign with virtuoso-mocked render
Three component tests under jsdom: skeleton render, search filter wiring,
row-click -> detail-panel-header flow. react-virtuoso is mocked at module
level (vi.mock) so jsdom can render rows without layout measurements.
Production behavior unchanged.

Refs #1142, #1138
2026-04-30 13:04:21 -04:00
Tam Nhu Tran ae3240d4c8 feat(ui): redesign dashboard logs page with virtualized 3-pane shell
Replace the ornamental logs page (radial-gradient overlays, faux-HUD
captions, scale-on-click animations, two competing tab shells, zero
virtualization) with a calm, accessible, performant surface.

- Calm 48px header + tabs + 3-pane shell (filters | list | detail)
- List virtualized via react-virtuoso; trace-grouped rows by requestId
  with expandable per-stage timeline and per-stage latencyMs
- Filters split into primary (search / level / source) + advanced
  (module / stage / requestId / time-window) with 250ms debounce and
  in-flight react-query cancellation on filter change
- Live-tail pause / resume with rotation-safe id-set diff for the
  new-entries pill; visibility-aware polling
- Detail panel: Overview / Context / Raw tabs; Copy JSON, Copy requestId,
  Show-trace jump; redaction-aware rendering
- Designed empty / loading / error states (4 empty variants, retry on error)
- Keyboard nav (j / k / Enter / Esc / / / Space / ?) with visible focus
  rings; reduced-motion respected; AA-tuned level palette
- New header button for keyboard-shortcuts discoverability (alongside ?
  keypress) -- pairs with the shortcuts dialog
- Optional fields added to LogsEntry to consume the #1141 contract; UI
  ships against fixture (?mock=logs URL flag) so backend isn't a hard
  prerequisite. Dev-only assertLogsEntryShape warns once on drift
- Deleted dead logs-overview-cards.tsx (was unmounted)

Closes #1142
Refs #1138, #1141
2026-04-30 13:03:47 -04:00
Kai (Tam Nhu) TranandGitHub 54e15c430e Merge pull request #1140 from kaitranntt/kai/feat/1139-claude-quota-bars
feat(ui): clarify Claude quota bars
2026-04-30 10:58:11 -04:00
Tam Nhu Tran 476c6d827f fix(ui): improve split quota bar readability 2026-04-30 10:32:43 -04:00
Tam Nhu Tran a8b686d643 feat(ui): show split Claude quota bars 2026-04-30 10:05:19 -04:00
Tam Nhu Tran 804b1e80b4 chore: remove build artifacts accidentally committed 2026-04-29 17:20:11 -04:00
Tam Nhu Tran c3c8cb7a82 fix(cliproxy): exclude colocated tests from tsconfig and eslint
- Add src/**/__tests__/** to tsconfig.json exclude list
- Add ignores pattern for __tests__ in eslint.config.mjs
- Fix ui/src/lib/api-client.ts import path for provider-entitlement-types
- Remove stale build artifacts from ui/src/lib/ and tests/mocks/
2026-04-29 17:18:23 -04:00
Tam Nhu Tran 5013d411a7 feat(cliproxy): self-pause exhausted quota accounts 2026-04-28 20:35:15 -04:00
Kai (Tam Nhu) TranandGitHub a8e0d95bc8 Merge pull request #1117 from kaitranntt/kai/feat/1115-session-affinity
feat: add local CLIProxy session affinity controls
2026-04-28 13:37:37 -04:00
Tam Nhu Tran 5267c421ca fix(cliproxy): harden session affinity behavior 2026-04-28 13:27:05 -04:00
Kai (Tam Nhu) TranandGitHub f328cff870 Merge pull request #1113 from yousiki/fix/codex-additional-rate-limits
fix(cliproxy): parse Codex additional_rate_limits for Spark quota
2026-04-28 13:04:16 -04:00
Tam Nhu Tran 148cf35560 fix(cliproxy): polish session affinity controls 2026-04-28 12:38:30 -04:00
Tam Nhu Tran c277955762 fix(cliproxy): tighten session affinity review issues 2026-04-28 12:33:12 -04:00
Tam Nhu Tran 20f3916d55 fix(cliproxy): tighten session affinity status handling 2026-04-28 12:26:42 -04:00
Tam Nhu Tran 4309db89c3 feat(cliproxy): add local session affinity controls 2026-04-28 12:17:26 -04:00
yousikiandClaude Opus 4.7 0e602932f2 fix(ui): widen getCodexWindowKind parameter to accept CodexWindowSummary
The new helpers added in this PR (inferCodeReviewCadence,
getCodexWindowDisplayLabel) operate on CodexWindowSummary - a Pick subset
of CodexQuotaWindow without usedPercent/remainingPercent/resetAt. They
pass that subset into getCodexWindowKind, which was still typed as
string | CodexQuotaWindow, so tsc -b fails with TS2345 in CI. The
function only reads label/category/cadence, all of which exist on
CodexWindowSummary, so widening the parameter is safe and lets a real
CodexQuotaWindow continue to flow through unchanged.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-28 10:59:58 +09:00
Tam Nhu Tran b2e4be310d fix(ui): tighten analytics session and query contracts 2026-04-27 12:30:56 -04:00
yousiki a90aadd202 fix(cliproxy): tighten Codex window classification on incomplete metadata
Apply Copilot review feedback on PR #1113:
- getCodexWindowKind: require cadence alongside category; fall through
  to label sniffing when missing instead of silently defaulting to 5h.
- getCodexWindowDisplayLabel (CLI): for category 'additional', fall back
  through featureLabel -> window.label -> 'Additional' so partial cached
  windows preserve user-visible context.
2026-04-27 16:57:28 +09:00
yousiki 725d95b0d2 fix(ui): surface Codex Spark windows in dashboard quota views
Mirror the server-side CodexQuotaWindow metadata extension on the UI
side: api-client.ts gains category/cadence/featureLabel; utils.ts
breakdown returns additionalWindows so Spark quota does not pollute
core 5h/weekly badges; quota-tooltip-content renders Spark inline with
prettified labels (GPT-5.3-Codex-Spark -> Codex Spark). Adds breakdown
unit tests and i18n strings for English/Chinese/Vietnamese/Japanese.
2026-04-27 15:50:31 +09:00
Tam Nhu Tran e2f1363708 fix(analytics): correct token accounting and usage aggregation
Align snapshot handling and UI contracts with the current analytics payloads.
2026-04-26 15:21:52 -04:00
Tam Nhu Tran aec5267651 fix(ui/design-system): address PR-Agent round 2 on PR #1109
Three more issues raised by upstream review after the v1.7 fix push.
Each is encoded both in code and in spec so future drift can't
reintroduce them:

1. Required `storageKey` was the wrong fix for cross-page state bleed.
   Forcing every callsite to pass `storageKey` made future
   production-page migrations a build-breaking event for no real
   safety benefit. Better engineering: keep the prop optional and
   default to a key derived from `window.location.pathname` so each
   route gets its own localStorage slot automatically. Pathname-
   derived defaults are unique by construction; the cross-page bleed
   risk only exists for hardcoded shared keys, which the doc warns
   against. SSR-safe (falls back to a stable string when window is
   unavailable). Pages can still pass an explicit `storageKey` to opt
   out of pathname coupling (e.g. when sub-routes should share state).

2. `MaskedInput` `type` was overridable by callers, which defeated
   the component's purpose for credentials. Two-layer fix:
   - Compile-time: `MaskedInputProps` now extends
     `Omit<InputHTMLAttributes, 'type'>` so callers literally cannot
     pass `type`. The TS surface enforces the contract.
   - Runtime: the `type` attribute on `<Input>` is now placed AFTER
     the spread so even an `as`-cast bypass can't override it.

3. The reveal toggle was keyboard-inaccessible. Both `MaskedInput`
   and `Field` had `tabIndex={-1}` on the eye-icon button, removing
   it from the tab order. Keyboard-only users couldn't show or hide
   the secret. Resolution:
   - Drop `tabIndex={-1}` so the button joins the natural tab order.
   - Add `aria-pressed={revealed}` so screen readers announce toggle
     state.
   - Add a focus-visible accent ring so the focused state is visible
     without a mouse.

Decisions log: v1.8 entry records the rationale for each correction
and explicitly calls out that v1.7's "required storageKey" fix was
worse than the problem it tried to solve.

Validation: typecheck + lint + format + build clean. Tests 519/521
pass (2 pre-existing account-visual-groups failures on dev,
unrelated). Styleguide demos still pass storageKey explicitly so
behavior is unchanged for the only existing callsites.
2026-04-26 14:04:39 -04:00
Tam Nhu Tran 839997d08e fix(ui/design-system): address PR-Agent feedback on PR #1109
Three substantive issues raised by upstream review, encoded in code +
spec so future drift can't reintroduce them:

1. Width floor was unenforceable. The previous spec wording said
   "form ≥ 360px / json ≥ 320px" but `react-resizable-panels` v3 only
   accepts percentage `minSize`. On a 1280px viewport this could let a
   user drag a pane down to ~250px — well below the documented floor.
   - `Panel minSize` bumped 25 → 30 (≥ 30% of body width after rail)
   - Spec rewritten percent-based with the actual 300–360px range
     across realistic viewports plus a note on the v3 API constraint
     and the `onResize`-clamp escape hatch if hard pixel floors become
     necessary later.

2. `storageKey` default caused cross-page state bleed. The previous
   default `storageKey="ccs.config-layout"` meant any `<ConfigLayout>`
   without an explicit key would share localStorage state with every
   other Config page — split ratios contaminating across unrelated
   pages.
   - `storageKey: string` is now REQUIRED (no default). TypeScript
     compile-fails any caller that omits it.
   - Spec restated to make the per-page-key contract explicit.

3. Sensitive-field heuristic was too narrow. The previous regex
   `AUTH_TOKEN|API_KEY|SECRET|PASSWORD|PRIVATE_KEY` missed common
   secret names (ACCESS_TOKEN, REFRESH_TOKEN, BEARER_TOKEN,
   CLIENT_SECRET, CLIENT_ID, AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY,
   GCP/Azure/GitHub/OpenAI/Anthropic variants, JWT, OAUTH, CREDENTIAL,
   PAT, WEBHOOK_SECRET, HMAC_KEY, SIGNING_KEY, SSH_KEY).
   - New `src/lib/sensitive-label.ts` Single Source of Truth
     (`isSensitiveLabel(label)`) with broadened regex; case-insensitive
     and tolerant of `_`/`-` separators.
   - `Field` imports the shared helper; future consumers do too.
   - Spec §5g enumerates the new patterns and points at the SSoT.

Decisions log: v1.7 entry records the rationale and the connections
between spec wording and library API constraints, so the next reviewer
sees the trail rather than re-discovering it.

Validation: typecheck + lint + format clean; build clean; tests
519/521 pass (2 pre-existing account-visual-groups failures on dev,
unrelated). Styleguide demos already pass storageKey explicitly.
2026-04-26 13:50:47 -04:00
Tam Nhu Tran 53030891c6 feat(ui/design-system): codify rail-anchored layout invariants and palette
Adds a page-level design system spec for CCS dashboard Config pages,
without migrating any production page yet. Pure-spec PR: docs +
primitive treatments + DEV-only /_styleguide route. Production page
adoption is intentionally left for follow-up work, one page at a time.

Spec — `ui/docs/design-system.md`:

- §0 Layout invariants (NEVER VIOLATE): two-column shell, no second
  horizontal strip below the global topbar, sibling-pane top alignment,
  cliproxy as canonical reference, user-resizable form/json split, and
  content-fit unified rail (`w-fit min-w-[240px] max-w-[360px]`)
- §1 identity-strip patterns: HeroBar (home), rail-anchored (cliproxy),
  PageHeader (Monitor-only), Bespoke (health)
- §2 body archetypes: Config (rail + form + optional json) and Monitor
- §4 anti-patterns with rejected code samples (PageHeader-over-
  ConfigLayout, tab-bar offsetting json pane, redundant description
  band, blank vertical band)
- §5 color & accent rules using the existing Pampas/Crail palette only:
  1-accent-dot rule, sibling-pane differentiation, status pill spec,
  three-tier intensity ladder, sensitive-field treatment per §5g

Decisions log — `ui/docs/design-decisions.md`:

- v1.6: content-fit rail (replaces fixed 260px)
- v1.5: color & accent treatment for body panes
- v1.4: resizable form/json split
- v1.3: layout invariants and rail-anchored default for Config

Primitives — `ui/src/components/config-layout/`:

- ConfigLayout: rail rendered as content-fit `<aside>` within the
  unified envelope; form↔json split via react-resizable-panels (already
  in deps) with autoSaveId-driven per-page localStorage persistence
- FormPane: 1px Crail top strip on header + soft from-card gradient +
  inset bottom shadow for scroll-depth cue; body wash bg-muted/20 so
  FormSections read as elevated cards; footer bg-muted/40 anchors save
- FormSection: 2px Crail leading-edge stripe with three-tier intensity
  (default 30% / hover 70% / focus-within solid + ring + shadow) + 1.5px
  accent dot prefixing each title
- JsonPane: bg-muted/30 shell visually recesses the read-only pane vs
  the active form pane (bg-card); accent dot + status pill in header;
  inner <pre> sits inside an inset-shadow well; content stays plain
  (a dedicated JSON viewer with its own color coding will replace it
  later — pages MUST NOT add inline JSON tints in the meantime)
- ListPane: selected row uses three-tier intensity ladder (no stripe at
  rest, accent/30 stripe on hover, solid Crail stripe + bg-accent/10
  row tint + accent-tinted icon and count badge on selection)

Sensitive input primitive — `ui/src/components/ui/masked-input.tsx`:

Upgraded to match §5g: lock glyph prefixing label, "sensitive" accent
pill on the label row, accent focus ring (the only place the default
ring is overridden), accent-tinted reveal toggle on hover. Existing
consumers (settings-dialog, websearch, friendly-ui-section, env-editor-
section) inherit the treatment without code changes.

Shared Field — `ui/src/components/forms/field.tsx`:

Tiny self-documenting Field component referenced by /_styleguide demos.
Auto-detects sensitive labels (AUTH_TOKEN | API_KEY | SECRET | PASSWORD
| PRIVATE_KEY) and applies §5g treatment. Production pages can adopt
this as a follow-up; no production code currently consumes it.

Live styleguide — `ui/src/pages/_styleguide.tsx`:

DEV-only route (gated by import.meta.env.DEV in App.tsx) showcasing
every primitive in isolation plus composed Config + Monitor archetype
demos. Intro callout surfaces the §0 invariants. §1b/§2a/§2b demos are
canonical rail-anchored examples authors can copy. Demo content uses
realistic Anthropic-style env field sets so the visual density mirrors
the cliproxy reference.

What this PR does NOT do:

- No production page rewrites — `pages/{api,accounts,codex,copilot,
  cursor,droid,shared,updates,analytics,claude-extension,cliproxy-ai-
  providers}` are unchanged
- No test selector updates — the existing test suite passes (excepting
  the 2 pre-existing account-visual-groups failures already on dev)
- No HTML preview reports — the canonical visual reference is the live
  /_styleguide page
- No new dependencies — `react-resizable-panels` was already declared

Validation: typecheck + lint + format + build clean. Test suite green
(519/521 — 2 pre-existing dev failures unrelated to this PR).
2026-04-26 13:30:03 -04:00
Tam Nhu Tran 8911906e9f fix(codex): resolve PR review contract and i18n drift 2026-04-26 11:24:08 -04:00
Tam Nhu Tran 82c92aa940 feat(codex): align ccsxp with native cliproxy shortcut 2026-04-26 11:09:54 -04:00
Tam Nhu Tran e64060fe0d fix(ui): address PR-Agent round 2 on health redesign
PR-Agent flagged two more real regressions vs the original health page:

1. Stale timestamp (HealthStatusRibbon): formatRelativeTime reads
   Date.now() during render, but nothing forces a re-render after mount
   so the 'last scan' label froze and never advanced to '1 minute ago',
   '2 hours ago', etc. Restore the per-second tick (matching the
   original health.tsx behavior) so the label behaves like a real
   relative timestamp.

2. Hidden copy action (HealthPriorityCard): the fix-copy button was
   gated by group-hover, making it invisible to keyboard users and
   unreachable on mobile (no hover state). Make it always visible with
   an aria-label so screen-reader and touch users can copy the fix
   command.
2026-04-25 19:14:42 -04:00
Tam Nhu Tran 525c4f9812 fix(ui): address PR-Agent feedback on health redesign
PR-Agent flagged two real bugs at score 84:

1. Accessibility (HealthPriorityCard): the priority card header was a
   clickable <div> with onClick, making expand/collapse mouse-only.
   Promote it to a <button type="button"> with aria-expanded reflecting
   state and a contextual aria-label, and a focus-visible ring. The
   inner content is extracted into a shared fragment so the button and
   the no-content fallback <div> render the same markup.

2. Time display (HealthStatusRibbon): formatRelativeTime lost the hours
   branch from the original health.tsx, so a 2-hour-old scan showed as
   '120 minutes ago' instead of '2 hours ago'. Restore the hours
   rollover with health.hoursAgo i18n key.
2026-04-25 19:10:00 -04:00
Tam Nhu Tran dd9854bfd7 docs(ui): sync design system docs with bespoke health redesign
After the v1.1 restructure, health was further redesigned per a separate
brief and went bespoke -- new components (HealthStatusRibbon,
HealthPriorityCard, HealthPriorityList, HealthAuditSection), no
PageShell/PageHeader/MonitorLayout, custom severity-driven hierarchy.

Bring the docs in line:

- design-system.md: add §1d Bespoke as a fourth identity-strip pattern
  with health as canonical reference; remove health from PageHeader
  and Monitor canonical-reference slots (now TBD)
- design-decisions.md: v1.2 revision row capturing the redesign and
  the resulting doc cleanup
- screenshots-phase2/after-03-health.png: replace stale Monitor-archetype
  screenshot with the actual final redesigned page (Privacy mode ON)
- design-system-phase2-preview.html: regenerate; describe the four
  patterns, embed the new screenshot, update the file change list
2026-04-25 18:55:47 -04:00
Tam Nhu Tran 63198ab1ff docs(ui): add health redesign verification report and screenshots
Self-contained verification report at ui/docs/health-redesign/report.html
showing four states of the redesigned health page (initial, loading,
expanded issue, expansion fix). Screenshots at the same level so the
report's relative image refs resolve when opened directly.

Captures the design exploration delivered against the handoff brief
(plans/reports/handoff-260425-1417-health-page-redesign.md).
2026-04-25 16:29:47 -04:00
Tam Nhu Tran 90a3d330df fix(ui): polish health page background and priority card layout
Iteration on top of the health redesign:
- layout.tsx: drop backdrop-blur transparency on the global header
  (interfered with the new health page colored backdrop)
- health.tsx: switch from fixed full-viewport background to scoped
  absolute positioning; replace unreliable external grain SVG (vercel
  app) with an inline data URI that always loads
- health-priority-card.tsx: refine spacing, expansion behavior, and
  visual hierarchy
2026-04-25 16:29:16 -04:00
Tam Nhu Tran 4d314ef2b7 chore: resolve merge conflicts and integrate health page redesign 2026-04-25 14:48:40 -04:00
Tam Nhu Tran 36ed091e4d feat(ui): redesign health page with prioritized layout and premium aesthetics 2026-04-25 14:45:58 -04:00