@pikku/skills 0.12.52 → 0.12.55

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.
Files changed (112) hide show
  1. package/README.md +1 -1
  2. package/dist/skills.gen.js +2 -2
  3. package/dist/snippets.js +1 -1
  4. package/package.json +1 -1
  5. package/skills/pikku-a11y/SKILL.md +22 -15
  6. package/skills/pikku-addon/SKILL.md +5 -5
  7. package/skills/pikku-addon/references/addon-package-manifest.md +7 -0
  8. package/skills/pikku-addon/references/openapi.md +11 -0
  9. package/skills/pikku-agent/SKILL.md +6 -6
  10. package/skills/pikku-agent/references/agents.md +8 -1
  11. package/skills/pikku-agent/references/runner-vercel.md +2 -2
  12. package/skills/pikku-agent/references/voice.md +7 -1
  13. package/skills/pikku-architect/SKILL.md +2 -2
  14. package/skills/pikku-architect/references/plan-defects.md +9 -7
  15. package/skills/pikku-auth/SKILL.md +15 -36
  16. package/skills/pikku-auth/references/better-auth.md +106 -133
  17. package/skills/pikku-auth/references/jose.md +6 -0
  18. package/skills/pikku-auth/references/machine-auth.md +6 -0
  19. package/skills/pikku-auth/references/sessions.md +6 -0
  20. package/skills/pikku-build/SKILL.md +37 -34
  21. package/skills/pikku-build/references/app.md +113 -99
  22. package/skills/pikku-build/references/design.md +43 -31
  23. package/skills/pikku-build/references/feature.md +17 -3
  24. package/skills/pikku-build/references/mocks.md +29 -0
  25. package/skills/pikku-build/references/multi-app.md +25 -18
  26. package/skills/pikku-build/references/native-app.md +9 -0
  27. package/skills/pikku-build/references/openapi.md +41 -34
  28. package/skills/pikku-build/references/platform.md +23 -14
  29. package/skills/pikku-build/references/post-clone.md +1 -1
  30. package/skills/pikku-build/references/quick.md +76 -39
  31. package/skills/pikku-build/references/scenarios.md +28 -16
  32. package/skills/pikku-build/references/ship.md +19 -16
  33. package/skills/pikku-build/references/theming.md +76 -127
  34. package/skills/pikku-changes/SKILL.md +7 -10
  35. package/skills/pikku-concepts/SKILL.md +34 -44
  36. package/skills/pikku-concepts/references/concept-mapping.md +18 -0
  37. package/skills/pikku-concepts/references/language.md +6 -6
  38. package/skills/pikku-convert-screen/SKILL.md +45 -0
  39. package/skills/pikku-deploy/references/cloudflare.md +8 -0
  40. package/skills/pikku-deploy/references/lambda.md +8 -0
  41. package/skills/pikku-deploy/references/nextjs.md +1 -1
  42. package/skills/pikku-deploy/references/uws.md +1 -1
  43. package/skills/pikku-deploy/references/ws.md +3 -3
  44. package/skills/pikku-emails/SKILL.md +3 -9
  45. package/skills/pikku-guide/SKILL.md +28 -27
  46. package/skills/pikku-i18n/SKILL.md +7 -7
  47. package/skills/pikku-i18n/references/enum-labels.md +9 -0
  48. package/skills/pikku-i18n/references/messages.md +21 -9
  49. package/skills/pikku-i18n/references/rtl.md +18 -38
  50. package/skills/pikku-knowledge/SKILL.md +12 -13
  51. package/skills/pikku-kysely/SKILL.md +3 -21
  52. package/skills/pikku-list-query/SKILL.md +2 -2
  53. package/skills/pikku-meta/references/audit.md +10 -1
  54. package/skills/pikku-meta/references/meta.md +6 -0
  55. package/skills/pikku-meta/references/versioning.md +10 -0
  56. package/skills/pikku-middleware/SKILL.md +7 -16
  57. package/skills/pikku-n8n-import/SKILL.md +9 -1
  58. package/skills/pikku-n8n-import/references/addon-mapping.md +6 -0
  59. package/skills/pikku-n8n-import/references/code-translation.md +7 -0
  60. package/skills/pikku-permissions/SKILL.md +41 -10
  61. package/skills/pikku-permissions/references/scopes.md +59 -0
  62. package/skills/pikku-react/SKILL.md +20 -22
  63. package/skills/pikku-react/references/client.md +2 -70
  64. package/skills/pikku-react/references/react-query.md +11 -0
  65. package/skills/pikku-react/references/workflows.md +11 -0
  66. package/skills/pikku-realtime/SKILL.md +9 -3
  67. package/skills/pikku-report/SKILL.md +17 -16
  68. package/skills/pikku-scenario/SKILL.md +72 -81
  69. package/skills/pikku-scenario/references/browser.md +5 -5
  70. package/skills/pikku-scenario/references/coverage.md +4 -4
  71. package/skills/pikku-scenario/references/steps.md +31 -30
  72. package/skills/pikku-scenario/references/{persona-run.md → virtual-user-run.md} +47 -42
  73. package/skills/pikku-scenario/references/virtual-users.md +114 -0
  74. package/skills/pikku-seo/SKILL.md +12 -16
  75. package/skills/pikku-service-backends/SKILL.md +0 -10
  76. package/skills/pikku-service-backends/references/aws.md +6 -0
  77. package/skills/pikku-service-backends/references/mongodb.md +7 -0
  78. package/skills/pikku-service-backends/references/redis.md +6 -0
  79. package/skills/pikku-services/references/audit-wire-service.md +2 -2
  80. package/skills/pikku-services/references/audit.md +10 -1
  81. package/skills/pikku-services/references/config.md +6 -0
  82. package/skills/pikku-services/references/services.md +9 -2
  83. package/skills/pikku-software-archaeology/SKILL.md +21 -27
  84. package/skills/pikku-software-archaeology/references/blueprint.schema.json +16 -16
  85. package/skills/pikku-software-archaeology/references/pikku-mapping.md +3 -3
  86. package/skills/pikku-software-archaeology/references/second-opinion.md +13 -1
  87. package/skills/pikku-tailwind/SKILL.md +111 -0
  88. package/skills/pikku-theme/SKILL.md +187 -0
  89. package/skills/pikku-theme/references/from-a-reference.md +44 -0
  90. package/skills/pikku-theme/references/presets.md +40 -0
  91. package/skills/pikku-ui/SKILL.md +41 -0
  92. package/skills/pikku-wiring/SKILL.md +13 -12
  93. package/skills/pikku-wiring/references/channel.md +7 -1
  94. package/skills/pikku-wiring/references/cli.md +7 -1
  95. package/skills/pikku-wiring/references/gateway-slack.md +6 -0
  96. package/skills/pikku-wiring/references/http.md +10 -3
  97. package/skills/pikku-wiring/references/mcp.md +8 -0
  98. package/skills/pikku-wiring/references/queue.md +6 -0
  99. package/skills/pikku-wiring/references/realtime.md +34 -4
  100. package/skills/pikku-wiring/references/rpc.md +6 -0
  101. package/skills/pikku-wiring/references/scheduler.md +6 -0
  102. package/skills/pikku-wiring/references/trigger.md +8 -0
  103. package/skills/pikku-workflow/SKILL.md +9 -7
  104. package/skills/pikku-admin-to-fabric/SKILL.md +0 -212
  105. package/skills/pikku-auth/references/permissions.md +0 -261
  106. package/skills/pikku-blueprint-to-fabric/SKILL.md +0 -377
  107. package/skills/pikku-blueprint-to-fabric/scripts/inventory.mjs +0 -367
  108. package/skills/pikku-fabric/SKILL.md +0 -619
  109. package/skills/pikku-fabric/references/debugging.md +0 -110
  110. package/skills/pikku-mantine/SKILL.md +0 -80
  111. package/skills/pikku-scenario/references/personas.md +0 -117
  112. package/skills/pikku-wiring/references/realtime-other-routes.md +0 -23
