@rubriclab/rubrot-ui
v0.9.2
Published
Rubric's design system — dark, dense, Linear-grade tokens and primitives for Next 16 + Tailwind v4
Keywords
Readme
@rubriclab/rubrot-ui
Rubric's design system and frontend platform — dark, dense, Linear-grade tokens and primitives shared by the fleet (rubrot, metal, vault, radar). Ships as TS source; consumers transpile it.
Adopting in an app
bun add @rubriclab/rubrot-ui(workspace apps:"@rubriclab/rubrot-ui": "workspace:*").next.config.ts:transpilePackages: ["@rubriclab/rubrot-ui"].app/globals.css:@import "tailwindcss"; @import "@rubriclab/rubrot-ui/theme.css"; @source "../node_modules/@rubriclab/rubrot-ui/src"; /* optional brand re-tint: */ @theme { --color-brand: #5e6ad2; --color-brand-fg: #ffffff; }PWA (optional, all pieces independent):
app/manifest.ts→export default () => defineManifest({ name: "Vault" })app/sw.js/route.ts→export const GET = createServiceWorkerRoute({ version: "shell-v1", offlinePath: "/offline.html" })(from@rubriclab/rubrot-ui/pwa/server)app/api/pwa/version/route.ts→export const dynamic = "force-dynamic"; export const GET = createPwaVersionRoute({ killEnvVar: "VAULT_PWA_KILL" })- layout:
<PwaProvider />(+<InstallPrompt appName="Vault" />if wanted)
SSE routes:
sseResponse+envelopefrom@rubriclab/rubrot-ui/live/server; the client end isuseEventStream+StreamStatusChip.Harness v2 UI (optional): install
@rubriclab/rubrot-harness, import the components from@rubriclab/rubrot-ui/harness, and import their protocol types from@rubriclab/rubrot-harness/protocol.
Rules the package holds itself to: semantic tokens only (its own ui-tokens
test rejects raw Tailwind palette classes), files ≤250 lines, one meaning per
color, focus-visible rings on every interactive element, motion via the
ui-* keyframes with a global reduced-motion kill switch.
Components
- Atoms —
avatarbadge(+BadgeTone)buttoncardempty-statenoticebanner(bordered callout)skeleton(+LogSkeleton)skeletons(route-shell kit)spinner(+FeedLoader)status-dotping-dotlive-pulsevault-badge(+lifecycle status)toggle - Forms —
field(Field/FieldStack/Input/Select/Textarea)image-inputinline-renameselect-listtag-inputenv-draft-editor(masked env drafts + paste-a-.env preflight)use-draft-state(sessionStorage dialog drafts; pair with RadixonInteractOutsideprevented while dirty) - Actions —
use-action(useAction/requestJson)action-error(actionErrorText) - Overlays —
dialogaction-menuconfirm-dialogedit-dialogcopy-once(CredentialsOnceDialog/CopyRow)command-palette(+ purecommand-palette-data+use-command-menuwrapper hook)set-in-vault-dialog(SetInVaultDialog/SetInVaultButton)step-up-reveal - Copy —
copy-buttoncopy-link-button - Rows & layout —
entity-rowresource-rowsection-cardtablemeta-pillfilter-tabspage-headerauth-frameteam-rosterpanel(+PanelFooterButton)section-label(the one uppercase eyebrow)segmented-control(the pill segment picker)figure(inline 13px stat)code-chip(mono one-time code + CopyButton) - Settings —
mail-settings-form(one ResourceRow mail card: set-in-vault → connect → reconnect, test/disconnect in the…menu)settings-modal(SettingsModal/SettingsSection: the wide sticky-header scaffold — settings live in a modal, not a page)setting-row(SettingRow: Linear-style inline edit — click the value, save on blur/Enter through injectedonSave, busy/error in place; clamped textarea for long text) - Content —
markdowndisclosurejot-list(dense collapsed-by-default expandable rows: glyph · clamped title · timestamp · trailing slot)log-view(filter/problem jumps/wrap/copy/stick-to-tail)run-tree(strict-contract v1 run journal) - Charts —
time-series-chart(multi-series time x-axis, null gaps, stacked mode, shared-crosshair tooltip,--color-chart-1..6tokens)breakdown-bar(100% share bar + legend)range-tabs(24h/7d/30d window)trend-chartsparklinespark-barsfunnelstat-tile(+delta/spark/subline) (sharedchart-math+ internalchart-parts) - Live —
live/use-event-streamlive/stream-status-chiplive/snapshot-cachelive/agent-live(createAgentLive/envelopeData: one shared EventSource in context)live/activity-face(the ticking/exploring/idle/offline vocabulary)live/agent-presence(ambient header chip)live/agent-rail(the Home command rail + drill-down) live activity components; server:live/server(sseResponse/envelope) - Harness (v2) — the
@rubriclab/rubrot-ui/harnesssubpath exportsHarnessRuntimeStatus,HarnessRuntimeControls,HarnessFreshness,HarnessAccounts,HarnessUsage,HarnessRateLimits, andHarnessSkills. Controls emit canonical revisioned mutations; the Skills surface binds directly to the canonical client and preserves authoritative desired/effective state through reconnects without an application mirror. - Timeline — the
@rubriclab/rubrot-ui/timelinesubpath exports the boundedTimelinefeed,TimelineRunDeepView, compactSourceChips, safeSourceLink,buildRunDeepTree, and their strict data/prop types and Zod contracts. Apps pass the canonical projected page/deep models directly; there is no renderer adapter. - Keyboard —
keyboard/kbdkeyboard/keyboard-nav(g-then-key + shortcuts overlay) - PWA —
pwa/register(PwaProvider)pwa/install-promptpwa/manifest(defineManifest); server:pwa/server(createServiceWorkerRoute/createPwaVersionRoute) - Utils —
utils(cn/formatCompact/copyText)datesdotenv(the one KEY=value parser)humanizescrubfile-kind
New in 0.3.0
Dataviz + the settings-modal primitives, built for the Wave-1 surface rebuilds (control room, cockpit, settings modal):
- TimeSeriesChart — 2–6 series on a real time axis (day/hour ticks from
the span), null-gap honesty, optional stacking, client-only shared
crosshair + tooltip (SSRs clean without it), TrendChart-idiom delta badge.
Series colors are the new
--color-chart-1..6theme tokens — a CVD-validated categorical order (worst adjacent pair ΔE 65.6, all six ≥ 3:1 on--color-card); identity only, never state. - RangeTabs — controlled 24h/7d/30d window selector (FilterTabs look, buttons not hrefs); one row scopes every chart below it.
- StatTile v2 — back-compatible
delta(tone = direction × goodness) and inlinesparksparkline. - BreakdownBar — 100% share bar + inline legend for per-provider / per-billing-method splits; color follows the entity via explicit slots.
- SettingsModal / SettingsSection / SettingRow — the D6/D7 settings
surface: wide dialog, sticky compact header, hairline sections, and the
inline-edit row (input/select/textarea variants, async save on blur/Enter
via injected
onSave,clampedoverflow textarea). - JotList — the compact log-row shape on Disclosure.
Promotions in 0.2.0
Fleet-proven components, generalized on the way in:
- SetInVaultDialog / SetInVaultButton (vault canonical + radar's launcher
deltas) — the vault-API coupling became an injected
onSubmit(values); info rows unify vault'sinfoRowsand radar'shinton CopyRow; the button degrades to afallbackHrefdeep link when in-app writes aren't provisioned. - MailSettingsForm (vault + radar unified on the ResourceRow shape) —
app wiring injected:
connectHref,setInVaultaction slot,onTest/onDisconnectreturning the note to show; vault's sending-as override kept viastatus.sendingAs. - TeamRoster (vault + radar) — read-only divide-y roster; per-member
state via
tone, everything app-specific in theactionsslot. - useDraftState + EnvDraftEditor + dotenv (metal) — sessionStorage
draft semantics preserved verbatim; the zod coupling became the structural
DraftContract(any zod schema still passes as-is). - Agent live rail (radar) —
createAgentLive<T>()keeps the wire shape app-side behindparse; AgentRail/AgentPresence render injected facts/counts/faces. - StepUpReveal (vault) — the WebAuthn ceremony became the injected
verify()resolving{ value } | { noPasskey }; the component owns only the step-up UX. - useCommandMenu (rubrot + metal wrappers) — ⌘K toggle, g-chords,
per-open refresh,
openCommandMenu()event, and the platform hint.
Export contract
Button, Field (with Input/Textarea), Dialog, Notice, Spinner,
Badge, Card, EmptyState are stable names other agents/apps code
against. Server-only factories are deliberately NOT in the root export:
import them from @rubriclab/rubrot-ui/live/server and @rubriclab/rubrot-ui/pwa/server.
Tests
bun test packages/ui — token guard, palette filtering, action error
mapping, chart math (incl. time ticks/stacking/gaps), a TimeSeriesChart
SSR-without-a-DOM fixture, SettingRow save/error behavior, JotList and
RangeTabs toggling, SettingsModal scaffold (behavior tests run on happy-dom,
registered per-file via test/dom.tsx), service-worker
routing/boundary/headers, manifest defaults, scrub + dates, dotenv parsing,
draft restore (parseDraft), and the command-menu editable-target guard.
