@boopkit/cap-web 0.5.6 → 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/README.md CHANGED
@@ -17,6 +17,7 @@ Copied into your project on install:
17
17
  - `src/controllers/pages.ts` — example page controllers (`home`, `about`)
18
18
  - `src/routes/pages.ts` — Express router mounting the example pages
19
19
  - `reference/web-ui.ref.md` — reference doc describing the UI conventions
20
+ - `scripts/ux-eval-capture.ts` — Playwright page capture the UX workflows use for screenshots, run as `npm run ux:capture`
20
21
 
21
22
  ### Scripts added to `package.json`
22
23
 
@@ -24,6 +25,7 @@ Copied into your project on install:
24
25
  |--------|---------|
25
26
  | `tailwind:build` | `npx @tailwindcss/cli -i styles/styles.css -o public/css/style.css --minify` |
26
27
  | `tailwind:watch` | `npx @tailwindcss/cli -i styles/styles.css -o public/css/style.css --watch` |
28
+ | `ux:capture` | `tsx scripts/ux-eval-capture.ts` — `npm run ux:capture -- <path> <label>` writes `tmp/ux-eval/<label>.png` |
27
29
 
28
30
  ### Server hooks
29
31
 
@@ -53,6 +55,8 @@ Order numbers are the `preRoutes` phase ordering; other capabilities slot themse
53
55
 
54
56
  Run `npm run tailwind:build` once after install to compile `styles/styles.css` into `public/css/style.css`. `boop status` reminds you of this.
55
57
 
58
+ Before the first UX capture, run `npx playwright install chromium` once to fetch the browser binary.
59
+
56
60
  ## Requirements
57
61
 
58
62
  **Other capabilities:** none — `cap-web` is self-contained.
@@ -62,6 +66,7 @@ Run `npm run tailwind:build` once after install to compile `styles/styles.css` i
62
66
  - `express-handlebars` — view engine binding
63
67
  - `tailwindcss` — utility-first CSS framework
64
68
  - `@tailwindcss/cli` (devDependency) — CSS compilation
69
+ - `playwright` and `tsx` (devDependencies) — the UX capture script
65
70
 
66
71
  **Peer dependencies** (already provided by the `web-app` kernel):
67
72
 
@@ -69,7 +74,7 @@ Run `npm run tailwind:build` once after install to compile `styles/styles.css` i
69
74
  - `express-handlebars >=7.0.0`
70
75
  - `handlebars >=4.0.0`
71
76
 
72
- **Environment variables:** none required. `NODE_ENV=production` enables a 1-day cache on static assets; otherwise caching is disabled.
77
+ **Environment variables:** none required. `NODE_ENV=production` enables a 1-day cache on static assets; otherwise caching is disabled. The UX capture script reads three optional settings: `BASE_URL` (the origin to capture, default `http://localhost:3000`), and `UX_EVAL_AUTH_COOKIE_NAME` with `UX_EVAL_AUTH_COOKIE_VALUE` (an auth cookie for authenticated pages).
73
78
 
74
79
  ## Usage
75
80
 
package/capability.json CHANGED
@@ -10,7 +10,9 @@
10
10
  ],
11
11
  "devDependencies": [
12
12
  "@tailwindcss/cli@^4",
13
- "@types/express-handlebars@^6"
13
+ "@types/express-handlebars@^6",
14
+ "playwright@^1.60.0",
15
+ "tsx@^4.0.0"
14
16
  ],
15
17
  "files": [
16
18
  "views/",
@@ -19,10 +21,16 @@
19
21
  "src/controllers/pages.ts",
20
22
  "src/routes/pages.ts",
21
23
  "tailwind.config.ts",
22
- "reference/web-ui.ref.md"
24
+ "reference/web-ui.ref.md",
25
+ "scripts/ux-eval-capture.ts"
23
26
  ],
24
27
  "skills": [
25
- "boop-web-web-ui-design"
28
+ "boop-web-web-ui-design",
29
+ "boop-web-css",
30
+ "boop-web-testing",
31
+ "boop-web-website-dev-workflow",
32
+ "boop-web-design-handoff-workflow",
33
+ "boop-web-design-handoff-bundle"
26
34
  ],