@@ -1,5 +1,17 @@
1
1
  # Design
2
2
 
3
+ ## Contents
4
+
5
+ - Commit to a direction before the first screen
6
+ - Offer to draw the screens before you build them
7
+ - One screen, designed properly, before the rest exist
8
+ - Judge it — and don't grade your own homework
9
+ - The kit is a floor, not a ceiling
10
+ - Design is a gate on the changeset, not a phase at the end
11
+ - Facts, not taste
12
+ - Two screens that get skipped
13
+ - Symptoms of a screen nobody designed
14
+
3
15
  Your job here is to design something worth the product — not to apply a house
4
16
  style. **There is no house style, and this file is not one.** Two apps built from
5
17
  this skill should not look like each other; if they do, something has gone wrong
@@ -16,14 +28,14 @@ nearest to hand, on every screen, in every app. Not because it lacks taste, but
16
28
  because there is nothing to be wrong against. A direction fixes that: state one,
17
29
  in words, before any screen exists.
18
30
 
19
- Say what this product *is* — its register, its reference points, what it feels
31
+ Say what this product _is_ — its register, its reference points, what it feels
20
32
  like to use, what it is deliberately not. "A warm, food-forward thing you use
21
33
  standing at an open fridge door, closer to a recipe card than to a dashboard, and
22
34
  never clinical" is a direction. "Clean and modern" is not — it rules nothing out,
23
35
  so it cannot be departed from.
24
36
 
25
37
  Write it to `knowledge/decisions/design/`, then build the theme from it
26
- (`references/theming.md`). **From that point you are accountable to your own
38
+ (the theming reference, see SKILL.md). **From that point you are accountable to your own
27
39
  direction, not to this file.** That is the whole mechanism: the freedom is real,
28
40
  and so is the commitment.
29
41
 
@@ -34,13 +46,13 @@ chosen — it has been defaulted to in words instead of in components.
34
46
 
35
47
  Being ambitious is easier against a list of specific things to not be. Generated
36
48
  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:
49
+ but because arriving at one _by default_ means no choice was made:
38
50
 
39
- - **Stock Mantine.** The strongest pull, and the hardest to see: a component
51
+ - **Stock component-library defaults.** The strongest pull, and the hardest to see: a component
40
52
  library's untouched defaults do not look broken, they look finished. Blue
41
- accent, `#dee2e6` borders, `md` radius on everything, `Card` + `Stack` + `Text`
53
+ accent, grey borders, one radius on everything, `Card` + stacked text
42
54
  down every page. An app can be entirely this and never trip a critique, because
43
- nothing on any screen is *wrong*.
55
+ nothing on any screen is _wrong_.
44
56
  - Warm cream ground with a serif display face and a terracotta accent.
45
57
  - Near-black with one acid-green or vermilion pop.
46
58
  - A purple-to-blue gradient header on white.
@@ -71,29 +83,29 @@ screens costs a fraction of eight built screens, and it is the only point where
71
83
  "that is not what I meant" is free.
72
84
 
73
85
  **Author the theme first, then draw the mock from it.** This order is the whole
74
- point. A beautiful page in hand-rolled CSS sets a bar Mantine then misses, and
86
+ point. A beautiful page in hand-rolled CSS sets a bar the real components then miss, and
75
87
  what the user approved is not what ships — they signed off on a picture and
76
88
  received an approximation of it. So write `themes/<name>.json` first
77
- (`references/theming.md`), and let the mock take its every value from that file:
89
+ (the theming reference, see SKILL.md), and let the mock take its every value from that file:
78
90
  the palette, `structure.radius`, the spacing scale, the fonts, the component
79
91
  `defaultProps`. Approving the mock then approves the theme, and the built screens
80
92
  inherit it rather than chase it.
81
93
 
82
- **The mock has two halves, and only one of them is Mantine's.** This is the same
94
+ **The mock has two halves, and only one of them is the component library's.** This is the same
83
95
  split the built screen lives under, applied a step earlier so the two agree by
84
96
  construction. The PAGE — the shell, the regions, the columns, the rhythm, the
