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,123 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: acuvo-design-system
|
|
3
|
+
description: How to make a generated page look designed rather than templated, under Acuvo's CSP
|
|
4
|
+
when: Building or restyling any user-facing page, component or app
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# Making it look designed
|
|
8
|
+
|
|
9
|
+
## ⚠️ THE CONSTRAINT THAT DECIDES EVERYTHING: the CSP
|
|
10
|
+
|
|
11
|
+
Generated apps run under a strict Content-Security-Policy. **No external
|
|
12
|
+
stylesheet, script, font host or CDN will load.** The only external origin that
|
|
13
|
+
works is Google Fonts.
|
|
14
|
+
|
|
15
|
+
So:
|
|
16
|
+
|
|
17
|
+
- **Never** `<link>` a CDN stylesheet, never `<script src="https://cdn...">`.
|
|
18
|
+
It fails silently and the page renders unstyled — which reads as "the AI is
|
|
19
|
+
bad at design" when it was a network refusal.
|
|
20
|
+
- **Vendor it instead.** Write the CSS into the project. If a component library
|
|
21
|
+
is genuinely needed, inline the handful of rules actually used.
|
|
22
|
+
- Images and media must be same-origin or `data:` URIs.
|
|
23
|
+
|
|
24
|
+
⭐ This is the single biggest measured cause of ugly output. It is not a taste
|
|
25
|
+
problem.
|
|
26
|
+
|
|
27
|
+
## The look
|
|
28
|
+
|
|
29
|
+
**Type.** One display face and one text face, maximum. Google Fonts is allowed —
|
|
30
|
+
use it. A serif display over a clean sans body reads premium; two sans faces read
|
|
31
|
+
like a template. Set `font-feature-settings` and real line-height (1.5–1.65 body,
|
|
32
|
+
1.05–1.2 display).
|
|
33
|
+
|
|
34
|
+
**Space is the design.** Most weak pages are weak because everything is 16px
|
|
35
|
+
apart. Use a scale (4 / 8 / 12 / 16 / 24 / 32 / 48 / 64) and let the important
|
|
36
|
+
things have room. Section padding should be 2–4× what feels right at first.
|
|
37
|
+
|
|
38
|
+
**Colour.** One ink, one paper, one accent, and greys derived from the ink rather
|
|
39
|
+
than pure `#888`. Pure black on pure white is a tell. Prefer near-black
|
|
40
|
+
(`#1a1713`) on warm paper (`#f3f3f3`) over `#000` on `#fff`.
|
|
41
|
+
|
|
42
|
+
**Depth without drop shadows.** Hairline borders (`1px` at 6–10% ink) plus a very
|
|
43
|
+
soft shadow beats a heavy `box-shadow`. Two competing shadows look amateur.
|
|
44
|
+
|
|
45
|
+
**Motion.** Transitions on hover/focus at 150–300ms, `ease-out`. Never animate
|
|
46
|
+
layout properties (`width`, `top`) — animate `transform` and `opacity`.
|
|
47
|
+
`prefers-reduced-motion` must disable them.
|
|
48
|
+
|
|
49
|
+
## Designed states — the thing that separates real apps from demos
|
|
50
|
+
|
|
51
|
+
Every list, table and data view needs FOUR states, and they must not look alike:
|
|
52
|
+
|
|
53
|
+
1. **Loading** — a skeleton with the shape of the real content, not a spinner.
|
|
54
|
+
2. **Empty** — says what will fill it and how. "No invoices yet — create your
|
|
55
|
+
first one" beats "No data".
|
|
56
|
+
3. **Error** — says what failed and what to do. Never a blank region.
|
|
57
|
+
4. **Populated.**
|
|
58
|
+
|
|
59
|
+
⚠️ An empty state and a failed load must render DIFFERENTLY. If they look the
|
|
60
|
+
same, a broken page looks merely new, and nobody reports it.
|
|
61
|
+
|
|
62
|
+
## Responsive
|
|
63
|
+
|
|
64
|
+
Design the small screen first. Real content must never cause horizontal page
|
|
65
|
+
scroll — wide tables and code blocks get their own `overflow-x: auto` container
|
|
66
|
+
instead. Use `clamp()` for display type rather than three breakpoints.
|
|
67
|
+
|
|
68
|
+
## Before calling it done
|
|
69
|
+
|
|
70
|
+
- Does it look like it was made for THIS content, or could the copy be swapped
|
|
71
|
+
for any other product?
|
|
72
|
+
- Is there one clear focal point per screen?
|
|
73
|
+
- Are the four states designed?
|
|
74
|
+
- Does it work at 360px wide?
|
|
75
|
+
|
|
76
|
+
## ⭐⭐⭐ THE TOOLBOX — two files you can reference for free
|
|
77
|
+
|
|
78
|
+
**Multi-file output only.** Reference either and it is written into the project
|
|
79
|
+
for you; never paste their contents, and never fetch them from a CDN.
|
|
80
|
+
|
|
81
|
+
```html
|
|
82
|
+
<link rel="stylesheet" href="acuvo-ui.css">
|
|
83
|
+
<script src="acuvo-motion.js" defer></script>
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
### `acuvo-ui.css` — the things a page cannot fake
|
|
87
|
+
|
|
88
|
+
A modern reset, a **fluid type scale** (`--step--1` … `--step-4`, all `clamp()`),
|
|
89
|
+
a designed dark mode via `prefers-color-scheme`, and primitives with real states:
|
|
90
|
+
`.card` `.card-hover` `.btn` `.btn-ghost` `.field` `.label` `.chip` `.eyebrow`
|
|
91
|
+
`.lead` `.divider`. Layout helpers: `.wrap` `.wrap-narrow` `.section` `.stack`
|
|
92
|
+
`.row` `.grid` (auto-fit, `--min` to tune).
|
|
93
|
+
|
|
94
|
+
⭐ **Restyle it with variables, do not fight it.** `--accent`, `--radius`,
|
|
95
|
+
`--font`, `--ink`, `--bg`, `--line` are the knobs. Setting `--accent` and a
|
|
96
|
+
Google font is usually the whole brand.
|
|
97
|
+
|
|
98
|
+
### `acuvo-motion.js` — the 90% of motion that reads as designed
|
|
99
|
+
|
|
100
|
+
Attributes, no API to learn:
|
|
101
|
+
|
|
102
|
+
| attribute | what it does |
|
|
103
|
+
|---|---|
|
|
104
|
+
| `data-reveal` | fades and lifts in when scrolled into view |
|
|
105
|
+
| `data-stagger="70"` | reveals the element's CHILDREN in sequence, 70ms apart |
|
|
106
|
+
| `data-count="900"` | counts a number up on first view, keeping its prefix/suffix (`$1,250` works) |
|
|
107
|
+
| `data-parallax="0.2"` | gentle transform-only parallax |
|
|
108
|
+
|
|
109
|
+
⚠️ **Everything is disabled under `prefers-reduced-motion`, and that is not
|
|
110
|
+
negotiable.** Motion someone did not consent to is an accessibility defect.
|
|
111
|
+
`data-count` also keeps the element's original text, so the page reads correctly
|
|
112
|
+
with JavaScript off.
|
|
113
|
+
|
|
114
|
+
## ⚠️⚠️ WHAT THE TOOLBOX DOES NOT DO — do not reach for a CDN
|
|
115
|
+
|
|
116
|
+
There is no timeline choreography, no physics, no chart library. **A generated
|
|
117
|
+
app may load a stylesheet from Google Fonts and NOTHING ELSE, and no external
|
|
118
|
+
script at all** — that is the Content Security Policy, it is deliberate, and a
|
|
119
|
+
`<script src="https://cdn…">` will be refused by the browser rather than
|
|
120
|
+
silently degrade.
|
|
121
|
+
|
|
122
|
+
⭐ So build the rest with inline CSS and SVG, which you are good at. A hand-drawn
|
|
123
|
+
SVG bar chart beats a blocked library every time.
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: animation
|
|
3
|
+
description: Motion that reads as quality — what to animate, timings that feel right, and reduced-motion
|
|
4
|
+
when: When adding a transition, a hover state, a loading indicator, or anything that moves
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# Animation
|
|
8
|
+
|
|
9
|
+
## Motion explains a change. It is not decoration.
|
|
10
|
+
|
|
11
|
+
Good motion answers *"what just happened and where did it come from"*. A panel
|
|
12
|
+
that slides from the button that opened it tells the eye where it belongs. The
|
|
13
|
+
same panel fading in from nowhere tells it nothing.
|
|
14
|
+
|
|
15
|
+
If a movement does not explain something, it is delay.
|
|
16
|
+
|
|
17
|
+
## ⚠️⚠️ Animate only `transform` and `opacity`
|
|
18
|
+
|
|
19
|
+
```css
|
|
20
|
+
✗ transition: left .3s, width .3s, height .3s; /* layout every frame */
|
|
21
|
+
✓ transition: transform .3s, opacity .3s; /* compositor only */
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
`transform` and `opacity` can be handled without recalculating layout or
|
|
25
|
+
repainting. Animating `left`, `top`, `width`, `height`, `margin` or `box-shadow`
|
|
26
|
+
forces work on every frame and is the usual cause of janky motion on a phone
|
|
27
|
+
(`performance`).
|
|
28
|
+
|
|
29
|
+
To move something: `transform: translateX()`. To resize: `scale()`.
|
|
30
|
+
|
|
31
|
+
## Timings that feel right
|
|
32
|
+
|
|
33
|
+
| what | duration |
|
|
34
|
+
|---|---|
|
|
35
|
+
| hover, small state change | 100–150ms |
|
|
36
|
+
| dropdown, tooltip, toggle | 150–250ms |
|
|
37
|
+
| panel, modal, page transition | 250–400ms |
|
|
38
|
+
| anything | **never past ~500ms** |
|
|
39
|
+
|
|
40
|
+
⭐ Under ~100ms reads as instant — fine for feedback, wasted on anything you
|
|
41
|
+
want noticed. Past ~500ms the interface feels like it is thinking, and users
|
|
42
|
+
start clicking again.
|
|
43
|
+
|
|
44
|
+
**Easing:** `ease-out` for things entering (fast then settling — feels
|
|
45
|
+
responsive), `ease-in` for things leaving. Never `linear` for anything physical;
|
|
46
|
+
it reads as mechanical because nothing in the world moves that way.
|
|
47
|
+
|
|
48
|
+
## Enter and exit are not symmetrical
|
|
49
|
+
|
|
50
|
+
Entrances can afford to be seen. Exits should be quicker — the user has already
|
|
51
|
+
decided, and making them wait to leave is the most irritating kind of animation.
|
|
52
|
+
Roughly: exit at half the entrance duration.
|
|
53
|
+
|
|
54
|
+
## ⚠️⚠️ Respect `prefers-reduced-motion` — it is a medical setting
|
|
55
|
+
|
|
56
|
+
For some people motion causes nausea or vertigo. This is not a preference to
|
|
57
|
+
override:
|
|
58
|
+
|
|
59
|
+
```css
|
|
60
|
+
@media (prefers-reduced-motion: reduce) {
|
|
61
|
+
*, *::before, *::after {
|
|
62
|
+
animation-duration: .01ms !important;
|
|
63
|
+
animation-iteration-count: 1 !important;
|
|
64
|
+
transition-duration: .01ms !important;
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
⭐ Reduced ≠ removed. Keep the state change instant and legible — a cross-fade
|
|
70
|
+
is usually still fine; it is *movement*, parallax and spin that cause trouble.
|
|
71
|
+
|
|
72
|
+
## Loading states
|
|
73
|
+
|
|
74
|
+
- Under ~300ms: show nothing. A spinner that flashes is worse than a still
|
|
75
|
+
moment.
|
|
76
|
+
- Longer: a skeleton in the shape of what is coming beats a spinner — it says
|
|
77
|
+
what is arriving, and the layout does not jump when it lands.
|
|
78
|
+
- Long and unknown: say what is happening in words.
|
|
79
|
+
|
|
80
|
+
## Restraint is the whole skill
|
|
81
|
+
|
|
82
|
+
One considered transition reads as expensive. Six competing ones read as a
|
|
83
|
+
template. If everything moves, nothing is emphasised — which is the same
|
|
84
|
+
argument as using one accent colour rather than seven.
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: api-design
|
|
3
|
+
description: REST shapes, status codes that mean something, idempotency and pagination that survives growth
|
|
4
|
+
when: When adding an endpoint, designing a route, or deciding what a handler returns
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# API design
|
|
8
|
+
|
|
9
|
+
## Nouns in the path, verbs in the method
|
|
10
|
+
|
|
11
|
+
```
|
|
12
|
+
✗ POST /createInvoice ✗ GET /getInvoiceById?id=7
|
|
13
|
+
✓ POST /invoices ✓ GET /invoices/7
|
|
14
|
+
✓ PATCH /invoices/7 ✓ DELETE /invoices/7
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
`GET` never changes anything — crawlers, prefetchers and browser history all
|
|
18
|
+
assume that, and one of them will eventually prove it.
|
|
19
|
+
|
|
20
|
+
## Status codes are the API's error handling
|
|
21
|
+
|
|
22
|
+
| code | meaning | the mistake it prevents |
|
|
23
|
+
|---|---|---|
|
|
24
|
+
| 200 | here it is | — |
|
|
25
|
+
| 201 | created, `Location:` points at it | 200 with a body you have to parse to learn the id |
|
|
26
|
+
| 400 | your request is malformed | using 500 for a typo |
|
|
27
|
+
| 401 | who are you? | conflated with 403 |
|
|
28
|
+
| 403 | I know who you are; no | conflated with 401 |
|
|
29
|
+
| 404 | no such thing | 200 with `{"error":"not found"}` |
|
|
30
|
+
| 409 | conflicts with current state | 400 for a duplicate |
|
|
31
|
+
| 422 | shape is fine, values are not | 400 for everything |
|
|
32
|
+
| 429 | slow down | silence |
|
|
33
|
+
| 500 | **we** broke | blaming the caller |
|
|
34
|
+
|
|
35
|
+
⚠️ `200 {"success": false}` forces every client to parse a body to discover
|
|
36
|
+
failure, and defeats every retry, cache and monitor in the path.
|
|
37
|
+
|
|
38
|
+
## ⚠️⚠️ Idempotency: the network will deliver twice
|
|
39
|
+
|
|
40
|
+
A client that times out will retry. Without an idempotency key, the customer is
|
|
41
|
+
charged twice and the receipt is genuine.
|
|
42
|
+
|
|
43
|
+
```
|
|
44
|
+
POST /payments
|
|
45
|
+
Idempotency-Key: 8f3a… ← client-generated, stored with the result
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
Same key → return the FIRST result, do not perform the work again. `PUT` and
|
|
49
|
+
`DELETE` are naturally idempotent; `POST` is the one that needs help.
|
|
50
|
+
|
|
51
|
+
## Pagination: never return everything
|
|
52
|
+
|
|
53
|
+
`GET /invoices` on a table that grows is a timeout waiting for a customer big
|
|
54
|
+
enough to trigger it.
|
|
55
|
+
|
|
56
|
+
- **Offset** (`?page=3&limit=50`) is simple and drifts: rows inserted while
|
|
57
|
+
paging shift the window and items are seen twice or missed.
|
|
58
|
+
- **Cursor** (`?after=<opaque>&limit=50`) is stable and is what to use for
|
|
59
|
+
anything ordered by time.
|
|
60
|
+
|
|
61
|
+
Always cap `limit` server-side. A client asking for 1,000,000 gets your maximum,
|
|
62
|
+
not an outage.
|
|
63
|
+
|
|
64
|
+
## Consistent shapes
|
|
65
|
+
|
|
66
|
+
Pick one envelope and use it everywhere:
|
|
67
|
+
|
|
68
|
+
```json
|
|
69
|
+
{ "data": [...], "next_cursor": "…" }
|
|
70
|
+
{ "error": { "code": "invoice_not_found", "message": "No invoice with id 7" } }
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
A machine-readable `code` plus a human-readable `message` — clients branch on
|
|
74
|
+
the code, humans read the message. Never make a client match on prose.
|
|
75
|
+
|
|
76
|
+
## Versioning, dates and money
|
|
77
|
+
|
|
78
|
+
- Version before you need it: `/v1/`. Removing a version is a conversation;
|
|
79
|
+
breaking an unversioned API is an outage.
|
|
80
|
+
- Timestamps in **UTC ISO-8601** with an offset. Never a bare local time.
|
|
81
|
+
- Money in **integer minor units** with a currency (`{"amount": 1250,
|
|
82
|
+
"currency": "AUD"}`). Floating point and money do not belong together.
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: auth-and-sessions
|
|
3
|
+
description: Sessions, refresh, and the re-signin bug — why a user gets logged out mid-click and how to stop it
|
|
4
|
+
when: When building sign-in, protecting a route, or debugging a user being logged out unexpectedly
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# Auth and sessions
|
|
8
|
+
|
|
9
|
+
## ⚠️⚠️ The re-signin bug, which is nearly always the same two causes
|
|
10
|
+
|
|
11
|
+
A user is signed in, clicks something, and lands on the login page. Almost every
|
|
12
|
+
time it is one of these:
|
|
13
|
+
|
|
14
|
+
**1. A failed refresh treated as "signed out".**
|
|
15
|
+
|
|
16
|
+
```js
|
|
17
|
+
✗ const { user } = await getUser();
|
|
18
|
+
if (!user) redirect('/login'); // a 500 from the auth server looks identical to a real logout
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
`getUser()` can fail three ways and they are not the same:
|
|
22
|
+
|
|
23
|
+
| what happened | correct response |
|
|
24
|
+
|---|---|
|
|
25
|
+
| valid session | continue |
|
|
26
|
+
| **401 / 403** — the token is genuinely bad | sign out |
|
|
27
|
+
| **network error, 5xx, timeout** | **do NOT sign out** — retry, or fail the request |
|
|
28
|
+
|
|
29
|
+
Destroying a session because the auth service had a bad second is the bug. Read
|
|
30
|
+
the status, not just the absence of a user. This is `error-handling`'s rule —
|
|
31
|
+
*empty ≠ unreadable* — applied to identity.
|
|
32
|
+
|
|
33
|
+
**2. Two things refreshing the same token at once.** Concurrent refreshes race;
|
|
34
|
+
one rotates the token, the other presents the now-stale one and is rejected.
|
|
35
|
+
Single-flight the refresh: one in-flight promise that every caller awaits.
|
|
36
|
+
|
|
37
|
+
## Sessions expire. Plan the moment.
|
|
38
|
+
|
|
39
|
+
- Short-lived access token + long-lived refresh token is the standard shape.
|
|
40
|
+
- Refresh **before** expiry, not on the 401 — a refresh triggered by a failure
|
|
41
|
+
means the user already saw an error.
|
|
42
|
+
- When the session really is over, say so and **preserve what they were doing**.
|
|
43
|
+
Sending someone to a bare login screen after they typed a long form is the
|
|
44
|
+
part that makes people angry, not the logout itself.
|
|
45
|
+
|
|
46
|
+
## ⚠️⚠️ The client cannot be the gate
|
|
47
|
+
|
|
48
|
+
Hiding a button is presentation. The check that matters runs on the server, on
|
|
49
|
+
every request, for every resource — because the client is a program the user
|
|
50
|
+
controls.
|
|
51
|
+
|
|
52
|
+
```js
|
|
53
|
+
✗ if (user.role === 'admin') showDeleteButton(); // and the endpoint checks nothing
|
|
54
|
+
✓ the endpoint verifies the caller may delete THIS row, every time
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
Route protection in a framework is convenience. An unprotected API under a
|
|
58
|
+
protected page is still an unprotected API.
|
|
59
|
+
|
|
60
|
+
## Passwords, if you must hold them
|
|
61
|
+
|
|
62
|
+
Never store them recoverable. `bcrypt`, `scrypt` or `argon2` — never a plain
|
|
63
|
+
hash, never your own scheme. Compare with a constant-time function.
|
|
64
|
+
|
|
65
|
+
Better: do not hold them. An OAuth provider or a magic link removes the entire
|
|
66
|
+
class of problem, including the breach you would otherwise have to disclose.
|
|
67
|
+
|
|
68
|
+
## Cookies
|
|
69
|
+
|
|
70
|
+
`HttpOnly` (JavaScript cannot read it, so XSS cannot steal it), `Secure`,
|
|
71
|
+
`SameSite=Lax` as the default. A token in `localStorage` is readable by every
|
|
72
|
+
script on the page, including one that arrived through a dependency.
|
|
73
|
+
|
|
74
|
+
## ⚠️ Multi-tenant: the row belongs to a tenant, not to a user
|
|
75
|
+
|
|
76
|
+
Every query filters by tenant, and the filter comes from the SESSION, never from
|
|
77
|
+
a parameter the caller supplied. `?tenant=other-company` is the whole attack.
|
|
78
|
+
See `supabase-multitenant` for enforcing that at the database.
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: build-with-a-framework
|
|
3
|
+
description: The scaffold that is known to build — pinned, base-relative, and the four traps that waste a round each
|
|
4
|
+
when: Before running npm install, or whenever the answer is React/Vue/Svelte/Tailwind rather than hand-written vanilla
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# Building with a framework
|
|
8
|
+
|
|
9
|
+
You have a real machine. `npm install` works, a build step works, and the output
|
|
10
|
+
of that build is what ships. This is the scaffold that is **known to work** —
|
|
11
|
+
measured on a live machine: install 11.9s, `vite build` 2.0s, exit 0 both.
|
|
12
|
+
|
|
13
|
+
## ⭐ Do not re-derive the scaffold. Write `src/App.jsx` and copy the rest.
|
|
14
|
+
|
|
15
|
+
Asking a model to emit `package.json`, `vite.config.js`, `index.html` and
|
|
16
|
+
`main.jsx` correctly on every build spends tokens re-deriving a solved problem
|
|
17
|
+
and fails in ways that are tedious to repair. **These four files are correct,
|
|
18
|
+
and they are free.** The part that is actually the request is `src/App.jsx`.
|
|
19
|
+
|
|
20
|
+
`package.json`
|
|
21
|
+
|
|
22
|
+
```json
|
|
23
|
+
{
|
|
24
|
+
"name": "app",
|
|
25
|
+
"private": true,
|
|
26
|
+
"type": "module",
|
|
27
|
+
"scripts": { "build": "vite build" },
|
|
28
|
+
"dependencies": { "react": "18.3.1", "react-dom": "18.3.1" },
|
|
29
|
+
"devDependencies": { "vite": "5.4.0", "@vitejs/plugin-react": "4.3.1" }
|
|
30
|
+
}
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
`vite.config.js`
|
|
34
|
+
|
|
35
|
+
```js
|
|
36
|
+
import { defineConfig } from 'vite';
|
|
37
|
+
import react from '@vitejs/plugin-react';
|
|
38
|
+
export default defineConfig({ plugins: [react()], base: './' });
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
`index.html`
|
|
42
|
+
|
|
43
|
+
```html
|
|
44
|
+
<!doctype html><html lang="en"><head><meta charset="utf-8">
|
|
45
|
+
<meta name="viewport" content="width=device-width,initial-scale=1">
|
|
46
|
+
<title>App</title></head><body><div id="root"></div>
|
|
47
|
+
<script type="module" src="/src/main.jsx"></script></body></html>
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
`src/main.jsx`
|
|
51
|
+
|
|
52
|
+
```js
|
|
53
|
+
import React from 'react';
|
|
54
|
+
import { createRoot } from 'react-dom/client';
|
|
55
|
+
import App from './App.jsx';
|
|
56
|
+
createRoot(document.getElementById('root')).render(<App />);
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
Then:
|
|
60
|
+
|
|
61
|
+
```
|
|
62
|
+
npm install --no-audit --no-fund && npm run build
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
## ⚠️ The four traps, each of which costs a round
|
|
66
|
+
|
|
67
|
+
**1. `"^latest"` is how a build that worked yesterday breaks today.** Every
|
|
68
|
+
version above is pinned on purpose. If you add a dependency, pin it too.
|
|
69
|
+
|
|
70
|
+
**2. `base: './'` is not decoration.** Without it the built `index.html` asks for
|
|
71
|
+
`/assets/…` from the site ROOT, and what ships is served from a path. The page
|
|
72
|
+
loads, the bundle 404s, and you get a blank screen with no error in the build.
|
|
73
|
+
|
|
74
|
+
**3. If your machine is a hosted sandbox, it is shut down while you think.**
|
|
75
|
+
That is true of Acuvo's builder, where the box stops billing for idle time and a
|
|
76
|
+
fresh one starts for your next command: **your files come back; anything you
|
|
77
|
+
INSTALLED does not.** It is NOT true when you are running on someone's own
|
|
78
|
+
computer, where nothing disappears between commands.
|
|
79
|
+
|
|
80
|
+
⭐ You do not have to know which you are on. If a build suddenly says
|
|
81
|
+
`vite: not found` after it worked, that is this and not your code — re-run the
|
|
82
|
+
install. Where it applies, the command output says so in its first line.
|
|
83
|
+
|
|
84
|
+
**4. What ships is `dist/`, not your source.** `src/App.jsx` is not a web page.
|
|
85
|
+
So the build MUST pass: a project whose `npm run build` fails ships nothing at
|
|
86
|
+
all, and you will be asked to fix it before you can finish.
|
|
87
|
+
|
|
88
|
+
## ⚠️ Every asset the built page references must exist
|
|
89
|
+
|
|
90
|
+
The built `dist/index.html` names its bundle and its stylesheet. If one of them
|
|
91
|
+
is missing from the tree, the page still validates and renders **nothing** —
|
|
92
|
+
which is the most expensive failure available, because every other signal says
|
|
93
|
+
the build succeeded. After `npm run build`, check that `dist/` contains what
|
|
94
|
+
`dist/index.html` asks for.
|
|
95
|
+
|
|
96
|
+
## When NOT to reach for this
|
|
97
|
+
|
|
98
|
+
A page a visitor only reads — a landing page, a brochure, a menu — is
|
|
99
|
+
`index.html` + `styles.css`, and a framework makes it slower to load and slower
|
|
100
|
+
to build for no gain. Reach for a framework when there is real state to manage:
|
|
101
|
+
a list that changes, a form with steps, a board you drag things around.
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: colour-and-contrast
|
|
3
|
+
description: Building a palette that looks deliberate — restraint, neutrals, one accent, and contrast that passes
|
|
4
|
+
when: Choosing any colour, theming a page, or when output looks flat, muddy or garish
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# Colour
|
|
8
|
+
|
|
9
|
+
Generated pages go wrong in one of two directions: **grey mush** (everything
|
|
10
|
+
`#666` on `#f5f5f5`, nothing to look at) or **carnival** (a purple gradient, a
|
|
11
|
+
teal button, an orange badge, and a red heading). Both come from choosing
|
|
12
|
+
colours one at a time instead of building a set.
|
|
13
|
+
|
|
14
|
+
## ⭐ The rule that fixes most of it: one accent
|
|
15
|
+
|
|
16
|
+
A page needs **many neutrals and exactly one accent.** The accent is what the
|
|
17
|
+
eye is supposed to find — the primary action, the live figure, the one link that
|
|
18
|
+
matters. The moment there are two accents, there is no accent.
|
|
19
|
+
|
|
20
|
+
```
|
|
21
|
+
neutrals 8–10 steps, near-grey, subtly tinted toward the accent
|
|
22
|
+
accent 1 hue, 2–3 steps (base, hover, subtle background)
|
|
23
|
+
semantic success / warning / danger — used ONLY for state, never decoration
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
⚠️ **Semantic colours are not palette colours.** Green means "it worked". If
|
|
27
|
+
green is also the brand colour, a success message is invisible and a decorative
|
|
28
|
+
green panel reads as a system state.
|
|
29
|
+
|
|
30
|
+
## Neutrals are the whole page — tint them
|
|
31
|
+
|
|
32
|
+
Pure `#000`/`#888`/`#fff` is the flattest a page can look. Real interfaces use
|
|
33
|
+
neutrals with a few degrees of hue in them, usually pulled toward the accent.
|
|
34
|
+
|
|
35
|
+
```css
|
|
36
|
+
:root {
|
|
37
|
+
/* accent hue 240 → neutrals carry a trace of it */
|
|
38
|
+
--n-0: hsl(240 20% 99%);
|
|
39
|
+
--n-50: hsl(240 16% 96%);
|
|
40
|
+
--n-100:hsl(240 14% 92%);
|
|
41
|
+
--n-300:hsl(240 10% 76%);
|
|
42
|
+
--n-500:hsl(240 8% 48%);
|
|
43
|
+
--n-700:hsl(240 10% 28%);
|
|
44
|
+
--n-900:hsl(240 18% 11%);
|
|
45
|
+
--accent: hsl(240 76% 56%);
|
|
46
|
+
--accent-weak: hsl(240 76% 96%);
|
|
47
|
+
}
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
⭐ **HSL, not hex, while you are choosing.** Same hue, same saturation, moving
|
|
51
|
+
lightness — that is what makes a family look like a family. Hex hides the
|
|
52
|
+
relationship and you end up with nine unrelated colours.
|
|
53
|
+
|
|
54
|
+
The vendored token layer already ships neutrals and an accent. **Prefer those
|
|
55
|
+
tokens.** Introduce new colour only when the brief names a brand colour.
|
|
56
|
+
|
|
57
|
+
## Contrast is a requirement, not a preference
|
|
58
|
+
|
|
59
|
+
| use | minimum |
|
|
60
|
+
|---|---|
|
|
61
|
+
| body text | **4.5:1** |
|
|
62
|
+
| large text (≥24px, or ≥19px bold) | **3:1** |
|
|
63
|
+
| icons, borders, focus rings, UI edges | **3:1** |
|
|
64
|
+
| disabled text | exempt, but then it must be obviously disabled |
|
|
65
|
+
|
|
66
|
+
⚠️ **Placeholder text and light-grey captions are where this fails almost every
|
|
67
|
+
time.** `#999` on `#fff` is 2.8:1 — it fails, and it fails for everyone in
|
|
68
|
+
sunlight, not just people with low vision.
|
|
69
|
+
|
|
70
|
+
⚠️ **White text on a mid-tone accent usually fails.** `#fff` on a 56%-lightness
|
|
71
|
+
blue is around 3.9:1. Either darken the accent to ~45% lightness for buttons, or
|
|
72
|
+
use near-black text on it. Check, do not assume.
|
|
73
|
+
|
|
74
|
+
⭐ **Never signal with colour alone.** A red border on an invalid field is
|
|
75
|
+
invisible to a colour-blind user and to a screenshot in greyscale. Add an icon,
|
|
76
|
+
a label, or text. Roughly 1 in 12 men cannot distinguish your red from your
|
|
77
|
+
green.
|
|
78
|
+
|
|
79
|
+
## Where colour goes
|
|
80
|
+
|
|
81
|
+
- **Backgrounds carry almost no saturation.** Depth comes from *slightly*
|
|
82
|
+
different neutrals plus a border, not from colour.
|
|
83
|
+
- **Borders `--n-100`–`--n-300`.** A 1px hairline does more for perceived
|
|
84
|
+
quality than a shadow does.
|
|
85
|
+
- ⚠️ **Gradients: at most one, and keep it subtle.** Two stops, close in hue.
|
|
86
|
+
A purple-to-pink hero gradient is the single most recognisable "AI made this"
|
|
87
|
+
signal in existence. If the brief did not ask for a gradient, do not add one.
|
|
88
|
+
- **Shadows are neutral and soft**, from the elevation ramp
|
|
89
|
+
(`--shadow-1`…`--shadow-6`). A coloured shadow reads as a toy.
|
|
90
|
+
|
|
91
|
+
## Dark mode, if asked for
|
|
92
|
+
|
|
93
|
+
Not an inversion. Dark surfaces need **less** saturation and **more**
|
|
94
|
+
lightness in the accent, or it vibrates.
|
|
95
|
+
|
|
96
|
+
```css
|
|
97
|
+
@media (prefers-color-scheme: dark) {
|
|
98
|
+
:root { --bg: hsl(240 14% 8%); --fg: hsl(240 12% 92%); --accent: hsl(240 70% 68%); }
|
|
99
|
+
}
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
- Surface `#0d0d10`-ish, not `#000`. Pure black with white text causes halation.
|
|
103
|
+
- Body text near `#e8e8ea`, not `#fff`.
|
|
104
|
+
- Elevation in dark mode is a *lighter surface*, not a bigger shadow.
|
|
105
|
+
|
|
106
|
+
## Before calling it done
|
|
107
|
+
|
|
108
|
+
- One accent. Count them — if there are two, one is wrong.
|
|
109
|
+
- Body text passes 4.5:1; buttons and placeholders were checked, not assumed.
|
|
110
|
+
- Neutrals share a hue; nothing is pure `#000` or pure `#888`.
|
|
111
|
+
- No unrequested gradient. No coloured shadows.
|
|
112
|
+
- Nothing is communicated by colour alone.
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: creative-engines
|
|
3
|
+
description: Image, voice and editing engines — cost first, edit rather than regenerate, check what came back
|
|
4
|
+
when: When asked to make, change, reframe or narrate an image or audio, or asked what something will cost
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# Creative Engines
|
|
8
|
+
|
|
9
|
+
Acuvo can generate and edit real media. The engines are good; most bad results
|
|
10
|
+
come from reaching for the wrong verb, not from the model.
|
|
11
|
+
|
|
12
|
+
| verb | use it for |
|
|
13
|
+
|---|---|
|
|
14
|
+
| `list_engines` | what this account can reach, and **what it costs in credits** |
|
|
15
|
+
| `generate_image` | a new image from a prompt |
|
|
16
|
+
| `edit_image` | change **one thing** in an image that already exists |
|
|
17
|
+
| `expand_image` | a new aspect ratio by painting new edges, not cropping |
|
|
18
|
+
| `speak` | text to an audio file, in a fixed voice |
|
|
19
|
+
| `read_image` | LOOK at what you produced |
|
|
20
|
+
|
|
21
|
+
## ⚠️⚠️ Cost before spending, every time
|
|
22
|
+
|
|
23
|
+
**`list_engines` before you spend, whenever the user asks what something costs,
|
|
24
|
+
asks for "the cheap one", or is about to buy something at volume.** It reports
|
|
25
|
+
what this account can actually reach and the credit price of each engine.
|
|
26
|
+
|
|
27
|
+
Spending someone's credits on an engine they did not choose, at a price they
|
|
28
|
+
never saw, is the worst thing you can do here. If a request implies many images,
|
|
29
|
+
say the total first.
|
|
30
|
+
|
|
31
|
+
## ⭐⭐ EDIT beats REGENERATE — almost always
|
|
32
|
+
|
|
33
|
+
When an image is nearly right, do **not** generate it again. A regeneration
|
|
34
|
+
throws away everything that was already good and rerolls the composition, the
|
|
35
|
+
lighting and the subject.
|
|
36
|
+
|
|
37
|
+
**`edit_image` names what to replace in plain words:**
|
|
38
|
+
`target: "the sign on the van"` → `replacement: "plain white panel"`.
|
|
39
|
+
|
|
40
|
+
⭐ This is cheaper, faster, and it *keeps the picture the user already liked*.
|
|
41
|
+
"Make the sign blank" is an edit. "Try again but better" is a reroll, and the
|
|
42
|
+
user will lose the version they wanted.
|
|
43
|
+
|
|
44
|
+
Same reasoning for aspect ratio: **`expand_image` paints new edges** to reach
|
|
45
|
+
16:9, 9:16 or 4:5. Cropping to reframe throws away the subject; expanding keeps
|
|
46
|
+
it and invents only the margins.
|
|
47
|
+
|
|
48
|
+
## ⚠️ Use the path the result gives you
|
|
49
|
+
|
|
50
|
+
`generate_image` returns a `.png` **or** a `.jpg` — the extension follows
|
|
51
|
+
whatever the engine produced. **Use the exact path from the result.** Do not
|
|
52
|
+
assume `.png` and do not construct the filename yourself; a hardcoded extension
|
|
53
|
+
is a file-not-found on the next step.
|
|
54
|
+
|
|
55
|
+
## ⚠️⚠️ `speak` uses a FIXED voice — never offer to clone one
|
|
56
|
+
|
|
57
|
+
`speak` reads in one fixed voice. **It cannot clone anybody.** Do not offer to
|
|
58
|
+
make it sound like the user, like a celebrity, or like anyone named. Leave
|
|
59
|
+
`engine` unset unless there is a reason.
|
|
60
|
+
|
|
61
|
+
Promising a cloned voice from this verb is a promise the tool cannot keep, and
|
|
62
|
+
the user finds out only after they have spent credits.
|
|
63
|
+
|
|
64
|
+
## ⭐ Always look at what came back
|
|
65
|
+
|
|
66
|
+
**`read_image` on anything you generated**, before you present it. Ask it a
|
|
67
|
+
question if you have a specific worry — spelling is the usual one, because text
|
|
68
|
+
rendered inside a generated image is wrong often enough that it must be checked
|
|
69
|
+
rather than assumed.
|
|
70
|
+
|
|
71
|
+
Presenting a generated image you have not looked at is presenting a guess. See
|
|
72
|
+
the `designing-by-looking` skill — it is the same rule as never shipping an
|
|
73
|
+
unrendered page.
|
|
74
|
+
|
|
75
|
+
## ⭐ Choosing size
|
|
76
|
+
|
|
77
|
+
Ask what the image is FOR before picking dimensions: a hero is wide, a post is
|
|
78
|
+
square or 4:5, a story is 9:16. Generating square and cropping later loses the
|
|
79
|
+
subject; generating at the right shape, or expanding to it, does not.
|
|
80
|
+
|
|
81
|
+
Related skills: `designing-by-looking`, `acuvo-design-system`.
|