@design-ai/cli 4.57.0 → 4.58.0
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/.claude-plugin/plugin.json +1 -1
- package/CHANGELOG.md +32 -0
- package/README.ko.md +4 -4
- package/README.md +4 -4
- package/cli/commands/route.mjs +13 -1
- package/cli/lib/recall.mjs +2 -2
- package/cli/lib/route.mjs +62 -5
- package/cli/lib/search-ranked.mjs +33 -1
- package/docs/ROADMAP.md +29 -0
- package/docs/external-status.md +6 -6
- package/knowledge/COVERAGE.md +9 -7
- package/knowledge/i18n/korean-density-conventions.md +108 -0
- package/knowledge/i18n/korean-product-conventions.md +2 -0
- package/knowledge/patterns/async-control.md +233 -0
- package/knowledge/patterns/error-states.md +1 -0
- package/knowledge/patterns/form-design.md +2 -0
- package/package.json +1 -1
- package/tools/audit/registry-smoke.py +3 -3
- package/tools/audit/smoke_assertions.py +3 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "design-ai",
|
|
3
|
-
"version": "4.
|
|
3
|
+
"version": "4.58.0",
|
|
4
4
|
"description": "Senior product designer for any AI coding agent. 20 skills, 17 commands, 4 review agents covering UI/UX, website improvement, design systems, motion, illustration, print, video, game UI, conversational, and spatial design. Korean market depth.",
|
|
5
5
|
"author": {
|
|
6
6
|
"name": "Sungjin Park",
|
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,38 @@
|
|
|
2
2
|
|
|
3
3
|
User-facing release notes for design-ai. Versions follow semver.
|
|
4
4
|
|
|
5
|
+
## v4.58.0 — Retrieval Across Surfaces and Corpus Depth (2026-07)
|
|
6
|
+
|
|
7
|
+
Completes the retrieval integration started in v4.57.0 by bringing it to the last untouched surface (`route --explain`), refines recall quality so injected knowledge is real design guidance rather than index files, and closes the remaining dogfood-named corpus gaps. All additions are additive and backward-compatible; default output is unchanged unless a flag is passed.
|
|
8
|
+
|
|
9
|
+
### Added
|
|
10
|
+
- `route --explain` now includes an advisory "Related knowledge" section — brief-relevant corpus knowledge recalled by the shared lexical scorer, excluding what the route already curates. This is advisory only: route ids, scores, and curated knowledge are unchanged, and default `route` output is byte-unchanged. `design_ai_route`'s existing `explain` parameter carries it to MCP automatically. Retrieval now spans every surface: `search --ranked`, `prompt`/`pack --with-recall`, `learn --recall`, and `route --explain`.
|
|
11
|
+
- `knowledge/patterns/async-control.md` — the async action in-flight lifecycle: pending states, double-submit prevention, duration-matched loading affordances, optimistic updates with rollback, debounce/throttle, cancellation and out-of-order handling, concurrency policy, timeouts, and the accessibility floor.
|
|
12
|
+
- `knowledge/i18n/korean-density-conventions.md` — Korean B2B/enterprise density: the density ladder, table-first layouts, label-left multi-column forms, dense navigation, typography at density, and where density must yield to accessibility.
|
|
13
|
+
|
|
14
|
+
### Changed
|
|
15
|
+
- Recall injection surfaces (`prompt`/`pack --with-recall`, `learn --recall`, `route --explain`) now exclude generated index/meta files (`COVERAGE.md`, `INDEX.md`, `docs/reference/*`) so injected context is real design knowledge; raw `search --ranked` still returns those files when explicitly searched.
|
|
16
|
+
- Knowledge corpus is now 94 files (from 92).
|
|
17
|
+
|
|
18
|
+
### Fixed
|
|
19
|
+
- `tools/audit/registry-smoke.py`'s learn-relevance assertion expected a stale token order (`keyboard, accessibility`) after the v4.57 shared-scorer change sorted matched tokens; aligned it with `package-smoke.py` and the real CLI output so the live post-publish registry smoke matches the published package.
|
|
20
|
+
|
|
21
|
+
### Verified
|
|
22
|
+
- All 8 audits passed.
|
|
23
|
+
- `npm run release:check`.
|
|
24
|
+
- `npm run release:metadata`.
|
|
25
|
+
- `git diff --check`.
|
|
26
|
+
- Main-branch GitHub Actions (`Design-AI audit`, `Deploy doc site`) passed for the constituent commits.
|
|
27
|
+
|
|
28
|
+
### Versions
|
|
29
|
+
- `package.json` + `.claude-plugin/plugin.json`: 4.57.0 → 4.58.0.
|
|
30
|
+
- `vscode-extension/package.json`: remains 0.4.1.
|
|
31
|
+
|
|
32
|
+
### What this enables
|
|
33
|
+
- Agents routing a brief now see additional relevant knowledge beyond the route's curated set, and every retrieval surface injects real design guidance rather than index tables.
|
|
34
|
+
- The Korean-market corpus gains async-control and B2B density coverage that dogfood runs specifically named as missing.
|
|
35
|
+
- Publishing v4.58.0 realigns `main` with the published package so the live registry smoke verifies cleanly again.
|
|
36
|
+
|
|
5
37
|
## v4.57.0 — Local Retrieval Memory: Ranked Search and Optional Embeddings (2026-07)
|
|
6
38
|
|
|
7
39
|
Ships the Phase 754 AI-learning retrieval work: a deterministic, zero-dependency local retrieval layer over the knowledge corpus and the local learning profile, plus an opt-in local embedding rerank backend. Defaults are unchanged — `search`, `route`, `prompt`, and `pack` behave exactly as before unless a new flag is passed — so this release is additive and backward-compatible. It also moves `refs/` source links behind generated reference pages and hardens the docs deploy against intermittent GitHub Pages failures.
|
package/README.ko.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
[](https://github.com/sungjin9288/design-ai/actions/workflows/audit.yml)
|
|
4
4
|
[](https://sungjin9288.github.io/design-ai/ko/)
|
|
5
|
-
[](knowledge/PRINCIPLES.md)
|
|
6
6
|
[](examples/README.md)
|
|
7
7
|
[](skills/README.md)
|
|
8
8
|
|
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
|
|
13
13
|
> **모델이 아니에요. 파인튜닝도 아니에요.** 디자인 전문 지식을 구조화한 코퍼스 + 에이전트가 바로 실행할 수 있는 지시문이에요. 범용 LLM을 이번 세션에서만큼은 시니어 디자이너로 바꿔주는 셈이에요.
|
|
14
14
|
|
|
15
|
-
> **배포 상태, 2026-07-
|
|
15
|
+
> **배포 상태, 2026-07-03 확인:** 로컬 `npm run release:check`는 통과했고, GitHub Pages 문서는 live 상태이며, GitHub Release `v4.57.0`과 npm `@design-ai/cli@4.57.0`(`latest`) publish가 provenance와 함께 확인됐어요. Homebrew formula는 `v4.57.0`에 pinning되어 있고, VS Code Marketplace에는 `sungjin.design-ai-vscode@0.4.1`이 공개되어 있어요. v4.57.0 post-publish live registry smoke는 stale 어서션에 걸렸고(`main`에서 수정됨), 패키지는 pre-publish packed-tarball smoke로 검증됐어요. 자세한 내용은 [`docs/external-status.md`](docs/external-status.md)를 확인하세요.
|
|
16
16
|
|
|
17
17
|
## 한눈에 보는 커버리지
|
|
18
18
|
|
|
@@ -107,7 +107,7 @@ design-ai/
|
|
|
107
107
|
│
|
|
108
108
|
├── refs/ # Sparse-clone된 업스트림 소스 (gitignored)
|
|
109
109
|
│
|
|
110
|
-
├── knowledge/ #
|
|
110
|
+
├── knowledge/ # 94개 손으로 쓴 + 추출된 지식 파일
|
|
111
111
|
│ ├── design-tokens/ # W3C DTCG, OKLCH, HCT
|
|
112
112
|
│ ├── components/ # Ant + MUI + shadcn 합성
|
|
113
113
|
│ ├── patterns/ # 인증, 가격, 랜딩 히어로, 브랜드, 이메일 등
|
|
@@ -171,7 +171,7 @@ design-ai는 한국 시장을 1순위로 만들어졌고, 글로벌 시장 패
|
|
|
171
171
|
|
|
172
172
|
## 상태
|
|
173
173
|
|
|
174
|
-
전체 단계 로그는 [`docs/ROADMAP.md`](docs/ROADMAP.md), 현재 완료 범위는 [`docs/PRODUCT-READINESS.md`](docs/PRODUCT-READINESS.md)에서 확인하세요. 현재 **v4.
|
|
174
|
+
전체 단계 로그는 [`docs/ROADMAP.md`](docs/ROADMAP.md), 현재 완료 범위는 [`docs/PRODUCT-READINESS.md`](docs/PRODUCT-READINESS.md)에서 확인하세요. 현재 **v4.57.0**: public npm publish, provenance-backed GitHub Actions release, public registry smoke, Website Console MCP readiness, workspace learning restore/eval coverage, handoff bundle verification, 90%+ component coverage가 완료됐어요.
|
|
175
175
|
|
|
176
176
|
핵심 디자인 컨설팅 워크플로우는 로컬 릴리스 기준으로 준비되어 있어요. 웹사이트 개선 컨트롤 타워는 zero-dependency static Web App과 `website-improvement` route/skill/command로 제공되고, Site Profile, audit checklist, MCP readiness, refactor prompt, handoff evidence tracking, bundle export/verify/repair를 한 번에 다뤄요. 로컬 학습 선호도는 `design-ai learn`으로 관리해요 — profile bootstrap, feedback 캡처, 읽기 전용 signal registry, 반복 QA 신호에서 만드는 skill 제안, 그리고 backup/restore/curate/audit까지 전부 로컬에서만 동작하는 opt-in 기능이에요. AI 모델 학습이나 fine-tuning은 여전히 현재 배포 범위 밖이에요.
|
|
177
177
|
|
package/README.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
[](https://github.com/sungjin9288/design-ai/actions/workflows/audit.yml)
|
|
4
4
|
[](https://sungjin9288.github.io/design-ai/)
|
|
5
|
-
[](knowledge/PRINCIPLES.md)
|
|
6
6
|
[](examples/README.md)
|
|
7
7
|
[](skills/README.md)
|
|
8
8
|
|
|
@@ -12,7 +12,7 @@ A model-agnostic design knowledge base + skill system. Drop it in front of any A
|
|
|
12
12
|
|
|
13
13
|
> **Not a model. Not a fine-tune.** A structured corpus of design expertise + agent-ready instructions that turn a general-purpose LLM into an expert.
|
|
14
14
|
|
|
15
|
-
> **Distribution status, checked 2026-07-
|
|
15
|
+
> **Distribution status, checked 2026-07-03:** local `npm run release:check` passes, GitHub Pages docs are live, GitHub Release `v4.57.0` is published, `@design-ai/cli@4.57.0` is public on npm (`latest`) with provenance and registry smoke coverage, the Homebrew formula is pinned to `v4.57.0`, and `sungjin.design-ai-vscode@0.4.1` is public on the VS Code Marketplace. The v4.57.0 post-publish live registry smoke hit a stale assertion (fixed on `main`); the package is validated by the pre-publish packed-tarball smoke. See [`docs/external-status.md`](docs/external-status.md).
|
|
16
16
|
|
|
17
17
|
## Coverage at a glance
|
|
18
18
|
|
|
@@ -107,7 +107,7 @@ design-ai/
|
|
|
107
107
|
│
|
|
108
108
|
├── refs/ # Sparse-cloned upstream sources (gitignored)
|
|
109
109
|
│
|
|
110
|
-
├── knowledge/ #
|
|
110
|
+
├── knowledge/ # 94 hand-written + extracted knowledge files
|
|
111
111
|
│ ├── design-tokens/ # Token systems (W3C DTCG, OKLCH, HCT)
|
|
112
112
|
│ ├── components/ # Component synthesis (Ant + MUI + shadcn)
|
|
113
113
|
│ ├── patterns/ # Auth, pricing, landing hero, brand, email, ...
|
|
@@ -213,7 +213,7 @@ Refresh refs/ on demand: `./tools/extractors/run-all.sh`.
|
|
|
213
213
|
|
|
214
214
|
## Status
|
|
215
215
|
|
|
216
|
-
See [`docs/ROADMAP.md`](docs/ROADMAP.md) for the full phase log and [`docs/PRODUCT-READINESS.md`](docs/PRODUCT-READINESS.md) for the current completion boundary. Currently at **v4.
|
|
216
|
+
See [`docs/ROADMAP.md`](docs/ROADMAP.md) for the full phase log and [`docs/PRODUCT-READINESS.md`](docs/PRODUCT-READINESS.md) for the current completion boundary. Currently at **v4.57.0**: public npm publish, provenance-backed GitHub Actions release, public registry smoke, Website Console MCP readiness, workspace learning restore/eval coverage, handoff bundle verification, and 90%+ component coverage are complete.
|
|
217
217
|
|
|
218
218
|
Core design consulting workflows are locally release-ready. The website improvement control tower ships as a zero-dependency static Web App plus a `website-improvement` route/skill/command, covering Site Profiles, audit checklists, MCP readiness, refactor prompts, handoff evidence tracking, and bundle export/verify/repair. Local learning preferences are available through `design-ai learn` — profile bootstrap, feedback capture, a read-only signals registry, skill-proposal generation from repeated QA signals, and full backup/restore/curate/audit tooling, all local-only and opt-in. AI model training or fine-tuning remains outside the shipped scope.
|
|
219
219
|
|
package/cli/commands/route.mjs
CHANGED
|
@@ -28,7 +28,7 @@ function printHelp() {
|
|
|
28
28
|
console.log(" --stdin Read the task brief, or route eval JSON with --eval, from standard input");
|
|
29
29
|
console.log(" --list List route ids without scoring a brief");
|
|
30
30
|
console.log(" --limit N Maximum route recommendations to return, 1-10. Default: 3");
|
|
31
|
-
console.log(" --explain Include route scoring
|
|
31
|
+
console.log(" --explain Include route scoring, reference coverage, and related knowledge");
|
|
32
32
|
console.log(" --eval-template Generate a runnable route eval checkpoint JSON template");
|
|
33
33
|
console.log(" --eval Run deterministic route-selection checkpoint cases");
|
|
34
34
|
console.log(" --strict With --eval, exit non-zero on warning or failure");
|
|
@@ -78,6 +78,17 @@ function printRoute(route, index, explain = false) {
|
|
|
78
78
|
for (const knowledge of route.knowledge) {
|
|
79
79
|
console.log(` read: ${formatPath(knowledge)}`);
|
|
80
80
|
}
|
|
81
|
+
|
|
82
|
+
if (explain) {
|
|
83
|
+
const related = route.relatedKnowledge || [];
|
|
84
|
+
if (related.length === 0) {
|
|
85
|
+
console.log(" related: (none)");
|
|
86
|
+
} else {
|
|
87
|
+
for (const item of related) {
|
|
88
|
+
console.log(` related: ${item.id} ${dim(`(score ${item.score.toFixed(2)})`)}`);
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
}
|
|
81
92
|
}
|
|
82
93
|
|
|
83
94
|
function printCatalogRoute(route, index) {
|
|
@@ -211,6 +222,7 @@ export async function runRoute(args) {
|
|
|
211
222
|
brief,
|
|
212
223
|
sourceRoot: DESIGN_AI_HOME,
|
|
213
224
|
limit: parsed.limit,
|
|
225
|
+
explain: parsed.explain,
|
|
214
226
|
});
|
|
215
227
|
|
|
216
228
|
const payload = {
|
package/cli/lib/recall.mjs
CHANGED
|
@@ -109,7 +109,7 @@ export function buildRecallContext({
|
|
|
109
109
|
const candidateCount = collectCorpusDocuments({ designAiPath, dirs }).length;
|
|
110
110
|
|
|
111
111
|
const hits = query
|
|
112
|
-
? rankedSearchCorpus({ query, designAiPath, dirs, limit: recallLimit }).hits
|
|
112
|
+
? rankedSearchCorpus({ query, designAiPath, dirs, limit: recallLimit, excludeGeneratedIndex: true }).hits
|
|
113
113
|
: [];
|
|
114
114
|
|
|
115
115
|
const selected = hits.map((hit) => ({
|
|
@@ -150,7 +150,7 @@ export function buildLearnRecall({
|
|
|
150
150
|
|
|
151
151
|
const corpusCandidateCount = collectCorpusDocuments({ designAiPath, dirs }).length;
|
|
152
152
|
const corpusHits = normalizedQuery
|
|
153
|
-
? rankedSearchCorpus({ query: normalizedQuery, designAiPath, dirs, limit }).hits
|
|
153
|
+
? rankedSearchCorpus({ query: normalizedQuery, designAiPath, dirs, limit, excludeGeneratedIndex: true }).hits
|
|
154
154
|
: [];
|
|
155
155
|
const corpusSelected = corpusHits.map((hit) => ({
|
|
156
156
|
id: hit.relPath,
|
package/cli/lib/route.mjs
CHANGED
|
@@ -7,6 +7,7 @@ import {
|
|
|
7
7
|
import path from "node:path";
|
|
8
8
|
|
|
9
9
|
import { parseBriefSourceFlag } from "./brief.mjs";
|
|
10
|
+
import { rankedSearchCorpus } from "./search-ranked.mjs";
|
|
10
11
|
import { suggestNearest, unknownOptionMessage } from "./suggest.mjs";
|
|
11
12
|
|
|
12
13
|
function exists(p) {
|
|
@@ -35,6 +36,17 @@ const ROUTE_OPTIONS = [
|
|
|
35
36
|
];
|
|
36
37
|
const ROUTE_EVAL_VERSION = 1;
|
|
37
38
|
const ROUTE_EVAL_DEFAULT_LIMIT = 3;
|
|
39
|
+
// Advisory "Related knowledge" recall (docs/AI-LEARNING-PHASE2.md, "Phase A
|
|
40
|
+
// implementation review" Q3): under `--explain` only, surface the top corpus
|
|
41
|
+
// knowledge/ files recalled by the shipped deterministic lexical scorer that the
|
|
42
|
+
// route's curated `knowledge` list does NOT already point to. Purely additive —
|
|
43
|
+
// it never changes route selection, ids, scores, or the curated list. Restricted
|
|
44
|
+
// to knowledge/ so it surfaces design knowledge, not docs/QUICKSTART. The recall
|
|
45
|
+
// pulls RELATED_KNOWLEDGE_RECALL_LIMIT candidates then keeps the top
|
|
46
|
+
// RELATED_KNOWLEDGE_KEEP after excluding the curated set.
|
|
47
|
+
const RELATED_KNOWLEDGE_DIRS = ["knowledge"];
|
|
48
|
+
const RELATED_KNOWLEDGE_RECALL_LIMIT = 10;
|
|
49
|
+
const RELATED_KNOWLEDGE_KEEP = 3;
|
|
38
50
|
const CONFIDENCE_ORDER = {
|
|
39
51
|
low: 1,
|
|
40
52
|
medium: 2,
|
|
@@ -456,6 +468,35 @@ function routeExplanation({ hits, command, skills, agents, knowledge, forced = f
|
|
|
456
468
|
};
|
|
457
469
|
}
|
|
458
470
|
|
|
471
|
+
// Advisory related-knowledge recall for a single route. REUSES rankedSearchCorpus
|
|
472
|
+
// (the shipped deterministic lexical scorer — score desc, id asc) scoped to
|
|
473
|
+
// knowledge/, excludes the route's curated knowledge relPaths, and keeps the top
|
|
474
|
+
// RELATED_KNOWLEDGE_KEEP remaining. Returns [] on empty brief or no hits.
|
|
475
|
+
function relatedKnowledgeFor({ brief, sourceRoot, curatedRelPaths }) {
|
|
476
|
+
const query = String(brief || "").trim();
|
|
477
|
+
if (!query) return [];
|
|
478
|
+
|
|
479
|
+
const curated = new Set(curatedRelPaths);
|
|
480
|
+
const { hits } = rankedSearchCorpus({
|
|
481
|
+
query,
|
|
482
|
+
dirs: RELATED_KNOWLEDGE_DIRS,
|
|
483
|
+
limit: RELATED_KNOWLEDGE_RECALL_LIMIT,
|
|
484
|
+
designAiPath: sourceRoot,
|
|
485
|
+
// Recall/injection surface: keep generated index/meta docs (COVERAGE.md,
|
|
486
|
+
// INDEX.md, docs/reference/*) out of the advisory related-knowledge list.
|
|
487
|
+
excludeGeneratedIndex: true,
|
|
488
|
+
});
|
|
489
|
+
|
|
490
|
+
return hits
|
|
491
|
+
.filter((hit) => !curated.has(hit.relPath))
|
|
492
|
+
.slice(0, RELATED_KNOWLEDGE_KEEP)
|
|
493
|
+
.map((hit) => ({
|
|
494
|
+
id: hit.relPath,
|
|
495
|
+
score: hit.score,
|
|
496
|
+
matchedTokens: hit.matchedTokens,
|
|
497
|
+
}));
|
|
498
|
+
}
|
|
499
|
+
|
|
459
500
|
function routeToResult(route, sourceRoot, hits, options = {}) {
|
|
460
501
|
const command = route.command
|
|
461
502
|
? { path: route.command, exists: exists(path.join(sourceRoot, route.command)) }
|
|
@@ -467,6 +508,17 @@ function routeToResult(route, sourceRoot, hits, options = {}) {
|
|
|
467
508
|
const fallback = Boolean(options.fallback);
|
|
468
509
|
const catalog = Boolean(options.catalog);
|
|
469
510
|
|
|
511
|
+
// Advisory related-knowledge is attached ONLY when explain is requested, so the
|
|
512
|
+
// default `route` JSON stays byte-unchanged. `knowledge` already merges
|
|
513
|
+
// COMMON_KNOWLEDGE + route.knowledge, so its relPaths are the dedupe set.
|
|
514
|
+
const relatedKnowledge = options.explain
|
|
515
|
+
? relatedKnowledgeFor({
|
|
516
|
+
brief: options.brief || "",
|
|
517
|
+
sourceRoot,
|
|
518
|
+
curatedRelPaths: knowledge.map((entry) => entry.path),
|
|
519
|
+
})
|
|
520
|
+
: null;
|
|
521
|
+
|
|
470
522
|
return {
|
|
471
523
|
id: route.id,
|
|
472
524
|
label: route.label,
|
|
@@ -479,6 +531,7 @@ function routeToResult(route, sourceRoot, hits, options = {}) {
|
|
|
479
531
|
knowledge,
|
|
480
532
|
keywords: route.keywords,
|
|
481
533
|
explanation: routeExplanation({ hits, command, skills, agents, knowledge, forced, fallback, catalog }),
|
|
534
|
+
...(relatedKnowledge ? { relatedKnowledge } : {}),
|
|
482
535
|
...(forced ? { forced: true } : {}),
|
|
483
536
|
...(fallback ? { fallback: true } : {}),
|
|
484
537
|
};
|
|
@@ -501,18 +554,22 @@ export function routeById({ routeId, sourceRoot }) {
|
|
|
501
554
|
};
|
|
502
555
|
}
|
|
503
556
|
|
|
504
|
-
function fallbackResult(sourceRoot) {
|
|
557
|
+
function fallbackResult(sourceRoot, options = {}) {
|
|
505
558
|
const route = ROUTES.find((item) => item.id === "design-from-brief");
|
|
506
559
|
return {
|
|
507
|
-
...routeToResult(route, sourceRoot, [], { fallback: true }),
|
|
560
|
+
...routeToResult(route, sourceRoot, [], { fallback: true, ...options }),
|
|
508
561
|
confidence: "low",
|
|
509
562
|
};
|
|
510
563
|
}
|
|
511
564
|
|
|
512
|
-
export function routeBrief({ brief, sourceRoot, limit = 3 }) {
|
|
565
|
+
export function routeBrief({ brief, sourceRoot, limit = 3, explain = false }) {
|
|
513
566
|
const normalized = brief.trim();
|
|
514
567
|
if (!normalized) return [];
|
|
515
568
|
|
|
569
|
+
// Only compute advisory related-knowledge under --explain; keep the default
|
|
570
|
+
// routing (keyword scoring, ordering, selection) completely unchanged.
|
|
571
|
+
const resultOptions = explain ? { explain: true, brief: normalized } : {};
|
|
572
|
+
|
|
516
573
|
const scored = ROUTES
|
|
517
574
|
.map((route) => ({ route, hits: keywordHits(normalized, route.keywords) }))
|
|
518
575
|
.filter((item) => item.hits.length > 0)
|
|
@@ -521,10 +578,10 @@ export function routeBrief({ brief, sourceRoot, limit = 3 }) {
|
|
|
521
578
|
return a.route.label.localeCompare(b.route.label);
|
|
522
579
|
})
|
|
523
580
|
.slice(0, limit)
|
|
524
|
-
.map((item) => routeToResult(item.route, sourceRoot, item.hits));
|
|
581
|
+
.map((item) => routeToResult(item.route, sourceRoot, item.hits, resultOptions));
|
|
525
582
|
|
|
526
583
|
if (scored.length > 0) return scored;
|
|
527
|
-
return [fallbackResult(sourceRoot)];
|
|
584
|
+
return [fallbackResult(sourceRoot, resultOptions)];
|
|
528
585
|
}
|
|
529
586
|
|
|
530
587
|
function isoTimestamp(now = new Date()) {
|
|
@@ -24,6 +24,22 @@ import { DEFAULT_SEARCH_DIRS } from "./search.mjs";
|
|
|
24
24
|
|
|
25
25
|
const RANKED_PREVIEW_LEN = 120;
|
|
26
26
|
|
|
27
|
+
// Predicate for GENERATED index/meta docs that must be kept out of the RECALL /
|
|
28
|
+
// context-injection layer (pack/prompt --with-recall, learn --recall corpus side,
|
|
29
|
+
// route --explain related knowledge). These files rank on keyword density but are
|
|
30
|
+
// meta/index tables, not design knowledge worth injecting into an agent's context.
|
|
31
|
+
// Rule: true when the basename is `COVERAGE.md` or `INDEX.md` (generated coverage
|
|
32
|
+
// table / component index), OR the forward-slash-normalized path starts with
|
|
33
|
+
// `docs/reference/` (generated upstream-reference pages). Raw `search --ranked`
|
|
34
|
+
// does NOT apply this — a user explicitly searching may legitimately want an index.
|
|
35
|
+
export function isGeneratedIndexDoc(relPath) {
|
|
36
|
+
const normalized = String(relPath || "").replace(/\\/g, "/");
|
|
37
|
+
const basename = normalized.slice(normalized.lastIndexOf("/") + 1);
|
|
38
|
+
return basename === "COVERAGE.md"
|
|
39
|
+
|| basename === "INDEX.md"
|
|
40
|
+
|| normalized.startsWith("docs/reference/");
|
|
41
|
+
}
|
|
42
|
+
|
|
27
43
|
// N = max(limit*5, 25): the number of top lexical candidates handed to the embedding
|
|
28
44
|
// reranker. Documented constant (docs/AI-LEARNING-PHASE2.md, Phase B CLI wiring).
|
|
29
45
|
export function embeddingCandidateCount(limit) {
|
|
@@ -66,12 +82,28 @@ export function rankedSearchCorpus({
|
|
|
66
82
|
dirs = DEFAULT_SEARCH_DIRS,
|
|
67
83
|
limit = 20,
|
|
68
84
|
indexDir = defaultIndexDir(),
|
|
85
|
+
// Opt-in RECALL filter: when true, drop generated index/meta docs
|
|
86
|
+
// (isGeneratedIndexDoc) BEFORE applying `limit`, so the limit fills with real
|
|
87
|
+
// knowledge instead of index files. Default false keeps raw `search --ranked`
|
|
88
|
+
// byte-unchanged. Filtering before the limit preserves determinism (score desc,
|
|
89
|
+
// id asc) — the rank pass already orders fully, we only remove excluded ids.
|
|
90
|
+
excludeGeneratedIndex = false,
|
|
69
91
|
} = {}) {
|
|
70
92
|
const documents = collectCorpusDocuments({ designAiPath, dirs });
|
|
71
93
|
const stats = buildLexicalStats(documents.map(({ id, text }) => ({ id, text })));
|
|
72
94
|
const textById = new Map(documents.map((doc) => [doc.id, doc.text]));
|
|
73
95
|
|
|
74
|
-
|
|
96
|
+
// When excluding, rank the FULL candidate pool first (limit = documents.length)
|
|
97
|
+
// so that filtering out index docs cannot let a real-knowledge hit fall off the
|
|
98
|
+
// pre-limit cutoff; then re-apply `limit`. Ordering is unchanged (score desc, id asc).
|
|
99
|
+
const ranked = rankLexical(query, stats, {
|
|
100
|
+
limit: excludeGeneratedIndex ? documents.length : limit,
|
|
101
|
+
});
|
|
102
|
+
const filtered = excludeGeneratedIndex
|
|
103
|
+
? ranked.filter((hit) => !isGeneratedIndexDoc(hit.id)).slice(0, limit)
|
|
104
|
+
: ranked;
|
|
105
|
+
|
|
106
|
+
const hits = filtered.map((hit) => ({
|
|
75
107
|
relPath: hit.id,
|
|
76
108
|
file: path.join(designAiPath, hit.id),
|
|
77
109
|
score: hit.score,
|
package/docs/ROADMAP.md
CHANGED
|
@@ -1,5 +1,34 @@
|
|
|
1
1
|
# Roadmap
|
|
2
2
|
|
|
3
|
+
## Phase 756 — Retrieval Across Surfaces and Corpus Depth Release (v4.58.0) ✓ ready
|
|
4
|
+
|
|
5
|
+
Groups the post-v4.57.0 retrieval completion and corpus work into one release: `route --explain` related-knowledge enrichment (retrieval now spans every surface), recall exclusion of generated index/meta files, the async-control and Korean B2B density knowledge files, and the registry-smoke assertion fix that realigns the live post-publish smoke with the shared scorer. Defaults are unchanged; the release is additive and backward-compatible.
|
|
6
|
+
|
|
7
|
+
### Changed
|
|
8
|
+
- `route --explain` gains an advisory related-knowledge section (routing unchanged), completing retrieval across `search`/`prompt`/`pack`/`learn`/`route`; carried to MCP via the existing `design_ai_route` `explain` parameter.
|
|
9
|
+
- Recall injection excludes generated index/meta files so injected context is real design knowledge; raw `search --ranked` is unchanged.
|
|
10
|
+
- Added `knowledge/patterns/async-control.md` and `knowledge/i18n/korean-density-conventions.md` (corpus now 94 files), closing the remaining dogfood-named gaps.
|
|
11
|
+
- Fixed the stale `registry-smoke.py` learn-relevance token-order assertion so the live post-publish registry smoke matches the published package.
|
|
12
|
+
|
|
13
|
+
### Verified
|
|
14
|
+
- All 8 audits passed.
|
|
15
|
+
- `npm run release:check` (unit tests, strict audits, whitespace, package contents, release metadata, release self-tests, packed-tarball smoke).
|
|
16
|
+
- `npm run release:metadata`.
|
|
17
|
+
- `git diff --check`.
|
|
18
|
+
- Main-branch GitHub Actions (`Design-AI audit`, `Deploy doc site`) passed for the constituent commits.
|
|
19
|
+
|
|
20
|
+
### Versions
|
|
21
|
+
- `package.json` + `.claude-plugin/plugin.json`: 4.57.0 → 4.58.0.
|
|
22
|
+
- `vscode-extension/package.json`: remains 0.4.1.
|
|
23
|
+
|
|
24
|
+
### What this enables
|
|
25
|
+
- Retrieval is uniform across every design-ai surface and injects real design knowledge, not index tables; the Korean corpus gains async-control and B2B density depth.
|
|
26
|
+
- Publishing v4.58.0 realigns `main` with the published package so the live registry smoke verifies cleanly.
|
|
27
|
+
|
|
28
|
+
### What's still ahead
|
|
29
|
+
- Push the v4.58.0 tag, then verify npm publish (provenance), GitHub Release, public `npm run registry:smoke` (now skew-free), and docs deployment.
|
|
30
|
+
- Refresh the Homebrew formula SHA-256 and the [external-status.md](external-status.md) / README distribution status from published `4.57.0` to `4.58.0` after the tag tarball exists.
|
|
31
|
+
|
|
3
32
|
## Phase 755 — Local Retrieval Memory Release (v4.57.0) ✓ ready
|
|
4
33
|
|
|
5
34
|
Groups the Phase 754 A+B retrieval work, the MCP ranked-search parameter, the `refs/` reference-page migration, and the docs-deploy retry hardening into one public package release. Scope and data boundaries are defined in [AI-LEARNING-PHASE2.md](AI-LEARNING-PHASE2.md); defaults are unchanged, so the release is additive and backward-compatible.
|
package/docs/external-status.md
CHANGED
|
@@ -1,22 +1,22 @@
|
|
|
1
1
|
# External Publication Status
|
|
2
2
|
|
|
3
|
-
> Checked: 2026-07-
|
|
3
|
+
> Checked: 2026-07-03
|
|
4
4
|
> Scope: npm registry, GitHub Pages, Homebrew tap, VS Code Marketplace, Claude/Codex MCP
|
|
5
5
|
|
|
6
6
|
## Summary
|
|
7
7
|
|
|
8
|
-
|
|
8
|
+
npm is publicly published at `@design-ai/cli@4.57.0` (npm dist-tag `latest` = `4.57.0`); publish run `28663430171` from tag `v4.57.0` succeeded with provenance and all pre-publish gates green (8 audits, CLI unit tests, release self-tests, package contents, and the packed-tarball installed-package smoke on the 4.57.0 tarball). The automated post-publish live registry smoke step failed on a stale learn-relevance token-order assertion in `tools/audit/registry-smoke.py` — a verification-tool bug fixed on `main` in `710656a`, not a package defect; the published package is validated by the pre-publish packed-tarball smoke. GitHub Release `v4.57.0` is published, and the Homebrew tap formula points at the `v4.57.0` release source tarball with a verified SHA-256. The VS Code extension `sungjin.design-ai-vscode` remains published at `0.4.1`. GitHub Pages docs are publicly reachable.
|
|
9
9
|
|
|
10
10
|
## Results
|
|
11
11
|
|
|
12
12
|
| Surface | Checked target | Result | Evidence |
|
|
13
13
|
|---|---|---|---|
|
|
14
|
-
| npm registry | `@design-ai/cli` | Published latest is `4.
|
|
14
|
+
| npm registry | `@design-ai/cli` | Published latest is `4.57.0`; publish run `28663430171` (tag `v4.57.0`) succeeded with provenance. Pre-publish packed-tarball smoke passed on the 4.57.0 tarball; the post-publish live registry smoke failed on a stale assertion (fixed on `main` `710656a`). | publish run `28663430171`; `npm view @design-ai/cli version` → `4.57.0` |
|
|
15
15
|
| GitHub Pages | `https://sungjin9288.github.io/design-ai/` | Published and reachable: HTTP `200`, design-ai MkDocs page rendered | `evidence/cli-logs/github-pages-status.log` |
|
|
16
|
-
| Homebrew tap | `Formula/design-ai.rb` | Formula pinned to `v4.
|
|
16
|
+
| Homebrew tap | `Formula/design-ai.rb` | Formula pinned to `v4.57.0` release source tarball with SHA-256 `c1fed279ed7bc2daf42473bd9a68cf3fa6df6823fe3390b2f2ccee8625407559` (recomputed from the published tag tarball) | `Formula/design-ai.rb` |
|
|
17
17
|
| VS Code Marketplace | `sungjin.design-ai-vscode` | Published: run `28431571256` published `v0.4.1`, and the Marketplace Gallery API returned visible version `0.4.1` on 2026-07-02. | `evidence/cli-logs/vscode-marketplace-status.log`, `evidence/cli-logs/vscode-marketplace-secret-status.log`, `evidence/cli-logs/vscode-extension-vsce-package.log`, `evidence/cli-logs/vscode-publish-workflow-status.log` |
|
|
18
|
-
| GitHub Release | `v4.
|
|
19
|
-
| MCP server | `@design-ai/cli@4.
|
|
18
|
+
| GitHub Release | `v4.57.0` | Published (not draft/prerelease) for tag `v4.57.0` at commit `4be46d7` | `gh release view v4.57.0` |
|
|
19
|
+
| MCP server | `@design-ai/cli@4.57.0` / local clone | Public npm `design-ai-mcp` responds to initialize and tools/list with 10 tools; local Codex and Claude Code both report `design-ai` MCP as configured and connected. | `evidence/cli-logs/npm-registry-smoke.log`, `evidence/cli-logs/design-ai-mcp-client-status.log` |
|
|
20
20
|
|
|
21
21
|
## Interpretation
|
|
22
22
|
|
package/knowledge/COVERAGE.md
CHANGED
|
@@ -12,7 +12,7 @@ generated_at: 2026-07-03
|
|
|
12
12
|
|
|
13
13
|
| Layer | Count | Detail |
|
|
14
14
|
| --- | --- | --- |
|
|
15
|
-
| Knowledge files |
|
|
15
|
+
| Knowledge files | 94 | 79 hand-written + 15 generated |
|
|
16
16
|
| Skills (PLAYBOOK + SKILL) | 20 | 20 with verification phase |
|
|
17
17
|
| Worked examples | 221 | |
|
|
18
18
|
| Extractors | 12 | |
|
|
@@ -30,12 +30,12 @@ generated_at: 2026-07-03
|
|
|
30
30
|
| `conversational` | 5 | 5 | 0 |
|
|
31
31
|
| `design-tokens` | 4 | 3 | 1 |
|
|
32
32
|
| `game-ui` | 5 | 5 | 0 |
|
|
33
|
-
| `i18n` |
|
|
33
|
+
| `i18n` | 7 | 7 | 0 |
|
|
34
34
|
| `icons` | 1 | 0 | 1 |
|
|
35
35
|
| `illustration` | 5 | 5 | 0 |
|
|
36
36
|
| `layout` | 1 | 1 | 0 |
|
|
37
37
|
| `motion` | 6 | 6 | 0 |
|
|
38
|
-
| `patterns` |
|
|
38
|
+
| `patterns` | 31 | 25 | 6 |
|
|
39
39
|
| `platforms` | 1 | 1 | 0 |
|
|
40
40
|
| `print` | 6 | 6 | 0 |
|
|
41
41
|
| `spatial` | 5 | 5 | 0 |
|
|
@@ -49,7 +49,7 @@ generated_at: 2026-07-03
|
|
|
49
49
|
|
|
50
50
|
| File | Lines | Type | Title |
|
|
51
51
|
| --- | --- | --- | --- |
|
|
52
|
-
| [knowledge/COVERAGE.md](../knowledge/COVERAGE.md) |
|
|
52
|
+
| [knowledge/COVERAGE.md](../knowledge/COVERAGE.md) | 743 | generated | Coverage report |
|
|
53
53
|
| [knowledge/PRINCIPLES.md](../knowledge/PRINCIPLES.md) | 108 | hand-written | Design-AI principles |
|
|
54
54
|
|
|
55
55
|
#### a11y
|
|
@@ -108,9 +108,10 @@ generated_at: 2026-07-03
|
|
|
108
108
|
| File | Lines | Type | Title |
|
|
109
109
|
| --- | --- | --- | --- |
|
|
110
110
|
| [knowledge/i18n/korean-app-store-visual.md](../knowledge/i18n/korean-app-store-visual.md) | 223 | hand-written | Korean app store visual design |
|
|
111
|
+
| [knowledge/i18n/korean-density-conventions.md](../knowledge/i18n/korean-density-conventions.md) | 108 | hand-written | Korean B2B density conventions |
|
|
111
112
|
| [knowledge/i18n/korean-document-style.md](../knowledge/i18n/korean-document-style.md) | 301 | hand-written | Korean document style |
|
|
112
113
|
| [knowledge/i18n/korean-payments.md](../knowledge/i18n/korean-payments.md) | 211 | hand-written | Korean payments |
|
|
113
|
-
| [knowledge/i18n/korean-product-conventions.md](../knowledge/i18n/korean-product-conventions.md) |
|
|
114
|
+
| [knowledge/i18n/korean-product-conventions.md](../knowledge/i18n/korean-product-conventions.md) | 98 | hand-written | Korean product UX conventions |
|
|
114
115
|
| [knowledge/i18n/korean-publishing.md](../knowledge/i18n/korean-publishing.md) | 139 | hand-written | Korean app store & publishing requirements |
|
|
115
116
|
| [knowledge/i18n/korean-typography.md](../knowledge/i18n/korean-typography.md) | 123 | hand-written | Korean (Hangul) typography for product UI |
|
|
116
117
|
|
|
@@ -151,6 +152,7 @@ generated_at: 2026-07-03
|
|
|
151
152
|
|
|
152
153
|
| File | Lines | Type | Title |
|
|
153
154
|
| --- | --- | --- | --- |
|
|
155
|
+
| [knowledge/patterns/async-control.md](../knowledge/patterns/async-control.md) | 233 | hand-written | Async control patterns |
|
|
154
156
|
| [knowledge/patterns/auth-flow-design.md](../knowledge/patterns/auth-flow-design.md) | 316 | hand-written | Authentication flow design |
|
|
155
157
|
| [knowledge/patterns/b2b-onboarding-flows.md](../knowledge/patterns/b2b-onboarding-flows.md) | 182 | hand-written | B2B onboarding flows |
|
|
156
158
|
| [knowledge/patterns/brand-identity.md](../knowledge/patterns/brand-identity.md) | 238 | hand-written | Brand identity |
|
|
@@ -162,8 +164,8 @@ generated_at: 2026-07-03
|
|
|
162
164
|
| [knowledge/patterns/document-typography.md](../knowledge/patterns/document-typography.md) | 248 | hand-written | Document typography |
|
|
163
165
|
| [knowledge/patterns/email-design.md](../knowledge/patterns/email-design.md) | 338 | hand-written | Email design |
|
|
164
166
|
| [knowledge/patterns/empty-states.md](../knowledge/patterns/empty-states.md) | 263 | hand-written | Empty states |
|
|
165
|
-
| [knowledge/patterns/error-states.md](../knowledge/patterns/error-states.md) |
|
|
166
|
-
| [knowledge/patterns/form-design.md](../knowledge/patterns/form-design.md) |
|
|
167
|
+
| [knowledge/patterns/error-states.md](../knowledge/patterns/error-states.md) | 319 | hand-written | Error states |
|
|
168
|
+
| [knowledge/patterns/form-design.md](../knowledge/patterns/form-design.md) | 239 | hand-written | Form design patterns |
|
|
167
169
|
| [knowledge/patterns/information-architecture.md](../knowledge/patterns/information-architecture.md) | 341 | hand-written | Information architecture |
|
|
168
170
|
| [knowledge/patterns/landing-hero-design.md](../knowledge/patterns/landing-hero-design.md) | 283 | hand-written | Landing hero design |
|
|
169
171
|
| [knowledge/patterns/landing-page-patterns.md](../knowledge/patterns/landing-page-patterns.md) | 681 | generated | Landing page patterns |
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
<!-- hand-written -->
|
|
2
|
+
---
|
|
3
|
+
title: Korean B2B density conventions
|
|
4
|
+
applies_to: [web, mobile, all-ui, korean]
|
|
5
|
+
version: 1.0.0
|
|
6
|
+
last_updated: 2026-07
|
|
7
|
+
stability: stable
|
|
8
|
+
---
|
|
9
|
+
|
|
10
|
+
# Korean B2B density conventions
|
|
11
|
+
|
|
12
|
+
Korean enterprise software — ERP, HR, groupware, admin consoles, financial back-office — is visibly denser than its Western counterpart, and denser again than Korean *consumer* apps. A Western enterprise layout ported unchanged into a Korean B2B product reads as sparse, slow, and "not serious." This file is the density half of the Korean playbook; for consumer density and the broader Korean conventions, see [`knowledge/i18n/korean-product-conventions.md`](korean-product-conventions.md).
|
|
13
|
+
|
|
14
|
+
## Why Korean B2B is denser
|
|
15
|
+
|
|
16
|
+
- **Information-per-screen is a value, not a cost.** Power users (accountants, HR admins, operators) work the same screens all day and want everything in view — more rows, more columns, fewer clicks to the next field. Scrolling and pagination read as friction, not breathing room.
|
|
17
|
+
- **The reference points are dense.** 더존, 영림원, SAP-Korea deployments, government e-gov portals, and bank back-office tools set the expectation. Naver Works / Kakao Work admin, Toss Business, and 뱅크샐러드 business consoles are the modern-but-still-dense benchmark.
|
|
18
|
+
- **Hangul carries more meaning per character.** A Korean label is often shorter than its English equivalent for the same content, so the same row holds more without feeling cramped.
|
|
19
|
+
|
|
20
|
+
This is a deliberate register, not clutter. The craft is being dense **and** legible — not dense **and** broken.
|
|
21
|
+
|
|
22
|
+
## The density ladder
|
|
23
|
+
|
|
24
|
+
Pick a mode per surface and hold it. Do not mix comfortable and compact rows in one table.
|
|
25
|
+
|
|
26
|
+
| Mode | Row height (approx) | Use for |
|
|
27
|
+
| --- | --- | --- |
|
|
28
|
+
| **Comfortable** | 48–56px | Consumer-facing, onboarding, marketing-in-product, low-frequency admin |
|
|
29
|
+
| **Cozy** (default KR B2B) | 36–44px | Most enterprise tables, forms, lists — the Korean B2B baseline |
|
|
30
|
+
| **Compact** | 28–32px | Data grids for power users, financial ledgers, high-row-count monitoring |
|
|
31
|
+
|
|
32
|
+
Western enterprise defaults to Comfortable; **Korean B2B defaults to Cozy.** Porting in usually means dropping one rung: reduce vertical padding ~15–25% and expect 30–50% more rows per fold. Offer a density toggle (Comfortable / Cozy / Compact) on heavy data surfaces so individual operators can go denser; persist the choice per user.
|
|
33
|
+
|
|
34
|
+
## Tables are the center of gravity
|
|
35
|
+
|
|
36
|
+
Korean B2B UIs are table-first. Most screens are a filter bar plus a table.
|
|
37
|
+
|
|
38
|
+
- **Many columns are normal.** 8–15 columns is routine; horizontal scroll with a **frozen first column** (and often a frozen header row) beats hiding columns behind a menu.
|
|
39
|
+
- **Compact rows, aligned numerals.** Right-align numbers and currency, use tabular figures, and keep row height in the Cozy/Compact band. See [`knowledge/patterns/money-and-amount.md`](../patterns/money-and-amount.md) for amount formatting.
|
|
40
|
+
- **Inline row actions.** Put edit / delete / detail as compact icon buttons or a trailing action cell, not behind a per-row overflow menu that costs an extra click.
|
|
41
|
+
- **Sticky context.** Freeze the header and the key identity column (사번, 거래처, 전표번호) so the operator never loses the row's identity while scrolling wide.
|
|
42
|
+
- **Totals and subtotals in view.** A pinned footer row with sums/counts is expected on financial and inventory tables — operators reconcile against it constantly.
|
|
43
|
+
- **Zebra or hairline rules, not heavy borders.** At Cozy/Compact density, 1px hairline separators or subtle zebra striping keep rows scannable without adding visual weight.
|
|
44
|
+
|
|
45
|
+
## Forms at density
|
|
46
|
+
|
|
47
|
+
- **Label-left (horizontal) layout** is the Korean B2B default for data-entry forms — it packs more fields per vertical space than label-top and matches the ledger-like mental model. Label-top is for consumer/onboarding.
|
|
48
|
+
- **Multi-column forms.** Two or three field columns per row are normal for registration and master-data screens. Group by section with thin dividers, not big gaps.
|
|
49
|
+
- **Tight field spacing.** Reduce inter-field vertical rhythm versus consumer forms; keep it consistent so the grid reads cleanly. See [`knowledge/patterns/form-design.md`](../patterns/form-design.md).
|
|
50
|
+
- **Required/optional density.** Mark required with a compact indicator (asterisk or a small "필수" chip); don't spend a whole helper-text line per field unless the rule is non-obvious.
|
|
51
|
+
- **Keyboard-first.** Power users tab through fields fast. Preserve a logical tab order, support Enter-to-next where appropriate, and don't trap focus in date/select popovers.
|
|
52
|
+
|
|
53
|
+
## Lists, trees, and navigation
|
|
54
|
+
|
|
55
|
+
- **Dense list rows** with secondary metadata inline (status chip, date, owner on one line) rather than stacked. See [`knowledge/patterns/list-and-feed.md`](../patterns/list-and-feed.md).
|
|
56
|
+
- **Tree navigation** (조직도, 계정과목, 메뉴 권한) is common and should stay compact — small row height, clear expand affordances, many levels visible at once.
|
|
57
|
+
- **Persistent side navigation** with dense, possibly two-level menus is expected over hamburger-hidden nav; enterprise users want the whole map visible. This is the opposite of the consumer preference noted in [`korean-product-conventions.md`](korean-product-conventions.md).
|
|
58
|
+
|
|
59
|
+
## Typography at density
|
|
60
|
+
|
|
61
|
+
- **Base size can drop, but not below legibility.** 13–14px body is common in KR B2B tables (vs 16px consumer). Do not go below ~12px for Hangul primary content — Hangul syllable blocks lose legibility faster than Latin at small sizes.
|
|
62
|
+
- **Line-height stays generous relative to size.** Even dense Korean text needs ~1.4–1.5 line-height for the syllable blocks to breathe; do not crush leading to save space. See [`knowledge/i18n/korean-typography.md`](korean-typography.md).
|
|
63
|
+
- **Numerals: tabular, aligned.** Financial density depends on figures lining up.
|
|
64
|
+
- **Truncate with intent.** Ellipsize long values with a tooltip/title for the full string rather than wrapping and breaking row rhythm.
|
|
65
|
+
|
|
66
|
+
## Density must not break accessibility
|
|
67
|
+
|
|
68
|
+
Density is the constraint; accessibility is the floor. When they conflict, accessibility wins.
|
|
69
|
+
|
|
70
|
+
- **Touch targets.** On touch/hybrid B2B (tablets on the floor, POS, hospital carts), keep interactive targets ≥ 44×44px *hit area* even when the visual row is compact — expand the clickable area beyond the visible cell rather than shrinking the target.
|
|
71
|
+
- **Contrast holds at every density.** Hairline separators and secondary text must still meet WCAG contrast; dense does not mean low-contrast gray-on-gray. See [`knowledge/a11y/contrast.md`](../a11y/contrast.md).
|
|
72
|
+
- **Focus visibility.** A visible focus ring is non-negotiable for keyboard-driven data entry; ensure it is not clipped by tight row borders. See [`knowledge/a11y/keyboard-and-focus.md`](../a11y/keyboard-and-focus.md).
|
|
73
|
+
- **Density toggle as an a11y affordance.** Offering Comfortable mode is itself an accessibility accommodation for users who need larger targets and text.
|
|
74
|
+
|
|
75
|
+
## Tokens consumed
|
|
76
|
+
|
|
77
|
+
```
|
|
78
|
+
--space-2xs, --space-xs (dense row padding, inter-field gaps)
|
|
79
|
+
--space-sm (section spacing at density)
|
|
80
|
+
--row-height-compact (or --space-based row sizing)
|
|
81
|
+
--font-size-sm (13–14px dense body)
|
|
82
|
+
--line-height-normal (~1.4–1.5 for Hangul)
|
|
83
|
+
--color-border-subtle (hairline separators)
|
|
84
|
+
--color-bg-subtle (zebra striping)
|
|
85
|
+
--color-text-secondary (inline metadata)
|
|
86
|
+
--color-text-tabular (aligned numerals)
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
Define a **density scale** as tokens (comfortable / cozy / compact row heights + padding) so the toggle flips a token set, not ad-hoc per-component values.
|
|
90
|
+
|
|
91
|
+
## Don't
|
|
92
|
+
|
|
93
|
+
- Don't ship Western enterprise comfortable-density defaults unchanged into a Korean B2B product — it reads as sparse and unserious.
|
|
94
|
+
- Don't hide columns behind menus to avoid width — Korean power users prefer horizontal scroll with frozen columns.
|
|
95
|
+
- Don't mix row-height modes within one table.
|
|
96
|
+
- Don't crush Hangul below ~12px or crush line-height to gain rows — legibility breaks before the space saved is worth it.
|
|
97
|
+
- Don't let density erode contrast, focus rings, or touch-target hit areas.
|
|
98
|
+
- Don't bury per-row actions behind an overflow menu on high-frequency screens — inline them.
|
|
99
|
+
- Don't apply consumer nav patterns (hamburger, hidden tabs) to enterprise — surface the full dense menu.
|
|
100
|
+
|
|
101
|
+
## Cross-reference
|
|
102
|
+
|
|
103
|
+
- [`knowledge/i18n/korean-product-conventions.md`](korean-product-conventions.md) — consumer density and the broader Korean conventions this file specializes from
|
|
104
|
+
- [`knowledge/i18n/korean-typography.md`](korean-typography.md) — Hangul sizing and line-height at density
|
|
105
|
+
- [`knowledge/patterns/form-design.md`](../patterns/form-design.md) — label-left, multi-column, dense forms
|
|
106
|
+
- [`knowledge/patterns/list-and-feed.md`](../patterns/list-and-feed.md) — dense list rows and data grids
|
|
107
|
+
- [`knowledge/patterns/money-and-amount.md`](../patterns/money-and-amount.md) — tabular numerals and aligned amounts
|
|
108
|
+
- [`knowledge/a11y/contrast.md`](../a11y/contrast.md), [`knowledge/a11y/keyboard-and-focus.md`](../a11y/keyboard-and-focus.md) — the accessibility floor density must respect
|
|
@@ -58,6 +58,8 @@ This is not "cluttered design" — it matches reading pace and screen-time habit
|
|
|
58
58
|
|
|
59
59
|
Reference: see how Coupang, Naver, KakaoTalk, Toss differ from Amazon, Google, WhatsApp, Robinhood.
|
|
60
60
|
|
|
61
|
+
For enterprise / B2B density (denser again — ERP, HR, groupware, admin, financial back-office), see [`knowledge/i18n/korean-density-conventions.md`](korean-density-conventions.md).
|
|
62
|
+
|
|
61
63
|
## Color associations
|
|
62
64
|
|
|
63
65
|
Different from Western color symbolism:
|
|
@@ -0,0 +1,233 @@
|
|
|
1
|
+
<!-- hand-written -->
|
|
2
|
+
---
|
|
3
|
+
title: Async control patterns
|
|
4
|
+
applies_to: [web, mobile, all-ui]
|
|
5
|
+
version: 1.0.0
|
|
6
|
+
last_updated: 2026-07
|
|
7
|
+
stability: stable
|
|
8
|
+
---
|
|
9
|
+
|
|
10
|
+
# Async control patterns
|
|
11
|
+
|
|
12
|
+
Every action that talks to a server has an in-flight life. This file is about that life — the moment between the tap and the answer — not about the error screen at the end (see [`patterns/error-states.md`](error-states.md) for that). Get the in-flight state wrong and you get the two most common async bugs in product UIs: double submits and stuck spinners.
|
|
13
|
+
|
|
14
|
+
## The async action lifecycle
|
|
15
|
+
|
|
16
|
+
Every async action moves through the same four states. Model them explicitly; do not infer "loading" from the absence of data.
|
|
17
|
+
|
|
18
|
+
```
|
|
19
|
+
idle ──trigger──▶ pending ──┬──▶ success ──▶ idle
|
|
20
|
+
▲ │
|
|
21
|
+
└─────────────────────────┴──▶ error ────▶ idle (retryable)
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
| State | The control shows | The user can |
|
|
25
|
+
| --- | --- | --- |
|
|
26
|
+
| **idle** | Its normal, enabled label | Trigger the action |
|
|
27
|
+
| **pending** | Busy affordance + disabled | Wait, or cancel (if cancellable) |
|
|
28
|
+
| **success** | Brief confirmation, then idle | Continue |
|
|
29
|
+
| **error** | Recovery affordance | Retry or recover — see `error-states.md` |
|
|
30
|
+
|
|
31
|
+
Rule: a control never sits in `pending` forever. Every request has a timeout that forces it to `error`.
|
|
32
|
+
|
|
33
|
+
## Action controls during pending
|
|
34
|
+
|
|
35
|
+
The trigger control (button, menu item, toggle) is where most async bugs live.
|
|
36
|
+
|
|
37
|
+
| Do | Don't |
|
|
38
|
+
| --- | --- |
|
|
39
|
+
| Disable the control while its own action is in flight | Leave it clickable and hope |
|
|
40
|
+
| Show a busy affordance **inside or beside** the control | Replace the whole screen with a spinner for a single button |
|
|
41
|
+
| Keep the label readable — swap to a present-progressive verb | Blank the label to just a spinner (the user forgets what they clicked) |
|
|
42
|
+
| Restore the control on success **and** error | Leave it disabled after a failed request |
|
|
43
|
+
|
|
44
|
+
Label swap, Korean and English:
|
|
45
|
+
|
|
46
|
+
| Idle | Pending | Done |
|
|
47
|
+
| --- | --- | --- |
|
|
48
|
+
| 저장 | 저장 중… | 저장됨 |
|
|
49
|
+
| 불러오기 | 불러오는 중… | — |
|
|
50
|
+
| 결제하기 | 결제 처리 중… | 결제 완료 |
|
|
51
|
+
| 제출 | 제출 중… | — |
|
|
52
|
+
|
|
53
|
+
```
|
|
54
|
+
┌─────────────────┐ ┌─────────────────┐
|
|
55
|
+
│ 저장 │ → │ ◌ 저장 중… │ (disabled, spinner leads label)
|
|
56
|
+
└─────────────────┘ └─────────────────┘
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
## Double-submit prevention
|
|
60
|
+
|
|
61
|
+
The single most important async rule: **a control that started an action cannot start it again until that action resolves.** Disabling on `pending` handles the fast double-tap. Also guard the handler itself, because disabled is a UI state that a determined tap (or a slow render) can beat.
|
|
62
|
+
|
|
63
|
+
```tsx
|
|
64
|
+
function SubmitButton({ onSubmit }) {
|
|
65
|
+
const [status, setStatus] = useState("idle"); // idle | pending | error
|
|
66
|
+
|
|
67
|
+
async function handleClick() {
|
|
68
|
+
if (status === "pending") return; // guard: not just the disabled attr
|
|
69
|
+
setStatus("pending");
|
|
70
|
+
try {
|
|
71
|
+
await onSubmit();
|
|
72
|
+
setStatus("idle");
|
|
73
|
+
} catch (err) {
|
|
74
|
+
setStatus("error"); // hand off to error-states.md
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
return (
|
|
79
|
+
<Button onClick={handleClick} disabled={status === "pending"} aria-busy={status === "pending"}>
|
|
80
|
+
{status === "pending" ? "저장 중…" : "저장"}
|
|
81
|
+
</Button>
|
|
82
|
+
);
|
|
83
|
+
}
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
For money and irreversible actions (payment, transfer, delete), double-submit guarding is a correctness requirement, not a nicety. Pair the client guard with a server-side idempotency key — cite [`patterns/money-and-amount.md`](money-and-amount.md).
|
|
87
|
+
|
|
88
|
+
## Choosing a loading affordance by duration
|
|
89
|
+
|
|
90
|
+
Match the indicator to how long the wait actually is. Guessing wrong is worse than no indicator.
|
|
91
|
+
|
|
92
|
+
| Expected wait | Affordance | Why |
|
|
93
|
+
| --- | --- | --- |
|
|
94
|
+
| < 100ms | **Nothing** | Faster than perception; a flashed spinner reads as a glitch |
|
|
95
|
+
| 100ms – 1s | **In-place busy** (button spinner, subtle) | Confirms the tap registered |
|
|
96
|
+
| 1s – 10s | **Skeleton** (for content) or **progress** (for known-length work) | Shows shape/position; feels faster than a spinner |
|
|
97
|
+
| > 10s | **Progress + cancel + background option** | Let the user leave and be notified |
|
|
98
|
+
| Unknown length | **Indeterminate spinner with a timeout** | Never an infinite bar |
|
|
99
|
+
|
|
100
|
+
Spinner vs skeleton: a spinner says "something is happening"; a skeleton says "*this* is what's coming and where." Prefer skeletons for content regions (lists, cards, detail panes) — see [`patterns/list-and-feed.md`](list-and-feed.md). Reserve spinners for actions and small inline waits.
|
|
101
|
+
|
|
102
|
+
Anti-flicker: if you show a delayed spinner (only after 100–300ms), also hold it for a minimum (~300ms) once shown, so a request that resolves at 310ms doesn't flash. Delay-in, min-hold-out.
|
|
103
|
+
|
|
104
|
+
## Optimistic updates
|
|
105
|
+
|
|
106
|
+
Apply the change in the UI immediately, before the server confirms, then reconcile.
|
|
107
|
+
|
|
108
|
+
Use optimistic when: the action almost always succeeds, is cheap to reverse, and the wait would break flow — likes, toggles, reordering, adding a to-do, marking read.
|
|
109
|
+
|
|
110
|
+
Do **not** use optimistic when: the action is money, irreversible, or the server is the source of truth for the result (payment, booking a seat, submitting an exam). There, show real `pending` and wait.
|
|
111
|
+
|
|
112
|
+
```
|
|
113
|
+
tap ─▶ update UI now ─▶ fire request ─┬─ ok: keep, drop the "syncing" mark
|
|
114
|
+
└─ fail: roll back to prior value + show why
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
Rules:
|
|
118
|
+
- Keep the pre-action value so you can roll back exactly.
|
|
119
|
+
- Mark optimistic items as unconfirmed (subtle) until the server agrees, so a rollback isn't a jarring surprise.
|
|
120
|
+
- On rollback, tell the user what reverted and why — a silent snap-back looks like a bug.
|
|
121
|
+
|
|
122
|
+
## Debounce and throttle for async-triggering input
|
|
123
|
+
|
|
124
|
+
Input that fires requests as the user types or drags must be rate-limited, or it floods the server and races itself.
|
|
125
|
+
|
|
126
|
+
| Technique | Fires | Use for |
|
|
127
|
+
| --- | --- | --- |
|
|
128
|
+
| **Debounce** (wait for a pause) | Once, after input stops for N ms | Search-as-you-type (250–350ms), autosave (500ms–2s) |
|
|
129
|
+
| **Throttle** (at most every N ms) | On a steady cadence | Drag/scroll-driven fetches, live position updates |
|
|
130
|
+
| **Leading + trailing** | Immediately, then once more at the end | Feels responsive but still settles |
|
|
131
|
+
|
|
132
|
+
Debounce is about *when to fire*; cancellation (next section) is about *what to do with requests already in flight*. You need both.
|
|
133
|
+
|
|
134
|
+
## Cancellation and out-of-order responses
|
|
135
|
+
|
|
136
|
+
Two requests fired in order can return out of order. Without cancellation, an older, slower response overwrites a newer one — the classic "I typed 서울, it shows 서" bug.
|
|
137
|
+
|
|
138
|
+
- Cancel the previous in-flight request when a new one supersedes it (`AbortController` on the web).
|
|
139
|
+
- Cancel in-flight requests when the component unmounts or the user navigates away.
|
|
140
|
+
- If you cannot cancel, **guard on apply**: tag each request and ignore any response that isn't the latest.
|
|
141
|
+
|
|
142
|
+
```tsx
|
|
143
|
+
useEffect(() => {
|
|
144
|
+
const controller = new AbortController();
|
|
145
|
+
fetchResults(query, { signal: controller.signal })
|
|
146
|
+
.then(setResults)
|
|
147
|
+
.catch(ignoreAbort);
|
|
148
|
+
return () => controller.abort(); // supersede on new query / unmount
|
|
149
|
+
}, [query]);
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
## Concurrency policy
|
|
153
|
+
|
|
154
|
+
When the same action can be triggered while one is running, decide the policy on purpose:
|
|
155
|
+
|
|
156
|
+
| Policy | Behavior | Use for |
|
|
157
|
+
| --- | --- | --- |
|
|
158
|
+
| **Block** | Ignore new triggers until the current resolves | Submits, payments (with double-submit guard) |
|
|
159
|
+
| **Latest-wins** | Cancel the running one, keep only the newest | Search, filters, autosave |
|
|
160
|
+
| **Queue** | Run in order, one at a time | Ordered writes that must all land |
|
|
161
|
+
| **Parallel** | Let all run | Independent reads |
|
|
162
|
+
|
|
163
|
+
Default to **block** for writes and **latest-wins** for reads unless you have a reason otherwise.
|
|
164
|
+
|
|
165
|
+
## Timeouts and the stuck-spinner failure
|
|
166
|
+
|
|
167
|
+
A spinner with no timeout is a trap: on a dropped connection it spins forever and the user is stuck.
|
|
168
|
+
|
|
169
|
+
- Give every request a client timeout (e.g. 10–30s by operation).
|
|
170
|
+
- On timeout, transition to `error` with a retry — hand off to [`patterns/error-states.md`](error-states.md) (network/server section, retry with backoff).
|
|
171
|
+
- For genuinely long work (video processing, exports), don't hold a spinner — move to a **background job** pattern: acknowledge, let the user leave, notify on completion.
|
|
172
|
+
|
|
173
|
+
## Perceived performance
|
|
174
|
+
|
|
175
|
+
The felt speed of an async UI is mostly about what you show during the wait:
|
|
176
|
+
|
|
177
|
+
- **Optimistic UI** — instant for reversible actions (above).
|
|
178
|
+
- **Skeletons** — for content; show structure, not a void.
|
|
179
|
+
- **Stale-while-revalidate** — show the last known data immediately, refresh in the background, swap in quietly. Mark it subtly as refreshing so a change isn't startling.
|
|
180
|
+
- **Progressive reveal** — render what's ready (shell, then data) instead of blocking on the slowest piece.
|
|
181
|
+
|
|
182
|
+
## Tokens consumed
|
|
183
|
+
|
|
184
|
+
```
|
|
185
|
+
--color-accent (spinner, progress fill)
|
|
186
|
+
--color-text-secondary (pending label, "저장 중…")
|
|
187
|
+
--color-text-tertiary (unconfirmed / optimistic mark, "동기화 중")
|
|
188
|
+
--color-bg-subtle (skeleton base)
|
|
189
|
+
--color-bg-elevated (skeleton shimmer highlight)
|
|
190
|
+
--radius-md
|
|
191
|
+
--space-xs, --space-sm
|
|
192
|
+
--duration-fast (spinner delay-in / min-hold)
|
|
193
|
+
--easing-standard
|
|
194
|
+
```
|
|
195
|
+
|
|
196
|
+
## Accessibility
|
|
197
|
+
|
|
198
|
+
- Set `aria-busy="true"` on a control or region while its action is pending; clear it when resolved.
|
|
199
|
+
- Announce state changes politely: a visually-hidden `role="status"` (`aria-live="polite"`) region saying "저장 중" then "저장됨". Do not use `assertive` for routine progress — reserve that for errors (see `error-states.md`).
|
|
200
|
+
- Keep the control's accessible name stable and meaningful during pending — the label swap ("저장" → "저장 중…") is fine; an icon-only spinner with no name is not.
|
|
201
|
+
- On success that removes the control (e.g. a submit that navigates), move focus deliberately to the next logical element; never drop focus to `<body>`.
|
|
202
|
+
- Respect `prefers-reduced-motion`: swap spinners/shimmer for a static or fade indicator.
|
|
203
|
+
- Disabled-while-pending must still be perceivable — don't rely on color alone; the busy affordance carries the meaning.
|
|
204
|
+
|
|
205
|
+
## Korean considerations
|
|
206
|
+
|
|
207
|
+
Per [`knowledge/i18n/korean-product-conventions.md`](../i18n/korean-product-conventions.md):
|
|
208
|
+
- Progress labels use the present-progressive `~중…`: "저장 중…", "불러오는 중…", "처리 중…", "결제 처리 중…".
|
|
209
|
+
- Keep pending labels short — Korean verbs + `중…` stay compact, so the button width rarely jumps.
|
|
210
|
+
- For payment and transfer flows, show explicit real `pending` (never optimistic) and a clear "결제 처리 중…" state; cite [`knowledge/i18n/korean-payments.md`](../i18n/korean-payments.md) for the surrounding flow and failure messages.
|
|
211
|
+
- Dense Korean B2B layouts favor in-place busy affordances (inline spinner, row-level skeleton) over full-page blocking overlays — cite [`knowledge/i18n/korean-product-conventions.md`](../i18n/korean-product-conventions.md) on density.
|
|
212
|
+
|
|
213
|
+
## Don't
|
|
214
|
+
|
|
215
|
+
- Don't leave a control clickable during its own async action — the double-submit bug.
|
|
216
|
+
- Don't guard double-submit with the disabled attribute alone; guard the handler too.
|
|
217
|
+
- Don't show a spinner for a sub-100ms wait — it reads as a flicker.
|
|
218
|
+
- Don't show a spinner that can spin forever — every request has a timeout.
|
|
219
|
+
- Don't blank a button to a bare spinner; keep a readable pending label.
|
|
220
|
+
- Don't use optimistic updates for money, payments, or irreversible actions.
|
|
221
|
+
- Don't let an older response overwrite a newer one — cancel or guard-on-apply.
|
|
222
|
+
- Don't hold a full-screen spinner for long work — move it to a background job with notify.
|
|
223
|
+
- Don't roll back an optimistic change silently — say what reverted and why.
|
|
224
|
+
- Don't announce routine progress with `aria-live="assertive"` — that's for errors.
|
|
225
|
+
|
|
226
|
+
## Cross-reference
|
|
227
|
+
|
|
228
|
+
- [`knowledge/patterns/error-states.md`](error-states.md) — where the `error` state goes: recovery, retry-with-backoff, network/server messages
|
|
229
|
+
- [`knowledge/patterns/form-design.md`](form-design.md) — submit buttons, validation-in-flight, form-level pending
|
|
230
|
+
- [`knowledge/patterns/list-and-feed.md`](list-and-feed.md) — skeletons, pull-to-refresh, infinite scroll loading
|
|
231
|
+
- [`knowledge/patterns/empty-states.md`](empty-states.md) — the resolved-but-no-data end state, distinct from loading
|
|
232
|
+
- [`knowledge/patterns/money-and-amount.md`](money-and-amount.md) — why money actions use real pending + idempotency, never optimistic
|
|
233
|
+
- [`knowledge/i18n/korean-payments.md`](../i18n/korean-payments.md) — payment in-flight and failure states
|
|
@@ -312,6 +312,7 @@ function PageContent() {
|
|
|
312
312
|
|
|
313
313
|
## Cross-reference
|
|
314
314
|
|
|
315
|
+
- [`knowledge/patterns/async-control.md`](async-control.md) — the in-flight state before the error: pending, retry-on-timeout, double-submit
|
|
315
316
|
- [`knowledge/patterns/empty-states.md`](empty-states.md) — when there's no data, but it's not an error
|
|
316
317
|
- [`knowledge/patterns/form-design.md`](form-design.md) — validation errors at the field level
|
|
317
318
|
- [`knowledge/i18n/korean-payments.md`](../i18n/korean-payments.md) — payment-specific error messages
|
|
@@ -232,6 +232,8 @@ Required affordances:
|
|
|
232
232
|
|
|
233
233
|
## Cross-reference
|
|
234
234
|
|
|
235
|
+
- [`knowledge/patterns/async-control.md`](async-control.md) — submit-in-flight, double-submit prevention, pending button states
|
|
236
|
+
|
|
235
237
|
- [knowledge/patterns/ux-guidelines.md](ux-guidelines.md) — broader UX rules
|
|
236
238
|
- [knowledge/i18n/korean-product-conventions.md](../i18n/korean-product-conventions.md) — Korean form expectations (phone, address, terms)
|
|
237
239
|
- [knowledge/a11y/keyboard-and-focus.md](../a11y/keyboard-and-focus.md) — keyboard navigation contract
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@design-ai/cli",
|
|
3
|
-
"version": "4.
|
|
3
|
+
"version": "4.58.0",
|
|
4
4
|
"description": "Senior product designer for any AI coding agent. Installs design-ai (20 skills, 17 commands, 4 agents) into Claude Code globally. Korean market depth plus website improvement control tower.",
|
|
5
5
|
"bin": {
|
|
6
6
|
"design-ai": "cli/bin/design-ai.mjs",
|
|
@@ -4785,7 +4785,7 @@ def assert_learning_query_human(raw: str, *, context: str, cmd: list[str]) -> No
|
|
|
4785
4785
|
"Limit: 2",
|
|
4786
4786
|
"Explain: selection score, matched tokens, and reason",
|
|
4787
4787
|
"[accessibility] Prioritize keyboard accessibility details for Button component API specs",
|
|
4788
|
-
"matched
|
|
4788
|
+
"matched accessibility, keyboard",
|
|
4789
4789
|
"reason brief-match",
|
|
4790
4790
|
):
|
|
4791
4791
|
require_registry_smoke(
|
|
@@ -8378,7 +8378,7 @@ def run_self_test() -> None:
|
|
|
8378
8378
|
"",
|
|
8379
8379
|
"1. [accessibility] Prioritize keyboard accessibility details for Button component API specs",
|
|
8380
8380
|
" learn-relevant · 2026-05-22T00:00:01.000Z",
|
|
8381
|
-
" score
|
|
8381
|
+
" score 2.114533 · matched accessibility, keyboard · reason brief-match",
|
|
8382
8382
|
]),
|
|
8383
8383
|
context="registry smoke self-test",
|
|
8384
8384
|
cmd=learn_query_human_cmd,
|
|
@@ -8392,7 +8392,7 @@ def run_self_test() -> None:
|
|
|
8392
8392
|
"Limit: 2",
|
|
8393
8393
|
"Explain: selection score, matched tokens, and reason",
|
|
8394
8394
|
"[accessibility] Prioritize keyboard accessibility details for Button component API specs",
|
|
8395
|
-
"matched
|
|
8395
|
+
"matched accessibility, keyboard",
|
|
8396
8396
|
]),
|
|
8397
8397
|
context="registry smoke self-test",
|
|
8398
8398
|
cmd=learn_query_human_cmd,
|
|
@@ -2246,6 +2246,7 @@ def passing_route_explain_human_output() -> str:
|
|
|
2246
2246
|
f" agent: ✓ {EXPECTED_ROUTE_AGENT}",
|
|
2247
2247
|
" read: ✓ knowledge/PRINCIPLES.md",
|
|
2248
2248
|
f" read: ✓ {EXPECTED_ROUTE_KNOWLEDGE}",
|
|
2249
|
+
" related: knowledge/patterns/design-system-qa.md (score 10.13)",
|
|
2249
2250
|
"",
|
|
2250
2251
|
])
|
|
2251
2252
|
|
|
@@ -3714,6 +3715,8 @@ def assert_route_explain_human_output(raw: str, *, context: str, cmd: list[str])
|
|
|
3714
3715
|
f"agent: ✓ {EXPECTED_ROUTE_AGENT}",
|
|
3715
3716
|
"read: ✓ knowledge/PRINCIPLES.md",
|
|
3716
3717
|
f"read: ✓ {EXPECTED_ROUTE_KNOWLEDGE}",
|
|
3718
|
+
# Advisory related-knowledge recall is surfaced only under --explain.
|
|
3719
|
+
"related: knowledge/",
|
|
3717
3720
|
),
|
|
3718
3721
|
context=context,
|
|
3719
3722
|
label="route explain human output",
|