85
97
  material behind the content, what overlaps what — is plain HTML and your own
86
98
  CSS, arranged however the layout decision demands; that half is free, and it is
87
99
  where the design actually happens. The COMPONENTS — anything a person would
88
100
  point at and call a control, and that the app will adopt as itself: buttons,
89
- inputs, selects, tables, badges, menus, modals — are drawn as *Mantine's*, at the
90
- metrics Mantine actually uses: its control heights, its input shapes, its table
101
+ inputs, selects, tables, badges, menus, modals — are drawn as _shadcn's_, at the
102
+ metrics shadcn actually uses: its control heights, its input shapes, its table
91
103
  and menu behaviour. The test is the one the build will apply too: is this thing
92
104
  the SHAPE OF THE PAGE, or a COMPONENT someone would point at?
93
105
 
94
106
  Getting that second half wrong is what makes a mock a lie. A control the mock
95
107
  invents is a promise the app cannot keep, and a beautiful hand-rolled input sets
96
- a bar the real one misses on screen one. If the mock wants something Mantine
108
+ a bar the real one misses on screen one. If the mock wants something shadcn
97
109
  does not do, that is a real finding, and finding it here is the point: change the
98
110
  theme so it does, or change the mock, and say which.
99
111
 
@@ -102,7 +114,7 @@ needs — not a prototype, not a click-through. Static markup, real content from
102
114
  their domain (never lorem), the empty and error states beside the happy path,
103
115
  laid out so the whole app is legible by scrolling. Its CSS is custom properties
104
116
  on `:root` carrying the theme JSON's values, so a change to either is a change
105
- to one number in both. Mantine itself will not load here — it is a React library
117
+ to one number in both. The components themselves will not load here — they are React
106
118
  and a page like this has no bundler, and on hosts that sandbox the page (a Claude
107
119
  Artifact) external stylesheets are blocked outright — so do not try; the mock
108
120
  reproduces the theme's values by hand, which is why they have to be written down
@@ -126,29 +138,29 @@ stating:
126
138
  mock was wrong. Update it, and say you did. Do not let the app and the mock
127
139
  drift and then quietly prefer the app.
128
140
  - The knowledge graph still owns the domain — objects, roles, rules. The mock
129
- owns what the screens look like. When they disagree about a *fact*, knowledge
130
- wins; when they disagree about a *layout*, the mock wins.
141
+ owns what the screens look like. When they disagree about a _fact_, knowledge
142
+ wins; when they disagree about a _layout_, the mock wins.
131
143
 
132
144
  **Building it is then a transcription, not a translation.** Because the theme
133
- already exists and the mock was drawn from it, the screen is Mantine components
145
+ already exists and the mock was drawn from it, the screen is shadcn components
134
146
  arranged the way the mock arranges them — the look arrives with the theme. Two
135
147
  rules keep it that way:
136
148
 
137
- - **Layout is yours to write; components are Mantine's.** The page shape — the
149
+ - **Layout is yours to write; components are shadcn's.** The page shape — the
138
150
  regions, the columns, the rhythm, what sits beside what — is ordinary markup
139
151
  and your own CSS. Anything a person would point at and call a control comes
140
- from Mantine: buttons, inputs, selects, tables, badges, menus. Those carry
152
+ from shadcn: buttons, inputs, selects, tables, badges, menus. Those carry
141
153
  focus rings, keyboard behaviour and i18n, and hand-rolling one throws all of
142
154
  it away.
143
155
  - **A gap goes back to the theme, never into a component override.** If a screen
144
156
  does not match the mock, the fix is a value in `themes/<name>.json`. A stack of
145
- one-off `className`s and `!important` fighting Mantine's defaults looks like
157
+ one-off `className`s and `!important` fighting the component defaults looks like
146
158
  progress on screen one and is unmaintainable by screen five, and the screens
147
159
  drift apart because nothing central holds them together.
148
160
 
149
161
  **Checking the built screen against the mock** is a structural comparison, not a
150
162
  pixel one: the same regions in the same order, the same hierarchy, the same
151
- states present, the same tokens used. Do not chase pixel equality — Mantine's
163
+ states present, the same tokens used. Do not chase pixel equality — shadcn's
152
164
  components have their own metrics and the mock was drawn without them. A built
153
165
  screen that reads as the same screen has passed.
154
166
 
@@ -192,9 +204,9 @@ the thing that made it is always yes. Use evidence.
192
204
  Then drive it over CDP from a script: `Page.navigate`,
193
205
  `Emulation.setDeviceMetricsOverride` for the two widths,
194
206
  `Page.captureScreenshot`, write the PNG, and open it. Sign in the way a person
195
- does — click the dev actor switcher on the login page — rather than reaching
196
- for the secret the client uses; the UI path is shorter and it also proves the
197
- login screen works. Keep the script; you will run it at every changeset.
207
+ does — through the login page — rather than reaching for the secret the
208
+ client uses; the UI path also proves the login screen works. Keep the script; you will run it at every changeset.
209
+
198
210
  - **Run `impeccable`** (`npx impeccable install`, Node 22.18+) and feed it the
199
211
  screenshots. It is external, it does not flatter, and it scores execution
200
212
  against interaction heuristics. But it audits how well you executed the design
@@ -228,7 +240,7 @@ inventory, and it produces the wall of identical boxes further down this file.
228
240
  list.** A product has objects of its own, and the ones that carry its meaning are
229
241
  the ones no generic kit ships:
230
242
 
231
- - the thing the product is *about*, rendered as itself — a funding meter, a
243
+ - the thing the product is _about_, rendered as itself — a funding meter, a
232
244
  streak, a seat map, a run's status over time. If a decision note says the
233
245
  progress toward a goal is the primary object, then a component that draws that
234
246
  progress has to exist, or the note is describing an app you did not build.
@@ -243,7 +255,7 @@ Adding them is not hand-rolling, and they are the difference between an app that
243
255
  uses a design system and an app that looks like one.
244
256
 
