f99010fae1
CI / lint (push) Failing after 1s
CI / frontend (push) Failing after 1s
CI / scripts (push) Failing after 1s
CI / Go Test (ubuntu-latest) (push) Failing after 0s
CI / frontend-node-25 (push) Failing after 1s
CI / docs (push) Failing after 0s
CI / coverage (push) Failing after 0s
CI / e2e (push) Failing after 0s
Docker / build-and-push (push) Failing after 1s
CI / integration (push) Failing after 4m43s
CI / Go Test (windows-latest) (push) Has been cancelled
CI / Desktop Unit Tests (Windows) (push) Has been cancelled
Desktop Artifacts / Desktop Build (Linux (arm64)) (push) Has been cancelled
Desktop Artifacts / Desktop Build (Linux) (push) Has been cancelled
Desktop Artifacts / Desktop Build (Windows) (push) Has been cancelled
Desktop Artifacts (macOS) / Desktop Build (macOS (aarch64)) (push) Has been cancelled
Desktop Artifacts (macOS) / Desktop Build (macOS (x86_64)) (push) Has been cancelled
117 lines
6.2 KiB
Markdown
117 lines
6.2 KiB
Markdown
# Design System
|
|
|
|
agentsview is a dense local-first product UI. The interface should use a small,
|
|
consistent component vocabulary rather than one-off styling for each new
|
|
control.
|
|
|
|
## Component Rules
|
|
|
|
- Before adding any interactive frontend control, search
|
|
`frontend/src/lib/components` for an existing component that already covers
|
|
the behavior.
|
|
- Prefer existing shared components over native controls with local CSS.
|
|
- Do not add new hand-styled native `<select>` controls. Use the shared
|
|
typeahead/combobox components for single-choice selectors unless there is a
|
|
documented reason the native control is required.
|
|
- Do not introduce component-specific control chrome such as `.foo-select`,
|
|
manual select chevrons, or one-off input/button height and padding rules
|
|
when a shared control or token should own the styling.
|
|
- If a new control pattern is genuinely needed, add or extend a shared component
|
|
first, then use it from feature components.
|
|
|
|
## Internationalization
|
|
|
|
- Treat user-facing frontend UI copy as part of the component contract. New
|
|
headings, labels, button text, placeholders, tooltips, aria labels, empty
|
|
states, loading states, and error messages should go through the locale
|
|
dictionaries instead of hard-coded English strings.
|
|
- Source messages live in `frontend/messages/{locale}.json`. Use flat,
|
|
lowercase, underscore-separated Paraglide message keys. Group ownership with
|
|
prefixes instead of nesting, for example `settings_language_title` or
|
|
`sidebar_filters_starred_only`.
|
|
- In Svelte components, import generated messages through the local facade:
|
|
`import { m } from "../../i18n/index.js";`. Render static messages as
|
|
`m.some_key()` and parameterized messages as `m.some_key({ value })`. Adjust
|
|
the relative import path for the file location.
|
|
- Do not import `svelte-i18n` or use `$_(...)`. Locale changes use Paraglide's
|
|
normal reload behavior, so components should call generated message
|
|
functions directly.
|
|
- Do not edit files under `frontend/src/lib/paraglide` by hand. They are
|
|
generated by the Paraglide Vite plugin or by `npm run i18n:compile`, and
|
|
`npm run check` regenerates them before running `svelte-check`.
|
|
- Keep every supported locale file in sync when adding, removing, or renaming a
|
|
key. The English locale should preserve the source copy, and translated
|
|
locales should provide the best available translation in the same change.
|
|
- Shared components should accept translated labels, placeholders, titles, and
|
|
aria text from their callers. Avoid hiding English copy inside reusable
|
|
controls unless the copy is internal-only and never user-visible.
|
|
- Do not translate technical identifiers such as agent names, model names, CLI
|
|
flags, API paths, config keys, file paths, command snippets, user session
|
|
content, or exported data formats.
|
|
- Prefer parameterized locale messages for dynamic sentences instead of
|
|
assembling user-visible strings from translated fragments.
|
|
- Count-based user-facing messages with count-sensitive nouns must use Paraglide
|
|
plural variants instead of hard-coded strings such as `{count} sessions` or
|
|
`{count} calls`. Pass a numeric `count` for plural selection, and pass a
|
|
separate formatted label such as `countLabel` when the displayed value needs
|
|
locale formatting.
|
|
- Locale selection is preference-first, not URL-as-source. Paraglide is
|
|
configured with `["localStorage", "preferredLanguage", "baseLocale"]`
|
|
because the frontend is an embedded SPA without localized routes.
|
|
|
|
## Current Shared Controls
|
|
|
|
Most shared controls come from the `@kenn-io/kit-ui` library (imported as
|
|
`import { ... } from "@kenn-io/kit-ui"`): Button, Chip, CopyButton, EmptyState,
|
|
FilterDropdown, FindBar, IconButton, KbdBadge, Modal, RangePicker,
|
|
RefreshControl, SegmentedControl, Spinner, StatusBar, StatusDot,
|
|
Table/TableHeaderCell, TextInput/SearchInput, Tooltip, TopBar, and Typeahead.
|
|
Shared components take pre-translated strings as props — call `m.*()` at the
|
|
call site (or in a thin app wrapper) and pass the result.
|
|
|
|
App-level glue that remains local:
|
|
|
|
- `frontend/src/lib/components/layout/ProjectTypeahead.svelte` wraps kit-ui
|
|
`Typeahead` for project selection. Use kit-ui `Typeahead` directly for other
|
|
bounded option lists.
|
|
- `frontend/src/lib/components/shared/RangePicker.svelte` is a thin wrapper over
|
|
kit-ui `DateRangePicker` that injects localized strings, the app locale, and
|
|
maps the app's selection API; date-range resolution stays in
|
|
`frontend/src/lib/components/shared/rangeSelection.ts`.
|
|
- `frontend/src/lib/components/shared/RefreshControl.svelte` wraps kit-ui
|
|
`RefreshControl`, injecting the localized age formatter and app locale.
|
|
- kit-ui components with a `locale` prop (DateRangePicker, RefreshControl,
|
|
Calendar) should receive `locale={getLocale()}` from the i18n facade so
|
|
their date formatting follows the app language setting instead of the
|
|
browser locale; the shared wrappers above do this.
|
|
- `frontend/src/lib/components/content/SessionFindBar.svelte` wires kit-ui
|
|
`FindBar` to the in-session search store.
|
|
- `frontend/src/lib/components/settings/SettingsSection.svelte` owns settings
|
|
section framing.
|
|
|
|
Relative date ranges follow kit-ui semantics: "Last N days" spans N calendar
|
|
days inclusive of today. `presetRange()` (dateRangeSelector.ts) and
|
|
`rollingRange()` (utils/dates.ts) must stay consistent with each other and with
|
|
kit-ui's `RangePicker`, which seeds its Custom tab from the same math.
|
|
|
|
### Known kit-ui gaps (tracked upstream)
|
|
|
|
These gaps ship with the current kit-ui pin and are resolved by adding props
|
|
upstream, then bumping the commit hash in `frontend/package.json` and threading
|
|
the new props through the app wrappers. Do not work around them with unsupported
|
|
props or forked styles.
|
|
|
|
- `Modal`'s close-X `aria-label` is hardcoded English; needs a label prop.
|
|
- `TopBar` cannot express "no active tab"; on routes that are not tabs
|
|
(settings), the first tab renders as current.
|
|
|
|
Resolved upstream (adopted at the current pin): `RefreshControl` takes a
|
|
`formatAge` prop, `DateRangePicker`'s `weekOfLabel` substitutes a `{date}`
|
|
placeholder, and both take a `locale` prop — the shared wrappers inject all
|
|
three.
|
|
|
|
## Legacy Exceptions
|
|
|
|
Some existing activity components still contain native `<select>` controls and
|
|
manual control CSS. Treat those as legacy debt, not precedent for new work.
|