docs: require impeccable skill for design and frontend
Adds the design-system issue, scaffolds .impeccable/live/config.json against web/index.html, and records the aoe spawn commands for the codex GPT-5.6 models in the driver table. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"files": ["web/index.html"],
|
||||
"insertBefore": "</body>",
|
||||
"commentSyntax": "html",
|
||||
"cspChecked": false
|
||||
}
|
||||
@@ -42,6 +42,27 @@ request that adds a heavy dependency to either is wrong.
|
||||
- No `unwrap()` in non-test code. The lint is on.
|
||||
- SQL goes through `sqlx` compile-time-checked queries.
|
||||
|
||||
## Design and frontend work
|
||||
|
||||
**Any issue labelled `area/web`, and any work that decides how something looks
|
||||
or behaves on screen, goes through the `impeccable` skill.** That includes the
|
||||
design system itself, component work, layout, visual hierarchy, empty and error
|
||||
states, and any change to the UI described in `DESIGN.md` §9.2 and §9.3. Invoke
|
||||
it before writing markup, not as a review pass afterwards.
|
||||
|
||||
`.impeccable/design.json` is the token system — colours, typography, spacing.
|
||||
It is authored once by that skill and then treated as the source of truth;
|
||||
components consume tokens rather than literal values. `.impeccable/live/config.json`
|
||||
points the skill's live browser iteration at `web/index.html`.
|
||||
|
||||
`~/tea/arcada` has the same setup and is worth reading for the shape of a
|
||||
finished `design.json`.
|
||||
|
||||
This matters more here than in a typical CRUD app because the manual-search
|
||||
view (§9.3) is the whole reason for the project's UI existing — Radarr's is
|
||||
unusable specifically because of a layout decision. Getting it right is a design
|
||||
problem, not a markup problem.
|
||||
|
||||
## Working an issue
|
||||
|
||||
1. Read `DESIGN.md`, then the issue, then the sections the issue cites.
|
||||
@@ -110,19 +131,28 @@ alone. The loop:
|
||||
|
||||
### Model selection
|
||||
|
||||
| Difficulty | Model |
|
||||
|---|---|
|
||||
| `difficulty/hard` | Opus 5 |
|
||||
| `difficulty/moderate` | Sonnet 5 |
|
||||
| `difficulty/easy` | Sonnet 5 |
|
||||
| `difficulty/trivial` | Haiku 4.5 |
|
||||
Sessions are spawned with `aoe`, which can run `claude`, `codex`, `opencode` and
|
||||
`pi`. The GPT-5.6 models are `terra` and `sol`, reached through the `codex` tool.
|
||||
|
||||
| Difficulty | Model | Spawn |
|
||||
|---|---|---|
|
||||
| `difficulty/hard` | Opus 5 | `aoe add <path> --tool claude --model claude-opus-5 -l` |
|
||||
| `difficulty/moderate` | Sonnet 5 or `gpt-5.6-sol` | `aoe add <path> --tool codex --extra-args "-m gpt-5.6-sol" -l` |
|
||||
| `difficulty/easy` | `gpt-5.6-terra` | `aoe add <path> --tool codex --extra-args "-m gpt-5.6-terra" -l` |
|
||||
| `difficulty/trivial` | Haiku 4.5 | `aoe add <path> --tool claude --model claude-haiku-4-5-20251001 -l` |
|
||||
|
||||
Use `-w <branch> -b` to put each session in its own git worktree, which is what
|
||||
makes parallel issues safe.
|
||||
|
||||
`gpt-5.6-terra` is confirmed — it is the default in `~/.codex/config.toml`.
|
||||
`gpt-5.6-sol` is inferred from the sibling naming and should be verified on
|
||||
first use.
|
||||
|
||||
Escalate one tier if a session fails CI twice on the same issue. Never
|
||||
de-escalate mid-issue.
|
||||
|
||||
> The operator mentioned a model named "terra" for the lower tiers. No such
|
||||
> model was identified — confirm what it refers to and correct this table
|
||||
> before relying on it.
|
||||
`area/web` issues ignore this table's lower tiers — see the design section
|
||||
above. They go through the `impeccable` skill regardless of difficulty label.
|
||||
|
||||
### What a driver must not do
|
||||
|
||||
|
||||
+70
-15
@@ -170,6 +170,9 @@ embedded into the binary with `include_dir` and served with correct MIME types.
|
||||
- Dev mode proxies `/api/*` to the running daemon so the SPA has hot reload.
|
||||
- One page that calls `GET /api/health` and renders it, to prove the whole path.
|
||||
|
||||
Use the `impeccable` skill for this — see the design section of `CLAUDE.md`.
|
||||
|
||||
|
||||
Acceptance: `cargo run` serves the built SPA from a single binary with no
|
||||
external files.
|
||||
""",
|
||||
@@ -625,10 +628,40 @@ client compiles.
|
||||
),
|
||||
|
||||
# ---------------------------------------------------------------- phase 5
|
||||
issue(
|
||||
"design", "Design system tokens",
|
||||
"phase/5-ui", "area/web", "difficulty/moderate",
|
||||
deps=["web"],
|
||||
body="""
|
||||
Author `.impeccable/design.json` — colours, typography, spacing, the token set
|
||||
every component consumes. **Use the `impeccable` skill for this**; it is what
|
||||
the file's schema belongs to (`CLAUDE.md`, design section).
|
||||
|
||||
`~/tea/arcada` has a finished `design.json` at `schemaVersion: 2` worth reading
|
||||
first for the shape.
|
||||
|
||||
Constraints specific to this app:
|
||||
|
||||
- The manual-search view (`DESIGN.md` §9.3) is dense, tabular and chip-heavy.
|
||||
The palette has to carry three verdict states — eligible, waived, rejected —
|
||||
legibly at small sizes and against each other, without relying on colour
|
||||
alone.
|
||||
- Library status (§4.2) is five states, and they are informational rather than
|
||||
severity-ranked. Do not borrow an error/warning/success ramp for them.
|
||||
- It runs on a NAS dashboard, often on a phone, often in a dark room. Dark mode
|
||||
is not an afterthought.
|
||||
|
||||
`.impeccable/live/config.json` is already scaffolded and points at
|
||||
`web/index.html`.
|
||||
|
||||
Acceptance: components consume tokens, never literal colour or spacing values,
|
||||
and CI can assert that.
|
||||
""",
|
||||
),
|
||||
issue(
|
||||
"uisearch", "Unified search box",
|
||||
"phase/5-ui", "area/web", "difficulty/moderate",
|
||||
deps=["web", "searchapi", "moviesapi"],
|
||||
deps=["web", "design", "searchapi", "moviesapi"],
|
||||
body="""
|
||||
`DESIGN.md` §9.2. One box, results grouped: in-library first, TMDB below. Enter
|
||||
on a TMDB result opens the add flow with root and policy pre-filled.
|
||||
@@ -639,13 +672,16 @@ The requirement, stated as a test: there is never a moment where the user has to
|
||||
know whether they are searching or adding. If the UI has an "Add Movie" page
|
||||
separate from search, this issue is not done.
|
||||
|
||||
Use the `impeccable` skill for this — see the design section of `CLAUDE.md`.
|
||||
|
||||
|
||||
Debounce, and cancel in-flight requests on keystroke.
|
||||
""",
|
||||
),
|
||||
issue(
|
||||
"uimanual", "Manual search buckets and attribute chips",
|
||||
"phase/5-ui", "area/web", "difficulty/moderate",
|
||||
deps=["web", "searchapi"],
|
||||
deps=["web", "design", "searchapi"],
|
||||
body="""
|
||||
`DESIGN.md` §9.3. The fix for Radarr's unusable manual search.
|
||||
|
||||
@@ -659,12 +695,15 @@ Debounce, and cancel in-flight requests on keystroke.
|
||||
- One click on a `waived` row grabs it and writes the override.
|
||||
|
||||
The client must not re-implement any policy. Verdicts arrive from the API.
|
||||
|
||||
Use the `impeccable` skill for this — see the design section of `CLAUDE.md`.
|
||||
|
||||
""",
|
||||
),
|
||||
issue(
|
||||
"uilibrary", "Library views with derived status",
|
||||
"phase/5-ui", "area/web", "difficulty/moderate",
|
||||
deps=["web", "moviesapi"],
|
||||
deps=["web", "design", "moviesapi"],
|
||||
body="""
|
||||
`DESIGN.md` §4.2. The default view shows `airing` and `incomplete` only;
|
||||
everything else collapses behind one toggle.
|
||||
@@ -675,12 +714,15 @@ the exact problem this design removed.
|
||||
|
||||
Show waivers honestly: a file imported under `allow_english_audio` reads as
|
||||
"English, no dub", not as a clean match.
|
||||
|
||||
Use the `impeccable` skill for this — see the design section of `CLAUDE.md`.
|
||||
|
||||
""",
|
||||
),
|
||||
issue(
|
||||
"uiqueues", "Attention queues: no-PT-source and needs-decision",
|
||||
"phase/5-ui", "area/web", "difficulty/easy",
|
||||
deps=["uilibrary", "failure"],
|
||||
deps=["uilibrary", "design", "failure"],
|
||||
body="""
|
||||
`DESIGN.md` §5.2 and §5.7. Two lists of things a human has to look at.
|
||||
|
||||
@@ -691,6 +733,9 @@ Show waivers honestly: a file imported under `allow_english_audio` reads as
|
||||
|
||||
These are the only places the app asks for attention, and they are the same
|
||||
events that notify (§9.5). Keep them consistent.
|
||||
|
||||
Use the `impeccable` skill for this — see the design section of `CLAUDE.md`.
|
||||
|
||||
""",
|
||||
),
|
||||
|
||||
@@ -929,32 +974,42 @@ def main():
|
||||
by_title = {i["title"]: i["number"] for i in open_issues}
|
||||
numbers = {}
|
||||
|
||||
# Pass 1 — ensure every issue exists, so dependency numbers are all known
|
||||
# before any body is rendered.
|
||||
for spec in ISSUES:
|
||||
title = spec["title"]
|
||||
if title in by_title:
|
||||
numbers[spec["key"]] = by_title[title]
|
||||
print(f"issue = #{by_title[title]} {title}")
|
||||
continue
|
||||
|
||||
body = spec["body"]
|
||||
if spec["deps"]:
|
||||
refs = []
|
||||
for d in spec["deps"]:
|
||||
refs.append(f"#{numbers[d]}" if d in numbers else f"`{d}` (not yet created)")
|
||||
body += "\n\n---\n\n**Depends on:** " + ", ".join(refs)
|
||||
body += f"\n\nLabels: `{'`, `'.join(spec['labels'])}`"
|
||||
|
||||
print(f"issue + {title}")
|
||||
if DRY:
|
||||
continue
|
||||
made = req("POST", f"/repos/{OWNER}/{REPO}/issues", {
|
||||
"title": title,
|
||||
"body": body,
|
||||
"body": spec["body"],
|
||||
"labels": [label_ids[n] for n in spec["labels"] if n in label_ids],
|
||||
})
|
||||
if made:
|
||||
numbers[spec["key"]] = made["number"]
|
||||
|
||||
# Pass 2 — render bodies with real dependency numbers and push them.
|
||||
# Declarative: this overwrites hand-edits to issue bodies. Edit the spec
|
||||
# here, not the issue on Gitea.
|
||||
for spec in ISSUES:
|
||||
if spec["key"] not in numbers:
|
||||
continue
|
||||
body = spec["body"]
|
||||
if spec["deps"]:
|
||||
refs = ", ".join(f"#{numbers[d]}" for d in spec["deps"] if d in numbers)
|
||||
body += f"\n\n---\n\n**Depends on:** {refs}"
|
||||
body += f"\n\nLabels: `{'`, `'.join(spec['labels'])}`"
|
||||
if DRY:
|
||||
continue
|
||||
req("PATCH", f"/repos/{OWNER}/{REPO}/issues/{numbers[spec['key']]}", {
|
||||
"body": body,
|
||||
"labels": [label_ids[n] for n in spec["labels"] if n in label_ids],
|
||||
})
|
||||
|
||||
print(f"\n{len(numbers)}/{len(ISSUES)} issues present")
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user