@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 +6 -1
- package/capability.json +26 -5
- package/package.json +1 -1
- package/scaffold/reference/web-ui.ref.md +29 -10
- package/scaffold/scripts/ux-eval-capture.ts +103 -0
- package/skills/boop-web-css/SKILL.md +129 -0
- package/skills/boop-web-design-handoff-bundle/SKILL.md +102 -0
- package/skills/boop-web-design-handoff-workflow/SKILL.md +118 -0
- package/skills/boop-web-testing/SKILL.md +180 -0
- package/skills/boop-web-web-ui-design/SKILL.md +27 -100
- package/skills/boop-web-website-dev-workflow/SKILL.md +248 -0
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
---
|
|
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 — 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
|
+
metadata:
|
|
5
|
+
category: test
|
|
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
|
+
# Testing: Express App (Web & APIs)
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
Parent skill: `boop-testing-testing` (philosophy, generic approach)
|
|
16
|
+
|
|
17
|
+
---
|
|
18
|
+
|
|
19
|
+
## Running Tests
|
|
20
|
+
|
|
21
|
+
```bash
|
|
22
|
+
npm test # Full suite
|
|
23
|
+
npx jest tests/services/foo.test.ts --no-coverage # Single file
|
|
24
|
+
npx jest tests/services/foo --no-coverage # Pattern match
|
|
25
|
+
npx jest tests/services/foo.test.ts -t "handles replay" # By test name
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
The focused forms are safe to reach for: the concurrency and per-worker memory settings live in `jest.config.js`, not in the `test` script, so `npx jest <path>` runs under the same settings the full suite does. Which of these you should be running on a given piece of work is a separate question — see "Which tests to run, and when" in the parent skill.
|
|
29
|
+
|
|
30
|
+
**Prerequisites**: integration tests run against the project's real backing services rather than mocks, so whatever the app talks to at runtime — the database first of all — has to be reachable from the machine running the tests. Which services those are depends on the capabilities the project has installed; its README and environment template are the authority.
|
|
31
|
+
|
|
32
|
+
## Test File Layout
|
|
33
|
+
|
|
34
|
+
```
|
|
35
|
+
tests/
|
|
36
|
+
helpers/ # shared setup, factories, harnesses (see below)
|
|
37
|
+
services/
|
|
38
|
+
foo.test.ts # Unit tests (mocked deps)
|
|
39
|
+
foo.integration.test.ts # Integration tests (real services)
|
|
40
|
+
controllers/
|
|
41
|
+
itemsAPI.test.ts # HTTP route tests
|
|
42
|
+
middleware/
|
|
43
|
+
utils/
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
- `.test.ts` for Jest auto-discovery. Keep the mocked tests and the real-service tests for the same subject in **separate files**: `jest.mock` is hoisted above the imports and applies to every test in the module, so one file cannot both stub a dependency and exercise the real one. `.integration.test.ts` is the conventional name for the half that uses the real services.
|
|
47
|
+
- Group by domain: `services/`, `controllers/`, `middleware/`
|
|
48
|
+
|
|
49
|
+
## Test Helpers
|
|
50
|
+
|
|
51
|
+
Shared test setup belongs in one place under `tests/`, not re-derived in each spec file: booting the app, creating the entities a test needs and tearing them down again, and harnesses that make an awkward dependency — a broker, a queue, a rendered template — drivable from a test.
|
|
52
|
+
|
|
53
|
+
**Read what the project already has before you write one.** These helpers are not scaffolded and cannot be, because they follow whichever capabilities and domain the project has. Every project grows its own set, and the shape varies from one shared module to a directory of purpose-built ones. Reuse what fits and extend it, rather than starting a private copy inside your test file. A helper used by exactly one test is not a helper — leave it in the test.
|
|
54
|
+
|
|
55
|
+
When you do add one, keep two properties. A factory returns a real, persisted entity rather than a hand-built object literal, so the test exercises the same shape production does. And whatever creates data owns its teardown, so one failed run does not poison the next.
|
|
56
|
+
|
|
57
|
+
## Integration Test Data Isolation
|
|
58
|
+
|
|
59
|
+
Integration tests share their backing services with every other integration test, and with whatever the last run left behind. Three habits keep them from interfering with each other:
|
|
60
|
+
|
|
61
|
+
- **Namespace whatever you create.** A per-run prefix (`_test/${Date.now()}`, a UUID) keeps parallel runs and repeat runs from colliding on unique constraints.
|
|
62
|
+
- **Track created records and remove them in `afterAll`**, each cleanup wrapped in its own `try/catch` so one failure doesn't strand the rest.
|
|
63
|
+
- **Scope instead of deleting where deletes are expensive.** Some stores — append-only logs, analytics columnstores, anything with asynchronous merge semantics — make row deletion slow or eventually-consistent. Isolate those by writing under a test-specific tenant or key and leave the rows in place.
|
|
64
|
+
|
|
65
|
+
## HTTP Route Tests
|
|
66
|
+
Test Express routes via **real HTTP requests** (`fetch()`) against a running local server. This exercises the full middleware chain (auth → controller → service → DB → response).
|
|
67
|
+
|
|
68
|
+
Why real HTTP over supertest: supertest skips TCP/listen and can mask middleware ordering or config bugs. Auth middleware is a critical path worth testing through, not around.
|
|
69
|
+
|
|
70
|
+
**Setup:**
|
|
71
|
+
```typescript
|
|
72
|
+
const BASE_URL = process.env.TEST_BASE_URL || 'http://localhost:3000';
|
|
73
|
+
const TEST_API_KEY = process.env.TEST_API_KEY; // however this project issues API keys
|
|
74
|
+
const authHeaders = { 'Authorization': `Bearer ${TEST_API_KEY}`, 'Content-Type': 'application/json' };
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
**What to cover:** auth rejection (401/403), request validation (400), the happy-path response shape, and the authorization boundary — that one account cannot reach another's data. If the project gates routes on anything further (feature flags, roles, plan tiers), cover the closed gate too, and assert the status code it actually returns rather than the one that seems right.
|
|
78
|
+
|
|
79
|
+
**Prereqs:** a local server running against a reachable database, a durable test account, and whatever credential the auth middleware expects (`TEST_API_KEY` by convention).
|
|
80
|
+
|
|
81
|
+
## Ad-Hoc Testing Tools
|
|
82
|
+
|
|
83
|
+
Jest is what runs in CI and before a commit. Alongside it, projects grow small CLIs for the things a test suite is the wrong shape for: generating fixture data, poking a live local server, checking what actually landed in the database.
|
|
84
|
+
|
|
85
|
+
Boop scaffolds one — `npm run db:query` (database capability) — for reading the local database without opening Prisma Studio. Anything beyond that is project-grown; `package.json` scripts and `src/scripts/` are where to look for what yours has, and `--help` on each is faster than reading its source.
|
|
86
|
+
|
|
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
|
+
|
|
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
|
+
|
|
175
|
+
## References
|
|
176
|
+
|
|
177
|
+
- `boop-testing-testing` — testing philosophy and the generic approach
|
|
178
|
+
- `boop-testing-tdd-light` — the red/green loop for a single change
|
|
179
|
+
- The project's own shared test setup under `tests/` — read it before adding another helper
|
|
180
|
+
- `jest.config.js` at the project root — test discovery, transform, and match config
|
|
@@ -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
|
|
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
|
-
##
|
|
24
|
-
|
|
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
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
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
|
-
###
|
|
87
|
-
|
|
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&b=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
|
-
<
|
|
117
|
-
<h1 class="
|
|
118
|
-
<p class="
|
|
119
|
-
</
|
|
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="
|
|
132
|
-
<label for="field-id"
|
|
133
|
-
<input id="field-id" name="fieldName" type="text"
|
|
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="
|
|
86
|
+
|
|
87
|
+
<div class="actions">
|
|
137
88
|
<button type="submit" class="btn-primary">Save</button>
|
|
138
|
-
<button type="button" class="btn-
|
|
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
|
|
148
|
-
<div class="
|
|
149
|
-
<h2 class="
|
|
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="
|
|
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
|
|
|
@@ -208,7 +135,7 @@ The flow:
|
|
|
208
135
|
|
|
209
136
|
1. **Build the CSS bundle.** `npm run tailwind:build` (or the project's equivalent). Semantic classes compiled via `@apply` do not exist in the served bundle until it is rebuilt.
|
|
210
137
|
2. **Restart the dev server if it caches the bundle at boot.** Many dev servers read the compiled CSS once at startup and a file watcher that ignores CSS will not pick up the rebuild.
|
|
211
|
-
3. **Capture the affected page** at desktop (1280px) and mobile (390px) widths
|
|
138
|
+
3. **Capture the affected page** at desktop (1280px) and mobile (390px) widths with `npm run ux:capture -- <path> <label>`, the Playwright capture script `cap-web` ships, or the project's equivalent.
|
|
212
139
|
4. **Read the captures.** Confirm the new fields appear, the layout reads coherently, and nothing collapsed or stacked in a way the structural tests cannot see.
|
|
213
140
|
|
|
214
141
|
If the capture surfaces a layout issue, fix it before closing the issue, even if all unit tests pass. A green test suite is not evidence that the surface looks right; only the capture is.
|