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

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/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.