From sd0x-dev-flow
Generates first-principles UI/IA analysis from scenarios and API fields: JTBD breakdown, principle-anchored field priorities, anti-pattern findings, bidirectional UI-API gap reports.
npx claudepluginhub sd0xdev/sd0x-dev-flow --plugin sd0x-dev-flowThis skill is limited to using the following tools:
Reasoning chain: `<scenario>` → JTBD → 5 IA/cognitive principles → field decisions → anti-patterns → gap report → validated handoff doc.
Applies UX principles, Nielsen's heuristics, and frameworks to review interface usability, plan user flows, evaluate designs for web, mobile, CLI, AI products.
Sets project context for UX and design strategy, routes to specialized skills, loads foundational UX knowledge. For UX/product design, evaluation, strategy, ethics, dark patterns.
Audits UI interfaces against UX principles for visual hierarchy, accessibility, cognitive load, navigation, and patterns like chunking and progressive disclosure. Useful for design reviews and pattern selection.
Share bugs, ideas, or general feedback.
Reasoning chain: <scenario> → JTBD → 5 IA/cognitive principles → field decisions → anti-patterns → gap report → validated handoff doc.
Output:
handoff-ui-first-principles.md(default<cwd>/handoff-ui-first-principles.md, override with--output). Downstream/frontend-designreads §5 Information Hierarchy directly.
SKILL.md is the normative source. Files under the
references/directory elaborate but do not override.
| # | Rule | Violation = |
|---|---|---|
| 1 | Phase 1 (redact.js) must run before any LLM phase. Raw input never enters Phases 3–6. | Skill invalid (PII risk) |
| 2 | Phase 7 critical violation (pii_leak_fingerprint / pii_leak_regex / missing_decision) → 1 retry with violation context → still critical → emit ⚠️ Need Human (no warn-only fallback). | Retry policy breach |
| 3 | Principle Anchor column must hold one ID from JTBD | CognitiveLoadTheory | HicksLaw | MillersLaw | ProgressiveDisclosure. Multi-principle prose is fine in rationale. | invalid_anchor soft violation |
| 4 | Priority column must hold one of primary | secondary | on_demand | hidden. | invalid_priority soft violation |
| 5 | Anti-Pattern Pattern column IDs must belong to the v1 whitelist in references/anti-patterns.md. Use literal (none detected) when no anti-patterns apply. | invalid_anti_pattern_id soft violation |
| 6 | Output must end with ✅ Ready (clean) or ⚠️ Soft warnings (soft only) or ⚠️ Need Human (post-retry critical). Hook + behavior layer parses these. | Auto-loop cannot parse |
/ui-first-principles <scenario>| Intent | Use instead |
|---|---|
| Visual layout / colour / Tailwind work | /frontend-design |
| Post-build evaluation of existing UI | /critique |
| Simplifying an already-shipped flow | /distill |
| Pure feasibility on a design idea | /feasibility-study |
| Tech-spec for an IA decision | /tech-spec (use this skill's output as input) |
| Arg | Required | Default | Purpose |
|---|---|---|---|
<scenario> | Yes | — | Free-text scenario name (e.g. transaction confirmation, NFT detail page). Drives JTBD. |
--api <path> | No | — | JSON sample file (single object literal). Phase 2 uses top-level keys as field set. |
--manual <path> | Deferred to v2 | — | Manual field-list file (fieldName: type (description) per line). Not supported in v1. Reason: redact.js masks via the KV-pair fallback parser, which treats field: type as field=type and masks the type literal — address: string becomes address: <redacted:address>. The masked line then fails normalize-input.js's MANUAL_LINE_RE (the type token must start with a letter or quote, not <), so the field is silently dropped from bundle.fields and Phase 7 Rule 2 cannot require a decision for it. Always use --api in v1. Manual-list support requires a redactor change tracked in the v2 backlog. |
--domain crypto | No | none | Phase 1 + 7 desensitization for 0x... addresses/hashes. |
--output <path> | No | <cwd>/handoff-ui-first-principles.md | Override report path. |
v1 invocation contract:
--apiis required in v1 (--manualis deferred to v2 — see Arguments table for why). Phase 0 rejects missing input or any combination that supplies--manual. The tech-spec §3.3 LLM-fallback path (running Phases 3–6 with no real input) is also deferred to v2 —redact.jscannot mask what does not exist, so a no-input run would publish an empty bundle and skip Rule 1 fingerprint coverage and Rule 2 field coverage. Rule 1b (regex rescan over the report) would still execute, but it cannot compensate for missing input — it only catches new PII the LLM hallucinates, not values that should have been redacted upstream.
Phase 0 preflight → Phase 1 redact → Phase 2 normalize → Phase 3 JTBD → Phase 4 principles → Phase 5 field table → Phase 5b anti-patterns → Phase 6 gap → Phase 7 validate → Emit
↑__________________________ retry-on-critical (×1) ____________________________|
Verify --api <path> was provided. v1 only accepts --api; reject --manual (deferred to v2 — see Arguments table). Reject and exit non-zero with the canonical usage banner:
⚠️ Need Human: ui-first-principles preflight error
Reason: <missing_input | unsupported_input_v1 | input_unreadable>
Usage: /ui-first-principles "<scenario>" --api <path> [--domain crypto] [--output <path>]
Detail: <one-line context — e.g. "--manual is deferred to v2; only --api is supported">
Verify the file at --api exists and is readable; on failure use Reason: input_unreadable with the offending path in Detail:.
TMPDIR=$(mktemp -d /tmp/ui-fp.XXXXXX). Pass to all later phases. Install the cleanup trap before any later phase runs:
set -Eeuo pipefail
cleanup() { rm -rf "${TMPDIR:-}" 2>/dev/null || true; }
trap cleanup EXIT
trap 'cleanup; trap - INT; kill -INT $$' INT
trap 'cleanup; trap - TERM; kill -TERM $$' TERM
This purges $TMPDIR (masked text + fingerprints) on normal exit, on a set -e failure, and on Ctrl-C / SIGTERM (the INT/TERM traps re-raise the signal so the caller observes the correct exit status). Caveats: a kill -9 (SIGKILL) cannot be trapped — $TMPDIR survives a hard kill, so do not rely on trap for security guarantees beyond a polite shutdown. set -Eeuo pipefail propagates ERR into functions (else trap ... ERR is silently dropped). Because Phase 0 uses Bash builtins (set, trap, function definitions), execute it through bash -c and keep Bash(bash:*) in allowed-tools; the Bash(node:*) | Bash(mktemp:*) | Bash(rm:*) prefixes alone do not authorize the preflight wrapper.
node scripts/skills/ui-first-principles/redact.js \
--input "${API_PATH:-$MANUAL_PATH}" \
--inputFormat "$INPUT_FORMAT" \
--domain "${DOMAIN:-}" \
--output "$TMPDIR/phase1.json"
INPUT_FORMATmust bejson_samplein v1 (the only supported value, since--apiis the only supported input mode — see Arguments table). Omitting--inputFormatletsredact.jsdefault tojson_sample, which is correct for--api; onJSON.parsefailure the redactor still falls back tofallbackStringMode(KV-pair masking) rather than producing an empty result, but the orchestrator should always pass--inputFormatexplicitly so Phase 2 can cross-check the format with--inputFormat(Phase 2 normalization branches on it). Once--manualships in v2 the contract becomes "pass exactly what Phase 0 selected."
Output schema (consumed by Phase 2 — exactly what redact.js --output writes):
{
"maskedText": "<input with PII replaced by <redacted:type> placeholders>",
"forbiddenFingerprints": ["sha256:abc...", ...],
"fieldDecisions": [{
"path": "<dot.path>",
"fieldName": "<key>",
"action": "keep" | "mask" | "crypto_allow",
"piiClass": "<present only when action=mask> — email | phone | address | account_id | national_id | credential",
"fingerprint": "<present only when action=mask> — sha256:..."
}],
"redactionSummary": {
"totalMasks": N,
"maskedClasses": ["email", "address", ...],
"cryptoAllowlistHits": M,
"baseRedactHits": K
}
}
The forbiddenFingerprints array is the Phase 7 input that catches LLM-fabricated leaks of original sensitive values. CLI exit codes: 0 on success; 2 on cli_args / unreadable_input / high_confidence_secret / redact_failed / write_failed (each emits a structured { ok: false, error, detail } JSON line on stdout).
Phase 1 and Phase 7 use different detection sets — keep them straight or you will misread Phase 7 violations. The validator file (scripts/skills/ui-first-principles/validate-report.js) is the source of truth for Phase 7; this table is a quick reference.
Phase 1 (redact.js) — masking authority. Combines regex content scan + field-name heuristics. Classes emitted as <redacted:{class}> placeholders + added to forbiddenFingerprints are exactly: email, phone, address, account_id, national_id, credential. Independent of these classes, scripts/security-redact.js runs as a base layer: high-confidence matches (PEM private keys, AWS AKIA…, OpenAI sk-…, GitHub ghp_… / github_pat_…, Slack xox*, Google AIza…) abort with high_confidence_secret; medium-confidence matches (password=, token:/api_key=/secret= assignments, JWT-like eyJ…, ≥32-char hex non-SHA1) are masked as [REDACTED] and counted as baseRedactHits. Base matches are not PII classes — they do not appear in <redacted:{class}> form.
Phase 7 (validate-report.js) — leak rescan. Two complementary checks:
| Check | Purpose | Source of truth |
|---|---|---|
pii_leak_fingerprint (Rule 1) | Catches tokenized re-leak of values whose SHA-256 prefix Phase 1 emitted into forbiddenFingerprints. The validator splits the report on whitespace + markdown/JSON delimiters and hashes each token, the punctuation-stripped variant, and the assignment RHS — so pwd:supersecret, supersecret., =supersecret all hit the same fingerprint as supersecret. It is not substring/window scanning: multi-token values (e.g. 123 Main St postal addresses) only match if the original full token appears intact. | forbiddenFingerprints Set carried via bundle.json |
pii_leak_regex (Rule 1b) | Catches LLM hallucination of plausible-looking PII that Phase 1 never saw. Smaller class set than Phase 1. | The 6 regex constants in validate-report.js (5 distinct labels — SSN and Taiwan ID share national_id) |
The actual Rule 1b regex set:
| Phase 7 regex label | Pattern source (validate-report.js) | --domain crypto behaviour |
|---|---|---|
email | EMAIL_PATTERN = /[\w.+-]+@[\w-]+\.[\w.-]+/g | always flagged |
national_id (US SSN) | SSN_PATTERN = /\b\d{3}-\d{2}-\d{4}\b/g | always flagged |
national_id (Taiwan ID) | TAIWAN_ID_PATTERN = /\b[A-Z][12]\d{8}\b/g | always flagged |
phone (E.164) | E164_PATTERN = /(?<![+\dA-Za-z_])\+\d{8,15}(?![\dA-Za-z_])/g | always flagged |
eth_address | ETH_ADDR_PATTERN = /\b0x[0-9a-fA-F]{40}\b/g | flagged only when --domain is NOT crypto |
eth_hash | ETH_HASH_PATTERN = /\b0x[0-9a-fA-F]{64}\b/g | flagged only when --domain is NOT crypto |
Classes Rule 1b does NOT regex-detect (rely on Rule 1 fingerprint instead): domestic non-E.164 phones, postal addresses, generic account IDs. These are caught only when Phase 1 actually masks or fingerprints them through value patterns, field-name heuristics (address_*, account_id, etc.), or the security-redact.js base layer. Coverage v1 does NOT regex-detect, AND redact.js has no value pattern for: IBAN, SWIFT, BIC, mnemonic / seed / recovery phrases, generic sk_… style API keys (note: only sk-… with hyphen is high-confidence). Such values must be removed from the input before invocation, or redact.js must be extended before claiming coverage.
Crypto domain semantics (current v1 implementation): --domain crypto simply suppresses Rule 1b's eth_address / eth_hash checks. It does not verify that a 0x... token in LLM prose originated from the input — fabricated hex strings under crypto domain are not regex-flagged but will still trip Rule 1 if their fingerprint matches an input value, or pass silently if they do not. Origin-aware crypto enforcement is on the v2 backlog.
node scripts/skills/ui-first-principles/normalize-input.js \
--phase1 "$TMPDIR/phase1.json" \
--scenario "$SCENARIO" \
--inputFormat "$INPUT_FORMAT" \
--output "$TMPDIR/bundle.json"
Output: ScenarioBundle JSON file with this schema. The LLM (Phases 3–6) consumes scenario, fields, inputFormat, and redactionSummary. The validator (Phase 7) consumes fields[].name (Rule 2 missing_decision), forbiddenFingerprints (Rule 1), and the three allowlists (Rules 3 / 3b / 5):
{
"scenario": "<free text>",
"fields": [{ "name": "...", "type": "...", "sampleValue": "...", "description": "...", "source": "json_sample" | "manual" }],
"inputFormat": "json_sample" | "manual_list",
"redactionSummary": { "totalMasks": N, "maskedClasses": [...], "cryptoAllowlistHits": M },
"forbiddenFingerprints": ["sha256:...", ...],
"allowedPrinciples": ["JTBD", "CognitiveLoadTheory", "HicksLaw", "MillersLaw", "ProgressiveDisclosure"],
"allowedPriorities": ["primary", "secondary", "on_demand", "hidden"],
"allowedAntiPatterns": ["too_many_primary", "scenario_field_mismatch", "pure_aesthetic_over_utility", "hidden_critical_info", "redundant_fields"]
}
Phases 3–6 read only this file plus the four reference docs — they never see raw input. Phase 7 reads the same file and uses fields[].name for Rule 2 (missing_decision), forbiddenFingerprints for Rule 1, and the three allowlists for Rules 3 / 3b / 5. CLI exit codes: 0 on success; 2 on cli_args / unreadable_phase1 / normalize_failed / write_failed.
references/jtbd-framework.md (functional / emotional / social elicitation rules; Web3 specialization).$TMPDIR/bundle.json.## 1. JTBD Analysis (3 subsections; empty dimensions explicitly say none in this scenario).references/principle-anchors.md (5 principles + reasoning-chain order + mask semantics).JTBD | CognitiveLoadTheory | HicksLaw | MillersLaw | ProgressiveDisclosure — every Phase 5 row cites exactly one.For each field in bundle.fields, decide:
| Column | Source |
|---|---|
Field | Exact name from bundle.fields[].name (no rename) |
Priority | One of primary | secondary | on_demand | hidden |
Principle Anchor | One ID from the principles whitelist |
Rationale | 1–2 sentences traced back to a Phase 3 job or quantitative threshold; never echoes the raw redacted value |
The validator's Rule 2 (
missing_decision, critical) checks every field appears as a row.
Read references/anti-patterns.md (5 IDs + triggers + severity rubric).
Emit ## 3. Anti-Pattern Findings as a markdown table (preferred — matches references/output-template.md schema).
Acceptable fallback: a bullet list whose every line matches the validator's strict grammar (parseAntiPatterns regex /^\s*[-*]\s+`([a-z][a-z0-9_]+)`/gm):
- `too_many_primary`: rationale text…
- `redundant_fields`: rationale text…
Forms the validator REJECTS as anti_pattern_unstructured: bullets without a leading backticked ID (e.g. - Pattern: too_many_primary — rationale), nested or indented sub-bullets without an ID at the top, and prose paragraphs that mention IDs inline.
If no anti-patterns apply → emit a single-row table: (none detected) | — | info | All fields pass anti-pattern checks. (parens + space — see references/anti-patterns.md § Detection Discipline).
Bidirectional gap analysis (validator Rule 4 requires both directions):
## 4. Gap Report
**UI needs but API missing**: <field1, field2 — or `none`>
**API provides but UI ignores**: <field3, field4 — or `none`>
node scripts/skills/ui-first-principles/validate-report.js \
--report "$DRAFT_PATH" \
--bundle "$TMPDIR/bundle.json" \
--domain "${DOMAIN:-}" \
> "$TMPDIR/validation.json"
Decision table:
| Result | Action |
|---|---|
ok=true, no soft violations | Write $OUTPUT, emit ✅ Ready, end |
ok=true, soft only | Prepend > ⚠️ Warnings: <list> block, write $OUTPUT, emit ⚠️ Soft warnings, end |
ok=false, critical, retry not yet attempted | Re-enter Phases 3–6 with violation context appended; retry counter = 1 |
ok=false, critical, retry already attempted | Discard draft, emit ⚠️ Need Human with violation summary; do not write report |
Rule 1 / 1b leak details are surfaced as
<redacted len=N>previews — never the raw value (re-leaking would defeat the discipline).
The report file ($OUTPUT) must follow references/output-template.md exactly — first line is # UI First-Principles Analysis: <scenario>, followed by the metadata blockquote and §1–§5. The block below is the operator-facing wrapper the skill prints to the conversation (path + run metadata + sentinel) — it is not what gets written to disk.
## UI First-Principles Analysis (run summary)
> Path: <output>
> Scenario: <scenario> Domain: <crypto|none> Input: <json_sample|manual_list>
<final markdown report rendered from $OUTPUT — header must be `# UI First-Principles Analysis: <scenario>` per references/output-template.md>
<gate sentinel>
Tech-spec §4 → NFR-5 Time Budget Breakdown sets the run target at p95 ≤ 120s (≈ 92s sum + 28s margin). When a phase exceeds its share:
| Trigger | First action | Escalation |
|---|---|---|
| Single phase exceeds its share by < 20% | Continue — margin absorbs | Log only |
| Single phase exceeds its share by 20–50% | Compress LLM prompt for that phase (drop optional examples; keep contracts) | Re-run; if still over, log to validation summary |
| Aggregate run exceeds 120s | Skip optional reference re-loads on retry; reuse cached bundle.json | If still over after one retry → emit ⚠️ Need Human: performance budget exceeded with per-phase timings |
Never relax validator strictness to recover budget — soft warnings still emit, critical violations still retry. Compress prompts, not gates.
Reference files are progressive context — loaded only when needed:
| Phase | Reference | Why |
|---|---|---|
| 3 | references/jtbd-framework.md | Three-dimension elicitation guide; FR-3 contract |
| 4–5 | references/principle-anchors.md | 5 principle definitions, anchor whitelist, mask semantics |
| 5b | references/anti-patterns.md | 5 anti-pattern IDs + detection triggers |
| Final pre-emit | references/output-template.md | Markdown schema the validator expects |
A skilled reader can cite all four in a single read; on retry, only the file relevant to the violation needs reloading.
See references/output-template.md for the full markdown contract: header metadata → ## 1. JTBD Analysis → ## 2. Field Decision Table → ## 3. Anti-Pattern Findings → ## 4. Gap Report → ## 5. Information Hierarchy (Primary / Secondary / On-Demand / Hidden zones).
/ui-first-principles "transaction confirmation" --api fixtures/tx-confirm.json --domain crypto
/ui-first-principles "NFT 詳情" --api fixtures/nft.json --domain crypto --output docs/handoffs/nft-fp.md
| Artifact | Default location |
|---|---|
| Handoff report | <cwd>/handoff-ui-first-principles.md (override with --output) |
| Validation log | $TMPDIR/validation.json (cleaned on exit) |
| Phase 1 / 2 intermediate files | $TMPDIR/*.json (cleaned on exit) |
--api and rejects --manual (deferred to v2)forbiddenFingerprints for any redacted-value inputbundle.json + the four reference docs (never raw input)⚠️ Need Human✅ Ready / ⚠️ Soft warnings / ⚠️ Need Humandocs/features/ui-first-principles/2-tech-spec.md §3 (orchestration), §3.4 (per-phase contracts)docs/features/ui-first-principles/1-requirements.md §FR-1 / §FR-3 / §NFR-7 / §NFR-8scripts/skills/ui-first-principles/validate-report.js (rules 1–5)scripts/skills/ui-first-principles/redact.jsscripts/skills/ui-first-principles/normalize-input.js