design-playbook 0.24.0 → 0.24.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/codex/AGENTS.md +1 -1
- package/examples/dogfood/run/craft-guard.md +3 -0
- package/examples/export-entry/run/craft-guard.md +3 -0
- package/examples/export-pointfix-upgrade/run/craft-guard.md +3 -0
- package/examples/export-upgrade/run/craft-guard.md +3 -0
- package/mcp/run_console/http_server.py +23 -0
- package/package.json +1 -1
- package/scripts/adapter_matrix.py +10 -0
- package/scripts/context_budget.py +147 -0
- package/scripts/doctor.py +2 -0
- package/scripts/generate_adapter.py +55 -0
- package/skills/craft-guard/references/detectors.md +1 -1
- package/skills/design-playbook/references/rules.md +90 -0
package/codex/AGENTS.md
CHANGED
|
@@ -26,3 +26,6 @@ Registry: `skills/design-playbook/references/rules.md`, full catalog (P3 run:
|
|
|
26
26
|
| DECIDE-01@1 | applicable | - | clear | 选中方向为全局 run console 构成重组(candidates/console-region.html 草图 + preview round 1/2 用户确认),非未审视的默认外观收敛 | DD-0001 理由可回溯 l1.scenes(切页续读)与 PERF-01 比较轴及经用户确认的布局段突破;DD-0002 理由可回溯 l6.c4(跨视图状态闭环)与运行中心第一步方向——均引用 brief 具体事实 | 常规方向经比较矩阵沿 brief 轴证成,非未审视默认;无基线默认方向身份声明 | - |
|
|
27
27
|
|
|
28
28
|
注:2026-08-28 注册批(COPY-01/02/03、A11Y-02、CRAFT-09/10、DECIDE-01)晚于本 run 存档;按三态谓词补记——blocked 行在理由列点名缺失的证据面;DECIDE-01 依归档内可读的决策报告求值为 applicable。
|
|
29
|
+
| STATE-01@1 | applicable | - | clear | 运行 feed 条目级进度持续更新(进行中项逐条指名) | Fill 声明条目级进度 + evidence 进度语义 | - | - |
|
|
30
|
+
| STATE-02@1 | applicable | - | clear | 「无运行时 feed 不渲染」为 spec 声明边界:无 feed 时控制台不呈现空集合区 | Fill 声明边界条目(L 边界对齐) | 例外成立——空呈现即 spec 声明的产品行为 | - |
|
|
31
|
+
| STATE-03@1 | applicable | - | clear | 暂停失败 toast(role=alert)+ 失败项保留并支持圈选批量重试 | Fill 声明(失败 toast + 批量重试操作条) | - | - |
|
|
@@ -26,3 +26,6 @@ Registry: `skills/design-playbook/references/rules.md`, full catalog (P2 run). S
|
|
|
26
26
|
| DECIDE-01@1 | applicable | - | clear | 选中方向为按周命名模式(compare 档轻量比较),不落入 2026-08 记录的自默认外观样貌;DD-0001 为 record 档,不在本规则范围 | DD-0002 理由可回溯 l1.target_user 比较轴(周频归档场景下检索是主任务)——引用 brief 具体事实而非通用措辞 | 常规方向经比较矩阵沿 brief 轴证成,非未审视默认;无基线默认方向身份声明 | - |
|
|
27
27
|
|
|
28
28
|
注:2026-08-28 注册批(COPY-01/02/03、A11Y-02、CRAFT-09/10、DECIDE-01)晚于本 run 存档;按三态谓词补记——blocked 行在理由列点名缺失的证据面;DECIDE-01 依归档内可读的决策报告求值为 applicable。
|
|
29
|
+
| STATE-01@1 | applicable | - | clear | 批量导出触发钮 busy/disabled 守卫(控件自身标签即操作名,pending 区域限于触发钮) | Fill 声明 busy state while exporting + busy/disabled guard | 单帧完成例外不成立(批量导出为长操作) | - |
|
|
30
|
+
| STATE-02@1 | blocked | 静态夹具无数据源,首载空/筛选空均不可达 | - | 表格列表区声明在场,空态分支缺席 | 无数据源可供触发空态 | - | 接入数据面后补评首载空与筛选空呈现 |
|
|
31
|
+
| STATE-03@1 | applicable | - | clear | cap 上限 toast(role=alert + 可读名称含上限值) | Fill 声明 toast 承载上限拒绝 | 上限拒绝为失败路径的可恢复呈现(出口=调整范围后再导出) | - |
|
|
@@ -26,3 +26,6 @@ Registry: `skills/design-playbook/references/rules.md`. Seven-column rows; after
|
|
|
26
26
|
| DECIDE-01@1 | not-applicable | 决策报告仅含 record 档 DD-0101,无 compare/explore 档方向决策条目 | - | - | - | - | - |
|
|
27
27
|
|
|
28
28
|
注:2026-08-28 注册批(COPY-01/02/03、A11Y-02、CRAFT-09/10、DECIDE-01)晚于本 run 存档;按三态谓词补记,blocked 行在理由列记缺失证据面。
|
|
29
|
+
| STATE-01@1 | applicable | - | clear | 导出触发钮 busy/disabled 守卫(自前次 run 保留) | Fill 声明 busy/disabled guard 保留 | 单帧完成例外不成立 | - |
|
|
30
|
+
| STATE-02@1 | applicable | - | clear | 空数据集预检:数据集为空时触发钮禁用 + toast「无可选行」(指名原因) | Fill 声明 empty-blocked pre-check(R4 修复) | 例外不成立——空数据呈现明确指名原因与出口 | - |
|
|
31
|
+
| STATE-03@1 | applicable | - | clear | cap 上限 toast(自前次 run 保留,role=alert) | Fill 声明 cap-limit toast 保留 | - | - |
|
|
@@ -26,3 +26,6 @@ Registry: `skills/design-playbook/references/rules.md`, full catalog (P3 run:
|
|
|
26
26
|
| DECIDE-01@1 | applicable | - | clear | 选中方向为启用既有 status region 收纳导出任务(candidates/B.html 草图 + preview round 1/2 用户确认),非未审视的默认外观收敛 | DD-0003 理由可回溯 l1.scenes(导出中切页全局可查)与 PERF-01 比较轴;DD-0004 理由可回溯 l6.c2(跨视图状态闭环)与基线 status region 惯例声明——均引用 brief 具体事实 | 常规方向经比较矩阵沿 brief 轴证成,非未审视默认;基线声明的是 status region 惯例而非默认外观身份 | - |
|
|
27
27
|
|
|
28
28
|
注:2026-08-28 注册批(COPY-01/02/03、A11Y-02、CRAFT-09/10、DECIDE-01)晚于本 run 存档;按三态谓词补记——blocked 行在理由列点名缺失的证据面;DECIDE-01 依归档内可读的决策报告求值为 applicable。
|
|
29
|
+
| STATE-01@1 | applicable | - | clear | 长导出条目级进度持续更新(30s 窗口 5 次采样) | evidence/L6.1-status-trace.json 进度采样序列 | - | - |
|
|
30
|
+
| STATE-02@1 | blocked | 候选页为静态导出面,无数据供给集合面,空态不可达 | - | 无空态分支呈现 | 无数据源 | - | 接入数据面后补评 |
|
|
31
|
+
| STATE-03@1 | not-applicable | 本 run 升档面未声明异步失败路径(进度轨迹为唯一运行时面) | - | - | 候选页无可触发失败分支 | - | - |
|
|
@@ -139,6 +139,29 @@ class RunConsoleRequestHandler(http.server.BaseHTTPRequestHandler):
|
|
|
139
139
|
def log_message(self, format: str, *args: object) -> None:
|
|
140
140
|
"""Never log: no URL, token, or locator may reach any log."""
|
|
141
141
|
|
|
142
|
+
def finish(self) -> None:
|
|
143
|
+
"""Graceful teardown: half-close, drain, then close.
|
|
144
|
+
|
|
145
|
+
A client body left unread in the receive buffer (e.g. a POST whose
|
|
146
|
+
Content-Length is absent: the stray bytes parse as garbage request
|
|
147
|
+
lines and the connection must die) turns a plain close() into a TCP
|
|
148
|
+
reset on Windows — the client gets WinError 10053 mid-read instead
|
|
149
|
+
of the response it was sent. Flush the response, half-close (FIN),
|
|
150
|
+
drain the peer's remaining bytes, then close, so teardown is never
|
|
151
|
+
a reset.
|
|
152
|
+
"""
|
|
153
|
+
super().finish()
|
|
154
|
+
try:
|
|
155
|
+
self.connection.shutdown(socket.SHUT_WR)
|
|
156
|
+
except OSError:
|
|
157
|
+
pass
|
|
158
|
+
try:
|
|
159
|
+
self.connection.settimeout(0.2)
|
|
160
|
+
while self.connection.recv(65536):
|
|
161
|
+
pass
|
|
162
|
+
except OSError:
|
|
163
|
+
pass
|
|
164
|
+
|
|
142
165
|
# -- dispatch ------------------------------------------------------
|
|
143
166
|
|
|
144
167
|
def do_GET(self) -> None: # noqa: N802
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "design-playbook",
|
|
3
|
-
"version": "0.24.
|
|
3
|
+
"version": "0.24.2",
|
|
4
4
|
"description": "Design I/O for coding agents: controllable UI generation via declarations (spec/domain/craft/design/components/template) and contracts (skill/evaluator). Use for product UI—console, dashboard, agent-ops, CJK-first apps.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"pi-package",
|
|
@@ -115,6 +115,16 @@ _TIER2: tuple[AgentRow, ...] = (
|
|
|
115
115
|
skills=False,
|
|
116
116
|
rules_target=".github/copilot-instructions.md",
|
|
117
117
|
),
|
|
118
|
+
AgentRow(
|
|
119
|
+
agent="zed",
|
|
120
|
+
tier=2,
|
|
121
|
+
rules=True,
|
|
122
|
+
commands=False,
|
|
123
|
+
mcp_project=True,
|
|
124
|
+
hooks=False,
|
|
125
|
+
skills=False,
|
|
126
|
+
rules_target=".rules + .zed/settings.json",
|
|
127
|
+
),
|
|
118
128
|
)
|
|
119
129
|
|
|
120
130
|
# Tier 3 — rules floor (generated AGENTS.md + inline MCP guide).
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""Context budget for design-playbook skills (report-only).
|
|
3
|
+
|
|
4
|
+
Walks ``skills/*/SKILL.md`` and ``skills/*/references/*.md`` under the
|
|
5
|
+
package root and reports the context weight of the skill surface: the
|
|
6
|
+
always-loaded SKILL.md face per skill, its on-demand references, and the
|
|
7
|
+
worst-case full-pipeline total. Purely informational — no thresholds, no
|
|
8
|
+
gates, no writes, no network.
|
|
9
|
+
|
|
10
|
+
Token counts are character-level estimates, never tokenizer-exact:
|
|
11
|
+
``ascii_chars / 4 + non_ascii_chars`` (CJK prose runs near one token per
|
|
12
|
+
character; every output carries ``estimate: true``).
|
|
13
|
+
|
|
14
|
+
Usage:
|
|
15
|
+
python scripts/context_budget.py # text view (package root)
|
|
16
|
+
python scripts/context_budget.py --json # machine view
|
|
17
|
+
python scripts/context_budget.py --root <package-root> --top 5
|
|
18
|
+
"""
|
|
19
|
+
from __future__ import annotations
|
|
20
|
+
|
|
21
|
+
import argparse
|
|
22
|
+
import json
|
|
23
|
+
import sys
|
|
24
|
+
from pathlib import Path
|
|
25
|
+
|
|
26
|
+
_PACKAGE_ROOT = Path(__file__).resolve().parent.parent
|
|
27
|
+
|
|
28
|
+
_ESTIMATE_NOTE = (
|
|
29
|
+
"token counts are character-level estimates: ascii/4 + non-ascii chars; "
|
|
30
|
+
"not tokenizer-exact"
|
|
31
|
+
)
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def estimate_tokens(text: str) -> int:
|
|
35
|
+
"""Character-level estimate (see module docstring)."""
|
|
36
|
+
non_ascii = sum(1 for ch in text if ord(ch) > 127)
|
|
37
|
+
return (len(text) - non_ascii) // 4 + non_ascii
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def _measure_file(path: Path) -> dict:
|
|
41
|
+
text = path.read_text(encoding="utf-8", errors="replace")
|
|
42
|
+
chars = len(text)
|
|
43
|
+
return {
|
|
44
|
+
"path": path.name,
|
|
45
|
+
"chars": chars,
|
|
46
|
+
"tokens_est": estimate_tokens(text),
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def measure_skills(package_root: Path) -> dict:
|
|
51
|
+
"""Measure the whole skill surface under *package_root*. Read-only."""
|
|
52
|
+
skills_dir = package_root / "skills"
|
|
53
|
+
skills: list[dict] = []
|
|
54
|
+
for skill_dir in sorted(p for p in skills_dir.iterdir() if p.is_dir()):
|
|
55
|
+
skill_file = skill_dir / "SKILL.md"
|
|
56
|
+
if not skill_file.is_file():
|
|
57
|
+
continue
|
|
58
|
+
skill = {
|
|
59
|
+
"skill": skill_dir.name,
|
|
60
|
+
"skill_md": _measure_file(skill_file),
|
|
61
|
+
"references": [
|
|
62
|
+
_measure_file(p) for p in sorted((skill_dir / "references").glob("*.md"))
|
|
63
|
+
],
|
|
64
|
+
}
|
|
65
|
+
skill["references_tokens_est"] = sum(
|
|
66
|
+
ref["tokens_est"] for ref in skill["references"]
|
|
67
|
+
)
|
|
68
|
+
skill["skill_tokens_est"] = skill["skill_md"]["tokens_est"]
|
|
69
|
+
skill["total_tokens_est"] = (
|
|
70
|
+
skill["skill_tokens_est"] + skill["references_tokens_est"]
|
|
71
|
+
)
|
|
72
|
+
skills.append(skill)
|
|
73
|
+
|
|
74
|
+
all_files = [
|
|
75
|
+
{"skill": s["skill"], **ref}
|
|
76
|
+
for s in skills
|
|
77
|
+
for ref in s["references"]
|
|
78
|
+
] + [{"skill": s["skill"], **s["skill_md"]} for s in skills]
|
|
79
|
+
heaviest = sorted(all_files, key=lambda f: f["tokens_est"], reverse=True)
|
|
80
|
+
|
|
81
|
+
return {
|
|
82
|
+
"estimate": True,
|
|
83
|
+
"estimate_note": _ESTIMATE_NOTE,
|
|
84
|
+
"skills": skills,
|
|
85
|
+
"total": {
|
|
86
|
+
"skill_md_tokens_est": sum(s["skill_tokens_est"] for s in skills),
|
|
87
|
+
"references_tokens_est": sum(s["references_tokens_est"] for s in skills),
|
|
88
|
+
"worst_case_pipeline_tokens_est": sum(s["total_tokens_est"] for s in skills),
|
|
89
|
+
},
|
|
90
|
+
"heaviest_files": heaviest[:10],
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
def _text_view(report: dict) -> str:
|
|
95
|
+
lines = [
|
|
96
|
+
"context budget (estimate: %s)" % report["estimate"],
|
|
97
|
+
report["estimate_note"],
|
|
98
|
+
"",
|
|
99
|
+
f"{'skill':<18} {'skill.md':>10} {'refs':>10} {'total':>10}",
|
|
100
|
+
]
|
|
101
|
+
for s in report["skills"]:
|
|
102
|
+
lines.append(
|
|
103
|
+
f"{s['skill']:<18} {s['skill_tokens_est']:>10} "
|
|
104
|
+
f"{s['references_tokens_est']:>10} {s['total_tokens_est']:>10}"
|
|
105
|
+
)
|
|
106
|
+
t = report["total"]
|
|
107
|
+
lines += [
|
|
108
|
+
"",
|
|
109
|
+
f"{'TOTAL':<18} {t['skill_md_tokens_est']:>10} "
|
|
110
|
+
f"{t['references_tokens_est']:>10} "
|
|
111
|
+
f"{t['worst_case_pipeline_tokens_est']:>10}",
|
|
112
|
+
"",
|
|
113
|
+
"heaviest files (tokens_est):",
|
|
114
|
+
]
|
|
115
|
+
for f in report["heaviest_files"]:
|
|
116
|
+
lines.append(f" {f['tokens_est']:>8} {f['skill']}/{f['path']}")
|
|
117
|
+
return "\n".join(lines)
|
|
118
|
+
|
|
119
|
+
|
|
120
|
+
def main(argv: list[str] | None = None) -> int:
|
|
121
|
+
parser = argparse.ArgumentParser(
|
|
122
|
+
prog="context_budget",
|
|
123
|
+
description="Report the context weight of the skill surface (report-only).",
|
|
124
|
+
)
|
|
125
|
+
parser.add_argument(
|
|
126
|
+
"--root",
|
|
127
|
+
default=str(_PACKAGE_ROOT),
|
|
128
|
+
help="package root holding skills/ (default: this package)",
|
|
129
|
+
)
|
|
130
|
+
parser.add_argument("--json", action="store_true", help="machine-readable output")
|
|
131
|
+
parser.add_argument("--top", type=int, default=10, metavar="N",
|
|
132
|
+
help="heaviest-file list length (default 10)")
|
|
133
|
+
args = parser.parse_args(argv)
|
|
134
|
+
|
|
135
|
+
root = Path(args.root).resolve()
|
|
136
|
+
report = measure_skills(root)
|
|
137
|
+
report["heaviest_files"] = report["heaviest_files"][: max(args.top, 0)]
|
|
138
|
+
|
|
139
|
+
if args.json:
|
|
140
|
+
print(json.dumps(report, ensure_ascii=False, indent=2))
|
|
141
|
+
else:
|
|
142
|
+
print(_text_view(report))
|
|
143
|
+
return 0
|
|
144
|
+
|
|
145
|
+
|
|
146
|
+
if __name__ == "__main__":
|
|
147
|
+
sys.exit(main())
|
package/scripts/doctor.py
CHANGED
|
@@ -59,6 +59,7 @@ _ORPHAN_SCAN_DIRS: dict[str, tuple[str, ...]] = {
|
|
|
59
59
|
"gemini-cli": (".gemini/commands",),
|
|
60
60
|
"windsurf": (".windsurf/rules", ".windsurf/workflows"),
|
|
61
61
|
"github-copilot": (".github/instructions",),
|
|
62
|
+
"zed": (".zed",),
|
|
62
63
|
}
|
|
63
64
|
# Whole-file candidates carrying the generated-by marker outside namespaced
|
|
64
65
|
# dirs (marker-block targets are always re-rendered in place, so they are
|
|
@@ -68,6 +69,7 @@ _WHOLE_FILE_CANDIDATES = (
|
|
|
68
69
|
"GEMINI.md",
|
|
69
70
|
".github/copilot-instructions.md",
|
|
70
71
|
"design-playbook-mcp-setup.md",
|
|
72
|
+
".rules",
|
|
71
73
|
)
|
|
72
74
|
|
|
73
75
|
_LIMITATIONS = (
|
|
@@ -649,6 +649,60 @@ def _agents_md_floor_files(version: str, out_dir: Path) -> list[tuple[str, str]]
|
|
|
649
649
|
return [_marker_entry(out_dir, "AGENTS.md", version, "".join(block_parts))]
|
|
650
650
|
|
|
651
651
|
|
|
652
|
+
# ---------------------------------------------------------------------------
|
|
653
|
+
# Zed renderer (Tier 2)
|
|
654
|
+
# ---------------------------------------------------------------------------
|
|
655
|
+
|
|
656
|
+
# Zed reads project-root rules via a first-match priority list with `.rules`
|
|
657
|
+
# on top, then `.cursorrules`, `.windsurfrules`, `.clinerules`,
|
|
658
|
+
# `.github/copilot-instructions.md`, `CLAUDE.md`, `AGENTS.md`, … (zed.dev
|
|
659
|
+
# agent rules docs, fetched 2026-09-19). Creating `.rules` when a
|
|
660
|
+
# lower-priority competitor exists would silently shadow the user's own
|
|
661
|
+
# rules, so the renderer refuses to introduce one into that state.
|
|
662
|
+
_ZED_RULES_COMPETITORS = (
|
|
663
|
+
".cursorrules",
|
|
664
|
+
".windsurfrules",
|
|
665
|
+
".clinerules",
|
|
666
|
+
"CLAUDE.md",
|
|
667
|
+
".github/copilot-instructions.md",
|
|
668
|
+
)
|
|
669
|
+
|
|
670
|
+
|
|
671
|
+
def _zed_files(version: str, out_dir: Path) -> list[tuple[str, str]]:
|
|
672
|
+
files: list[tuple[str, str]] = []
|
|
673
|
+
skills = _read_skills()
|
|
674
|
+
|
|
675
|
+
# `.rules` — marker-block (may pre-exist). Skip creating a *new* `.rules`
|
|
676
|
+
# while a documented lower-priority competitor is present; an existing
|
|
677
|
+
# `.rules` (ours or the user's) takes refresh/append semantics instead,
|
|
678
|
+
# since Zed already reads that exact file.
|
|
679
|
+
rules_existing = _existing_text(out_dir, ".rules")
|
|
680
|
+
if rules_existing is not None or not any(
|
|
681
|
+
(out_dir / c).exists() for c in _ZED_RULES_COMPETITORS
|
|
682
|
+
):
|
|
683
|
+
block_parts = _digest_head(skills)
|
|
684
|
+
files.append(_marker_entry(out_dir, ".rules", version, "".join(block_parts)))
|
|
685
|
+
|
|
686
|
+
# .zed/settings.json — merge-safe context_servers (project-level MCP).
|
|
687
|
+
# Official docs (fetched 2026-09-19) document the stdio entry as
|
|
688
|
+
# {"command": "…", "args": […], "env": {…}}; community examples also show
|
|
689
|
+
# an object form ({"command": {"path": …, "args": […]}}) — the string
|
|
690
|
+
# form is pinned here per the official page, and the merge keeps any
|
|
691
|
+
# user-managed entries verbatim.
|
|
692
|
+
mcp_servers = _mcp_servers_abs()
|
|
693
|
+
zed_servers: dict = {}
|
|
694
|
+
for name, srv in mcp_servers.items():
|
|
695
|
+
entry: dict = {"command": srv["command"], "args": srv["args"]}
|
|
696
|
+
if "env" in srv and any(v for v in srv["env"].values()):
|
|
697
|
+
entry["env"] = srv["env"]
|
|
698
|
+
zed_servers[name] = entry
|
|
699
|
+
files.append(
|
|
700
|
+
_merge_json_entry(out_dir, ".zed/settings.json", {"context_servers": zed_servers})
|
|
701
|
+
)
|
|
702
|
+
|
|
703
|
+
return files
|
|
704
|
+
|
|
705
|
+
|
|
652
706
|
# ---------------------------------------------------------------------------
|
|
653
707
|
# Renderer dispatch
|
|
654
708
|
# ---------------------------------------------------------------------------
|
|
@@ -664,6 +718,7 @@ _SPECIALIZED_RENDERERS: dict[str, Renderer] = {
|
|
|
664
718
|
"opencode": _opencode_files,
|
|
665
719
|
"windsurf": _windsurf_files,
|
|
666
720
|
"github-copilot": _github_copilot_files,
|
|
721
|
+
"zed": _zed_files,
|
|
667
722
|
}
|
|
668
723
|
|
|
669
724
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Craft audit protocol (registry reference layer)
|
|
2
2
|
|
|
3
|
-
The craft detectors live in the first-party rule registry: [`../../design-playbook/references/rules.md`](../../design-playbook/references/rules.md)
|
|
3
|
+
The craft detectors live in the first-party rule registry: [`../../design-playbook/references/rules.md`](../../design-playbook/references/rules.md). The registry is the single authority for entry definitions — this file never enumerates or summarizes entries (enumerations here have drifted before; read the registry for the current set, families, statuses, and provenance). This file is the thin execution reference: how to evaluate an entry's applicability predicate and how to write its audit row.
|
|
4
4
|
|
|
5
5
|
Run every registry craft entry whose applicability predicate evaluates to `applicable` for implemented UI. Inspect rendered UI at declared target viewports and relevant source. Generic registry outcomes never override a verified project baseline; safety, usability, and explicit declarations still do.
|
|
6
6
|
|
|
@@ -607,3 +607,93 @@ fix: answer the self-check inside the DD rationale with brief facts, add at leas
|
|
|
607
607
|
related:
|
|
608
608
|
history: 1 | 2026-08-28 | docs | initial registry registration, first-party decision-hygiene entry; default-direction examples recorded as dated, refreshable observations (2026-08, dogfood evidence)
|
|
609
609
|
```
|
|
610
|
+
|
|
611
|
+
## STATE-01 — Pending feedback
|
|
612
|
+
|
|
613
|
+
```yaml
|
|
614
|
+
id: STATE-01
|
|
615
|
+
version: 1
|
|
616
|
+
title: Pending feedback
|
|
617
|
+
statement: Asynchronous triggers and data-loading regions present pending feedback that names the operation while it runs (observable); a silent trigger or a bare anonymous spinner leaves users guessing whether the action registered and whether waiting is safe (user impact).
|
|
618
|
+
capability-domain: D4
|
|
619
|
+
executes-in: D4:cross-cutting
|
|
620
|
+
authority: advisory-aesthetic
|
|
621
|
+
applicability-applicable: run has Fill output and the surface contains asynchronous triggers or data-loading regions (form submits, fetch-driven lists, uploads)
|
|
622
|
+
applicability-not-applicable: planning-only run, or every visible operation completes synchronously within the interaction (observable reason required)
|
|
623
|
+
applicability-blocked: pending states cannot be reached or captured with the available evidence surface
|
|
624
|
+
check-type: protocol-check
|
|
625
|
+
check-inputs: rendered pending states per asynchronous path; source request and state handling that gates the pending branch
|
|
626
|
+
signals-rendered: an asynchronous trigger with no visible pending feedback; a pending region that does not name what is loading or processing (a page-wide bare spinner covering one field's request)
|
|
627
|
+
signals-source: request handling with no pending branch bound to a rendered region; a pending flag consumed by nothing visible
|
|
628
|
+
evidence-layers: rendered>=1, source>=1
|
|
629
|
+
evidence-method: expert-review
|
|
630
|
+
severity-default: S2 / fact
|
|
631
|
+
exceptions: operations that complete and surface their result within a single frame on the audited device (the pending state would only flash); background prefetch not tied to a visible user task
|
|
632
|
+
false-positives: progress already carried by a dedicated inline region that names the operation; a verified baseline prescribing silent local mutations
|
|
633
|
+
owner: craft -> R4
|
|
634
|
+
provenance: benchmark-input-only
|
|
635
|
+
status: advisory
|
|
636
|
+
fix: bind each asynchronous path to a pending region that names the operation, keep it scoped to the region it concerns, and resolve it deterministically into the result or failure state (PERF-01 covers the pacing of the feedback itself)
|
|
637
|
+
related: PERF-01@1
|
|
638
|
+
history: 1 | 2026-09-19 | docs | initial registry registration, benchmark-informed state-completeness entry in first-party wording
|
|
639
|
+
```
|
|
640
|
+
|
|
641
|
+
## STATE-02 — Zero-data presentation
|
|
642
|
+
|
|
643
|
+
```yaml
|
|
644
|
+
id: STATE-02
|
|
645
|
+
version: 1
|
|
646
|
+
title: Zero-data presentation
|
|
647
|
+
statement: Data-fed collection surfaces declare what users see when the list is empty on first load and after filtering (observable); a blank region forces users to guess whether data is still loading, absent, or broken, and hides the next action (user impact).
|
|
648
|
+
capability-domain: D4
|
|
649
|
+
executes-in: D4:cross-cutting
|
|
650
|
+
authority: advisory-aesthetic
|
|
651
|
+
applicability-applicable: run has Fill output and the surface contains a collection or list display fed by data
|
|
652
|
+
applicability-not-applicable: planning-only run, or no data-fed collection display in the audited scope (observable reason required)
|
|
653
|
+
applicability-blocked: empty states cannot be reached or captured with the available evidence surface
|
|
654
|
+
check-type: protocol-check
|
|
655
|
+
check-inputs: rendered first-load-empty and filtered-empty presentations; source empty-branch handling for the collection's data source
|
|
656
|
+
signals-rendered: the collection region renders with no content and no hint while its data is empty; an empty presentation that offers no next action or explanation
|
|
657
|
+
signals-source: empty-result branches that render no region content; a data source with an empty path unhandled in the UI layer
|
|
658
|
+
evidence-layers: rendered>=1, source>=1
|
|
659
|
+
evidence-method: expert-review
|
|
660
|
+
severity-default: S2 / fact
|
|
661
|
+
exceptions: create-first surfaces whose documented initial state is intentionally the empty canvas itself; the empty presentation is the specified product behavior (spec or verified baseline)
|
|
662
|
+
false-positives: a populated skeleton resolving within the declared loading flow (judged under STATE-01, not here)
|
|
663
|
+
owner: craft -> R4
|
|
664
|
+
provenance: benchmark-input-only
|
|
665
|
+
status: advisory
|
|
666
|
+
fix: give every data-fed collection an explicit empty presentation that says what the region is for and offers the next action, visually distinct from the pending presentation (STATE-01) and from failure feedback (STATE-03)
|
|
667
|
+
related: STATE-01@1, STATE-03@1
|
|
668
|
+
history: 1 | 2026-09-19 | docs | initial registry registration, benchmark-informed state-completeness entry in first-party wording
|
|
669
|
+
```
|
|
670
|
+
|
|
671
|
+
## STATE-03 — Failure feedback
|
|
672
|
+
|
|
673
|
+
```yaml
|
|
674
|
+
id: STATE-03
|
|
675
|
+
version: 1
|
|
676
|
+
title: Failure feedback
|
|
677
|
+
statement: Failed asynchronous paths present recoverable feedback that names what failed and the next action — retry, undo, or exit — instead of failing silently to the console or surfacing a raw exception trace (observable); users stranded at a failure without a recovery path abandon the flow or retrigger the damage (user impact).
|
|
678
|
+
capability-domain: D4
|
|
679
|
+
executes-in: D4:cross-cutting
|
|
680
|
+
authority: advisory-aesthetic
|
|
681
|
+
applicability-applicable: run has Fill output and the audited scope declares or triggers failure paths for asynchronous operations
|
|
682
|
+
applicability-not-applicable: run has no asynchronous failure path in the audited scope (observable reason required)
|
|
683
|
+
applicability-blocked: failure states cannot be reached or captured with the available evidence surface
|
|
684
|
+
check-type: protocol-check
|
|
685
|
+
check-inputs: rendered failure states per asynchronous failure path; source error handling bound to request or mutation paths
|
|
686
|
+
signals-rendered: an asynchronous action fails with no rendered feedback beyond the previous state; a raw exception or stack trace rendered to the user; a failure banner offering no retry, undo, or exit action
|
|
687
|
+
signals-source: error branches that only log to the console; a catch path with no bound rendered region or recovery action
|
|
688
|
+
evidence-layers: rendered>=1, source>=1
|
|
689
|
+
evidence-method: expert-review
|
|
690
|
+
severity-default: S2 / fact
|
|
691
|
+
exceptions: diagnostic or operator surfaces where raw detail is the declared point (spec L1 or verified baseline); security-sensitive withholdings — still state what the user can do next even when the cause is withheld
|
|
692
|
+
false-positives: failure feedback deferred by a declared retry policy that still lands in a rendered recovery state; terse diagnostics for a declared expert audience (spec L1)
|
|
693
|
+
owner: craft -> R4
|
|
694
|
+
provenance: benchmark-input-only
|
|
695
|
+
status: advisory
|
|
696
|
+
fix: bind each failure path to a rendered recovery region naming what failed where safe and what to do next, with retry or exit always present; keep the tone even (COPY-03 covers the message wording)
|
|
697
|
+
related: COPY-03@1, STATE-01@1
|
|
698
|
+
history: 1 | 2026-09-19 | docs | initial registry registration, benchmark-informed state-completeness entry in first-party wording
|
|
699
|
+
```
|