一、④段整套同步(用户选候选 A)
- 新增 `references/stage-proto-doc/assets/产品文档模板.html`(16 KB/247 行,从备份拷、转 LF)。
- ④段 SKILL.md 换成备份 15:28 那版,并按模板口径改齐:
· 「产品文档框架」从「四块+一附录」改成**六节** —— 页面结构/功能清单/使用场景/操作流程/
跨页与界面状态/待确认与偏离;与模板同构,⛔ 不从空白页重写结构。
· 编号从 R/S/F 改成 **P 页面/S 场景/F 功能/J 二级功能**,各自唯一、按展示顺序通排。
· 保留旧版有价值的要求:每项功能补齐 做什么/入口在哪/输入边界/异常与恢复;重点功能三处标红;
横向流程图每步写清页面/功能/操作/完成判据。
· 完成标准「七节」→「六节」,自查清单同步换掉「需求」那批条目,补模板与配图两条。
· 路径 `designs/<项目>/` → `docs/pm/<项目>/ui/`(2 处)。
· 补回本机那条「每份产出文档开头都要有一张图」的引用(指向主 skill,段内不复述)。
- 主 skill 同步:四段结构表④行、④段沿革、画图小节(④段画页面结构图 + 用户路径)。
二、open-design 移到技能根(用户选候选 A)
- `git mv product-planning/references/stage-delivery/assets/open-design → open-design/`(4178 文件)。
- `.gitignore` 白名单加 `!/open-design/` —— 否则移出去就不再纳入版本控制。
- 引用改 11 处:路径形态 `assets/open-design/...` → `<技能根>/open-design/...`;
「随段携带的自带件」改称「同根技能」;「路径怎么找」那条改成自带件(段内相对路径)与
同根技能(`<技能根>/<技能名>/`)两类分开写。
- open-design 自己的 ATTRIBUTION.md 两处同步(它现在与 oil-ui-pro 同层)。
- 副作用(有益):命名闸门读数由 2063 降到 225 —— 此前它把 open-design 里 919 个第三方 md
当成规则一起扫了;现在只扫本包 23 个 md。已确认④段的 H1 与 4a/4b/4c 子步仍在检查范围内。
三、验收
- check_naming.py --ws E:/ProgramData/AIProject/contentm_agent = 通过 225 / 失败 0 / 提示 27。
- 活文件里 `assets/open-design` 残留 0;工作区 CRLF 文件 0。
6.9 KiB
State coverage craft rules
Universal rules for what every interactive surface must render. The active
DESIGN.md decides how each state looks; this file decides which states must
exist and what they must contain. The single most reliable AI-design failure
is shipping only the populated state.
Distilled from WCAG 2.2, NN/g, Material Design 3, Apple HIG, and Baymard Institute checkout research.
The five required states
Every surface that fetches, transforms, or accepts data must render all five.
| State | Triggered when | Must contain |
|---|---|---|
| Loading | Data is in flight | Skeleton, spinner, or shell — plus a 15 s "taking longer than expected" fallback |
| Empty | No records yet, or query returned nothing | Headline, plain explanation, primary CTA |
| Error | Fetch failed, server failure, validation rejection | Plain-language cause, recovery action, preserved user input |
| Populated | Data present, primary case | The state the design was actually drawn for |
| Edge | Extreme volume, long strings, missing optional fields, RTL or long-word content, partial network | Layout that does not break |
Render-and-screenshot test: every list, table, card, form, and panel in the artifact has all five. Missing states are the most common silent failure of AI-generated UI.
Test matrix. Concrete edge scenarios the surface must survive:
| Skill type | Edge scenario |
|---|---|
| Dashboard / table | 10,000+ rows, all numeric columns, sort + filter applied |
| Mobile card / list | 200-char title, missing avatar, missing secondary CTA |
| Form | All optional fields empty, all required fields at max length |
| Search results | Single-character query, query with only special chars, 1,000+ result count |
| Detail view | Missing all optional metadata, RTL primary content with LTR embeds |
Form-specific states
Forms add three states on top of the five.
| State | Triggered when | Behavior |
|---|---|---|
| Untouched | Field has not yet had focus | Default styling; no validation messages |
| Dirty (valid) | User typed and field passes validation | Persistent helper text remains; no success-coloring |
| Submitted-pending | Submit clicked, awaiting server | Submit button enters loading state; fields lock against re-submission |
Validation timing: validate on blur, not on first keystroke. For password and similar live fields, validate on each keystroke only after the first blur. Remove the error message the instant input becomes valid.
Empty state composition
Empty is not the absence of state. It is its own state with a job.
- First-use empty — illustration + headline + value sentence + primary CTA. The empty is the onboarding moment.
- No-results empty — echo the query, suggest alternatives, never leave a true blank.
- Cleared empty — celebratory phrasing, optional next-action.
- Error-as-empty — never. An error is its own state with recovery information; do not collapse error into empty.
Server-driven vs client-driven. When a search or query API can return fallback content in the empty payload (suggestions, related categories, popular results), prefer that over a client-side echo. Algolia, Elastic, and most modern search backends support this — the server has more context for what "no results, but maybe try X" should mean.
Error state composition
Every error must answer three questions, in this order:
- What happened. "Your card was declined." Not "Something went wrong."
- Why, if knowable. "Insufficient funds." Or "Network unreachable — check your connection."
- What the user can do. A retry button, an alternative path, or a support link.
Preserve user input across the error. The form must not clear on submit failure.
Severity tiers:
- Field-level — red border, inline message, focus moves to the field.
- Form-level — error summary banner at top + per-field markers.
- Section-level — inline panel with retry, surrounding sections still functional.
- Page-level — full error state with illustration and recovery CTA.
- App-level — persistent banner or modal for critical loss-of-functionality.
Match severity to surface scope. A field validation failure does not warrant a page-level error.
Retry discipline. A retry surface is not a button alone. It has timing rules:
- First retry fires immediately on user click.
- Second and third retries use exponential backoff: 2 s, 4 s, 8 s max.
- After 3 failed retries, replace "Retry" with "Contact support" plus a copyable error ID. The user has done their job; the system now needs a human.
- Show "Last attempted: Xs ago" on the error surface after the first retry, so the user knows how stale the failure is.
Loading state thresholds
Pick the indicator by expected duration, not by what's available in the component library.
| Duration | Indicator |
|---|---|
| 0–300 ms | None. Render synchronously; users perceive no delay. |
| 300 ms – 2 s | Subtle spinner or skeleton. |
| 2 – 10 s | Skeleton matched to expected layout, or labelled spinner ("Loading payments…"). |
| 10 – 30 s | Determinate progress bar with cancel option. |
| 30 – 60 s | Progress bar with explicit cancel affordance. The "taking longer than expected" notice already appeared at 15 s; do not repeat it. |
| 60 s+ | Stop animation. Show error with retry, cancel, or continue. |
Never leave a spinner running indefinitely. Start a timeout on every request.
ARIA and focus rules
State changes must be announced and focused correctly.
| Change | ARIA | Focus action |
|---|---|---|
| Inline error on submit | role="alert" on the message |
Move focus to first error field |
| Toast / non-urgent confirmation | role="status" (polite live region) |
Do not move focus |
| Critical error or destructive confirmation | role="alertdialog" (assertive) |
Move focus to dialog |
| Loading begins | role="status" announcement ("Loading…") |
Do not move focus to spinner |
| Loading ends, content appears | — | Move focus to loaded content if action was user-initiated |
Live region containers must exist in the DOM before content is injected.
Adding aria-live simultaneously with content does not trigger an
announcement.
Common mistakes (lint these)
- Surface renders only the populated state; loading, empty, error, and edge are absent.
- Empty state is a literal blank or "No data" text with no headline, explanation, or action.
- Error message reads "Something went wrong" with no cause or recovery.
- Spinner with no timeout; runs indefinitely on slow or failed requests.
- Submit clears form fields on validation failure, forcing re-entry.
- Inline validation fires on first keystroke instead of on blur.
- Full-page loading replaces the chrome when only one section is fetching.
- Toast appears at a different screen position than previous toasts in the same artifact.
- Color alone conveys error state — no icon, no text label.
- Auto-dismissing toast cannot be paused on hover or focus (WCAG SC 2.2.1).