27
35
  "scripts": {
28
36
  "tailwind:build": {
@@ -32,12 +40,25 @@
32
40
  "tailwind:watch": {
33
41
  "cmd": "npx @tailwindcss/cli -i styles/styles.css -o public/css/style.css --watch",
34
42
  "description": "Watch for changes and rebuild Tailwind CSS"
43
+ },
44
+ "ux:capture": {
45
+ "cmd": "tsx scripts/ux-eval-capture.ts",
46
+ "description": "Screenshot a page for the UX workflows: npm run ux:capture -- <path> <label> writes tmp/ux-eval/<label>.png"
35
47
  }
36
48
  },
49
+ "env": [
50
+ { "name": "BASE_URL", "description": "Base origin the UX capture script renders against" },
51
+ { "name": "UX_EVAL_AUTH_COOKIE_NAME", "description": "Name of an auth cookie to inject for authenticated UX captures (cap-users uses 'auth_id')" },
52
+ { "name": "UX_EVAL_AUTH_COOKIE_VALUE", "description": "Value (session token) for the auth cookie used in UX captures" }
53
+ ],
37
54
  "postInstall": {
38
55
  "steps": [
39
- "Build CSS for the first time: npm run tailwind:build"
40
- ]
56
+ "Build CSS for the first time: npm run tailwind:build",
57
+ "Install the Playwright browser binary once, for the UX capture script: npx playwright install chromium",
58
+ "Capture a page: npm run ux:capture -- <path> <label> (writes tmp/ux-eval/<label>.png; needs the dev server running)",
59
+ "For authenticated pages, set UX_EVAL_AUTH_COOKIE_NAME + UX_EVAL_AUTH_COOKIE_VALUE, or wire the cap-users opt-in block documented in the script header"
60
+ ],
61
+ "verify": "npm run ux:capture -- / baseline"
41
62
  },
