temps-design-system
GitHub用于构建或审查符合 Temps 设计系统的控制台 UI,涵盖组件使用、页面模板及样式规范。适用于新屏幕开发、UI PR 审查及品牌一致性检查,排除遗留系统。
Trigger Scenarios
Install
npx skills add gotempsh/temps --skill temps-design-system -g -y
SKILL.md
Frontmatter
{
"name": "temps-design-system",
"description": "Build or review console UI so it reads as Temps: the paper-and-ink operator design system (`@temps-sdk\/ds` primitives, the `operator ink v1` skin, the Ledger \/ Detail \/ Settings page templates, the status vocabulary and the record recipe). Invoke when a task adds or redesigns a console screen, a landing section or a status page on the new design system, when the user says \"follow the design system\", \"make it look like temps\", \"brand guidelines\", \"taste\", \"op components\", or when reviewing a UI PR against the guidelines. Not for the legacy `web\/src` console: that stays on its current shadcn look until it is migrated screen by screen."
}
Temps design system
The design system is a sandbox app plus a component package. Everything a UI task needs is in the repo; do not invent tokens, colours or page shapes.
| What | Where |
|---|---|
| Rules digest for agents (read first, imperative, short) | design-system/docs/RULES.md |
| Brand guidelines (why the rules exist) | design-system/docs/brand-guidelines.md |
| Handoff: tokens, primitive catalogue, page templates, responsive, keyboard | design-system/docs/design-system-handoff.md |
| Component package consumed by screens | web/packages/ds (@temps-sdk/ds) |
Consumer setup (a plugin, an outside app): install, op.css, @source, the skin class, fonts |
web/packages/ds/README.md |
| Reference implementation of every screen | design-system/src/sections/ConsoleV1*.tsx |
| Browsable guide, component gallery, console mockups | cd design-system && bun install && bun run dev → /guide, /op-components, /v1 |
Scope boundary
What exists today, stated plainly so nobody assumes more: the production
console (web/src, rsbuild) does not import @temps-sdk/ds yet. What is
built is the system (the docs), the package, and the sandbox that renders every
primitive and every screen shape against it. Console migration happens screen by
screen, on a schedule, not as a side effect of another task.
- Redesign work (new screens on
@temps-sdk/ds, the sandbox, the landing and status page mockups): this skill applies in full. - Legacy console (
web/src/**on shadcn/ui): follow the frontend rules inCLAUDE.md. Do not restyle legacy screens piecemeal to the new system; a screen moves to the new system whole, when its migration is scheduled. - Plugin UI (a separate document in an iframe): see "Plugin UI" below. It gets the system by bundling the package, not by inheriting anything.
- The package (
web/packages/ds): change a primitive only together with its entry in the handoff doc §6, the gallery on/op-componentsand theCHANGELOG.mdof the package.
Procedure for a UI task
- Read
design-system/docs/RULES.mdend to end. It is 120 lines. When it disagrees with the two long docs, the long docs win; fix the digest. - Classify the screen from its data, not from habit (RULES.md "Page
structure"): many records of one kind →
Ledger; one record read top to bottom →Detail+Columns; a configuration →Settings; nothing yet, not set up or failed →PageState. - Find the closest reference screen in
design-system/src/sections/and start from its shape. Deployment (ConsoleV1Deploy.tsx), Nodes (ConsoleV1Nodes.tsx), Database (ConsoleV1Database.tsx) and Settings (ConsoleV1Settings.tsx) cover the record, list, tool and configuration cases. - Build with primitives from
@temps-sdk/dsonly. Import the skin once (@import '@temps-sdk/ds/op.css') and putoperator ink v1on the root you want skinned, including portalled content. - Apply the record recipe: title + meta → status verdict →
Ledewith four to six facts →Columns(main: the thing and its timeline; aside: what is left) → sections. A fact appears once. Colour only throughStatus, as glyph + word + tone. Icons say what kind, glyphs say what state. - Wire every drawn control. A
Kbdbadge needs a handler, a filter must filter, a destination is a typed/${string}path, never#. The ledger cursor moves DOM focus. - Check both widths, both modes: 1440 and 390, light and dark. Below md,
ledger rows render
mobileand it carries the row's primary action.
Adding a primitive
- Build it in
web/packages/ds/src/*.tsxand export it fromsrc/index.ts. - Give it a gallery block: a
<section id="…">indesign-system/src/sections/blocks/*.tsx, or inOpComponents.tsxfor a primitive that belongs to no rule document. Show every state, not a happy path. Add its id and label to the page's TOC (OpComponents.tsx, or the*_TOCexport the page spreads). - Add the id to
BLOCKSindesign-system/e2e/visual.spec.ts, in page order. AtoEqual([...BLOCKS])assertion compares that list against the sections the page renders, so the run stays red until both agree. - Shoot the baseline: run the gallery tests, then adopt only the new
block's actuals — copy each
-actual.pngPlaywright wrote undertest-results/overdesign-system/e2e/__screenshots__/visual.spec.ts/<name>-<project>.png. Never a blanket--update-snapshots: it rewrites ~85 blocks for sub-pixel noise and the diff you were meant to read drowns. - Write the handoff §6 entry: what it is for, what it refuses to do, its states.
- Add the
CHANGELOG.mdline inweb/packages/ds/.
Before you ship
Run from design-system/:
bun run lint # tsc --noEmit + scripts/audit-records.mjs + tokens.mjs check
bun run e2e # overflow at 390/1440, keyboard, drop focus, reload signatures, axe, visual
Both must be clean. Fix dev-console warnings from Lede and Detail. When a
visual baseline changes on purpose, adopt the actuals as in step 4 above and
say which blocks moved, and why, in the PR.
audit-records.mjs audits src/sections by default and takes --dir <path>
(repeatable) for any other folder of screens:
node scripts/audit-records.mjs --dir ../examples/example-plugin/web/src
Know what green proves. Lint-enforced: types (tsc), the record recipe
(audit-records.mjs, literal-only and heuristic on two of its rules), and
tokens.json against op.css (tokens.mjs check). E2E-enforced: no
horizontal scroll at 390/1440 with a clean console, no new serious/critical axe
violation in light and dark, the keyboard contract, reload signatures, and the
visual baselines.
Honour system — nothing fails if you break these: paper and ink only, no second
hue, colour only through Status, no cards and one .op-raise per screen, no
hex / oklch() / palette literal / ms literal in a .tsx, the closed
spacing scale, every drawn control wired, view state in the URL beyond what
state.spec.ts samples, and the words (content.md, localisation.md,
icons.md). A green lint means the types, the recipe and the tokens hold; it
does not mean the screen follows the design system. Read it yourself.
Plugin UI
A plugin's UI is a separate document: web/src/pages/plugins/PluginPage.tsx
mounts it in a same-origin iframe at /api/x/{plugin}/ui/, and the plugin
serves its own HTML, JS and CSS. Nothing crosses that boundary — not the
console's stylesheet, not the operator ink v1 root, not the fonts, not the
Tailwind build that generated the utilities the primitives use. A plugin that
assumes it inherits the skin renders unstyled.
So a plugin sets the system up for itself, like any outside app. It is a plain
Vite + React app (examples/example-plugin/web/ is the shape), so
web/packages/ds/README.md is the setup, verbatim:
bun add @temps-sdk/dsplus its peer dependencies, and pin the version the console ships — two versions of the skin side by side drift in a way that reads as "this page looks slightly wrong", not as a bug.@import '@temps-sdk/ds/op.css'at the top of the entry stylesheet, and@source "../node_modules/@temps-sdk/ds/dist"so the consumer's Tailwind scans the package and generates the utilities it renders.operator ink v1on the plugin's own root, and on any portalled content.- Theme: there is no theme channel today.
PluginPage.tsxsyncs the route (hash orpostMessage) and nothing else, so readprefers-color-schemefor now and toggle.darkfrom it; theme sync from the parent is a follow-up (handoff §15). - Same conventions as a console screen:
CopyActionfor a copy,Button busyfor an action in flight,useUrlStatefor the view. A plugin's route is mirrored into the console's address bar, so a plugin that keeps its facet in React state produces a link that does not reopen what the reader was looking at.
Handoff §3b says the same thing at length.
Changing a rule
A rule changes in one commit that edits brand-guidelines.md,
design-system-handoff.md, RULES.md and the reference page together. A
rule stated in only one place is not a rule.
Version History
- da04954 Current 2026-09-09 08:35


