acuvo-code 0.2.0
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/CHANGELOG.md +328 -0
- package/ENTERPRISE.md +927 -0
- package/LICENSE +120 -0
- package/README.md +1245 -0
- package/ROADMAP.md +556 -0
- package/bin/acuvo-mcp.mjs +208 -0
- package/bin/acuvo.mjs +3108 -0
- package/lib/acceptance-consent.mjs +168 -0
- package/lib/acceptance.mjs +859 -0
- package/lib/account.mjs +226 -0
- package/lib/acuvo-dir.mjs +72 -0
- package/lib/acuvo-models.mjs +141 -0
- package/lib/apply-patch.mjs +570 -0
- package/lib/ask-user.mjs +173 -0
- package/lib/audit.mjs +530 -0
- package/lib/auto-lease.mjs +174 -0
- package/lib/background.mjs +842 -0
- package/lib/best-of.mjs +334 -0
- package/lib/board.mjs +232 -0
- package/lib/breaker.mjs +93 -0
- package/lib/budget.mjs +1375 -0
- package/lib/builtin-skills.mjs +135 -0
- package/lib/cache-floor.mjs +204 -0
- package/lib/chain.mjs +303 -0
- package/lib/changed-paths.mjs +84 -0
- package/lib/chat.mjs +434 -0
- package/lib/checkpoint.mjs +637 -0
- package/lib/child-lifetime.mjs +71 -0
- package/lib/cli-args.mjs +1255 -0
- package/lib/code-review.mjs +1382 -0
- package/lib/colour.mjs +82 -0
- package/lib/command.mjs +2847 -0
- package/lib/compact.mjs +1151 -0
- package/lib/completion.mjs +515 -0
- package/lib/creative-engines.mjs +779 -0
- package/lib/db-inspect.mjs +1624 -0
- package/lib/delete.mjs +111 -0
- package/lib/design-loop.mjs +570 -0
- package/lib/diff-preview.mjs +1044 -0
- package/lib/doctor.mjs +2139 -0
- package/lib/dropped.mjs +216 -0
- package/lib/edit-diagnostics.mjs +277 -0
- package/lib/edit.mjs +460 -0
- package/lib/env-file.mjs +250 -0
- package/lib/escalate.mjs +702 -0
- package/lib/evaluate.mjs +284 -0
- package/lib/fetch-text.mjs +952 -0
- package/lib/fleet-budget.mjs +256 -0
- package/lib/gh.mjs +1536 -0
- package/lib/git.mjs +1341 -0
- package/lib/github.mjs +261 -0
- package/lib/h2.mjs +194 -0
- package/lib/handoff.mjs +417 -0
- package/lib/hooks.mjs +626 -0
- package/lib/http-probe.mjs +907 -0
- package/lib/image-director.mjs +322 -0
- package/lib/image-edit.mjs +522 -0
- package/lib/imagegen.mjs +998 -0
- package/lib/interrupt.mjs +234 -0
- package/lib/learned.mjs +353 -0
- package/lib/lease-watch.mjs +115 -0
- package/lib/lease.mjs +868 -0
- package/lib/localize.mjs +834 -0
- package/lib/log-tail.mjs +1052 -0
- package/lib/login.mjs +157 -0
- package/lib/lsp.mjs +1613 -0
- package/lib/mcp-consent.mjs +377 -0
- package/lib/mcp-defaults.mjs +780 -0
- package/lib/mcp-server.mjs +1343 -0
- package/lib/mcp.mjs +1263 -0
- package/lib/media.mjs +1283 -0
- package/lib/memory-workspace.mjs +179 -0
- package/lib/model-json.mjs +132 -0
- package/lib/model-tier.mjs +171 -0
- package/lib/model.mjs +1445 -0
- package/lib/parallel.mjs +144 -0
- package/lib/perchance.mjs +210 -0
- package/lib/plan-coherence.mjs +1461 -0
- package/lib/plan-ledger.mjs +981 -0
- package/lib/plan.mjs +461 -0
- package/lib/policy.mjs +783 -0
- package/lib/prefix-order.mjs +38 -0
- package/lib/project-memory.mjs +127 -0
- package/lib/prompt.mjs +109 -0
- package/lib/python.mjs +862 -0
- package/lib/rcfile.mjs +853 -0
- package/lib/read-window.mjs +743 -0
- package/lib/refute-tools.mjs +34 -0
- package/lib/refute.mjs +806 -0
- package/lib/repl-driver.mjs +264 -0
- package/lib/repl.mjs +324 -0
- package/lib/replay.mjs +1218 -0
- package/lib/repo-map.mjs +1101 -0
- package/lib/report.mjs +419 -0
- package/lib/search-rank.mjs +99 -0
- package/lib/search.mjs +659 -0
- package/lib/secret-paths.mjs +54 -0
- package/lib/session.mjs +1017 -0
- package/lib/skills.mjs +703 -0
- package/lib/slash.mjs +356 -0
- package/lib/spawn-argv.mjs +1151 -0
- package/lib/spend.mjs +250 -0
- package/lib/steer.mjs +280 -0
- package/lib/stream.mjs +253 -0
- package/lib/stuck.mjs +712 -0
- package/lib/subagent.mjs +749 -0
- package/lib/terminal-graphics.mjs +171 -0
- package/lib/tool-prefix.mjs +226 -0
- package/lib/tool-shortlist.mjs +162 -0
- package/lib/tools.mjs +2333 -0
- package/lib/tsserver.mjs +423 -0
- package/lib/turn.mjs +5672 -0
- package/lib/untrusted-block.mjs +271 -0
- package/lib/verify-claim.mjs +299 -0
- package/lib/vision.mjs +330 -0
- package/lib/voice-task.mjs +561 -0
- package/lib/warm-provider.mjs +255 -0
- package/lib/websearch.mjs +401 -0
- package/lib/workspace.mjs +928 -0
- package/lib/write-approval.mjs +235 -0
- package/lib/write-many.mjs +162 -0
- package/package.json +62 -0
- package/scripts/bundle.mjs +768 -0
- package/scripts/cache-floor.mjs +176 -0
- package/scripts/machine.mjs +226 -0
- package/scripts/test.mjs +139 -0
- package/skills/accessibility.md +87 -0
- package/skills/acuvo-design-system.md +123 -0
- package/skills/animation.md +84 -0
- package/skills/api-design.md +82 -0
- package/skills/auth-and-sessions.md +78 -0
- package/skills/build-with-a-framework.md +101 -0
- package/skills/colour-and-contrast.md +112 -0
- package/skills/creative-engines.md +81 -0
- package/skills/css-layout.md +85 -0
- package/skills/data-and-charts.md +77 -0
- package/skills/debugging.md +76 -0
- package/skills/designing-by-looking.md +84 -0
- package/skills/error-handling.md +78 -0
- package/skills/forms-and-validation.md +93 -0
- package/skills/nextjs-app-router.md +75 -0
- package/skills/page-composition.md +103 -0
- package/skills/performance.md +77 -0
- package/skills/plan-before-building.md +52 -0
- package/skills/planning-and-delegating.md +72 -0
- package/skills/refactoring.md +70 -0
- package/skills/security-basics.md +76 -0
- package/skills/state-management.md +73 -0
- package/skills/supabase-multitenant.md +72 -0
- package/skills/typescript-strict.md +90 -0
- package/skills/typography.md +135 -0
- package/skills/verify-your-own-work.md +62 -0
- package/skills/web-app-quality.md +62 -0
- package/skills/working-in-the-background.md +64 -0
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: css-layout
|
|
3
|
+
description: Grid vs flex, spacing that scales, and building layouts that do not break at 320px
|
|
4
|
+
when: When laying out a page or component, or when something overflows or will not centre
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# CSS layout
|
|
8
|
+
|
|
9
|
+
## Grid or flex — the question answers itself
|
|
10
|
+
|
|
11
|
+
- **Flex** = one dimension. A row of buttons, a nav bar, a card's inner stack.
|
|
12
|
+
- **Grid** = two dimensions, or *"I want the container to decide the shape"*.
|
|
13
|
+
Page layouts, card galleries, anything with rows AND columns.
|
|
14
|
+
|
|
15
|
+
If you are writing `flex-wrap` plus width percentages plus negative margins to
|
|
16
|
+
make a gallery, you want grid:
|
|
17
|
+
|
|
18
|
+
```css
|
|
19
|
+
.gallery {
|
|
20
|
+
display: grid;
|
|
21
|
+
grid-template-columns: repeat(auto-fill, minmax(260px, 1fr));
|
|
22
|
+
gap: 1.5rem;
|
|
23
|
+
}
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
⭐ That is a responsive gallery with **no media queries at all** — it reflows on
|
|
27
|
+
available width, which is what you actually meant.
|
|
28
|
+
|
|
29
|
+
## Use `gap`, not margins between children
|
|
30
|
+
|
|
31
|
+
`gap` puts space *between* items and never before the first or after the last.
|
|
32
|
+
`margin-right` on every child plus `:last-child { margin-right: 0 }` is the old
|
|
33
|
+
workaround, and it is still wrong when the row wraps.
|
|
34
|
+
|
|
35
|
+
## ⚠️ Test at 320px. That is where layouts break.
|
|
36
|
+
|
|
37
|
+
Not 1440. The failure modes are always the same:
|
|
38
|
+
|
|
39
|
+
- a fixed `width` that should be `max-width`
|
|
40
|
+
- a long unbroken string (a URL, an email) forcing horizontal scroll —
|
|
41
|
+
`overflow-wrap: anywhere` on the container
|
|
42
|
+
- a table with no `overflow-x: auto` wrapper
|
|
43
|
+
- padding that eats the whole viewport
|
|
44
|
+
|
|
45
|
+
⭐ **The body must never scroll horizontally.** Wide content scrolls inside its
|
|
46
|
+
own container, not the page.
|
|
47
|
+
|
|
48
|
+
## Centring
|
|
49
|
+
|
|
50
|
+
```css
|
|
51
|
+
.center { display: grid; place-items: center; } /* both axes, done */
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
For a page column: `margin-inline: auto` with a `max-width`. Do not use
|
|
55
|
+
absolute positioning and transforms for something the layout can do.
|
|
56
|
+
|
|
57
|
+
## Spacing on a scale, not by feel
|
|
58
|
+
|
|
59
|
+
Pick a scale (4 / 8 / 12 / 16 / 24 / 32 / 48) and use only those values. A
|
|
60
|
+
layout with `13px` here and `17px` there looks unresolved even when nobody can
|
|
61
|
+
say why. Put them in custom properties and reference those.
|
|
62
|
+
|
|
63
|
+
## The modern tools, in order of how often they help
|
|
64
|
+
|
|
65
|
+
1. **`clamp()`** — fluid type and spacing without breakpoints:
|
|
66
|
+
`font-size: clamp(1.5rem, 4vw, 3rem)`
|
|
67
|
+
2. **Container queries** — a component that adapts to ITS box rather than the
|
|
68
|
+
viewport, which is what a card in a sidebar actually needs:
|
|
69
|
+
`@container (min-width: 400px) { … }`
|
|
70
|
+
3. **Logical properties** — `padding-inline`, `margin-block`. They do the right
|
|
71
|
+
thing in every writing direction and cost nothing to adopt.
|
|
72
|
+
4. **`aspect-ratio`** — reserve space for media and stop the reflow.
|
|
73
|
+
|
|
74
|
+
## ⚠️ z-index is not a ladder to climb
|
|
75
|
+
|
|
76
|
+
`z-index: 9999` means someone lost an argument with the stacking context. Define
|
|
77
|
+
a small set of layers (base, dropdown, modal, toast) as custom properties and
|
|
78
|
+
use those names. And remember z-index only applies to positioned elements.
|
|
79
|
+
|
|
80
|
+
## Do not fight the flow
|
|
81
|
+
|
|
82
|
+
`position: absolute` for something that should be in the document, `height:
|
|
83
|
+
100vh` on a mobile browser whose chrome hides and reappears (use `100dvh`),
|
|
84
|
+
`!important` to beat your own selector — each is a signal that the layout is
|
|
85
|
+
being forced rather than described.
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: data-and-charts
|
|
3
|
+
description: Picking a chart that answers the question, and the defaults that make a dashboard readable
|
|
4
|
+
when: When building a dashboard, a report, or anything that shows numbers to a person
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# Data and charts
|
|
8
|
+
|
|
9
|
+
## Pick the chart from the QUESTION, not from the data
|
|
10
|
+
|
|
11
|
+
| the question | the chart |
|
|
12
|
+
|---|---|
|
|
13
|
+
| how has this changed over time? | line |
|
|
14
|
+
| which of these is biggest? | horizontal bar |
|
|
15
|
+
| what is this made of? | stacked bar — **not** a pie |
|
|
16
|
+
| are these two related? | scatter |
|
|
17
|
+
| what is the single number right now? | just print the number, large |
|
|
18
|
+
|
|
19
|
+
⭐ The last row is the one most often over-built. A metric that is one number
|
|
20
|
+
should be one number. A gauge, a donut and a sparkline around it are decoration
|
|
21
|
+
that makes the number harder to read.
|
|
22
|
+
|
|
23
|
+
## ⚠️ Never a pie chart for more than five things
|
|
24
|
+
|
|
25
|
+
People compare angles badly. Past about five slices a pie becomes a legend with
|
|
26
|
+
a picture attached, and the reader has to look things up. A horizontal bar chart
|
|
27
|
+
sorted descending answers "which is biggest" instantly and takes the same space.
|
|
28
|
+
|
|
29
|
+
Never a pie for values that do not sum to a meaningful whole.
|
|
30
|
+
|
|
31
|
+
## Sort bars, unless the order means something
|
|
32
|
+
|
|
33
|
+
Alphabetical is the wrong default — it scatters the answer. Sort descending by
|
|
34
|
+
value. The exceptions are categories with an inherent order: days of the week,
|
|
35
|
+
age bands, survey responses from "never" to "always".
|
|
36
|
+
|
|
37
|
+
## The axis rules that change conclusions
|
|
38
|
+
|
|
39
|
+
- **Bar charts start at zero.** Truncating the axis exaggerates differences and
|
|
40
|
+
is the single most common way a chart misleads.
|
|
41
|
+
- **Line charts need not** start at zero — the shape of the change is the point.
|
|
42
|
+
- Label the units. "Revenue" is not a unit; "Revenue (A$, ex GST)" is.
|
|
43
|
+
|
|
44
|
+
## ⚠️ Show the empty and loading states
|
|
45
|
+
|
|
46
|
+
A dashboard with no data yet is the state every user sees FIRST, and it is
|
|
47
|
+
usually the one nobody designed. Say what will appear and why it is empty:
|
|
48
|
+
|
|
49
|
+
```
|
|
50
|
+
✗ (a blank rectangle)
|
|
51
|
+
✓ "No calls yet. This fills in once your first campaign runs."
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
And distinguish **empty** from **failed to load** — see `error-handling`. A
|
|
55
|
+
chart that renders zero because the request 500'd is a lie told in pictures.
|
|
56
|
+
|
|
57
|
+
## Numbers people can read
|
|
58
|
+
|
|
59
|
+
- Round to the precision that matters: `A$1,284` not `A$1284.3891`
|
|
60
|
+
- Thousands separators, always
|
|
61
|
+
- Percentages need a base: "12% (of 340 calls)"
|
|
62
|
+
- Dates in a form with no ambiguity: `18 Aug 2026`, never `08/09/26`
|
|
63
|
+
|
|
64
|
+
## Colour carries meaning or it carries nothing
|
|
65
|
+
|
|
66
|
+
Use one accent for the series that matters and grey for context. A chart where
|
|
67
|
+
every series is a different bright colour has told the reader that everything is
|
|
68
|
+
equally important, which is never true.
|
|
69
|
+
|
|
70
|
+
⚠️ Never encode meaning in colour ALONE — about 1 in 12 men cannot separate red
|
|
71
|
+
from green. Label the line, or vary the shape.
|
|
72
|
+
|
|
73
|
+
## Tables are underrated
|
|
74
|
+
|
|
75
|
+
If the reader's real question is "what exactly was this number", a sorted table
|
|
76
|
+
with aligned right-hand numerals beats every chart. Charts are for shape;
|
|
77
|
+
tables are for values.
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: debugging
|
|
3
|
+
description: Read the error literally, doubt the check before the code, and change one thing at a time
|
|
4
|
+
when: When something does not work, a test fails, or a fix did not take effect
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# Debugging
|
|
8
|
+
|
|
9
|
+
## ⚠️⚠️ Read the error literally. It is usually telling the truth.
|
|
10
|
+
|
|
11
|
+
`x is not a function` means `x` is not a function. Not "the import is
|
|
12
|
+
circular", not "the bundler is confused" — **print `x` and look at it.** Most
|
|
13
|
+
time lost to debugging is spent on a theory formed before reading the message.
|
|
14
|
+
|
|
15
|
+
Ask, in this order:
|
|
16
|
+
|
|
17
|
+
1. What does the message actually say?
|
|
18
|
+
2. What is the value it is complaining about? Print it.
|
|
19
|
+
3. Which line? Read that line, not the one you assume it means.
|
|
20
|
+
|
|
21
|
+
## ⚠️⚠️ Doubt the check before you doubt the code
|
|
22
|
+
|
|
23
|
+
When a test fails on code you believe is correct, one of two things is true, and
|
|
24
|
+
the expensive mistake is assuming it is the first:
|
|
25
|
+
|
|
26
|
+
- the code is wrong, **or**
|
|
27
|
+
- **the check is wrong** — and a check that fails correct work is worse than no
|
|
28
|
+
check, because it sends you to rewrite something that already worked.
|
|
29
|
+
|
|
30
|
+
Prove which: **break the code deliberately and see whether the check notices.**
|
|
31
|
+
A check that stays green when you break the thing it guards was never testing
|
|
32
|
+
it. See `verify-your-own-work`.
|
|
33
|
+
|
|
34
|
+
## The fix that "did not work"
|
|
35
|
+
|
|
36
|
+
Before concluding a fix is wrong, prove it ran:
|
|
37
|
+
|
|
38
|
+
- Are you editing the file that is actually loaded? (Two files with the same
|
|
39
|
+
name in different directories is the classic.)
|
|
40
|
+
- Did the build/dev server pick it up? Stale cache?
|
|
41
|
+
- Is the branch you edited the branch you ran?
|
|
42
|
+
- Is something later overwriting it?
|
|
43
|
+
|
|
44
|
+
⭐ Put a deliberate marker in — `throw new Error('REACHED')` — and confirm you
|
|
45
|
+
see it. If you do not, the problem was never the logic.
|
|
46
|
+
|
|
47
|
+
## Change one thing at a time
|
|
48
|
+
|
|
49
|
+
Three changes then a test tells you nothing about which mattered — and if it
|
|
50
|
+
still fails you now have four suspects. One change, one observation.
|
|
51
|
+
|
|
52
|
+
Keep the last known-good state reachable so you can always get back.
|
|
53
|
+
|
|
54
|
+
## Bisect when you have no theory
|
|
55
|
+
|
|
56
|
+
If it worked before and does not now, the fastest route is not thinking harder,
|
|
57
|
+
it is **halving the search space**: `git bisect`, comment out half the file,
|
|
58
|
+
disable half the config. Two or three halvings usually beats an hour of staring.
|
|
59
|
+
|
|
60
|
+
## ⚠️ Reproduce it before you fix it
|
|
61
|
+
|
|
62
|
+
A bug you cannot reproduce is a bug you cannot confirm you fixed. If it happens
|
|
63
|
+
"sometimes", find the input that makes it happen every time first — that
|
|
64
|
+
usually IS the diagnosis.
|
|
65
|
+
|
|
66
|
+
## Say what you know and what you assume
|
|
67
|
+
|
|
68
|
+
"The request 404s" is an observation. "The route is not registered" is a theory.
|
|
69
|
+
Keeping the two apart is what stops you fixing a route that was fine while the
|
|
70
|
+
real fault — a typo in the URL — sits unexamined.
|
|
71
|
+
|
|
72
|
+
## When you are truly stuck
|
|
73
|
+
|
|
74
|
+
Explain the problem out loud from the beginning, including the parts you are
|
|
75
|
+
sure about. The wrong assumption is almost always in the part you did not think
|
|
76
|
+
was worth saying.
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: designing-by-looking
|
|
3
|
+
description: Render it and LOOK — the see_page loop, not better CSS written blind
|
|
4
|
+
when: After building or restyling any page, component or app, and before telling anyone it is done
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# Designing By Looking
|
|
8
|
+
|
|
9
|
+
## ⚠️⚠️ You cannot judge a page you have not seen
|
|
10
|
+
|
|
11
|
+
You have `see_page`. It renders an HTML file in a real browser and returns a
|
|
12
|
+
screenshot **plus measured problems** — invisible text, overflow, cramped
|
|
13
|
+
sections. Use it.
|
|
14
|
+
|
|
15
|
+
**A page you have not looked at is not finished, it is only written.** Markup
|
|
16
|
+
that reads correctly is routinely broken on screen: white text on white,
|
|
17
|
+
a section 500px tall holding one line, a row that overflows on the narrow
|
|
18
|
+
viewport, a delete button stretched across the full width.
|
|
19
|
+
|
|
20
|
+
Those are the exact defects that shipped in real builds here. Every one of them
|
|
21
|
+
is invisible in the source and obvious in a screenshot.
|
|
22
|
+
|
|
23
|
+
## ⭐⭐ The loop, in order
|
|
24
|
+
|
|
25
|
+
1. **Build** the page.
|
|
26
|
+
2. **`see_page`** on the file you just wrote.
|
|
27
|
+
3. **Read the findings first, then look at the screenshot.** The findings are
|
|
28
|
+
measured; your impression is not.
|
|
29
|
+
4. **Fix the specific thing.** One at a time.
|
|
30
|
+
5. **Look again.** A fix you did not re-check is a hope.
|
|
31
|
+
|
|
32
|
+
⭐ **Repeat until the screenshot is good, not until the code looks right.** The
|
|
33
|
+
loop is the method. Better prompts are not the method — a page improves because
|
|
34
|
+
someone looked at it, noticed a real defect, and fixed that defect.
|
|
35
|
+
|
|
36
|
+
## ⚠️ Do not fix blind
|
|
37
|
+
|
|
38
|
+
The failure mode is reading your own CSS, forming a theory about what it must
|
|
39
|
+
look like, and rewriting it without rendering. That is how a "fix" makes the
|
|
40
|
+
page worse and nobody notices for three rounds.
|
|
41
|
+
|
|
42
|
+
If `see_page` reports overflow, you do not need a theory about why. Look at the
|
|
43
|
+
screenshot, find the element that is too wide, and constrain that element.
|
|
44
|
+
|
|
45
|
+
## ⭐ What to actually check when you look
|
|
46
|
+
|
|
47
|
+
Ask these in this order, because they fail in this order:
|
|
48
|
+
|
|
49
|
+
1. **Is every piece of text visible?** Contrast, and not clipped.
|
|
50
|
+
2. **Does anything overflow?** Especially tables, code blocks, long words and
|
|
51
|
+
button rows.
|
|
52
|
+
3. **Is there dead space?** A section far taller than its content reads as
|
|
53
|
+
broken, not airy.
|
|
54
|
+
4. **Is there ONE clear first thing to look at?** If everything is the same
|
|
55
|
+
weight, nothing is.
|
|
56
|
+
5. **Do the controls look like controls?** A full-width bar is not a button.
|
|
57
|
+
6. **Is there a real `<h1>`?** Pages ship without one constantly.
|
|
58
|
+
|
|
59
|
+
## ⭐ `read_image` for anything you generated
|
|
60
|
+
|
|
61
|
+
`see_page` is for pages. For an image you made with `generate_image`, use
|
|
62
|
+
**`read_image`** — it returns a factual description, and you can ask it a
|
|
63
|
+
question about the file.
|
|
64
|
+
|
|
65
|
+
**Generating an image and never looking at it is the same mistake as shipping an
|
|
66
|
+
unrendered page.** Text inside generated images is misspelled often enough that
|
|
67
|
+
it must be checked every time, not assumed.
|
|
68
|
+
|
|
69
|
+
## ⚠️ Looking costs tokens — so look deliberately, not constantly
|
|
70
|
+
|
|
71
|
+
A screenshot is charged as image tokens (roughly one token per 750 pixels, with
|
|
72
|
+
the long edge capped at 1568). That is cheap, but it is not free.
|
|
73
|
+
|
|
74
|
+
⭐ **Look after a meaningful change, not after every line.** Build the whole
|
|
75
|
+
page, then look. Fix the three findings, then look again. Two or three passes
|
|
76
|
+
usually settles a page; ten means you are guessing between them.
|
|
77
|
+
|
|
78
|
+
## The bar
|
|
79
|
+
|
|
80
|
+
Not "does this render". The bar is: **does it look like someone designed it,
|
|
81
|
+
or does it look like a template with content dropped in?**
|
|
82
|
+
|
|
83
|
+
Related skills: `acuvo-design-system`, `page-composition`, `typography`,
|
|
84
|
+
`colour-and-contrast`, `verify-your-own-work`.
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: error-handling
|
|
3
|
+
description: Empty is not unreadable is not never-instrumented — and why a swallowed error is the expensive one
|
|
4
|
+
when: When writing a catch block, a default value, or anything that reports a result
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# Errors
|
|
8
|
+
|
|
9
|
+
## ⚠️⚠️ The house rule: empty ≠ unreadable ≠ never-instrumented
|
|
10
|
+
|
|
11
|
+
Three different facts get flattened into one innocent-looking value:
|
|
12
|
+
|
|
13
|
+
| what happened | what it means | what it must NOT return |
|
|
14
|
+
|---|---|---|
|
|
15
|
+
| we looked, there is nothing | **empty** | — |
|
|
16
|
+
| we looked and could not read it | **failure** | `[]` |
|
|
17
|
+
| nobody ever measured this | **unknown** | `0`, `false`, "fine" |
|
|
18
|
+
|
|
19
|
+
Returning `[]` for "the query failed" is the bug that hides for months, because
|
|
20
|
+
every caller downstream reads it as "there are none" and behaves perfectly
|
|
21
|
+
sensibly on a false premise.
|
|
22
|
+
|
|
23
|
+
```js
|
|
24
|
+
✗ try { return await load(); } catch { return []; }
|
|
25
|
+
✓ try { return { ok: true, rows: await load() }; }
|
|
26
|
+
catch (e) { return { ok: false, error: String(e) }; }
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
The same rule for a check: a check that could not run reports **unknown**, never
|
|
30
|
+
**pass**. "No problems found" is a real claim and has to be earned.
|
|
31
|
+
|
|
32
|
+
## A caught error you do nothing with is worse than a crash
|
|
33
|
+
|
|
34
|
+
A crash tells you where and when. A swallowed error produces a program that is
|
|
35
|
+
subtly wrong later, somewhere else, for reasons nobody can trace.
|
|
36
|
+
|
|
37
|
+
```js
|
|
38
|
+
✗ catch (e) {}
|
|
39
|
+
✗ catch (e) { console.log('oops'); }
|
|
40
|
+
✓ catch (e) { console.error('loading invoices failed', e); showError(e); }
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
⭐ If you genuinely want to ignore one, say why in a comment. That comment is
|
|
44
|
+
the difference between a decision and an oversight:
|
|
45
|
+
|
|
46
|
+
```js
|
|
47
|
+
catch { /* an observation must never break the thing it observes */ }
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
## Error messages are read by two audiences
|
|
51
|
+
|
|
52
|
+
- **The user** needs to know what to do: *"Could not save — you are offline.
|
|
53
|
+
Your changes are kept, try again."*
|
|
54
|
+
- **You** need to know what broke: the stack, the input, the identifiers.
|
|
55
|
+
|
|
56
|
+
Do not show the user a stack trace, and do not log only "error occurred".
|
|
57
|
+
|
|
58
|
+
## ⚠️ Fail the whole operation, not half of it
|
|
59
|
+
|
|
60
|
+
A loop that catches per item and continues quietly produces a half-written
|
|
61
|
+
result nobody knows is half-written. Either:
|
|
62
|
+
|
|
63
|
+
- collect the failures and report them together at the end, or
|
|
64
|
+
- stop on the first one.
|
|
65
|
+
|
|
66
|
+
Silently skipping item 7 of 200 is the option that costs the most later.
|
|
67
|
+
|
|
68
|
+
## Errors at the boundary
|
|
69
|
+
|
|
70
|
+
Every network call has three outcomes, not two: **success**, **failure**, and
|
|
71
|
+
**still going**. A UI that only models the first two shows nothing at all while
|
|
72
|
+
the request is in flight, and users click again.
|
|
73
|
+
|
|
74
|
+
## ⚠️ Never let an error string tell you what to do
|
|
75
|
+
|
|
76
|
+
*"try again"* in a message is not instruction — it is the remote end's guess. A
|
|
77
|
+
retry loop that trusts it can spend an entire budget on a service that is down.
|
|
78
|
+
Retry on a policy you chose, with a ceiling you chose.
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: forms-and-validation
|
|
3
|
+
description: Building a form that actually works — validation timing, error copy, and the states people forget
|
|
4
|
+
when: Whenever the thing you are building contains an input, a form, or a submit button
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# Forms
|
|
8
|
+
|
|
9
|
+
The most common thing a generated app contains, and the most commonly half-built.
|
|
10
|
+
|
|
11
|
+
## The four states every form has
|
|
12
|
+
|
|
13
|
+
A form is not "empty" and "filled". It is:
|
|
14
|
+
|
|
15
|
+
1. **empty** — nothing typed, no errors shown yet
|
|
16
|
+
2. **invalid** — the user has been told what is wrong, in words
|
|
17
|
+
3. **submitting** — the button is disabled and says so
|
|
18
|
+
4. **done** — the user can see it worked
|
|
19
|
+
|
|
20
|
+
Skipping 3 is how a form gets double-submitted. Skipping 4 is how a user fills
|
|
21
|
+
it in twice because nothing appeared to happen.
|
|
22
|
+
|
|
23
|
+
## ⚠️ Validation timing is the difference between helpful and hostile
|
|
24
|
+
|
|
25
|
+
- **Do not validate while the user is still typing the first time.** Turning a
|
|
26
|
+
field red at the third character of an email address is punishing someone for
|
|
27
|
+
not having finished.
|
|
28
|
+
- **Validate on blur**, and again on submit.
|
|
29
|
+
- **Once a field has errored, re-validate as they type** — so the error clears
|
|
30
|
+
the moment they fix it, rather than after they leave the field again.
|
|
31
|
+
|
|
32
|
+
## Error messages say what to do
|
|
33
|
+
|
|
34
|
+
```
|
|
35
|
+
✗ "Invalid input"
|
|
36
|
+
✗ "Error: field required"
|
|
37
|
+
✓ "Enter an email address — we send the confirmation there"
|
|
38
|
+
✓ "Password needs 8 characters or more"
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
Put the message next to the field, not in a banner at the top. A banner cannot
|
|
42
|
+
say *which* of nine fields is wrong.
|
|
43
|
+
|
|
44
|
+
## ⚠️⚠️ Never trust the client
|
|
45
|
+
|
|
46
|
+
Client validation is a convenience for the user. It is not a check. Anything
|
|
47
|
+
that matters is validated again on the server, because the client is a program
|
|
48
|
+
the user controls.
|
|
49
|
+
|
|
50
|
+
Marking a field `required` in HTML and nowhere else means the first person to
|
|
51
|
+
open devtools sends whatever they like.
|
|
52
|
+
|
|
53
|
+
## Accessibility, which is three attributes
|
|
54
|
+
|
|
55
|
+
```html
|
|
56
|
+
<label for="email">Email</label>
|
|
57
|
+
<input id="email" name="email" type="email" aria-describedby="email-error">
|
|
58
|
+
<p id="email-error" role="alert">Enter an email address</p>
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
A placeholder is not a label — it disappears exactly when the user needs it,
|
|
62
|
+
and screen readers do not reliably announce it.
|
|
63
|
+
|
|
64
|
+
## Use the right input type
|
|
65
|
+
|
|
66
|
+
`type="email"`, `type="tel"`, `type="number"`, `inputmode="numeric"`. On a
|
|
67
|
+
phone this changes the keyboard that appears, which is a real difference in
|
|
68
|
+
whether the form gets completed.
|
|
69
|
+
|
|
70
|
+
## The submit handler
|
|
71
|
+
|
|
72
|
+
```js
|
|
73
|
+
form.addEventListener('submit', async (e) => {
|
|
74
|
+
e.preventDefault(); // or the page reloads and the work is lost
|
|
75
|
+
if (busy) return; // guard the double-click
|
|
76
|
+
busy = true;
|
|
77
|
+
button.disabled = true;
|
|
78
|
+
button.textContent = 'Saving…';
|
|
79
|
+
try {
|
|
80
|
+
await save(new FormData(form));
|
|
81
|
+
showSuccess();
|
|
82
|
+
} catch (err) {
|
|
83
|
+
showError(err); // tell the user, do not swallow it
|
|
84
|
+
} finally {
|
|
85
|
+
busy = false; // ⚠️ in finally, or one failure freezes the form forever
|
|
86
|
+
button.disabled = false;
|
|
87
|
+
button.textContent = 'Save';
|
|
88
|
+
}
|
|
89
|
+
});
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
⚠️ The `finally` is the part that gets left out. Without it, a single failed
|
|
93
|
+
request leaves the button disabled and the user with no way to retry.
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: nextjs-app-router
|
|
3
|
+
description: Next.js App Router — server vs client components, data loading, the mistakes that cost hours
|
|
4
|
+
when: Working in a Next.js project, or any app/ directory with page.tsx
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# Next.js App Router
|
|
8
|
+
|
|
9
|
+
## The rule everything else follows
|
|
10
|
+
|
|
11
|
+
**Server components fetch. Client components render.** A component is a server
|
|
12
|
+
component unless it says `'use client'`.
|
|
13
|
+
|
|
14
|
+
Put every read in the page (server), pass results down as props. A child that
|
|
15
|
+
fetches forms its own opinion about a failure the page already has one sentence
|
|
16
|
+
for — and its failure renders as an empty state, which is indistinguishable from
|
|
17
|
+
"nothing happened".
|
|
18
|
+
|
|
19
|
+
```tsx
|
|
20
|
+
// page.tsx — server. Reads, decides, passes down.
|
|
21
|
+
export default async function Page({ params }: { params: { id: string } }) {
|
|
22
|
+
const { rows, error } = await read(params.id);
|
|
23
|
+
if (error) return <Unreadable why={error} />; // ONE place says "broken"
|
|
24
|
+
return <Detail rows={rows} />; // the child only renders
|
|
25
|
+
}
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
⚠️ `'use client'` is contagious downward. A client component's children are all
|
|
29
|
+
client. Push it to the leaf that actually needs interactivity — a single
|
|
30
|
+
`<LikeButton />`, not the page.
|
|
31
|
+
|
|
32
|
+
## What forces 'use client'
|
|
33
|
+
|
|
34
|
+
`useState` · `useEffect` · `useRef` · event handlers (`onClick`) · browser APIs
|
|
35
|
+
(`window`, `localStorage`, `IntersectionObserver`). Nothing else.
|
|
36
|
+
|
|
37
|
+
## Params are async in Next 15+
|
|
38
|
+
|
|
39
|
+
```tsx
|
|
40
|
+
export default async function Page({ params }: { params: Promise<{ id: string }> }) {
|
|
41
|
+
const { id } = await params;
|
|
42
|
+
}
|
|
43
|
+
```
|
|
44
|
+
⚠️ Getting this wrong gives a confusing runtime error, not a type error.
|
|
45
|
+
|
|
46
|
+
## Caching, which is where the hours go
|
|
47
|
+
|
|
48
|
+
- `export const dynamic = 'force-dynamic'` when the page must read fresh data
|
|
49
|
+
every request. Without it you can ship a dashboard that shows build-time data
|
|
50
|
+
forever and looks merely "stale" rather than broken.
|
|
51
|
+
- `revalidate = N` for periodic refresh.
|
|
52
|
+
- Mutations use a server action or route handler, then `revalidatePath()`.
|
|
53
|
+
|
|
54
|
+
## Common failures, in order of how often they bite
|
|
55
|
+
|
|
56
|
+
1. **A server component imported into a client component** — everything below
|
|
57
|
+
becomes client and the fetch breaks. Pass it as `children` instead.
|
|
58
|
+
2. **`useSearchParams` without `<Suspense>`** — build error at the very end.
|
|
59
|
+
3. **Env vars.** Only `NEXT_PUBLIC_*` reach the browser. A secret read in a
|
|
60
|
+
client component is `undefined`, not an error.
|
|
61
|
+
4. **Hydration mismatch** — `Date.now()`, `Math.random()` or `localStorage` in
|
|
62
|
+
render. Move to `useEffect`.
|
|
63
|
+
5. **Images** — `next/image` needs `width`/`height` or `fill` + a positioned
|
|
64
|
+
parent.
|
|
65
|
+
|
|
66
|
+
## Route handlers
|
|
67
|
+
|
|
68
|
+
```ts
|
|
69
|
+
export async function POST(req: Request) {
|
|
70
|
+
const body = await req.json(); // throws on bad JSON — catch it
|
|
71
|
+
return Response.json({ ok: true }, { status: 200 });
|
|
72
|
+
}
|
|
73
|
+
```
|
|
74
|
+
Always validate the body. Never trust `body.tenantId` from the client to decide
|
|
75
|
+
what data to return — derive it from the authenticated session.
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: page-composition
|
|
3
|
+
description: Visual hierarchy, whitespace and section anatomy — why a technically correct page still looks generated
|
|
4
|
+
when: Laying out any marketing page, landing page or app screen, or when output looks flat and evenly grey
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# Composition
|
|
8
|
+
|
|
9
|
+
`css-layout` covers the mechanics — grid, flex, gap, breakpoints. This is the
|
|
10
|
+
layer above it: **where things go and how big they are relative to each other.**
|
|
11
|
+
A page can be flawlessly implemented and still look generated, and this is
|
|
12
|
+
almost always why.
|
|
13
|
+
|
|
14
|
+
## ⚠️ The failure has a shape: everything is the same size
|
|
15
|
+
|
|
16
|
+
Generated pages tend toward uniformity — three equal cards, then three more,
|
|
17
|
+
each section the same height, the same padding, the same weight. Nothing
|
|
18
|
+
dominates, so the eye has nowhere to land, and the page reads as a list rather
|
|
19
|
+
than a designed thing.
|
|
20
|
+
|
|
21
|
+
⭐ **Every screen needs ONE dominant element.** In a hero it is the headline. In
|
|
22
|
+
a dashboard it is the number that matters. Make it dominant by **scale
|
|
23
|
+
contrast**, not decoration: 3–4× the body size, not a border and a badge.
|
|
24
|
+
|
|
25
|
+
## Whitespace is the material, not the gap
|
|
26
|
+
|
|
27
|
+
- **Space between sections should be 3–5× the space inside them.** That single
|
|
28
|
+
ratio is most of what separates a designed page from a template. If section
|
|
29
|
+
padding is `24px`, the space between sections is `96–120px`, not `32px`.
|
|
30
|
+
- ⚠️ **Generated pages are almost always too tight.** When unsure, add more.
|
|
31
|
+
Cramped reads as cheap; generous reads as confident.
|
|
32
|
+
- Group by proximity. A label 4px from its input and 40px from the next field
|
|
33
|
+
needs no box drawn around it. **Proximity beats borders** — reach for space
|
|
34
|
+
before reaching for a card.
|
|
35
|
+
|
|
36
|
+
```css
|
|
37
|
+
section { padding-block: clamp(4rem, 10vw, 8rem); }
|
|
38
|
+
.section-inner { display: grid; gap: 1.5rem; max-width: 72rem; margin-inline: auto; padding-inline: 1.5rem; }
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
## Break the symmetry, deliberately
|
|
42
|
+
|
|
43
|
+
Three equal columns is the default and the default is what looks generated.
|
|
44
|
+
|
|
45
|
+
- **Asymmetric splits read as designed**: 7/5, 8/4, 2fr/3fr. A hero with text
|
|
46
|
+
at 55% and an image at 45% has tension; 50/50 has none.
|
|
47
|
+
- **Vary the rhythm down the page**: full-bleed, then contained, then two-up,
|
|
48
|
+
then contained. Four identical stacked sections is the problem.
|
|
49
|
+
- ⚠️ **Do not centre everything.** Centred headline, centred paragraph, centred
|
|
50
|
+
button, section after section — it is the most common generated-page shape
|
|
51
|
+
there is. Left-align body content; centre only short, deliberate moments.
|
|
52
|
+
|
|
53
|
+
## Landing page anatomy — what earns its place
|
|
54
|
+
|
|
55
|
+
In order. Each section answers one question and then gets out of the way.
|
|
56
|
+
|
|
57
|
+
1. **Hero** — *what is this and who is it for?* One headline (a claim, not a
|
|
58
|
+
slogan), one sentence of support, one primary action. Optionally one real
|
|
59
|
+
image. ⚠️ Not three buttons, not a feature list.
|
|
60
|
+
2. **Proof** — *why believe you?* Logos, a number, a testimonial with a real
|
|
61
|
+
name and role. Immediately after the hero, because it is the first objection.
|
|
62
|
+
3. **What it does** — 3–4 concrete capabilities in the customer's words.
|
|
63
|
+
Outcomes, not features.
|
|
64
|
+
4. **How it works** — 3 steps. This is where a diagram or screenshot earns
|
|
65
|
+
its place.
|
|
66
|
+
5. **Objection handling** — pricing, FAQ, guarantee. Whatever the actual
|
|
67
|
+
hesitation is for that business.
|
|
68
|
+
6. **Close** — the same action as the hero, restated. Someone who read to the
|
|
69
|
+
bottom is ready; do not make them scroll back up.
|
|
70
|
+
|
|
71
|
+
⭐ **Cut before adding.** A five-section page where every section does work
|
|
72
|
+
beats a nine-section page padded with "Our Values". If a section does not answer
|
|
73
|
+
one of those questions, delete it.
|
|
74
|
+
|
|
75
|
+
## Images
|
|
76
|
+
|
|
77
|
+
- **A real photograph is worth more than any amount of decoration.** Use the
|
|
78
|
+
image marker so a real one is generated rather than a grey box.
|
|
79
|
+
- **Give images a job.** A hero image should show the thing or the person, not
|
|
80
|
+
an abstract swoop. For a trade business: the van, the work, the person. For a
|
|
81
|
+
product: the product in use.
|
|
82
|
+
- Always `object-fit: cover` with a fixed aspect-ratio so layouts do not jump:
|
|
83
|
+
`aspect-ratio: 16/9; object-fit: cover; width: 100%`.
|
|
84
|
+
- ⚠️ Never a stock-photo handshake, never a generic "team in a meeting". It
|
|
85
|
+
reads as filler and undercuts everything around it.
|
|
86
|
+
|
|
87
|
+
## Density is a choice you make once
|
|
88
|
+
|
|
89
|
+
A marketing page is **spacious** — few elements, large type, lots of air.
|
|
90
|
+
A dashboard is **dense** — small type, tight rows, many things visible.
|
|
91
|
+
|
|
92
|
+
⚠️ Mixing the two is jarring. A dashboard with hero-sized headings wastes the
|
|
93
|
+
screen; a landing page with dashboard density looks like a spreadsheet. Decide
|
|
94
|
+
which the brief is asking for before setting a single size.
|
|
95
|
+
|
|
96
|
+
## Before calling it done
|
|
97
|
+
|
|
98
|
+
- One element on each screen is clearly dominant.
|
|
99
|
+
- Space between sections is several times the space inside them.
|
|
100
|
+
- At least one section breaks the symmetry of the others.
|
|
101
|
+
- Not everything is centred.
|
|
102
|
+
- Every section answers a question a visitor actually has.
|
|
103
|
+
- At 320px it is still one readable column, and nothing overflows sideways.
|