42
63
  "hooks": [
43
64
  {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@boopkit/cap-web",
3
- "version": "0.5.6",
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&amp;b&#x3D;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,103 @@
1
+ /**
2
+ * UX evaluation capture — single-page screenshot for the UX workflows.
3
+ *
4
+ * Renders one URL in headless Chromium and writes a labeled full-page PNG to
5
+ * the capture-output directory. Used by the boop-ux-eval-and-refine-workflow
6
+ * and boop-ux-app-ui-dev-workflow skills to produce before/after screenshots.
7
+ *
8
+ * Usage (cap-web supplies it as the `ux:capture` npm script):
9
+ * npm run ux:capture -- <path> <label>
10
+ * Examples:
11
+ * npm run ux:capture -- /items baseline
12
+ * npm run ux:capture -- /items after-change
13
+ *
14
+ * Environment:
15
+ * BASE_URL base origin to capture against (default http://localhost:3000)
16
+ * UX_EVAL_OUTPUT_DIR output directory (default tmp/ux-eval)
17
+ * UX_EVAL_AUTH_COOKIE_NAME optional — name of an auth cookie to inject
18
+ * UX_EVAL_AUTH_COOKIE_VALUE optional — value of that cookie (a valid session token)
19
+ *
20
+ * Public pages need no auth. For authenticated pages, supply a session cookie
21
+ * via the two UX_EVAL_AUTH_COOKIE_* env vars, or wire the cap-users opt-in
22
+ * block below to mint one automatically.
23
+ *
24
+ * One-time setup: `npx playwright install chromium` (downloads the browser).
25
+ */
26
+
27
+ import 'dotenv/config';
28
+ import { chromium } from 'playwright';
29
+ import * as fs from 'node:fs';
30
+ import * as path from 'node:path';
31
+
32
+ const BASE_URL = process.env.BASE_URL || 'http://localhost:3000';
33
+ const OUTPUT_DIR = path.resolve(
34
+ process.cwd(),
35
+ process.env.UX_EVAL_OUTPUT_DIR || path.join('tmp', 'ux-eval'),
36
+ );
37
+
38
+ const targetPath = process.argv[2] || '/';
39
+ const label = process.argv[3] || 'capture';
40
+
41
+ /**
42
+ * Resolve the auth cookie to inject, if any.
43
+ *
44
+ * Default: read a cookie name/value from the environment (works for any auth
45
+ * scheme — paste a valid session token into UX_EVAL_AUTH_COOKIE_VALUE).
46
+ *
47
+ * --- cap-users opt-in -------------------------------------------------------
48
+ * If this project uses @boopkit/cap-users, you can mint a session token here
49
+ * instead of pasting one, by replacing the body of this function with:
50
+ *
51
+ * import UserService from '../src/services/users.service';
52
+ * import { connectDatabase } from '@boopkit/cap-database';
53
+ * import db from '../src/db';
54
+ * await connectDatabase(db);
55
+ * const { identityService, authService } = UserService;
56
+ * const email = process.env.DEMO_EMAIL;
57
+ * const identity = await identityService.findByDomainAndKey('email', email);
58
+ * if (!identity) throw new Error(`No user for ${email}`);
59
+ * const token = await authService.generateSessionToken(identity.userId);
60
+ * await authService.setAuthorization(identity.userId, token, 'session', 'authenticated');
61
+ * return { name: 'auth_id', value: token }; // 'auth_id' is cap-users' cookie name
62
+ *
63
+ * (remember to `await db.$disconnect()` in main() if you open a DB connection).
64
+ * ---------------------------------------------------------------------------
65
+ */
66
+ async function resolveAuthCookie(): Promise<{ name: string; value: string } | null> {
67
+ const name = process.env.UX_EVAL_AUTH_COOKIE_NAME;
68
+ const value = process.env.UX_EVAL_AUTH_COOKIE_VALUE;
69
+ if (name && value) return { name, value };
70
+ return null;
71
+ }
72
+
73
+ async function main() {
74
+ fs.mkdirSync(OUTPUT_DIR, { recursive: true });
75
+
76
+ const browser = await chromium.launch({ headless: true });
77
+ const context = await browser.newContext({ viewport: { width: 1280, height: 900 } });
78
+
79
+ const cookie = await resolveAuthCookie();
80
+ if (cookie) {
81
+ await context.addCookies([
82
+ { name: cookie.name, value: cookie.value, domain: new URL(BASE_URL).hostname, path: '/' },
83
+ ]);
84
+ }
85
+
86
+ const page = await context.newPage();
87
+ const url = `${BASE_URL}${targetPath}`;
88
+ const response = await page.goto(url, { waitUntil: 'networkidle', timeout: 15_000 });
89
+ if (!response || response.status() >= 400) {
90
+ throw new Error(`HTTP ${response?.status() ?? 'no response'} for ${url}`);
91
+ }
92
+
93
+ const outFile = path.join(OUTPUT_DIR, `${label}.png`);
94
+ await page.screenshot({ path: outFile, fullPage: true });
95
+ console.log(`[ux-eval] Saved ${outFile}`);
96
+
97
+ await browser.close();
98
+ }
99
+
100
+ main().catch((e) => {
101
+ console.error(e);
102
+ process.exit(1);
103
+ });
@@ -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.
@@ -0,0 +1,118 @@
1
+ ---
2
+ name: boop-web-design-handoff-workflow
3
+ description: "When the operator hands off a design package (a README plus a drop-in view template and CSS) for a redesigned surface, follow this process: read the README, VERIFY its claims against the real code, surface any buried decision to the operator, file an issue, run it as a sub-agent (branch for UAT or merge-on-clean-visual-check), then close out with knowledge integration and archival. The README is confident; confidence is not correctness — verify before you run."
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-handoff workflow — turn a design package into shipped, reviewed code.
13
+
14
+ A design handoff package is a high-leverage input: someone has already done the design work and grounded it against the real view-model, so the implementation is mostly mechanical. The leverage is real, but so is the risk, and the risk lives in one place — **the README's claims about the existing code.** A handoff that says "the service already computes X, just re-expose it" is trivial if true and a hidden architectural decision if false. The whole process turns on verifying those claims before any code is written.
15
+
16
+ ## What a handoff package looks like
17
+
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:
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.
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.
23
+ - **A CSS file** — appended into an existing style block; its header comment names the rules it supersedes.
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
+
28
+ ## The process
29
+
30
+ ### 1. Read the README in full
31
+
32
+ It is the spec — read it before touching anything. Note the "Data to surface" items (the view-model gaps), what it claims already exists, what it dropped and why, and the named follow-ons.
33
+
34
+ ### 2. Verify the README's claims against the real code (the load-bearing step)
35
+
36
+ The README makes assertions about the current code: "X already exists," "Y was retired," "field Z is right there on the type," "this renders today with zero controller changes." **Every one of those is a feasibility claim, and each must be checked against the actual code before you judge the package.** This is the step that separates a clean run from a buried decision.
37
+
38
+ - Confirm each "already exists" / "is right there" claim by reading the named symbol. (In a real run: a handoff claimed a field was "right there" — it was, on the underlying type, just dropped on the way to the view-model; trivial. Another claimed an upstream surface "already computed" a value — it deliberately did *not*, behind a documented boundary; that was a hidden architectural decision, not a re-expose.)
39
+ - Confirm the template's "renders today" claim: the fields it depends on now exist on the view-model (or are provided by the controller), and the genuinely-new fields are guarded with a fallback.
40
+ - Confirm any "retired" / "dropped" claim — a value the README says is gone may still be live and reusable.
41
+
42
+ When every claim checks out, it is a clean single sub-agent run. When a claim is wrong — especially when "re-expose this" turns out to mean "reverse a deliberate architectural boundary" — there is a design decision hiding inside the package.
43
+
44
+ ### 3. Judge feasibility for a single sub-agent run
45
+
46
+ Ask: is the work bounded with a clear path, or is there a judgment call hiding — an architectural decision, a premise error in the README, a spec ambiguity, a value that has to be derived rather than re-exposed? If the path is clear, it is one sub-agent run. If a decision is hiding, **do not let a sub-agent silently resolve it** (it will take the easy path — e.g. calling the function that reverses the boundary — because that is what "makes it work").
47
+
48
+ ### 4. Surface the verdict and any decision to the operator
49
+
50
+ Report: feasible or not, the scope, and the one question that is genuinely theirs — the buried architectural decision, framed at their altitude with a recommendation. Use `boop-product-design-thought-partnership` here. Get the call, then bake the decision into the issue so the sub-agent inherits it rather than re-deriving it. (Example: "derive the value from the shared source, not by duplicating — the README's 're-expose' framing assumed an upstream surface already had it, which it didn't.")
51
+
52
+ ### 5. File a bd issue (goal/approach/context shape)
53
+
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.
60
+
61
+ ### 6. Run it as a sub-agent
62
+
63
+ - **Isolated worktree on a non-default port.** The operator's dev server on the main checkout (e.g. `:3000`) is theirs — never touch it. Create a worktree branch off main (e.g. `work/<id>`), run the worktree's dev server on a different port (`:3001`/`:3002`/etc.), copy `.env` in if tests/capture need it (gitignored, don't commit).
64
+ - **Apply template + CSS**, removing the rules the CSS header names as superseded.
65
+ - **Make the view-model edits** per the (corrected) data list.
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.
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.
80
+ - **Disposition:**
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.
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.
83
+
84
+ ### 7. Close out (after the operator's UAT, or on a clean merge)
85
+
86
+ Often itself a sub-agent. Per `boop-product-design-knowledge-backfill`:
87
+
88
+ - **Merge** the branch to main; run the full suite green first.
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.
90
+ - **Archive the handoff package** (unedited) to `specs/history/<name>-handoff/`.
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.
92
+
93
+ ## When to use this vs the other workflows
94
+
95
+ | Situation | Workflow |
96
+ |---|---|
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** |
99
+ | A goal-framed improvement to an existing surface, no package | `boop-light-dev-workflow` |
100
+ | A backlog cleanup sweep of lingering P1/P2 items | `boop-project-mgmt-issue-sweep-workflow` |
101
+ | A feature with backend work driven by a spec | `boop-feature-dev-workflow` |
102
+
103
+ ## Anti-patterns
104
+
105
+ - **Running the package without verifying its claims.** The README reads as authoritative; authority is not accuracy. The verify-against-code step (2) is the whole game — skip it and you ship a hidden decision or hit a wall mid-run.
106
+ - **Letting a sub-agent resolve a buried architectural decision.** When "re-expose X" actually means "reverse a documented boundary," a sub-agent will reverse it because that is the path of least resistance. Surface it to the operator instead.
107
+ - **Auto-merging a substantial visual redesign without the operator's eye.** Branch for UAT. Reserve merge-on-clean-visual-check for quick, clear, additive fixes.
108
+ - **Lovingly reworking brittle tests on a surface in flux.** Trim them; the visual UAT is the safety net. Reworking assertions that the redesign is about to invalidate again is wasted effort.
109
+ - **`pkill`.** It can kill the operator's main-checkout server. Precise PID only, and never the main checkout's server.
110
+ - **Following the README's premise after the operator corrected it.** If step 4 settled a decision, the issue must say it supersedes the README, or the sub-agent inherits the wrong framing.
111
+ - **Taking a package's scope at face value against the full mockup.** A handoff package may be a *subset* of the designer's full vision: a package redesigned only the primary list and scoped the header and empty-state out as "unchanged," but the full mockup redesigned them too — the gap only surfaced at the operator's UAT. In step 2/4, confirm the package's scope against the full design mockup; a package that says parts are "unchanged / out of scope" may still leave the surface divergent from what the operator expects. Surface that at UAT.
112
+
113
+ ## Related
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.
116
+ - `boop-product-design-knowledge-backfill` — the archive + reference-integration discipline for the closeout.
117
+ - [`boop-web-web-ui-design`](../boop-web-web-ui-design/SKILL.md) — the visual-check-before-done rule the run leans on.
118
+ - `boop-product-design-thought-partnership` — surfacing the buried decision at the operator's altitude.