@design-ai/cli 4.61.0 → 4.63.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.
@@ -0,0 +1,219 @@
1
+ # Agent SDK walkthrough
2
+
3
+ A concrete, copy-pastable walkthrough of driving design-ai entirely through the **programmatic Agent SDK** (`@design-ai/cli/sdk`) — the read-only verbs plus the opt-in `learn.*` local-write namespace. This is a different surface than [`docs/integrations/sdk-walkthrough.md`](sdk-walkthrough.md) (which embeds corpus Markdown into a model's system prompt via the Anthropic/OpenAI SDKs); this page is for driving design-ai's own deterministic functions — `route`, `pack`, `check`, `learn.captureFromCheck` — from your own Node.js program or agent runtime. See [`docs/SDK.md`](../SDK.md) for the full API reference.
4
+
5
+ ## Prerequisites
6
+
7
+ ```bash
8
+ npm install @design-ai/cli
9
+ ```
10
+
11
+ ```js
12
+ import { route, pack, check, learn } from "@design-ai/cli/sdk";
13
+ ```
14
+
15
+ Only the `./sdk` subpath is exported — no CLI shell-out, no MCP server, no separate corpus clone. TypeScript types (`RouteResult`, `Pack`, `CheckReport`, `CaptureResult`, …) resolve automatically from `cli/sdk/index.d.ts` as long as your `tsconfig.json` sets `moduleResolution: "node16"` / `"nodenext"` (or `"bundler"`), so the subpath `types` condition is honored.
16
+
17
+ ## Setup
18
+
19
+ Nothing to configure. The 8 read-only verbs (`route`, `prompt`, `pack`, `search`, `recall`, `check`, `routes`, `version`) never write to disk. The single write surface is the `learn` namespace (`learn.remember`, `learn.feedback`, `learn.captureFromCheck`) — every `learn.*` call writes only the local learning profile, and only when you call it. Point `DESIGN_AI_LEARNING_FILE` at a temp path any time you want to keep that write out of your real profile — which is what every walkthrough below does, so running these scripts never touches `~/.design-ai/learning.json`.
20
+
21
+ ## Walkthrough 1: route a brief to a design-ai route
22
+
23
+ **Goal**: get a scored route recommendation for a task brief, so an agent knows which command/skills/knowledge to use next.
24
+
25
+ ```js
26
+ import { route } from "@design-ai/cli/sdk";
27
+
28
+ const brief =
29
+ "커뮤니티 앱의 게시물 신고 및 사용자 차단 플로우 설계 — " +
30
+ "신고 사유 선택, 처리 상태 안내, 차단 후 상호작용 차단 범위";
31
+
32
+ const routed = route(brief, { limit: 3, explain: true });
33
+ console.log(routed.map((r) => `[${r.confidence}] ${r.id} score=${r.score}`));
34
+ ```
35
+
36
+ ```
37
+ [ '[low] design-from-brief score=1' ]
38
+ ```
39
+
40
+ This is real output from dogfooding this exact brief — see [`docs/DOGFOOD-SDK-FINDINGS.md`](../DOGFOOD-SDK-FINDINGS.md) for the full run and the route-table gap it surfaced (F-1: no dedicated flow-design route yet, so a report/block brief falls back to `design-from-brief`). `route()` still returns a usable recommendation — that's what makes it safe to chain into `pack()` unconditionally, even on a low-confidence match.
41
+
42
+ ## Walkthrough 2: pack a bounded context bundle
43
+
44
+ **Goal**: turn the same brief into a ready-to-use prompt plus a byte-bounded bundle of the corpus files an agent should read before authoring the artifact.
45
+
46
+ ```js
47
+ import { pack } from "@design-ai/cli/sdk";
48
+
49
+ const packed = pack(brief, {
50
+ routeId: routed[0].id,
51
+ withRecall: true,
52
+ recallLimit: 5,
53
+ maxBytes: 60000,
54
+ });
55
+
56
+ console.log(packed.summary);
57
+ ```
58
+
59
+ ```
60
+ {
61
+ totalFiles: 15,
62
+ includedFiles: 15,
63
+ truncatedFiles: 10,
64
+ missingFiles: 0,
65
+ usedBytes: 60000,
66
+ maxBytes: 60000,
67
+ remainingBytes: 0,
68
+ usedRatio: 1,
69
+ status: 'partial'
70
+ }
71
+ ```
72
+
73
+ `pack()` never exceeds `maxBytes` — it fills the budget exactly (60,000/60,000 bytes here) and reports which files got truncated in `packed.warnings`, so an agent can decide whether to raise the budget or read a truncated file's remainder directly. `packed.plan` carries the same ready-to-use prompt `prompt()` would return; `packed.files` is the bounded bundle itself.
74
+
75
+ ## Walkthrough 3: author the artifact (this is the agent's job)
76
+
77
+ **Goal**: the SDK does not write design artifacts — that's the LLM/agent's job, using `packed.plan` and `packed.files` as grounding. This walkthrough embeds a short sample artifact string so the script is runnable end-to-end without a live model call; in a real agent, replace `SAMPLE_ARTIFACT` with the model's actual Markdown output.
78
+
79
+ ```js
80
+ // In a real agent, this string is the model's response — produced by
81
+ // feeding `packed.plan.prompt` and `packed.files` to your LLM call, then
82
+ // taking the Markdown it returns. Kept short here so the script runs
83
+ // standalone. See flow-example.test.mjs for the same constant (kept in
84
+ // lockstep with this walkthrough — the comment there points back here).
85
+ const SAMPLE_ARTIFACT = `# 게시물 신고 및 사용자 차단 플로우
86
+
87
+ Grounded in knowledge/patterns/ui-reasoning.md and knowledge/PRINCIPLES.md.
88
+
89
+ ## 신고 플로우
90
+ 1. 게시물 상세에서 "신고" 메뉴 선택 (keyboard: Tab으로 접근, Enter로 실행)
91
+ 2. 신고 사유 목록에서 선택 (스팸, 욕설/혐오, 음란물, 사기, 기타) — 라디오 그룹, arrow key로 이동
92
+ 3. 기타 선택 시 상세 사유 텍스트 입력 (최대 500자)
93
+ 4. 제출 후 "신고가 접수되었습니다" 확인 화면 표시
94
+ 5. 처리 상태: 접수 → 검토 중 → 조치 완료 (마이페이지 > 신고 내역에서 확인 가능)
95
+
96
+ ## 차단 플로우
97
+ 1. 사용자 프로필에서 "차단" 메뉴 선택
98
+ 2. 확인 다이얼로그: "OO님을 차단하시겠어요?" — focus trap 적용, Escape로 취소
99
+ 3. 차단 후 상호작용 범위: 서로의 게시물/댓글 비노출, DM 차단, 팔로우 자동 해제
100
+ 4. 차단 사실은 상대방에게 통지되지 않음 (비통지 원칙)
101
+
102
+ ## 반응형 동작
103
+ - Mobile: 전체 화면 시트로 신고 사유 목록 표시
104
+ - Desktop: 중앙 정렬 모달, 최대 너비 480px
105
+ - Tablet: mobile과 동일한 시트 레이아웃
106
+
107
+ ## 접근성
108
+ - 신고 사유 목록: role="radiogroup", 각 항목 aria-checked
109
+ - 차단 확인 다이얼로그: focus-visible 스타일, aria-modal="true"
110
+ - Screen reader: 신고 접수 완료 시 aria-live="polite"로 상태 안내
111
+ - 대비: 경고/오류 텍스트는 최소 4.5:1 contrast ratio 확보 (WCAG AA)
112
+
113
+ ## 하지 말아야 할 것 (Don't)
114
+ - Don't: 차단 시 상대방에게 알림을 보내지 마세요 (비통지 원칙 위반)
115
+ - Avoid: 신고 사유를 하나의 텍스트 필드로만 받는 방식 (분류 불가)
116
+ `;
117
+ ```
118
+
119
+ ## Walkthrough 4: check the artifact
120
+
121
+ **Goal**: score the authored artifact for grounding, accessibility, responsive, and route-specific requirements before shipping it.
122
+
123
+ ```js
124
+ import { check } from "@design-ai/cli/sdk";
125
+
126
+ const checked = check(SAMPLE_ARTIFACT, { routeId: routed[0].id });
127
+ console.log(`${checked.status} ${checked.score}`);
128
+ ```
129
+
130
+ ```
131
+ warn 8/10
132
+ ```
133
+
134
+ `checked.results` is the per-check breakdown (10 checks total here: 8 pass, 2 warns, 0 fail). One warn is `route-design-from-brief-design-system-foundation` — a route-specific check reporting that a flow-design artifact is missing design-system-foundation evidence, which is exactly what you'd expect from the F-1 route gap in walkthrough 1. The other, `korean-context`, flags that this short sample mentions no Korean-specific UX/market consideration — the fuller dogfooded artifact addressed it (정보통신망법 처리 기한 안내) and scored `warn 9/10` with only the route warn left. `check()` never writes anything; it's as read-only as `route` and `pack`.
135
+
136
+ ## Walkthrough 5: capture check results into local learning
137
+
138
+ **Goal**: turn the check's non-pass results into local learning-profile entries — the one write in this whole flow, and it's opt-in and explicit.
139
+
140
+ ```js
141
+ import { learn } from "@design-ai/cli/sdk";
142
+ import { mkdtempSync } from "node:fs";
143
+ import { tmpdir } from "node:os";
144
+ import path from "node:path";
145
+
146
+ // Point DESIGN_AI_LEARNING_FILE at a temp file so this never touches your
147
+ // real profile (~/.design-ai/learning.json by default).
148
+ const dir = mkdtempSync(path.join(tmpdir(), "design-ai-sdk-walkthrough-"));
149
+ process.env.DESIGN_AI_LEARNING_FILE = path.join(dir, "learning.json");
150
+
151
+ const captured = learn.captureFromCheck(SAMPLE_ARTIFACT, { routeId: routed[0].id });
152
+ console.log(`added ${captured.addedCount}, skipped ${captured.skippedCount}`);
153
+ ```
154
+
155
+ ```
156
+ added 2, skipped 0
157
+ ```
158
+
159
+ Both non-pass results (the two warns from walkthrough 4) got captured into the temp profile at `process.env.DESIGN_AI_LEARNING_FILE`. Calling `captureFromCheck` again with the same artifact adds nothing new — every candidate is skipped as `duplicate-entry-text` (`added 0, skipped 2` on the second call), so re-running the flow on an unchanged artifact never grows the profile.
160
+
161
+ ## Full script
162
+
163
+ Everything above, assembled into one runnable file:
164
+
165
+ ```js
166
+ import { route, pack, check, learn } from "@design-ai/cli/sdk";
167
+ import { mkdtempSync } from "node:fs";
168
+ import { tmpdir } from "node:os";
169
+ import path from "node:path";
170
+
171
+ // Never touch the real profile — point learning writes at a temp file.
172
+ const dir = mkdtempSync(path.join(tmpdir(), "design-ai-sdk-walkthrough-"));
173
+ process.env.DESIGN_AI_LEARNING_FILE = path.join(dir, "learning.json");
174
+
175
+ const brief =
176
+ "커뮤니티 앱의 게시물 신고 및 사용자 차단 플로우 설계 — " +
177
+ "신고 사유 선택, 처리 상태 안내, 차단 후 상호작용 차단 범위";
178
+
179
+ // 1. route — score the brief against the route catalog.
180
+ const routed = route(brief, { limit: 3, explain: true });
181
+ const routeId = routed[0].id;
182
+ console.log(routed.map((r) => `[${r.confidence}] ${r.id} score=${r.score}`));
183
+
184
+ // 2. pack — bounded context bundle + ready-to-use prompt.
185
+ const packed = pack(brief, { routeId, withRecall: true, recallLimit: 5, maxBytes: 60000 });
186
+ console.log(packed.summary);
187
+
188
+ // 3. author — the LLM/agent writes the artifact here, grounded in
189
+ // packed.plan + packed.files. Embedded as a short sample so this script
190
+ // runs standalone. Kept in lockstep with cli/sdk/flow-example.test.mjs.
191
+ const SAMPLE_ARTIFACT = `# 게시물 신고 및 사용자 차단 플로우
192
+ ...`; // see Walkthrough 3 above for the full text
193
+
194
+ // 4. check — score the artifact.
195
+ const checked = check(SAMPLE_ARTIFACT, { routeId });
196
+ console.log(`${checked.status} ${checked.score}`);
197
+
198
+ // 5. learn.captureFromCheck — the one write, opt-in, into the temp profile.
199
+ const captured = learn.captureFromCheck(SAMPLE_ARTIFACT, { routeId });
200
+ console.log(`added ${captured.addedCount}, skipped ${captured.skippedCount}`);
201
+ ```
202
+
203
+ This exact flow was dogfooded end-to-end before this walkthrough was written — see [`docs/DOGFOOD-SDK-FINDINGS.md`](../DOGFOOD-SDK-FINDINGS.md) for the full run, the numbers above, and the corpus/route gaps the run surfaced. The SDK verdict from that run: zero friction — the flow above is the walkthrough.
204
+
205
+ ## Read-only vs `learn.*` write boundary
206
+
207
+ - `route`, `prompt`, `pack`, `search`, `recall`, `check`, `routes`, `version` — pure, read-only. No file writes, no network calls, no learning-usage sidecar writes, even when `prompt`/`pack`'s `withLearning` option is set (that option only **reads** the local learning profile).
208
+ - `learn.remember`, `learn.feedback`, `learn.captureFromCheck` — the only write surface, grouped under the `learn` namespace so the read/write boundary is visible at the call site. Every `learn.*` call writes exactly one file: the local learning profile at `DESIGN_AI_LEARNING_FILE` (or its default path).
209
+
210
+ See [`docs/SDK.md`](../SDK.md) for the full verb-by-verb reference, including `search`, `recall`, `prompt`, and `routes`, which this walkthrough doesn't exercise directly.
211
+
212
+ ## Next
213
+
214
+ - [`docs/SDK.md`](../SDK.md) — full Agent SDK reference (all 8 read-only verbs + `learn.*`)
215
+ - [`docs/AGENT-SDK.md`](../AGENT-SDK.md) — design rationale and phased plan
216
+ - [`docs/DOGFOOD-SDK-FINDINGS.md`](../DOGFOOD-SDK-FINDINGS.md) — the real end-to-end run this walkthrough is based on
217
+ - [`docs/integrations/sdk-walkthrough.md`](sdk-walkthrough.md) — the Anthropic/OpenAI SDK pattern (embedding corpus Markdown in a system prompt, a different surface than this page)
218
+
219
+ This exact flow also has a `node --test` regression guard in the repo's source checkout (`cli/sdk/flow-example.test.mjs`, not shipped in the npm package) that runs on every `npm test`.
@@ -235,6 +235,18 @@ Worked outputs from each skill — what "good" looks like. Use as reference when
235
235
  | [report-example.md](report-example.md) | `ux-audit` | UX audit report — TL;DR pyramid, severity-aggregated findings, citations, code diffs, "things that work well". |
236
236
  | [email-transactional-example.md](email-transactional-example.md) | `document-author` (email) | Korean fintech transactional email — preheader, receipt structure, bulletproof button, sender info per 정보통신망법. |
237
237
 
238
+ ### Feature flow specs
239
+
240
+ | File | Skill | Demonstrates |
241
+ | --- | --- | --- |
242
+ | [flow-design-report-block.md](flow-design-report-block.md) | `ux-audit` / `design-critique` (`flow-design` route) | Report + block interaction flow. Entry points, step/state tables, edge/error paths, completion criteria, Korean 정보통신망법 제44조의2 임시조치 handling. |
243
+
244
+ ### Dashboard / data screen specs
245
+
246
+ | File | Skill | Demonstrates |
247
+ | --- | --- | --- |
248
+ | [dashboard-design-settlement.md](dashboard-design-settlement.md) | `design-critique` / `handoff-spec` (`dashboard-design` route) | Seller settlement dashboard. KPI strip + table-first layout, Korean amount/number formatting, table accessibility (caption, header scope, aria-sort), density + responsive degradation, export/alert edge cases. |
249
+
238
250
  ### End-to-end deliverables
239
251
 
240
252
  | File | Skill | Demonstrates |
@@ -0,0 +1,87 @@
1
+ <!-- hand-written -->
2
+ # Worked example: seller settlement dashboard design
3
+
4
+ > Generated by the `dashboard-design` route (`skills/design-critique`, `skills/handoff-spec`). Demonstrates the canonical data-screen spec format — KPI strip + table-first layout, Korean amount/number conventions, table accessibility (caption, header scope, `aria-sort`), density and responsive degradation strategy, and export/alert edge cases. Adapted from the dogfood brief in [`docs/DOGFOOD-DASHBOARD-FINDINGS.md`](../docs/DOGFOOD-DASHBOARD-FINDINGS.md) (G-1).
5
+
6
+ **Brief**: 커머스 셀러용 정산 대시보드 설계 — 일별 매출·수수료·정산 예정 금액 표, 기간 필터, 엑셀 내보내기, 미정산 알림.
7
+
8
+ Cite [`knowledge/patterns/dashboard-composition.md`](../knowledge/patterns/dashboard-composition.md) (KPI strip + table-first layout for data-dense B2B screens), [`knowledge/patterns/money-and-amount.md`](../knowledge/patterns/money-and-amount.md) (Korean 원 formatting, tabular-nums, sign+color conventions), and [`knowledge/i18n/korean-density-conventions.md`](../knowledge/i18n/korean-density-conventions.md) (Korean B2B back-office density expectations) as the grounding for this spec.
9
+
10
+ ---
11
+
12
+ ```markdown
13
+ # 커머스 셀러 정산 대시보드 스펙
14
+
15
+ > 근거: knowledge/patterns/dashboard-composition.md, knowledge/patterns/money-and-amount.md,
16
+ > knowledge/i18n/korean-density-conventions.md, knowledge/patterns/list-and-feed.md,
17
+ > knowledge/layout/spacing-and-grid.md, knowledge/typography/type-scale-fundamentals.md, knowledge/PRINCIPLES.md
18
+
19
+ ## 1. 정보 구조와 레이아웃
20
+
21
+ - 상단 KPI 스트립 4매: 오늘 매출, 이번 주 확정 수수료, 정산 예정 금액, 미정산 건수. 각 카드에 전기간 대비 증감(▲/▼ + 퍼센트, 색+기호 병행).
22
+ - 본문은 테이블 우선(한국 B2B 밀도 관례): 일별 정산 내역 테이블이 화면의 주인공. 카드 그리드로 풀지 않는다.
23
+ - 기간 필터는 테이블 헤더 위 고정: 프리셋(오늘/7일/이번 달/지난 정산 주기) + 커스텀 범위. 정산 주기 프리셋이 기본값.
24
+
25
+ ## 2. 금액 표기
26
+
27
+ - 원화 표기: 1,234,567원 — 천 단위 콤마, 통화 기호 대신 '원' 접미. 마이너스(환불·차감)는 -금액 + 적색이 아닌 색+괄호 병행 표기.
28
+ - 테이블 금액 컬럼은 우측 정렬(right-aligned), 고정폭 숫자(tabular-nums)로 세로 스캔 정렬 보장. Amount 컬럼은 항상 number format이 일관되어야 한다.
29
+ - 수수료는 금액과 요율(%)을 병기하고, 정산 예정 금액 = 매출 − 수수료 − 차감의 산식을 툴팁으로 노출.
30
+
31
+ ## 3. 테이블 구조와 동작
32
+
33
+ - 테이블에는 `<caption>정산 내역 (기간: 선택된 필터 범위)</caption>`을 명시하고, 각 column header는 `scope="col"`로 마크업한다.
34
+ - 컬럼: 날짜, 주문 수, 매출, 수수료, 차감, 정산 예정, 상태(확정/대기/보류). 상태는 색+텍스트 배지.
35
+ - 정렬은 날짜 기본 내림차순, 금액 컬럼 정렬 지원 — 정렬 가능한 컬럼 header에는 `aria-sort="ascending"|"descending"|"none"`을 반영해 현재 정렬 상태를 노출한다.
36
+ - 페이지네이션 50행, 밀도(density) 토글 제공(기본 촘촘/compact, 여유형 선택 가능).
37
+ - 행 클릭 → 일별 상세 드로어(주문 단위 분해). 드로어는 focus trap, Escape 닫기.
38
+
39
+ ## 4. 엑셀 내보내기와 미정산 알림
40
+
41
+ - 내보내기는 현재 필터 범위를 그대로 반영, 파일명에 기간 포함(정산내역_2026-06-01_2026-06-30.xlsx). 생성 중 pending 상태(버튼 비활성+진행 라벨), 완료 토스트.
42
+ - 미정산 알림: 정산 예정일 초과 건은 KPI 카드와 테이블 행에 경고 배지, 알림 센터 푸시. 배지는 색+아이콘+텍스트.
43
+ - **엣지 케이스 — 내보내기 실패**: 생성 실패(네트워크 error) 시 인라인 에러 배너 + 재시도 버튼을 노출하고, 실패 사유를 요약 문구로 안내한다. 데이터 재요청 전까지 이전 필터 상태는 보존한다.
44
+ - **엣지 케이스 — 빈 결과**: 필터 범위에 데이터가 없으면 테이블 본문을 "해당 기간에 정산 내역이 없어요" 안내로 대체하고, 내보내기 버튼은 비활성화한다.
45
+
46
+ ## 5. 접근성
47
+
48
+ - 테이블에 캡션과 컬럼 헤더 `scope`, 정렬 상태 `aria-sort`를 위 3장 기준대로 적용한다. 상태 배지는 색만으로 구분하지 않는다(색+텍스트+아이콘 병행).
49
+ - keyboard: 필터 프리셋은 Tab 순회 + Enter/Space로 선택, 테이블 행은 Enter로 드로어 열기, 드로어는 focus trap 유지 후 Escape로 닫힘(닫히면 트리거로 focus 복귀). 정렬 가능한 header도 Tab으로 도달 가능해야 한다.
50
+ - screen reader: KPI 증감은 `aria-label`에 방향 포함("전주 대비 12% 증가"), 내보내기 완료는 `aria-live="polite"`로 스크린 리더에 안내.
51
+ - 대비(contrast): 본문 텍스트와 금액 숫자는 최소 **4.5:1 contrast ratio** (WCAG AA) 확보. 밀도 높은 테이블일수록 이 기준을 더 엄격히 지키며, 행 hover 배경도 동일 대비를 유지한다. 경고 배지 텍스트도 동일 기준(4.5:1) 적용.
52
+
53
+ ## 6. 반응형 동작
54
+
55
+ - Desktop 우선(셀러 백오피스, 1280px+). Tablet(768–1024px): KPI 2×2 그리드, 테이블은 가로 스크롤 + 고정 첫 컬럼(날짜)으로 강등한다.
56
+ - Mobile(360–430px): 테이블을 일자별 카드 리스트로 강등(demote)하되 금액은 우측 정렬을 유지하고, 내보내기 기능은 그대로 제공한다.
57
+ - breakpoint 전환 시 정렬/필터 상태는 유지되며 재요청하지 않는다.
58
+
59
+ ## Don't
60
+
61
+ - Don't: KPI를 6개 이상 늘어놓지 않는다 — 정산 의사결정과 무관한 지표는 별도 리포트로 분리한다.
62
+ - Don't: 미확정 금액을 확정처럼 굵게 표기하지 않는다 — 상태별 시각 위계(확정>예정>보류)를 지킨다.
63
+ - Don't: 엑셀 내보내기를 전체 기간 기본값으로 걸지 않는다 — 대용량 생성 남용을 방지하기 위해 현재 필터 범위를 기본으로 한다.
64
+ - Avoid: 상태 배지를 색상만으로 구분하는 디자인 — 색각 이상 사용자에게 상태 정보가 사라진다.
65
+ ```
66
+
67
+ ---
68
+
69
+ ## Why this is a good dashboard-design example
70
+
71
+ - **Amount/number-format evidence is explicit** — 원 표기, 천 단위 콤마, 우측 정렬, tabular-nums, number format 일관성, matching `route --check dashboard-design`'s money-format requirement.
72
+ - **Table accessibility is concrete** — `<caption>`, `scope="col"`, `aria-sort` on sortable headers, matching the table-a11y requirement.
73
+ - **Density and responsive degradation are explicit** — density/compact toggle plus a named `demote`/강등 strategy per breakpoint (tablet horizontal scroll + sticky column, mobile card-list demotion), matching the density-responsive requirement.
74
+ - **Grounding is load-bearing** — every section traces back to `dashboard-composition.md`, `money-and-amount.md`, or `korean-density-conventions.md`, cited in the header.
75
+ - **Accessibility is concrete beyond the table** — keyboard traversal, focus trap, `aria-live`, explicit 4.5:1 contrast ratio.
76
+ - **Edge cases are named** — export failure, empty result set — not just the happy path.
77
+ - **A Don't section closes the spec** — four concrete anti-patterns tied to rules stated earlier.
78
+
79
+ ## Cross-reference
80
+
81
+ - [`knowledge/patterns/dashboard-composition.md`](../knowledge/patterns/dashboard-composition.md) — KPI strip + table-first layout pattern reference
82
+ - [`knowledge/patterns/money-and-amount.md`](../knowledge/patterns/money-and-amount.md) — Korean amount formatting and sign conventions
83
+ - [`knowledge/i18n/korean-density-conventions.md`](../knowledge/i18n/korean-density-conventions.md) — Korean B2B density expectations
84
+ - [`knowledge/patterns/list-and-feed.md`](../knowledge/patterns/list-and-feed.md) — table/list pagination and density patterns reused above
85
+ - [`knowledge/layout/spacing-and-grid.md`](../knowledge/layout/spacing-and-grid.md) — KPI grid and breakpoint layout foundations
86
+ - [`knowledge/typography/type-scale-fundamentals.md`](../knowledge/typography/type-scale-fundamentals.md) — numeric type scale for KPI hero values
87
+ - [`docs/DOGFOOD-DASHBOARD-FINDINGS.md`](../docs/DOGFOOD-DASHBOARD-FINDINGS.md) — the real dogfood run (G-1) this example is adapted from
@@ -0,0 +1,141 @@
1
+ <!-- hand-written -->
2
+ # Worked example: report & block flow design
3
+
4
+ > Generated by the `flow-design` route (`skills/ux-audit`, `skills/design-critique`). Demonstrates the canonical feature-flow spec format — entry points, step sequence, state transitions, edge/error paths, completion criteria, and the Korean trust & safety regulatory floor. Adapted from the dogfood brief in [`docs/DOGFOOD-SDK-FINDINGS.md`](../docs/DOGFOOD-SDK-FINDINGS.md) (F-1).
5
+
6
+ **Brief**: 커뮤니티 앱의 게시물 신고 및 사용자 차단 플로우 설계 — 신고 사유 선택, 처리 상태 안내, 차단 후 상호작용 차단 범위.
7
+
8
+ Cite [`knowledge/patterns/ui-reasoning.md`](../knowledge/patterns/ui-reasoning.md) (task-shape reasoning for interaction flows), [`knowledge/patterns/async-control.md`](../knowledge/patterns/async-control.md) (in-flight submit states), and [`knowledge/patterns/trust-safety-moderation.md`](../knowledge/patterns/trust-safety-moderation.md) (report/block domain rules) as the grounding for this spec.
9
+
10
+ ---
11
+
12
+ ```markdown
13
+ # 게시물 신고 및 사용자 차단 플로우
14
+
15
+ > 플랫폼: 모바일 웹 (iOS Safari + Android Chrome) + 데스크톱
16
+ > 대상: 커뮤니티 앱 — 게시물, 댓글, 프로필 대상 신고/차단
17
+ > 근거: knowledge/patterns/ui-reasoning.md, knowledge/patterns/async-control.md, knowledge/patterns/trust-safety-moderation.md, knowledge/PRINCIPLES.md
18
+
19
+ ## 진입점 (Entry points)
20
+
21
+ - **신고**: 게시물/댓글/프로필의 오버플로 메뉴(⋯) → "신고" 항목. 목적성 있는 트리거이며 삭제 계열 액션이 아니므로 destructive red로 스타일링하지 않는다.
22
+ - **차단**: 사용자 프로필 화면의 "차단" 메뉴, 또는 신고 완료 확인 화면에서 옵션으로 제공(신고와 차단은 독립 액션 — 자동 번들 금지).
23
+ - 두 진입점 모두 키보드로 Tab 이동 후 Enter/Space로 활성화 가능해야 한다.
24
+
25
+ ## 신고 플로우 — 단계 (Steps)
26
+
27
+ | 단계 | 상태 | 설명 |
28
+ | --- | --- | --- |
29
+ | 1. 사유 선택 | `idle` → `selecting` | 바텀시트(모바일)/모달(데스크톱)에서 라디오 그룹으로 사유 6종 중 1개 선택 |
30
+ | 2. 상세 입력 (선택 시) | `selecting` → `detailing` | "기타" 선택 시에만 텍스트 영역 노출 (최대 500자) |
31
+ | 3. 제출 | `detailing`/`selecting` → `submitting` | 제출 버튼은 사유 미선택 상태에서 비활성화 |
32
+ | 4. 접수 확인 | `submitting` → `received` | 토스트/확인 뷰 "신고가 접수되었어요" 즉시 표시 |
33
+ | 5. 처리 상태 추적 | `received` → `in_review` → `actioned` \| `declined` | "신고 내역"에서 상태 뱃지로 추적, 전환마다 알림 발송 |
34
+
35
+ 전체 신고 사유 목록 (마이너 태그, 모더레이션 백오피스는 더 세분화):
36
+
37
+ 1. 스팸/광고
38
+ 2. 욕설·혐오
39
+ 3. 성적 콘텐츠
40
+ 4. 사기·사칭
41
+ 5. 개인정보 노출
42
+ 6. 기타 (자유 텍스트)
43
+
44
+ ## 처리 상태 커뮤니케이션
45
+
46
+ ```
47
+ 접수됨 ──▶ 검토 중 ──▶ 조치 완료
48
+ └──▶ 기각
49
+ └──▶ 임시조치됨 (권리침해 주장 시, 최대 30일)
50
+ ```
51
+
52
+ - 상태는 색상 + 텍스트 뱃지로 표시한다 (색상 단독 금지).
53
+ - 조치 완료 시 조치 여부만 안내하고 구체적 제재 내용은 밝히지 않는다 (보복 방지) — "커뮤니티 가이드라인 위반으로 조치했어요".
54
+ - 기각 시 재신고 경로를 제공한다(추가 근거 첨부 가능).
55
+
56
+ ### 한국 정보통신망법 준수 — 임시조치 (제44조의2)
57
+
58
+ - 게시물이 타인의 권리(명예훼손, 사생활 침해)를 침해한다는 주장이 접수되면, 운영자는 **최대 30일간 임시조치**(콘텐츠 비공개)를 취할 수 있다.
59
+ - 임시조치 상태는 접수/검토/조치 완료와 구분되는 별도 상태로 UI에 노출한다 ("임시조치됨").
60
+ - 작성자에게는 조치 사유(법적 근거)와 이의제기 경로(30일 이내 처리 예정 명시)를 함께 안내한다. 신고자 신원은 공개하지 않는다.
61
+ - 청소년유해매체물 카테고리는 별도 라벨/연령 게이트 처리 대상이며 "삭제됨"으로 뭉뚱그리지 않는다.
62
+
63
+ ## 차단 플로우 — 단계 (Steps)
64
+
65
+ | 단계 | 상태 | 설명 |
66
+ | --- | --- | --- |
67
+ | 1. 차단 트리거 | `idle` → `confirming` | 프로필/게시물 메뉴에서 "차단" 선택 → 확인 다이얼로그 |
68
+ | 2. 확인 | `confirming` → `blocking` | 다이얼로그에 범위를 한 줄로 명시: "서로의 게시물이 보이지 않고 DM을 보낼 수 없어요" |
69
+ | 3. 완료 | `blocking` → `blocked` | 완료 즉시 상호작용 범위 적용, 성공 토스트 없이 조용히 반영(과시적 확인 지양) |
70
+
71
+ ### 차단 범위 (블라스트 반경)
72
+
73
+ | 상호작용 | A가 B를 차단한 후 |
74
+ | --- | --- |
75
+ | B가 A의 프로필/게시물 조회 | 숨김 또는 최소 정보만("게시물이 없어요") — "차단되었습니다" 노출 금지 |
76
+ | B가 A에게 댓글/DM 시도 | 작성창에서 조용히 차단, 일반적인 실패 문구 |
77
+ | A가 B의 콘텐츠 조회 | 피드/검색/알림에서 모두 숨김 |
78
+ | 기존 팔로우 관계 | 양방향 자동 해제 |
79
+ | 공유 스레드/그룹 콘텐츠 | A에게 "차단한 사용자의 댓글이에요" + 펼치기로 접힘 표시 |
80
+
81
+ - **비통지 원칙**: 차단 사실은 상대방에게 절대 알리지 않는다. 상대가 추론하는 것은 허용하되 확인해주지 않는다.
82
+ - 차단 해제는 설정 > 차단 목록에서 1회 확인 후 진행하며, 해제해도 끊긴 팔로우는 복구되지 않음을 안내한다.
83
+
84
+ ## 엣지 케이스와 오류 경로 (Edge cases & error paths)
85
+
86
+ - **제출 실패(네트워크 에러)**: 제출 버튼이 `submitting` 상태에서 실패로 전환되면 "신고 접수에 실패했어요. 다시 시도해주세요" 인라인 에러 + 재시도 버튼. 입력한 사유/텍스트는 보존한다.
87
+ - **취소/이탈**: 사유 선택 시트에서 바깥 영역 클릭 또는 Escape로 취소 가능. 취소 시 어떤 서버 호출도 발생하지 않는다.
88
+ - **신고 남용(레이트 리밋)**: 사용자당 시간당 신고 횟수를 제한하되, 한도 초과 시 "신고 한도 초과" 같은 명시적 에러 대신 조용히 큐잉("접수되었어요"로 동일 표시)한다 — 악용자에게 임계값을 노출하지 않기 위함.
89
+ - **브리게이딩 방지**: 신고 수가 자동으로 콘텐츠를 조치하지 않으며, 신고 카운트를 UI에 노출하지 않는다("n명이 신고했어요" 금지).
90
+ - **차단 확인 중 이탈**: 다이얼로그를 닫으면(Escape/바깥 클릭) `confirming` → `idle`로 되돌아가고 아무 상태도 변경되지 않는다.
91
+ - **중복 차단 시도**: 이미 차단된 사용자에 대해 차단 메뉴는 "차단 해제"로 전환되어 표시된다(중복 액션 방지).
92
+
93
+ ## 완료 기준과 이탈 지점 (Completion & exit criteria)
94
+
95
+ - **신고 완료 기준**: 사유 선택 + (조건부) 상세 텍스트 + 제출 성공 응답 수신 시 완료로 간주하며, 리포터 측 화면에서 신고 콘텐츠는 즉시 블러/축소 처리한다(모더레이션 판정이 아닌 클라이언트 배려로 명시).
96
+ - **차단 완료 기준**: 확인 다이얼로그에서 명시적 확인 클릭 + 서버 응답 수신 시 완료. 실패 시 `confirming` 상태로 롤백하고 에러를 표시한다.
97
+ - **이탈(dropoff) 지점**: 사유 선택 화면에서의 이탈이 가장 흔하다 — 사유 미선택 상태에서 시트를 닫아도 데이터 손실 경고는 표시하지 않는다(신고는 낮은 리스크 작업).
98
+
99
+ ## 반응형 동작
100
+
101
+ - Mobile (360–430px): 전체 폭 바텀시트, 사유 리스트는 세로 스택.
102
+ - Tablet (768px): 모바일과 동일한 시트 레이아웃 유지(모달로 전환하지 않음).
103
+ - Desktop (1024px+): 중앙 정렬 모달, 최대 너비 480px, 배경 스크림 적용.
104
+
105
+ ## 접근성
106
+
107
+ - 사유 리스트: `role="radiogroup"`, 각 항목 `aria-checked`, 시트 오픈 시 첫 라디오에 초기 포커스, 화살표 키로 이동.
108
+ - 다이얼로그/시트: 포커스 트랩, `aria-modal="true"`, `Escape`로 닫힘, 닫힐 때 트리거(⋯ 메뉴)로 포커스 복귀.
109
+ - 상태 전환(접수 확인, 처리 상태 변경)은 `aria-live="polite"`로 스크린 리더에 안내.
110
+ - 터치 타깃은 사유 행/차단 확인 버튼 모두 44×44px 이상, 8px 그리드 간격 준수.
111
+ - 상태 뱃지 및 블러 오버레이 텍스트는 최소 **4.5:1 contrast ratio** (WCAG AA) 확보 — 경고/오류 텍스트도 동일 기준 적용.
112
+
113
+ ## 하지 말아야 할 것 (Don't)
114
+
115
+ - Don't: 차단 시 상대방에게 알림을 보내지 마세요 — 비통지 원칙 위반이며 보복 위험을 키운다.
116
+ - Don't: 신고 사유를 하나의 자유 텍스트 필드로만 받지 마세요 — 분류/집계가 불가능해진다.
117
+ - Don't: 신고 수를 콘텐츠에 노출하지 마세요 — 브리게이딩을 유발한다.
118
+ - Don't: 임시조치를 작성자에게 설명 없이 적용하지 마세요 — 법적 근거와 이의제기 경로는 필수 요소다.
119
+ - Avoid: 조치 완료 사유를 구체적으로("게시물 삭제 처리") 드러내는 문구 — 재신고 대상에게 회피 학습을 제공한다.
120
+ ```
121
+
122
+ ---
123
+
124
+ ## Why this is a good flow-design example
125
+
126
+ - **Entry points are explicit** — where the flow starts, for both report and block, including keyboard trigger.
127
+ - **Steps and states are tabular** — every step names its before/after state, matching `route --check flow-design`'s "states/steps" requirement.
128
+ - **Edge cases and error paths get their own section** — network failure, abuse rate-limiting, cancellation, duplicate actions.
129
+ - **Completion and exit criteria are explicit** — what counts as "done" for each flow, and where users drop off.
130
+ - **The Korean regulatory floor is load-bearing, not a footnote** — 정보통신망법 제44조의2 임시조치 gets its own subsection with the 30-day window, notice-and-objection duty, and youth-protection carve-out.
131
+ - **Accessibility is concrete** — `role="radiogroup"`, focus trap, `aria-live`, explicit 4.5:1 contrast ratio, 44×44px touch targets.
132
+ - **A Don't section closes the spec** — five concrete anti-patterns, each tied back to a rule stated earlier (non-notification, abuse prevention, legal disclosure).
133
+
134
+ ## Cross-reference
135
+
136
+ - [`knowledge/patterns/trust-safety-moderation.md`](../knowledge/patterns/trust-safety-moderation.md) — full report/block/moderation pattern reference
137
+ - [`knowledge/patterns/ui-reasoning.md`](../knowledge/patterns/ui-reasoning.md) — task-shape reasoning for flow specs
138
+ - [`knowledge/patterns/async-control.md`](../knowledge/patterns/async-control.md) — in-flight submit state handling
139
+ - [`knowledge/patterns/form-design.md`](../knowledge/patterns/form-design.md) — radio group and validation timing patterns reused above
140
+ - [`knowledge/patterns/error-states.md`](../knowledge/patterns/error-states.md) — inline error + retry pattern for the submit-failure edge case
141
+ - [`docs/DOGFOOD-SDK-FINDINGS.md`](../docs/DOGFOOD-SDK-FINDINGS.md) — the real dogfood run (F-1) this example is adapted from
@@ -1,7 +1,7 @@
1
1
  <!-- generated by tools/audit/check-coverage.py — do not hand-edit -->
2
2
  ---
3
3
  title: Design-AI coverage report
4
- generated_at: 2026-07-03
4
+ generated_at: 2026-07-06
5
5
  ---
6
6
 
7
7
  # Coverage report
@@ -12,9 +12,9 @@ generated_at: 2026-07-03
12
12
 
13
13
  | Layer | Count | Detail |
14
14
  | --- | --- | --- |
15
- | Knowledge files | 94 | 79 hand-written + 15 generated |
15
+ | Knowledge files | 95 | 80 hand-written + 15 generated |
16
16
  | Skills (PLAYBOOK + SKILL) | 20 | 20 with verification phase |
17
- | Worked examples | 221 | |
17
+ | Worked examples | 223 | |
18
18
  | Extractors | 12 | |
19
19
  | Canonical components | 200 | indexed across Ant / MUI / shadcn |
20
20
  | Components with worked spec | 181 | **90.5% spec coverage** (16 via parent/alias specs) |
@@ -35,7 +35,7 @@ generated_at: 2026-07-03
35
35
  | `illustration` | 5 | 5 | 0 |
36
36
  | `layout` | 1 | 1 | 0 |
37
37
  | `motion` | 6 | 6 | 0 |
38
- | `patterns` | 31 | 25 | 6 |
38
+ | `patterns` | 32 | 26 | 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) | 743 | generated | Coverage report |
52
+ | [knowledge/COVERAGE.md](../knowledge/COVERAGE.md) | 746 | generated | Coverage report |
53
53
  | [knowledge/PRINCIPLES.md](../knowledge/PRINCIPLES.md) | 108 | hand-written | Design-AI principles |
