@boopkit/cap-web 0.5.7 → 0.5.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/capability.json CHANGED
@@ -26,9 +26,11 @@
26
26
  ],
27
27
  "skills": [
28
28
  "boop-web-web-ui-design",
29
+ "boop-web-css",
29
30
  "boop-web-testing",
30
31
  "boop-web-website-dev-workflow",
31
- "boop-web-design-handoff-workflow"
32
+ "boop-web-design-handoff-workflow",
33
+ "boop-web-design-handoff-bundle"
32
34
  ],
33
35
  "scripts": {
34
36
  "tailwind:build": {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@boopkit/cap-web",
3
- "version": "0.5.7",
3
+ "version": "0.5.8",
4
4
  "description": "Express view engine configuration, Handlebars helpers, and static asset middleware",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -2,7 +2,7 @@
2
2
 
3
3
  How this project renders pages: Handlebars templates, Tailwind CSS, static assets, and the request pipeline.
4
4
 
5
- **Related skills**: [design.web-ui-design.skill.md](../skills/design.web-ui-design.skill.md)
5
+ **Related skills**: [boop-web-web-ui-design](../skills/boop-web-web-ui-design/SKILL.md) for pages, templates and markup; [boop-web-css](../skills/boop-web-css/SKILL.md) for class names and stylesheet rules
6
6
 
7
7
  ---
8
8
 
@@ -29,18 +29,31 @@ Handlebars via `express-handlebars`. Templates in `views/`, layouts in `views/la
29
29
 
30
30
  ### Helpers
31
31
 
32
- ~130 helpers registered from `handlebars-helpers` (comparison, string, math, array). Custom helpers from `@boopkit/cap-web`:
32
+ About 130 helpers are registered from [`handlebars-helpers`](https://github.com/helpers/handlebars-helpers).
33
+ The most useful:
34
+
35
+ | Kind | Helpers |
36
+ |------|---------|
37
+ | Comparison | `gt`, `lt`, `gte`, `lte`, `eq`, `is`, `not`, `compare` |
38
+ | Logical | `and`, `or`, `not` |
39
+ | String | `capitalize`, `trim`, `replace`, `startsWith`, `contains` |
40
+ | Array | `first`, `last`, `sort`, `forEach`, `join` |
41
+ | Math | `add`, `subtract`, `multiply`, `divide`, `ceil`, `floor`, `round` |
42
+
43
+ Custom helpers from `@boopkit/cap-web`:
33
44
 
34
45
  | Helper | Purpose |
35
46
  |--------|---------|
36
- | `json` | JSON stringify with formatting |
37
- | `truncate` | Truncate string with ellipsis |
38
- | `urlencode` | Percent-encode a value for a query parameter: `href="/signup?next={{urlencode next}}"`. Handlebars escapes for HTML, not for a URL, so a value interpolated straight into a query string is truncated at its first `&`. |
39
- | `fmt_dollars` | Currency formatting |
40
- | `fmt_percent` | Percentage formatting |
41
- | `fmt_date` | Human-readable date (e.g., "March 28, 2026") |
42
- | `markdown` | Render markdown to HTML |
43
- | `icon` | Inline SVG from heroicons: `{{icon "user" "24/outline"}}` with optional `class=` |
47
+ | `fmt_dollars` | Currency: `{{fmt_dollars 1234}}` → `$1,234`. An optional `sigfigs` rounds for data visualisation: `{{fmt_dollars 12345 sigfigs=4}}` → `$12,350`. |
48
+ | `fmt_percent` | Percentage: `{{fmt_percent 45}}` → `45%` |
49
+ | `fmt_date` | Human-readable date: `{{fmt_date createdAt}}` → `March 28, 2026` |
50
+ | `markdown` | Render markdown to HTML: `{{markdown text}}` or `{{#markdown}}...{{/markdown}}` |
51
+ | `icon` | Inline SVG from heroicons: `{{icon "user" "24/outline"}}`, with an optional class: `{{icon "check" "24/solid" class="icon done"}}`. Styles are `24/outline`, `24/solid`, `20/solid` and `16/solid`. Icons are read from `node_modules/heroicons/` and cached. |
52
+ | `json` | JSON stringify with formatting: `{{json obj}}` |
53
+ | `truncate` | Truncate a string with an ellipsis: `{{truncate text 100}}` |
54
+ | `urlencode` | Percent-encode a value for a query parameter: `href="/signup?next={{urlencode next}}"`. Handlebars escapes an interpolated value for HTML, not for a URL, so `?next={{next}}` renders a return path of `/a&b=c` as `next=/a&b=c` and the browser reads `next=/a`. Use it for every value that lands in a query string. It encodes rather than validates, so whatever checks the value keeps doing that job. |
55
+ | `upper` / `lower` | Case conversion |
56
+ | `eq` | Strict equality (also available from `handlebars-helpers`) |
44
57
 
45
58
  ---
46
59
 
@@ -62,6 +75,12 @@ npm run tailwind:watch # Development (watch mode)
62
75
 
63
76
  Use semantic class names in templates, define them in `styles/styles.css` with `@apply` for Tailwind utilities. Avoid inline Tailwind classes in templates except for one-off spacing.
64
77
 
78
+ Class names nest.
79
+ A block has one class, its children are nested inside it in the stylesheet with short names (`.quote-card .price`), and a variant or state is a second class on the element (`class="quote-card booked"`), selected with `&.booked`.
80
+ BEM's `__element` and `--modifier` separators are not used, nor are flat prefixed names such as `.quote-card-price`.
81
+ Colors, sizes and spacing come from the theme tokens in `@boopkit/cap-web/styles/theme.css`, through `var()` or `@apply`.
82
+ The rules, with an example, are in the [boop-web-css](../skills/boop-web-css/SKILL.md) skill.
83
+
65
84
  ---
66
85
 
67
86
  ## Static Assets
@@ -0,0 +1,129 @@
1
+ ---
2
+ name: boop-web-css
3
+ description: "When you need to write or change CSS — adding or renaming a class, writing a selector or a stylesheet rule, or editing a class attribute in a template — name classes for what their elements are for, nest each block's children and variants inside it, and never use BEM's __element or --modifier separators."
4
+ metadata:
5
+ category: web
6
+ ---
7
+ > **Read `PROJECT.md`, beside this file, first.**
8
+ > It records how this project applies the guidance below, and where it deliberately differs.
9
+ > Where the two conflict, `PROJECT.md` wins.
10
+ > Record project-specific rules there, never in this file.
11
+
12
+ # Writing CSS
13
+
14
+ ## Goal
15
+
16
+ **A stylesheet's nesting is its structure, so a class name never repeats it.**
17
+
18
+ A block gets one class that names what it is.
19
+ Its children are nested inside it in the stylesheet and take short names for what they are.
20
+ A variant or a state is a second class on the same element, selected with `&.name`.
21
+ The stylesheet then reads as the shape of the page, and a template's class attributes stay short.
22
+
23
+ This skill applies every time you touch a class, not only when you design a page.
24
+ It is separate from `boop-web-web-ui-design` for that reason: a one-line change to a class attribute is CSS work, and it is where the naming goes wrong.
25
+
26
+ ## Name classes for what an element is for
27
+
28
+ A class name says what the element is for, not how it looks.
29
+ `.page-title`, `.price` and `.booked` survive a redesign; `.big-bold`, `.text-green` and `.mt-4` do not, and they tell a reader nothing about the page.
30
+
31
+ Use lowercase words joined by hyphens.
32
+ Keep Tailwind utility classes out of templates, except for a one-off spacing tweak; apply them inside a semantic class with `@apply` instead.
33
+
34
+ ## The nested shape
35
+
36
+ ```css
37
+ /** === Quote card === **/
38
+ .quote-card {
39
+ border-color: var(--color-on-surface-muted);
40
+ border-radius: var(--radius-lg);
41
+ @apply border p-4;
42
+
43
+ .vendor {
44
+ @apply font-medium;
45
+ }
46
+
47
+ .price {
48
+ color: var(--color-brand-700);
49
+ @apply font-semibold;
50
+ }
51
+
52
+ &.booked {
53
+ border-color: var(--color-brand-500);
54
+ }
55
+ }
56
+ ```
57
+
58
+ ```handlebars
59
+ <article class="quote-card{{#if booked}} booked{{/if}}">
60
+ <h3 class="vendor">{{vendorName}}</h3>
61
+ <span class="price">{{fmt_dollars total}}</span>
62
+ </article>
63
+ ```
64
+
65
+ - The block, `.quote-card`, is the only class that has to be unique across the stylesheets.
66
+ - Each child takes a short name for what it is: `.price`, not `.quote-card-price`.
67
+ - A variant or a state is a second class on the block or child element (`class="quote-card booked"`, `class="filter-tab active"`), styled with `&.booked` inside the block.
68
+ - Group a page's styles under the page's class, and mark each major section with a comment such as `/** === Settings page === **/`.
69
+ - Nest only as deep as the markup needs. Each level raises the selector's specificity, and a rule three levels down is hard to override.
70
+
71
+ ## No BEM
72
+
73
+ Never write BEM's `__element` or `--modifier` separators, in a stylesheet or in a template.
74
+ `.quote-card__price` and `.quote-card--booked` are not allowed, and neither are flat prefixed names such as `.quote-card-price`, which are one step from BEM.
75
+
76
+ Nesting already ties a child to its block.
77
+ Repeating the block's name in every child's class attribute adds nothing, and a flat list of `.quote-card__vendor`, `.quote-card__price` and `.quote-card--booked` hides the hierarchy that the stylesheet should show.
78
+
79
+ ## When a short name matches more than you meant
80
+
81
+ A nested selector such as `.quote-card .price` matches every element with the class `price` anywhere inside the block.
82
+ That includes an element inside another component nested in the block, such as a line-item list that has its own `.price`.
83
+ A short name also picks up any rule that styles that name on its own, at the top level of any stylesheet.
84
+
85
+ Before you add a short child name, check both:
86
+
87
+ 1. Search the stylesheets for a top-level rule of that name.
88
+ 2. Look at what components the block's template renders inside it.
89
+
90
+ If either applies, use a child combinator (`> .price`) so the rule reaches only the block's own children, or pick a more specific name for what the element is (`.total` instead of `.price`).
91
+
92
+ ## When the stylesheet around you uses old names
93
+
94
+ A stylesheet you are editing may still hold BEM or prefixed names.
95
+ Write your new classes in the nested shape anyway, rather than copying the names around them.
96
+ The convention to match is this skill's, not the file's.
97
+
98
+ Do not rename the existing classes as part of an unrelated change, because their templates change with them.
99
+ If the old names are worth replacing, file an issue for it.
100
+
101
+ ## Colors, sizes and spacing come from the theme
102
+
103
+ Take every color, radius, font and spacing value from the theme's tokens, through `var()` (`var(--color-brand-600)`, `var(--radius-lg)`) or through `@apply` of a Tailwind utility the theme backs.
104
+ Do not write a literal value such as `#10b981` or `13px` in a rule.
105
+ A literal does not change when the project's theme does, so the page drifts from the rest of the site.
106
+
107
+ If no token fits, add one to the project's `@theme` block and use it.
108
+ The web capability's default tokens are in `@boopkit/cap-web/styles/theme.css`, and [`reference/boop-guide.ref.md`](../../reference/boop-guide.ref.md) describes how a project overrides them.
109
+
110
+ ## Reuse a class by job
111
+
112
+ Reuse a class when an element does the same job as the elements the class already styles.
113
+ Elements that do the same job should look the same everywhere, so one class serves them all, and a later change reaches every one.
114
+
115
+ Matching declarations are not a reason to share a class.
116
+ Two classes for different jobs stay separate even when their rules are identical today, because they will change for different reasons.
117
+
118
+ Never create a class named for its layout or its look, such as `.stack-sm` or `.card-shadow`.
119
+ It is a utility class under another name.
120
+
121
+ Before adding a class:
122
+
123
+ 1. Look for an existing class that already does the job, starting with the shared classes the web capability ships (`.btn-primary`, `.btn-secondary` and the others in its `base.css`).
124
+ 2. If one fits, use it. Do not create a near-duplicate with a slightly different name.
125
+ 3. If one is close but not right, decide whether the job is really the same. If it is, change that class; if it is not, write a new simple class. Two simple classes are better than one class with variants for every case.
126
+
127
+ ## Related
128
+
129
+ - [`boop-web-web-ui-design`](../boop-web-web-ui-design/SKILL.md) — when the change is a new page, partial or component, for the markup patterns and the visual check before done.
@@ -0,0 +1,102 @@
1
+ ---
2
+ name: boop-web-design-handoff-bundle
3
+ description: "When the operator points at a design canvas (an artifact of artboards) and asks for it to be bundled, turned into a handoff, or made ready for the coding agent, turn it into a design handoff bundle under specs/design-handoffs/: a README, drop-in templates and CSS in the project's conventions, the decisions, and an eval checklist. The producing side of boop-web-design-handoff-workflow; the system wins over the prototype, and every departure is written down."
4
+ metadata:
5
+ category: workflow
6
+ ---
7
+ > **Read `PROJECT.md`, beside this file, first.**
8
+ > It records how this project applies the guidance below, and where it deliberately differs.
9
+ > Where the two conflict, `PROJECT.md` wins.
10
+ > Record project-specific rules there, never in this file.
11
+
12
+ # Design canvas → design handoff bundle
13
+
14
+ ## Goal
15
+
16
+ **A bundle is a translation of the design into the project's system, not a copy of the prototype.**
17
+
18
+ A canvas is drawn with the designer's own sizes, colours and one-off markup.
19
+ The bundle restates it in the project's tokens, shared classes and partials, and records every place it departs from the drawing, so the coding agent builds the design without re-deciding it.
20
+ The output is a folder under `specs/design-handoffs/<name>/` that [`boop-web-design-handoff-workflow`](../boop-web-design-handoff-workflow/SKILL.md) consumes.
21
+ This skill produces the bundle; that one verifies and builds it.
22
+
23
+ Never commit; the operator commits the bundle.
24
+
25
+ ## 1. Find and read the canvas, all of it
26
+
27
+ - Find the canvas with the Artifact tool.
28
+ A canvas shared from another organization does not appear in `list`, even under "shared", so ask for its link straight away.
29
+ - **List the canvas's files (`scope: "files"`) every time before bundling.**
30
+ Boards get added while you work.
31
+ Read `project/canvas.json` for the board titles, then read every `project/*.dc.html`; do not trust a board count from an earlier read.
32
+ - People edit the canvas live.
33
+ Re-read the boards right before writing the bundle, and diff them against your earlier copy to pick up the edits.
34
+
35
+ ## 2. Check the canvas's design system against the project
36
+
37
+ The canvas's design system is under `project/ds/<namespace>/tokens.json`.
38
+ Compare its `meta.source` and its values with the project's theme: the `@theme` block in `styles/styles.css`, `@boopkit/cap-web/styles/theme.css`, and Tailwind's own `theme.css`.
39
+ If the tokens were generated from this project and the values match, say so in the decisions file: there is nothing to reconcile, and the build uses the tokens by name.
40
+ If they differ, list every difference and ask the operator which wins before writing any CSS.
41
+
42
+ ## 3. Read the project's conventions before mapping anything
43
+
44
+ - [`boop-web-css`](../boop-web-css/SKILL.md) for class names and nesting, and [`boop-web-web-ui-design`](../boop-web-web-ui-design/SKILL.md) for templates and partials. The bundle's CSS and templates follow them; do not restate them in the bundle.
45
+ - The project's web-UI and brand reference pages, including any rules on copy and on claims about the product.
46
+ - The shared stylesheets and `views/partials/`, for the classes and partials a section can be built from.
47
+ - The routes, controllers and forms the pages touch.
48
+ - `git log` on `views/` and `styles/`, and `specs/history/`: handoffs get built and archived quickly, so the code the bundle replaces may have changed today.
49
+
50
+ ## 4. Map every section
51
+
52
+ For each section of each board, decide which existing class or partial it is, or which new class it needs.
53
+ A pattern used on more than one page goes into a shared additions file; a pattern used on one page goes into that page's stylesheet.
54
+ Markup used on more than one page becomes a partial with documented parameters.
55
+
56
+ **The system wins over the prototype.** Record each departure in `00-decisions.md`:
57
+
58
+ - Type sizes, radii and spacing are the theme's tokens, not the prototype's own values.
59
+ - Each inline colour maps to a token; keep a table of prototype value, where it is used, and what the build uses. Never introduce a new colour.
60
+ - A fixed width or height a designer set on a heading to stop a line wrapping is not shipped. Widen the heading's container instead, so it still wraps on a phone.
61
+ - A picture of the product is a real screenshot, not drawn markup, because a mockup can look right and not match what the product does. Specify the capture instead of building it: seeded demo data in a throwaway database, never the shared development one, then `npm run ux:capture` against the bundle's dev server, with the `<img>` width and height left for the coding agent to fill.
62
+
63
+ ## 5. Copy
64
+
65
+ - Copy is the board's, word for word, with the project's copy rules applied.
66
+ - Remove canvas debris, such as placeholder text, absolutely positioned leftovers and forced line breaks. Fix obvious missing punctuation, and list every fix.
67
+ - Flag, do not silently change, anything that breaks the copy rules, such as a claim beyond what the product does. The operator decides; once they have, record the result as kept on purpose in both `00-decisions.md` and the template's comment, so a later review does not "fix" it.
68
+
69
+ ## 6. Write the bundle
70
+
71
+ At `specs/design-handoffs/<name>/`:
72
+
73
+ | File | Contents |
74
+ |---|---|
75
+ | `README.md` | The authoritative spec the workflow reads first: what is being built and which views and rules it replaces, a read-in-this-order table, the **Data to surface** list (view-model fields the controller does not pass yet), what was dropped and why, follow-ons out of scope, and the canvas link. |
76
+ | `00-decisions.md` | The design-system check, the departures table, the copy fixes, a dated list of what was settled with the operator, and open questions. The bundle is not finished while any question is open. |
77
+ | `<slug>.hbs` + `<slug>.css` per page | A drop-in template, with every new field guarded so it renders against today's view-model, and its CSS, whose header comment names the rules it supersedes. A short `NN-page-<slug>.md` beside them gives each section's layout and how it reflows. |
78
+ | `shared-additions.css`, `partials/` | Shared classes and new partials, with where each goes and which pages use it. For a change to an existing partial, show only what changes. |
79
+ | `NN-eval.md` | A pass/fail checklist at desktop, tablet and phone widths, which the workflow's visual check runs against. |
80
+ | `prototypes/` | Each board exported, with cross-board links renamed to the exported filenames. |
81
+
82
+ Add a routes document only when pages, redirects or forms are new or retired; shape any form endpoint like the project's existing ones.
83
+ Each template opens with a comment naming the bundle and board, what the page is for, what the controller passes, and where its CSS lives.
84
+ Each section gets a short comment saying why it is built the way it is.
85
+
86
+ ## 7. Check before handing over
87
+
88
+ Run a script over the bundle and fix everything it finds:
89
+
90
+ - Every `var(--…)` in the new CSS exists in the project's stylesheets, including cap-web's and Tailwind's theme files.
91
+ - Every class in the new templates is defined in the project's CSS or the bundle's.
92
+ - Every visible string on each board appears in its template; expect only shared navigation, footers and the strings you fixed to differ.
93
+
94
+ ## 8. Working with the operator
95
+
96
+ Ask decisions as short, separate questions.
97
+ Record each answer with its date in `00-decisions.md`, and update every file it touches: templates, routes document and checklist.
98
+ Finish with where the bundle is, that nothing is committed, and what the coding agent still has to produce, such as the screenshots.
99
+
100
+ ## Related
101
+
102
+ - [`boop-web-design-handoff-workflow`](../boop-web-design-handoff-workflow/SKILL.md) — when the bundle is done and the operator hands it to a coding agent; its README claims are verified there before anything is built.
@@ -15,12 +15,16 @@ A design handoff package is a high-leverage input: someone has already done the
15
15
 
16
16
  ## What a handoff package looks like
17
17
 
18
- Three files, typically grouped under a per-handoff directory in the project's design/eval reference area:
18
+ A folder under `specs/design-handoffs/<name>/`, which [`boop-web-design-handoff-bundle`](../boop-web-design-handoff-bundle/SKILL.md) writes from a design canvas.
19
+ Three kinds of file carry the build:
19
20
 
20
21
  - **`README.md`** — the authoritative spec. Names what is being built, which files it replaces, a "Data to surface" list (the view-model fields the redesign needs that the controller does not pass yet), what was deliberately dropped, and any follow-ons that are explicitly out of scope.
21
22
  - **A drop-in view template** — replaces an existing view. Every new field is guarded (rendered only when the field is present) with a live fallback, so the template renders *today* against the current view-model and "lights up" as each data field lands. Ship the template first, work the data list independently.
22
23
  - **A CSS file** — appended into an existing style block; its header comment names the rules it supersedes.
23
24
 
25
+ Beside them, `00-decisions.md` records where the build departs from the prototype and what the operator has already settled, and an eval checklist gives the pass/fail checks for the visual check in step 6.
26
+ A decision recorded there is settled; do not raise it again in step 4.
27
+
24
28
  ## The process
25
29
 
26
30
  ### 1. Read the README in full
@@ -47,7 +51,12 @@ Report: feasible or not, the scope, and the one question that is genuinely their
47
51
 
48
52
  ### 5. File a bd issue (goal/approach/context shape)
49
53
 
50
- Per `boop-project-mgmt-beads-planning`. Breadcrumb the handoff README as the authoritative spec, and **if the operator settled a decision in step 4, state it in the issue and say it supersedes the README where they differ** — otherwise the sub-agent will follow the README's wrong premise. Note the template's graceful-degradation (template + CSS can drop first; view-model edits light it up). Name the visual-check as the done-gate.
54
+ Per `boop-project-mgmt-beads-planning`.
55
+ Breadcrumb the handoff README as the authoritative spec, and **if the operator settled a decision in step 4, state it in the issue and say it supersedes the README where they differ** — otherwise the sub-agent will follow the README's wrong premise.
56
+ Note the template's graceful-degradation (template + CSS can drop first; view-model edits light it up).
57
+ Name the visual-check as the done-gate.
58
+ Name the skills the sub-agent needs, such as `boop-light-dev-workflow` for the per-issue steps and the project's web design skill, in the skills list that `boop-project-mgmt-beads-planning` sets out under "Breadcrumb the skills that carry the discipline".
59
+ The sub-agent reads the issue, not this workflow, so a skill the issue does not name is one it never opens.
51
60
 
52
61
  ### 6. Run it as a sub-agent
53
62
 
@@ -55,8 +64,19 @@ Per `boop-project-mgmt-beads-planning`. Breadcrumb the handoff README as the aut
55
64
  - **Apply template + CSS**, removing the rules the CSS header names as superseded.
56
65
  - **Make the view-model edits** per the (corrected) data list.
57
66
  - **Trim brittle tests rather than lovingly rework them.** A redesigned surface is in flux; the operator's visual UAT is the safety net. Keep the full suite green; do not invest in expanding coverage on a surface that is still moving.
58
- - **The visual-check is the gate.** Build the CSS bundle, capture the affected states at desktop (1280) and mobile (390), and *read* the captures — the render is the proof, not the diff. (Auth caveat: the default capture tool may log in as a demo account and not reach the populated data you need; use a capture path that authenticates as the fully-populated account so the render shows real content.)
59
- - **Never `pkill` or pattern-kill a process** — an over-broad `pkill -f "<server command>"` can kill the operator's main-checkout server. Stop only your own server, by precise PID via `lsof -i:<port>`.
67
+ - **The visual-check is the gate.** Build the CSS bundle, capture each affected state, and *read* the captures — the render is the proof, not the diff.
68
+ Capture with `npm run ux:capture -- <path> <label>`, which writes `tmp/ux-eval/<label>.png` at a desktop width of 1280.
69
+ It has no narrow viewport, so check the mobile layout another way, such as a browser's responsive view at 390.
70
+
71
+ - **`BASE_URL`** — the capture defaults to `http://localhost:3000`, the operator's server, not your worktree's. Pass `BASE_URL=http://localhost:<port>` with every capture, or you will screenshot someone else's build and review it as your own.
72
+ - **Auth** — the capture logs in only with the session cookie in `UX_EVAL_AUTH_COOKIE_NAME` and `UX_EVAL_AUTH_COOKIE_VALUE`. Use one for an account that has the data the surface shows: an empty account renders an empty page, which reads as a broken redesign but is an artefact of who is logged in.
73
+ - **Stop the dev server you started, all of it, not only the process on the port.**
74
+ A dev script that watches files runs a supervisor above the server, and only the server holds the port: boop's web-app profile runs `nodemon --exec tsx src/server.ts`, so killing the port holder leaves `nodemon` running while the port reads free, and `nodemon` starts the server again on the next file change.
75
+ Find the supervisor by the worktree's absolute path in its command line, `ps -eo pid=,args= | grep "<absolute-worktree-path>/node_modules/.bin/[n]odemon"`, with the dev script's own watcher in place of `nodemon` if it runs another, and kill that pid; the server under it stops too.
76
+ Then run that search again and `lsof -ti tcp:<port> -sTCP:LISTEN`, and confirm both print nothing, because a free port does not show that the supervisor is gone.
77
+ If the dev script runs no supervisor, the process on the port is the whole server, and `lsof -ti tcp:<port> -sTCP:LISTEN | xargs kill` stops it.
78
+ Never pattern-kill (`pkill -f nodemon`, `pkill -f tsx`, and the like): a pattern reaches every other worktree's server and the operator's own.
79
+ Never stop a server you did not start, or one a running loop owns.
60
80
  - **Disposition:**
61
81
  - **Branch for UAT** when the change is a substantial visual redesign the operator will want to see — leave the branch, the worktree, and the captures; do not merge, do not close.
62
82
  - **Merge on a clean visual-check** when the change is quick, clear, and additive — merge to main only if the capture clearly confirms the result and tests are green; if verification is blocked or ambiguous, fall back to branch-for-UAT rather than merging blind.
@@ -68,13 +88,14 @@ Often itself a sub-agent. Per `boop-product-design-knowledge-backfill`:
68
88
  - **Merge** the branch to main; run the full suite green first.
69
89
  - **Integrate the knowledge** — update the relevant reference page (e.g. the web-UI reference for a redesigned surface) to describe the new shape, and fix any reference line the redesign made stale. Commit docs direct to main.
70
90
  - **Archive the handoff package** (unedited) to `specs/history/<name>-handoff/`.
71
- - **Clean up** — close the issue, remove the worktree, delete the branch, stop the server by precise PID. Do not push to origin unless asked.
91
+ - **Clean up** — stop the server and its supervisor as step 6 describes, then close the issue, remove the worktree and delete the branch. Do not push to origin unless asked.
72
92
 
73
93
  ## When to use this vs the other workflows
74
94
 
75
95
  | Situation | Workflow |
76
96
  |---|---|
77
97
  | A design handoff package (README + drop-in template + CSS) for a surface | **this** |
98
+ | A design canvas the operator wants made ready for the coding agent | `boop-web-design-handoff-bundle`, then **this** |
78
99
  | A goal-framed improvement to an existing surface, no package | `boop-light-dev-workflow` |
79
100
  | A backlog cleanup sweep of lingering P1/P2 items | `boop-project-mgmt-issue-sweep-workflow` |
80
101
  | A feature with backend work driven by a spec | `boop-feature-dev-workflow` |
@@ -91,7 +112,7 @@ Often itself a sub-agent. Per `boop-product-design-knowledge-backfill`:
91
112
 
92
113
  ## Related
93
114
 
115
+ - [`boop-web-design-handoff-bundle`](../boop-web-design-handoff-bundle/SKILL.md) — when the operator points at a design canvas rather than a finished package; bundle it first.
94
116
  - `boop-product-design-knowledge-backfill` — the archive + reference-integration discipline for the closeout.
95
117
  - [`boop-web-web-ui-design`](../boop-web-web-ui-design/SKILL.md) — the visual-check-before-done rule the run leans on.
96
- - `boop-light-dev-workflow` — the per-issue beats that run inside the sub-agent.
97
118
  - `boop-product-design-thought-partnership` — surfacing the buried decision at the operator's altitude.
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: boop-web-testing
3
- description: "When you need to write or run tests for the Express server — services, controllers, middleware, and HTTP routes."
3
+ description: "When you need to write or run tests for the Express server — services, controllers, middleware, and HTTP routes — or to confirm in a browser that a change to something a person looks at does what its issue said, since a green suite proves behaviour but not what renders."
4
4
  metadata:
5
5
  category: test
6
6
  ---
@@ -86,6 +86,92 @@ Boop scaffolds one — `npm run db:query` (database capability) — for reading
86
86
 
87
87
  These are exploratory aids. They are not part of the automated suite, and none of them substitutes for a Jest test that can go red on its own.
88
88
 
89
+ ## Verifying a change someone will look at
90
+
91
+ When a change alters something a person looks at, such as a page, a form, a list, an empty state or a message, finish by driving it in a browser against seeded data.
92
+ **A passing suite proves the behaviour. Only the rendered page proves that a person can reach it.**
93
+
94
+ A change can be correct and still fail the person it was built for.
95
+ A confirmation renders six screens below the fold; an error appears at the bottom of a form while the reader looks at the top; a button's label does not say what it does; a control lands on the wrong one of two pages.
96
+ In each, the code does what it should and its test passes, because a test asserts that the confirmation renders and none asserts where on the page it lands.
97
+ In one downstream project a change shipped with over eight thousand passing tests and a clean typecheck, and twenty minutes of driving it found a P1: the confirmation that was the only defence against a repeated submit rendered more than four thousand pixels down the page, so the reader saw a form redisplay unchanged and pressed submit again.
98
+
99
+ So the question here is not whether it works, which the suite answered, but **whether the person this is for would get what the issue promised.**
100
+ Get the suite green first, or you will spend the session diagnosing a failure a test would have named in seconds.
101
+ This is no substitute for tests, and it does not re-prove logic a unit test already covers.
102
+
103
+ ### The rendered page is the authority for what a page shows
104
+
105
+ Before you say a surface works or is broken, sign in as the person it is for and render it.
106
+ A database query and a code trace each describe something next to the page, not the page.
107
+ In the same project, a query found the value a fix had written and the fix was called done, while the view read a different, empty field and still showed the old text; a later fix was then called broken from a code trace, and the render showed it worked.
108
+
109
+ - Verify the broad claim the change makes across the whole surface, not only the row you changed.
110
+ - Before handing to the operator, ask what they would catch on it, and go and find that first.
111
+ - A sub-agent's "verified on real data" is a lead to check, not a verdict.
112
+
113
+ A surface you are making no claim about needs no render.
114
+
115
+ ### Stand up an environment with the states the surface will meet
116
+
117
+ - **Never the shared development database or a deployed environment.**
118
+ Use a throwaway database and a server started from the worktree whose code you are verifying.
119
+ A server running another checkout's code produces a page that looks plausible and is wrong, so confirm the server process's working directory is your worktree.
120
+ - **Rebuild the compiled stylesheet first.**
121
+ It is usually gitignored, so a fresh worktree lacks it or holds a build from before the branch's CSS, and nothing warns you: the page still renders, and you judge a layout you did not ship.
122
+ - **Seed the variety of states.**
123
+ A fault that shows only in an empty, single, partial, finished or failed state is invisible while the database holds one well-formed example.
124
+ Reuse a seed the project already has that writes those states.
125
+ When a feature brings a new kind of list, roster, directory or inbox, scope a seed-data spec beside the feature spec, naming the permutations it must write; the two can run as parallel loops, neither gating the other, so the feature develops against the seed and exposes shapes the seed missed.
126
+ - **Run the background workers the pages depend on.**
127
+ A web process alone leaves the jobs a page waits on sitting in their queue, and the page eventually reports a failure that nothing logs as an error.
128
+
129
+ ### Drive it
130
+
131
+ Use the browser tools against the real pages: read the page, fill the real inputs, press the real buttons.
132
+ A bare HTTP request to the route is not a check of the surface, because what renders is the thing under test.
133
+ Walk the issue's Done-when one line at a time, and for each ask what a person would see, not whether the write happened.
134
+
135
+ These traps each cost a downstream session time:
136
+
137
+ - **Page-text extraction reports screen-reader-only text as if it were visible.**
138
+ Confirm anything you rely on with a screenshot or by measuring the element.
139
+ - **Presence is not position.**
140
+ When something must catch the reader's attention, such as a confirmation, an error or a warning, measure where it lands rather than confirming it exists:
141
+ ```js
142
+ const r = el.getBoundingClientRect();
143
+ ({ top: Math.round(r.top), aboveFold: r.top < window.innerHeight })
144
+ ```
145
+ - **A Chromium capture can miss a mobile overflow caused by a native form control.**
146
+ Chromium shrinks a `<select>` with long options; Safari sizes it to its longest option, so a picker that fitted 390px and 320px captures overflowed on an iPhone.
147
+ Where `<select>`, date or file inputs sit in a narrow layout, reason about the layout's structure, prefer fixes that hold in any browser (`min-width: 0` on flex children, a column layout, `max-width: 100%`), and have the change confirmed on a real device.
148
+ - **`:focus` did not match under a headless Playwright probe**, measured once: a focused input read back its unfocused border colour, including one whose focus rule had worked for months.
149
+ Read focus and hover rules from the compiled stylesheet instead, and compare any probe with a sibling known to work, so that a false result shows itself.
150
+ - **The seed may not hold the case you need.**
151
+ Rather than hunting for a record that happens to match, change one you control so that it does; that tests your change and the path beneath it in one move.
152
+
153
+ ### Judge what you see
154
+
155
+ Once a flow works, whether it reads well is a separate judgement.
156
+ When the project has the ux capability, run `boop-ux-purpose-judgement` for whether each thing on the page belongs there, then `boop-ux-visual-judgement` for how it looks and scans.
157
+
158
+ - **Read each page, message or notification as its recipient would:** addressed to its reader, warm and professional, free of internal identifiers and jargon, and carrying nothing that reader should not see, such as the operator's budget.
159
+ Wording that confuses you will confuse the person it is for.
160
+ - **When a count or a set appears on more than one surface**, such as a badge in the navigation, a summary on a hub and the rows of a list, put the surfaces side by side.
161
+ A difference between them is the defect, and comparing them finds what a test of one surface cannot.
162
+ - **When the page and the data could disagree, look at the data** with the project's query tool (`npm run db:query` above).
163
+ A row that repeats one value five times is either a rendering fault or a storage fault, and the two have different owners, so establish which before you file it.
164
+
165
+ ### Record and tear down
166
+
167
+ **Findings are observations, not commits.**
168
+ Capture what you saw and file it; do not start fixing mid-run, because editing the code restarts the server and destroys the state you are standing in.
169
+
170
+ File each finding as its own issue, saying what the person sees, why it matters, and what good looks like.
171
+ Put a finding caused by the work under test beneath that work's epic, and file a pre-existing one standalone, saying in its body how you established it was not this branch: a seed fault blamed on the branch sends the next person to the wrong code.
172
+
173
+ Then stop the server you started and drop the throwaway database, and confirm that the port is free and the database is gone rather than assuming the teardown worked.
174
+
89
175
  ## References
90
176
 
91
177
  - `boop-testing-testing` — testing philosophy and the generic approach
@@ -11,7 +11,7 @@ metadata:
11
11
 
12
12
  # Web UI Design Principles
13
13
 
14
- For controller design (the code that handles requests and renders views), apply the core principles in `design.controllers.skill.md` from the `services` capability: thin dispatch, no business logic in controllers, services handle the work. This skill adds web-specific guidance on top: view rendering, template structure, CSS patterns.
14
+ For controller design (the code that handles requests and renders views), apply the core principles in `design.controllers.skill.md` from the `services` capability: thin dispatch, no business logic in controllers, services handle the work. This skill adds web-specific guidance on top: view rendering, template structure and markup patterns.
15
15
 
16
16
  ## Core Philosophy
17
17
  When building UI components, align with the **Spartan Code Philosophy** (see `boop-coding-development`):
@@ -20,44 +20,15 @@ When building UI components, align with the **Spartan Code Philosophy** (see `bo
20
20
  - Resist adding extra styling, animations, or features beyond what's requested
21
21
  - If additional UI enhancements seem beneficial, discuss with the team first
22
22
 
23
- ## Semantic CSS Approach
24
- We use **semantic CSS classes** that describe the purpose of elements rather than their appearance:
23
+ ## CSS
24
+ Before you add or change a class, a selector or a stylesheet rule, read [`boop-web-css`](../boop-web-css/SKILL.md).
25
+ It states how classes are named and nested, rules out BEM, and says when to reuse a class.
26
+ The class names in the markup patterns below follow it.
25
27
 
26
- **Good (semantic):**
27
- ```html
28
- <h1 class="page-title">Dashboard</h1>
29
- <p class="page-subtitle">Welcome back</p>
30
- <button class="btn-primary">Save Changes</button>
31
- <div class="policy-item">...</div>
32
- ```
33
-
34
- **Avoid (presentational):**
35
- ```html
36
- <h1 class="text-2xl font-bold text-gray-900">Dashboard</h1>
37
- <p class="text-sm text-gray-600 mt-2">Welcome back</p>
38
- <button class="bg-blue-500 text-white px-4 py-2 rounded">Save Changes</button>
39
- ```
40
-
41
- ### Implementation
42
- - Define semantic class names in `styles.css` using nested structure
43
- - Apply Tailwind utility classes with `@apply` directive inside semantic classes
44
- - Use semantic classes in HTML/Handlebars templates
45
- - Group related styles together with CSS nesting
46
-
47
- **Example:**
48
- ```css
49
- .settings-page {
50
- @apply px-6 py-4;
51
-
52
- .settings-block {
53
- @apply mb-8 bg-white border border-gray-200 rounded-lg p-6;
54
- }
55
-
56
- .block-heading {
57
- @apply text-lg font-semibold text-gray-900 mb-4;
58
- }
59
- }
60
- ```
28
+ ## State labels and action verbs
29
+ Before you name a state, choose its tone or signal, or write the label of a button or link, read the "Status communication" and "Affordance signaling" sections of `boop-ux-visual-judgement`, if the project has the `ux` capability.
30
+ They say to take each state's label, tone and signal from one shared table, and to use one verb for an action on every surface.
31
+ Whether or not it does, keep the project's state table and chosen verbs in its reference pages, and check them before adding a new state word or action label.
61
32
 
62
33
  ## Modular Templates and Partials
63
34
  Structure templates for reusability while maintaining simplicity:
@@ -83,28 +54,8 @@ When considering whether to reuse existing components:
83
54
  - **DON'T make** components overly configurable to handle every edge case
84
55
  - **DO create** a second simpler component if the existing one requires too many parameters or conditions
85
56
 
86
- ### Handlebars Helpers
87
- `@boopkit/cap-web` includes the [`handlebars-helpers`](https://github.com/helpers/handlebars-helpers) library (~130 helpers) plus custom formatting helpers.
88
-
89
- **Standard helpers (from handlebars-helpers) — most useful:**
90
- - Comparison: `gt`, `lt`, `gte`, `lte`, `eq`, `is`, `not`, `compare`
91
- - Logical: `and`, `or`, `not`
92
- - String: `capitalize`, `trim`, `replace`, `startsWith`, `contains`
93
- - Array: `first`, `last`, `sort`, `forEach`, `join`
94
- - Math: `add`, `subtract`, `multiply`, `divide`, `ceil`, `floor`, `round`
95
-
96
- **Custom helpers (from @boopkit/cap-web):**
97
- - `fmt_dollars` — format number as currency: `{{fmt_dollars 1234}}` → `$1,234` (optional `sigfigs` for data viz: `{{fmt_dollars 12345 sigfigs=4}}` → `$12,350`)
98
- - `fmt_percent` — format as percentage: `{{fmt_percent 45}}` → `45%`
99
- - `fmt_date` — human-readable date: `{{fmt_date createdAt}}` → `March 28, 2026`
100
- - `markdown` — render markdown to HTML: `{{markdown text}}` or `{{#markdown}}...{{/markdown}}`
101
- - `icon` — inline SVG icon from heroicons: `{{icon "user" "24/outline"}}` or with CSS class: `{{icon "check" "24/solid" class="w-5 h-5 text-green-600"}}`. Styles: `24/outline`, `24/solid`, `20/solid`, `16/solid`. Icons are read from `node_modules/heroicons/` and cached.
102
- - `json` — JSON stringify: `{{json obj}}`
103
- - `truncate` — truncate string: `{{truncate text 100}}`
104
- - `urlencode` — percent-encode a value for a query parameter: `<a href="/signup?next={{urlencode next}}">`. Handlebars escapes an interpolated value for HTML, not for a URL, so `?next={{next}}` renders a return path of `/a&b=c` as `next=/a&amp;b&#x3D;c` and the browser reads `next=/a`. Use this helper for **every** value that lands in a query string; it encodes rather than validates, so whatever gates the value keeps doing that job.
105
- - `upper` / `lower` — case conversion
106
- - `eq` — strict equality check (also available from handlebars-helpers)
107
-
57
+ ### Template logic
58
+ The Handlebars helpers the web capability registers are listed in [`reference/web-ui.ref.md`](../../reference/web-ui.ref.md#helpers).
108
59
  Keep template logic minimal—complex logic belongs in the controller/service layer.
109
60
 
110
61
  ## UI Structure Patterns
@@ -113,10 +64,10 @@ Keep template logic minimal—complex logic belongs in the controller/service la
113
64
  Follow consistent page patterns:
114
65
  ```html
115
66
  <div class="[page-type]-page">
116
- <div class="page-header mb-6">
117
- <h1 class="page-title">Page Title</h1>
118
- <p class="page-subtitle">Brief description of the page</p>
119
- </div>
67
+ <header class="header">
68
+ <h1 class="title">Page Title</h1>
69
+ <p class="subtitle">Brief description of the page</p>
70
+ </header>
120
71
 
121
72
  <section class="[content-section-class]">
122
73
  <!-- Page content -->
@@ -128,60 +79,37 @@ Follow consistent page patterns:
128
79
  Use consistent form patterns:
129
80
  ```html
130
81
  <form method="POST" action="/path" class="form">
131
- <div class="form-group horizontal">
132
- <label for="field-id" class="form-label">Field Name</label>
133
- <input id="field-id" name="fieldName" type="text" class="form-input">
82
+ <div class="field horizontal">
83
+ <label for="field-id">Field Name</label>
84
+ <input id="field-id" name="fieldName" type="text">
134
85
  </div>
135
-
136
- <div class="form-actions">
86
+
87
+ <div class="actions">
137
88
  <button type="submit" class="btn-primary">Save</button>
138
- <button type="button" class="btn-cancel">Cancel</button>
89
+ <button type="button" class="btn-secondary">Cancel</button>
139
90
  </div>
140
91
  </form>
141
92
  ```
142
93
 
143
94
  ### Component Structure
144
- Group related elements with semantic containers:
95
+ Group related elements with semantic containers, a block class on the outside and short child names inside it:
145
96
  ```html
146
97
  <div class="policy-list">
147
- <div class="policy-item">
148
- <div class="policy-header">
149
- <h2 class="policy-name">Policy Name</h2>
98
+ <div class="policy{{#if archived}} archived{{/if}}">
99
+ <div class="header">
100
+ <h2 class="name">Policy Name</h2>
150
101
  <!-- controls -->
151
102
  </div>
152
- <p class="policy-description">Description text</p>
103
+ <p class="description">Description text</p>
153
104
  </div>
154
105
  </div>
155
106
  ```
156
107
 
157
- ## Best Practices
158
-
159
- ### Naming Conventions
160
- - Use lowercase with hyphens: `page-title`, `btn-primary`, `policy-item`
161
- - Be descriptive of purpose, not appearance: `block-heading` not `large-bold-text`
162
- - Group related classes with common prefixes: `policy-item`, `policy-header`, `policy-name`
163
-
164
- ### CSS Organization
165
- - Group page-specific styles under page class selector
166
- - Use nesting to show hierarchy and relationships
167
- - Add comments to mark major sections: `/** === Settings Page === **/`
168
- - Keep specificity low—avoid deep nesting unless necessary
169
-
170
- ### Reuse Strategy
171
- 1. **First**, check if an existing semantic class fits your needs
172
- 2. **If close**, use the existing class—don't create near-duplicates
173
- 3. **If not quite right**, evaluate:
174
- - Is it worth making the existing class more flexible?
175
- - Or is a new simple class clearer?
176
- 4. **Remember**: Two simple classes are better than one complex parameterized system
177
-
178
108
  ## Anti-Patterns to Avoid
179
- - ❌ Using Tailwind utility classes directly in templates (except for one-off spacing tweaks)
180
109
  - ❌ Creating overly abstract/configurable components for simple use cases
181
110
  - ❌ Deep partial nesting that makes template flow hard to follow
182
111
  - ❌ Adding UI elements or styles "just in case" they're needed later
183
112
  - ❌ Creating partials for markup that only appears once
184
- - ❌ Duplicating semantic classes with slightly different names
185
113
 
186
114
  ## "Paper-Style" Reports (Analytics & Insights)
187
115
  When designing report/analytics UIs, prefer a **paper-like** presentation:
@@ -198,7 +126,6 @@ When designing report/analytics UIs, prefer a **paper-like** presentation:
198
126
  See also:
199
127
  - `boop-coding-development` for Spartan Code Philosophy
200
128
  - [`reference/web-ui.ref.md`](../../reference/web-ui.ref.md) for the current implementation: the view engine, the CSS pipeline and the page pattern
201
- - [`reference/boop-guide.ref.md`](../../reference/boop-guide.ref.md) for the CSS extension model (`@theme` tokens, package styles, cascade overrides)
202
129
 
203
130
  ## Visually verify before declaring done
204
131
 
@@ -74,18 +74,22 @@ If a content situation cannot be resolved by 1–5 (the refs don't cover it; the
74
74
  This protects the ref docs as durable guardrails and keeps the next page's implementer from re-deriving the same decision.
75
75
 
76
76
  ## Workflow
77
- Prime directive: **Use beads issues for everything**. Beads has hierarchical issues (epics, features, tasks, sub-tasks, bugs). All steps of this website development workflow should be an issue, with appropriate dependencies and reference material (breadcrumbs, etc). We will use feature → task → sub-task for most work. If you find yourself doing work that is not in an issue, stop and create an issue for it.
77
+ Prime directive: **Use beads issues for everything**. Beads has hierarchical issues (epics, features, tasks, sub-tasks, bugs). All steps of this website development workflow should be an issue, with appropriate dependencies and reference material (breadcrumbs, etc). We will use an epic root with `task` children for most work, and a `feature` container for the implementation tasks. If you find yourself doing work that is not in an issue, stop and create an issue for it.
78
78
 
79
79
  **Flow overview.** The website work ships as a chain of issues, each one a separate task whose output is written somewhere the next task can find it:
80
80
 
81
- > feature (parent) → understand → plan → decompose → implement → review → punch-list → closeout
81
+ > epic (root) → understand → plan → decompose → implement → review → punch-list → closeout
82
82
 
83
- The first issue is the feature parent; working it produces the next issue(s), and so on. `bd-loop` picks up each newly-ready issue and runs it until the chain is complete.
83
+ The first issue is the epic root; working it produces the next issue(s), and so on. `bd-loop` picks up each newly-ready issue and runs it until the chain is complete.
84
84
 
85
85
  **The loop ends with the feature branch pushed for human review.** Merging, main-branch coordination, and branch deletion are out of scope for the loop. See step 6 (Closeout).
86
86
 
87
87
  ### First step:
88
- 1. **Create the feature issue**: Create a new feature issue in beads for the requested website work. This is the parent issue for all work tasks. Write a clear title and description that names the page(s) or IA change and links the driving spec (these are the 'breadcrumbs'). Ref-doc and marketing-skill identification happens in the Understand task (step 1 below); the feature issue does not need to enumerate them up front.
88
+ 1. **Create the root issue as an `epic`**: `bd create "<page or IA change>" -t epic`. This is the parent issue for all work tasks, and the rest of this workflow calls it the feature issue. Write a clear title and description that names the page(s) or IA change and links the driving spec (these are the 'breadcrumbs'). Ref-doc and marketing-skill identification happens in the Understand task (step 1 below); the feature issue does not need to enumerate them up front.
89
+
90
+ **Only `task` issues are claimed as work.**
91
+ `epic` and `feature` organise the tree and anchor dependencies, and a loop never picks either up, as "Hierarchy with `--parent`" in `boop-project-mgmt-beads-planning` explains.
92
+ The one consequence to plan for is the closing order in step 6: bd refuses to close an epic that still has an open child.
89
93
 
90
94
  ### Task construction:
91
95
  Create tasks as child issues of the feature issue. The descriptions below are the content of the created issue — so don't *do* the work described in the issue description, this is just the content of the issue.
@@ -133,32 +137,56 @@ Create tasks as child issues of the feature issue. The descriptions below are th
133
137
 
134
138
  When you decompose the plan into implementation sub-tasks (step 3), **include the guardrail registry as breadcrumbs in each sub-task**: the brand ref doc(s), the realization / tone-mode doc, the audience-type marketing skill, the web design skill, and the per-page tone / persona selections from the plan. Review the plan to ensure it fully covers the spec; create follow-up tasks for any gaps it can't resolve.
135
139
 
136
- 3. **Decompose the plan**: Next task (don't *do* this work, this is the content of the task issue): **Start by reading the website implementation plan** at `specs/{feature-name}-plan.md` (confirm path from this task's breadcrumbs). Break it down into a "do the work" task issue plus smaller sub-tasks following the `boop-project-mgmt-beads-planning` skill.
140
+ 3. **Decompose the plan**: Next task (don't *do* this work, this is the content of the task issue): **Start by reading the website implementation plan** at `specs/{feature-name}-plan.md` (confirm path from this task's breadcrumbs). Break it down into a "do the work" container, a `feature`-typed organiser rather than a task, holding one `task` child per unit of work, following the `boop-project-mgmt-beads-planning` skill.
137
141
 
138
142
  Sub-task granularity for website work is typically **page-level or surface-level**: one sub-task per new page, one per page rewrite, one per retired-route deletion, one for global-nav structure, etc. Shared concerns like semantic CSS extensions or new partials get their own sub-tasks and become blockers for the pages that depend on them.
139
143
 
140
144
  **Propagate the guardrail registry into every sub-task as breadcrumbs** — brand ref doc(s), realization / tone-mode doc, audience-type marketing skill, web design skill — so the implementation agent does not have to re-discover them.
145
+ Write the skills among them, and any other skill the sub-task's work needs, as the skills list that `boop-project-mgmt-beads-planning` sets out in its section "Breadcrumb the skills that carry the discipline": a `## Skills` heading, the line "Before making changes, read each of these in full.", and a path to each skill's `SKILL.md`.
146
+ The session that claims a sub-task reads its issue, not this workflow, so a skill the issue only names in passing is one it rarely opens.
147
+
148
+ **Put the test run scope on every implementation sub-task.**
149
+ Each sub-task runs the focused tests for what it touches, plus the project's whole-program static check; the full suite runs once per branch, on the Review task in step 4.
150
+ Left unstated, a sub-task that has just changed a shared partial or a widely used helper runs the whole suite on its own reasoning.
151
+ The rule and the wording to put on each are in the section "Test run scope: what each task runs, and what runs once" in `boop-project-mgmt-beads-planning`.
152
+
153
+ **Audit the sub-task bodies before this task closes, then revise them.**
154
+ Writing an issue and auditing it are separate passes, and doing both at once produces bodies that read well to their author and cost their reader time.
155
+ Once every sub-task exists, re-read them all side by side as the agent who will claim each one cold, following the section "Write, audit and revise in three separate turns" in `boop-project-mgmt-beads-planning`, then rewrite whatever the audit finds.
156
+ Some faults show only side by side: a sub-task that says "a sibling issue handles this" without naming it, or a term the container defines and a sub-task then uses bare.
141
157
 
142
158
  Create the feature branch `work/<feature-issue-id>` (e.g. `work/boop-arc.14`) — branch names MUST use the `work/` prefix + issue ID, never feature names like `home-page-refresh`. All sub-tasks work within this branch.
143
159
 
144
- **Re-wire the Review dependency (critical):** Decompose only plans the work, it does not perform it. So Review (step 4) must depend on the "do the work" task, not on this Decompose task. If Review already exists with a Review→Decompose dep, fix it:
160
+ **Wire Review to each sub-task (critical):** Decompose only plans the work, so Review (step 4) must not depend on this task.
161
+ It must not depend on the container either: bd never closes a container on its own, so an edge on it holds Review until someone closes the container by hand, which is not the same event as the work being done.
162
+ Give Review one plain `blocks` edge on each sub-task instead, so it becomes ready only when every sub-task has closed:
145
163
  ```
146
- bd dep remove <review-task-id> <this-decompose-task-id>
147
- bd dep add <review-task-id> <do-the-work-task-id>
164
+ bd dep remove <review-task-id> <this-decompose-task-id> # only if Review was wired to Decompose
165
+ bd dep add <review-task-id> <sub-task-id> # once per sub-task
148
166
  ```
149
- If Review doesn't exist yet, create it now with the correct dep on the do-the-work task.
167
+ If Review doesn't exist yet, create it now with those edges.
168
+ Every edge in this tree is plain `blocks`.
169
+ Step 3 of `boop-feature-dev-workflow` gives the whole tree recipe, why each piece is shaped as it is, and how to verify the wiring.
150
170
 
151
- 4. **Review the work done**: Next task (don't *do* this work, this is the content of the task issue). Depends on the "do the work" task from step 3 (step 3 is responsible for rewiring this if pre-created with the wrong dep). Review all changes **in the feature branch** `work/<feature-issue-id>` (confirm with `git branch` or breadcrumbs). Run `bd children <feature-issue-id>` to see what sub-tasks were created and closed for context. Pull the **guardrail registry** from the findings file (`specs/{feature-name}-plan-findings.md`) before reviewing — every review below is anchored against those project-specific guardrails. This task will:
152
- - Perform a **code review and content review** of all changes in the feature branch. The content review checks that each affected page (1) implements the driving spec's stated intent for every page-level goal, (2) where the spec specifies exact copy, uses that copy verbatim, and (3) where the spec leaves a content slot to implementation judgment, the copy aligns with the project's brand-voice ref and the audience-type marketing skill (no audience-register drift, no forbidden patterns, no superlatives without evidence, no invented positioning that contradicts settled ref-doc content).
171
+ 4. **Review the work done**: Next task (don't *do* this work, this is the content of the task issue). Blocked by each sub-task of the "do the work" container from step 3, so it runs only once all of them have closed (step 3 wires this, and rewires it if Review was created with an edge on Decompose). Review all changes **in the feature branch** `work/<feature-issue-id>` (confirm with `git branch` or breadcrumbs). Run `bd children <feature-issue-id>` to see what sub-tasks were created and closed for context. Pull the **guardrail registry** from the findings file (`specs/{feature-name}-plan-findings.md`) before reviewing — every review below is anchored against those project-specific guardrails. This task will:
172
+ - Perform a **code review and content review** of all changes in the feature branch. The content review checks that each affected page (1) implements the driving spec's stated intent for every page-level goal, (2) where the spec specifies exact copy, uses that copy verbatim, and (3) where the spec leaves a content slot to implementation judgment, the copy aligns with the project's brand-voice ref and the audience-type marketing skill (no copy pitched at a different audience, no forbidden patterns, no superlatives without evidence, no invented positioning that contradicts settled ref-doc content).
153
173
  - Perform a **brand-voice review** against the project's brand ref doc — its voice / language constraints / do-don't / exclusions sections. Flag any drift from the project-specific rules.
154
- - Perform a **marketing-copy review** against the audience-type marketing skill (if the project ships one) — its universal forbidden patterns (hype cadence, vague universals, unearned superlatives, wrong-audience register) and its quick self-check.
174
+ - Perform a **marketing-copy review** against the audience-type marketing skill (if the project ships one) — its universal forbidden patterns (hype cadence, vague universals, unearned superlatives, copy pitched at the wrong audience) and its quick self-check.
155
175
  - Perform a **tone-mode review** — each page uses the tone mode named in the plan (per the project's realization doc's tone-mode assignments).
156
176
  - Perform a **machine-readability / semantic-HTML review** — headings describe sections; meaningful prose is in the HTML rather than JS-injected; code samples are `<pre><code>`, not background images; copy-paste-safe snippets; plain URLs for the things that matter; OG / meta descriptions truthful (and ideally lifted from the hero lede).
157
- - Perform a **browser validation** — start the dev server on a port confirmed free (`lsof -ti tcp:<port>` prints nothing when free), load each affected page, and confirm the golden path (page renders, primary CTA is wired correctly, internal links work, no console errors, no broken images, the project's theme is intact, no contrast regressions). Test the page narrow (mobile-ish) as well as wide. **If browser validation isn't possible in this environment, say so explicitly — do not claim success.** (Per project `CLAUDE.md` / `AGENTS.md` conventions where present — *for UI or frontend changes, start the dev server and use the feature in a browser before reporting the task as complete.*)
158
- - **Stop the dev server this task started** before it closes (`lsof -ti tcp:<port> | xargs kill`), by port only. Never pattern-kill (`pkill -f <runner>` reaches every other worktree's server and the operator's own — this project runs several worktrees and loops at once). Never stop a server you did not start: if the port you wanted is already held, identify the holder's cwd via `lsof -a -d cwd -p <pid> -Fn` and reuse it only when the directory is this feature's worktree, else take the next free port.
177
+ - Perform a **browser validation** — start the dev server on a port confirmed free (`lsof -ti tcp:<port> -sTCP:LISTEN` prints nothing when free), load each affected page, and confirm the golden path (page renders, primary CTA is wired correctly, internal links work, no console errors, no broken images, the project's theme is intact, no contrast regressions). Test the page narrow (mobile-ish) as well as wide. Drive it as the section "Verifying a change someone will look at" in `boop-web-testing` describes, which names the traps a browser pass falls into. **If browser validation isn't possible in this environment, say so explicitly — do not claim success.** (Per project `CLAUDE.md` / `AGENTS.md` conventions where present — *for UI or frontend changes, start the dev server and use the feature in a browser before reporting the task as complete.*)
178
+ - **Stop the dev server this task started** before it closes.
179
+ Stop all of it, not only the process on the port.
180
+ A dev script that watches files runs a supervisor above the server, and only the server holds the port: boop's web-app profile runs `nodemon --exec tsx src/server.ts`, so killing the port holder leaves `nodemon` running while the port reads free, and `nodemon` starts the server again on the next file change.
181
+ Find the supervisor by the worktree's absolute path in its command line, `ps -eo pid=,args= | grep "<absolute-worktree-path>/node_modules/.bin/[n]odemon"`, with the dev script's own watcher in place of `nodemon` if it runs another, and kill that pid; the server under it stops too.
182
+ Then run that search again and `lsof -ti tcp:<port> -sTCP:LISTEN`, and confirm both print nothing, because a free port does not show that the supervisor is gone.
183
+ If the dev script runs no supervisor, the process on the port is the whole server, and `lsof -ti tcp:<port> -sTCP:LISTEN | xargs kill` stops it.
184
+ Never pattern-kill (`pkill -f nodemon`, `pkill -f tsx`, and the like): a pattern reaches every other worktree's server and the operator's own.
185
+ Never stop a server you did not start, or one a running loop owns: if the port you wanted is already held, identify the holder's cwd via `lsof -a -d cwd -p <pid> -Fn` and reuse it only when the directory is this feature's worktree, else take the next free port.
186
+ - Run the **full test suite** once, plus the project's whole-program static check. This is the branch's only full-suite run; every implementation sub-task ran focused tests.
159
187
  - Perform an **IA review** — the top-nav structure matches the driving spec; retired routes return the spec's prescribed behavior (404, redirect, or other); any home-page cross-links the spec prescribes are present; the primary CTA destination exists and is reachable from every surface that the spec routes traffic from.
160
188
  - Write the review to `labs/claude-runs/{feature-issue-id}-review-summary.md`, structured as: code findings, content findings, brand-voice findings, marketing-copy findings, tone-mode findings, semantic-HTML findings, browser findings, IA findings, and an overall punch-list.
161
- - Append a **punch-list** of issues that must be addressed before merge. Punch-list items typically span categories: code (refactor, naming, controller cleanliness), content (drift from spec intent or from brand-grounded copy judgment), brand-voice (project-specific forbidden phrasing, wrong register), marketing-copy (universal forbidden patterns — hype cadence, vague universals, unearned proof), tone (wrong mode applied), semantic HTML (heading order, missing alt text, JS-injected content), browser (visual bugs, broken links, console errors, mobile breaks, contrast issues), and IA (wrong nav, missing cross-links, retired routes still live).
189
+ - Append a **punch-list** of issues that must be addressed before merge. Punch-list items typically span categories: code (refactor, naming, controller cleanliness), content (drift from spec intent or from brand-grounded copy judgment), brand-voice (project-specific forbidden phrasing, wording more formal, casual or salesy than the brand ref allows), marketing-copy (universal forbidden patterns — hype cadence, vague universals, unearned proof), tone (wrong mode applied), semantic HTML (heading order, missing alt text, JS-injected content), browser (visual bugs, broken links, console errors, mobile breaks, contrast issues), and IA (wrong nav, missing cross-links, retired routes still live).
162
190
 
163
191
  5. **Add a punch-list work task**: Create a 'punch list' task issue as a child of the feature issue. Its goal: turn each item in the punch-list from the review document into a sub-task issue under this punch-list task. **Propagate the guardrail registry into the punch-list task and its children** so the fixers operate against the same brand ref / marketing skill the implementation did.
164
192
 
@@ -166,9 +194,15 @@ Create tasks as child issues of the feature issue. The descriptions below are th
166
194
 
167
195
  **Verify before closing the punch-list task:** Run `bd children <punch-list-task-id>` and confirm every item appears in the tree. Fix any missing item with `bd update <id> --parent <punch-list-task-id>` (remove conflicting deps first if needed). Only close once the tree is correct.
168
196
 
197
+ **Hold Closeout until every fix is done (critical):** a `blocks` edge on the punch-list task alone releases Closeout when that task closes, which can happen while its fix sub-tasks are still open, and Closeout would then archive the spec and update reference against the pre-fix site.
198
+ Once `bd children` lists every fix sub-task, create Closeout (step 6) if it does not exist yet, with a `blocks` edge on the punch-list task, and add a `blocks` edge from Closeout onto each fix sub-task: `bd dep add <closeout-task-id> <fix-sub-task-id>`.
199
+ Confirm with `bd dep list <closeout-task-id>` that the punch-list task and every fix sub-task are there, and only then close the punch-list task.
200
+ If Review found nothing to fix, the edge on the punch-list task is enough.
201
+ Step 5 of `boop-feature-dev-workflow` gives the full order.
202
+
169
203
  Punch-list sub-tasks of category "browser" or "content" should re-validate in the dev server after the fix, and each such sub-task stops the server it started before it closes, on the same terms as the browser validation in step 4. There is one sub-task per punch-list item, so deferring the stop to closeout leaves one stray watcher process per item. Sub-tasks of category "brand-voice" or "tone" should re-check against the project's brand ref doc. Sub-tasks of category "marketing-copy" should re-check against the audience-type marketing skill.
170
204
 
171
- 6. **Close out the website work**: After the punch-list is resolved and the website work is complete, create a closeout task as a child of the feature issue. The closeout task performs two activities:
205
+ 6. **Close out the website work**: After the punch-list is resolved and the website work is complete, create a closeout task as a child of the feature issue, if step 5 has not already created it, blocked by the punch-list task and by each of its fix sub-tasks. The closeout task performs two activities:
172
206
 
173
207
  **a. Archive the spec:**
174
208
  - Move the completed driving spec from `specs/` to `specs/history/` (create the directory if it doesn't exist).
@@ -178,7 +212,9 @@ Create tasks as child issues of the feature issue. The descriptions below are th
178
212
 
179
213
  **b. Integrate into reference:**
180
214
  - Follow the `boop-product-design-reference-integration` skill to blend the new website knowledge into existing `reference/` pages.
181
- - This is NOT "create a new page for the feature" — it's merging knowledge into the existing concept pages. The integration targets are the **guardrail registry** captured in the findings file:
215
+ - This is NOT "create a new page for the feature". Name the subjects the work created or changed, the way a reader would look them up; merge each into the page whose title names it, and give a subject its own page when it has none or has outgrown the page it sits in.
216
+ - **Write this task's description as what was learned, not as pages to edit.** When you create the closeout task, list the knowledge the work produced: what the site now says and where, what was retired, and what voice or audience rules the work surfaced. A page list written before the work can only name pages that already existed, and the session that runs closeout tends to treat it as the whole job.
217
+ - The **guardrail registry** captured in the findings file names the pages most likely to change. Check each of them, as a starting point rather than the scope:
182
218
  - The project's brand realization / site doc — IA, page roster, retired-surfaces list, tone-mode assignments, any messaging tables (if rows changed).
183
219
  - The project's brand ref doc — any voice / language drift discovered during implementation that warrants a new *Do/Don't* row, a new entry in *Language we avoid*, a new persona, etc.
184
220
  - Any other project ref docs that surfaced gaps during the work.
@@ -195,12 +231,18 @@ Create tasks as child issues of the feature issue. The descriptions below are th
195
231
  1. Commit the closeout edits (spec archive + reference updates) on the feature branch.
196
232
  2. `git push` the feature branch to its own remote ref.
197
233
  3. `bd dolt push` so beads state is synced.
198
- 4. `bd close <closeout-task-id>`.
199
- 5. `bd close <feature-issue-id>` (all child work is now done).
200
- 6. Stop. The loop will go idle on "No ready issues for prefix"; the next action belongs to the human reviewer.
234
+ 4. Close the tree from the bottom up, because bd refuses to close an epic that still has an open child:
235
+ ```
236
+ bd close <do-the-work-container-id> # a feature; its sub-tasks closed before Review ran
237
+ bd close <closeout-task-id> # this task, the last open child of the root
238
+ bd close <feature-issue-id> # the epic root, now that every child is closed
239
+ ```
240
+ If the last close fails, bd reports how many children are still open: find them with `bd children <feature-issue-id>` and find out why each is open before trying again, rather than forcing the close.
241
+ 5. Stop. The loop will go idle on "No ready issues for prefix"; the next action belongs to the human reviewer.
201
242
 
202
243
  ## Related
203
244
 
204
245
  - [`boop-web-web-ui-design`](../boop-web-web-ui-design/SKILL.md) — the primary design skill for this workflow; read it at the design step, before deciding what a page is made of.
205
246
  - `boop-ux-purpose-judgement` — when reviewing the rendered page, to ask whether each thing on it belongs there at all, which the visual pass does not ask.
247
+ - [`boop-web-design-handoff-bundle`](../boop-web-design-handoff-bundle/SKILL.md) — when the pages' design arrives as a design canvas rather than a spec; bundle it before building.
206
248
  - `boop-writing-technical-prose` — when writing the driving spec and the child issues; each is picked up cold by an agent who was not in this session.