From ba2f1a1b021bc5c3264b5712c526b6fe0b2797fb Mon Sep 17 00:00:00 2001 From: Lucas Date: Mon, 28 Sep 2026 17:13:42 +0800 Subject: [PATCH 01/48] Add the domain-interview skill Interviews the user and writes a buildable brief as a software-factory Wiki card, with no design decisions. Co-Authored-By: Claude Opus 5.5 --- index.md | 2 + skills/domain-interview/SKILL.md | 173 ++++++++++++++++++ .../references/brief-template.md | 61 ++++++ skills/glossary.md | 3 + 4 files changed, 239 insertions(+) create mode 100644 skills/domain-interview/SKILL.md create mode 100644 skills/domain-interview/references/brief-template.md diff --git a/index.md b/index.md index a0342543..878c72b2 100644 --- a/index.md +++ b/index.md @@ -75,6 +75,7 @@ Every skill lives in `skills/` and auto-activates on its description triggers - **[`boxel-ui-guidelines/`](skills/boxel-ui-guidelines/SKILL.md)** — Template UI rules: theme tokens, `@fields` vs `@model`, container queries, layout safety. - **[`boxel-ui-component-discovery/`](skills/boxel-ui-component-discovery/SKILL.md)** — Mandatory catalog search for a boxel-ui component Spec before hand-rolling any UI primitive in a `.gts` template. - **[`boxel-design/`](skills/boxel-design/SKILL.md)** — Visual design language, mood, typography, asset direction. +- **[`domain-interview/`](skills/domain-interview/SKILL.md)** — Interview the user and write a brief as a software-factory Wiki card — schema, coverage matrix, per-screen content contracts, flows, sample data — with no design decisions. The card URL is the factory's `--brief-url`. - **[`boxel-theme-development/`](skills/boxel-theme-development/SKILL.md)** — The theme artifact itself: creating, converting, auditing, or patching StructuredTheme / StyleReference / BrandGuide cards, DESIGN.md import/export, logo and functional-palette placement. - **[`boxel-file-def/`](skills/boxel-file-def/SKILL.md)** — File-typed fields (FileDef, ImageDef, MarkdownDef, PngDef, CsvFileDef). - **[`boxel-flavored-markdown/`](skills/boxel-flavored-markdown/SKILL.md)** — Authoring BFM content with `:card`/`::card` directives, mermaid, math, alerts. @@ -127,5 +128,6 @@ Every skill lives in `skills/` and auto-activates on its description triggers - Three formats minimum: every CardDef needs `isolated`, `embedded`, AND `fitted`. - **Every user-facing card built from scratch goes through [`design-playbook.md`](skills/boxel/references/design-playbook.md).** Four stages: mockup with no variables → extract theme DNA → tokenize → derive fitted + embedded. Skip only for utility cards. - **Card families (2+ related CardDefs) require Stage 0 planning artifacts before any real schema** — Goal · Brief · DataModelPlan · Sample Data · ASCII Layout · MicroMockups. Without them, fitted views come out pedestrian. See design-playbook.md "Stage 0" and pattern `build-planning-cards-trio`. +- **Exception — `domain-interview` comes first.** When `domain-interview` is attached, or the user asks for a spec or a brief, run that skill and stop at its brief card (`Wiki/-brief.json`). The design-playbook, Stage 0 and build rules above apply to what is later built from the brief, not to the interview. - **When embedding child cards via `<@fields.X @format='...' />`, read [`delegated-render-control.md`](skills/boxel-ui-guidelines/references/delegated-render-control.md) first.** The host injects CardContainer chrome the parent must own (via `:deep()`, the theme cascade, or `@displayContainer={{false}}`); the doc covers the four high-frequency traps — plural-field wrapper, atom chrome on dark backgrounds, stagger via CSS variables, and the binary divider strategy. - **CQ fitted layout is mandatory for every `fitted` template.** Derive the visual layout from [`design-playbook.md`](skills/boxel/references/design-playbook.md) stage 4, then implement it with [`container-query-fitted-layout.md`](skills/boxel/references/container-query-fitted-layout.md). For standard compositions, prefer the `FittedCard` component from `@cardstack/boxel-ui/components` (implements the standard internally; tune via `--fc-*` variables). When hand-rolling: a single root `.fit` grid whose `@container` rules query the host-provided `fitted-card` size container (never create your own container on the root), container-query sub-formats, `pow()` typography variables, `minmax(0, 1fr)` body rows, and `min-height: 0` overflow discipline. Do not hand-roll fitted CSS without CQ. diff --git a/skills/domain-interview/SKILL.md b/skills/domain-interview/SKILL.md new file mode 100644 index 00000000..2cdb6937 --- /dev/null +++ b/skills/domain-interview/SKILL.md @@ -0,0 +1,173 @@ +--- +name: domain-interview +description: >- + Use when someone asks for an app, kit or card in a domain ("build me a scheduling app for + salons", "spec this out"), or when a brief is too vague to build from — even when the user sounds + confident. Interviews the user and writes a buildable brief as a Wiki card — schema, coverage + matrix, per-screen content contracts, flows, real sample data — that the software factory builds + from. NOT for layout, style or motion (boxel-design and the design-playbook own those). +boxel: + kind: skill +--- + +# Domain Interview + +_What the thing is, never how it looks._ + +**While this skill is active, the brief card is the only deliverable.** It replaces the build path +for this conversation: do not run the design-playbook or its Stage 0 artifacts, do not pick a +theme, do not write any `.gts`, and do not build a Home app — the index's build rules apply to +whatever builds from the brief, not to this skill. The only file you write is +`Wiki/-brief.json`. Start with interview round 1 below; do not open with a design, a +mockup or a schema. + +You are interviewing to find out what a practitioner in this domain actually does, then writing +it down as something a builder can build from. Two jobs, in order: **upskill the user** so they +can judge the result, and **produce the spec**. + +**Make no design decisions.** No layout, no style, no palette, no typography, no motion, no +mockups, no adjectives about feel. Content contracts describe *what information must be present +and in what priority* — never where it sits or what it looks like. This boundary is the point of +the skill: mixing the two is how a spec ends up prescribing a dashboard before anyone asked +whether the job needs one, and the design step cannot do its job on a spec that has already half-decided the screens. + +## Interview + +Research the domain first if you can — real workflows, real terminology, what practitioners +complain about. Arriving with context makes the interview shorter and better, and the user +usually cannot list what they have never had to name. + +Ask in rounds, not all at once. Each round should change what you ask next. + +1. **Who and what** — who uses this, what job it does for them, what they use today and what is + wrong with it. +2. **The work itself** — walk one real instance end to end. Concrete: names, numbers, what + happens on a bad day. Vague answers here mean the spec will be vague. +3. **The unwritten rules** — what a novice gets wrong, what the domain's edge cases are, what + must never happen. This is where expert-level features come from. +4. **Scope** — MoSCoW (must / should / could / won't) against what you have heard. Say what you + are cutting and why, so the user can push back while it is cheap. + +**Upskill as you go.** When the domain has a concept the user has not named, explain it in a +sentence and ask whether it applies. A spec the user cannot evaluate is a spec that gets +approved and then rebuilt. + +**Ask with selectable options, not a blank prompt, whenever the environment supports it.** Most +of this interview's questions have a small, nameable set of likely answers ("shared stock across +platforms, or separate per platform?", "full status lifecycle, or a simple resolved/unresolved +flag?"). When the harness offers a structured choice tool (e.g. Claude Code's `AskUserQuestion`), +use it for these instead of typing the question as prose the user must answer from scratch — it +is friendlier to someone who is not a domain expert and does not yet have the vocabulary to +compose an answer, and naming the options is itself part of upskilling them. Always include a +recommended option and leave room for free text (the tool's "Other" is enough). Reserve plain +open-ended chat questions for the ones no short option list could represent — "walk me through a +real order end to end," "what's the worst version of this you've dealt with." Batch a round's +related questions into one multi-question call rather than one at a time. If a question call is +rejected or the user wants to answer in free text instead, drop the tool for that round and +continue in chat — don't force it. + +## Output + +The spec is a **brief card**: a software-factory `Wiki` card at `Wiki/-brief.json` whose +`content` is the spec markdown, written from `references/brief-template.md`. The card URL is the +deliverable — it is what `pnpm factory:go --brief-url` takes. + +**Where it goes.** In the realm the user named. If they named none, the current realm — the +`realmUrl` in your context — when its `realmPermissions.canWrite` is true; otherwise ask which +realm to use before writing. + +**The card.** `Wiki` lives in the software-factory realm, which has no `@cardstack/*` prefix, so +`adoptsFrom` is an absolute URL: the target realm's **origin** plus `/software-factory/wiki` (for +example `https://app.boxel.ai/software-factory/wiki`). Build it from the origin — never hardcode one +environment's host. The factory reads only these attributes: + +| Attribute | Holds | +|---|---| +| `cardInfo.name` | the brief title — `{Name}`, without the "— brief" suffix | +| `cardInfo.summary` | the Overview paragraph | +| `content` | the whole spec markdown, from the template's first heading to Open questions | +| `tags` | optional — a few domain words | +| `sourceCardUrl` | leave empty for a new app; set it only when the brief adjusts an existing card | + +**Writing it in the Boxel AI assistant.** Two steps, so the spec markdown is never hand-escaped +into a JSON string: + +1. **Create the card** with one SEARCH/REPLACE block (read `source-code-editing` first if you have + not read it this session). The URL line is `Wiki/-brief.json (new)`; the body + is the card JSON with `adoptsFrom` `{ "module": "/software-factory/wiki", "name": "Wiki" }`, + `cardInfo.name`, `cardInfo.summary` and an empty `content`. +2. **Fill `content`** with `patch-fields` on that card, passing the whole spec markdown as the + value. The tool serializes it; do not escape newlines or quotes yourself. + +**Then check it landed.** Read the card back. If `content` is empty or shorter than the spec you +wrote, patch it again — never tell the user the brief is saved until the card you read back holds +it. + +It carries: + +- **Overview** — one paragraph: what it does, who for, the single deliverable. +- **Domain primer** — enough for an engineer with no domain knowledge to make sensible calls: + why the domain exists, a glossary, and the practitioner's workflow as steps, not screens. +- **Schema** — CardDefs, FieldDefs, relationships. Say which fields are computed, which are + sensitive, which are **links rather than contained copies** — that call is load-bearing, because + the build declares the real link from the first screen rather than standing it in. **Any field + whose data is an image is an image field, never a URL string**: the catalog's `Image Source Field` + lets each instance choose a pasted URL *or* a file uploaded into the realm, and a `StringField` + removes that choice before the user sees the form. Several images means the multi form; where + caption and credit matter, they belong in the same field, not in strings beside it. Do not design the field *types* in detail; + name what the data is and let the build stage pick. +- **Element coverage matrix** — every card, field, component and command the build needs, each + marked **new / extend / reuse**. The reuse column is what stops the build re-inventing a + field the catalog already ships; leave it unmarked and it will. Fill it from a real search, not + from memory — `catalog-reuse` says what to search for and declares + the tool. Name the **commands** even when they look obvious — the built app is checked against + this row, and a command that was never listed is a command nobody notices is missing. +- **Content contracts, per screen** — for each screen: its purpose, whether it **reads, writes or + both**, the primary action **and its mechanism**, what it must contain **in priority order**, the + key moment, and what the empty state has to say. The priority order is the most load-bearing + thing in the spec — the design step decides layout by reading which block the list puts first. + + **Mechanism** is one of four words, and it costs nothing to write here while costing a rewrite to + discover late: **navigate** (opens another card), **write** (changes a field on a card), **work** + (runs a command), or **read-only** (this screen has no primary action, which is a legitimate + answer and should be said out loud). The build turns each into a real control at its first + screen and wires it at the end, and a row whose mechanism was never declared is a row nobody + can wire. Do not describe *how* the action presents — that is the design step's. + + A screen that writes also says **what it writes to**: which CardDef and which fields. A form + whose target is unnamed is how a build ends up with an enquiry form bound to one thing and a save + button bound to another, both looking finished. +- **Flows** — the two or three paths that matter, as steps. +- **Sample data** — three to five instances per CardDef, written as real content: real names, + real numbers, real dates. Lorem ipsum here produces a thin-looking app later, because the + build stage composes against whatever content exists. + +## Schema thickness + +A card with three or four thin fields will look thin no matter what anyone does with it later. +When the domain has detail — a rating, an author, a duration, a category, a status history — +put it in the schema. Err toward more fields; the build stage can ignore one, but it cannot +compose with one that was never specified. + +Expect schema to grow at build time when the design needs something. That is not a spec failure. + +## Finish + +The brief card is the whole deliverable. Give the user its URL and stop — do not offer to start +building, and do not ask what happens next. If the brief still has open questions, say so in +one line; the user refines it by asking again. + +## Pair with + +- **`catalog-reuse`** — the search behind the coverage matrix's reuse column. +- **`boxel`** — the CardDef, FieldDef and link rules the schema has to respect. + +## Don't use for + +- Deciding layout, style, colour or motion — that is `boxel-design` and the design-playbook (`boxel/references/design-playbook.md`), after this spec is agreed. +- Writing CardDef or FieldDef code — that is `boxel` or the software factory, building from the brief. +- A catalog search on its own — that is `catalog-reuse`; this skill only uses it to fill the coverage matrix. + +## Sections (load on demand) + +- `references/brief-template.md` — the brief's `content`, section by section diff --git a/skills/domain-interview/references/brief-template.md b/skills/domain-interview/references/brief-template.md new file mode 100644 index 00000000..823b6c30 --- /dev/null +++ b/skills/domain-interview/references/brief-template.md @@ -0,0 +1,61 @@ +# {Name} — brief + +Written by `domain-interview` on {date}. Contains no design decisions — layout, style and motion are decided by whatever builds from this brief. + +## Overview +{One paragraph: what it does, who for, the single deliverable.} + +## Domain primer +**Why this exists** — {what the domain is for, in plain words} + +**Glossary** +| Term | Means | +|---|---| +| {term} | {plain-language definition} | + +**How a practitioner works** — steps, not screens +1. {step} +2. {step} + +**Unwritten rules** — what a novice gets wrong +- {rule, and what goes wrong without it} + +## Scope (MoSCoW) +| Priority | Item | Why | +|---|---|---| +| Must | {item} | {why} | +| Should | {item} | | +| Could | {item} | | +| Won't (this round) | {item} | {why cut} | + +## Schema +| CardDef | Fields | Links | Notes | +|---|---|---|---| +| {Name} | {field: what the data is} | {linksTo / linksToMany, to what} | {computed, sensitive} | + +## Element coverage matrix +| Element | Kind | New / Extend / Reuse | Source if reusing | +|---|---|---|---| +| {name} | card / field / component / command | | {catalog Listing or Spec, base realm, design-system component — from a `catalog-reuse` search} | + +## Content contracts +### {Screen name} +- **Purpose**: {what this screen is for} +- **Mode**: {reads | writes | both} +- **Primary action**: {what the user does here first} +- **Mechanism**: {navigate → which card | write → which CardDef and fields | work → which command | read-only} +- **Must contain, in priority order**: 1. {most important} 2. {next} 3. {next} +- **Key moment**: {the action that matters most} +- **Empty state must say**: {what the user needs to know and do} + +{repeat per screen} + +## Flows +**{Flow name}**: {step} → {step} → {step} + +## Sample data +### {CardDef} +- {real name, real numbers, real dates — written as content, not placeholders} + +## Open questions +- [ ] {what the user still needs to decide} diff --git a/skills/glossary.md b/skills/glossary.md index 44f9e7bc..d1544f5b 100644 --- a/skills/glossary.md +++ b/skills/glossary.md @@ -152,6 +152,8 @@ The 4-stage recommended process for any user-facing card: → `boxel/references/design-playbook.md` +- **Content contract** — per-screen purpose, mode, primary action + mechanism (navigate / write / work / read-only), must-contain in priority order, key moment, empty state. Written by `domain-interview`; the priority order is what the design step reads to decide layout. → `domain-interview/references/brief-template.md` + ## 8. Lint workflow - **`npx boxel file lint --realm --file `** — Local lint before push. Use during development. @@ -362,6 +364,7 @@ Use the namespaced CLI published from the Boxel monorepo through `npx boxel`. Th - **`boxel-ui-guidelines`** — Template UI rules, `delegated-render-control.md`, `template-patterns.md`, `style-budget.md`, `prevent-content-overflow.md`. - **`boxel-design`** — Visual design + asset selection + critical anti-LLM-cliché rules. - **`boxel-theme-development`** — Theme/StyleReference/BrandGuide creation, DESIGN.md mapping, brand tokens, logo/mark capture, and audit workflow. +- **`domain-interview`** — Interviews the user and writes a brief (primer, schema, coverage matrix, per-screen content contracts, flows, sample data) as a software-factory Wiki card, with no design decisions. The card URL is the factory's `--brief-url`. - **`boxel-environment`** — Driving the live app + host commands; `workflows-and-orchestration.md`, `user-environment-awareness.md`, `assistant-persona.md`. - **`boxel-file-def`** — File-backed fields (`FileDef`, `ImageDef`, etc.). - **`boxel-flavored-markdown`** — BFM authoring with directives + fenced renderers. From 248ac749e0450cde03f48d801307baac0744b6eb Mon Sep 17 00:00:00 2001 From: Lucas Date: Mon, 28 Sep 2026 17:15:31 +0800 Subject: [PATCH 02/48] Add the design-direction skill Decides how a unit looks, moves and responds before it is built, and records it as a DESIGN-DIRECTION.md. SKILL.md routes to one reference file per phase. Co-Authored-By: Claude Opus 5.5 --- index.md | 1 + skills/design-direction/SKILL.md | 132 ++++++++++ .../design-direction-md-template.md | 168 +++++++++++++ .../references/enrichment-moves.md | 203 +++++++++++++++ .../references/interaction-ways.md | 234 ++++++++++++++++++ .../references/layout-gravity.md | 21 ++ .../references/phase-1-inventory.md | 36 +++ .../references/phase-2-layout.md | 39 +++ .../references/phase-3-interaction.md | 35 +++ .../references/phase-4-style.md | 91 +++++++ .../references/phase-5-write.md | 15 ++ .../references/phase-6-hand-off.md | 47 ++++ .../references/phase-7-after-build.md | 28 +++ .../design-direction/references/revisions.md | 61 +++++ .../references/screen-types.md | 82 ++++++ .../references/signature-treatments.md | 124 ++++++++++ skills/glossary.md | 6 + 17 files changed, 1323 insertions(+) create mode 100644 skills/design-direction/SKILL.md create mode 100644 skills/design-direction/references/design-direction-md-template.md create mode 100644 skills/design-direction/references/enrichment-moves.md create mode 100644 skills/design-direction/references/interaction-ways.md create mode 100644 skills/design-direction/references/layout-gravity.md create mode 100644 skills/design-direction/references/phase-1-inventory.md create mode 100644 skills/design-direction/references/phase-2-layout.md create mode 100644 skills/design-direction/references/phase-3-interaction.md create mode 100644 skills/design-direction/references/phase-4-style.md create mode 100644 skills/design-direction/references/phase-5-write.md create mode 100644 skills/design-direction/references/phase-6-hand-off.md create mode 100644 skills/design-direction/references/phase-7-after-build.md create mode 100644 skills/design-direction/references/revisions.md create mode 100644 skills/design-direction/references/screen-types.md create mode 100644 skills/design-direction/references/signature-treatments.md diff --git a/index.md b/index.md index a0342543..e6987ce1 100644 --- a/index.md +++ b/index.md @@ -75,6 +75,7 @@ Every skill lives in `skills/` and auto-activates on its description triggers - **[`boxel-ui-guidelines/`](skills/boxel-ui-guidelines/SKILL.md)** — Template UI rules: theme tokens, `@fields` vs `@model`, container queries, layout safety. - **[`boxel-ui-component-discovery/`](skills/boxel-ui-component-discovery/SKILL.md)** — Mandatory catalog search for a boxel-ui component Spec before hand-rolling any UI primitive in a `.gts` template. - **[`boxel-design/`](skills/boxel-design/SKILL.md)** — Visual design language, mood, typography, asset direction. +- **[`design-direction/`](skills/design-direction/SKILL.md)** — Decide how a unit looks, moves and responds before it is built; asks only feeling and style, decides layout itself, and records a `DESIGN-DIRECTION.md` a builder executes and a reviewer ticks. - **[`boxel-theme-development/`](skills/boxel-theme-development/SKILL.md)** — The theme artifact itself: creating, converting, auditing, or patching StructuredTheme / StyleReference / BrandGuide cards, DESIGN.md import/export, logo and functional-palette placement. - **[`boxel-file-def/`](skills/boxel-file-def/SKILL.md)** — File-typed fields (FileDef, ImageDef, MarkdownDef, PngDef, CsvFileDef). - **[`boxel-flavored-markdown/`](skills/boxel-flavored-markdown/SKILL.md)** — Authoring BFM content with `:card`/`::card` directives, mermaid, math, alerts. diff --git a/skills/design-direction/SKILL.md b/skills/design-direction/SKILL.md new file mode 100644 index 00000000..7bc52730 --- /dev/null +++ b/skills/design-direction/SKILL.md @@ -0,0 +1,132 @@ +--- +name: design-direction +description: Decide how a Boxel unit — app, card, component or field — should look, move and respond BEFORE it is built, and record it as a DESIGN-DIRECTION.md a builder executes and a reviewer checks. Use it whenever something is about to be built or restyled, whenever a user names a style ("Mario style", "make it look like AirAsia"), calls a result boring, generic, too plain or too busy, or whenever a spec is about to go straight to code — even when nobody asks for design, because no direction means the default layout. It asks only the feeling and the style, decides layout itself with reasons written down, owns the gate where the user judges the built screen, and neither builds nor reviews. +boxel: + kind: skill +--- + +# design-direction — decide it, write it down, let them judge it built + +You are the design director for one unit. You decide how it looks before code exists, and you +record the decision so a builder can execute it and a reviewer can check it without taste. + +## Ask only what words can answer + +A person can tell you what a screen should feel like and which style it should wear. They cannot +pick a layout from wireframes with any confidence — they pick whichever sounds sensible, then +reject it once it is built and real. So: + +- **Ask** the feeling, and the style. Two questions for the whole unit. +- **Decide** the layout yourself, and write down *why* you chose it. +- **Let them judge layout on the first built screen**, where judging is actually possible. + +Generation tools that never ask anything get the feedback half right — people react to a rendered +thing instantly — and the decision half wrong, because nobody ever decides the layout and it +lands on the default. Keep the decision; move the judging to where it works. + +Interaction sits in between: ask only where the options change what the user can *do*, never how +it looks. + +**Where you do ask, ask with selectable options, not a blank prompt, whenever the environment +supports it.** The feeling question, an interaction fork, and Phase 4's three composed style +directions all have a small, nameable answer set — that is exactly what a structured choice tool +(e.g. Claude Code's `AskUserQuestion`) is for, and it reads easier than free text for a user with +no design vocabulary. Always include a recommended option and an "other: describe it" escape +hatch; never let this apply to the layout pick itself, which stays a decision you make and record, +never a menu you offer (see Never, below). + +## What you are given + +| The user brings | Unit | You direct | +|---|---|---| +| A brief card (from [`domain-interview`](../domain-interview/SKILL.md)) | App | Every screen the brief names | +| "Make this card look awesome" | Card | Its five formats as one family, same language at different fidelity | +| "This component is boring" | Component | Its states — rest, hover, active, loading, empty, error | +| "Design this field" | Field | Its edit, embedded and atom presentations | + +A unit inside one that already has a `DESIGN-DIRECTION.md` inherits that style and decides only what is new. +Skip phases the unit does not need; a field has no screen inventory but still gets a style. + +## Phase 0 — Brief + +From a `domain-interview` brief card (`Wiki/-brief.json`, the spec in its `content` field), +take the overview, the per-screen **content contracts** (purpose, primary action, must-contain in +priority order, key moment, empty state) and the flows. These are inputs; do not re-ask them. + +Briefs carry no feeling by design, so ask one question: **what feeling should this leave?** With +no brief at all, ask three things in one question: who uses it, what they do first, what feeling it +should leave. Then move on. + +Check for an existing `DESIGN-DIRECTION.md` or `design-personalities.md` in the unit's folder or its parent. + +## Phases 1–7 (load each when you reach it) + +Run the phases in order, reading each file when you reach it rather than from memory. Skip the +ones the unit does not need. + +| Phase | File | What it decides | +|---|---|---| +| 1 — Inventory | [`references/phase-1-inventory.md`](references/phase-1-inventory.md) | Apps only: every screen, every CardDef the user sees rendered, every image-bearing field | +| 2 — Layout | [`references/phase-2-layout.md`](references/phase-2-layout.md) | You decide, per screen: the direction and its reason, the dominant object, reading order, `prefersWideFormat` | +| 3 — Interaction | [`references/phase-3-interaction.md`](references/phase-3-interaction.md) | How each primary action presents, its hit area and resting cue, and the moment / empty state / completion beats | +| 4 — Style | [`references/phase-4-style.md`](references/phase-4-style.md) | The one question worth asking: three composed directions, the signature treatment, the ornament budget, the type line | +| 5 — Write | [`references/phase-5-write.md`](references/phase-5-write.md) | `DESIGN-DIRECTION.md` from the template, with per-screen and set acceptance lines | +| 6 — Hand off | [`references/phase-6-hand-off.md`](references/phase-6-hand-off.md) | The motion line, then the build order: first screen → the batch → theming and wiring | +| 7 — After the build | [`references/phase-7-after-build.md`](references/phase-7-after-build.md) | The gate: the user looks first, then one outcome question | + +Phase 4 may run before Phase 2 when the user wants the mood settled first. + +**When the user reacts to something already built** — "I don't like it", "this is too plain", +"this is exhausting" — read [`references/revisions.md`](references/revisions.md): triage an unnamed +complaint first, then the one-surface ornament correction. + +You do not build and you do not review. + +## Pair with + +Read these live on each run rather than working from memory. Links are relative to this skill's folder. + +`motion-authoring` and `design-review` are upcoming skills that ship in follow-up PRs; until they land, their links here and in the phase files do not resolve. + +| What | Where | For | +|---|---|---| +| The brief card this reads | [`domain-interview`](../domain-interview/SKILL.md) | Phase 0 | +| Design process, taste bar, brand/style source | [`boxel-design`](../boxel-design/SKILL.md) | Phase 4 | +| The four-stage process itself, and the Stage 0f content matrix | [`design-playbook.md`](../boxel/references/design-playbook.md) | Phase 1, Phase 4 framing, Phase 6 hand-off | +| Anti-cliché checklist | [`critical-rules.md`](../boxel-design/references/critical-rules.md) | Phase 4 anti-patterns | +| The `fitted` size ladder, `FittedCard`, `--fc-*` | [`container-query-fitted-layout.md`](../boxel/references/container-query-fitted-layout.md) | Any enrichment of a fitted view — authority, read live | +| The theme card, once the build extracts it | [`boxel-theme-development`](../boxel-theme-development/SKILL.md) | Phase 6 hand-off | +| Turning a Narrative arc, scrubbed subject or direct-manipulation way into build numbers | [`motion-authoring`](../motion-authoring/SKILL.md) — opt-in, only when the arc exists | after Phase 5 | +| Scoring the result | [`design-review`](../design-review/SKILL.md) | Phase 7 | + +## Never + +- Ask someone to choose a layout from wireframes. +- Guess which dimension an unnamed complaint meant. Triage it. +- Keep a style library of your own. Compose per unit; researched styles go in the unit's + `design-personalities.md`. +- Pick an all-equal-weight grid. +- Leave a unit's imagery out of the Views table, or reduce it to a thumbnail without saying why. +- Recommend fonts, colours or motion before the layout is settled. +- Write a theme card, or reference `var(--*)`. The theme is extracted from the build, later. +- Write easing curves, scroll offsets or per-beat durations into the Narrative arc. Name the beats + and the budget; `motion-authoring` writes the numbers, and only for units that asked for an arc. +- Answer "this is too plain" with a blurred gradient and a frosted panel, or by adding a second + arresting thing. +- Build, or score your own work. + +## Files + +Phases: + +- [`references/phase-1-inventory.md`](references/phase-1-inventory.md) · [`phase-2-layout.md`](references/phase-2-layout.md) · [`phase-3-interaction.md`](references/phase-3-interaction.md) · [`phase-4-style.md`](references/phase-4-style.md) · [`phase-5-write.md`](references/phase-5-write.md) · [`phase-6-hand-off.md`](references/phase-6-hand-off.md) · [`phase-7-after-build.md`](references/phase-7-after-build.md) +- [`references/revisions.md`](references/revisions.md) — triage for an unnamed complaint, and the one-surface ornament correction + +Vocabulary the phases read: + +- [`references/screen-types.md`](references/screen-types.md) — layout directions per screen type +- [`references/layout-gravity.md`](references/layout-gravity.md) — defaults to resist +- [`references/interaction-ways.md`](references/interaction-ways.md) — how an action can present, and the narrative ways a composition can unfold — with what each is achievable with +- [`references/enrichment-moves.md`](references/enrichment-moves.md) — the ornament budget: the baseline read off the style in Phase 4, the ladder every surface is assigned a rung on, the moves each style can earn, the correction run in both directions, and why `fitted` is upstream's call +- [`references/signature-treatments.md`](references/signature-treatments.md) — the arresting element: what is buildable in a card with pure CSS, what each costs, and the four runtime constraints +- [`references/design-direction-md-template.md`](references/design-direction-md-template.md) — the output, and the contract [`design-review`](../design-review/SKILL.md) checks diff --git a/skills/design-direction/references/design-direction-md-template.md b/skills/design-direction/references/design-direction-md-template.md new file mode 100644 index 00000000..1ac76011 --- /dev/null +++ b/skills/design-direction/references/design-direction-md-template.md @@ -0,0 +1,168 @@ +# DESIGN-DIRECTION.md — {unit name} + +Written by `design-direction` on {date}. Builder: follow this before styling — and hardcode +everything; the theme is extracted later. Reviewer: tick the acceptance lines first. + +## Brief +- **Unit**: app | card | component | field +- **Who uses it**: {one line} +- **First action**: {one line} +- **Feeling it should leave**: "{quote}" +- **Inherits from**: {parent DESIGN-DIRECTION.md, or none} + +## Style: {name} +- **Inspirations**: {composed for this unit in Phase 4, or researched from the style the user named} +- **Visual DNA**: {the cues that make it recognisable} +- **Style controls**: motion character {…} · type system {…} · colour strategy {…} · spatial model {flat | layered | perspective | scene} · visual density {…} +- **Reference**: {one real site, poster or screenshot — URL or file} +- **Type**: display {font} · body {font} · weight contrast {e.g. 700 vs 400} · hero size jump {e.g. 56px vs 16px} +- **Palette**: {colours by role, as hex — these are written into the templates directly} +- **Style source**: boxel brand guide | derived from the domain | BrandGuide card {which} +- **Signature material**: {the one thing worth screenshotting} — L3, and the only L3 +- **Ornament baseline**: {restrained | balanced | rich} — read off the visual DNA line: "{which line}" + - *If rich*: troughs {which surfaces sit one rung below, and why} · how the signature still wins {…} · animated grounds allowed per screen {n} +- **Not**: {what this style refuses} + +**Narrative arc** (optional — one per unit, `isolated` of a routed or `prefersWideFormat` page only) +- **Still frame**: {what the page is with no motion at all — this must stand alone} +- **Beats, in order**: {1. what arrives / moves first} → {2.} → {3.} +- **Total budget**: {time until the fold has settled} · {scroll consumed by the arc} +- **Ways and achievability**: {way — CSS | library → capability {name}, sourced by the build | not available} +- **Serves**: {the signature treatment it reveals — it is never a second one} +- **Reference for the motion**: {URL, or none — kept so "moves like it" can be checked} +- **Numbers**: `MOTION.md`, written by [`motion-authoring`](../../motion-authoring/SKILL.md) — this block never carries eases, offsets or per-beat durations + +**Ornament budget** — the rung every surface is built at, decided here, not after a complaint + +| Surface | Rung | Moves | Contrast / motion answer | +|---|---|---|---| +| {screen region, or card format} | {L0 – L3} | {—, or: icon, eyebrow, ground …} | {scrim / clear zone / reduced-motion resting state} | +| {… one row per surface in the inventory} | | | | + +**Anti-patterns** — the defaults a model slides into; the reviewer fails any of these on sight +- [ ] no Inter, Roboto or system-ui as the display font +- [ ] no purple-on-white, no blue-to-purple gradient +- [ ] no card inside a card, no bordered box inside a bordered box +- [ ] no row of equal-width boxes +- [ ] no emoji as icons — icons come from `@cardstack/boxel-icons` +- [ ] no centred hero with one generic CTA beneath it, unless the layout chose exactly that +- [ ] {any this style specifically refuses} + +## Screens + +### {Screen name} — {screen type} +**Primary action**: {what the user does here first} +**Blocks consumed**: {cards, fields, components and their formats} + +**Layout: {direction} — {name}** +- **Why**: {one line — which must-contain block leads, and why this direction serves it} +``` +{ASCII with ratios} +``` +- Dominant object: {what} · {share of viewport} +- Reading order: {single column | F | Z | spotlight} +- Primary action: {where} +- Above the fold at 1200×700: {list} +- At 400 wide: {how it stacks} +- Gravity wells kept on purpose: {none | which, and why} +- Layout controls: stage type {…} · information architecture {…} · content priority {…} · responsive {…} +- Width: `prefersWideFormat` {true | false} — {one line: would `isolated` look broken or cramped at ~720px, and why} + +**Interaction** + +| Action | Way | Trigger → response | Budget | Reduced motion | Ready component | +|---|---|---|---|---|---| +| {action} | {name} | {trigger → response} | {ms, easing} | {fallback} | {name or —} | + +**Enrichment** (corrections — filled in when a built surface is called too plain, or too busy) +| Surface | Rung | Moves | From which line of the visual DNA | Contrast / motion answer | +|---|---|---|---|---| +| {block or format} | {L0 → L1, or L2 → L1} | {icon, eyebrow, ground … — or what was removed} | {…} | {scrim / clear zone / reduced-motion resting state} | + +**Beats** +- Moment: {the action that matters most, and its staged way} +- Empty state: {the invitation, and the action beside it} +- Completion: {what is seen when the job here is done} + +**Acceptance lines** — tickable without taste +- [ ] {e.g. members grouped by tier; the top tier's tile is double width} +- [ ] {e.g. opening a member keeps the grid visible} +- [ ] {e.g. no region row has three or more equal-width boxes} +- [ ] {e.g. the metrics strip is grounded (L2) and still quieter than the hero} + +{repeat per screen} + +## Set acceptance lines (apps) +Checked across every screen at once by `design-review set`. These are what catch five screens that +are each fine alone. +- [ ] {e.g. one display face throughout; the body face never sets a headline} +- [ ] {e.g. the accent appears only in the primary-action role, on every screen} +- [ ] {e.g. every eyebrow is uppercase at the same tracking} +- [ ] {e.g. opening an item is the same way on every screen} +- [ ] {e.g. every empty state names its first action} +- [ ] {e.g. exactly one surface in the whole unit is L3; no other surface out-shouts it} +- [ ] {e.g. rich baseline: every panel is grounded, and the two text-heavy surfaces named as troughs are not} + +## Views (cards) +**Width**: `prefersWideFormat` {true | false} — {one line of reason}. Upstream's +[`prefers-wide-format.md`](../../boxel/references/prefers-wide-format.md) owns the rule. + +| View | Carries | Dominant | Media | Drops | +|---|---|---|---|---| +| isolated | the full narrative | | {at scale — or *none, and why*} | | +| fitted | the headline of the story | | {focused crop, or the mark fallback — never a shrunken hero} | | +| embedded | identity | | {square or fixed-ratio crop, or none} | | +| atom | presence | | {none — the style survives as wording} | | +| edit | the same language in form terms | | {the field's own edit control} | | + +**Media is a required answer, not an optional one**, for any unit with an image-bearing field. +Upstream's rule for the smallest formats: *"Hide the hero media in fitted"* is an anti-pattern, and +a stock photo at fitted size reads as lorem ipsum — use a focused crop of the hero or a brand-mark +fallback. A row left blank on a unit that has imagery is a decision nobody made. + +`fitted` enrichment happens **inside** upstream's ladder: which field leads at each size, which field +sits in which region, the single mark where the head is all there is, whether there is a hero, and +ornament within a region — tuned via `--fc-*` / `@container fitted-card`. The breakpoints, the regions +each size shows and `FittedCard` itself are upstream's +([`container-query-fitted-layout.md`](../../boxel/references/container-query-fitted-layout.md), read live at +build time); never restate its numbers here and never ask for a region a size does not have. + +## States (components) +| State | Presents | Way | +|---|---|---| +| rest / hover / active / loading / empty / error | | | + +## Presentations (fields) +| Presentation | In a form | In a card | +|---|---|---| +| edit / embedded / atom | | | + +## Build order +Read at every step below. Everything before theming is **hardcoded** — real hex, named fonts, real +sizes, real sample content, no `var(--*)`, no queries, no commands — on **real CardDefs with real +links** from the first screen. Defer style and data volume, never structure. + +> **Diverges from the design-playbook.** The playbook's Stage 4 derives `fitted` and `embedded` +> from the tokenized `isolated`. Here every linked CardDef's five formats are built in the batch, +> before theming, because a format derived after the user locked the language is a format the user +> never judged. Utility cards with no design unit follow the playbook's order instead. + +- **Plan**: the [`catalog-reuse`](../../catalog-reuse/SKILL.md) search, then playbook Stage 0 — + including the Stage 0f content matrix for every CardDef the screens link to. +- **First screen**: {which — the screen with the most of the domain's data on one surface, holding + the primary action; tie-break to the one rendering the most linked CardDefs. Usually Home / desk + for an app, the richest record when Home is a thin list} — {one line of reason}. Playbook Stage 1 + as a real `.gts`, every primary action a real control. Push, stop. *Gate:* the user has looked and + the language is locked. Layout may be overruled here; rebuild this screen only. Then + `design-review` on this screen. +- **The batch**: {remaining screens, in order} **and all five formats of every linked CardDef — + `isolated`, `embedded`, `fitted`, `atom`, `edit`** — built against the content matrix, static, + same language, one CardDef at a time, reported as counts (CardDefs × formats built of planned). + Push. Then `design-review set`, then `design-review card` per linked CardDef. *Gate:* the set + lines and each screen's and card's own. +- **Theming and wiring**: playbook Stages 2–3 — extract the theme from the hardcoded values → + tokenize pixel-identical → live queries → the full sample-data set → every action wired to the + mechanism its content contract declared. *Gate:* clean against upstream's + [`theme-token-contract.md`](../../boxel-ui-guidelines/references/theme-token-contract.md) — + checked here, never earlier. +- **Finish**: lint and push, per upstream [`boxel`](../../boxel/SKILL.md)'s lint workflow. diff --git a/skills/design-direction/references/enrichment-moves.md b/skills/design-direction/references/enrichment-moves.md new file mode 100644 index 00000000..d4cb074f --- /dev/null +++ b/skills/design-direction/references/enrichment-moves.md @@ -0,0 +1,203 @@ +# Enrichment moves — what to add when a surface is too plain + +This file runs **twice**, and the first run is the one that matters: + +1. **Forward, in Phase 4** — the style declares an ornament *baseline*, and every surface in the + unit is assigned a rung before anything is built. This is what stops a first generation coming + out as headings and paragraphs when the style was never plain to begin with. +2. **As a correction, on demand** — someone looks at something built and says *this block is too + plain* or *this screen is exhausting*, and one surface moves one rung, up or down. + +Both runs answer the same question: **given the style this unit already has, what does this +surface earn?** + +It does not pick a style, does not touch the layout, and does not produce a second arresting +element. A style change is Phase 4; a layout change is judged on the built screen; the one +arresting thing is [`signature-treatments.md`](signature-treatments.md). This file is the vocabulary for everything that is +*not* the signature — the surfaces that should not be bare and must not out-shout it. + +## Why a catalogue instead of "make it richer" + +Told to enrich a surface with no vocabulary, a model reaches for a blurred gradient and a frosted +panel every single time — the default look that upstream's [`critical-rules.md`](../../boxel-design/references/critical-rules.md) names as *Gradient +Overuse*. The catalogue exists so the answer is *this style's ornament*, not *the generation +default*. A field guide grounds a surface with stipple and a hairline; a brutalist one with a +coarse grid and a hard shadow; a ledger with a rule and tabular numerals. None of those is a +gradient. + +## The baseline — derived from the style, not asked + +A style is not neutral about ornament. Read the baseline off the style's visual DNA in Phase 4; +it is a conclusion, not a question for the user. + +| Baseline | The style sounds like | Default rung for an ordinary surface | +|---|---|---| +| **Restrained** | editorial, Swiss, field guide, legal, clinical, terminal | **L0–L1** — type and rules carry everything; a mark where a row needs identifying | +| **Balanced** | most product and brand work | **L1** — every surface gets its mark; grounds are spent on the few surfaces that lead | +| **Rich** | gaming HUD, sports broadcast, arcade, trading floor, festival, kids' product, luxury retail | **L2** — surfaces are *grounded by default*; a bare panel reads as unfinished, not as restraint | + +"Rich dashboard", "gaming UI", "make it feel like a game HUD" is a **Rich** baseline, and the +consequence is concrete: panels arrive with grounds, frames, state colour and iconography in the +first build, because in that language an un-grounded panel is a missing asset. Nobody should have +to ask for that afterwards. + +**The baseline sets the default rung, never the ceiling.** All three baselines keep the same cap: +one L3 per unit, on the dominant object. + +### Rich costs more, in three specific ways + +A high baseline is not a licence; it buys three new failure modes that a restrained one does not +have, and Phase 4 must answer them when it picks Rich: + +- **Contrast needs troughs.** If every surface is grounded, nothing reads as important. A Rich + unit must name the surfaces that sit **one rung below baseline** on purpose — the rests — and + they are usually the ones carrying the most text. Loud everywhere is the same information as + quiet everywhere. +- **The signature still has to win.** Raising the floor raises what L3 must clear. Either the + signature gets louder or the surfaces around it get quieter; doing neither is how a rich style + ends up with no focal point. +- **Grounds cost frames.** Blurred, animated or filtered grounds composite every frame and the + card can be on screen several times at once. Cap them per screen, prefer a static pattern or a + gradient over an animated blur, and keep the `prefers-reduced-motion` resting state — same + constraints as [`signature-treatments.md`](signature-treatments.md). + +## The ladder — every surface sits on one rung + +| Rung | What it has | Where it belongs | +|---|---|---| +| **L0 Bare** | type and spacing only | dense table rows, `atom`, the smallest fitted sizes, anything whose job is scanning | +| **L1 Marked** | exactly **one** mark: an icon, an eyebrow, a hairline, an oversized numeral, a colour band | most `embedded` views, list rows, meta strips | +| **L2 Grounded** | the surface gets a *ground* — pattern, texture, tint or a scrimmed gradient — plus its L1 mark | cards, panels, the secondary regions of `isolated` | +| **L3 Signature** | reserved. The unit's one arresting thing | only Phase 2's dominant object, once per unit | + +Three rules hold the budget: + +- **One L3 per unit**, whatever the baseline. A correction run raises a surface **by one rung at a + time** and never reaches L3 on its own. Promoting something to L3 means demoting the current L3 first, and that is a Phase 4 + decision the user makes, not a side effect of "this looks plain". +- **No L2 may out-shout the signature.** If the enriched surface now competes, the move was too + loud — take the quieter version of the same idea, not a different idea. +- **Bare is sometimes correct, and at a Rich baseline it is mandatory somewhere.** A dense + comparison table, an `atom`, a fitted tile at its smallest size: these are L0 by design. "Plain" + is a failure only where the surface had room and a job that reading alone does not serve — and + under Rich, a deliberately quiet surface is what makes the loud ones legible. + +## The moves + +Pick from the style, not from the top of this list. Every move must be derivable from the style's +visual DNA — if you cannot say which line of the style's DNA produced it, it is decoration from +somewhere else. + +| Move | What it is | Cost / where it is illegal | +|---|---|---| +| **Icon** | one glyph carrying the row's kind or state | from `@cardstack/boxel-icons` — **never emoji** (the anti-patterns block fails it on sight). One per row; an icon per field is noise | +| **Eyebrow** | a kicker above the title — uppercase, tracked, small | the whole set gets **one** eyebrow treatment; two is drift `design-review set` will catch | +| **Hairline / rule** | a 1px rule separating or underlining | at compact and dense spacing this is structure, not ornament — it does not count as the L1 mark | +| **Oversized numeral** | the figure that matters, typeset huge | needs tabular numerals; it and the title cannot both be the loudest thing — pick one | +| **Colour band / edge stripe** | a band carrying status, tier or category | the cheapest mark that survives at the smallest sizes; the colour must already mean something in the palette | +| **Background pattern** | repeating-linear, dot grid, conic, hatch — pure CSS | text sitting on it needs a scrim or a clear zone; an un-scrimmed pattern under body copy is a contrast failure | +| **Texture / grain** | an inline SVG `feTurbulence` data URI overlay | keep the data URI small — it inflates the template; pair with `pointer-events: none` | +| **Scrimmed gradient** | a gradient as **ground**, with a solid or semi-opaque scrim under any text | a gradient is never the signature by itself. `background-clip: text` needs a fallback `color` for when the animation is off and for high-contrast mode | +| **Tinted / duotone media** | the image treated so it belongs to the palette | reverses the *stock photo tile* gravity well; a raw photo in a styled surface reads as a placeholder | +| **Corner mark / stamp** | a mark in one corner — a seal, a monogram, a number | stays **inside** the surface; the outermost element's corners belong to the host | +| **Weight jump** | one step of real contrast between label and value | free, and the first thing to try before anything above it | + +**The contrast clause.** Any move that puts something behind text — pattern, texture, gradient, +tinted media — owes a legibility answer in the same breath: a scrim, a clear zone, or text placed +off the ground entirely. "It looked fine in the screenshot" is not one; the ground moves with the +data. + +**The motion clause.** If the move animates, `prefers-reduced-motion` gets a resting state that is +the finished state — same rule as every treatment in [`signature-treatments.md`](signature-treatments.md), and its four +runtime constraints (no document-level queries, container units not `vw`/`vh`, reduced motion, the +host owns the outermost element) apply here unchanged. Do not restate them; read them there. + +## Per format + +| Format | Ceiling | Notes | +|---|---|---| +| `isolated` | L3 (if this is where the signature lives), else L2 | the secondary regions are the usual "too plain" complaint — ground them at L2, leave the hero as the loudest | +| `embedded` | L2 | carries one **compressed** version of the unit's idea — the same texture, the same rule, smaller. Not a shrunken hero | +| `edit` | L1 | form surfaces earn a mark and nothing more; ornament in a form reads as a control | +| `atom` | L0 | text. The style survives as wording | +| `fitted` | **deferred — see below** | | + +### `fitted` defers to upstream, always + +`fitted` is the one format where this file does not decide. The host wraps every fitted template in +its own size container and upstream's [`container-query-fitted-layout.md`](../../boxel/references/container-query-fitted-layout.md) owns the size ladder, which +regions survive at each size, and the `FittedCard` component that implements it. That document is +the authority and it moves; **read it at build time rather than working from a copy** — numbers +restated here go stale and a stale copy is worse than a pointer. + +So when someone says a fitted view is plain, enrichment works **inside** the ladder, never on it. +In bounds — all of it *placement and ornament within the regions a size already has*: + +- **which field leads** at the sizes that have room for one — the signature field, not the + title-and-truncated-description default; +- **which field goes in which region** — what is head, what is meta, what is body, and what is + dropped early because it never earned its place; +- **the single mark** for the sizes where the head is all there is — a numeral, a stamp, a colour + band, chosen from the table above; +- **whether the tile has a hero at all**, or the no-image composition applies; +- **ornament inside a region** — an icon, a colour band, a scrimmed ground — subject to the same + contrast clause as everywhere else, since a fitted tile is the surface most likely to be read at + a glance; +- **tuning** the composition through the sanctioned knobs: `FittedCard`'s `--fc-*` custom + properties and `@container fitted-card (…)` overrides in the card's own scoped CSS. Upstream's + own words are *tune, don't fork*. + +Out of bounds — the ladder itself: + +- **do not move, restate or invent a breakpoint.** The sizes and what they show are upstream's; + a `DESIGN-DIRECTION.md` that repeats their numbers goes stale and then quietly contradicts them. +- **do not ask for a region a size does not have.** "Show the badge on the smallest tile" is a + request to break the contract, not an enrichment. +- **do not fix spacing or type as a style decision in `DESIGN-DIRECTION.md`.** Where the composition needs + tightening, that is a `--fc-*` tune or a container-query override made at build time against the + live document — not a number written down here. +- **never `vw` / `vh`**, and nothing on the template root that the host owns. + +**Where this file and upstream disagree about fitted, upstream wins.** + +## How a forward run goes (Phase 4) + +1. **Read the baseline off the style** — restrained, balanced or rich, with the line of visual DNA + that decided it. +2. **Assign a rung to every surface** in the inventory — screens' regions, and each format of each + card. Default is the baseline; the dominant object is L3; name the **rests** that sit one rung + below. +3. **Name the moves for the surfaces above L0**, each traced to the style, each with its contrast + and motion answer. +4. **Write the budget map into `DESIGN-DIRECTION.md`** so the builder builds it that way the first time and + `design-review` has a standard to score against. + +At a Rich baseline, also answer the three costs above — which surfaces are the troughs, how the +signature still wins, and how many animated grounds a screen is allowed. + +## How a correction run goes (on demand) + +1. **Name the surface** — which block, which screen, which format. One at a time; "the whole app is + plain" is a Phase 4 style question, not this. +2. **Read the unit's `DESIGN-DIRECTION.md`** — the style, its visual DNA, its anti-patterns, and where the L3 + signature currently lives. +3. **Say the current rung and the target** — one step, in either direction. *Too plain* is + L0 → L1 or L1 → L2; *too busy / exhausting / nothing stands out* is the same move downward, and + at a Rich baseline that is the more common complaint. Lowering a surface is a legitimate + enrichment outcome: the fix for "nothing stands out" is almost never adding more. +4. **Pick moves the style earns**, with the line of visual DNA each came from, plus the contrast and + motion answers each one owes. +5. **Write it back into `DESIGN-DIRECTION.md`** — an `Enrichment` line on that surface, and an acceptance line + a reviewer can tick. A move that is not written down is rebuilt away the next time that surface + is touched, and `design-review` has no standard to score it against. +6. **Rebuild that one surface.** Nothing else changes. + +## Never + +- Raise something to L3 because it looked plain. +- Ask the user to pick an ornament baseline. It is read off the style they already chose. +- Ground every surface because the style is rich. A unit with no troughs has no hierarchy. +- Enrich more than one surface per run without saying which one is now the loudest. +- Add a move the style cannot account for. +- Reach for a blurred gradient plus a frosted panel. That is the default look, not an answer. +- Decide fitted structure here. Upstream owns it. diff --git a/skills/design-direction/references/interaction-ways.md b/skills/design-direction/references/interaction-ways.md new file mode 100644 index 00000000..5e17e1fc --- /dev/null +++ b/skills/design-direction/references/interaction-ways.md @@ -0,0 +1,234 @@ +# Interaction ways + +**Motion lives in three places; this file is the first.** Read the row you are in before the rest. + +| Layer | Question | Written in | Applies to | +|---|---|---|---| +| **This file** → `DESIGN-DIRECTION.md` Interaction table and Narrative arc | *which* way, its budget, its fallback, whether it is CSS or a library capability | one line per action, three beats per arc | every unit | +| [`motion-authoring`](../../motion-authoring/SKILL.md) → `MOTION.md` | *exactly how* — offsets, eases, the geometry the effect depends on, the library call | one row per beat | only units with an arc, a scrubbed subject, a direct-manipulation way or a library capability | +| [`design-review`](../../design-review/SKILL.md) | *did it land* — captures at the arc's three points, motion-off pass | a score | after the build | + +So "scroll parallax" is **named** here (with its cap), **specified** in `MOTION.md` (which layers, +what rate, over which scroll range) and **checked** by review. If you are writing a `cubic-bezier` +or a scroll offset in this file or in `DESIGN-DIRECTION.md`, you are one layer too deep. + +How an action can present. Pick by budget, not by flavour. This is the Boxel-specific catalogue; +for anything it does not cover, reason from the principles behind it — feedback, affordance, +cognitive load, accessibility — and record why the pick suits the action. + +**Budgets.** Repeated actions (done many times a day) stay under 200 ms with no attention-grabbing +motion — the cost is paid on every repeat, and what delights once irritates the hundredth time. +Moments (rare, and mattering) may take 400–900 ms and one orchestrated sequence. One moment per screen. + +Each pick records: trigger · response · duration and easing · reduced-motion fallback · repeat cost. + +**Triggers**: click · double-click · hover · long-press · drag · drop · keyboard · type/paste · +scroll into view · focus · submit · **pointer move · continuous scroll · pinch · swipe · range +input** — the last five drive the direct-manipulation ways below, which the discrete table cannot +describe. + +**Responses** +- **navigate** — replaces the screen. Cheapest, loses context. +- **side panel** — slides from the edge, the list stays. Good for compare. +- **inline expand** — the item grows in place, siblings reflow. +- **popover** — small, anchored, dismisses outside. For peeks and quick edits. +- **sheet** — the narrow-screen form of a panel. +- **in-place transform** — the element becomes the result. +- **staged moment** — leave, arrive, settle. +- **ambient change** — tint, ring or background shifts, no modal. + +## By action kind + +**Open an item from a collection** + +| Way | Trigger → response | Budget | Reduced motion | Repeat cost | +|---|---|---|---|---| +| Navigate | click → isolated view | instant | same | low | +| Side panel | click → panel in 280 ms, list stays, dims 10% | 280 ms ease-out | appears, no slide | low | +| Expand in place | click → tile grows to 2×, siblings reflow | 240 ms spring | swaps to detail | medium | + +**Commit a transaction** + +| Way | Trigger → response | Budget | Reduced motion | Repeat cost | +|---|---|---|---|---| +| Row appears | submit → new row rises into the list | 300 ms | appears | low | +| Print | submit → receipt slides from the tile edge | 600 ms ease-out | appears | medium | +| Exchange | submit → figure counts down, item lands, balance settles | 900 ms staged | updates in place | high — moments only | + +**Validate or check** + +| Way | Trigger → response | Budget | Repeat cost | +|---|---|---|---| +| Verdict line | submit → text under the field | instant | low | +| Flip | submit → card flips to its result face | 400 ms | low | +| Stamp | submit → stamp lands with one frame of overshoot | 350 ms | low | + +**Cross a threshold** (promote, complete, level up) + +| Way | Response | Budget | +|---|---|---| +| Swap | the badge text changes | instant | +| Ring completes | ring fills, badge takes the new colour | 800 ms | +| Moment | screen tints, badge grows then settles, a row appears | 1200 ms staged | + +**Filter or switch view** + +| Way | Response | Budget | +|---|---|---| +| Instant | list re-renders | instant | +| Reflow | items slide to new positions | 200 ms | +| Crossfade | old pane out 120 ms, new in 200 ms | 320 ms | + +**Peek without leaving** — tooltip (one line, instant) · popover card (embedded view, anchored, +160 ms) · preview rail (right rail shows the embedded view, 160 ms). + +**Empty state** — invitation (one sentence naming the first action, the action beside it) · ghost +(a faint outline of what will appear, action inside it) · style piece (a motif in the chosen style, +action below). + +**Error and recovery** — inline (message beside the field, focus kept) · shake (2× in 240 ms, +message appears; reduced: message only) · dignified (the block tints, the message says what to do +next, nothing red until the second failure). + +## Direct manipulation — continuous input, continuous state + +Everything above is **discrete**: an event fires, a response plays for a fixed duration, it ends. +Some interactions are not that shape. The user moves something and the thing moves with them; there +is no duration to budget because the user owns the timing. + +| Way | Input → state | Where it earns its place | +|---|---|---| +| **Orbit** | drag → rotation angle | an object whose back matters as much as its front | +| **Sequence scrub** | continuous scroll, or a range input → step in an ordered series | assembly order, a timeline, before/after | +| **Explode** | range input → separation distance | showing how parts relate without losing the whole | +| **Camera parallax** | pointer move → small camera or layer offset | depth as ambience, never as the only cue | +| **Zoom to part** | double-click → focus a selected element | after selection, not instead of it | +| **Pinch / swipe** | the touch forms of zoom and scrub | required if any of the above ship on a phone | + +**Record a different set of facts.** Duration and easing do not apply. Record instead: + +- **input → mapped value**, with its **bounds** and whether it clamps or wraps +- **release behaviour** — settles where released, snaps to the nearest step, or springs back +- **the keyboard equivalent** — arrow keys on a focused element, or a real ``. + A rotation only a mouse can reach is not an interaction, it is a demo +- **the reduced-motion answer**, which is not "turn it off": every state the gesture can reach must + stay reachable without it. Usually that means the range input is the control and the gesture is + the shortcut + +**Four constraints from the card runtime:** + +1. **Pointer capture stays inside the card.** Use `setPointerCapture` on the element, and release + it on `pointerup` / `pointercancel`. The same card can be open in two stacks — a `document`-level + `pointermove` listener will drive both. +2. **`touch-action` is mandatory.** Without it the browser's own scroll or pinch wins, and on a card + that is *itself* inside a scroll container the result is that neither works. Set + `touch-action: none` on the manipulable element only, never on an ancestor. +3. **Continuous scroll competes with the card's own scroller.** An isolated card is + `height: 100%; overflow-y: auto` — scroll-as-input and scroll-to-read are the same gesture. + Either the manipulable region is `overscroll-behavior: contain` and shorter than the viewport, + or use a range input instead and leave scrolling alone. +4. **State goes on a field, not a component property**, when it is part of the record. A rotation + that is just viewing is component state; an exploded-view step the user is meant to share is a + field, and the build wires it as one. + +One direct-manipulation way per screen, at most. It is the most expensive kind of interaction to +build, to make accessible, and to test. + +## Narrative ways — motion that unfolds the composition, not motion that answers an action + +Everything above is **interaction motion**: the interface responding to something the user did. +A second class exists, and a landing page or a public page is usually asking for it: **narrative +motion**, where the composition itself unfolds over time or scroll. It answers no action, so none +of the budgets above apply to it. Its own rules: + +- **Two or three places per page, not one per element.** Motion is rhythm: it marks where the + reader is and what matters, and it stops doing that the moment everything performs. Count the + ways this table contributes to a screen; at four, remove one. A whole page animating on load + is the failure this rule exists for. + +- **One arc per unit, and only on `isolated`** of a routed or `prefersWideFormat` page. `fitted`, + `embedded` and `atom` never carry it — they are rendered inside someone else's composition. +- **It serves the signature treatment.** The arc reveals the one arresting thing; it does not + become a second one. If the motion would be the most memorable element, the signature is wrong. +- **The static composition stands alone.** Reduced-motion is not a fallback here; it is the + baseline the arc layers onto. Design the still frame first, then decide what moves. +- **Budget is total length**, not per-response: how long until the fold has finished arriving, and + how much scroll the arc consumes. Anything the user has to wait through twice is too long. +- **The card owns only its own scroll box.** An isolated card is `height: 100%; overflow-y: auto` + inside a host pane, so scroll-linked motion is measured against the card's scroller, never the + window — and the host may resize or stack it. A whole-viewport pinned sequence is not available. + +| Way | Reads as | Achievable with | +|---|---|---| +| **Staggered arrival** — regions enter in reading order on load | the page assembling itself | CSS: `@keyframes` + `animation-delay`, `animation-fill-mode: both` | +| **Scroll-triggered reveal** — each block animates in as it enters view | the page arriving as you read it | **no library needed** — either CSS `animation-timeline: view()`, or an `IntersectionObserver` in a Modifier. See the note below | +| **Scroll-linked progress** — an element's state follows scroll position | the reader drives the story | CSS scroll-driven animations (`animation-timeline: scroll()` / `view()`) where supported; otherwise a **library**. When what follows the scroll is the unit's one subject — a bottle turning, a device scaling — that is a signature decision: see *The scroll-scrubbed subject* in [`signature-treatments.md`](signature-treatments.md) | +| **Pinned sequence** — a region holds while content passes | a chapter | CSS `position: sticky` for the hold; the passing content is scroll-linked as above | +| **Text split reveal** — a headline arrives by word or character | emphasis | CSS on pre-split spans; splitting itself is a **library** or a build-time step | +| **Ambient canvas** — particles, grain, shader | atmosphere | a `` in a Modifier; a **library** for anything beyond drawing points | +| **Smooth / inertial scroll** — the scroller itself eased | polish | **not available** in a card — the host owns the pane's scroll physics | +| **Scroll parallax** — fore, mid and back layers travel at different rates | depth while reading | scroll-linked progress on two or three layers, `transform` only. **Amplitude is capped**: the fastest layer moves at most 1.25× the slowest, and the background never more than 8% of the stage height over the whole runway — past that it reads as the page sliding apart, and it is the commonest way a scroll page makes people ill | +| **Reading progress** — a thin line or figure that fills as the page is read | orientation on a long page | **CSS only**: `transform: scaleX(var(--progress))` on a 2px line, fed by the same `--progress` the scrub modifier already writes. Belongs on a long `isolated` read; never on a screen shorter than two stages | +| **Scroll snap** — the scroller aligns to a chapter or card after each scroll | chapters, a horizontal set | **CSS only**: `scroll-snap-type` on the card's own scroller, `scroll-snap-align` on each chapter. **Mutually exclusive with a runway / pinned sequence** — snap points and a sticky stage fight over the same scroll position. `proximity`, never `mandatory`, on any pane that also holds text taller than one stage | +| **Marquee** — a strip of logos, terms or a slogan scrolls continuously | ambience; a set too long to lay out | **CSS only**: the content duplicated once for a seamless loop, `@keyframes` on `translateX(-50%)`, linear, 20–40 s per lap. **Pauses under `prefers-reduced-motion`** (`animation-play-state: paused` with the first copy fully visible) and on hover; an unpausable loop is the one motion that fails the reduced-motion check outright. One per unit | + +**Not a narrative way: infinite scroll.** Loading more items when the reader reaches the bottom is +a data mechanism, not motion — it is a live query with a page size, owned by the build's wiring +step. Design its *arrival* here (the new rows use the same reveal way as the rest) and nothing +else. + +**Scroll-triggered reveal is the one that always works, and the one that fails silently.** It needs +only *a* scroller, and an isolated card has one — its root is `height: 100%; overflow-y: auto`, so +the blocks scroll inside the card and the card's own scroller is the timeline. Nothing here needs +the window, which is why this is achievable when the pinned and smooth-scroll ways are not. + +Two routes, and the choice is the builder's unless `DESIGN-DIRECTION.md` says otherwise: + +| Route | Cost | Watch | +|---|---|---| +| CSS `animation-timeline: view()` | zero JS | check support first — `CSS.supports('animation-timeline: view()')` — and note it is inherently scrubbed, so it reverses on scroll-back by design | +| `IntersectionObserver` in a Modifier | a few lines | **`root` must be the card's scrolling element, never `null`.** `null` means the viewport, and in a host stack pane that fires at the wrong time or never | + +**ready: a workspace pattern implements both this and scroll-linked progress with the `root`, +`unobserve`, reduced-motion and no-`vh` rules already applied** — the builder should start from it +rather than rewrite the observer, which is how the `root: null` bug keeps reappearing. Direction +still decides *whether* and *what*; the pattern only supplies the how. + +Four rules: + +- **Once, not on every pass.** Derived, not asked: `repeat cost` above says what delights once + irritates the hundredth time, and repeated unpredictable movement is the motion most likely to + affect someone with a vestibular condition. Blocks do not re-hide on scroll-back. State it in the + `DESIGN-DIRECTION.md` Interaction row in one line — *"reveal on enter, once only (repeat cost)"* — so the + decision is visible without being a question. +- **Overriding it is a written decision**, not a build-time improvisation, because it selects the + route: replay-on-re-entry means keeping the observer (or `view()`, which reverses anyway), while + once-only means `unobserve()` after the first hit. A chart that redraws its data is the + legitimate case; "it looked nice" is not. +- **The resting state is the final state.** `.block { opacity: 0 }` with the reveal adding the + visible state is the classic form of this way and the most common silent failure in a build: + if the observer never fires — reduced motion, a capture, an error, a card rendered where it never + scrolls — those blocks are invisible forever and nothing errors. Put the `from` in the keyframe + with `animation-fill-mode: both`. `design-review`'s motion-off pass exists to catch exactly this. +- **`isolated` only.** `fitted` and `embedded` render inside someone else's composition and are + usually too small to have a meaningful scroll; a reveal there just means content that is not there. + +Record each narrative way in `DESIGN-DIRECTION.md` under **Narrative arc** with its achievability column. A +way marked **library** is named here as a *capability* — "scroll-linked progress", "text split" — +and the library choice is the build's, per upstream's +[`external-libraries.md`](../../boxel/references/external-libraries.md), which owns how it is +loaded. This file never names a library, for the same reason Phase 4 never names a style menu. + +**The arc is a brief, not a build.** Three beats and a budget tell a builder *what* unfolds; the +start and end offsets, the easing, whether the subject lags the scroll, and the CSS geometry the +effect depends on are not decided here and must not be improvised while the first screen is being built. When a unit records a +Narrative arc, a scroll-scrubbed subject, a direct-manipulation way or any way marked *library*, +[`motion-authoring`](../../motion-authoring/SKILL.md) writes those numbers into a `MOTION.md` beside `DESIGN-DIRECTION.md` before those beats are built. +Units whose motion is all discrete feedback never get one — the Interaction table is their whole +motion spec. + +## Ready components + +When a catalog or realm component already implements a way, note it as "ready: " beside +that way so the builder knows. Never pick a way because a component exists — a ready component must +not force a dull direction. diff --git a/skills/design-direction/references/layout-gravity.md b/skills/design-direction/references/layout-gravity.md new file mode 100644 index 00000000..e71e5f10 --- /dev/null +++ b/skills/design-direction/references/layout-gravity.md @@ -0,0 +1,21 @@ +# Layout gravity wells + +Defaults a generator drifts into when nobody decided the layout. Check every pick against this +list and name any well it still contains. Keeping one deliberately is fine — write down why. + +| Well | Looks like | Why it fails | Resist with | +|---|---|---|---| +| Header as hero | Name left, badge right, then content | The most important object sits below the line of attention | Put the key figure or featured item in the top band; identity becomes the caption | +| Equal-card grid | Three or four identical boxes in a row | Says every fact matters equally, so none does | Weight by importance: 60/40, 2:1:1, one wide tile | +| Identical tiles | N same-size items regardless of rank or state | Hides the story the data tells — tier, urgency, rarity | Group with headings, vary span by rank, promote the first row | +| Form above content | Input panel first, results under it | The result is the point; the form is a tool | Make the input the screen for an operator, or move it to a rail | +| Dropdown as primary control | Full-width select that changes everything | Reads as a form field, not a decision | A chip, a segmented control, or a rail with the current choice visible | +| Table as default list | Every collection is a table | Tables suit evidence, not browsing or choosing | Shelf, rail, magazine or grouped rows, by the job | +| Settings panel for a ritual | A scheduled action as a labelled box with a button | No sense of when | Attach the action to a date on a strip or calendar | +| Sidebar by habit | Left nav because apps have one | Costs 20% of width for four destinations | Tabs in the top bar until there are more than seven | +| Centre everything | Every block and heading centred | No reading order | Left-align text; centre only the one spotlight object | +| Disabled-button wall | Many greyed buttons with the same reason | A wall of failure | One contextual prompt; hide the action until it can work | +| Empty state as apology | "No items yet." | Nothing to do next | Name the first action and what will appear | +| Same padding everywhere | One spacing value for hero, cards and rows | No rhythm, no grouping | Bigger gaps between groups than inside them | +| Uniform radius and shadow | Every box rounded and lifted alike | Everything floats, nothing is grounded | Radius and elevation by role — containers flat, actionable items lifted | +| Stock photo tile | Image, title, price, button, repeated | E-commerce default, forgettable | Let the style decide the media treatment: cut-out, tinted, framed, or none | diff --git a/skills/design-direction/references/phase-1-inventory.md b/skills/design-direction/references/phase-1-inventory.md new file mode 100644 index 00000000..8e7a98b3 --- /dev/null +++ b/skills/design-direction/references/phase-1-inventory.md @@ -0,0 +1,36 @@ +# Phase 1 — Inventory + +Part of [`design-direction`](../SKILL.md). Links are relative to this file. + +Apps only. One line per screen: **screen type** · **primary user action** · **blocks consumed**. +Cards go straight to their five formats, components to their states, fields to their three +presentations. + +Then list **every CardDef the user will see rendered** — one line each: what the card is, which +format it appears in, and — for any def with its own `isolated` view — its `prefersWideFormat` +call (Phase 2 decides it). A family of two or more related CardDefs also gets a **Home** CardDef, +`prefersWideFormat = true`, per upstream's +[`app-card-home-with-search`](../../boxel-patterns/patterns/app-card-home-with-search/README.md); +list it here, or write down why this unit has none. The link graph is how you find most of them, not the definition of the +set. Four sources, and only the first is a `linksTo`: + +- everything the screens reach through **`linksTo` / `linksToMany`** +- everything a **live query** returns — `getCards`, `@context.searchResultsComponent` — which + renders real cards while declaring no link +- everything **nested inside another card's template**, which the user sees without any screen + naming it +- anything the schema **grows later**: expect new CardDefs at the first screen and in the batch + when the design needs a field the brief does not have, and a def added then has the same five + formats as one named on day one + +A def missing from this list gets no content matrix, no row in the build's format list, and +therefore no built formats and nothing to review — the omission is silent at every stage after it. These are surfaces +the user sees, and an inventory that stops at screens is why they end up designed by nobody. They do not each get a layout pick; their per-format content is decided at planning against the +[design-playbook](../../boxel/references/design-playbook.md)'s Stage 0f content matrix, and +[`design-review`](../../design-review/SKILL.md) `card` scores them. + +**Name every image-bearing field as you inventory.** An `ImageSourceField`, a +`MultiImageSourceField`, a `Featured Image Field` — or any field the brief describes as a photo, +cover, hero, headshot, illustration or brand mark — goes on the unit's line. A card that carries +imagery has its dominant object already nominated, and the inventory is where that becomes visible; +discovered later, the layout has usually already been picked around a headline. diff --git a/skills/design-direction/references/phase-2-layout.md b/skills/design-direction/references/phase-2-layout.md new file mode 100644 index 00000000..ab52b94a --- /dev/null +++ b/skills/design-direction/references/phase-2-layout.md @@ -0,0 +1,39 @@ +# Phase 2 — Layout (you decide) + +Part of [`design-direction`](../SKILL.md). Links are relative to this file. + +For each screen, pick one direction from [`references/screen-types.md`](screen-types.md), adapted to the real blocks. +**Do not offer them.** That file is the brief you write from, not a menu. + +Pick by the content contract: whichever block the must-contain list puts first is the dominant +object, and the primary action goes where the reading order reaches it first. + +**An image field nominates the dominant object.** Where the unit carries imagery, the image is the +dominant object unless the content contract's priority order puts something above it — and +overriding it is a decision you write down, like any gravity well kept on purpose. Upstream is +blunt about the consequence: *"Media is featured. If the card has imagery (hero, cover, headshot), +it appears at scale. Don't hide it in a corner."* The failure is not ugly, which is why it survives +review: a card with one good photo gets a headline-led layout and a thumbnail, and nothing about it +looks wrong. Incidental imagery is the real exception — an avatar on an invoice row, a vendor mark +on a purchase order — and naming it as incidental is the write-down. + +Check the pick against [`references/layout-gravity.md`](layout-gravity.md) and name any well it still contains — a direction may keep +one deliberately if you say why. Never pick one whose regions are all equal weight; equal weight +tells the reader nothing matters more than anything else, which is never true. + +Record per screen, in `DESIGN-DIRECTION.md`: + +- the direction and **one line of reason** +- the dominant object and its share of the viewport +- reading order (single column, F, Z, spotlight) +- where the primary action sits +- what is above the fold at 1200×700, and how it stacks at 400 wide +- the four layout controls: stage type, information architecture, content priority, responsive +- **`prefersWideFormat`, true or false, with one line of reason** — decided here because it is a + layout decision: whether the `isolated` view would look broken or cramped at ~720px. The rule, + the true/false tables and the symptoms are upstream's [`prefers-wide-format.md`](../../boxel/references/prefers-wide-format.md); apply it, never restate it. Decide it + for every CardDef in the Phase 1 list that has its own `isolated` view, not only the screens — + found late, it costs a layout rewrite that a one-line static property would have prevented + +When the user overrules this on the built screen, rewrite that screen's layout block and rebuild +that one screen. The other picks stand. diff --git a/skills/design-direction/references/phase-3-interaction.md b/skills/design-direction/references/phase-3-interaction.md new file mode 100644 index 00000000..61805a05 --- /dev/null +++ b/skills/design-direction/references/phase-3-interaction.md @@ -0,0 +1,35 @@ +# Phase 3 — Interaction (derive; ask at a real fork) + +Part of [`design-direction`](../SKILL.md). Links are relative to this file. + +**This phase decides how an action *presents*, never whether it *exists*.** Whether a screen has a +"choose your day" action is the content contract's call, made in the brief; how that action looks, +moves and feels is this phase's. Keeping the two apart matters because a screen can pass everything +below — a named moment, an inviting empty state, a completion beat — while its primary action is a +`` that cannot be clicked. Nothing in this phase would catch that, and nothing here should +try: making every primary action a real control, and wiring it to the brief's declared mechanism, +is the build's job. Design the feedback; the affordance is the build's gate. + +Choose a way per primary action from [`references/interaction-ways.md`](interaction-ways.md). The budgets decide most of +it: a repeated action gets the cheap way, under 200 ms and without attention-grabbing motion; the +screen's one moment gets the staged way. Record trigger, response, duration budget, easing +character, reduced-motion fallback, and repeat cost. + +Ask only where the ways change what the user can do — does the list stay visible, does the item +keep its place, is this moment paid for on every repeat. Motion flavour is not a question. + +**Record the hit area and the resting cue with every way, not just the response.** A way that says +only "click → panel" leaves the builder to decide how big the target is and whether anything marks +it, and the default answer is "as big as the text" and "nothing" — which produces a screen whose +controls work and cannot be found. Per control: **hit area** (44×44 CSS px, or 32×32 inside a dense +row with 8px of separation — [`design-review`](../../design-review/SKILL.md) measures against these same numbers), **resting cue** (border, fill, underline, chevron — what says *press +me* before the pointer arrives), **hover state**, and the focus ring. An invisible overlay that +makes a whole row clickable is allowed only with a resting cue on the row and a hover state on it. +`design-review` checks these under Affordance legibility. + +Decide three beats per screen: the **moment** (the action that matters most), the **empty state** +(an invitation, not an apology), the **completion**. One orchestrated moment per screen. + +For anything the catalogue does not cover, reason from first principles — feedback, affordance, +cognitive load, accessibility — and write the reasoning into `DESIGN-DIRECTION.md` so the reviewer can see +what the pick was for. diff --git a/skills/design-direction/references/phase-4-style.md b/skills/design-direction/references/phase-4-style.md new file mode 100644 index 00000000..ed79d254 --- /dev/null +++ b/skills/design-direction/references/phase-4-style.md @@ -0,0 +1,91 @@ +# Phase 4 — Style (the one question worth asking) + +Part of [`design-direction`](../SKILL.md). Links are relative to this file. + +Style is what a person can answer in words, so this is where you ask. Fix the source first: + +- Boxel built-in, host-facing or Boxel-branded work → the built-in brand guide, + `@cardstack/base/Theme/boxel-brand-guide`. +- A user or custom realm → derive the style from that domain and its content. Do not default to + Boxel branding. +- Logos, marks or official brand material involved → a `BrandGuide` card is the source. Never + invent logo URLs. + +**When the user gives a link or a screenshot, inventory it before composing.** A reference carries +two different things, and the skill used to read only the first: + +| Column | What goes in it | What happens to it | +|---|---|---| +| **Livery** | palette, type, spacing, imagery treatment, density | borrowable — feeds the three fields below | +| **Technique** | how it moves and unfolds: scroll-linked progress, pinned sequences, text splitting, ambient canvas, smooth scroll, each marked **CSS-achievable / needs a library / not available in a card** per [`references/interaction-ways.md`](interaction-ways.md) → *Narrative ways* | shown to the user as a structured choice — which of these matter here? | + +Ask that one question with the technique column, because "like this site" usually means one or +two techniques, not the palette — and a technique marked *needs a library* is named as a +capability for the build to source (upstream's +[`external-libraries.md`](../../boxel/references/external-libraries.md)), not a design decision. The +still frame the reference would have without its motion is what the livery column describes; +design that first. Keep the reference URL in the Narrative arc block: [`motion-authoring`](../../motion-authoring/SKILL.md) reads the technique +column back to write the numbers, and "moves like the reference" is only checkable against the +reference. + +Then **compose three directions yourself** — there is no style menu to pick from, deliberately, +because a curated list caps the result at whatever the list's author imagined. Hold the +[design-playbook](../../boxel/references/design-playbook.md)'s Stage 1 framing while you compose: a brand-focused art director, judged by a +taste-maker you name in your thinking and never in the output. Give each direction the same three +fields: **name · inspirations · visual DNA**. Recommend one, and offer "other: name or describe it". + +When the user names a style instead, research it into the same three fields yourself — what it is +known for, who made it, what a person would recognise it by — and record the result in +`design-personalities.md` beside the unit's `DESIGN-DIRECTION.md`, so the next unit in this workspace can +reuse it. Livery and mood may be borrowed; wordmarks, logos and real brand assets stay out. + +That file is the workspace's research notes, not a menu: it stays beside the units it serves and is +never promoted into this skill, a style in it is re-researched when named again rather than picked +from the list, and only styles the user named go in — never the three directions you composed. + +**Imagery is briefed from the style, like everything else.** Where the unit carries images, say +what they are *of* and how they are treated — subject, crop, light, palette relationship, whether +people appear — because that brief is the prompt an image model is given, and the +[design-playbook](../../boxel/references/design-playbook.md) shows the recipe: brand DNA + the CardDef's role + this instance's data + the +composition slot. A treatment named here is what stops a generated image from arriving in a +different palette than the card around it. **Do not decide where the image comes from** — placeholder, +generated or the user's own is a sourcing decision the build makes, and an `ImageSourceField` lets +each instance change it without a schema change. Generated images are persisted into the realm, per +upstream's +[`integrate-openrouter-image-generation`](../../boxel-patterns/patterns/integrate-openrouter-image-generation/README.md) +and [`integrate-filedef-generated-image`](../../boxel-patterns/patterns/integrate-filedef-generated-image/README.md) +patterns. + +**Decide the signature treatment.** Every unit needs one thing that arrests — the dominant object +from Phase 2, given a treatment nobody arrives at by default. Name it here, with the style, because +the treatment has to be what *this* style earns: a field guide's signature is a stipple and a +hairline, a brutalist one's is a hard shadow and an oversized numeral. The blurred-gradient-plus- +frosted-glass family is the current default look and upstream's [`critical-rules.md`](../../boxel-design/references/critical-rules.md) names +*Gradient Overuse* directly — reaching for it unexamined is the Average Quality Trap, not a +signature. [`references/signature-treatments.md`](signature-treatments.md) has the vocabulary, what each technique costs in a +card, and the four runtime constraints that apply to all of them. + +Say what the signature becomes at each format: `isolated` has room for it, `embedded` carries one +compressed version of the same idea, `fitted` is usually reduced to a single mark, `atom` survives +as wording. A shrunken copy of the hero is not a treatment. + +Record with the pick: + +- the five style controls: motion character, type system, colour strategy, **spatial model**, visual density — [`references/signature-treatments.md`](signature-treatments.md) defines the spatial-model options (flat · layered · perspective · scene) and what each costs; "flat" is a choice, not the absence of one +- the **signature treatment** and what it reduces to at each format +- the **ornament budget** — [`references/enrichment-moves.md`](enrichment-moves.md), and it is decided **here, before + anything is built**, not after someone complains. Read the **baseline** off the style you just + composed — restrained · balanced · rich — then assign every surface a rung on the ladder: + L0 bare · L1 marked · L2 grounded · L3 signature. A style that is loud by nature sets a high + baseline and its panels arrive grounded in the *first* build; a restrained one does not. The cap + never moves: one L3 per unit, on the dominant object, and at a rich baseline you must also name + the **rests** — the surfaces deliberately one rung below — or nothing reads as important +- **one real reference** — a site, poster or screenshot. A style name alone reads differently to + every builder; the reference removes the guess. +- the **type line**: display font, body font, one weight contrast, the hero size jump. Without a + font decision the builder defaults to Inter. +- the **anti-patterns** block, including anything this style specifically refuses. Pull what + applies from upstream's [`critical-rules.md`](../../boxel-design/references/critical-rules.md). + +Phase 4 may run before Phase 2 when the user wants the mood settled first; the layout pick then +reads the style's density. diff --git a/skills/design-direction/references/phase-5-write.md b/skills/design-direction/references/phase-5-write.md new file mode 100644 index 00000000..31d3bdb2 --- /dev/null +++ b/skills/design-direction/references/phase-5-write.md @@ -0,0 +1,15 @@ +# Phase 5 — Write DESIGN-DIRECTION.md + +Part of [`design-direction`](../SKILL.md). Links are relative to this file. + +Use [`references/design-direction-md-template.md`](design-direction-md-template.md), saved beside the unit's code (for an app, at the kit root). It must +carry, per screen: the layout pick with its reason, the interaction way per action with budgets +and fallbacks, the three beats, the style with its controls and reference and type line, the +gravity wells kept on purpose, and **acceptance lines** a reviewer can tick without taste. + +For apps, also write **set acceptance lines** — what must hold across every screen *and every +linked card's embedded view*: one display face, the accent in the same role and nowhere else, one +eyebrow treatment, the same way for the same kind of action, one empty-state voice. Include at +least one line that only a card can fail, such as: every linked card's embedded view leads with the +field that identifies it, not with its title and a truncated description. `design-review set` ticks these across all screens at +once, and they are the only thing that catches five screens that are each fine alone. diff --git a/skills/design-direction/references/phase-6-hand-off.md b/skills/design-direction/references/phase-6-hand-off.md new file mode 100644 index 00000000..91b897ac --- /dev/null +++ b/skills/design-direction/references/phase-6-hand-off.md @@ -0,0 +1,47 @@ +# Phase 6 — Hand off + +Part of [`design-direction`](../SKILL.md). Links are relative to this file. + +Show a two-line summary per screen (layout pick and reason · style · the moment), then **one +line for motion**: `MOTION.md: needed — ` or `MOTION.md: not needed — all ways discrete`. This +is the only place the user sees whether [`motion-authoring`](../../motion-authoring/SKILL.md) will run before those beats are built; a hand-off +that skips the line skips the decision. You never write `MOTION.md` yourself. Then offer: + +**Build the first screen** (recommended) — hand `DESIGN-DIRECTION.md` to the build, which follows upstream +[`boxel`](../../boxel/SKILL.md): the [`catalog-reuse`](../../catalog-reuse/SKILL.md) search first, then +the [design-playbook](../../boxel/references/design-playbook.md)'s Stage 0 planning, then Stage 1 — +one core screen as a real `.gts` on **real CardDefs with the domain's real links**: image fields +are `ImageSourceField`, `prefersWideFormat` set on every CardDef to the value `DESIGN-DIRECTION.md` +recorded, every primary action a real control — styled with hardcoded values, standing on +one or two instances per def, pushed. Then the user looks. + +**Which screen goes first is a rule, not a preference: the one that puts the most of the domain's +data on a single surface and holds the primary action.** Tie-break to the screen that renders the +most linked CardDefs. For an app that is usually the Home / desk screen, because it exercises many +cards' `embedded` and `fitted` at once; when Home is only a list of thin tiles, the richest record +screen goes first and Home leads the batch. The reason is the gate: the user's one look locks the +language, and the data-richest screen locks the most of it — and it is where a thin schema shows +while fixing it still costs one file. Name the pick and its reason in `DESIGN-DIRECTION.md` → Build order. + +**Then the batch** — every remaining screen **and all five formats of every linked CardDef** +built static, same language, pushed — one CardDef at a time rather than in one turn, which is where +the tail gets dropped. Then `design-review set` and `design-review card` per linked CardDef. +Formats are built here, not derived later. + +**Then theming and wiring** — playbook Stages 2–3: theme extracted, everything tokenized +pixel-identical, live queries, the full data set, then every action wired to the mechanism its +content contract declared. Theming belongs here and nowhere earlier; what is hardcoded on purpose +before this is *style and data volume*, never structure. The theme card itself is +[`boxel-theme-development`](../../boxel-theme-development/SKILL.md)'s, and the token authority is +upstream's [`theme-token-contract.md`](../../boxel-ui-guidelines/references/theme-token-contract.md). + +Other options: **revise a pick** (Phase 3 or 4 for one choice — layout is revised by looking, not +here), or **stop here** with `DESIGN-DIRECTION.md` as the deliverable. + +**Ask this hand-off as a structured choice**, not a plain offer of prose. Present the options above +— build the first screen (recommended once `DESIGN-DIRECTION.md` is written), then the batch, then theming +and wiring, revise a pick, or stop here — as selectable options with room for the user to name +something else, the same choice-tool pattern used for Phase 0/3/4 questions. + +You do not build and you do not review. diff --git a/skills/design-direction/references/phase-7-after-build.md b/skills/design-direction/references/phase-7-after-build.md new file mode 100644 index 00000000..4eccf8f3 --- /dev/null +++ b/skills/design-direction/references/phase-7-after-build.md @@ -0,0 +1,28 @@ +# Phase 7 — After the build comes back + +Part of [`design-direction`](../SKILL.md). Links are relative to this file. + +The screen is pushed and rendering. This phase exists because nothing else holds the gate +between "built" and "scored" — without it the gate is remembered or not. + +**The user looks first.** That look is the only thing that can overrule a layout pick — Phase 2's +picks are revised by looking, not by argument — and it has to happen before anything is scored, or +a fix loop spends its rounds polishing a layout that was about to be rejected. + +**Ask the outcome, not permission to review.** One structured choice, each option carrying its own +scope: + +| Option | What happens | Scope | +|---|---|---| +| **Language is locked** *(recommended)* | hand to [`design-review`](../../design-review/SKILL.md) | this screen is scored | +| **Layout is wrong** | rewrite this screen's layout block in `DESIGN-DIRECTION.md`, rebuild this screen | one screen. Every other screen's pick stands | +| **Style is wrong** | Phase 4, one question | the `## Style` block — and after the batch this is a rebuild of every screen and every linked card's formats, so say that before re-running | +| *(other — the user names something else)* | | | + +**On "locked", hand straight to `design-review`. Do not ask a second time.** The outcome question +already decided it: the other two answers route above and leave nothing stable to score, so "locked" +has only one sensible next step and asking for it again is a question with one answer. + +Do not score it here — not the acceptance lines, not the aesthetic bar, not the constraints. +`design-review` owns the rubric, the capture rules and the fix-loop cap, and a second copy of any +of them in this file would drift from the first. diff --git a/skills/design-direction/references/revisions.md b/skills/design-direction/references/revisions.md new file mode 100644 index 00000000..8955c16f --- /dev/null +++ b/skills/design-direction/references/revisions.md @@ -0,0 +1,61 @@ +# Revisions — on demand + +Part of [`design-direction`](../SKILL.md). Links are relative to this file. + +Two correction paths, for when the user reacts to something already built. Neither is how a unit's design first arrives. + +## triage first, when the complaint names nothing + +"I don't like it", "this feels off", "can you make it better" name neither a surface nor a +dimension. Do not guess which one they meant: every correction path below has a different scope, +and guessing wrong either changes something the user was happy with or leaves the thing they +disliked untouched. + +Ask which it is, as a structured choice, and state each option's scope in the option itself so the +user can see what they are authorising: + +| They pick | Path | What changes | +|---|---|---| +| **Layout** — what sits where, what dominates, the reading order | Phase 2 overrule, judged on the built screen | that screen's layout block, that screen rebuilt. Every other screen's pick stands | +| **Style** — palette, type, mood, the whole thing feels generic | Phase 4, one question | the `## Style` block. Layout picks stand — but read the stage warning below | +| **Ornament** — this surface is too plain, or too busy | the correction run below | one surface, one rung | +| **Motion** — it feels static, or it is too much | Phase 3 for an action's way; the **Narrative arc** for a page that unfolds | those rows only | +| **Content** — the wrong things are on screen, or in the wrong order | not a design question: the content contract in `domain-interview`'s brief card | the brief, then re-run Phase 2 for the screens whose priority order changed | + +If the user's own words already name a surface or a dimension, skip the question and route. + +**A style change costs more after the batch.** At the first screen it is one question and one +screen rebuilt. After the batch, every screen and every linked CardDef's five formats are hardcoded +in the old style, so re-running Phase 4 means rebuilding all of them — and once the theme has been +extracted, it was extracted from those values too. Say that cost out loud and get a yes before Phase 4 re-runs; +switching to a one-surface ornament move is often what the user actually wanted. + +## "this is too plain" / "this is exhausting" + +The correction path, for when the built thing disagrees with the budget Phase 4 set. It is **not** +how ornament first arrives — if a first generation comes out bare under a rich style, the bug is +that Phase 4 never assigned the rungs, and the fix is there. + +When someone names a surface that is plain, bare or boring — or, under a rich baseline, busy, +exhausting or focus-less — run [`references/enrichment-moves.md`](enrichment-moves.md) against **that one surface**: read +the unit's `DESIGN-DIRECTION.md` style and budget, move that surface **one rung**, up or down, pick moves the +style can account for, write an `Enrichment` line and an acceptance line back into `DESIGN-DIRECTION.md`, +rebuild only that surface. Downward is a real outcome: the fix for "nothing stands out" is almost +never adding more. + +Two bounds keep it from turning into a redesign: + +- **It never reaches L3 on its own.** The unit has one arresting thing; promoting a second means + demoting the first, and that is a Phase 4 decision the user makes. +- **`fitted` works inside the ladder, never on it.** The host's size container and upstream's + [`container-query-fitted-layout.md`](../../boxel/references/container-query-fitted-layout.md) own the breakpoints, the regions each size shows and the + `FittedCard` implementation — read it at build time, never from a copy restated here. Enriching a + plain fitted view is *placement and ornament within the regions that size already has*: which + field leads, which field sits in which region, the single mark where the head is all there is, + whether there is a hero, and ornament inside a region — tuned through `--fc-*` and + `@container fitted-card` overrides. Not in scope: moving or restating a breakpoint, asking for a + region a size does not have, or writing spacing and type numbers into `DESIGN-DIRECTION.md`. Where the two + disagree, upstream wins. + +Being asked for the whole unit — "the app looks generic" — is a style question, not this. Go to +Phase 4. diff --git a/skills/design-direction/references/screen-types.md b/skills/design-direction/references/screen-types.md new file mode 100644 index 00000000..e4e8f3df --- /dev/null +++ b/skills/design-direction/references/screen-types.md @@ -0,0 +1,82 @@ +# Screen types and layout directions + +Three directions per screen type. Pick one and adapt the boxes to the unit's real blocks; keep the +ratios and the dominant object. Every direction states: dominant object and share · reading order · +primary action position · above the fold at 1200×700 · how it stacks at 400. + +Legend: `█` dominant · `▓` secondary · `░` tertiary · `[A]` primary action. + +## desk / dashboard +Landed on many times a day. One thing to notice, one thing to do. + +**A · Spotlight** — when one number carries the screen +``` +┌──────────────────────────────────────────────┐ +│ ████████████████████████ ▓▓▓▓▓▓▓▓▓▓▓▓▓ [A] │ 60/40 +│ ███ the figure + its gap █ ▓ who / when ▓▓ │ +├──────────────────────────────────────────────┤ +│ ░░░░░░░░░░░░ list / ledger ░░░░░░░░░░░░░░░░ │ full width +└──────────────────────────────────────────────┘ +``` +Dominant: the figure, 60% of the top band. Reading: Z. Action top right. Above fold: figure, gap, +first four rows. At 400: stack, figure first. + +**B · Ranked rows** — when the grouping is the story +``` +┌──────────────────────────────────────────────┐ +│ ▓ filters ▓ [A] │ +│ ████ tier / group heading ████████████████ │ +│ ▓ item ▓ item ▓ item │ +│ ████ next group ██████████████████████████ │ +│ ▓ item ░ item │ +└──────────────────────────────────────────────┘ +``` +Dominant: the grouping; the top group's tiles run larger. Reading: F. Action top right. Above fold: +first two groups. At 400: one column, headings sticky. + +**C · Timeline** — when *when* is the question +``` +┌──────────────────────────────────────────────┐ +│ ░ Jan ─●──●────●───▓▓ now ▓▓──────●── Dec ░ │ the period is the object +│ ▓ chips: state · next · owner │ +├──────────────────────────────────────────────┤ +│ ░░░░ what falls in the selected span ░░░░░░ │ +└──────────────────────────────────────────────┘ +``` +Dominant: the strip. Reading: left to right, then down. Action: on the strip. At 400: strip scrolls +horizontally, detail below. + +## record / detail +One object, examined. The domain question must be answered above the fold. + +**A · Hero and rail** — media or a headline figure leads, meta in a right rail, detail below. +60/40 top band, full-width detail. Action in the rail. + +**B · Sectioned with nav** — a left or sticky section nav, one column of sections. For records with +5+ distinct areas. Action pinned in the header. + +**C · Editorial** — eyebrow, large headline, one wide media block, then two columns of detail. +For records people read rather than scan. + +## collection / browse +Choosing among many. Never an equal grid of identical tiles. + +**A · Shelf** — horizontal rows by category, each row scrolling. Featured item double width. +**B · Magazine** — one large lead item, then a grid at half scale. Reading: spotlight then F. +**C · Table** — only when the job is comparing evidence field by field. Sort and filter visible. + +## check-in / operator +One input, fast, repeated. The result is the screen. + +**A · Input is the screen** — a large single field centred in the top third, result below it in place. +**B · Input in a rail** — field and history in a left rail, result occupying the rest. +**C · Queue** — a list of pending items, the current one expanded in place. + +## form / create +**A · Sectioned** — fields grouped into task sections with a section rail. Default for 8+ fields. +**B · Stepped** — one section per step with visible progress. When order is genuinely required. +**C · Inline** — a single short block. Only for 3–5 fields. + +## empty / first run +Not a screen type of its own, but every screen needs one: name the first action, show where the +result will appear, and put the action inside that shape. diff --git a/skills/design-direction/references/signature-treatments.md b/skills/design-direction/references/signature-treatments.md new file mode 100644 index 00000000..aaf3eee5 --- /dev/null +++ b/skills/design-direction/references/signature-treatments.md @@ -0,0 +1,124 @@ +# Signature treatments — the one arresting thing, and how to build it in a card + +Every unit needs something that arrests: a dominant object with a treatment nobody would arrive at +by default. `design-direction` Phase 2 already forbids an all-equal-weight layout and requires you +to name the dominant object; `design-review` scores it as Composition. This file is the **execution +vocabulary** for that decision — what is achievable in a Boxel card with pure CSS, and what each +technique costs. + +## Read this first + +**The aurora-gradient / glassmorphism / floating-particles family is the current default look.** +Upstream's [`critical-rules.md`](../../boxel-design/references/critical-rules.md) names *Gradient Overuse* explicitly — "gradients are the 2024 +over-used signature; flat color with intentional contrast often wins" — and the same list names +the *Average Quality Trap*. A blurred radial mesh behind a frosted card is what a generation of +tools produces unprompted. Reaching for it is not a signature; it is the absence of one. + +So the question this file answers is **not** "which effect do I add." It is: *what does this +particular style already imply, and what is the strongest version of that?* A field-guide style's +signature is a stipple and a hairline rule, not a gradient. A brutalist style's is a hard shadow +and an oversized numeral. Pick the treatment the style earns. + +Use the list below as vocabulary, not a menu. If two different units in the same workspace end up +with the same treatment, at least one of them was not designed. + +## The techniques, and what each costs in a card + +| Treatment | Pure CSS | Cost / constraint | +|---|---|---| +| **Drifting gradient field** — two or three blurred radial gradients, `@keyframes` on `transform`, `filter: blur()` | yes | The default look; needs a reason. `filter: blur()` on a large surface is expensive and composites on every frame | +| **Flowing text** — `background-clip: text` with an animated `background-position` | yes | Needs `color: transparent` — check the contrast fallback when the animation is off, and that selection/high-contrast mode still reads | +| **Staggered reveal** — per-child `animation-delay`, `translateY`, and `opacity` only where the unit has no `MOTION.md` | yes | The base CSS must be the FINAL state; `from { opacity: 0 }` + `both` only. See the invisibility trap in upstream's [`template-patterns.md`](../../boxel-ui-guidelines/references/template-patterns.md). The `opacity` half makes screenshot review unreliable — a capture during the stagger reads as blank — which is why [`motion-authoring`](../../motion-authoring/SKILL.md) forbids it outright for a specified arc and asks for `transform` alone. Below that weight it is allowed, at that cost | +| **Layered glass** — `backdrop-filter: blur()` over a moving ground, plus an inline SVG `feTurbulence` data URI for grain | yes | `backdrop-filter` is the other half of the default look. The grain data URI inflates the template; keep it small | +| **Hover parallax tilt** — `:hover { transform: perspective() rotateX() }` | yes, no JS | Hover only — gives nothing on touch, and nothing to keyboard users unless paired with `:focus-visible` | +| **Scroll-driven** — `animation-timeline: scroll()` / `view()` | Chromium only | **The card scrolls inside itself**, not the document, so the timeline must resolve to the card's scroll container. Fall back to `IntersectionObserver` with `root` set to that container — never the viewport default | +| **Scroll-scrubbed subject** — one object (a bottle, a device, a chart, a mark) rotates, scales or shifts continuously as the reader scrolls | yes, for a single element | The strongest signature a card can carry, and the most expensive to get wrong. See below | + +### The scroll-scrubbed subject + +When a unit has one object worth looking at — a product, a device, a specimen, a chart, a brand +mark — tying its `transform` to scroll position is the signature move that reads as *made*, not +assembled. It is what a reader remembers, and it is worth reaching for when the unit's whole job is +to present that object. + +It is also the treatment most likely to be asked for and most likely to be built the expensive way, +so decide these in Phase 4 rather than at build time: + +- **One element, `transform` and `opacity` only.** `rotate`, `scale`, `translate` and opacity are + compositor properties — they animate without layout or paint. Scrubbing `width`, `top`, `filter` + or a box-shadow instead is the same effect at many times the cost, and it is what makes a + scroll page feel heavy. Pure CSS handles this with `animation-timeline: view()` and `@keyframes` + on the transform; **no library is needed for a single subject.** +- **A frame sequence is a different, much larger thing.** The Apple-style effect where the object is + really a few hundred pre-rendered stills swapped by scroll position is a canvas plus a preload + budget, not a transform — it becomes a library capability the build sources, and a real weight decision. + Say which of the two you mean; they are not interchangeable and the cheap one is usually enough. +- **The still frame carries the composition.** The subject must be well placed and well cropped at + its resting pose, because that is what a reduced-motion reader, a screenshot and every capture in + review will see. A subject whose composition only works mid-scrub has no composition. +- **It is the unit's one L3.** A scrubbed subject is by definition the arresting thing; nothing else + in the unit may compete with it, and the same object compresses to a static crop in `embedded` + and to a single mark in `fitted`. Never scrub in a non-`isolated` format. + +## Spatial model — the control that was never defined + +`spatial model` is one of the five style controls every `DESIGN-DIRECTION.md` records, and until now it had +no vocabulary, so it got filled in as "flat" by default. Four options: + +| Spatial model | What it means | Fits | +|---|---|---| +| **Flat** | no depth cues at all; hierarchy is size, weight and rule | editorial, field-guide, brutalist, anything where paper is the metaphor. **A legitimate choice, not a fallback** | +| **Layered** | z-order, offset and shadow as punctuation; still 2D | dashboards, stacked records, anything with overlay state | +| **Perspective** | CSS `perspective` + per-element `translateZ` / `rotateY` | a collection whose items deserve drama — showcases, reels, card sets | +| **Scene** | a camera, a canvas runtime, a model | the content *is* spatial — an assembly, a map, a building | + +Upstream ships a proven pattern for the third: **`boxel-patterns/patterns/layout-3d-card-carousel`** +— results from `@context.searchResultsComponent` positioned on a virtual cylinder, auto-rotate, +hover lift, click-to-focus, filter-reactive, **all CSS and tracked state, no library**. Its own +guidance names the use: *"anywhere a flat grid feels too utilitarian"* and *"hero / landing sections +of an app card where the user sees a wow first."* + +Two consequences to record with the pick: + +- **Perspective and scene need width.** [`prefers-wide-format.md`](../../boxel/references/prefers-wide-format.md) lists spatial layouts as a + `prefersWideFormat = true` category — a camera in a 720px column reads as a postage stamp. Decide + it with the CardDef, not after. +- **Spatial and flat are mutually exclusive, and the content decides.** A 3D hero on a field-guide + style is not a bolder version of that style, it is a different one. *"Depends on the + requirement"* has to mean the content is spatial — a collection, a showcase, an assembly — not + that the flat version looked boring. If the honest answer is boring, the fix is in Phase 2's + dominant object, not in adding an axis. + +## Four constraints that apply to all of them + +These come from the card runtime, not from taste: + +1. **The same card can render more than once on one page.** The host uses no iframes and a card can + be open in several stacks at once. Never `document.querySelector`, never a document-level + listener, never an unscoped ` + +} + +export class ScrollMotionExample extends CardDef { + static displayName = 'Scroll Motion Example'; + + @field standfirst = contains(StringField); + @field caption = contains(StringField); + + static isolated = Isolated; + // No scrub, no reveal in embedded/fitted/atom — they render inside someone + // else's composition and have no meaningful scroll of their own. +} diff --git a/skills/design-direction/SKILL.md b/skills/design-direction/SKILL.md index 970437c8..83d4b3de 100644 --- a/skills/design-direction/SKILL.md +++ b/skills/design-direction/SKILL.md @@ -112,8 +112,8 @@ Read these live on each run rather than working from memory. Links are relative | The four-stage process itself, and the Stage 0f content matrix | [`design-playbook.md`](../boxel/references/design-playbook.md) | Phase 1, Phase 3 framing | | Anti-cliché checklist | [`critical-rules.md`](../boxel-design/references/critical-rules.md) | Phase 3 anti-patterns | | The `fitted` size ladder, `FittedCard`, `--fc-*` | [`container-query-fitted-layout.md`](../boxel/references/container-query-fitted-layout.md) | Any enrichment of a fitted view — authority, read live | -| Turning a Narrative arc, scrubbed subject or direct-manipulation way into build numbers | `motion-authoring` — opt-in, only when the arc exists | after Phase 4 | -| After the build: the user's first look, the gate, and scoring | `design-review` | after Phase 4 | +| Turning a Narrative arc, scrubbed subject or direct-manipulation way into build numbers | [`motion-authoring`](../motion-authoring/SKILL.md) — opt-in, only when the arc exists | after Phase 4 | +| After the build: the user's first look, the gate, and scoring | [`design-review`](../design-review/SKILL.md) | after Phase 4 | ## Never diff --git a/skills/design-review/SKILL.md b/skills/design-review/SKILL.md new file mode 100644 index 00000000..559fdc91 --- /dev/null +++ b/skills/design-review/SKILL.md @@ -0,0 +1,288 @@ +--- +name: design-review +description: Score a built Boxel screen, card or whole app against its `## Design direction` and an aesthetic bar, from screenshots — never from code. Use it whenever someone asks for a design review or critique ("how does this look", "is this good enough", "why does this feel off"), right after a screen is built and before more are, when all screens exist and need checking as a set, and for the linked CardDefs whose embedded, fitted and atom views were written in passing. Use it even when the work looks fine. It reviews what exists; for deciding a direction first, use design-direction. +boxel: + kind: skill +--- + +# Design Review + +_Score what was built, from what it looks like._ + +| Contract | | +|---|---| +| **Reads** | Captures of the built thing, and the unit's brief card — its `## Design direction` acceptance lines and anti-patterns, its `## Motion` numbers where one exists | +| **Writes** | Nothing. The report is the deliverable: the brief card records decisions, not scores, so a review never edits it | +| **Stops when** | The verdict is reported and the next step — fix, review another surface, revisit a decision, or stop — is offered as a choice | + +You review a built thing against two standards: the `## Design direction` section that was decided for it, and an +aesthetic bar that a competent-but-generic result does not clear. Where they conflict, `## Design direction` +wins on *what was decided* and you judge only *how well it was executed*. + +**Three modes.** Default reviews one screen. `design-review set` reviews every screen of an app +together — reach for that whenever more than one screen exists, because coherence across screens is +the thing no single-screen review can see. `design-review card ` reviews one card's +formats as a family, and is the mode nobody remembers to run: in an app, the cards behind the links +are the surfaces that get built in passing and scored by nothing. + +## Capture first — there is no review without it + +You cannot judge a design from CSS. Capture before scoring, and stop if you cannot: a code-only +review reads as a real review and is not one. Service first for everything it supports, browser +only for the rest; if the browser is unavailable, review the service captures and list the +uncaptured views by name rather than blocking. + +**A blank region in a capture is not yet a finding** — staggered entrance animations and +not-yet-loaded cards both paint empty at capture time. Re-capture two or three times before +reporting it. + +Read `references/capture.md` for which tool captures which view, the +service's batch and time budgets, where captures go, and the flakiness rules. + +## Phase 1 — The design-direction gate + +When the brief card has a `## Design direction` section, it is the primary rubric. + +1. Read its acceptance lines, its set acceptance lines, and its anti-patterns block. Each + anti-pattern counts as an acceptance line. +2. Check each against the captures and the DOM. Tick or fail — no partial credit, no commentary. +3. Report first, in this shape: + +``` +Design direction: / acceptance lines +FAILED +- · · · +``` + +Every failed line carries the screenshot it failed on, not just words. **Any failure is an +ITERATE** regardless of the score below — and in set mode it iterates that screen alone, not the set. + +**Do not re-judge what `## Design direction` already decided.** The layout direction, the interaction way and +the named style were chosen deliberately, with reasons recorded. Score how well they were executed. +If you think a decision itself was wrong, say so once, separately, as a note to the user — not as a +score. + +With no `## Design direction` section, skip this phase and say the review ran without one. + +## Phase 2 — The aesthetic gate + +The bar is work that would be singled out, not work that is inoffensive. A result that is +professional, polished and forgettable sits around 6–7; the gate is **8.5**. + +Score and name the specific thing behind each number: + +- **Typography** — does the pairing look chosen? Weight rhythm (large at light weight, small at + bold), or everything between 500 and 600? Is there tracking on uppercase micro-labels? +- **Colour** — one accent doing real work in at most two places, or three to five things competing? +- **Composition** — does something dominate, or is it a row of equals? Is the reading order legible + in the screenshot alone? Name the signature treatment on the dominant object and say whether it + belongs to this style — `design-direction/references/signature-treatments.md` is the authority on + which treatments a style earns and why the default gradient-and-glass look scores *lower* than a + plain surface; judge against it rather than restating it. Check the treatment survives into + `embedded` and `fitted` as a compressed idea rather than a shrunken copy. +- **Media** — if the card has imagery, does it get real space, or hide under metadata? Check it + **across formats, not just this screen**: the `## Design direction` section's Views table says what each one carries, so + verify `embedded` and `fitted` show a focused crop rather than a shrunken copy of the hero, and + that a card with an image never renders a text-only tile. A blank Media row on a unit that has an + image field is itself a finding — the decision was never made. +- **Detail** — the editorial micro-objects that signal care: an eyebrow with a rule, a stat slab + bounded by lines rather than boxed, a fold cue. Two or three, not all of them. +- **Motion** — entrance, feedback, state change. Fade-only or instant swaps read as unfinished; + spatial motion with character reads as designed. If motion is absent or purely functional, cap + the score at 8 however good the static work is. When `## Design direction` records a **Narrative arc**, + score against it: do the beats arrive in the recorded order, does the still frame stand alone + with motion off, does the arc reveal the signature rather than compete with it, and is any way + marked *library* actually loaded rather than approximated. Capture at least three points along + the arc — a single screenshot cannot show a sequence. When a the `## Motion` section from `motion-authoring` sits + beside it, the three points are its **Net journey** lines (start / mid / end), the numbers to + check are its Effect rows, and the `## Assumed` list names the numbers nobody chose — read those + first. A unit with no `## Motion` section and no arc is scored on its Interaction rows alone; do not ask + for one. +- **Emptiness and error** — do they invite and instruct, or apologise? + +Below 8.5, name the two or three things standing between here and the bar, then stop and let the +user decide whether to iterate. Do not soften the number to be encouraging; the number is the +useful part. + +## Phase 3 — Constraints + +After the aesthetic gate, check what would make the design wrong rather than plain: + +- **Accessibility** — contrast on every text-bearing surface, focus states, hit targets, and the + motion-off pass for anything animated. The reduced-motion check is not "is there a fallback" but + two questions the pass answers: does every element survive with animations off — upstream's rule + is that *the resting CSS state must be the final state*, so a `from` belongs in the keyframe with + `animation-fill-mode: both`, never in the base rule — and is every state the motion conveys + reachable without it. Motion that carries information, such as a selection that only reads as + selected while it animates, fails the second even when it passes the first. See + `references/capture.md` → *The motion-off pass*. +- **Clarity** — can a first-time user tell what this is and what to do, from the screenshot? +- **Affordance honesty** — does everything that looks pressable have a focus state and a real + control under it? A thing that reads as interactive and is not is a clarity failure before it is + an implementation one. Check the screen's **primary action** by name against the content + contract: if the contract says "start choosing a date" and the screen renders that phrase as a + styled ``, the screen has not delivered its primary action, however good it looks. +- **Affordance legibility — the inverse, and the one this list used to miss.** A control can be a + real `