54
54
 
55
55
  #### a11y
@@ -181,6 +181,7 @@ generated_at: 2026-07-03
181
181
  | [knowledge/patterns/slide-deck-design.md](../knowledge/patterns/slide-deck-design.md) | 316 | hand-written | Slide deck design |
182
182
  | [knowledge/patterns/styles-catalog.md](../knowledge/patterns/styles-catalog.md) | 2599 | generated | Visual style catalog |
183
183
  | [knowledge/patterns/technical-writing.md](../knowledge/patterns/technical-writing.md) | 306 | hand-written | Technical writing |
184
+ | [knowledge/patterns/trust-safety-moderation.md](../knowledge/patterns/trust-safety-moderation.md) | 107 | hand-written | Trust & safety moderation patterns |
184
185
  | [knowledge/patterns/ui-reasoning.md](../knowledge/patterns/ui-reasoning.md) | 1853 | generated | UI category decision rules |
185
186
  | [knowledge/patterns/ux-guidelines.md](../knowledge/patterns/ux-guidelines.md) | 1454 | generated | UX guidelines |
186
187
 
@@ -468,11 +469,13 @@ generated_at: 2026-07-03
468
469
  | [examples/component-voice-input.md](../examples/component-voice-input.md) | 408 | `VoiceInput` (custom — push-to-talk + transcript voice input) — spec |