245
257
  The tell that you skipped this: your `components/` directory maps one-to-one onto
246
- your data model and contains nothing that names a *quality* of the product.
258
+ your data model and contains nothing that names a _quality_ of the product.
247
259
 
248
260
  ## Design is a gate on the changeset, not a phase at the end
249
261
 
@@ -269,7 +281,7 @@ repaint is a rewrite.
269
281
  ### The seed is part of the gate
270
282
 
271
283
  A screenshot is only evidence if the screen has something on it. Before the gate
272
- runs, the dev seed must populate what each screen *is for* — not one row, and not
284
+ runs, the dev seed must populate what each screen _is for_ — not one row, and not
273
285
  an empty state.
274
286
 
275
287
  This is a real and quiet failure: a list app whose seed has no list, a countdown
@@ -280,7 +292,7 @@ designing and is not what the changeset is about.
280
292
  Seed enough to be judged against: several rows, not three identical ones, and a
281
293
  deliberate spread of the cases the screen has to hold — a long title that wraps,
282
294
  a missing optional field, a picture and no picture, one item in each state the
283
- screen can show. Anything derived from *today* — a countdown, "3 days ago", an
295
+ screen can show. Anything derived from _today_ — a countdown, "3 days ago", an
284
296
  expiry — is seeded as an interval from `now`, never as a fixed date: fixed dates
285
297
  are correct on the afternoon they are written and meaningless a month later.
286
298
 
@@ -308,8 +320,8 @@ These are not design opinions and are not open to a different answer.
308
320
  - **Empty, loading and error are states that exist.** The empty state is what a
309
321
  new user meets first and the one most often skipped entirely.
310
322
  - **Contrast and tap targets are measured, not judged.** `pikku-a11y` covers it.
311
- One trap worth knowing: a Mantine `light` variant paints its label at the
312
- generated ramp's stop, which lands just under AA on its own tint — name the
323
+ One trap worth knowing: a tinted badge paints its label in the
324
+ accent, which lands just under AA on its own tint — name the
313
325
  darker ink once and reuse it.
314
326
 
315
327
  ## Two screens that get skipped
@@ -351,7 +363,7 @@ first one.
351
363
  and a display font applied to a default layout is a well-dressed default.
352
364
  - Every border, divider and disabled control is a cool blue-grey while the
353
365
  accent is not — the surest sign the neutrals were inherited rather than
354
- chosen. See `references/theming.md`.
366
+ chosen. The theming reference (see SKILL.md) covers it.
355
367
  - It looks like the last app you built.
356
- - It looks like Mantine. Not *built with* Mantine, which it is and should be —
368
+ - It looks like stock shadcn. Not _built with_ shadcn, which it is and should be —
357
369
  but indistinguishable from a component gallery with the brand hue swapped in.
@@ -1,5 +1,19 @@
1
1
  # Pikku Create-a-Feature
2
2
 
3
+ ## Contents
4
+
5
+ - Agent Operating Procedure
6
+ - Stage 1 — Discover
7
+ - Stage 2 — State intent in plain English (BEFORE writing code)
8
+ - Stage 3 — Branch off
9
+ - Stage 4 — Implement
10
+ - Stage 5 — Verify
11
+ - Stage 6 — Commit
12
+ - Stage 7 — Hand off
13
+ - Report what fought you
14
+ - Hard constraints
15
+ - Output discipline
16
+
3
17
  The stages, in order:
4
18
 
