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.
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).
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
Add ANTHROPIC_EXTRA_MODELS env var to allow each API profile to
configure additional models alongside the primary ANTHROPIC_MODEL.
These are synced to CLIProxy config.yaml during ccs cliproxy sync.
- profile-mapper.ts: parse ANTHROPIC_EXTRA_MODELS on sync
- profile-writer.ts: write extra models to settings.json
- shared.ts + create-command.ts: --extra-models CLI flag
- profile-routes.ts + route-helpers.ts: REST API support
- profile-dialog.tsx + api-client.ts: frontend input