@pikku/skills 0.12.32 → 0.12.33
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/package.json
CHANGED
|
@@ -33,9 +33,10 @@ plus more effort" — it is App plus a deliberate surface checklist, so read the
|
|
|
33
33
|
base first and follow it in full rather than blending the two into one plan.
|
|
34
34
|
|
|
35
35
|
The supporting references belong to whichever mode sends you to them:
|
|
36
|
-
`references/multi-app.md` (a second frontend), `references/design.md` (
|
|
37
|
-
to a design direction and judging whether
|
|
38
|
-
the first screen is built, not after the
|
|
36
|
+
`references/multi-app.md` (a second frontend), `references/design.md` (offering
|
|
37
|
+
to mock the screens first, committing to a design direction, and judging whether
|
|
38
|
+
the screens realise it — read before the first screen is built, not after the
|
|
39
|
+
last), `references/theming.md`
|
|
39
40
|
(authoring the theme),
|
|
40
41
|
`references/ship.md` (deploying, and the Fabric-readiness contract).
|
|
41
42
|
|
|
@@ -61,6 +61,12 @@ in one message. Then stop; do not interview the user.
|
|
|
61
61
|
Neutral (fine for an internal tool, but say so out loud); a direction in words;
|
|
62
62
|
a reference (brand guide, screenshots, a site whose register they want); or
|
|
63
63
|
their own design agent/prompt, whose output you take as the direction.
|
|
64
|
+
- **Do they want to see the screens before you build them?** Offer it here, in
|
|
65
|
+
this same round, as a question and not a gate: one HTML page mocking the main
|
|
66
|
+
screens, a few minutes, far cheaper to change than built screens. If they say
|
|
67
|
+
yes, `references/design.md` owns what to make and what it then binds — the
|
|
68
|
+
approved page becomes source of truth for the screens, and the theme is written
|
|
69
|
+
before it so what they approve is what ships. If they say no, build.
|
|
64
70
|
- **What language should the app speak, and what language does the team work
|
|
65
71
|
in?** Two answers, not one — see §1a, which is where they go. Ask only if the
|
|
66
72
|
request is not obviously English; a brief written in English about an English
|
|
@@ -332,6 +338,10 @@ What a milestone is:
|
|
|
332
338
|
persona. If you cannot write the gherkin, you cannot build it yet — that is a
|
|
333
339
|
`questions/` note, not a milestone.
|
|
334
340
|
|
|
341
|
+
If §1's screen mock was made and approved, the milestones are read off it: every
|
|
342
|
+
screen on that page belongs to some milestone, and a screen no milestone builds
|
|
343
|
+
is a hole in this plan. Say which milestone covers which screen.
|
|
344
|
+
|
|
335
345
|
How to order them:
|
|
336
346
|
|
|
337
347
|
1. **The spine first.** The one object everything else hangs off, and the screen
|
|
@@ -415,16 +425,26 @@ over, and an uncovered function is a half-milestone whether or not the note says
|
|
|
415
425
|
5. **UI.** Pages in `<app>/src/pages/`, one route file each in `<app>/src/routes/`,
|
|
416
426
|
calling functions through the generated `usePikkuQuery` / `usePikkuMutation`
|
|
417
427
|
hooks from `@project/functions-sdk/pikku/api.gen`. One component per `.tsx`
|
|
418
|
-
file. Compose the kit from `@/components/<Name>` rather than hand-rolling
|
|
419
|
-
|
|
420
|
-
|
|
428
|
+
file. Compose the kit from `@/components/<Name>` rather than hand-rolling
|
|
429
|
+
controls — and **add to that kit**: the component that draws the thing this
|
|
430
|
+
product is actually about, and the furniture you would otherwise copy-paste
|
|
431
|
+
into eight pages. The kit is where you start, not where you stop; an app whose
|
|
432
|
+
every screen is `Card` + `Stack` + `Text` composed the inventory rather than a
|
|
433
|
+
design. Register the screen in `useNavItems()` — that one file feeds both the
|
|
434
|
+
desktop sidebar and the phone navigation.
|
|
421
435
|
**Read `references/design.md` before you write the first screen.** You commit
|
|
422
436
|
to a design direction there and are then accountable to it — it hands you no
|
|
423
|
-
layouts, because the design is yours to make.
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
437
|
+
layouts, because the design is yours to make. What you build here is then
|
|
438
|
+
judged at step 7, at the end of this milestone rather than once at §8, where
|
|
439
|
+
the only affordable fix is a repaint of eight screens.
|
|
440
|
+
6. **Scenario** (§7).
|
|
441
|
+
7. **Look at it.** Screenshot every screen this milestone touched, at both
|
|
442
|
+
widths, with the seed in place, and look at the images. This is a gate, the
|
|
443
|
+
same as the scenario: a milestone whose screens nobody has seen is not built,
|
|
444
|
+
it is unproven at the one layer scenarios cannot reach. `references/design.md`
|
|
445
|
+
carries how to take the shot when no browser tool is wired up, and what to
|
|
446
|
+
look for. Then `status: built`, and say in the note what you looked at and
|
|
447
|
+
what it made you change.
|
|
428
448
|
|
|
429
449
|
Rules that are not optional:
|
|
430
450
|
|
|
@@ -30,6 +30,117 @@ and so is the commitment.
|
|
|
30
30
|
Be ambitious with it. A direction that could describe any SaaS app has not been
|
|
31
31
|
chosen — it has been defaulted to in words instead of in components.
|
|
32
32
|
|
|
33
|
+
### The looks you will default to
|
|
34
|
+
|
|
35
|
+
Being ambitious is easier against a list of specific things to not be. Generated
|
|
36
|
+
interfaces cluster hard, and these are the attractors — not because any is ugly,
|
|
37
|
+
but because arriving at one *by default* means no choice was made:
|
|
38
|
+
|
|
39
|
+
- **Stock Mantine.** The strongest pull, and the hardest to see: a component
|
|
40
|
+
library's untouched defaults do not look broken, they look finished. Blue
|
|
41
|
+
accent, `#dee2e6` borders, `md` radius on everything, `Card` + `Stack` + `Text`
|
|
42
|
+
down every page. An app can be entirely this and never trip a critique, because
|
|
43
|
+
nothing on any screen is *wrong*.
|
|
44
|
+
- Warm cream ground with a serif display face and a terracotta accent.
|
|
45
|
+
- Near-black with one acid-green or vermilion pop.
|
|
46
|
+
- A purple-to-blue gradient header on white.
|
|
47
|
+
- Inter, or Space Grotesk, as the "safe" typeface.
|
|
48
|
+
- Emoji as section markers; everything centre-aligned; one radius and one shadow
|
|
49
|
+
stamped on every block, which flattens hierarchy instead of creating it.
|
|
50
|
+
|
|
51
|
+
If the user asks for one of these, build it — their words win. What is not
|
|
52
|
+
allowed is landing on one because it was nearest to hand.
|
|
53
|
+
|
|
54
|
+
## Offer to draw the screens before you build them
|
|
55
|
+
|
|
56
|
+
Before the first milestone, **ask** whether they want to see the screens first.
|
|
57
|
+
One question, in §1's round, not a gate of its own:
|
|
58
|
+
|
|
59
|
+
> Want me to mock the main screens as a page you can look at before I build
|
|
60
|
+
> anything? It takes a few minutes and it is much cheaper to change a picture
|
|
61
|
+
> than a built screen.
|
|
62
|
+
|
|
63
|
+
If they decline, build; the direction in words is enough to be accountable to.
|
|
64
|
+
If they accept, this is the cheapest decision in the project — a picture of eight
|
|
65
|
+
screens costs a fraction of eight built screens, and it is the only point where
|
|
66
|
+
"that is not what I meant" is free.
|
|
67
|
+
|
|
68
|
+
**Author the theme first, then draw the mock from it.** This order is the whole
|
|
69
|
+
point. A beautiful page in hand-rolled CSS sets a bar Mantine then misses, and
|
|
70
|
+
what the user approved is not what ships — they signed off on a picture and
|
|
71
|
+
received an approximation of it. So write `themes/<name>.json` first
|
|
72
|
+
(`references/theming.md`), and let the mock take its every value from that file:
|
|
73
|
+
the palette, `structure.radius`, the spacing scale, the fonts, the component
|
|
74
|
+
`defaultProps`. Approving the mock then approves the theme, and the built screens
|
|
75
|
+
inherit it rather than chase it.
|
|
76
|
+
|
|
77
|
+
**The mock has two halves, and only one of them is Mantine's.** This is the same
|
|
78
|
+
split the built screen lives under, applied a step earlier so the two agree by
|
|
79
|
+
construction. The PAGE — the shell, the regions, the columns, the rhythm, the
|
|
80
|
+
material behind the content, what overlaps what — is plain HTML and your own
|
|
81
|
+
CSS, arranged however the layout decision demands; that half is free, and it is
|
|
82
|
+
where the design actually happens. The COMPONENTS — anything a person would
|
|
83
|
+
point at and call a control, and that the app will adopt as itself: buttons,
|
|
84
|
+
inputs, selects, tables, badges, menus, modals — are drawn as *Mantine's*, at the
|
|
85
|
+
metrics Mantine actually uses: its control heights, its input shapes, its table
|
|
86
|
+
and menu behaviour. The test is the one the build will apply too: is this thing
|
|
87
|
+
the SHAPE OF THE PAGE, or a COMPONENT someone would point at?
|
|
88
|
+
|
|
89
|
+
Getting that second half wrong is what makes a mock a lie. A control the mock
|
|
90
|
+
invents is a promise the app cannot keep, and a beautiful hand-rolled input sets
|
|
91
|
+
a bar the real one misses on screen one. If the mock wants something Mantine
|
|
92
|
+
does not do, that is a real finding, and finding it here is the point: change the
|
|
93
|
+
theme so it does, or change the mock, and say which.
|
|
94
|
+
|
|
95
|
+
**What to make.** One self-contained HTML page holding every screen the app
|
|
96
|
+
needs — not a prototype, not a click-through. Static markup, real content from
|
|
97
|
+
their domain (never lorem), the empty and error states beside the happy path,
|
|
98
|
+
laid out so the whole app is legible by scrolling. Its CSS is custom properties
|
|
99
|
+
on `:root` carrying the theme JSON's values, so a change to either is a change
|
|
100
|
+
to one number in both. Mantine itself will not load here — it is a React library
|
|
101
|
+
and a page like this has no bundler, and on hosts that sandbox the page (a Claude
|
|
102
|
+
Artifact) external stylesheets are blocked outright — so do not try; the mock
|
|
103
|
+
reproduces the theme's values by hand, which is why they have to be written down
|
|
104
|
+
first. Whatever your host offers for showing a page is how you show it: an
|
|
105
|
+
Artifact, a file they open, a preview server. The page is the deliverable; how it
|
|
106
|
+
gets in front of them is not this file's business.
|
|
107
|
+
|
|
108
|
+
Write it to `knowledge/decisions/design/screens.html` and treat it as **source of
|
|
109
|
+
truth for the screens** once they approve it. That has consequences worth
|
|
110
|
+
stating:
|
|
111
|
+
|
|
112
|
+
- The milestones are read off it. A screen in the mock that no milestone builds
|
|
113
|
+
is a gap in the plan, not a spare drawing.
|
|
114
|
+
- A screen the build turns out to need that the mock does not have means the
|
|
115
|
+
mock was wrong. Update it, and say you did. Do not let the app and the mock
|
|
116
|
+
drift and then quietly prefer the app.
|
|
117
|
+
- The knowledge graph still owns the domain — objects, roles, rules. The mock
|
|
118
|
+
owns what the screens look like. When they disagree about a *fact*, knowledge
|
|
119
|
+
wins; when they disagree about a *layout*, the mock wins.
|
|
120
|
+
|
|
121
|
+
**Building it is then a transcription, not a translation.** Because the theme
|
|
122
|
+
already exists and the mock was drawn from it, the screen is Mantine components
|
|
123
|
+
arranged the way the mock arranges them — the look arrives with the theme. Two
|
|
124
|
+
rules keep it that way:
|
|
125
|
+
|
|
126
|
+
- **Layout is yours to write; components are Mantine's.** The page shape — the
|
|
127
|
+
regions, the columns, the rhythm, what sits beside what — is ordinary markup
|
|
128
|
+
and your own CSS. Anything a person would point at and call a control comes
|
|
129
|
+
from Mantine: buttons, inputs, selects, tables, badges, menus. Those carry
|
|
130
|
+
focus rings, keyboard behaviour and i18n, and hand-rolling one throws all of
|
|
131
|
+
it away.
|
|
132
|
+
- **A gap goes back to the theme, never into a component override.** If a screen
|
|
133
|
+
does not match the mock, the fix is a value in `themes/<name>.json`. A stack of
|
|
134
|
+
one-off `className`s and `!important` fighting Mantine's defaults looks like
|
|
135
|
+
progress on screen one and is unmaintainable by screen five, and the screens
|
|
136
|
+
drift apart because nothing central holds them together.
|
|
137
|
+
|
|
138
|
+
**Checking the built screen against the mock** is a structural comparison, not a
|
|
139
|
+
pixel one: the same regions in the same order, the same hierarchy, the same
|
|
140
|
+
states present, the same tokens used. Do not chase pixel equality — Mantine's
|
|
141
|
+
components have their own metrics and the mock was drawn without them. A built
|
|
142
|
+
screen that reads as the same screen has passed.
|
|
143
|
+
|
|
33
144
|
## One screen, designed properly, before the rest exist
|
|
34
145
|
|
|
35
146
|
Design the first real screen as if it were the only one, and take it further than
|
|
@@ -48,6 +159,31 @@ the thing that made it is always yes. Use evidence.
|
|
|
48
159
|
- **Screenshot every screen and look at the image**, at ~390px and at ~1440px.
|
|
49
160
|
Judging your own UI from source is guessing, and the failures that matter —
|
|
50
161
|
proportion, hierarchy, a wall of identical boxes — are invisible in JSX.
|
|
162
|
+
**Sort out how you will take that screenshot before you need it**, because an
|
|
163
|
+
instruction with no working mechanism behind it is one that gets skipped, and
|
|
164
|
+
this is the one that gets skipped. If a browser-driving tool is wired up, use
|
|
165
|
+
it. If it is not — or it fails to connect, which happens — the fallback is
|
|
166
|
+
short enough to write once and keep:
|
|
167
|
+
|
|
168
|
+
```sh
|
|
169
|
+
CHROME=$(command -v google-chrome || command -v chromium || \
|
|
170
|
+
command -v chromium-browser || \
|
|
171
|
+
echo "/Applications/Google Chrome.app/Contents/MacOS/Google Chrome")
|
|
172
|
+
"$CHROME" --headless=new --no-sandbox \
|
|
173
|
+
--remote-debugging-port=9333 --user-data-dir=/tmp/gc-shots &
|
|
174
|
+
```
|
|
175
|
+
|
|
176
|
+
Resolve the binary rather than hardcoding the macOS path: the same gate has to
|
|
177
|
+
work on a Linux box and in CI, and a fallback that only starts on one host is
|
|
178
|
+
a gate that gets skipped everywhere else. If the host already exposes a CDP
|
|
179
|
+
endpoint, point the script at that instead and start nothing.
|
|
180
|
+
|
|
181
|
+
Then drive it over CDP from a script: `Page.navigate`,
|
|
182
|
+
`Emulation.setDeviceMetricsOverride` for the two widths,
|
|
183
|
+
`Page.captureScreenshot`, write the PNG, and open it. Sign in the way a person
|
|
184
|
+
does — click the dev actor switcher on the login page — rather than reaching
|
|
185
|
+
for the secret the client uses; the UI path is shorter and it also proves the
|
|
186
|
+
login screen works. Keep the script; you will run it at every milestone.
|
|
51
187
|
- **Run `impeccable`** (`npx impeccable install`, Node 22.18+) and feed it the
|
|
52
188
|
screenshots. It is external, it does not flatter, and it scores execution
|
|
53
189
|
against interaction heuristics. But it audits how well you executed the design
|
|
@@ -69,6 +205,77 @@ questions are fixed:
|
|
|
69
205
|
had?
|
|
70
206
|
6. Would you show it to the user without apologising for it?
|
|
71
207
|
|
|
208
|
+
## The kit is a floor, not a ceiling
|
|
209
|
+
|
|
210
|
+
The scaffold hands you a component kit, and the build instructions tell you to
|
|
211
|
+
compose from it rather than hand-roll. Both are right, and together they have a
|
|
212
|
+
failure mode worth naming: an app whose every screen is `Card` + `Stack` + `Text`
|
|
213
|
+
because those were the pieces in the box. That is not a composed design, it is an
|
|
214
|
+
inventory, and it produces the wall of identical boxes further down this file.
|
|
215
|
+
|
|
216
|
+
**Composing from the kit means using its primitives, not being limited to its
|
|
217
|
+
list.** A product has objects of its own, and the ones that carry its meaning are
|
|
218
|
+
the ones no generic kit ships:
|
|
219
|
+
|
|
220
|
+
- the thing the product is *about*, rendered as itself — a funding meter, a
|
|
221
|
+
streak, a seat map, a run's status over time. If a decision note says the
|
|
222
|
+
progress toward a goal is the primary object, then a component that draws that
|
|
223
|
+
progress has to exist, or the note is describing an app you did not build.
|
|
224
|
+
- the repeated furniture that is currently copy-pasted — the page header, the
|
|
225
|
+
section label, the empty state, the recessed panel a form sits in. Eight inline
|
|
226
|
+
copies of a heading block will not agree with each other; they will disagree by
|
|
227
|
+
a few pixels each, and the screens will read as unrelated for reasons nobody
|
|
228
|
+
can point at.
|
|
229
|
+
|
|
230
|
+
Both kinds are ordinary components built out of kit primitives and theme values.
|
|
231
|
+
Adding them is not hand-rolling, and they are the difference between an app that
|
|
232
|
+
uses a design system and an app that looks like one.
|
|
233
|
+
|
|
234
|
+
The tell that you skipped this: your `components/` directory maps one-to-one onto
|
|
235
|
+
your data model and contains nothing that names a *quality* of the product.
|
|
236
|
+
|
|
237
|
+
## Design is a gate on the milestone, not a phase at the end
|
|
238
|
+
|
|
239
|
+
The loop that actually runs is plan, build, prove, close. Design advice that
|
|
240
|
+
lives outside that loop does not run — it gets read, agreed with, and skipped,
|
|
241
|
+
because nothing blocks on it. Milestones close on green scenarios, and scenarios
|
|
242
|
+
say nothing about how anything looks.
|
|
243
|
+
|
|
244
|
+
So put it in the loop. **A milestone is not built until its screens have been
|
|
245
|
+
looked at**, in the same sense that it is not built until its scenario passes:
|
|
246
|
+
|
|
247
|
+
- Screenshot every screen the milestone touched, at both widths, with the seed
|
|
248
|
+
in place.
|
|
249
|
+
- Look at the images. Not the JSX.
|
|
250
|
+
- Fix what they show, in this milestone, while it is one screen and not eight.
|
|
251
|
+
- Say in the milestone note what you looked at and what you changed.
|
|
252
|
+
|
|
253
|
+
A milestone closed without that is closed on a claim, not on evidence. The cost
|
|
254
|
+
of being honest about it now is minutes; the cost at §8 is a repaint of the whole
|
|
255
|
+
app, and by then the wrong register has been inherited by every screen so the
|
|
256
|
+
repaint is a rewrite.
|
|
257
|
+
|
|
258
|
+
### The seed is part of the gate
|
|
259
|
+
|
|
260
|
+
A screenshot is only evidence if the screen has something on it. Before the gate
|
|
261
|
+
runs, the dev seed must populate what each screen *is for* — not one row, and not
|
|
262
|
+
an empty state.
|
|
263
|
+
|
|
264
|
+
This is a real and quiet failure: a list app whose seed has no list, a countdown
|
|
265
|
+
whose seed has no dates, judged for weeks against its own empty state while the
|
|
266
|
+
screen it was built for was never once looked at. The empty state is worth
|
|
267
|
+
designing and is not what the milestone is about.
|
|
268
|
+
|
|
269
|
+
Seed enough to be judged against: several rows, not three identical ones, and a
|
|
270
|
+
deliberate spread of the cases the screen has to hold — a long title that wraps,
|
|
271
|
+
a missing optional field, a picture and no picture, one item in each state the
|
|
272
|
+
screen can show. Anything derived from *today* — a countdown, "3 days ago", an
|
|
273
|
+
expiry — is seeded as an interval from `now`, never as a fixed date: fixed dates
|
|
274
|
+
are correct on the afternoon they are written and meaningless a month later.
|
|
275
|
+
|
|
276
|
+
Data left over from a scenario run is not a seed. If the screens are full of
|
|
277
|
+
`Filter coffee grinder mttqdvsx`, you are designing against test debris.
|
|
278
|
+
|
|
72
279
|
## Facts, not taste
|
|
73
280
|
|
|
74
281
|
These are not design opinions and are not open to a different answer.
|
|
@@ -125,4 +332,15 @@ first one.
|
|
|
125
332
|
- Every row carries the same buttons, and the buttons outweigh the content.
|
|
126
333
|
- The palette's meaningful colours are also used decoratively, so they have
|
|
127
334
|
stopped meaning anything.
|
|
335
|
+
- A decision note describes something the screens do not do — the note says
|
|
336
|
+
progress is the primary object and no screen draws progress, or it says warm
|
|
337
|
+
and not clinical and the error page is still template blue.
|
|
338
|
+
- The theme JSON is rich and the screens are bare. Tokens are the cheapest half
|
|
339
|
+
of design and the easiest to mistake for the whole of it: a considered palette
|
|
340
|
+
and a display font applied to a default layout is a well-dressed default.
|
|
341
|
+
- Every border, divider and disabled control is a cool blue-grey while the
|
|
342
|
+
accent is not — the surest sign the neutrals were inherited rather than
|
|
343
|
+
chosen. See `references/theming.md`.
|
|
128
344
|
- It looks like the last app you built.
|
|
345
|
+
- It looks like Mantine. Not *built with* Mantine, which it is and should be —
|
|
346
|
+
but indistinguishable from a component gallery with the brand hue swapped in.
|
|
@@ -53,6 +53,128 @@ Then **write the direction into `knowledge/decisions/design/`** — the words th
|
|
|
53
53
|
user gave you, what you chose, and what it rules out. The JSON records what the
|
|
54
54
|
theme is; only the note records why.
|
|
55
55
|
|
|
56
|
+
## The colours the theme has no field for
|
|
57
|
+
|
|
58
|
+
`brand` is the product's accent. It is not the only colour a screen needs, and
|
|
59
|
+
the missing ones are why "don't hardcode colours per component" gets broken by
|
|
60
|
+
the same agent that wrote it down.
|
|
61
|
+
|
|
62
|
+
A screen has to say *covered* and *still open*, *fine* and *needs attention* —
|
|
63
|
+
and those are not the accent. Using the accent for them is worse than a stray
|
|
64
|
+
hex: the brand colour stops meaning "this product" and starts meaning "good", so
|
|
65
|
+
it means nothing. But there is no `brand.covered` field, so the value lands
|
|
66
|
+
inline as `#3f7d5c`, once per component, slightly different each time.
|
|
67
|
+
|
|
68
|
+
Give them a home. A small stylesheet of custom properties, imported once beside
|
|
69
|
+
the Mantine styles, is enough:
|
|
70
|
+
|
|
71
|
+
```css
|
|
72
|
+
:root:root:root {
|
|
73
|
+
--app-covered: #3f7d5c; --app-covered-bg: #e6f1ea;
|
|
74
|
+
--app-open: #a8701a; --app-open-bg: #fbeedb;
|
|
75
|
+
--app-sunk: #fdf7f4; /* a recessed surface, for forms and asides */
|
|
76
|
+
--app-hairline: #ecdfd9; /* NOT var(--mantine-color-gray-2) — see below */
|
|
77
|
+
}
|
|
78
|
+
:root:root[data-mantine-color-scheme='dark'] {
|
|
79
|
+
--app-covered: #7fc09a; --app-covered-bg: #1e2f26;
|
|
80
|
+
--app-open: #e0ab5c; --app-open-bg: #33271a;
|
|
81
|
+
--app-sunk: #241b18;
|
|
82
|
+
--app-hairline: #392b26;
|
|
83
|
+
}
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
Those values are one app's warm direction, not a palette to copy — derive your
|
|
87
|
+
own from yours.
|
|
88
|
+
|
|
89
|
+
## Choose the neutrals; do not inherit them
|
|
90
|
+
|
|
91
|
+
The two details in that snippet that look like typos are the two things most
|
|
92
|
+
likely to make your app look like every other app.
|
|
93
|
+
|
|
94
|
+
**The hairline is a literal, not `var(--mantine-color-gray-2)`.** Mantine's grey
|
|
95
|
+
ramp is blue-biased — `#f8f9fa`, `#dee2e6`, `#868e96` are all cool — and it is
|
|
96
|
+
what draws card borders, dividers, the shell's edges and every disabled control.
|
|
97
|
+
The theme JSON has `brand` and `structure` and **no neutral field at all**, so
|
|
98
|
+
unless you choose otherwise, every app built from this skill runs its accent on
|
|
99
|
+
somebody else's greys.
|
|
100
|
+
|
|
101
|
+
If the accent is not itself blue, that mismatch lands on every screen at once:
|
|
102
|
+
warm content ruled off in cold lines, off everywhere and wrong nowhere in
|
|
103
|
+
particular, which is the hardest kind of wrong to find. It survives a careful
|
|
104
|
+
critique because no single screen is broken.
|
|
105
|
+
|
|
106
|
+
So bias the whole ramp toward the accent — not just the tokens with obvious
|
|
107
|
+
names. Keep Mantine's lightness steps so contrast behaviour and every component
|
|
108
|
+
that picks a step by number are unchanged; move only the hue:
|
|
109
|
+
|
|
110
|
+
```css
|
|
111
|
+
/* The ramps: hue only. Mantine picks a step by number in either scheme, so
|
|
112
|
+
these are scheme-independent and belong in the unscoped block. */
|
|
113
|
+
:root:root:root {
|
|
114
|
+
--mantine-color-gray-0: #faf7f5; --mantine-color-gray-5: #b8a49d;
|
|
115
|
+
--mantine-color-gray-1: #f5efec; --mantine-color-gray-6: #93807a;
|
|
116
|
+
--mantine-color-gray-2: #efe6e2; --mantine-color-gray-7: #574a45;
|
|
117
|
+
--mantine-color-gray-3: #e6dad5; --mantine-color-gray-8: #3d332f;
|
|
118
|
+
--mantine-color-gray-4: #d8c8c2; --mantine-color-gray-9: #2a1f1b;
|
|
119
|
+
--mantine-color-dark-0: #f3e9e4; --mantine-color-dark-5: #4a3a34;
|
|
120
|
+
--mantine-color-dark-1: #cdbdb6; --mantine-color-dark-6: #392b26;
|
|
121
|
+
--mantine-color-dark-2: #a8938c; --mantine-color-dark-7: #241b18;
|
|
122
|
+
--mantine-color-dark-3: #7a655e; --mantine-color-dark-8: #1b1512;
|
|
123
|
+
--mantine-color-dark-4: #5c4a44; --mantine-color-dark-9: #120d0b;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
/* Ground, text and border are a different colour in each scheme, so each one
|
|
127
|
+
is set in the scheme it belongs to. A scheme-dependent token left in the
|
|
128
|
+
block above is the classic unreadable-in-dark bug. */
|
|
129
|
+
:root:root:root[data-mantine-color-scheme='light'] {
|
|
130
|
+
--mantine-color-body: #fdfaf8;
|
|
131
|
+
--mantine-color-text: #2a1f1b;
|
|
132
|
+
--mantine-color-default-border: #ecdfd9;
|
|
133
|
+
--mantine-color-dimmed: #7a625c; /* keep AA: ~5.4:1 on the body above */
|
|
134
|
+
--mantine-color-placeholder: #826a64; /* ~4.8:1 */
|
|
135
|
+
}
|
|
136
|
+
:root:root:root[data-mantine-color-scheme='dark'] {
|
|
137
|
+
--mantine-color-body: #1b1512;
|
|
138
|
+
--mantine-color-text: #f3e9e4;
|
|
139
|
+
--mantine-color-default-border: #392b26;
|
|
140
|
+
--mantine-color-dimmed: #a8938c; /* ~5.6:1 on the body above */
|
|
141
|
+
--mantine-color-placeholder: #8c7a74; /* ~4.7:1 */
|
|
142
|
+
}
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
Check the four text tokens against your own grounds rather than copying these —
|
|
146
|
+
`dimmed` is the most-used text colour in the app and the easiest to drop below
|
|
147
|
+
4.5:1 while making it prettier, and it has to clear the bar on *both* grounds.
|
|
148
|
+
|
|
149
|
+
**The selector is tripled on purpose.** Mantine's `cssVariablesResolver` injects
|
|
150
|
+
its own `:root` block into `<head>` at runtime, which lands *after* your
|
|
151
|
+
stylesheet and wins on source order at equal specificity. A plain `:root` here is
|
|
152
|
+
silently reverted: the file reads correct, the app renders Mantine's defaults,
|
|
153
|
+
and nothing errors. Repeating the pseudo-class raises specificity without adding
|
|
154
|
+
an element to the selector.
|
|
155
|
+
|
|
156
|
+
This one costs a whole pass if you meet it without knowing: you diagnose the
|
|
157
|
+
colours correctly, write the right values, reload, and see no change — so you
|
|
158
|
+
assume the diagnosis was wrong. **The only thing that catches it is looking at a
|
|
159
|
+
screenshot and disbelieving the CSS.**
|
|
160
|
+
|
|
161
|
+
Name them for what they *mean* in this product, never for the colour — `covered`,
|
|
162
|
+
not `green`. The name is the whole value: it survives a change of palette, and it
|
|
163
|
+
is the thing that makes the second use agree with the first. Define both colour
|
|
164
|
+
schemes at once; a token defined only in light is the classic unreadable-in-dark
|
|
165
|
+
bug, and Mantine will happily render it.
|
|
166
|
+
|
|
167
|
+
The same file is where a couple of other things belong that the theme JSON has no
|
|
168
|
+
field for and every screen otherwise re-invents: the hairline that separates rows
|
|
169
|
+
in a list, the recessed surface a form sits on so it does not carry the same
|
|
170
|
+
weight as the content it adds to, and the one animation the product is allowed
|
|
171
|
+
(behind `prefers-reduced-motion`). Two or three rules, not a framework.
|
|
172
|
+
|
|
173
|
+
If the app ships template screens you did not write — the error and not-found
|
|
174
|
+
pages usually — read them before you call the palette done. They arrive with the
|
|
175
|
+
scaffold's colours hardcoded, and a stock blue accent on an app whose direction
|
|
176
|
+
says warm is the single loudest contradiction in the build.
|
|
177
|
+
|
|
56
178
|
**Set the theme once, don't hardcode colours per component.** A screen full of
|
|
57
179
|
inline `color="blue"` and one-off hex values is why apps look templated. Change
|
|
58
180
|
the theme, not the components — and keep it theme-aware for light and dark.
|