First-principles UI/IA reasoning: turns a `<scenario>` + API field set into JTBD analysis, principle-anchored field-priority decisions, anti-pattern findings, and a bidirectional UI↔API gap report. Trigger: UI、UX、資訊架構、IA、scenario-driven UI、欄位優先級、information hierarchy. Not for: visual/CSS work (use `/frontend-design`), post-build critique (use `/critique`), or simplifying existing layouts (use `/distill`).
Scanned 9/4/2026
Install to Claude Code
npx -y skills add sd0xdev/sd0x-harness --skill ui-first-principles --agent claude-codeInstalls into .claude/skills of the current project.
Are you the author of Ui First Principles?
Add the live security badge to your README — it updates automatically with every re-scan.
[](https://www.skillsdirectory.com/skills/sd0xdev-ui-first-principles-sd0x-harness)More formats (shields.io, HTML) on the badges page.
---
name: ui-first-principles
description: "First-principles UI/IA reasoning: turns a `<scenario>` + API field set into JTBD analysis, principle-anchored field-priority decisions, anti-pattern findings, and a bidirectional UI↔API gap report. Trigger: UI、UX、資訊架構、IA、scenario-driven UI、欄位優先級、information hierarchy. Not for: visual/CSS work (use `/frontend-design`), post-build critique (use `/critique`), or simplifying existing layouts (use `/distill`)."
allowed-tools: Read, Grep, Glob, Write, Bash(bash:*), Bash(node:*), Bash(mktemp:*), Bash(rm:*)
---
# UI First-Principles
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-design` reads §5 Information Hierarchy directly.
## Non-Negotiable Rules
> 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 |
## Trigger
- Keywords: UI first principles, IA design, 資訊架構, 欄位優先級, scenario-driven UI, JTBD UI, anti-pattern audit, ui-first-principles
- Slash form: `/ui-first-principles <scenario>`
## When NOT to Use
| 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) |
## Arguments
| 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: `--api` is required in v1 (`--manual` is 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.js` cannot 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.
## Workflow
```
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) ____________________________|
```
### Phase 0 — Preflight (Bash)
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">
```
2. Verify the file at `--api` exists and is readable; on failure use `Reason: input_unreadable` with the offending path in `Detail:`.
3. `TMPDIR=$(mktemp -d /tmp/ui-fp.XXXXXX)`. Pass to all later phases. Install the cleanup trap **before** any later phase runs:
```bash
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.
### Phase 1 — Redact (Bash → JSON file)
```bash
node scripts/skills/ui-first-principles/redact.js \
--input "${API_PATH:-$MANUAL_PATH}" \
--inputFormat "$INPUT_FORMAT" \
--domain "${DOMAIN:-}" \
--output "$TMPDIR/phase1.json"
```
> `INPUT_FORMAT` must be `json_sample` in v1 (the only supported value, since `--api` is the only supported input mode — see Arguments table). Omitting `--inputFormat` lets `redact.js` default to `json_sample`, which is correct for `--api`; on `JSON.parse` failure the redactor still falls back to `fallbackStringMode` (KV-pair masking) rather than producing an empty result, but the orchestrator should always pass `--inputFormat` explicitly so Phase 2 can cross-check the format with `--inputFormat` (Phase 2 normalization branches on it). Once `--manual` ships in v2 the contract becomes "pass exactly what Phase 0 selected."
Output schema (consumed by Phase 2 — exactly what `redact.js --output` writes):
```json
{
"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).
#### PII Class Reference
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.
### Phase 2 — Normalize (Bash → JSON file)
```bash
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):
```json
{
"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`.
### Phase 3 — JTBD Analysis (LLM)
1. Read `references/jtbd-framework.md` (functional / emotional / social elicitation rules; Web3 specialization).
2. Read `$TMPDIR/bundle.json`.
3. Produce `## 1. JTBD Analysis` (3 subsections; empty dimensions explicitly say `none in this scenario`).
### Phase 4 — Principles Briefing (LLM)
1. Read `references/principle-anchors.md` (5 principles + reasoning-chain order + mask semantics).
2. Internalize the closed enum `JTBD | CognitiveLoadTheory | HicksLaw | MillersLaw | ProgressiveDisclosure` — every Phase 5 row cites exactly one.
### Phase 5 — Field Decision Table (LLM)
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.
### Phase 5b — Anti-Pattern Findings (LLM)
1. Read `references/anti-patterns.md` (5 IDs + triggers + severity rubric).
2. 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.
3. 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).
### Phase 6 — Gap Report (LLM)
Bidirectional gap analysis (validator Rule 4 requires both directions):
```markdown
## 4. Gap Report
**UI needs but API missing**: <field1, field2 — or `none`>
**API provides but UI ignores**: <field3, field4 — or `none`>
```
### Phase 7 — Validate (Bash → JSON, then act)
```bash
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).
### Emit
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.
```markdown
## 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>
```
## Performance Budget
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 Loading Order
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.
## Output Schema (authoritative)
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).
## Examples
```
/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
```
<!-- A `--manual` example is intentionally omitted in v1; see Arguments table for the deferral rationale. -->
## Output
| 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) |
## Verification Checklist
- [ ] Phase 0 rejects missing `--api` and rejects `--manual` (deferred to v2)
- [ ] Phase 1 produces non-empty `forbiddenFingerprints` for any redacted-value input
- [ ] Phases 3–6 reference only `bundle.json` + the four reference docs (never raw input)
- [ ] Phase 7 critical → exactly 1 retry, then `⚠️ Need Human`
- [ ] Output ends with one sentinel: `✅ Ready` / `⚠️ Soft warnings` / `⚠️ Need Human`
- [ ] No raw redacted values appear anywhere in the report (validator's Rule 1 / 1b would catch, but verify too)
## Cross-References
- Tech spec: `docs/features/ui-first-principles/2-tech-spec.md` §3 (orchestration), §3.4 (per-phase contracts)
- Requirements: `docs/features/ui-first-principles/1-requirements.md` §FR-1 / §FR-3 / §NFR-7 / §NFR-8
- Validator: `scripts/skills/ui-first-principles/validate-report.js` (rules 1–5)
- Redactor: `scripts/skills/ui-first-principles/redact.js`
- Normalizer: `scripts/skills/ui-first-principles/normalize-input.js`
Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.
No comments yet. Be the first to comment!