Commit Graph
3634 Commits
Author SHA1 Message Date
Tam Nhu Tran b2e4be310d fix(ui): tighten analytics session and query contracts 2026-04-27 12:30:56 -04:00
Tam Nhu Tran e95b354297 fix(analytics): avoid bridge double counting in native collectors 2026-04-26 18:19:41 -04:00
Tam Nhu Tran fe7cc6f9ba fix(analytics): add native codex and droid collectors 2026-04-26 18:13:33 -04:00
Tam Nhu Tran 01fcd77573 fix(cli-proxy): preserve historical analytics across syncs 2026-04-26 16:52:12 -04:00
Tam Nhu Tran 69b75c0449 fix(cli-proxy): durable analytics history persistence and dedupe migration 2026-04-26 16:44:39 -04:00
Tam Nhu Tran 286b5781a7 fix: address analytics review follow-ups 2026-04-26 15:31:32 -04: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
github-actions[bot] 10cf27f63e chore(release): 7.74.0-dev.11 [skip ci] 2026-04-26 18:23:35 +00:00
Kai (Tam Nhu) TranandGitHub 7892d4fdc8 Merge pull request #1109 from kaitranntt/kai/feat/design-system-spec
feat(ui/design-system): codify rail-anchored layout invariants and palette
2026-04-26 14:20:10 -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
github-actions[bot] 78d80748f3 chore(release): 7.74.0-dev.10 [skip ci] 2026-04-26 17:39:43 +00:00
Kai (Tam Nhu) TranandGitHub a6d05a4826 Merge pull request #1108 from kaitranntt/kai/fix/ccsxp-argv0-codex-alias
fix(codex): resolve ccsxp argv alias as codex
2026-04-26 13:36:20 -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 cd941195d1 fix(codex): resolve ccsxp argv alias as codex 2026-04-26 13:19:58 -04:00
github-actions[bot] fa34ee82a1 chore(release): 7.74.0-dev.9 [skip ci] 2026-04-26 16:42:40 +00:00
Kai (Tam Nhu) TranandGitHub 4c99486710 Merge pull request #1106 from kaitranntt/kai/feat/ccsxp-native-cliproxy-parity
feat(codex): align ccsxp with native cliproxy shortcut
2026-04-26 12:39:12 -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
github-actions[bot] 74285ca9ec chore(release): 7.74.0-dev.8 [skip ci] 2026-04-26 14:21:08 +00:00
Kai (Tam Nhu) TranandGitHub db3becfbe0 Merge pull request #1104 from sgaluza/sgaluza/fix-test-mock-contamination
test(cliproxy): replace process-wide mock.module with DI seams
2026-04-26 10:17:45 -04:00
Sergey Galuza a7871283d4 test(cliproxy): replace process-wide mock.module with DI seams
Bun's `mock.module()` is process-wide and is NOT undone by `mock.restore()`.
Two test files used module-level mocks that leaked across test runs,
silently breaking unrelated suites that imported the mocked modules
transitively:

- tests/unit/cliproxy/version-checker-stale-cache.test.ts mocked
  binary/version-checker, contaminating cliproxy-stats-routes-install
  and cliproxy-stats-routes-model-update suites that import a route
  module which transitively imports version-checker.
- tests/unit/cliproxy/service-manager-startup.test.ts mocked 8 modules
  at top level (binary-manager, stats-fetcher, etc.), contaminating any
  later file that imports them.

CI happens to be green because file ordering on the GitHub runner places
victim files BEFORE contaminators in `bun test`. On Linux locally the
order is reversed, producing 6 reproducible test failures on every
`bun run test:fast` run. A change in OS, bun version, or new test files
that import the mocked modules could silently flip the CI runner's
order and surface these failures unexpectedly.

Replaced module-level mocks with explicit dependency-injection seams in
production code, following the existing pattern in version-checker.ts
and version-cache.ts (`fetchJsonFn?`, `fetchLatestVersionFn?`):

- src/cliproxy/types.ts: `BinaryManagerConfig.checkForUpdatesFn` (optional)
- src/cliproxy/binary/lifecycle.ts: route through new fn with default
- src/cliproxy/service-manager.ts: `ensureCliproxyService(deps?)` with
  optional `ensureBinaryFn`, `detectRunningProxyFn`,
  `configNeedsRegenerationFn`, `withStartupLockFn`

All deps fields are optional with real-implementation defaults, so
production callers are unchanged. Tests now inject deterministic stubs
through call-site parameters instead of module-level mocks.

