8.2 KiB
UI patterns
Use the shared components in src/lib/ui/components to keep pages consistent.
Primitives live in its primitives directory.
Page structure and controls
- Use
SectionHeaderfor page titles and primary actions. The page owns spacing between sections. - Use
FieldwithInput,SelectorTextareafor labels, help and errors. Give controls distinctariaLabelvalues when several share one field. UsehiddenLabelonFieldfor compact filters with a visually hidden label.InputandSelectforward native validation, accessibility and event props; usebind:elementwhen a workflow needs to focus the native control.Selectaccepts eitheroptionsor childoption/optgroupelements and preserves the bound value's type. Keep layout on the field or its parent; controls own their appearance. npm run check:stylesrejects raw selects and text-like inputs outside their primitives, including search, password, numeric and date/time fields. Native checkboxes, radios, file pickers, hidden inputs, range/color pickers and input buttons are allowed. The check parses Svelte markup, so comments and examples in scripts are ignored.- Pair
Tabswith a labeled panel. UseListFooterfor long lists. - Use
StatusBadgefor workflow states andServerIdentityfor server names. Keep raw IDs available for copying and troubleshooting.
Dialogs and feedback
Use Dialog for modal workflows and confirmAction from src/lib/ui/confirm.ts
for ordinary confirmations. Browser unload protection remains native.
Keep loaded data visible when a refresh fails, show the error and provide a retry action. Distinguish loading, empty and failed states so operators know whether to wait or act.
Live data
Use pollVisible from src/lib/ui/poll.ts for scheduled page refreshes. It avoids
overlapping requests, pauses while the page is hidden and returns a cleanup
function for use when the component unmounts.
Read live server data through src/lib/ui/stores/fleet.ts instead of adding
another /api/servers polling loop. Server detail views use a scoped Fleet store
so their snapshots do not replace the shared fleet.
Accessibility and small screens
Preserve visible keyboard focus, associated field labels and separate navigation and action targets. Avoid nested buttons or placing a button inside a link. Allow long server identities to wrap and tab/filter rows to scroll on narrow screens.
Responsive widths
Use the shared breakpoint scale in src/lib/ui/styles/tokens.css:
| Token | Width | Use |
|---|---|---|
--breakpoint-xs |
360 px | Extra-small screen adjustments |
--breakpoint-sm |
640 px | Compact controls, cards and page summaries |
--breakpoint-md |
960 px | Sidebar switches to the navigation drawer |
--breakpoint-lg |
1100 px | Dense, multi-column layouts reflow |
CSS custom properties cannot be used inside media conditions. Write the literal
value, for example @media (max-width: 640px), and choose the shared width that
fits the layout. Related page and component changes should use the same width.
For JavaScript behavior, use the identical query and invert matches when you
need its complement; avoid adding a pixel to the boundary.
npm run check:styles reads this scale and rejects other widths in Svelte and
CSS @media queries. The file-and-width allowlist in scripts/check-ui-styles.mjs
records existing layouts awaiting migration. Remove entries as those layouts
move to the shared scale; do not add exceptions for new components. The app
shell, Servers page and server cards use only shared widths.
Navigation
Use the shared navigation metadata for Fleet, Releases, Activity, Players, and Admin. Campaigns stays under Releases beside Scripts, Configs, Plugins, Game files, and Betas. The Servers page is the Fleet landing page. Desktop navigation uses a collapsible sidebar; mobile navigation uses a modal drawer.
Use Cmd/Ctrl K to search pages, servers, hosts, players, and recorded matches.
Common actions open their existing forms and retain role checks and confirmations.
Keyboard shortcut help is available from the account menu and with ?.
Detail pages use Breadcrumbs. Its parent link restores the validated returnTo
URL, including filters, sorting, pagination, and anchors. Supply fallback when
the default parent also needs filters. Do not add a duplicate Back link.
Activity history
Use ActivityLog for the fleet timeline and scoped history. The server History
tab fixes the server filter; Maintenance fixes the maintenance/recovery source.
The API counts and pages retained records together, with stable ordering. Parent
operations represent their host receipts and maintenance verification so a single
user action does not appear multiple times. Routine successful host checks stay
in the technical Host operation history. Keep detail links contextual so their
parent breadcrumb restores filters and pagination.
Type and numbers
Use the shared --text-* scale, with 12 px as the minimum. Mobile inputs use
--text-input (16 px) to avoid browser zoom. npm run check:styles, also included
in npm run check, rejects raw pixel font sizes. Use Inter with tabular figures
for counts, scores, durations and charts; reserve mono for code, IDs, paths and
logs. Static values use text or state colors; the accent marks links and controls.
Product language
| Concept | Visible label |
|---|---|
| A managed game instance | Server; use the friendly map and lobby name |
| Creating a server | Create server |
| Removing a server | Remove server |
| Favourite server | Pin |
| Game does not answer | Not responding |
| Games | Black Ops I / Black Ops II; BO1 / BO2 in compact labels |
| Releasing file changes | Campaigns |
| Player completion evidence | Present in final report / Not present in final report |
Keep internal values such as t5, t6, destroy, instance IDs, and filenames in
API fields and technical details. User-facing actions use the labels above.
Player presence in the final report does not establish a win or continuous
survival. A missing timestamp is Not recorded, not Never.
Use RelativeTime for observed history: seconds, minutes or hours for the last
24 hours, Yesterday for the preceding calendar day, then the shared absolute
format. It includes the full timestamp in a tooltip and follows the account's
timezone. Scheduled future actions use timeFormat.dateTime directly. Database
timestamps without an offset are UTC; never parse them as browser-local time.
Receipt freshness uses the browser clock and shows Paused or a retry reason
when updates are unavailable. These clocks are not live regions.
Provisioning detail pages show the requester, absolute start time and duration
in the summary. Keep the finish time, raw IDs and copy controls in collapsed
Technical details. Show freshness only while the operation is active. Poll active
operations and finished operations whose recovery capacity is still held.
Use OperationTime for steps, checks and activity: offsets from
the operation start (+m:ss or +h:mm:ss), with negative offsets for earlier
evidence. Missing timestamps stay Not recorded.
Use plural or countLabel from src/lib/ui/plural.ts for count-dependent
language, including verbs: plural(count, 'needs', 'need'). The helper uses
Intl.PluralRules; supply irregular plural forms explicitly. Keep separators
inside string expressions when a Svelte conditional would trim their leading
space, for example {offline ? ' · Offline' : ''}.
Recommend an agent upgrade only when its reported version is known to lack a needed capability. Missing calibration samples can simply mean the host is still collecting measurements.