@pikku/skills 0.12.57 → 0.12.59
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/dist/skills.gen.js +2 -2
- package/package.json +10 -7
- package/skills/pikku-a11y/SKILL.md +5 -3
- package/skills/pikku-addon/SKILL.md +8 -9
- package/skills/pikku-agent/SKILL.md +16 -13
- package/skills/pikku-agent/references/runner-vercel.md +1 -1
- package/skills/pikku-architect/SKILL.md +7 -10
- package/skills/pikku-auth/SKILL.md +16 -20
- package/skills/pikku-auth/references/better-auth.md +74 -37
- package/skills/pikku-auth/references/sessions.md +8 -0
- package/skills/pikku-build/SKILL.md +36 -68
- package/skills/pikku-build/references/app.md +27 -828
- package/skills/pikku-build/references/feature.md +3 -5
- package/skills/pikku-build/references/phase-1-what-it-is.md +167 -0
- package/skills/pikku-build/references/phase-2-people-and-apps.md +133 -0
- package/skills/pikku-build/references/phase-3-plan.md +62 -0
- package/skills/pikku-build/references/phase-4-build.md +249 -0
- package/skills/pikku-build/references/phase-5-prove-and-ship.md +148 -0
- package/skills/pikku-build/references/platform.md +5 -6
- package/skills/pikku-build/references/quick.md +2 -2
- package/skills/pikku-build/references/ship.md +4 -5
- package/skills/pikku-changes/SKILL.md +13 -7
- package/skills/pikku-concepts/SKILL.md +19 -258
- package/skills/pikku-concepts/references/cli.md +63 -0
- package/skills/pikku-concepts/references/functions.md +117 -0
- package/skills/pikku-concepts/references/language.md +5 -5
- package/skills/pikku-concepts/references/packages.md +6 -4
- package/skills/pikku-concepts/references/testing.md +14 -0
- package/skills/pikku-convert-screen/SKILL.md +7 -1
- package/skills/pikku-deploy/SKILL.md +29 -30
- package/skills/pikku-deploy/references/ws.md +1 -1
- package/skills/pikku-emails/SKILL.md +7 -3
- package/skills/pikku-guide/SKILL.md +7 -10
- package/skills/pikku-i18n/SKILL.md +18 -19
- package/skills/pikku-i18n/references/messages.md +6 -8
- package/skills/pikku-knowledge/SKILL.md +42 -45
- package/skills/pikku-kysely/SKILL.md +50 -13
- package/skills/pikku-list-query/SKILL.md +27 -14
- package/skills/pikku-meta/SKILL.md +13 -16
- package/skills/pikku-meta/references/audit.md +1 -45
- package/skills/pikku-meta/references/versioning.md +1 -1
- package/skills/pikku-middleware/SKILL.md +23 -13
- package/skills/pikku-middleware/references/middleware-patterns.md +2 -2
- package/skills/pikku-n8n-import/SKILL.md +7 -8
- package/skills/pikku-permissions/SKILL.md +14 -8
- package/skills/pikku-react/SKILL.md +7 -6
- package/skills/pikku-realtime/SKILL.md +16 -11
- package/skills/pikku-report/SKILL.md +8 -10
- package/skills/pikku-scenario/SKILL.md +15 -18
- package/skills/pikku-scenario/references/steps.md +3 -4
- package/skills/pikku-scenario/references/virtual-users.md +1 -1
- package/skills/pikku-seo/SKILL.md +16 -8
- package/skills/pikku-service-backends/SKILL.md +26 -28
- package/skills/pikku-services/SKILL.md +8 -9
- package/skills/pikku-services/references/services.md +1 -1
- package/skills/pikku-software-archaeology/README.md +12 -1
- package/skills/pikku-software-archaeology/SKILL.md +9 -2
- package/skills/pikku-software-archaeology/references/pikku-mapping.md +1 -1
- package/skills/pikku-software-archaeology/references/sources/express.md +75 -0
- package/skills/pikku-software-archaeology/references/sources/laravel.md +77 -0
- package/skills/pikku-software-archaeology/references/sources/nestjs.md +73 -0
- package/skills/pikku-software-archaeology/references/sources/rails.md +72 -0
- package/skills/pikku-software-archaeology/references/tests-to-scenarios.md +72 -0
- package/skills/pikku-tailwind/SKILL.md +15 -16
- package/skills/pikku-theme/SKILL.md +15 -17
- package/skills/pikku-ui/SKILL.md +4 -3
- package/skills/pikku-webhook/SKILL.md +10 -11
- package/skills/pikku-wiring/SKILL.md +45 -47
- package/skills/pikku-wiring/references/channel.md +1 -1
- package/skills/pikku-wiring/references/scheduler.md +1 -1
- package/skills/pikku-wiring/references/trigger.md +24 -1
- package/skills/pikku-workflow/SKILL.md +7 -8
|
@@ -1,45 +1,24 @@
|
|
|
1
1
|
# Build a product on open-source Pikku
|
|
2
2
|
|
|
3
|
-
## Contents
|
|
4
|
-
|
|
5
|
-
- Agent Operating Procedure
|
|
6
|
-
- 0. Bootstrap, before anything else
|
|
7
|
-
- 1. One more round of questions — then stop asking
|
|
8
|
-
- 1a. Three languages, and you must not collapse them
|
|
9
|
-
- PHASE 1 — What the app is
|
|
10
|
-
- 2. Write the knowledge graph — before any code
|
|
11
|
-
- PHASE 2 — Who it is for, and what it is made of
|
|
12
|
-
- 3. Declare the people — virtual users and roles
|
|
13
|
-
- 4. Declare the apps — one API, several frontends
|
|
14
|
-
- PHASE 3 — The plan
|
|
15
|
-
- 5. File the work as changes
|
|
16
|
-
- 5a. The technical plan — per changeset, before you build it
|
|
17
|
-
- PHASE 4 — Build
|
|
18
|
-
- 6. Build each changeset
|
|
19
|
-
- 6a. Close the changeset against its plan, not against your memory
|
|
20
|
-
- 7. Prove it — scenarios
|
|
21
|
-
- 8. Make it look like someone designed it
|
|
22
|
-
- 9. Ship it, and stay deploy-ready
|
|
23
|
-
- Reference
|
|
24
|
-
|
|
25
3
|
You have a scaffolded project with skills installed. This skill owns everything
|
|
26
4
|
from here: no hosted account, no card, no hosted build — while keeping the
|
|
27
5
|
project shaped so a hosting platform can adopt it later with zero rework.
|
|
28
6
|
|
|
29
|
-
**
|
|
7
|
+
**Five phases, and you do not skip ahead.** This file is the index: read it,
|
|
8
|
+
then open each phase file when you reach that phase, not before. Section
|
|
9
|
+
numbers (§0-§9) are stable and are cited elsewhere as "app §N".
|
|
30
10
|
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
11
|
+
| Phase | Sections | Read |
|
|
12
|
+
| -------------------------------------------------------------- | ------------------------------------------------------------------- | --------------------------------------- |
|
|
13
|
+
| 1. What the app is — write the knowledge graph before any code | §0 bootstrap, §1 questions, §1a three languages, §2 knowledge graph | `references/phase-1-what-it-is.md` |
|
|
14
|
+
| 2. Who it is for — virtual users, roles, frontends | §3 people, §4 apps | `references/phase-2-people-and-apps.md` |
|
|
15
|
+
| 3. The plan — file the work as changes, in dependency order | §5 changes, §5a technical plan | `references/phase-3-plan.md` |
|
|
16
|
+
| 4. Build, one changeset at a time | §6 build, §6a close against the plan, §6b feed back | `references/phase-4-build.md` |
|
|
17
|
+
| 5. Prove it, make it look designed, ship it | §7 scenarios, §7a coverage, §8 look and theme (§8a, §8b), §9 ship | `references/phase-5-prove-and-ship.md` |
|
|
35
18
|
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
changes and each changeset's technical plan, §6-§6a building and closing one,
|
|
40
|
-
§7-§7a proving it, §8 design, §9 ship. Four of them have their own reference —
|
|
41
|
-
scenarios, design, multi-app and ship, each listed in SKILL.md — to read at the
|
|
42
|
-
point you need it.
|
|
19
|
+
Work through them in order rather than searching. Each changeset repeats §5a,
|
|
20
|
+
§6-§7a; §8 and §9 run when the queue is empty (design is read before the first
|
|
21
|
+
screen, though — see below).
|
|
43
22
|
|
|
44
23
|
## Agent Operating Procedure
|
|
45
24
|
|
|
@@ -54,800 +33,20 @@ point you need it.
|
|
|
54
33
|
wirings, schemas or generated clients may have changed.
|
|
55
34
|
4. If validation fails, fix the source cause and rerun. Do not paper over
|
|
56
35
|
generated errors by editing generated files.
|
|
57
|
-
5. Report at the person's level (SKILL.md, "Who you are talking to"):
|
|
58
|
-
|
|
59
|
-
developer.
|
|
60
|
-
|
|
61
|
-
##
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
`pikku virtual-user list`. Those failures look alarming (`Cannot find module
|
|
72
|
-
'#pikku/workflow/pikku-workflow-types.gen.js'`, `Schema generation failed for 16
|
|
73
|
-
schemas`) and they are nothing but this missing step.
|
|
74
|
-
|
|
75
|
-
`pikku knowledge index` and `knowledge validate` work without it — which is why
|
|
76
|
-
the planning phases below are safe either way.
|
|
77
|
-
|
|
78
|
-
## 1. One more round of questions — then stop asking
|
|
79
|
-
|
|
80
|
-
Ask **three to five** questions that actually change the schema or the screens,
|
|
81
|
-
in one message. Then stop; do not interview the user.
|
|
82
|
-
|
|
83
|
-
- **Who uses it — who are the distinct kinds of people?** Ask this however small
|
|
84
|
-
the app is. The answer becomes §3's virtual users and roles, and you build only the
|
|
85
|
-
roles they name.
|
|
86
|
-
- **What are the two or three core objects?**
|
|
87
|
-
- **What is the main thing someone does on their first visit?** This answer
|
|
88
|
-
becomes the second changeset, not the tenth.
|
|
89
|
-
- **One app or several?** Separate apps on separate hosts, or one app with paths.
|
|
90
|
-
Cheap to answer now, expensive after the routes exist.
|
|
91
|
-
- **What should it look like?** The template ships one theme — "Neutral", a
|
|
92
|
-
deliberately unopinionated monochrome scaffold — and **nothing in the
|
|
93
|
-
open-source toolchain will ever replace it for you.** Accept any of: keep
|
|
94
|
-
Neutral (fine for an internal tool, but say so out loud); a direction in words;
|
|
95
|
-
a reference (brand guide, screenshots, a site whose register they want); or
|
|
96
|
-
their own design agent/prompt, whose output you take as the direction.
|
|
97
|
-
- **Do they want to see the screens before you build them?** Offer it here, in
|
|
98
|
-
this same round, with yes marked recommended, in the words of
|
|
99
|
-
the design reference (see SKILL.md) — "a picture of the main screens so you can say 'yes,
|
|
100
|
-
like that' or 'no, move this'", never "mock" or "wireframe". Behind it is one
|
|
101
|
-
HTML page mocking the main screens, a few minutes of work. On a yes, or no
|
|
102
|
-
answer at all, the design reference (see SKILL.md) owns what to make and what it then binds — the
|
|
103
|
-
approved page becomes source of truth for the screens, and the theme is written
|
|
104
|
-
before it so what they approve is what ships. Only an explicit no skips it.
|
|
105
|
-
- **May I write test records into the system it talks to?** Ask only when the
|
|
106
|
-
app reads a live system through an addon (an ERP, a CRM) and a changeset needs
|
|
107
|
-
data that isn't there yet: an unpaid invoice, a closed ticket. Say what you
|
|
108
|
-
would create and that it will be marked "Test". A no means building those
|
|
109
|
-
screens against their empty states.
|
|
110
|
-
- **What language should the app speak, and what language does the team work
|
|
111
|
-
in?** Two answers, not one — see §1a, which is where they go. Ask only if the
|
|
112
|
-
request is not obviously English; a brief written in English about an English
|
|
113
|
-
product answers both.
|
|
114
|
-
|
|
115
|
-
Skip anything you can decide yourself. If nobody answers, assume one app with
|
|
116
|
-
paths, the roles implied by the request, Neutral, English — and say so.
|
|
117
|
-
|
|
118
|
-
## 1a. Three languages, and you must not collapse them
|
|
119
|
-
|
|
120
|
-
A brief saying "the entire UI is German" is about **one** of these. Getting this
|
|
121
|
-
wrong has already shipped a project that can never add a second language, so
|
|
122
|
-
settle all three explicitly before you write code.
|
|
123
|
-
|
|
124
|
-
| Axis | What it covers | Where it goes |
|
|
125
|
-
| --------------- | ----------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------ |
|
|
126
|
-
| **Identifiers** | Function, component, type, variable and file names. Database tables and columns. Commit messages. | Nowhere — **always English**, no setting, not negotiable |
|
|
127
|
-
| **Meta** | `description` on functions and steps, `name`/`title` on features and scenarios, step `template`, role and virtual user descriptions | `metaLocale` in `pikku.config.json`, default `en` |
|
|
128
|
-
| **Product UI** | Every string the app shows a user | `messages/<locale>.json`, and `defaultLocale` for what a first-time visitor opens in |
|
|
129
|
-
|
|
130
|
-
**Identifiers are English.** The product's market does not change this and
|
|
131
|
-
neither does `metaLocale`. Identifiers are the surface the generated `#pikku/*`
|
|
132
|
-
clients, `pikku info`, the typed RPC map and the Kysely types all bind to, and
|
|
133
|
-
unlike a string an identifier cannot be translated later — renaming one is a
|
|
134
|
-
migration. A German practice management tool gets `getWorklist`, `case`,
|
|
135
|
-
`event`, not `getUebersicht`, `vorgang`, `ereignis`.
|
|
136
|
-
|
|
137
|
-
**Meta follows `metaLocale`.** Write the team's answer into `pikku.config.json` in
|
|
138
|
-
this phase, before there is any meta to be wrong:
|
|
139
|
-
|
|
140
|
-
```json
|
|
141
|
-
{ "metaLocale": "de" }
|
|
142
|
-
```
|
|
143
|
-
|
|
144
|
-
It exists for the Pikku Console. Meta is the one part of a project the Console
|
|
145
|
-
renders back to a human, so a team working in German reads their own functions,
|
|
146
|
-
features and scenario reports in German. Default `en` and do not ask when the
|
|
147
|
-
project is obviously English. **On every later run, read this field first and
|
|
148
|
-
author descriptions, titles and step templates in it** — a project whose
|
|
149
|
-
`metaLocale` you ignored reports half in one language and half in another.
|
|
150
|
-
|
|
151
|
-
**Product UI is the message catalogue.** `messages/<locale>.json` via
|
|
152
|
-
`pikku-i18n`, with `defaultLocale` deciding what a visitor opens in.
|
|
153
|
-
`baseLocale` in `project.inlang/settings.json` **stays `en`**: it names the
|
|
154
|
-
message source, the catalogue every other language is cloned from, so a project
|
|
155
|
-
that repoints it has nothing to translate from and `--add-locale` is broken
|
|
156
|
-
forever.
|
|
157
|
-
|
|
158
|
-
A German medical portal, correctly:
|
|
159
|
-
|
|
160
|
-
```jsonc
|
|
161
|
-
// project.inlang/settings.json
|
|
162
|
-
{ "baseLocale": "en", "locales": ["en", "de"] }
|
|
163
|
-
// apps/app/src/i18n/active.json
|
|
164
|
-
{ "defaultLocale": "de" }
|
|
165
|
-
// pikku.config.json
|
|
166
|
-
{ "metaLocale": "de" }
|
|
167
|
-
```
|
|
168
|
-
|
|
169
|
-
Record the two non-obvious answers as a `decisions/` note in §2 — neither is
|
|
170
|
-
discoverable from code, and the next agent will otherwise re-derive them wrong.
|
|
171
|
-
|
|
172
|
-
---
|
|
173
|
-
|
|
174
|
-
## PHASE 1 — What the app is
|
|
175
|
-
|
|
176
|
-
## 2. Write the knowledge graph — before any code
|
|
177
|
-
|
|
178
|
-
`knowledge/` is not documentation you write at the end. It is the record of what
|
|
179
|
-
the app IS, in the words its users use, and it is the one part of the project
|
|
180
|
-
another agent picks up and continues from. **Nothing gets built until there is a
|
|
181
|
-
note saying what it is.**
|
|
182
|
-
|
|
183
|
-
Read `knowledge/index.md` and the `pikku-knowledge` skill, then write the notes
|
|
184
|
-
for what the user just told you. A project whose `knowledge/` is still only the
|
|
185
|
-
shipped index is a project nobody can resume.
|
|
186
|
-
|
|
187
|
-
Four sections, each answering exactly one question:
|
|
188
|
-
|
|
189
|
-
- `entities/` — what a thing IS, in the words users use for it
|
|
190
|
-
- `decisions/` — what was chosen and what that rules out.
|
|
191
|
-
`decisions/security/` for who may reach what, `decisions/design/` for how it
|
|
192
|
-
looks and behaves
|
|
193
|
-
- `questions/` — what you asked and never got an answer to
|
|
194
|
-
- `wishlist/` — what someone wants that nobody has asked you to build
|
|
195
|
-
|
|
196
|
-
Rules that make it a graph rather than a pile of files:
|
|
197
|
-
|
|
198
|
-
- **A note's path is its identity.** Markdown, YAML frontmatter, `type` required.
|
|
199
|
-
Cross-link notes with plain markdown links — that is what makes it a graph.
|
|
200
|
-
- **Create a section the turn you have a note for it**, with its own `index.md`
|
|
201
|
-
written in the same turn. Never scaffold empty directories, and never leave
|
|
202
|
-
notes flat at the root: a `product.md` and a `glossary.md` at `knowledge/` is
|
|
203
|
-
not a knowledge base, and it leaves the project unbuildable.
|
|
204
|
-
- **A note says what the app is, never what is left to do.** No status, no
|
|
205
|
-
todo list: the work is filed as changes in §5, and `pikku knowledge gaps`
|
|
206
|
-
says which notes no change builds yet.
|
|
207
|
-
- **Record only what pikku cannot tell you.** Tables, columns, function
|
|
208
|
-
signatures, routes, wirings, permissions and roles are all discoverable with
|
|
209
|
-
`pikku info` / `pikku meta`. Copying them into a note gives you a second copy
|
|
210
|
-
that goes stale. Knowledge is the why: decisions, constraints, what a thing
|
|
211
|
-
means.
|
|
212
|
-
|
|
213
|
-
Three decisions belong here on day one, because every later choice leans on them
|
|
214
|
-
and none is discoverable from code:
|
|
215
|
-
|
|
216
|
-
- **How the product is split into apps**, and why (§4) — `decisions/`
|
|
217
|
-
- **What each kind of person may reach**, in domain language — `decisions/security/`
|
|
218
|
-
- **What the app should look like** — the direction from §1 (§8) — `decisions/design/`
|
|
219
|
-
|
|
220
|
-
Then keep it honest — both must pass, and `validate` is a real gate:
|
|
221
|
-
|
|
222
|
-
```sh
|
|
223
|
-
bunx --bun pikku knowledge index
|
|
224
|
-
bunx --bun pikku knowledge validate
|
|
225
|
-
```
|
|
226
|
-
|
|
227
|
-
---
|
|
228
|
-
|
|
229
|
-
## PHASE 2 — Who it is for, and what it is made of
|
|
230
|
-
|
|
231
|
-
## 3. Declare the people — virtual users and roles
|
|
232
|
-
|
|
233
|
-
The answer to "who uses it" becomes code, in one file:
|
|
234
|
-
`packages/functions/src/virtual-users.ts`. It ships with a single `visitor`; add the
|
|
235
|
-
people the user named, and the roles they imply.
|
|
236
|
-
|
|
237
|
-
```typescript
|
|
238
|
-
import { defineVirtualUsers } from '#pikku/scopes/pikku-virtual-users.gen.js'
|
|
239
|
-
import { defineSystemRole } from '#pikku/scopes'
|
|
240
|
-
|
|
241
|
-
defineSystemRole({
|
|
242
|
-
owner: {
|
|
243
|
-
displayName: 'Owner',
|
|
244
|
-
description: 'Runs their own properties — sees only what they own',
|
|
245
|
-
scopes: [],
|
|
246
|
-
},
|
|
247
|
-
tenant: {
|
|
248
|
-
displayName: 'Tenant',
|
|
249
|
-
description: 'Lives in one unit — sees only their own tenancy',
|
|
250
|
-
scopes: [],
|
|
251
|
-
},
|
|
252
|
-
})
|
|
253
|
-
|
|
254
|
-
defineVirtualUsers({
|
|
255
|
-
visitor: {
|
|
256
|
-
name: 'Visitor',
|
|
257
|
-
jobTitle: 'Synthetic health-check user',
|
|
258
|
-
account: {},
|
|
259
|
-
},
|
|
260
|
-
amina: {
|
|
261
|
-
name: 'Amina',
|
|
262
|
-
jobTitle: 'Property owner',
|
|
263
|
-
personality: 'Checks arrears first, every single time',
|
|
264
|
-
roles: ['owner'],
|
|
265
|
-
account: {},
|
|
266
|
-
},
|
|
267
|
-
bilal: {
|
|
268
|
-
name: 'Bilal',
|
|
269
|
-
jobTitle: 'Property owner',
|
|
270
|
-
personality:
|
|
271
|
-
'A second owner — exists so "you see yours, not theirs" is testable',
|
|
272
|
-
roles: ['owner'],
|
|
273
|
-
account: {},
|
|
274
|
-
},
|
|
275
|
-
chidi: {
|
|
276
|
-
name: 'Chidi',
|
|
277
|
-
jobTitle: 'Tenant',
|
|
278
|
-
personality: 'Reports the boiler, wants to know it was seen',
|
|
279
|
-
roles: ['tenant'],
|
|
280
|
-
account: {},
|
|
281
|
-
},
|
|
282
|
-
})
|
|
283
|
-
```
|
|
284
|
-
|
|
285
|
-
- **Keep `visitor`.** The shipped scenarios name `virtualUsers.visitor`, and PKU677
|
|
286
|
-
requires a browser step's virtual user to be a literal `virtualUsers.<name>`. Removing it
|
|
287
|
-
stops `virtualUsers.visitor` type-checking, which fails `pikku all`, and nothing you
|
|
288
|
-
write after that registers.
|
|
289
|
-
- **One `defineVirtualUsers` call for the whole project.** Codegen builds the
|
|
290
|
-
`VirtualUserId` union from it, materialises one scenario virtual user per person, and
|
|
291
|
-
seeds a user row each. A second call site is a second answer to "who uses this
|
|
292
|
-
app".
|
|
293
|
-
- **`roles` is typechecked against `defineSystemRole`.** An undeclared role is a
|
|
294
|
-
build error rather than a runtime surprise.
|
|
295
|
-
- **Never write an email address.** Each is derived from the virtual user id and
|
|
296
|
-
`scenarios.emailDomain` in `pikku.config.json` — `visitor@virtualuser.local`.
|
|
297
|
-
Hand-writing one is how a run signs in as somebody who was never created.
|
|
298
|
-
- **Declare a second person of the same kind** whenever the rule is ownership
|
|
299
|
-
(`bilal` above). "You see yours, not theirs" is not testable with one owner,
|
|
300
|
-
and §7 is where it gets caught.
|
|
301
|
-
- **Build the roles the user's answer produces, no more.** An invented role
|
|
302
|
-
becomes invented screens and invented rules, and it is the user who has to
|
|
303
|
-
live with them.
|
|
304
|
-
- **Roles are what a permission check reads, not where it lives.** The check goes
|
|
305
|
-
in the function's `permissions` field (§6), never in the body. Read the
|
|
306
|
-
`pikku-auth` skill.
|
|
307
|
-
|
|
308
|
-
`pikku virtual-user list` shows who is declared and `pikku roles audit` reports roles
|
|
309
|
-
the database still holds that code no longer declares — both need §0's bootstrap
|
|
310
|
-
to have run, and both are worth a look once it has.
|
|
311
|
-
|
|
312
|
-
**One warning about the scaffold's own notes:** `knowledge/index.md` may claim
|
|
313
|
-
the people live in `pikku.config.json`, put there by a virtual user command.
|
|
314
|
-
That is stale. In this template they live in `virtual-users.ts` as above, and
|
|
315
|
-
`pikku.config.json` carries no virtual users key at all — only `scenarios.emailDomain`.
|
|
316
|
-
Trust the file you can read over the note describing it.
|
|
317
|
-
|
|
318
|
-
## 4. Declare the apps — one API, several frontends
|
|
319
|
-
|
|
320
|
-
### The rule that makes it cheap
|
|
321
|
-
|
|
322
|
-
**One backend, many frontends. Never fork `packages/functions`.**
|
|
323
|
-
|
|
324
|
-
Every app imports the same generated SDK (`@project/functions-sdk`) and calls the
|
|
325
|
-
same RPCs. What differs is which screens exist and what the shell looks like.
|
|
326
|
-
What must NOT differ is the data layer: two copies of a `listProperties` function
|
|
327
|
-
is two places for the permission check to be wrong.
|
|
328
|
-
|
|
329
|
-
**The app split is presentation, not security.** A tenant app that simply never
|
|
330
|
-
renders the arrears screen is not access control — it is a hidden button.
|
|
331
|
-
Security is the `permissions` field on the function, and it holds whether the
|
|
332
|
-
caller arrived from the admin origin, the tenant origin, curl, or the generated
|
|
333
|
-
client. Build the split for clarity, and prove the boundary with a refusal
|
|
334
|
-
scenario in §7.
|
|
335
|
-
|
|
336
|
-
### Choosing the split
|
|
337
|
-
|
|
338
|
-
- **Separate apps on separate hosts** (`admin.example.com`, `portal.example.com`)
|
|
339
|
-
— when the two audiences share almost no screens, when they should not see each
|
|
340
|
-
other's brand register, or when one may later ship independently.
|
|
341
|
-
- **One app with paths** (`/app/admin/*`, `/app/portal/*`) — when they share most
|
|
342
|
-
of the shell and the difference is a handful of screens. Cheaper, and honest:
|
|
343
|
-
two nav trees in one app is still two apps to a user.
|
|
344
|
-
|
|
345
|
-
Either way, write the choice and its reason into `knowledge/decisions/`.
|
|
346
|
-
|
|
347
|
-
### Adding a second frontend — later, not now
|
|
348
|
-
|
|
349
|
-
Recording the decision is Phase 2 work. **Creating the directory is not.**
|
|
350
|
-
Cloning `apps/app` materialises a folder of copied screens, so it belongs to the
|
|
351
|
-
changeset that first needs the second app, not to planning.
|
|
352
|
-
|
|
353
|
-
When you get there, read the multi-app reference (see SKILL.md). It carries the clone, the
|
|
354
|
-
`package.json` edits, the `frontends` map in `pikku.config.json`, the dev-runner
|
|
355
|
-
change that otherwise silently never starts your second app, the per-frontend
|
|
356
|
-
scenario environments, and how sessions behave across two origins.
|
|
357
|
-
|
|
358
|
-
What Phase 2 owes you now is only this: the split, its reason, and who each app
|
|
359
|
-
serves, written into `knowledge/decisions/`.
|
|
360
|
-
|
|
361
|
-
---
|
|
362
|
-
|
|
363
|
-
## PHASE 3 — The plan
|
|
364
|
-
|
|
365
|
-
## 5. File the work as changes
|
|
366
|
-
|
|
367
|
-
Turn the app into changes on the project's queue — the pikku-changes skill.
|
|
368
|
-
`pikku knowledge gaps` lists every note no change builds yet; file each as one
|
|
369
|
-
or more changes with `pikku changes file`, one commit's worth each, and
|
|
370
|
-
end every body with the gap's `Knowledge:` line so it is not filed twice.
|
|
371
|
-
|
|
372
|
-
What a change is:
|
|
373
|
-
|
|
374
|
-
- **One commit's worth.** "The owner sees this month's arrears" is a change,
|
|
375
|
-
or a few. "Add the database schema" is not; it is a step inside one.
|
|
376
|
-
- **It ends in something a person can do**, in a browser, signed in as a named
|
|
377
|
-
virtual user. If you cannot say what they see when it is done, that is a
|
|
378
|
-
`questions/` note, not a change.
|
|
379
|
-
|
|
380
|
-
If §1's screen mock was made — approved, or drawn because nobody answered —
|
|
381
|
-
every screen on that page belongs to some change, and a screen no change builds
|
|
382
|
-
is a hole in this plan.
|
|
383
|
-
|
|
384
|
-
How they group into changesets, in order:
|
|
385
|
-
|
|
386
|
-
1. **The spine first.** The one object everything else hangs off, and the screen
|
|
387
|
-
that proves the app exists at all.
|
|
388
|
-
2. **Then the loop the user named as "the main thing someone does on their first
|
|
389
|
-
visit."** That answer from §1 is the second changeset, not the tenth.
|
|
390
|
-
3. **Then each audience's own surface**, one at a time. With two apps, finish one
|
|
391
|
-
app's spine before starting the other's — a half-built app in each is worse
|
|
392
|
-
than one working app.
|
|
393
|
-
4. **Refusals ride along with the changeset that creates the thing being
|
|
394
|
-
refused**, never as a "permissions" changeset at the end.
|
|
395
|
-
|
|
396
|
-
**One approval, then build to the end.** Show the picture of the screens and
|
|
397
|
-
the changes together, in one message, as the plan. That is the only approval you
|
|
398
|
-
ask for. Once they approve it, or don't answer, run `pikku changes next --exec <harness>
|
|
399
|
-
--loop`: it hands each changeset to a fresh agent, merges it, and moves on. Post
|
|
400
|
-
one line as each changeset merges, and carry on. Stop only for what is theirs to
|
|
401
|
-
decide: a credential you don't have, spending money, posting in public, deleting
|
|
402
|
-
or overwriting their data, or a finding that changes the plan.
|
|
403
|
-
|
|
404
|
-
## 5a. The technical plan — per changeset, before you build it
|
|
405
|
-
|
|
406
|
-
`pikku changes claim` decides whether a changeset needs a **plan**: one
|
|
407
|
-
that creates or alters a table always does, and the judge decides the rest. The
|
|
408
|
-
plan says what has to exist: the tables, functions, wires, roles, scopes,
|
|
409
|
-
screens and scenarios, split into passes. It is JSON at
|
|
410
|
-
`knowledge/plans/<changeset>.plan.json`, committed on the changeset's branch,
|
|
411
|
-
and `pikku knowledge plan progress` measures the finished build against it.
|
|
412
|
-
|
|
413
|
-
**Read `pikku-architect` and follow it.** The plan is written in its own turn,
|
|
414
|
-
before any of the code it measures exists, and is never edited afterwards to
|
|
415
|
-
match what you ended up building. An item that will not land is deferred with
|
|
416
|
-
its reason — `plan defer` — not quietly rewritten:
|
|
417
|
-
|
|
418
|
-
```sh
|
|
419
|
-
pikku knowledge plan schema # the only spec there is
|
|
420
|
-
pikku knowledge plan set <changeset> /tmp/plan.json
|
|
421
|
-
pikku knowledge plan show <changeset> --for-build # what you then build
|
|
422
|
-
```
|
|
423
|
-
|
|
424
|
-
---
|
|
425
|
-
|
|
426
|
-
## PHASE 4 — Build
|
|
427
|
-
|
|
428
|
-
## 6. Build each changeset
|
|
429
|
-
|
|
430
|
-
**Per changeset** — claim it, plan it if the claim says so (§5a), do the six
|
|
431
|
-
steps one commit per change, close it out (§6a), mark its changes done. Do not
|
|
432
|
-
mark the last one done until §6a passes, §7 is green for it _and §7a shows its
|
|
433
|
-
functions covered_; `changes done` refuses a planned changeset's last change
|
|
434
|
-
while the plan is short.
|
|
435
|
-
|
|
436
|
-
1. **Migration.** SQL in `db/sqlite/` at the project root (`db/postgres/` or
|
|
437
|
-
`db/mysql/` when `createConfig` sets `postgresUrl` or `mysqlUrl`), numbered on from the
|
|
438
|
-
ones already there. Apply with `bunx --bun pikku db migrate`, which also
|
|
439
|
-
regenerates the Kysely types your functions import. **Neither `pikku all` nor
|
|
440
|
-
restarting `pikku dev` applies a migration** — so a new column reads as
|
|
441
|
-
`TS2353 … does not exist in type 'InsertExpression<DB, "…">'` on the function
|
|
442
|
-
that writes it. An unapplied migration is the usual cause and the cheapest to
|
|
443
|
-
rule out, so run `db migrate` first — but the same error is what a misspelt
|
|
444
|
-
column or a stale generated type looks like, so if it survives the migration,
|
|
445
|
-
go and read the SQL.
|
|
446
|
-
2. **Seed.** Demo rows in `db/sqlite-dev-seed.sql`. **There is no seed command** —
|
|
447
|
-
`bunx --bun pikku db reset` is the only thing that applies the file, and it
|
|
448
|
-
wipes, migrates and seeds in one go (`--no-seed` stops after the migration,
|
|
449
|
-
for working on an empty state the test data would hide). Because reset always
|
|
450
|
-
arrives at a database it just wiped, **the seed file is plain `INSERT`s** — no
|
|
451
|
-
`ON CONFLICT DO NOTHING`, no `INSERT OR IGNORE`. Nothing ever applies it
|
|
452
|
-
twice, so it never has to defend itself. It is also **local only** — no deploy
|
|
453
|
-
applies it, so anything the app would be broken without in production is
|
|
454
|
-
configuration and belongs in a migration, not here.
|
|
455
|
-
Do this generously and do it now: an empty app demos badly and critiques
|
|
456
|
-
badly, and you cannot judge a screen's hierarchy, overflow, or truncation
|
|
457
|
-
against zero rows. Seed rows each virtual user sees differently — with an ownership
|
|
458
|
-
rule that means seeding rows for the _second_ owner too.
|
|
459
|
-
3. **Functions.** One `pikkuFunc` per `*.function.ts`. Mark it `expose: true` and
|
|
460
|
-
Pikku generates the typed RPC client and the React Query hooks the UI calls;
|
|
461
|
-
you do NOT write an HTTP route for it. Add `wireHTTP` only for a real REST
|
|
462
|
-
shape (a third-party webhook).
|
|
463
|
-
4. **Regenerate:** `bunx --bun pikku all`
|
|
464
|
-
5. **UI.** Pages in `<app>/src/pages/`, one route file each in `<app>/src/routes/`,
|
|
465
|
-
calling functions through the generated `usePikkuQuery` / `usePikkuMutation`
|
|
466
|
-
hooks from `@project/functions-sdk/pikku/api.gen`. One component per `.tsx`
|
|
467
|
-
file. Compose the kit from `@/components/<Name>` rather than hand-rolling
|
|
468
|
-
controls — and **add to that kit**: the component that draws the thing this
|
|
469
|
-
product is actually about, and the furniture you would otherwise copy-paste
|
|
470
|
-
into eight pages. The kit is where you start, not where you stop; an app whose
|
|
471
|
-
every screen is `Card` + `Stack` + `Text` composed the inventory rather than a
|
|
472
|
-
design. Register the screen in `useNavItems()` — that one file feeds both the
|
|
473
|
-
desktop sidebar and the phone navigation.
|
|
474
|
-
**Read the design reference (see SKILL.md) before you write the first screen.** You commit
|
|
475
|
-
to a design direction there and are then accountable to it — it hands you no
|
|
476
|
-
layouts, because the design is yours to make. What you build here is then
|
|
477
|
-
judged at step 7, at the end of this changeset rather than once at §8, where
|
|
478
|
-
the only affordable fix is a repaint of eight screens.
|
|
479
|
-
6. **Scenario** (§7).
|
|
480
|
-
7. **Look at it.** Screenshot every screen this changeset touched, at both
|
|
481
|
-
widths, with the seed in place, and look at the images. This is a gate, the
|
|
482
|
-
same as the scenario: a changeset whose screens nobody has seen is not built,
|
|
483
|
-
it is unproven at the one layer scenarios cannot reach. the design reference
|
|
484
|
-
carries how to take the shot when no browser tool is wired up, and what to
|
|
485
|
-
look for. Then close it against its plan (§6a) — `pikku knowledge plan
|
|
486
|
-
progress` has to exit zero before the last change is done — and only then
|
|
487
|
-
mark it done, saying in the `--note` what you looked at and what it made you
|
|
488
|
-
change.
|
|
489
|
-
|
|
490
|
-
Rules that are not optional:
|
|
491
|
-
|
|
492
|
-
- A function's input and output types come from its `input:`/`output:` zod
|
|
493
|
-
schemas. Never pass generic type params, never annotate the return type inline.
|
|
494
|
-
The schema is the type. (Generics XOR schemas — never both.)
|
|
495
|
-
- Auth and permission checks go in the `permissions` field, never in the function
|
|
496
|
-
body. This is what makes §4's app split safe.
|
|
497
|
-
- No `process.env` inside a function. Read config through the injected
|
|
498
|
-
`variables` / `secrets` services; `process.env` belongs only in bootstrap. Every
|
|
499
|
-
secret a function reads needs a matching `defineSecret`, or `pikku all` reports
|
|
500
|
-
PKU951 and nobody knows what to provision at deploy.
|
|
501
|
-
- Let the database type your values, via `db/annotations.ts`. A `BOOLEAN` column
|
|
502
|
-
is derived for you. On SQLite the other two are **not** — add them by hand,
|
|
503
|
-
once, and they are typed AND coerced end-to-end:
|
|
504
|
-
|
|
505
|
-
```typescript
|
|
506
|
-
export const classifications = {
|
|
507
|
-
payment: {
|
|
508
|
-
paid_at: { kind: 'date' }, // -> Date, not an ISO string
|
|
509
|
-
metadata: { kind: 'json', tsType: 'PaymentMeta' }, // -> parsed object, not unknown
|
|
510
|
-
},
|
|
511
|
-
}
|
|
512
|
-
```
|
|
513
|
-
|
|
514
|
-
A `TIMESTAMP`/`DATETIME`/`DATE` column with no entry types as `string`, and a
|
|
515
|
-
`JSON` column with no entry types as `unknown` (the CLI warns PKU481). Add the
|
|
516
|
-
annotation rather than casting around the generated type. Once the file carries
|
|
517
|
-
manual fields, `db migrate` stops overwriting it.
|
|
518
|
-
|
|
519
|
-
- A `z.date()` on a function's **input** arrives over RPC as an ISO string, not a
|
|
520
|
-
`Date`. Normalise before calling date methods on it (`new Date(value)`), or it
|
|
521
|
-
throws `.getTime is not a function` at runtime — schema validation accepts the
|
|
522
|
-
string without converting it.
|
|
523
|
-
- Every user-facing string is a translation key. Never a hardcoded literal. With
|
|
524
|
-
two apps that means two `messages/` directories; a string used by both belongs
|
|
525
|
-
to whichever app renders it, and duplication beats a shared bundle that couples
|
|
526
|
-
the apps together.
|
|
527
|
-
- Surface errors. No empty catch, no swallowed promise. If a mutation can fail,
|
|
528
|
-
render the failure inline next to the control that triggered it — not a toast.
|
|
529
|
-
- An exposed function with no session and no permission is reachable by anyone
|
|
530
|
-
over `POST /rpc/:rpcName` (PKU574). Either gate it, drop `expose: true`, or —
|
|
531
|
-
when public is the point — write `auth: false` on it to say so.
|
|
532
|
-
- A public, signed-out read (a homepage's programme, a price list) is a
|
|
533
|
-
`pikkuSessionlessFunc` with `auth: false` written out, which is what keeps
|
|
534
|
-
PKU574 quiet for it. `pikkuFunc` with `auth: false` still answers
|
|
535
|
-
`MissingSessionError` over `/rpc` to a caller with no session.
|
|
536
|
-
- Better Auth already owns the `user`, `session`, `account` and `verification`
|
|
537
|
-
tables. A domain table with one of those names — a class _session_, a drop-in
|
|
538
|
-
_session_ — collides in the migration. Name it for the domain instead
|
|
539
|
-
(`evening`, `class_meeting`) and keep the word in the UI copy.
|
|
540
|
-
- The template's `/` redirects to `/app`, so the login screen is what a
|
|
541
|
-
signed-out visitor sees first. Replace `/` with a public homepage and a
|
|
542
|
-
reviewer lands on a site with no way in: link it to the login screen.
|
|
543
|
-
|
|
544
|
-
Before the first run, make sure `.env` at the project root holds the secret
|
|
545
|
-
the local stack needs. `bun run dev` appends it if missing, but check anyway —
|
|
546
|
-
a project scaffolded from an older template, or a `.env` copied in from
|
|
547
|
-
elsewhere, can lack it, and the failure does not name the variable:
|
|
548
|
-
|
|
549
|
-
- `BETTER_AUTH_SECRET` — without it the first sign-up is a 500.
|
|
550
|
-
|
|
551
|
-
Virtual user sign-in needs nothing in `.env`: `pikku dev` creates its own key
|
|
552
|
-
pair and hands the app the public half.
|
|
553
|
-
|
|
554
|
-
```sh
|
|
555
|
-
grep -q '^BETTER_AUTH_SECRET=' .env 2>/dev/null || echo "BETTER_AUTH_SECRET=$(openssl rand -base64 32)" >> .env
|
|
556
|
-
```
|
|
557
|
-
|
|
558
|
-
`.env` is gitignored and local only — never commit it. A deployed stage gets its
|
|
559
|
-
secrets from the hosting platform, not from this file.
|
|
560
|
-
|
|
561
|
-
Then run it:
|
|
562
|
-
|
|
563
|
-
```sh
|
|
564
|
-
bun run prebuild && bun run dev
|
|
565
|
-
```
|
|
566
|
-
|
|
567
|
-
That starts the API on :3000 and every frontend in `frontends`. A
|
|
568
|
-
frontend running against a dead API looks exactly like an app bug, so if every
|
|
569
|
-
request fails, check that both halves came up.
|
|
570
|
-
|
|
571
|
-
**Start the stack through `bun run dev`, not by launching `vite` or `pikku dev`
|
|
572
|
-
yourself.** The frontend needs to reach _your_ API. If you start the
|
|
573
|
-
frontend on its own (say :3000 is taken by another project), point
|
|
574
|
-
`VITE_API_PROXY` at your API: the dev proxy defaults to `http://localhost:3000`,
|
|
575
|
-
so beside another project's server every request goes to the wrong API.
|
|
576
|
-
|
|
577
|
-
The `--bun` in `bunx --bun pikku …` is load-bearing — keep it. Without it the
|
|
578
|
-
CLI's `#!/usr/bin/env node` shebang hands the process to whatever Node is on
|
|
579
|
-
PATH, which fails below Node 24 with `ERR_UNKNOWN_BUILTIN_MODULE: No such
|
|
580
|
-
built-in module: node:sqlite`.
|
|
581
|
-
|
|
582
|
-
Open it, sign up, and click through what you built. **HTTP 200 is not evidence.**
|
|
583
|
-
The pages are client-rendered: the server returns 200 with an empty shell, so a
|
|
584
|
-
page whose component throws still looks fine to `curl`.
|
|
585
|
-
|
|
586
|
-
That click-through is a smoke check, and it is the only thing it is. **Do not
|
|
587
|
-
hand-drive a browser tool in place of a test.** Steering Playwright yourself
|
|
588
|
-
proves a page rendered once, on your machine, in an order only you remember —
|
|
589
|
-
nothing about it re-runs, so the next agent inherits a claim rather than a test,
|
|
590
|
-
and a regression lands silently. When a journey is worth driving through the UI,
|
|
591
|
-
it is worth writing as a browser step on §7's scenario and running
|
|
592
|
-
`pikku scenario run local --spawn --run browser`: same clicks, same assertions,
|
|
593
|
-
in the repo, green or red on every future run.
|
|
594
|
-
|
|
595
|
-
## 6a. Close the changeset against its plan, not against your memory
|
|
596
|
-
|
|
597
|
-
```sh
|
|
598
|
-
pikku knowledge plan progress <changeset>
|
|
599
|
-
```
|
|
600
|
-
|
|
601
|
-
It reads §5a's plan and reconciles it against the generated meta under
|
|
602
|
-
`.pikku/` — the function exists or it does not, the route is wired or it is not,
|
|
603
|
-
the `pikkuScenario` export is there or it is not. Nothing it reports comes from
|
|
604
|
-
what anyone claimed. It exits non-zero while anything in the first pass is
|
|
605
|
-
missing, and `changes done` runs the same check on the last change.
|
|
606
|
-
|
|
607
|
-
Three things it says, and what each one asks of you:
|
|
608
|
-
|
|
609
|
-
- **MISSING** — the first pass owes it and the meta cannot see it. Either build
|
|
610
|
-
it, or, if it genuinely belongs to later work, move it out with a reason on
|
|
611
|
-
the record:
|
|
612
|
-
|
|
613
|
-
```sh
|
|
614
|
-
pikku knowledge plan defer <changeset> function:sendReminder \
|
|
615
|
-
-r "The email service it needs is the next changeset."
|
|
616
|
-
```
|
|
617
|
-
|
|
618
|
-
**A deferral is capped at two per plan.** Past that, the plan was wrong and the
|
|
619
|
-
changeset is two changesets — say so rather than deferring again. What you may
|
|
620
|
-
never do is drop the item silently. A merged changeset with deferrals leaves its
|
|
621
|
-
notes `partial`, and `pikku knowledge gaps` files what it left behind.
|
|
622
|
-
|
|
623
|
-
- **PROBLEMS** — something exists but does not do what was planned. A function
|
|
624
|
-
planned as restricted whose meta says `auth: false`; a `cascade` no migration
|
|
625
|
-
declares; a browser scenario that opens a page and asserts it is still on it.
|
|
626
|
-
These are never deferred. Fix the app.
|
|
627
|
-
- **DEFERRED to a later pass** — already accounted for. Reported so it is
|
|
628
|
-
visible, never blocking.
|
|
629
|
-
|
|
630
|
-
Do not edit the plan to match what you built — the plan was written before the
|
|
631
|
-
code on purpose, and rewriting your own denominator afterwards is exactly what
|
|
632
|
-
that order exists to stop.
|
|
633
|
-
|
|
634
|
-
### 6b. Feed the changeset back into the seats
|
|
635
|
-
|
|
636
|
-
Before starting the next changeset, answer two questions out loud:
|
|
637
|
-
|
|
638
|
-
- **What did the plan fail to say?** A field nothing wrote, a promise no function
|
|
639
|
-
could keep, a pass 1 that turned out to be two. That is a `pikku-architect`
|
|
640
|
-
lesson.
|
|
641
|
-
- **What did the build learn the hard way?** Anything that cost a wasted run —
|
|
642
|
-
a stale process, a scenario that only passes once, a diagnostic that turned
|
|
643
|
-
out to be an echo of an earlier one. That is a `pikku-build` lesson.
|
|
644
|
-
|
|
645
|
-
Then edit the skill — **at most one change to each per changeset**, and only for
|
|
646
|
-
something that actually went wrong here. A rule with no incident behind it is a
|
|
647
|
-
guess, and these files are read in full every time: they earn their length by
|
|
648
|
-
naming failures a reader would otherwise repeat. Prefer sharpening an existing
|
|
649
|
-
line to appending a new one, and delete a rule the last few changesets have
|
|
650
|
-
shown to be noise.
|
|
651
|
-
|
|
652
|
-
The gates are the compounding part. A lesson written into a scenario the suite
|
|
653
|
-
runs, or into a check `plan progress` can see, is enforced; the same lesson
|
|
654
|
-
written as prose is a thing the next reader has to remember. Reach for prose
|
|
655
|
-
only when there is nothing to hang a check on.
|
|
656
|
-
|
|
657
|
-
## 7. Prove it — scenarios
|
|
658
|
-
|
|
659
|
-
A scenario is a user journey run as one of your virtual users, over the real
|
|
660
|
-
transport, with that virtual user's session. It is the only kind of test worth writing
|
|
661
|
-
here, because a passing one proves the app works the way a signed-in person
|
|
662
|
-
experiences it. Three ship in `packages/functions/test/scenarios/` — keep them
|
|
663
|
-
green — and every scenario a changeset's plan names becomes one more.
|
|
664
|
-
|
|
665
|
-
**Read the scenarios reference (see SKILL.md) before writing the changeset's scenarios,
|
|
666
|
-
and again whenever one of these describes what you are doing.** It is the file
|
|
667
|
-
where the expensive lessons live, and most of them produce a GREEN suite that
|
|
668
|
-
proves nothing:
|
|
669
|
-
|
|
670
|
-
- the shape of a scenario, and `do` vs `given`/`when`/`then`
|
|
671
|
-
- what survives DSL extraction — a `then` in `return` position asserts nothing,
|
|
672
|
-
and a shared setup helper credits its steps to the wrong virtual user
|
|
673
|
-
- what to assert — refusals must read the REASON, totals must be deltas, and the
|
|
674
|
-
assertion nobody writes is the row count
|
|
675
|
-
- living without a state reset: the suite must be green on its SECOND run
|
|
676
|
-
- how a shared step rots as later changesets add writers of the rows it selects
|
|
677
|
-
- browser specifics — the click/navigate race, testids, and `Outlet` nesting
|
|
678
|
-
|
|
679
|
-
Run them:
|
|
680
|
-
|
|
681
|
-
```sh
|
|
682
|
-
bunx --bun pikku scenario run local --spawn # server-side, the fast path
|
|
683
|
-
bunx --bun pikku scenario run local --spawn --run browser # the same journeys, driven as a human
|
|
684
|
-
```
|
|
685
|
-
|
|
686
|
-
In a multi-app project that one run covers both frontends: each virtual user carries
|
|
687
|
-
its own `app` and `@pikku/playwright` resolves the base url from the
|
|
688
|
-
environment's `appUrls` map, so there is no second environment to run.
|
|
689
|
-
|
|
690
|
-
**Run the whole suite, not the changeset's own scenarios.** The changeset's
|
|
691
|
-
scenarios are the ones you wrote to pass; the regression lives in someone
|
|
692
|
-
else's. Tightening what "archived" means is a one-function change that reads as
|
|
693
|
-
local and quietly breaks the first changeset's scenario nobody re-ran.
|
|
694
|
-
|
|
695
|
-
**Restart the server after adding a function, and never edit one while a run is
|
|
696
|
-
in flight.** Hot reload does not register a new RPC and does not re-run
|
|
697
|
-
`afterStart`, so a fresh function answers 404 and anything provisioned at boot
|
|
698
|
-
is missing — failures that read like a wiring bug and are nothing but a stale
|
|
699
|
-
process. The same reload is what makes a run unrepeatable if you edit during
|
|
700
|
-
it: a browser pass is long enough to feel like free time, and a schema touched
|
|
701
|
-
at minute four hot-reloads into a half-generated contract, so every scenario
|
|
702
|
-
after that point fails on something you have already fixed. Wait for the run or
|
|
703
|
-
kill it — a run you edited under is not a result.
|
|
704
|
-
|
|
705
|
-
### 7a. Coverage — which functions have actually been run
|
|
706
|
-
|
|
707
|
-
Green scenarios tell you the journeys you wrote still work. They say nothing
|
|
708
|
-
about the code you never wrote a journey for:
|
|
709
|
-
|
|
710
|
-
```sh
|
|
711
|
-
bunx --bun pikku dev --coverage # server, instrumented
|
|
712
|
-
bunx --bun pikku scenario run local --coverage # against that server
|
|
713
|
-
```
|
|
714
|
-
|
|
715
|
-
**A function no scenario touches has no scenario coverage** — the file knows
|
|
716
|
-
what the suite exercises and nothing else, so a unit test, a scheduled job, a
|
|
717
|
-
webhook or a hand call leaves no trace in it. Read
|
|
718
|
-
`coverage/scenario-coverage.json` **as each changeset closes** — per changeset it is a short list you can act on, whereas read for the
|
|
719
|
-
first time after ten changesets it is a wall of red nobody triages. Every gap is
|
|
720
|
-
a missing scenario, a function that should not exist, or a deferral worth
|
|
721
|
-
writing down; the scenarios reference (see SKILL.md) says how to tell them apart. Report
|
|
722
|
-
the number when you hand the changeset over.
|
|
723
|
-
|
|
724
|
-
## 8. Make it look like someone designed it
|
|
725
|
-
|
|
726
|
-
**This section numbers 8, but half of it has already happened.** Read
|
|
727
|
-
the design reference (see SKILL.md) before the first screen is built — a design pass run on
|
|
728
|
-
eight changesets of scaffolded screens is a repaint, and it shows. What is left
|
|
729
|
-
here at §8 is the theme you may have deferred and the critique you cannot run
|
|
730
|
-
until there are screens to critique.
|
|
731
|
-
|
|
732
|
-
Three separate jobs, and conflating them is why open-source builds come out
|
|
733
|
-
looking like the template:
|
|
734
|
-
|
|
735
|
-
- **Direction** — deciding what it should look like. **No open-source tool does
|
|
736
|
-
this.** A hosting platform may offer one; you have §1's answer and 8a below.
|
|
737
|
-
- **Execution** — whether the screens actually realise that direction, or
|
|
738
|
-
default to whatever component was nearest. No theme does this, and it is where
|
|
739
|
-
"works but looks like nobody decided anything" comes from.
|
|
740
|
-
the design reference (see SKILL.md) carries the process for it, and it belongs at §6, per
|
|
741
|
-
screen.
|
|
742
|
-
- **Critique** — judging how well the built screens execute the direction.
|
|
743
|
-
`impeccable` does this well, and it is free.
|
|
744
|
-
|
|
745
|
-
Impeccable audits the design you chose. It will never tell you the app should
|
|
746
|
-
have looked like something else — it will happily award a clean bill of health to
|
|
747
|
-
a perfectly-executed default. Skip the first two and you ship Neutral with good
|
|
748
|
-
spacing.
|
|
749
|
-
|
|
750
|
-
### 8a. Author the theme — the step nothing does for you
|
|
751
|
-
|
|
752
|
-
The look lives in `packages/theme`, and it is data, not code — one JSON
|
|
753
|
-
per theme, `active.json` naming the live one. **Read the theming reference (see SKILL.md)** for
|
|
754
|
-
the file layout, what each field changes, and how to turn a direction in words
|
|
755
|
-
into a theme.
|
|
756
|
-
|
|
757
|
-
Two things that belong here rather than in the reference, because they govern
|
|
758
|
-
every screen you then build:
|
|
759
|
-
|
|
760
|
-
**Set the theme once, don't hardcode colours per component.** A screen full of
|
|
761
|
-
inline `color="blue"` and one-off hex values is why apps look templated. Change
|
|
762
|
-
the theme, not the components — and keep it theme-aware for light and dark.
|
|
763
|
-
|
|
764
|
-
With two apps, **share the theme package and vary the register, not the
|
|
765
|
-
palette.** A back-office can be denser and more tabular; a customer-facing app
|
|
766
|
-
can be roomier and warmer — that is `structure` and layout, not a second `brand`.
|
|
767
|
-
Two unrelated colour schemes read as two products from two companies.
|
|
768
|
-
|
|
769
|
-
Then **write the direction into `knowledge/decisions/design/`** — the words the
|
|
770
|
-
user gave you, what you chose, and what it rules out. The JSON records what the
|
|
771
|
-
theme is; only the note records why.
|
|
772
|
-
|
|
773
|
-
### 8b. Compose real components, then critique
|
|
774
|
-
|
|
775
|
-
**The design reference (see SKILL.md) is where design actually lives** — committing to a
|
|
776
|
-
direction before the first screen, judging the result from screenshots rather
|
|
777
|
-
than from source, the two screens that get skipped, and why you reset the dev
|
|
778
|
-
database before judging anything. It prescribes no layouts on purpose: two apps
|
|
779
|
-
built from this skill should not look like each other. What follows here is only
|
|
780
|
-
the component inventory.
|
|
781
|
-
|
|
782
|
-
**Compose with rich components — not tables and text everywhere:**
|
|
783
|
-
|
|
784
|
-
- **shadcn `chart`** (Recharts underneath, coloured by `--chart-1` to `--chart-5`) for overviews — area,
|
|
785
|
-
bar, line and pie charts. A metric worth showing is
|
|
786
|
-
worth a chart, not a number in a paragraph.
|
|
787
|
-
- **shadcn `calendar` and `date-picker`** for anything time-based, including
|
|
788
|
-
range inputs. Never hand-roll a date field.
|
|
789
|
-
- Composed layouts over flat lists — a timeline for history, steps for
|
|
790
|
-
multi-step progress, `Card` + a CSS grid for a gallery, `Progress` for
|
|
791
|
-
completion, `Badge` for status.
|
|
792
|
-
|
|
793
|
-
Add each one with the shadcn CLI and use the real component.
|
|
794
|
-
|
|
795
|
-
Then critique it. Free, and works across coding agents:
|
|
796
|
-
|
|
797
|
-
```sh
|
|
798
|
-
npx impeccable install # current releases need Node 22.18+
|
|
799
|
-
```
|
|
800
|
-
|
|
801
|
-
Impeccable scores a screen against interaction heuristics and names what is
|
|
802
|
-
wrong: hierarchy, spacing, type registers, states you forgot. Run it on **every**
|
|
803
|
-
screen in **every** app, fix what it finds, and re-run the ones you changed.
|
|
804
|
-
|
|
805
|
-
Screenshot each page and feed it the images. Without them its findings drop to
|
|
806
|
-
inference from source, and it misses real misalignment, contrast, and overflow.
|
|
807
|
-
Judging your own UI from source code is guessing.
|
|
808
|
-
|
|
809
|
-
**Screenshot at a phone width too (≈390px), not just desktop, and critique
|
|
810
|
-
those.** A layout that is fine at 1440px routinely breaks at 390 — a table that
|
|
811
|
-
overflows, a row of buttons that wraps into a pile, text jammed against the edge,
|
|
812
|
-
a modal taller than the viewport. Tailwind gives you the tools (responsive `grid-cols-*`,
|
|
813
|
-
`md:hidden` / `hidden md:block`, `flex-col` instead of `flex-row` at small sizes); use
|
|
814
|
-
them. The template already mounts a phone navigation per `AGENTS.md` — pick
|
|
815
|
-
`MobileTabBar` or `MobileNavDrawer` deliberately per app, never both.
|
|
816
|
-
|
|
817
|
-
The gate: **no P0 findings left on any screen, in any app, at either width**,
|
|
818
|
-
and every screen honestly answers the questions in the design reference —
|
|
819
|
-
including whether it looks like the direction you committed to or like the
|
|
820
|
-
components you had. Don't silence a finding by deleting the feature it is
|
|
821
|
-
about.
|
|
822
|
-
|
|
823
|
-
**Critique the data too, not just the layout.** Scenario runs write run-tagged
|
|
824
|
-
rows into the dev database, so by §8 the app is full of `Ripe peaches d193e2aa`
|
|
825
|
-
seven copies deep. Run `pikku db reset` and restart the dev server before you
|
|
826
|
-
screenshot anything — a screen judged against that data gets designed for a
|
|
827
|
-
problem it does not have.
|
|
828
|
-
|
|
829
|
-
## 9. Ship it, and stay deploy-ready
|
|
830
|
-
|
|
831
|
-
When the queue is empty, `pikku knowledge gaps` lists nothing and the scenarios are green, read
|
|
832
|
-
the ship reference (see SKILL.md). It carries the open-source deploy paths (`--provider
|
|
833
|
-
standalone`, `cloudflare`, `aws`), how to serve several frontends behind one
|
|
834
|
-
API, the pre-release gate to run, and the contract that keeps the project
|
|
835
|
-
importable by a hosting platform later rather than a migration.
|
|
836
|
-
|
|
837
|
-
The app is not handed over without its user guide. The scenarios you wrote are
|
|
838
|
-
already its skeleton: read **pikku-guide**, write one page per audience citing
|
|
839
|
-
every feature, and build it from a full, passing `--run browser --screenshots`
|
|
840
|
-
run.
|
|
841
|
-
|
|
842
|
-
Two things from it are worth knowing before you get there, because they are
|
|
843
|
-
cheaper to honour than to retrofit:
|
|
844
|
-
|
|
845
|
-
- **Nothing hardcodes a host, a port, or a `process.env` read inside a
|
|
846
|
-
function.** Secrets go through `defineSecret` and the injected `secrets`
|
|
847
|
-
service. This is the most common reason a working local project fails its
|
|
848
|
-
first deploy, on any platform.
|
|
849
|
-
- **Generated files stay generated.** No hand edits to `.pikku/`, `*.gen.*`, or
|
|
850
|
-
the SDK.
|
|
36
|
+
5. Report at the person's level (SKILL.md, "Who you are talking to"): plain
|
|
37
|
+
words, the app and API links `bun run dev` printed, and no code unless they
|
|
38
|
+
are a developer.
|
|
39
|
+
|
|
40
|
+
## Rules that hold in every phase
|
|
41
|
+
|
|
42
|
+
- **Identifiers are always English**; meta follows `metaLocale`; product UI strings are
|
|
43
|
+
translation keys (§1a, §6).
|
|
44
|
+
- **Permissions live in a function's `permissions` field**, never in its body or in a frontend (§4, §6).
|
|
45
|
+
- **No `process.env` inside a function**; secrets go through `defineSecret` and the injected `secrets` service (§6, §9).
|
|
46
|
+
- **Generated files stay generated** — never hand-edit `.pikku/`, `*.gen.*`, or the SDK.
|
|
47
|
+
- **Nothing is built until a knowledge note says what it is** (§2), and a plan is never edited to match what you built (§5a, §6a).
|
|
48
|
+
- **Every changeset ends proven**: a passing scenario including its refusals (§7), screens looked at (§6), functions covered (§7a).
|
|
49
|
+
- **One approval, then build to the end** (§5). Stop only for what is theirs to decide.
|
|
851
50
|
|
|
852
51
|
## Reference
|
|
853
52
|
|