469
470
  | [examples/component-watermark.md](../examples/component-watermark.md) | 217 | `Watermark` — spec |
470
471
  | [examples/component-zoom.md](../examples/component-zoom.md) | 92 | `Zoom` — spec |
472
+ | [examples/dashboard-design-settlement.md](../examples/dashboard-design-settlement.md) | 87 | Worked example: seller settlement dashboard design |
471
473
  | [examples/doc-explanation-example.md](../examples/doc-explanation-example.md) | 196 | Worked example: explanation |
472
474
  | [examples/doc-how-to-example.md](../examples/doc-how-to-example.md) | 205 | Worked example: how-to |
473
475
  | [examples/doc-tutorial-example.md](../examples/doc-tutorial-example.md) | 162 | Worked example: tutorial |
474
476
  | [examples/dogfood-korean-fintech-system.md](../examples/dogfood-korean-fintech-system.md) | 433 | Dogfood: Korean fintech mobile app — design system bootstrap |
475
477
  | [examples/email-transactional-example.md](../examples/email-transactional-example.md) | 259 | Worked example: transactional email (Korean fintech) |
478
+ | [examples/flow-design-report-block.md](../examples/flow-design-report-block.md) | 141 | Worked example: report & block flow design |
476
479
  | [examples/palette-saas-violet.md](../examples/palette-saas-violet.md) | 235 | Palette: B2B SaaS — violet primary |