5
19
  - [Agent Operating Procedure](#agent-operating-procedure)
@@ -257,8 +271,8 @@ Do not push without explicit confirmation. Do not merge.
257
271
  ## Report what fought you
258
272
 
259
273
  When pikku itself is what cost you time — a wrong generated type, a check that
260
- passed when it should not, a skill that misled you — file it with `pikku fabric
261
- report`. The `pikku-report` skill owns the ladder, the two kinds, the JSON-on-
274
+ passed when it should not, a skill that misled you — file it as the
275
+ `pikku-report` skill describes. That skill owns the ladder, the two kinds, the JSON-on-
262
276
  stdin form and asking the user at hand-over; read it before filing.
263
277
 
264
278
  Filing is permitted here (see **Hard constraints**), and sending what was filed
@@ -276,7 +290,7 @@ The skill's `allowed-tools` does **not** permit:
276
290
  - `pikku deploy apply` (never deploy)
277
291
  - secret writes
278
292
  - network calls beyond what the implementation requires, except
279
- `pikku fabric report`, which is explicitly permitted
293
+ filing a finding as `pikku-report` describes, which is explicitly permitted
280
294
 
281
295
  If the feature genuinely needs any of these, **stop and ask** with a clear
282
296
  explanation of why and what would change.
@@ -0,0 +1,29 @@
1
+ # Data the backend does not have yet
2
+
3
+ Read this when a screen needs data no function returns yet, or returns in a different shape. Real functions come first; mock only what is missing.
4
+
5
+ ## Three ways to get data
6
+
7
+ - `usePikkuQuery('rpc:name')` and `usePikkuMutation('rpc:name')` take only RPCs that exist, with the function's own types. `api.gen.ts` lists them. Use these whenever a function fits.
8
+ - Mocks are files, not code: `.mocks/<rpc.name>/<scenario>.json` is exactly what the RPC answers, and `<scenario>.meta.json` beside it holds `label`, `description`, `state` (healthy, empty, error or slow), `default`, `delayMs` and `status`. Exactly one scenario per RPC has `default: true`. Add an empty and an error scenario for every list and every mutation.
9
+ - `usePikkuQueryStub('rpc:name')` and `usePikkuMutationStub('rpc:name')` always answer from the mock, and their output type is inferred from the mock files. Use a stub only when the function's types are not enough: no function exists, or the shape the screen needs differs from the function's output. If the mock already fits the function, use `usePikkuQuery`.
10
+
11
+ The app loads its mocks with `registerPikkuMocks()` from the generated `mocks.gen.ts` beside `api.gen.ts`, called when `import.meta.env.DEV || import.meta.env.VITE_MOCK`. It covers the app's `.mocks/` and every wired addon's, so never glob `.mocks` or `node_modules` by hand.
12
+
13
+ ## Addon mocks
14
+
15
+ An addon ships default mocks in its own package as `.mocks/<function>/<scenario>.json`, named without a namespace, and lists `.mocks` in `files`. An app that wires the addon as `studio` gets them as `studio:<function>`. The app's `.mocks/studio.<function>/` adds scenarios; one with the same name replaces the addon's, and a `default: true` in the app wins over the addon's default. Put mocks for an addon's functions in the addon, and only the app's own variations in the app.
16
+
17
+ With `VITE_MOCK` set the plain hooks answer from the default mock too, so every state of every screen can be looked at without a backend.
18
+
19
+ ## Publishing
20
+
21
+ A stub with no `{ featureFlag }` works in development and cannot be published. `usePikkuQueryStub('rpc:name', { featureFlag: 'flag' })` can, as long as the flag is declared in the project. Production never ships mock data; a stub there is a plain call that gets a 404.
22
+
23
+ ## Making a screen real
24
+
25
+ `pikku mocks check` lists every stub and blocks the unflagged ones. For each stub: implement the function so its output matches the mock, replace the stub with `usePikkuQuery`, delete the flag if nothing else uses it. `pikku mocks diff` shows what changed, what is unused and what is dead; delete a mock directory it reports as removed.
26
+
27
+ ## Screens built on mocks
28
+
29
+ The screens are the real app's: real components, `m.*()` for every string, the theme tokens. Only the data is sample. Make it look lived in: at least six realistic records where a page lists things, real copy, tinted and textured placeholder images instead of flat grey boxes. A button that leads nowhere real stays inert.
@@ -1,5 +1,12 @@
1
1
  # Adding a second frontend
2
2
 
3
+ ## Contents
4
+
5
+ - Deciding it is two apps, not one
6
+ - The second app
7
+ - Sessions across two origins
8
+ - Building the second app's screens
9
+
3
10
  Read this when the split you recorded in Phase 2 is "separate apps" and you have
4
11
  reached the changeset that needs the second one. **Not before.** Cloning
5
12
  `apps/app` materialises a directory of copied screens; doing it during planning
@@ -14,7 +21,7 @@ The split you are acting on should already be recorded, but this is the reasonin
14
21
  behind it — and the place people get it wrong is the third case at the bottom.
15
22
 
16
23
  **A group that comes in through its own front door gets its own app.** A role
17
- *inside* an app is not that: it changes which nav items and which buttons a person
24
+ _inside_ an app is not that: it changes which nav items and which buttons a person
18
25
  sees, and lives in `useNavItems()` and the `permissions` on the function, not in a
19
26
  route subtree.
20
27
 
@@ -24,7 +31,7 @@ across the counter with an account of their own gets their own — the customer,
24
31
  tenant, the patient. One app is a real answer and often the right one.
25
32
 
26
33
  **The asymmetry that forces a split is sign-up.** Where staff accounts are created
27
- *for* people and customers create their own, the two need different sign-up, different
34
+ _for_ people and customers create their own, the two need different sign-up, different
28
35
  onboarding and different session shape, and bending one app around both costs more
29
36
  than the second app does. Do not collapse two audiences into one app to save a build.
30
37
 
@@ -34,7 +41,7 @@ Never invent a person the notes do not name in order to reach two.
34
41
 
35
42
  Some people use the product with no account at all — ordering from a menu, booking a
36
43
  table, opening an invitation. They are not a third case, and **they do not get their
37
- own frontend**: an app is built around the personas who sign into it. What they get is
44
+ own frontend**: an app is built around the virtual users who sign into it. What they get is
38
45
  the public route space every app already has.
39
46
 
40
47
  - **`/app/*` is the signed-in application.** One `beforeLoad` on `/app` bounces a
@@ -60,19 +67,19 @@ and someone else's record: if the invoices nav item is hidden but `listAllInvoic
60
67
  has no `permissions`, the app is wide open and the nav is decoration.
61
68
 
62
69
  Worth a scenario each, because they are two different claims: that a mechanic cannot
63
- *see* the invoices nav item, and that their call to an invoices RPC is *refused*. The
70
+ _see_ the invoices nav item, and that their call to an invoices RPC is _refused_. The
64
71
  second is the one that catches a `permissions` field nobody wired.
65
72
 
66
73
  ## The second app
67
74
 
68
75
  ```bash
69
- pikku app new admin --serves staff --personas manager,mechanic
76
+ pikku app new admin --serves staff --virtual-users manager,mechanic
70
77
  ```
71
78
 
72
79
  One command does every step this section used to list by hand: it fetches
73
80
  `pikkujs/starter-template`'s `apps/app`, re-points its `package.json` at the
74
81
  new name, its own dev/preview port and its own `--tsBuildInfoFile`, stamps
75
- `app: '<slug>'` onto each named persona in `definePersonas({…})`, adds the
82
+ `app: '<slug>'` onto each named virtual user in `defineVirtualUsers({…})`, adds the
76
83
  `frontends` entry to `pikku.config.json`, and re-runs `bun install`. `pikku app
77
84
  list` shows every app afterwards.
78
85
 
@@ -101,12 +108,12 @@ second app nobody needed, so the command will not create one when:
101
108
  - **An existing app already serves that audience.** People sharing an audience
102
109
  share ONE app and differ by nav and permitted actions. A new app is for a
103
110
  group the first app is not for.
104
- - **A named persona already signs into another app.** A person signs into one
111
+ - **A named virtual user already signs into another app.** A person signs into one
105
112
  app; move them out first if they really belong here.
106
113
  - **The slug is what the plan calls an app that already exists.** The plan's
107
114
  FIRST app is the one the project starts with — only the apps after it get
108
115
  created.
109
- - **A persona is not in `definePersonas({…})`.** The app is built around who
116
+ - **A virtual user is not in `defineVirtualUsers({…})`.** The app is built around who
110
117
  signs into it, so it is not created for people who do not exist yet.
111
118
 
112
119
  It also repairs its own half-states: a run that died between writing the
@@ -121,14 +128,14 @@ supervisor, a dev runner, a deploy target — belongs to whatever is hosting it.
121
128
  On a plain checkout, `bun --filter @project/<slug> dev` is enough.
122
129
 
123
130
  Packaging it as a desktop or Android app is a separate step, on the same
124
- `frontends` entry: `pikku app native init <slug>`. See
125
- `references/native-app.md`.
131
+ `frontends` entry: `pikku app native init <slug>`. The native-app reference
132
+ (see SKILL.md) covers it.
126
133
 
127
134
  ### Scenarios across the two apps
128
135
 
129
- `pikku app new` stamps `app: '<slug>'` onto each persona it is given, and that
130
- field — not the `personas` array on the `frontends` entry — is what
131
- `@pikku/playwright` resolves a persona's base url from at sign-in. A persona
136
+ `pikku app new` stamps `app: '<slug>'` onto each virtual user it is given, and that
137
+ field — not the `virtualUsers` array on the `frontends` entry — is what
138
+ `@pikku/playwright` resolves a virtual user's base url from at sign-in. A virtual user
132
139
  with no `app` lands on the fallback frontend, which is the other app's screens
133
140
  with the right session on them.
134
141
 
@@ -145,15 +152,15 @@ single `appUrl` fallback, and the CLI takes `--app-url <app>=<url>,<app>=<url>`:
145
152
 
146
153
  Three things follow, and each one has cost a run:
147
154
 
148
- - **An actor is bound to ONE app for the whole run.** The resolution happens once,
149
- at sign-in. A staff persona cannot be walked through the customer app to check
150
- something is absent from it — drive that assertion with a persona who lives
155
+ - **A virtual user is bound to ONE app for the whole run.** The resolution happens once,
156
+ at sign-in. A staff virtual user cannot be walked through the customer app to check
157
+ something is absent from it — drive that assertion with a virtual user who lives
151
158
  there, and say in the scenario's docblock why the witness is who it is. This
152
159
  binds a SIGNED-OUT scenario too, and that is where it bites: 'a visitor with no
153
- session cannot open the admin desk' driven by a customer-app persona points the
160
+ session cannot open the admin desk' driven by a customer-app virtual user points the
154
161
  browser at a router with no such route at all, so what the assertion measures is
155
162
  a 404, not the guard — and on an app with a catch-all redirect to login it
156
- passes while proving nothing. Pick the actor by which app owns the ROUTE, not by
163
+ passes while proving nothing. Pick the virtual user by which app owns the ROUTE, not by
157
164
  who should be refused, and drop their session instead.
158
165
  - **Every frontend needs its API proxy pointed at the API you are actually
159
166
  running.** `vite.config.ts` reads a variable (`VITE_API_PROXY`) with a default
@@ -1,5 +1,14 @@
1
1
  # Shipping an app as a desktop or Android app
2
2
 
3
+ ## Contents
4
+
5
+ - The commands
6
+ - Pick the mode first
7
+ - What pikku owns and what you own
8
+ - Plugins
9
+ - The identifier
10
+ - Building
11
+
3
12
  Read this when an app in `frontends` has to be installed rather than visited: a
4
13
  desktop app, an Android APK, later an iPhone app. The app is still the same
5
14
  frontend. A native app is that frontend packaged in a Tauri shell, configured on
@@ -1,5 +1,12 @@
1
1
  # An app on top of an OpenAPI spec
2
2
 
3
+ ## Contents
4
+
5
+ - Pick the auth mode
6
+ - The auth-config file
7
+ - The screens
8
+ - Scenarios
9
+
3
10
  The spec becomes an addon first — the `pikku-addon` skill's
4
11
  `references/openapi.md` covers generating, verifying and checking it against
5
12
  the real API. This reference is the decision that shapes the app around it:
@@ -7,13 +14,13 @@ the real API. This reference is the decision that shapes the app around it:
7
14
 
8
15
  ## Pick the auth mode
9
16
 
10
- | The upstream… | Mode | Command | Who signs in, and how |
11
- | ------------------------------------------------------------------------ | ----------------- | ----------------------------- | ------------------------------------------------------- |
12
- | Is where the users already have accounts, and has a login endpoint | Delegated | `--auth-config <file>` | Users sign in with their upstream login; no new account |
13
- | Takes a per-user API key, bearer token or basic auth | Per-user key | (the default) | App account, then a Connect screen for the key |
14
- | Uses OAuth2 | Per-user OAuth | (the default) | App account, then Connect via the OAuth consent |
15
- | Is used on everyone's behalf with one key the business owns | Shared secret | `--auth shared` | App account only; the key is a secret |
16
- | Takes no auth | None | `--auth none` | App account only |
17
+ | The upstream… | Mode | Command | Who signs in, and how |
18
+ | ------------------------------------------------------------------ | -------------- | ---------------------- | ------------------------------------------------------- |
19
+ | Is where the users already have accounts, and has a login endpoint | Delegated | `--auth-config <file>` | Users sign in with their upstream login; no new account |
20
+ | Takes a per-user API key, bearer token or basic auth | Per-user key | (the default) | App account, then a Connect screen for the key |
21
+ | Uses OAuth2 | Per-user OAuth | (the default) | App account, then Connect via the OAuth consent |
22
+ | Is used on everyone's behalf with one key the business owns | Shared secret | `--auth shared` | App account only; the key is a secret |
23
+ | Takes no auth | None | `--auth none` | App account only |
17
24
 
18
25
  Per-user is the default because the upstream then enforces each person's own
19
26
  permissions. A shared secret acts with one identity for everyone, so the
@@ -62,30 +69,30 @@ JSON, passed with `--auth-config`. Every field is optional except where noted.
62
69
 
63
70
  Top level — how every call authenticates:
64
71
 
65
- | Field | Meaning |
66
- | -------------- | ---------------------------------------------------------------------------------------------------- |
67
- | `headerName` | The header the API reads the token or key from. Setting it alone means a per-user API key |
72
+ | Field | Meaning |
73
+ | -------------- | ------------------------------------------------------------------------------------------------------ |
74
+ | `headerName` | The header the API reads the token or key from. Setting it alone means a per-user API key |
68
75
  | `headerFormat` | `raw` sends the bare value, `bearer` prefixes `Bearer `. Default: bearer for `Authorization`, else raw |
69
- | `extraHeaders` | Static headers on every request, login included — for upstreams that route on a header |
70
- | `delegated` | Present when users sign in with their upstream login |
76
+ | `extraHeaders` | Static headers on every request, login included — for upstreams that route on a header |
77
+ | `delegated` | Present when users sign in with their upstream login |
71
78
 
72
79
  `delegated`:
73
80
 
74
- | Field | Meaning |
75
- | ---------------- | --------------------------------------------------------------------------------------------------------- |
76
- | `loginPath` | Required. The spec-relative login operation |
77
- | `loginMethod` | Default `post` |
78
- | `credentials` | What the sign-in form collects: `login` (a username or email), `email`, `password`, `apiKey`. Default `["email","password"]` |
79
- | `fields` | The upstream's name for each credential, e.g. `{ "login": "username" }` |
80
- | `encoding` | `json`, `form` or `query` — how the login fields travel. Default `json` |
81
- | `apiKeyHeader` | The header an `apiKey` credential is sent in. Default `x-api-key` |
82
- | `tokenPath` | Required. Dot-path to the token in the login response |
83
- | `expiresAtPath` | Dot-path to an epoch-seconds expiry. Defaults to the JWT's `exp` when claims come from the JWT |
84
- | `identity` | An operation that returns the signed-in user, called with the new token — for logins that return only a token |
85
- | `claims.source` | `jwt`, `response` or `identity`. Default `identity` when `identity` is set, else `response` |
86
- | `claims.*` | Dot-paths to `externalId`, `email`, `name` (one path or several joined with a space), `role`, `tenantId` |
87
- | `emailTemplate` | Builds an email for an upstream user who has none: `{login}`, `{externalId}`, `{host}`. Such an address never claims an existing user |
88
- | `roles` | Maps the raw `claims.role` value onto an app role; an unmapped value gets no role |
81
+ | Field | Meaning |
82
+ | --------------- | ------------------------------------------------------------------------------------------------------------------------------------- |
83
+ | `loginPath` | Required. The spec-relative login operation |
84
+ | `loginMethod` | Default `post` |
85
+ | `credentials` | What the sign-in form collects: `login` (a username or email), `email`, `password`, `apiKey`. Default `["email","password"]` |
86
+ | `fields` | The upstream's name for each credential, e.g. `{ "login": "username" }` |
87
+ | `encoding` | `json`, `form` or `query` — how the login fields travel. Default `json` |
88
+ | `apiKeyHeader` | The header an `apiKey` credential is sent in. Default `x-api-key` |
89
+ | `tokenPath` | Required. Dot-path to the token in the login response |
90
+ | `expiresAtPath` | Dot-path to an epoch-seconds expiry. Defaults to the JWT's `exp` when claims come from the JWT |
91
+ | `identity` | An operation that returns the signed-in user, called with the new token — for logins that return only a token |
92
+ | `claims.source` | `jwt`, `response` or `identity`. Default `identity` when `identity` is set, else `response` |
93
+ | `claims.*` | Dot-paths to `externalId`, `email`, `name` (one path or several joined with a space), `role`, `tenantId` |
94
+ | `emailTemplate` | Builds an email for an upstream user who has none: `{login}`, `{externalId}`, `{host}`. Such an address never claims an existing user |
95
+ | `roles` | Maps the raw `claims.role` value onto an app role; an unmapped value gets no role |
89
96
 
90
97
  Check it before building on it: start `pikku dev`, sign in once with
91
98
  `POST /api/auth/sign-in/delegated` as a real upstream user, and call an exposed
@@ -93,7 +100,7 @@ operation with the session. A wrong password must be refused. Keep real
93
100
  credentials in the environment, never in the config or a test.
94
101
 
95
102
  What the install wires for delegated mode — `pikkuDelegatedAuth` in
96
- `src/auth.ts`, the token stored per user, actors carrying it into scenarios — is
103
+ `src/auth.ts`, the token stored per user, virtual users carrying it into scenarios — is
97
104
  in the `pikku-auth` skill's `references/better-auth.md`.
98
105
 
99
106
  ## The screens
@@ -107,13 +114,13 @@ in the `pikku-auth` skill's `references/better-auth.md`.
107
114
  call fails with `missing_credential`.
108
115
  - A `credential_rejected` error (`CredentialRejectedError`, 403) means the
109
116
  upstream refused the stored token: show the sign-in again (`reauth:
110
- 'sign-in'`) or the Connect screen (`reauth: 'connect'`), not a generic error.
117
+ 'sign-in'`) or the Connect screen (`reauth: 'connect'`), not a generic error.
111
118
 
112
119
  ## Scenarios
113
120
 
114
- A persona that signs in through the upstream needs an upstream credential to
115
- act with. The install adds `credentials` to `pikkuActor` in `src/auth.ts`, and
116
- at sign-in each actor stores `ACTOR_CREDENTIAL_<PERSONA>_<NAME>` from the
117
- environment (e.g. `ACTOR_CREDENTIAL_SALES_REP_DOLIBARR`). Without it every
121
+ A virtual user that signs in through the upstream needs an upstream credential to
122
+ act with. The install adds `credentials` to `pikkuVirtualUser` in `src/auth.ts`, and
123
+ at sign-in each virtual user stores `VIRTUAL_USER_CREDENTIAL_<VIRTUAL_USER>_<NAME>` from the
124
+ environment (e.g. `VIRTUAL_USER_CREDENTIAL_SALES_REP_DOLIBARR`). Without it every
118
125
  scenario step that reaches the upstream fails with `missing_credential`. The
119
- `pikku-scenario` skill's `references/personas.md` has the details.
126
+ `pikku-scenario` skill's `references/virtual-users.md` has the details.
@@ -1,12 +1,22 @@
1
1
  # Build a platform showcase on Pikku
2
2
 
3
- **This skill is a delta. `references/app.md` is the base — read it and follow it in
4
- full.** Everything there applies: knowledge base first, personas and roles,
3
+ ## Contents
4
+
5
+ - What "platform" means here
6
+ - Choosing surfaces — during app workflow §5 (planning)
7
+ - The surfaces
8
+ - Coverage — where the bar is higher than the base workflow
9
+ - The full gate
10
+ - Deploy
11
+ - Reference
12
+
13
+ **This file is a delta: it adds to the app workflow, which stays the base and is
14
+ followed in full.** Everything there applies: knowledge base first, virtual users and roles,
5
15
  changes grouped into changesets, planned then built one at a time, scenarios, design pass, deploy
6
- gates, Fabric-readiness. This file adds the surfaces that turn an app into a
16
+ gates, deploy-readiness. This file adds the surfaces that turn an app into a
7
17
  demonstration of the platform, and says where each one slots into that workflow.
8
18
 
9
- Read `references/app.md` now, then come back. Do not blend the two into one plan —
19
+ Do not blend the two into one plan —
10
20
  the phases below hang off its phases by number.
11
21
 
12
22
  ## What "platform" means here
@@ -23,7 +33,7 @@ or agent page, never the queue, scheduler or wire pages behind it.
23
33
  Budget the extra surfaces at one changeset each. They are not free, and a
24
34
  half-wired workflow engine is worse than no workflow engine.
25
35
 
26
- ## Choosing surfaces — during `references/app.md` §5 (planning)
36
+ ## Choosing surfaces — during app workflow §5 (planning)
27
37
 
28
38
  When you file the work, each surface below becomes its own changeset, ordered
29
39
  after the spine it depends on.
@@ -76,7 +86,7 @@ on a human or a timer and must survive a restart.
76
86
  gap in the middle? If every operation completes in one request, you do not need
77
87
  workflows and forcing one is noise.
78
88
  - **Prove it:** a scenario that starts the workflow, advances it as a second
79
- persona, and asserts the end state. `pikku-react` covers driving it
89
+ virtual user, and asserts the end state. `pikku-react` covers driving it
80
90
  from the UI.
81
91
  - Three workflows ship with the template. Read them before writing yours.
82
92
 
@@ -123,7 +133,7 @@ generated typed client.
123
133
  - **Motivation test:** two people looking at the same thing at the same time, or
124
134
  a long operation whose progress matters.
125
135
  - **Prove it:** a browser scenario is the only honest proof — assert the second
126
- persona's screen changed without a reload.
136
+ virtual user's screen changed without a reload.
127
137
 
128
138
  ### MCP — `pikku-wiring`
129
139
 
@@ -137,7 +147,7 @@ correct rather than a smell: a third-party caller needs a real REST shape.
137
147
 
138
148
  ### Locales — `pikku-i18n`
139
149
 
140
- `references/app.md` already requires every string to be a key. **Here, ship three
150
+ The app workflow already requires every string to be a key. **Here, ship three
141
151
  locales, and make one of them RTL** (`pikku-i18n`). Two LTR locales prove the
142
152
  plumbing; an RTL one proves the layout, and it will find real bugs — mirrored
143
153
  icons, hardcoded `marginLeft`, a nav that opens on the wrong side.
@@ -150,7 +160,7 @@ every added locale is cloned from, so three locales is `locales: ["en", …]` an
150
160
  never a repointed base. Shipping locales is also not a reason for anything in
151
161
  the code to stop being English: identifiers are English in every project, and
152
162
  the language of `description`/`title`/`template` is `metaLocale` in
153
- `pikku.config.json`. See `references/app.md` §1a.
163
+ `pikku.config.json`. The app workflow's §1a covers it.
154
164
 
155
165
  ### Emails — `pikku-emails`
156
166
 
@@ -177,7 +187,7 @@ the domain has a piece that genuinely belongs to no single app.
177
187
 
178
188
  ## Coverage — where the bar is higher than the base workflow
179
189
 
180
- `references/app.md` §7a already has the mechanics and the per-changeset habit:
190
+ The app workflow's §7a already has the mechanics and the per-changeset habit:
181
191
  run the server instrumented, run the scenarios against it, read
182
192
  `coverage/scenario-coverage.json`, and triage every gap as a missing scenario, a
183
193
  function that should not exist, or a documented deferral. Do all of that here.
@@ -196,7 +206,7 @@ Two things change in a showcase:
196
206
 
197
207
  ## The full gate
198
208
 
199
- Everything in `references/app.md` §9, plus the checks a showcase should be able to
209
+ Everything in the app workflow's §9, plus the checks a showcase should be able to
200
210
  survive:
201
211
 
202
212
  ```sh
@@ -218,7 +228,7 @@ bunx --bun pikku scenario run local --spawn --run browser
218
228
 
219
229
  ## Deploy
220
230
 
221
- `references/app.md` §9 covers the open-source paths (`--provider standalone`,
231
+ The app workflow's §9 covers the open-source paths (`--provider standalone`,
222
232
  `cloudflare`, `aws`). One thing specific to this mode: **the extra surfaces are
223
233
  extra deploy units.** Workflow workers, queue workers, schedules and the events
224
234
  channel each appear in `pikku deploy plan` as their own entries. Read the plan
@@ -227,8 +237,7 @@ built.
227
237
 
228
238
  ## Reference
229
239
 
230
- - Base workflow: `references/app.md` — read it first, follow it in full
240
+ - Base workflow: the app workflow, which this file adds to
231
241
  - Per-surface skills: `pikku-workflow`, `pikku-wiring`, `pikku-agent`,
232
242
  `pikku-i18n`, `pikku-emails`,
233
243
  `pikku-meta`, `pikku-addon`, `pikku-auth`, `pikku-services`
234
- - Every feature, end to end: https://pikkufabric.com/llm-all-features.txt