Verified locally: \`bun run validate\` is now 2540 pass / 0 fail
(previously 2534 pass / 6 fail at the same upstream/main HEAD).

Built [OnSteroids](https://onsteroids.ai)
2026-04-26 09:43:09 +02:00
github-actions[bot] eb6c621217 chore(release): 7.74.0-dev.7 [skip ci] 2026-04-25 23:23:50 +00:00
Kai (Tam Nhu) TranandGitHub 5b2729ba10 Merge pull request #1089 from kaitranntt/kai/feat/design-system-references
feat(ui): bespoke health redesign + design-system docs (4 patterns)
2026-04-25 19:20:32 -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
Tam Nhu Tran ba4fbcf75f docs(ui): add HeroBar and rail-anchored demos to styleguide
The v1.1 design contract names three identity-strip patterns but the
styleguide previously only demoed PageHeader. Future contributors
visiting /_styleguide had no live preview of HeroBar or rail-anchored
identity, leaving the contract under-documented.

Add:
- 1a. HeroBar demo (canonical: home pattern) -- single-row dense hero
  with logo + title + version badge + 4 inline stat tiles, dotted
  background pattern, anonymized data
- 1b. Rail-anchored identity demo (canonical: cliproxy pattern) -- a
  Config 3-pane layout where the left rail carries brand + Quick Setup
  CTA + provider list + footer status, with NO top chrome
- 1c. PageHeader demo (existing, retitled with health pattern reference)

All demos use anonymized data (Provider A/B/C, Demo Dashboard, fake
metrics) so screenshots are safe to publish without Privacy mode.
2026-04-25 14:08:42 -04:00
Tam Nhu Tran d9332907cc docs(ui): drop redundant home/cliproxy screenshots from phase2 report
home and cliproxy were reverted to their original designs; before/after
screenshots would show identical content. Keep only health migration
screenshots and refocus the HTML report on the design-system docs
restructure (three identity-strip patterns).
2026-04-25 14:02:07 -04:00
Tam Nhu Tran 1a2fd848f0 refactor(ui): revert home/cliproxy migrations and restructure design system
User feedback caught a Phase 2 mistake: forcing home and cliproxy into
a one-size-fits-all PageShell + PageHeader chrome regressed density on
both pages.

- home: original 1-row hero (logo + title + version + 4 inline stats)
  was split into PageHeader + KpiRow, doubling vertical footprint
- cliproxy: original rail-anchored brand was duplicated by a top
  PageHeader, stealing ~80px from the 3-pane body

Resolution: revert both pages to dev-branch state, then restructure
ui/docs/design-system.md to extract THREE identity-strip patterns from
the canonical references rather than imposing one:

  1. HeroBar       (1-row dense)             -> home pattern
  2. Rail-anchored (no top chrome)           -> cliproxy pattern
  3. PageHeader    (title + description)     -> health pattern

Health stays migrated -- the Monitor archetype + PageHeader is a real
improvement there because the gauge + KPI row + group cards don't fit
the HeroBar pattern.

Future page migrations adapt to whichever identity-strip pattern fits
the page's content shape, not the other way around. design-decisions.md
gets a v1.1 revision row capturing the rationale.
2026-04-25 13:34:43 -04:00
Tam Nhu Tran c59622fabd fix(ui): restore click-through navigation on home KPI cards
PR-Agent caught a regression: pre-migration the four home stats had
onClick handlers navigating to /providers, /cliproxy, /accounts, and
/health. The Phase 2 migration dropped those handlers, removing the
shortcuts for all users.

Add an optional onClick prop to KpiCard. When supplied, the tile
promotes from a static <div> to a <button> with hover lift, keyboard
focus ring, and an accessible aria-label derived from the label.
Wire all four home KPIs back to their original navigation targets.
2026-04-25 13:18:44 -04:00
Tam Nhu Tran 339fcd81e6 docs(ui): add phase 2 visual preview with before/after screenshots
Self-contained HTML report at ui/docs/design-system-phase2-preview.html
with embedded base64 before/after screenshots so reviewers can scrub
the cliproxy / home / health migrations side-by-side without leaving
GitHub. Captured via Playwright with Privacy mode on; data anonymized.
2026-04-25 13:13:44 -04:00
Tam Nhu Tran 9a2c09357a feat(ui): migrate cliproxy page to design system Config archetype
Wrap master-detail layout in PageShell + PageHeader + ConfigLayout.
PageHeader carries the brand strip (Zap icon + CLIProxy title +
account-management description) and refresh button. The existing
sidebar (Quick Setup CTA, sectioned providers list, variants list,
ProxyStatusWidget, footer connection summary) becomes ConfigLayout's
left rail; ProviderEditor and EmptyProviderState become the form pane.

Pure layout migration: every sub-flow still mounts correctly --
QuickSetupWizard dialog, AddAccountDialog (URL action=auth deep-link),
AccountSafetyWarningCard for gemini/agy, provider selection persisted
to localStorage and URL deep-link, all bulk pause/resume/solo/delete
mutations untouched. ProviderEditor (which contains the form + raw JSON
internally) remains as a single 'form' slot.
2026-04-25 13:13:14 -04:00
Tam Nhu Tran 5e3e554693 feat(ui): migrate health page to design system Monitor archetype
Hero gauge + status copy moved into a MonitorCard variant=terminal,
preserving the ccs doctor / live-ops aesthetic per locked decision #4.

HealthStatsBar replaced by a 4-column KpiRow with severity tones
(Checks / Passed / Warnings / Errors). Each HealthGroupSection becomes
its own MonitorCard while keeping the existing accordion behavior
intact -- so check expansion, sort-by-severity, and live monitoring
continue to work unchanged.

PageHeader carries the version badge, last-scan relative time,
auto-refresh hint, ccs doctor copy action, and refresh button.

Pure layout + visual hierarchy migration -- no new metrics, no new
features. LOC: 263 -> ~245.
2026-04-25 13:12:41 -04:00
Tam Nhu Tran 3ae5f20dc8 feat(ui): migrate home page to design system Monitor archetype
Wrap in PageShell + PageHeader + MonitorLayout. Replace bespoke hero
stat row with KpiRow + 4 KpiCards (profiles, cliproxy, accounts, health
with status-derived tone). AuthMonitor and ErrorLogsMonitor become
MonitorCard children; the 'logs moved' callout becomes a MonitorCard.

Pure layout migration -- behavior preserved (navigation, configuration
warning Alert, hooks unchanged). Hero punch retained via tone-coded KPI
cards. LOC: 208 -> 173.
2026-04-25 13:12:08 -04:00
github-actions[bot] b868852e63 chore(release): 7.74.0-dev.6 [skip ci] 2026-04-25 16:58:28 +00:00
Kai (Tam Nhu) TranandGitHub 283f866897 Merge pull request #1088 from kaitranntt/kai/feat/design-system-foundations
feat(ui): add design system foundations (PageShell + ConfigLayout + MonitorLayout)
2026-04-25 12:54:57 -04:00
Tam Nhu Tran 6261e150e3 fix(ui): address PR-Agent round 3 review on ConfigLayout duplicate DOM
Render exactly one layout at a time -- desktop 3-pane grid OR mobile
tabs -- via a useIsDesktop matchMedia hook gated at the lg breakpoint.

Previously the component rendered BOTH and toggled visibility via
Tailwind 'hidden lg:grid' / 'lg:hidden'. This duplicated FormSection
elements in the DOM, so document.getElementById() in SectionRail's
scroll-spy and click-to-jump would resolve to the (hidden) desktop copy
first on mobile -- making the rail attach to the wrong scroller and
never update the active section.
2026-04-25 12:48:06 -04:00
Tam Nhu Tran caef89a7aa fix(ui): address PR-Agent round 2 review feedback
MonitorLayout:
- Wrap ScrollArea in an explicit flex-col container with min-h-0. The
  previous version relied on the parent (PageShell) being flex-col for
  flex-1 to compute a definite height; that's fragile. The wrapper now
  establishes its own flex column so the ScrollArea reliably scrolls
  whether or not the caller's wrapper is flex.

ConfigLayout (MobileTabs):
- Derive active tab during render with fallback to first available, same
  pattern applied to JsonPane in the previous round. Parent toggling
  `left` or `json` props could change the available tabs and leave
  the controlled Tabs value pointing at a tab id that no longer existed,
  rendering an empty pane on mobile until the user manually switched.
2026-04-25 12:43:41 -04:00
Tam Nhu Tran 37de42d463 fix(ui): address PR-Agent review feedback on JsonPane and SectionRail
JsonPane:
- Reset active tab when the tabs prop changes. Previously activeTab was
  initialized once and never re-synced; a parent swapping the tab set
  (e.g. selecting a different entity) could leave us pointing at a stale
  id, leaving the pane empty and Copy returning '{}'.
- Force textarea remount on data change in editable mode via key={text}.
  Uncontrolled defaultValue retained the prior value when the underlying
  config changed, so onBlur could save stale text.

SectionRail:
- Default scroll-spy root to the nearest scrollable ancestor instead of
  the page viewport. FormPane wraps its body in a shadcn ScrollArea, so
  IntersectionObserver with root:null was watching the wrong scroller and
  the active section never updated as the form scrolled.
- observeRoot prop still wins when supplied for explicit overrides.
2026-04-25 12:36:58 -04:00
Tam Nhu Tran e0d2ab0444 docs(ui): add design system visual preview + screenshots
Self-contained HTML report at ui/docs/design-system-preview.html with
embedded base64 screenshots so the PR review and the report itself remain
viewable without external hosting. Companion to the live /_styleguide
route.

Screenshots captured with Privacy mode ON via Playwright; all data
anonymized (Provider A/B/C, fake metrics) -- no real account names,
emails, or OAuth identifiers.
2026-04-25 12:21:33 -04:00
Tam Nhu Tran 64e78f6e69 feat(ui): add design system foundations
Introduce two locked page archetypes -- Config (3-pane) and Monitor
(KPI row + 12-col grid) -- wrapped by shared PageShell + PageHeader.

Primitives:
- page-shell/: PageShell, PageHeader, EmptyState, ErrorState
- config-layout/: ConfigLayout, ListPane, SectionRail, FormPane,
  FormSection, JsonPane (read-only by default, opt-in editable)
- monitor-layout/: MonitorLayout, KpiRow, KpiCard, MonitorGrid,
  MonitorCard (incl. variant="terminal")

Single ConfigLayout component, prop-controlled left rail:
- ListPane for multi-entity (cliproxy, accounts, providers)
- SectionRail for single-entity (codex, copilot, cursor, droid)
  with IntersectionObserver scroll-spy
- omit for none

DEV-ONLY /_styleguide route gated by import.meta.env.DEV. Shows every
primitive plus composed Config (multi + single) and Monitor demos using
fully anonymized data (Provider A/B/C, fake metrics).

Phase 1 of dashboard design system unification. Existing pages untouched
this phase -- migrations land in Phase 2+.

Locked decisions in ui/docs/design-decisions.md:
1. In-app /_styleguide over Storybook
2. Archetype name: Monitor (not Dashboard)
3. JsonPane read-only by default
4. Health terminal aesthetic kept as variant
5. i18n per-page namespaces
6. SectionRail uses scroll-spy
2026-04-25 12:16:27 -04:00
github-actions[bot] ba63758e8c chore(release): 7.74.0-dev.5 [skip ci] 2026-04-25 16:10:07 +00:00
Kai (Tam Nhu) TranandGitHub 5dd5878bb4 Merge pull request #1086 from BlackJulySnow/main
feat: add extra models support for API profiles
2026-04-25 12:07:01 -04:00
Tam Nhu Tran bc16c5168c fix(ui/profiles): latch extraModels touch state for explicit-clear
The previous fix used react-hook-form's `dirtyFields.extraModels` to
gate forwarding of the empty value. RHF compares each value to its
`defaultValues` entry, so typing "x" and erasing back to "" reverts
the dirty flag to false — making it impossible to clear a saved
ANTHROPIC_EXTRA_MODELS through the dashboard.

Track a local boolean that latches true on the first onChange and
resets when the dialog closes. The latch survives a "type then
delete" round-trip, so the empty-string delete signal still reaches
the server, while a never-touched field still gets skipped to
preserve the existing saved value.
2026-04-25 12:00:50 -04:00
Tam Nhu Tran 9122f68dd4 fix(ui/profiles): preserve ANTHROPIC_EXTRA_MODELS on untouched edit-dialog save
The edit dialog cannot pre-populate `extraModels` from the existing
profile (the Profile type carries no env data), so the field always
opens blank in edit mode. Forwarding that blank value unconditionally
clobbered any saved ANTHROPIC_EXTRA_MODELS, since the PUT route treats
an empty string as a delete signal.

Track react-hook-form's `dirtyFields.extraModels` and only include the
field in the update payload when the user actually touched it. Typing
then clearing still marks the field dirty, so the explicit-clear UX
keeps working.
2026-04-25 11:45:51 -04:00