Files
wehub-resource-sync 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
chore: import upstream snapshot with attribution
2026-07-13 12:30:36 +08:00

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.