6.2 KiB
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/componentsfor 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 examplesettings_language_titleorsidebar_filters_starred_only. - In Svelte components, import generated messages through the local facade:
import { m } from "../../i18n/index.js";. Render static messages asm.some_key()and parameterized messages asm.some_key({ value }). Adjust the relative import path for the file location. - Do not import
svelte-i18nor 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/paraglideby hand. They are generated by the Paraglide Vite plugin or bynpm run i18n:compile, andnpm run checkregenerates them before runningsvelte-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} sessionsor{count} calls. Pass a numericcountfor plural selection, and pass a separate formatted label such ascountLabelwhen 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.sveltewraps kit-uiTypeaheadfor project selection. Use kit-uiTypeaheaddirectly for other bounded option lists.frontend/src/lib/components/shared/RangePicker.svelteis a thin wrapper over kit-uiDateRangePickerthat injects localized strings, the app locale, and maps the app's selection API; date-range resolution stays infrontend/src/lib/components/shared/rangeSelection.ts.frontend/src/lib/components/shared/RefreshControl.sveltewraps kit-uiRefreshControl, injecting the localized age formatter and app locale.- kit-ui components with a
localeprop (DateRangePicker, RefreshControl, Calendar) should receivelocale={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.sveltewires kit-uiFindBarto the in-session search store.frontend/src/lib/components/settings/SettingsSection.svelteowns 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-Xaria-labelis hardcoded English; needs a label prop.TopBarcannot 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.