477
480
  | [examples/print-business-card-spec.md](../examples/print-business-card-spec.md) | 205 | Print spec: business card (Korean 명함, premium tier) |
478
481
  | [examples/print-packaging-spec.md](../examples/print-packaging-spec.md) | 285 | Print spec: folding carton (Korean cosmetics, mid-tier) |
@@ -0,0 +1,107 @@
1
+ <!-- hand-written -->
2
+ ---
3
+ title: Trust & safety moderation patterns
4
+ applies_to: [web, mobile, community, social]
5
+ version: 1.0.0
6
+ last_updated: 2026-07
7
+ stability: stable
8
+ ---
9
+
10
+ # Trust & safety moderation patterns
11
+
12
+ Report and block are the two user-facing safety controls every community product ships. They look small — a menu item and a confirm dialog — but they carry legal obligations (Korea's 정보통신망법), retaliation risk, and abuse vectors that most flows get wrong. This file covers the report flow, moderation status communication, block semantics, and abuse prevention. For the in-flight states of the submit action itself, see [`patterns/async-control.md`](async-control.md).
13
+
14
+ ## Report flow
15
+
16
+ ### Entry and reach
17
+
18
+ - Entry point: the overflow menu (⋯) on every reportable object — post, comment, profile, DM. Reporting is not a destructive action on the reporter's data; do not style it in destructive red.
19
+ - Never hide or bury the report entry to reduce report volume. Reach is a safety property; volume is a moderation-capacity problem, solved by triage, not by friction.
20
+ - The whole flow completes in **two taps plus optional text**: open → pick a reason → submit. No review-summary step, no login-wall re-prompt mid-flow.
21
+
22
+ ### Reason taxonomy
23
+
24
+ Pick 5–6 fixed reasons plus "other". More granularity belongs to the moderation back office, not the reporter.
25
+
26
+ | Reason | Typical scope |
27
+ | --- | --- |
28
+ | 스팸/광고 (spam/ads) | Commercial flooding, link farms |
29
+ | 욕설·혐오 (abuse/hate) | Harassment, slurs, hate speech |
30
+ | 성적 콘텐츠 (sexual content) | NSFW where prohibited |
31
+ | 사기·사칭 (fraud/impersonation) | Scams, fake identity |
32
+ | 개인정보 노출 (privacy exposure) | Doxxing, leaked personal data |
33
+ | 기타 (other) | Free text, 200–500 chars |
34
+
35
+ - Single-select radio group in a mobile bottom sheet / desktop centered modal (~480px max width).
36
+ - "기타" reveals its text area only when selected — progressive disclosure keeps the sheet short.
37
+ - Submit stays disabled until a reason is selected.
38
+
39
+ ### After submit
40
+
41
+ - Immediate acknowledgment: toast or confirmation view — "신고가 접수되었어요. 검토 후 알려드릴게요."
42
+ - **Reporter-side soft-hide**: blur or collapse the reported content in the reporter's own feed immediately. This is a client-side courtesy, not a moderation verdict — never present it as "removed".
43
+ - Let the reporter optionally block the author from the confirmation view (block and report are independent actions; offer, don't bundle).
44
+
45
+ ## Moderation status communication
46
+
47
+ Reports move through a visible pipeline. Silence after a report is the top driver of "reporting does nothing" distrust.
48
+
49
+ ```
50
+ 접수됨 (received) ──▶ 검토 중 (in review) ──▬▶ 조치 완료 (actioned)
51
+ └──▶ 기각 (declined)
52
+ ```
53
+
54
+ - Surface status in a "신고 내역" (my reports) list — my page or notification center — and push a notification on each transition.
55
+ - Status badges use **color + text**, never color alone.
56
+ - On 조치 완료, say *that* action was taken, not *what* action ("커뮤니티 가이드라인 위반으로 조치했어요"). Detailing sanctions invites retaliation and gaming.
57
+ - On 기각, offer a path to re-report with more context rather than a dead end.
58
+
59
+ ### Korean regulatory floor (정보통신망법)
60
+
61
+ For services operating in Korea:
62
+
63
+ - **제44조의2 (임시조치)**: when someone claims a post violates their rights (defamation, privacy), the operator may temporarily blind the content for **up to 30 days** while the dispute is assessed. Design implication: a distinct "임시조치됨" state exists between received and actioned — the content is hidden from everyone, the author is notified with the legal basis and an objection path (이의제기).
64
+ - State the expected handling window in the report confirmation ("접수 후 30일 이내 처리"), because the statute anchors user expectations.
65
+ - The author of blinded content must see *why* (which clause, who requested category-wise — not the reporter's identity) and *how to object*. An unexplained disappearance is both a UX failure and a legal-risk amplifier.
66
+ - Youth-protection categories (청소년유해매체물) carry separate labeling/age-gate duties — treat them as a distinct moderation outcome, not a generic "removed".
67
+
68
+ ## Block semantics
69
+
70
+ Define the blast radius precisely and document it in the confirm dialog — vague blocking erodes trust in both directions.
71
+
72
+ | Interaction | After A blocks B |
73
+ | --- | --- |
74
+ | B views A's profile/posts | Hidden or minimal profile ("게시물이 없어요" 패턴), never "A님이 회원님을 차단했어요" |
75
+ | B comments on / DMs A | Blocked at the composer, generic failure copy |
76
+ | A sees B's content | Hidden from feeds, search, and notifications |
77
+ | Existing follows | Auto-severed both ways |
78
+ | Shared spaces (same thread/group) | B's content collapsed for A ("차단한 사용자의 댓글이에요" + 펼치기) |
79
+
80
+ - **Non-notification principle**: never tell B they were blocked — notification invites retaliation. Accept that B may infer it; do not confirm it.
81
+ - Block ≠ report: block is personal distance (immediate, reversible, no review); report is a moderation request (queued, reviewed). Keep the verbs and flows separate.
82
+ - Confirm dialog states the scope in one line ("서로의 게시물이 보이지 않고 DM을 보낼 수 없어요"), with focus trap, `Escape` to cancel, and the confirm button carrying the specific verb ("차단하기", never "확인").
83
+ - Unblock lives in 설정 > 차단 목록, one confirm, and does **not** restore severed follows — say so.
84
+
85
+ ## Abuse prevention (the reporter side is also an attack surface)
86
+
87
+ - Rate-limit reports per user per hour; throttle silently past the limit (accept-but-queue) rather than showing "신고 한도 초과", which teaches the abuser the threshold.
88
+ - Brigading (mass-report campaigns) means report *count* must never auto-action content by itself; visible counters invite pile-ons — do not show "n명이 신고했어요".
89
+ - False-report patterns feed the reporter's own trust score in the back office; the UI stays neutral either way.
90
+ - Never let the reported author see who reported them — in any state, including 임시조치 notices and moderation appeals.
91
+
92
+ ## Accessibility floor
93
+
94
+ - Reason list: `role="radiogroup"`, arrow-key navigation, first radio takes initial focus when the sheet opens.
95
+ - Sheet/modal: focus trap, `Escape` closes, `aria-modal="true"`; returning focus to the triggering ⋯ menu on close.
96
+ - Status changes announce via `aria-live="polite"` (submit confirmation, status transitions surfaced in-page).
97
+ - Touch targets ≥ 44×44px for reason rows and the block confirm actions; 8px spacing grid.
98
+ - Status badges and blur overlays meet 4.5:1 contrast; blurred-content notices remain readable text, not just a visual effect.
99
+
100
+ ## Don't
101
+
102
+ - Don't hide the report entry to suppress report volume — triage capacity is the fix, reach is non-negotiable.
103
+ - Don't notify the blocked user, ever — the non-notification principle exists to prevent retaliation.
104
+ - Don't show report counts on content — it invites brigading.
105
+ - Don't expand the reporter-facing taxonomy past ~6 reasons — classification depth belongs to the moderation back office.
106
+ - Don't present the reporter-side soft-hide as a moderation verdict — "숨김" is personal, "조치" is institutional.
107
+ - Don't leave 임시조치 unexplained to the author — the legal basis and objection path are part of the design, not legal fine print.