HH-787: add CreatorHub design system specification (#13)

This commit is contained in:
2026-08-28 17:51:21 +08:00
parent b875149605
commit 60eda8f39e
+378
View File
@@ -0,0 +1,378 @@
---
version: alpha
name: CreatorHub Operations Console
description: Enterprise UI and interaction source of truth for CreatorHub.
colors:
primary: "#0866EF"
primary-hover: "#0759D4"
on-primary: "#FFFFFF"
secondary: "#061A38"
secondary-hover: "#0B2A54"
on-secondary: "#FFFFFF"
on-secondary-muted: "#A9B7CC"
surface-page: "#FFFFFF"
surface-raised: "#FFFFFF"
surface-subtle: "#F8FAFC"
text-primary: "#111827"
text-secondary: "#475467"
text-tertiary: "#667085"
divider: "#D7DDE7"
success: "#067647"
success-surface: "#ECFDF3"
warning: "#B54708"
warning-surface: "#FFFAEB"
error: "#B42318"
error-surface: "#FEF3F2"
typography:
page-title:
fontFamily: "Inter, ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, Segoe UI, sans-serif"
fontSize: 2.25rem
fontWeight: 760
lineHeight: 1.15
letterSpacing: -0.035em
section-title:
fontFamily: "Inter, ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, Segoe UI, sans-serif"
fontSize: 1.25rem
fontWeight: 700
lineHeight: 1.3
letterSpacing: -0.01em
body-lg:
fontFamily: "Inter, ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, Segoe UI, sans-serif"
fontSize: 1.05rem
fontWeight: 400
lineHeight: 1.55
body-md:
fontFamily: "Inter, ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, Segoe UI, sans-serif"
fontSize: 1rem
fontWeight: 400
lineHeight: 1.5
label:
fontFamily: "Inter, ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, Segoe UI, sans-serif"
fontSize: 0.875rem
fontWeight: 650
lineHeight: 1.4
caption:
fontFamily: "Inter, ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, Segoe UI, sans-serif"
fontSize: 0.75rem
fontWeight: 400
lineHeight: 1.4
mono:
fontFamily: "ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, Liberation Mono, monospace"
fontSize: 0.875rem
fontWeight: 400
lineHeight: 1.5
spacing:
xxs: 4px
xs: 8px
sm: 12px
md: 16px
lg: 24px
xl: 32px
page-desktop: 36px
section: 48px
control-height: 40px
primary-control-height: 56px
appbar-height: 70px
drawer-collapsed: 76px
drawer-expanded: 240px
content-max-width: 1280px
rounded:
none: 0px
subtle: 4px
control: 7px
panel: 8px
full: 9999px
components:
page:
backgroundColor: "{colors.surface-page}"
textColor: "{colors.text-primary}"
typography: "{typography.body-md}"
panel:
backgroundColor: "{colors.surface-raised}"
textColor: "{colors.text-primary}"
rounded: "{rounded.panel}"
padding: "{spacing.lg}"
panel-compact:
backgroundColor: "{colors.surface-raised}"
textColor: "{colors.text-primary}"
rounded: "{rounded.panel}"
padding: "{spacing.md}"
table-header:
backgroundColor: "{colors.surface-subtle}"
textColor: "{colors.text-secondary}"
typography: "{typography.label}"
page-title:
textColor: "{colors.text-primary}"
typography: "{typography.page-title}"
section-heading:
textColor: "{colors.text-primary}"
typography: "{typography.section-title}"
intro-copy:
textColor: "{colors.text-secondary}"
typography: "{typography.body-lg}"
metadata:
textColor: "{colors.text-tertiary}"
typography: "{typography.caption}"
code-value:
textColor: "{colors.text-primary}"
typography: "{typography.mono}"
button-primary:
backgroundColor: "{colors.primary}"
textColor: "{colors.on-primary}"
typography: "{typography.label}"
rounded: "{rounded.control}"
height: "{spacing.control-height}"
padding: 0 16px
button-primary-hover:
backgroundColor: "{colors.primary-hover}"
textColor: "{colors.on-primary}"
button-secondary:
backgroundColor: "{colors.surface-raised}"
textColor: "{colors.primary}"
typography: "{typography.label}"
rounded: "{rounded.control}"
height: "{spacing.control-height}"
padding: 0 16px
button-danger:
backgroundColor: "{colors.error}"
textColor: "{colors.on-primary}"
typography: "{typography.label}"
rounded: "{rounded.control}"
height: "{spacing.control-height}"
padding: 0 16px
icon-button:
backgroundColor: "{colors.surface-raised}"
textColor: "{colors.text-primary}"
rounded: "{rounded.control}"
size: "{spacing.control-height}"
input-field:
backgroundColor: "{colors.surface-raised}"
textColor: "{colors.text-primary}"
typography: "{typography.body-md}"
rounded: "{rounded.control}"
height: "{spacing.primary-control-height}"
padding: 0 14px
navigation-item:
backgroundColor: "{colors.secondary}"
textColor: "{colors.on-secondary-muted}"
typography: "{typography.body-md}"
rounded: "{rounded.panel}"
height: 52px
padding: 0 16px
navigation-item-hover:
backgroundColor: "{colors.secondary-hover}"
textColor: "{colors.on-secondary}"
navigation-item-active:
backgroundColor: "{colors.primary}"
textColor: "{colors.on-primary}"
status-success:
backgroundColor: "{colors.success-surface}"
textColor: "{colors.success}"
typography: "{typography.label}"
rounded: "{rounded.full}"
padding: 4px 8px
status-warning:
backgroundColor: "{colors.warning-surface}"
textColor: "{colors.warning}"
typography: "{typography.label}"
rounded: "{rounded.full}"
padding: 4px 8px
status-error:
backgroundColor: "{colors.error-surface}"
textColor: "{colors.error}"
typography: "{typography.label}"
rounded: "{rounded.full}"
padding: 4px 8px
separator:
backgroundColor: "{colors.divider}"
size: 1px
---
## Overview
CreatorHub should feel like an **audit-ready operations console**, not a consumer social dashboard: the calm, stable shift board in a control room where operators can see current state, take one deliberate action, and verify its outcome without visual noise.
The audience is an internal operator managing authorized accounts, isolated browser environments, network exits, confirmations, tasks, and audit evidence. The interface must communicate control, traceability, and safe stopping before speed or novelty.
Three principles decide ambiguous designs:
1. **State before decoration.** Current state, ownership, last evidence, and safe next action receive the strongest hierarchy.
2. **Explicit domain actions.** Start, stop, recycle, confirm, pause, and cancel remain named actions; never disguise them as generic CRUD.
3. **Quiet density.** Use type, spacing, borders, and alignment for hierarchy. Color is reserved for navigation, primary action, status, focus, and errors.
`design.md` is the normative design contract for future pages. Existing implementation differences are migration work for separate issues; do not silently fork this system inside page code.
## Colors
The palette keeps the existing CreatorHub blue and navy anchors while replacing ad hoc page colors with semantic roles.
- **Primary** `{colors.primary}` is the single interaction accent. Use it for the main action, active navigation, links, and focus—not for decoration.
- **Secondary** `{colors.secondary}` is the stable navigation shell. `{colors.secondary-hover}` is its only hover layer.
- **Surfaces** remain white. `{colors.surface-subtle}` is limited to table headers, grouped metadata, and quiet empty-state regions; it must not become a field of nested gray cards.
- **Text** uses `{colors.text-primary}` for decisions and data, `{colors.text-secondary}` for explanation, and `{colors.text-tertiary}` for metadata. Do not lower opacity to invent more tiers.
- **Semantic colors** mean state only. Success, warning, and error always pair color with a label or icon and never stand alone as the sole signal.
- **Dividers** `{colors.divider}` create structure. Regular panels use a 1px divider rather than a shadow.
Do not introduce gradients, glow, glass surfaces, decorative color blocks, or page-specific hex values. A new color requires a reusable semantic role and contrast verification before it enters this file and `web/src/theme.js`.
## Typography
Use the existing Inter-first system stack. Do not download a web font for a control console; system fallbacks preserve speed and Chinese coverage.
- `page-title` is reserved for one page-level `h1`.
- `section-title` labels major work regions, not every card.
- `body-lg` is limited to the page summary or a high-value empty-state explanation.
- `body-md` is the default reading and data size.
- `label` is used for controls, field labels, table headers, and status labels.
- `caption` is supporting metadata; it must still pass normal-text contrast.
- `mono` is for identifiers, endpoints, profile names, hashes, and machine values only.
Use sentence case in Chinese and English. Do not use all caps, decorative display type, or more than three weights in one view. Numeric identifiers must not be made smaller merely to fit; wrap them or expose a copy action.
## Layout
The shell reuses the current application geometry:
- fixed app bar: `{spacing.appbar-height}`;
- permanent desktop drawer: `{spacing.drawer-expanded}` expanded and `{spacing.drawer-collapsed}` collapsed;
- content width: at most `{spacing.content-max-width}`, left-aligned in the main work area;
- page padding: 16px on `xs`, 24px from `sm`, and `{spacing.page-desktop}` from `lg`;
- section separation: 2448px; panel padding: 16px compact or 24px standard.
Every page follows the same anatomy: page title and one-sentence purpose, page-level primary action when present, filters or creation controls, results, then supporting evidence. Place the action near the object it changes; avoid a detached toolbar full of unrelated buttons.
Use MUI's existing breakpoints: `xs` below 600px, `sm` from 600px, `md` from 900px, and `lg` from 1200px. The application must remain usable without horizontal page scrolling at 320px.
## Elevation & Depth
CreatorHub is border-first and nearly flat. Regular panels, tables, forms, and cards use a 1px divider and no shadow. Hierarchy comes from containment, whitespace, and subtle surface changes.
Only transient overlays—menus, dialogs, popovers, and tooltips—may use MUI's default elevation. Do not stack raised cards inside raised cards, invent page-level z-index scales, or use shadows as focus indicators.
## Shapes
Controls use the 7px `control` radius; panels and navigation items use the 8px `panel` radius. The 4px `subtle` radius is reserved for small inline regions. Full pills are limited to compact statuses and tags.
Do not use oversized rounded cards, capsule buttons for ordinary actions, mixed corner systems, or decorative circles. Icons come from the installed MUI icon set and use the same stroke character within an action group.
## Components
### Shared state model
Interactive components must define, as applicable: `default → hover → focus-visible → active → disabled`, plus `busy`, `error`, `empty`, and `success` at the workflow level. Hover never substitutes for focus. Disabled controls explain the prerequisite in nearby text or a tooltip when the reason is not obvious.
### Buttons and domain actions
- Allow one contained primary button per work region. Secondary actions are outlined or text buttons.
- Use verb-first labels: “创建环境”, “启动”, “停止”, “回收”. Avoid “确定”, “处理”, and icon-only domain actions.
- Destructive or high-impact actions use the error color and a confirmation that names the target and consequence. Default focus stays on cancel.
- Busy labels describe progress (“创建中…”, “停止中…”), disable repeat submission, and preserve layout width.
- Disable only the affected row or object unless the operation invalidates the whole page.
### Inputs and forms
- Every input has a persistent visible label. Placeholder text is an example, never the label.
- Required fields use both native `required` semantics and a visible marker; validation runs on blur or submit, not on each keystroke unless the value can be checked without noise.
- Error text sits next to the field, states how to recover, and remains until corrected. Preserve valid input after request failure.
- Group no more than one primary submission action per form. Put advanced or risky fields behind explicit disclosure only when they actually exist.
### Navigation
- The active item uses primary blue, white text, and the router's current-location semantics.
- Collapsed navigation retains accessible names and tooltips; mobile navigation closes after a route selection and returns focus to the opener when dismissed.
- Disabled destinations must look unavailable and remain non-interactive. Do not present speculative routes as working navigation.
### Tables and responsive cards
- Desktop tables are for comparison across rows. Keep stable column order, left-align text, right-align numeric measures, and place actions last.
- At widths below `md`, replace wide tables with cards that preserve the same data, status, action availability, and reading order.
- Empty results show what is empty and the safe next action. Loading preserves the content region; errors use an alert with a retry action.
- Long identifiers wrap safely or truncate with an accessible full-value affordance. Never shrink below the type tokens to force fit.
### Status and feedback
- Status uses a localized label plus color and, when space permits, an icon or dot. Unknown backend states display a safe “未知状态” label and the raw value as secondary evidence.
- Success is shown by the changed object state; use a toast only when the result would otherwise be invisible.
- Page or action errors use a persistent alert near the affected work region. Do not expose secrets, raw stack traces, or unactionable transport errors.
- Polling updates data without stealing focus, resetting forms, moving the viewport, or repeatedly announcing unchanged content.
### Dialogs, menus, and tooltips
- Use a dialog only for a decision that cannot safely occur inline. The title names the action and target; the body states the consequence.
- Escape and the close control dismiss a dialog before submission. After completion or cancellation, return focus to the trigger.
- Tooltips supplement icon-only controls and unfamiliar terms; they never carry required instructions or validation errors.
## Do's and Don'ts
- **Do** make the current state and next safe action obvious before adding density.
- **Do** reuse MUI and react-admin interaction patterns, theme tokens, and installed icons.
- **Do** keep high-risk lifecycle actions explicit, confirmable, idempotent where supported, and backed by visible outcome evidence.
- **Do** design loading, empty, error, permission/policy hold, and stale-data states with the default state.
- **Do** keep filters, sort, and pagination stable when data refreshes.
- **Don't** add a hero, KPI vanity cards, illustrations, gradients, glassmorphism, or animation merely to make an internal tool feel “modern”.
- **Don't** mix Ant Design, Arco, Mantis code, or another component language into MUI.
- **Don't** encode status only with color, use disabled text as ordinary metadata, or hide critical actions behind hover.
- **Don't** auto-retry `needs_confirmation`, `policy_hold`, unknown results, platform warnings, authentication failures, or destructive actions.
- **Don't** show credentials, cookies, tokens, proxy secrets, or cross-account data in UI copy, logs, screenshots, errors, or exports.
## Naming
- Token names are lowercase kebab-case and semantic: `text-secondary`, not `gray-600`; `status-error`, not `red-pill`.
- Component variants use `<component>-<variant>-<state>`, for example `button-primary-hover`. Do not name tokens after a single page.
- React components use PascalCase, props and handlers use camelCase, and domain actions use exact verbs (`start`, `stop`, `recycle`, `confirm`, `pause`, `cancel`).
- Backend state keys stay stable lowercase English; user-facing labels are localized Chinese. Never use the display label as the program state.
- A user-visible resource has one canonical noun across navigation, title, table, form, dialog, error, and audit text. For the current runtime resource, use “运行环境”; use “Profile” only for the isolated profile artifact.
- Accessibility names describe action plus target (“停止 account-a”), not icon shape (“方形按钮”) or position (“右侧按钮”).
## Interaction
Interaction is quick and mechanical, like operating a reliable control panel:
| Token | Value | Use |
|---|---:|---|
| `motion.feedback` | 120ms | hover, press, focus, toggle |
| `motion.exit` | 160ms | dismissing menus and dialogs |
| `motion.layout` | 200ms | drawer and bounded layout transitions |
| `motion.easing` | `cubic-bezier(0.4, 0, 0.2, 1)` | all transitions |
Nothing bounces, overshoots, pulses indefinitely, or animates longer than 200ms. Under `prefers-reduced-motion: reduce`, durations collapse to effectively zero while state changes remain perceivable.
Actions update in this order: acknowledge the trigger, prevent duplicate submission, perform the request, expose the resulting state or actionable error, then restore focus. Never use optimistic UI for destructive actions or results whose server outcome may be unknown.
## Responsive Behavior
- **320599px (`xs`):** temporary drawer; one-column forms; cards replace data tables; actions wrap without reordering; 16px page padding.
- **600899px (`sm`):** 24px page padding; forms may use two columns only when labels and errors still fit.
- **9001199px (`md`):** permanent drawer; comparison tables; two-column creation forms; no horizontal page overflow.
- **1200px+ (`lg`):** 36px page padding; up to three form columns; content stops growing at 1280px.
Responsive variants must preserve content and action parity. A mobile card may rearrange a row, but it may not omit state, evidence, errors, or a permitted action. Pointer and keyboard behavior must both work at every breakpoint.
## Accessibility
WCAG 2.2 AA is the minimum release boundary.
- Normal text requires 4.5:1 contrast; large text and non-text controls require 3:1. Disabled controls are exempt from text contrast but must remain distinguishable.
- All workflows operate by keyboard in a logical order with no trap. Use native controls first and preserve visible focus.
- Focus uses a 2px high-contrast ring with a 2px offset. On primary or navy surfaces, switch to a white ring when blue cannot reach 3:1.
- Pointer targets are at least 24×24px with sufficient separation; prefer 44×44px for primary, mobile, and destructive actions. A 40px dense control is allowed only when adjacent targets remain separated and keyboard access is equivalent.
- Page landmarks, one `h1`, ordered headings, table headers, field labels, descriptions, and error associations are semantic—not simulated with styling.
- Loading indicators and live results have concise accessible names. Announce completion and errors once; routine polling must not create repeated live-region output.
- Icons never replace text for domain actions. Decorative icons are hidden from assistive technology; icon-only shell controls require explicit accessible names and tooltips.
- Zoom to 200% and text spacing overrides must not clip content, hide actions, or force two-dimensional scrolling outside genuine data tables.
- Do not communicate state, required fields, or errors by color alone.
## Implementation Boundary and Review Checklist
CreatorHub owns its MUI theme, Layout/AppBar/Menu, and react-admin data-provider seam. Future UI work must update those existing seams instead of building a parallel component system. Mantis Free may inform visual judgment under its license, but its template code is not imported or forked. Do not add a token runtime, generator, Storybook, or new UI dependency until a concrete implementation issue proves the existing MUI theme insufficient.
Before a page is accepted, verify:
1. tokens and terminology come from this file or an explicitly reviewed extension;
2. default, hover, focus-visible, disabled, busy, loading, empty, error, and success states are covered where applicable;
3. the page works at 320px, 600px, 900px, and 1200px without losing content or actions;
4. keyboard order, focus return, accessible names, contrast, reduced motion, and 200% zoom pass;
5. domain actions retain their explicit semantics and failure/unknown outcomes never auto-retry;
6. focused interaction tests cover any changed theme, layout, navigation, resource action, or data-provider behavior.
Format and philosophy reference: [google-labs-code/design.md](https://github.com/google-labs-code/design.md) at `9bf8eae67128b6cc55ad9bf86665767deb4c11cd` (Apache-2.0), accessed 2026-08-28. This file adopts the structured-token-plus-rationale approach; CreatorHub's values, rules, and prose are project-specific.