@pikku/skills 0.12.37 → 0.12.39
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 +1 -1
- package/package.json +1 -1
- package/skills/pikku-addon/SKILL.md +30 -0
- package/skills/pikku-addon/references/openapi.md +69 -38
- package/skills/pikku-auth/references/better-auth.md +17 -2
- package/skills/pikku-build/SKILL.md +80 -9
- package/skills/pikku-build/references/app.md +33 -11
- package/skills/pikku-build/references/design.md +16 -5
- package/skills/pikku-build/references/multi-app.md +51 -76
- package/skills/pikku-build/references/openapi.md +119 -0
- package/skills/pikku-build/references/platform.md +4 -0
- package/skills/pikku-build/references/quick.md +4 -3
- package/skills/pikku-knowledge/SKILL.md +21 -22
- package/skills/pikku-scenario/SKILL.md +9 -8
- package/skills/pikku-scenario/references/personas.md +65 -3
package/package.json
CHANGED
|
@@ -359,6 +359,36 @@ export const myFunc = pikkuFunc({
|
|
|
359
359
|
})
|
|
360
360
|
```
|
|
361
361
|
|
|
362
|
+
### Wrap an addon function only to reshape it
|
|
363
|
+
|
|
364
|
+
A screen that shows the addon's data as the addon returns it calls the addon
|
|
365
|
+
function itself: name it in `wireAddon({ expose: ['listTodos'], auth: true })`
|
|
366
|
+
and the frontend calls `rpc.invoke('todos:listTodos', …)` (over HTTP,
|
|
367
|
+
`POST /rpc/todos:listTodos` with `{ "data": … }`), still behind the session.
|
|
368
|
+
Use `ref('todos:listTodos')` on an HTTP or MCP wiring only when the addon needs
|
|
369
|
+
a route of its own. Don't write an app function that calls `rpc.invoke` and
|
|
370
|
+
returns the result unchanged: it's a second name and a second schema for the
|
|
371
|
+
same thing, and it drifts. `expose: true` exposes only what the addon itself
|
|
372
|
+
declared `expose: true` — an OpenAPI-generated addon declares none, so list
|
|
373
|
+
the names.
|
|
374
|
+
|
|
375
|
+
Write your own function when the app needs the data narrowed, typed, or
|
|
376
|
+
combined (a flag the upstream sends as `"0"`, a total summed from several
|
|
377
|
+
calls, one field out of fifty), or when the app adds a permission of its own.
|
|
378
|
+
`wireAddon`'s `scopes` gate every function in the addon at once, and a wiring
|
|
379
|
+
carries middleware, not permissions, so a rule on one addon function lives in
|
|
380
|
+
the `permissions` of an app function that calls it. An addon called with the
|
|
381
|
+
user's own credential is already limited upstream to what that user may do, so
|
|
382
|
+
a data-aware `pikkuPermission` repeating that check (may they read *this*
|
|
383
|
+
invoice?) adds nothing. A session-only `pikkuAuth` (a role, a tier) is still
|
|
384
|
+
worth it: it can be checked before any input exists, so the functions a user
|
|
385
|
+
can't call drop out of the tools an MCP client, an agent or a workflow is
|
|
386
|
+
offered, instead of failing upstream when called. Name it for what the screen means
|
|
387
|
+
(`getMyProfile`), not after the upstream operation (`usersRetrieveInfo`), and
|
|
388
|
+
give it an `output:` schema of only what the app uses. For an OpenAPI-generated
|
|
389
|
+
addon this matters more: its outputs mirror the upstream's loose, oversized
|
|
390
|
+
payloads, and the wrapper is where they become the app's own shape.
|
|
391
|
+
|
|
362
392
|
### Wire to HTTP
|
|
363
393
|
|
|
364
394
|
```typescript
|
|
@@ -13,67 +13,81 @@ A JSON or YAML document with a top-level `openapi` key (3.x) or `swagger` key
|
|
|
13
13
|
(2.0), and a `paths` object. A URL ending in `openapi.json`, `swagger.json` or
|
|
14
14
|
`.yaml` is almost always one; open it and check the key before generating.
|
|
15
15
|
|
|
16
|
-
## 1 —
|
|
16
|
+
## 1 — Look at the spec first
|
|
17
17
|
|
|
18
|
-
`--openapi`
|
|
19
|
-
|
|
18
|
+
`--openapi` takes a path or a URL. A spec published only to signed-in callers
|
|
19
|
+
takes the key the way the API reads it: `--openapi-header "NAME: value"`
|
|
20
|
+
(repeatable), or in the URL's query string when the API reads it there
|
|
21
|
+
(Dolibarr's explorer takes `?DOLAPIKEY=`). A 401 while fetching says which.
|
|
20
22
|
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
```
|
|
23
|
+
The generator warns loudly when the spec has fewer than five operations, or only
|
|
24
|
+
auth routes. That is almost always the public half of a spec that shows more
|
|
25
|
+
to an authenticated caller — fetch it again with the key, don't build on it.
|
|
25
26
|
|
|
26
|
-
|
|
27
|
+
Keep a copy in `specs/` as the record of what the addon was generated from.
|
|
27
28
|
|
|
28
|
-
|
|
29
|
+
## 2 — Generate, from the app's root
|
|
29
30
|
|
|
30
31
|
```bash
|
|
31
|
-
bunx --bun pikku new addon <name> --openapi
|
|
32
|
+
bunx --bun pikku new addon <name> --openapi <path-or-url>
|
|
32
33
|
```
|
|
33
34
|
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
35
|
+
One command. Inside an app it writes `packages/addon-<name>` as
|
|
36
|
+
`@pikku/addon-<name>`, then installs it into the app:
|
|
37
|
+
|
|
38
|
+
- the dependency in the root and the functions `package.json`
|
|
39
|
+
- `src/addons/<name>.addon.ts` — `wireAddon` with `auth: true` and an explicit
|
|
40
|
+
`expose` list (every operation in per-user modes, only the `GET`s behind a
|
|
41
|
+
shared secret)
|
|
42
|
+
- the auth wiring in `src/auth.ts` for the chosen mode
|
|
43
|
+
- `<NAME>_BASE_URL` in `.env`
|
|
44
|
+
|
|
45
|
+
and then runs install and the addon's build. `--no-install` generates the
|
|
46
|
+
package alone.
|
|
47
|
+
|
|
48
|
+
| Flag | When |
|
|
49
|
+
| -------------------------------------------- | -------------------------------------------------------------------------------- |
|
|
50
|
+
| `--auth user` (default) | Each user brings their own credential |
|
|
51
|
+
| `--auth shared` | One secret behind every user; locally it goes in `.env` |
|
|
52
|
+
| `--auth none` | The API really takes no auth |
|
|
53
|
+
| `--credential apikey\|bearer\|basic\|oauth2` | Override what the spec's `securitySchemes` declares |
|
|
54
|
+
| `--auth-config <file>` | Users sign in with their upstream login, or the spec gets auth wrong |
|
|
55
|
+
| `--tags a,b` / `--include` / `--exclude` | Keep part of a huge spec: tags, or globs on operationId, `/path`, `METHOD /path` |
|
|
56
|
+
| `--mcp` | The operations should also be MCP tools |
|
|
57
|
+
| `--camel-case` | The API's property names are snake_case and the app's are not |
|
|
58
|
+
|
|
59
|
+
The mode comes from the spec unless a flag says otherwise. A spec with no
|
|
60
|
+
machine-readable auth is refused rather than guessed: pass one of the flags the
|
|
61
|
+
error names. Which mode fits, and the auth-config format, are in the
|
|
62
|
+
`pikku-build` skill's `references/openapi.md`.
|
|
48
63
|
|
|
49
64
|
## 3 — Check what was generated
|
|
50
65
|
|
|
51
66
|
```
|
|
52
67
|
packages/addon-<name>/
|
|
68
|
+
├── <name>.svg # placeholder icon; replace with the real one
|
|
53
69
|
├── src/<name>-api.service.ts # one fetch wrapper, reads <NAME>_BASE_URL
|
|
54
|
-
├── src/<name>.variable.ts # <NAME>_BASE_URL,
|
|
70
|
+
├── src/<name>.variable.ts # <NAME>_BASE_URL: z.string().url(), the first server as default
|
|
55
71
|
├── src/functions/<op>.function.ts
|
|
56
72
|
├── src/functions/<op>.schemas.ts # the op's zod schemas — never import #pikku here
|
|
57
73
|
└── src/index.ts # re-exports every function
|
|
58
74
|
```
|
|
59
75
|
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
to `z.string().url()` so each deployment sets its own.
|
|
64
|
-
|
|
65
|
-
## 4 — Wire it into the app
|
|
76
|
+
An operation whose spec gives no response, or one too vague to validate
|
|
77
|
+
against, outputs `z.unknown()`. Tighten it in `<op>.schemas.ts` once §6 shows
|
|
78
|
+
what the API really returns.
|
|
66
79
|
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
80
|
+
An upstream 401 on a per-user credential throws `CredentialRejectedError`
|
|
81
|
+
(403, `reauth: 'sign-in' | 'connect'`); a UI shows the matching screen again
|
|
82
|
+
rather than a generic error.
|
|
70
83
|
|
|
71
|
-
|
|
72
|
-
```
|
|
84
|
+
## 4 — Call it from the app
|
|
73
85
|
|
|
74
|
-
|
|
86
|
+
Operations are reached by reference — `ref('<name>:<operationFn>')` in a
|
|
75
87
|
workflow, agent tool or HTTP wiring — the same way as any other addon. See
|
|
76
|
-
"Consuming an Addon" in the skill.
|
|
88
|
+
"Consuming an Addon" in the skill. An exposed operation is also callable from
|
|
89
|
+
the frontend at `POST /rpc/<name>:<operationFn>` with a body of
|
|
90
|
+
`{ "data": { … } }`, as the signed-in user.
|
|
77
91
|
|
|
78
92
|
## 5 — Verify
|
|
79
93
|
|
|
@@ -92,6 +106,23 @@ another shared package. Codegen then reads the app's
|
|
|
92
106
|
schemas with the wrong copy and fails on schemas that are correct. Pin one
|
|
93
107
|
version for the whole install, as the finding says, and reinstall.
|
|
94
108
|
|
|
109
|
+
## 6 — Check the spec against the real API
|
|
110
|
+
|
|
111
|
+
Specs are often wrong, and the generated schemas repeat every mistake. Before
|
|
112
|
+
building on the addon:
|
|
113
|
+
|
|
114
|
+
- **Call the reads you can.** The `GET`s the credential can reach, following ids
|
|
115
|
+
from lists into retrieves. Writes only if the user opts in.
|
|
116
|
+
- **Fix the addon, not the app**: the schema in the op's `<op>.schemas.ts`, or the
|
|
117
|
+
request shape in `src/<name>-api.service.ts`. Then rebuild it.
|
|
118
|
+
- **List each mismatch in `packages/addon-<name>/SPEC-ISSUES.md`**: a title and a
|
|
119
|
+
short description. No credentials or customer data.
|
|
120
|
+
|
|
121
|
+
Then tell the user in one line: "FYI, the spec deviates from the real API in
|
|
122
|
+
N ways: [SPEC-ISSUES.md](…)". Add to the file whenever a later call disagrees
|
|
123
|
+
with its schema. Ask before sending it to the API's maintainers, because an issue
|
|
124
|
+
on their tracker is a public post.
|
|
125
|
+
|
|
95
126
|
## Then
|
|
96
127
|
|
|
97
128
|
Go back to the mode you were building in (`pikku-build`). The addon is a
|
|
@@ -307,7 +307,7 @@ singleton a 403 that leaves no platform user behind.
|
|
|
307
307
|
|
|
308
308
|
```typescript
|
|
309
309
|
pikkuDelegatedAuth({
|
|
310
|
-
authenticate: async ({ email, password, apiKey }) => upstream.login(...),
|
|
310
|
+
authenticate: async ({ login, email, password, apiKey }) => upstream.login(...),
|
|
311
311
|
storeCredential: (userId, identity) =>
|
|
312
312
|
credentialService.set('acme', identity.credential, userId),
|
|
313
313
|
defaultRole: 'member',
|
|
@@ -318,7 +318,10 @@ pikkuDelegatedAuth({
|
|
|
318
318
|
```
|
|
319
319
|
|
|
320
320
|
`POST /sign-in/delegated` forwards the credentials the user already has to
|
|
321
|
-
`authenticate`.
|
|
321
|
+
`authenticate`. The body takes `email`, `login` or `username` with `password`
|
|
322
|
+
(or `apiKey`); whichever identifier was sent reaches `authenticate` as
|
|
323
|
+
`credentials.login`, and `email` as well when it was one. Upstreams that sign in
|
|
324
|
+
with a username — most ERPs — need nothing more. On success it JIT-provisions a real user row (email-keyed and
|
|
322
325
|
`emailVerified` — the upstream just verified them), links it via an `account`
|
|
323
326
|
row (`providerId: 'delegated'`, `accountId: externalId`), persists the upstream
|
|
324
327
|
token **before** minting the session, and returns a normal session cookie.
|
|
@@ -333,6 +336,18 @@ a warning and the user still gets in.
|
|
|
333
336
|
`storeCredential` failing, by contrast, **fails the sign-in**: every proxied
|
|
334
337
|
call would be dead anyway.
|
|
335
338
|
|
|
339
|
+
An upstream user with no email gets one made up from the login, and the
|
|
340
|
+
identity says so with `syntheticEmail: true`. A made-up address never links to
|
|
341
|
+
an existing user row, so it cannot take over someone else's account.
|
|
342
|
+
|
|
343
|
+
For an addon generated from an OpenAPI spec, none of this is written by hand:
|
|
344
|
+
`pikku new addon --openapi … --auth-config <file>` generates
|
|
345
|
+
`authenticate<Name>Upstream` in the addon and wires this plugin, the stored
|
|
346
|
+
credential and the actor credentials into `src/auth.ts`. The config format is
|
|
347
|
+
in the `pikku-build` skill's `references/openapi.md`. When the upstream later
|
|
348
|
+
refuses the stored token, the addon throws `CredentialRejectedError` (403,
|
|
349
|
+
`reauth: 'sign-in'`): the UI shows the sign-in again.
|
|
350
|
+
|
|
336
351
|
#### `pikkuFabric()` — control-plane operator sign-in
|
|
337
352
|
|
|
338
353
|
```typescript
|
|
@@ -44,12 +44,14 @@ plus more effort" — it is App plus a deliberate surface checklist, so read the
|
|
|
44
44
|
base first and follow it in full rather than blending the two into one plan.
|
|
45
45
|
|
|
46
46
|
The supporting references belong to whichever mode sends you to them:
|
|
47
|
-
`references/multi-app.md` (a second frontend), `references/design.md` (
|
|
48
|
-
|
|
49
|
-
the screens realise it — read before the first screen is built, not
|
|
50
|
-
last), `references/theming.md`
|
|
47
|
+
`references/multi-app.md` (a second frontend), `references/design.md` (showing
|
|
48
|
+
a picture of the screens first, committing to a design direction, and judging
|
|
49
|
+
whether the screens realise it — read before the first screen is built, not
|
|
50
|
+
after the last), `references/theming.md`
|
|
51
51
|
(authoring the theme),
|
|
52
|
-
`references/ship.md` (deploying, and the Fabric-readiness contract)
|
|
52
|
+
`references/ship.md` (deploying, and the Fabric-readiness contract),
|
|
53
|
+
`references/openapi.md` (an app on an OpenAPI spec: the auth mode, the
|
|
54
|
+
auth-config format, and the sign-in or connect screen it implies).
|
|
53
55
|
|
|
54
56
|
## Bootstrap before anything else
|
|
55
57
|
|
|
@@ -67,10 +69,10 @@ while still planning. Those failures look alarming and are nothing but this.
|
|
|
67
69
|
When the request comes with a file or a URL, look at it before planning
|
|
68
70
|
anything. Two kinds are converted first and then built on:
|
|
69
71
|
|
|
70
|
-
| Handed | Say, then do
|
|
71
|
-
| ------------------------------------------------------------------------------------------------------------------- |
|
|
72
|
-
| An **OpenAPI / Swagger spec** — top-level `openapi` or `swagger` key, a `paths` object | "This is an OpenAPI spec — I'll turn it into an addon first."
|
|
73
|
-
| An **n8n export** — an object with `nodes` and `connections`, an array of them, or a `{ workflows: [...] }` wrapper | "This is an n8n workflow — I'll import it first." Follow `pikku-n8n-import`.
|
|
72
|
+
| Handed | Say, then do |
|
|
73
|
+
| ------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
74
|
+
| An **OpenAPI / Swagger spec** — top-level `openapi` or `swagger` key, a `paths` object | "This is an OpenAPI spec — I'll turn it into an addon first." Pick the auth mode in `references/openapi.md`, then follow the `pikku-addon` skill's OpenAPI reference. |
|
|
75
|
+
| An **n8n export** — an object with `nodes` and `connections`, an array of them, or a `{ workflows: [...] }` wrapper | "This is an n8n workflow — I'll import it first." Follow `pikku-n8n-import`. |
|
|
74
76
|
|
|
75
77
|
Say it at once, in one line, and start: this is the obvious first move, not a
|
|
76
78
|
question for the user. Generate the whole spec, however large.
|
|
@@ -103,6 +105,75 @@ generated functions through `ref()`.
|
|
|
103
105
|
`pikku knowledge plan progress` measures the build against it from the
|
|
104
106
|
generated meta. You plan it and you build it; what you never do is edit the
|
|
105
107
|
plan afterwards to match what you built — that is grading yourself.
|
|
108
|
+
- **Print the links whenever the stack comes up, and in every hand-over.** Full,
|
|
109
|
+
clickable URLs, with the ports taken from what `bun run dev` actually printed:
|
|
110
|
+
- **App** — the frontend's URL (`http://localhost:7104` in the template; each
|
|
111
|
+
frontend in `pikkufabric.config.json` has its own port)
|
|
112
|
+
- **API** — `http://localhost:3000`
|
|
113
|
+
- **Console** — `http://localhost:3000/console`, plus a deep link to each
|
|
114
|
+
page that shows what this turn produced (the paths are below)
|
|
115
|
+
|
|
116
|
+
A person who has to go hunting for the port assumes the app did not start.
|
|
117
|
+
|
|
118
|
+
## Keep a BUILD-REPORT.md
|
|
119
|
+
|
|
120
|
+
Whenever pikku or a skill costs you time — a command that failed on a fresh
|
|
121
|
+
tree, a skill that described a flag the CLI does not have, generated code you
|
|
122
|
+
had to fix by hand — add an entry to `BUILD-REPORT.md` at the repo root as it
|
|
123
|
+
happens: what you ran, what you expected, what happened, and the workaround.
|
|
124
|
+
Leave out secrets, tokens and customer data.
|
|
125
|
+
|
|
126
|
+
At hand-over, show the file and ask the user whether to send it. Only with
|
|
127
|
+
their okay, send each entry with `pikku fabric report --stdin` (JSON on stdin;
|
|
128
|
+
`"kind": "product"` when pikku behaved wrongly, `"kind": "harness"` with
|
|
129
|
+
`"skill"` and `"passage"` when a skill misled you). The `pikku-report` skill
|
|
130
|
+
has the fields. When the CLI is not signed in to Fabric, the report is queued
|
|
131
|
+
locally rather than sent: say so, and that `pikku fabric findings flush` sends
|
|
132
|
+
the queue once they sign in. Do not retry or file it twice.
|
|
133
|
+
|
|
134
|
+
## Who you are talking to
|
|
135
|
+
|
|
136
|
+
The prompt asks first how technical the person is: **not technical**,
|
|
137
|
+
**technical, no code**, or **developer** (the default when unsaid). Whenever the
|
|
138
|
+
build makes or changes something the console can show, give the
|
|
139
|
+
`http://localhost:<port>/console/...` link instead of describing it.
|
|
140
|
+
|
|
141
|
+
Until the app is deployed that is the local open-source console, on the port
|
|
142
|
+
`pikku dev` printed. Once it is on Fabric, link the Fabric console for the stage
|
|
143
|
+
you are talking about instead; the `pikku-fabric` skill says which.
|
|
144
|
+
|
|
145
|
+
| Level | Links | Code in the conversation |
|
|
146
|
+
| ------------------ | --------------------------- | ------------------------ |
|
|
147
|
+
| Not technical | Product pages only | Never |
|
|
148
|
+
| Technical, no code | Product and technical pages | Never |
|
|
149
|
+
| Developer | Product and technical pages | As normal |
|
|
150
|
+
|
|
151
|
+
"Never" includes snippets and command lines; say what changed in the person's
|
|
152
|
+
words and link to where they can see it.
|
|
153
|
+
|
|
154
|
+
**Product pages** — the only ones a non-technical person gets:
|
|
155
|
+
|
|
156
|
+
| Shows | Path |
|
|
157
|
+
| --------------------- | -------------------------------------------------------------------------- |
|
|
158
|
+
| Knowledge, plans | `/console/knowledge`, `/console/knowledge?id=<note path>` |
|
|
159
|
+
| Personas | `/console/personas`, `/console/virtual-users?persona=<id>` |
|
|
160
|
+
| Roles and permissions | `/console/roles`, `/console/scopes` (only with `@pikku/addon-admin` wired) |
|
|
161
|
+
| Scenarios and runs | `/console/scenarios?id=<id>`, `/console/scenarios?view=runs&run=<run id>` |
|
|
162
|
+
| Workflows | `/console/workflow?id=<id>` |
|
|
163
|
+
| Agents | `/console/agents`, `/console/agents/playground?id=<agent id>` |
|
|
164
|
+
|
|
165
|
+
**Technical pages** — never for a non-technical person: `/console/overview`,
|
|
166
|
+
`/console/functions`, `/console/surface`, `/console/database`,
|
|
167
|
+
`/console/changes`, `/console/wires/http`, `/console/wires/channel`,
|
|
168
|
+
`/console/wires/mcp`, `/console/wires/cli`, `/console/wires/gateway`,
|
|
169
|
+
`/console/async/scheduler`, `/console/async/queue`, `/console/async/trigger`,
|
|
170
|
+
`/console/runtime`, `/console/emails`, `/console/webhooks`, `/console/secrets`,
|
|
171
|
+
`/console/variables`, `/console/security`, `/console/auth-providers`,
|
|
172
|
+
`/console/addons`, `/console/analytics`, `/console/credentials`,
|
|
173
|
+
`/console/users`, `/console/audit`, `/console/flags`, `/console/scorers`.
|
|
174
|
+
|
|
175
|
+
These come from `packages/console/src/App.tsx`; do not link a path that is not
|
|
176
|
+
listed here.
|
|
106
177
|
|
|
107
178
|
## What NOT to do
|
|
108
179
|
|
|
@@ -24,6 +24,9 @@ project shaped so `pikku fabric init` later adopts it with zero rework.
|
|
|
24
24
|
wirings, schemas or generated clients may have changed.
|
|
25
25
|
4. If validation fails, fix the source cause and rerun. Do not paper over
|
|
26
26
|
generated errors by editing generated files.
|
|
27
|
+
5. Report at the person's level (SKILL.md, "Who you are talking to"): a console
|
|
28
|
+
link for everything the console can show, and no code unless they are a
|
|
29
|
+
developer.
|
|
27
30
|
|
|
28
31
|
## 0. Bootstrap, before anything else
|
|
29
32
|
|
|
@@ -62,11 +65,18 @@ in one message. Then stop; do not interview the user.
|
|
|
62
65
|
a reference (brand guide, screenshots, a site whose register they want); or
|
|
63
66
|
their own design agent/prompt, whose output you take as the direction.
|
|
64
67
|
- **Do they want to see the screens before you build them?** Offer it here, in
|
|
65
|
-
this same round,
|
|
66
|
-
|
|
67
|
-
|
|
68
|
+
this same round, with yes marked recommended, in the words of
|
|
69
|
+
`references/design.md` — "a picture of the main screens so you can say 'yes,
|
|
70
|
+
like that' or 'no, move this'", never "mock" or "wireframe". Behind it is one
|
|
71
|
+
HTML page mocking the main screens, a few minutes of work. On a yes, or no
|
|
72
|
+
answer at all, `references/design.md` owns what to make and what it then binds — the
|
|
68
73
|
approved page becomes source of truth for the screens, and the theme is written
|
|
69
|
-
before it so what they approve is what ships.
|
|
74
|
+
before it so what they approve is what ships. Only an explicit no skips it.
|
|
75
|
+
- **May I write test records into the system it talks to?** Ask only when the
|
|
76
|
+
app reads a live system through an addon (an ERP, a CRM) and a milestone needs
|
|
77
|
+
data that isn't there yet: an unpaid invoice, a closed ticket. Say what you
|
|
78
|
+
would create and that it will be marked "Test". A no means building those
|
|
79
|
+
screens against their empty states.
|
|
70
80
|
- **What language should the app speak, and what language does the team work
|
|
71
81
|
in?** Two answers, not one — see §1a, which is where they go. Ask only if the
|
|
72
82
|
request is not obviously English; a brief written in English about an English
|
|
@@ -343,8 +353,9 @@ What a milestone is:
|
|
|
343
353
|
persona. If you cannot write the gherkin, you cannot build it yet — that is a
|
|
344
354
|
`questions/` note, not a milestone.
|
|
345
355
|
|
|
346
|
-
If §1's screen mock was made
|
|
347
|
-
screen on that page belongs to some milestone,
|
|
356
|
+
If §1's screen mock was made — approved, or drawn because nobody answered — the
|
|
357
|
+
milestones are read off it: every screen on that page belongs to some milestone,
|
|
358
|
+
and a screen no milestone builds
|
|
348
359
|
is a hole in this plan. Say which milestone covers which screen.
|
|
349
360
|
|
|
350
361
|
How to order them:
|
|
@@ -363,8 +374,17 @@ How to order them:
|
|
|
363
374
|
Number the files (`01-…`, `02-…`) so the order is visible in the tree. Then
|
|
364
375
|
`knowledge index && knowledge validate` before you write a line of code.
|
|
365
376
|
|
|
366
|
-
**
|
|
367
|
-
|
|
377
|
+
**One approval, then build to the end.** Show the picture of the screens and
|
|
378
|
+
the milestone list together, in one message, as the plan: which milestone builds
|
|
379
|
+
which screen, in what order. That is the only approval you ask for. It is the
|
|
380
|
+
last cheap moment to reorder: after §6 the migrations are numbered and the order
|
|
381
|
+
is concrete.
|
|
382
|
+
|
|
383
|
+
Once they approve it, or don't answer, build every milestone in order without
|
|
384
|
+
stopping to ask between them. Post one line as each milestone closes, with its
|
|
385
|
+
console links, and carry on. Stop only for what is theirs to decide: a
|
|
386
|
+
credential you don't have, spending money, posting in public, deleting or
|
|
387
|
+
overwriting their data, or a finding that changes the plan.
|
|
368
388
|
|
|
369
389
|
## 5a. The technical plan — one milestone at a time, before you build it
|
|
370
390
|
|
|
@@ -399,6 +419,8 @@ no plan, and everything after the current milestone is still allowed to move.
|
|
|
399
419
|
|
|
400
420
|
## 6. Implement milestones, one at a time
|
|
401
421
|
|
|
422
|
+
All of them, one after another, on the one approval from §5.
|
|
423
|
+
|
|
402
424
|
**Per milestone** — plan it (§5a), set its note to `status: dispatched`, do the
|
|
403
425
|
six steps, close it out (§6a), set it to `built`. Do not start the next one
|
|
404
426
|
until §6a passes, §7 is green for this one _and §7a shows its functions
|
|
@@ -498,8 +520,8 @@ Rules that are not optional:
|
|
|
498
520
|
`pikkuSessionlessFunc`. `pikkuFunc` with `auth: false` still answers
|
|
499
521
|
`MissingSessionError` over `/rpc` to a caller with no session.
|
|
500
522
|
- Better Auth already owns the `user`, `session`, `account` and `verification`
|
|
501
|
-
tables. A domain table with one of those names — a class
|
|
502
|
-
|
|
523
|
+
tables. A domain table with one of those names — a class _session_, a drop-in
|
|
524
|
+
_session_ — collides in the migration. Name it for the domain instead
|
|
503
525
|
(`evening`, `class_meeting`) and keep the word in the UI copy.
|
|
504
526
|
- The template's `/` redirects to `/app`, so the login screen — and its "Sign in
|
|
505
527
|
as …" switcher — is what a signed-out visitor sees first. Replace `/` with a
|
|
@@ -544,7 +566,7 @@ start the frontend on its own (say :3000 is taken by another project), you owe
|
|
|
544
566
|
it three things: the two `VITE_DEV_*` values the dev script would have computed,
|
|
545
567
|
and `VITE_API_PROXY` pointing at your API — the dev proxy defaults to
|
|
546
568
|
`http://localhost:3000`, so beside another project's server your sign-ins go to
|
|
547
|
-
|
|
569
|
+
_its_ API and come back `401 Invalid actor secret`, which reads like a bad
|
|
548
570
|
credential rather than the wrong server.
|
|
549
571
|
|
|
550
572
|
The `--bun` in `bunx --bun pikku …` is load-bearing — keep it. Without it the
|
|
@@ -54,13 +54,18 @@ allowed is landing on one because it was nearest to hand.
|
|
|
54
54
|
## Offer to draw the screens before you build them
|
|
55
55
|
|
|
56
56
|
Before the first milestone, **ask** whether they want to see the screens first.
|
|
57
|
-
One question, in §1's round, not a gate of its own
|
|
57
|
+
One question, in §1's round, not a gate of its own, with yes marked
|
|
58
|
+
recommended every time:
|
|
58
59
|
|
|
59
|
-
>
|
|
60
|
-
>
|
|
61
|
-
> than a built
|
|
60
|
+
> Before I build, shall I show you a picture of the main screens so you can say
|
|
61
|
+
> "yes, like that" or "no, move this"? (Recommended: it takes a few minutes and
|
|
62
|
+
> changing a picture is much cheaper than changing a built app.)
|
|
62
63
|
|
|
63
|
-
|
|
64
|
+
Never say "mock", "mockup" or "wireframe" to the person; most people do not
|
|
65
|
+
know the words. They stay the technical terms in this file only.
|
|
66
|
+
|
|
67
|
+
If they do not answer, draw it anyway and build from it. Only an explicit no
|
|
68
|
+
skips it; then build, and the direction in words is enough to be accountable to.
|
|
64
69
|
If they accept, this is the cheapest decision in the project — a picture of eight
|
|
65
70
|
screens costs a fraction of eight built screens, and it is the only point where
|
|
66
71
|
"that is not what I meant" is free.
|
|
@@ -105,6 +110,12 @@ first. Whatever your host offers for showing a page is how you show it: an
|
|
|
105
110
|
Artifact, a file they open, a preview server. The page is the deliverable; how it
|
|
106
111
|
gets in front of them is not this file's business.
|
|
107
112
|
|
|
113
|
+
**Say it is a picture, on the page and in the message.** A well-drawn screen
|
|
114
|
+
reads as a finished app, and a person who thinks it is already built asks why
|
|
115
|
+
nothing works. The page opens with a banner that stays in view: "A picture of
|
|
116
|
+
the planned screens. Nothing is built yet." The message that shows it says the
|
|
117
|
+
same, then says what happens next: "Once you're happy with it, I'll build it."
|
|
118
|
+
|
|
108
119
|
Write it to `knowledge/decisions/design/screens.html` and treat it as **source of
|
|
109
120
|
truth for the screens** once they approve it. That has consequences worth
|
|
110
121
|
stating:
|
|
@@ -63,86 +63,61 @@ Worth a scenario each, because they are two different claims: that a mechanic ca
|
|
|
63
63
|
*see* the invoices nav item, and that their call to an invoices RPC is *refused*. The
|
|
64
64
|
second is the one that catches a `permissions` field nobody wired.
|
|
65
65
|
|
|
66
|
-
## The
|
|
66
|
+
## The second app
|
|
67
67
|
|
|
68
68
|
```bash
|
|
69
|
-
|
|
70
|
-
rm -rf apps/admin/node_modules apps/admin/src/paraglide
|
|
69
|
+
pikku new app admin --serves staff --personas manager,mechanic
|
|
71
70
|
```
|
|
72
71
|
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
under exactly one frontend. A persona listed nowhere is a person with no way
|
|
123
|
-
in, and that is a design bug worth seeing now rather than at review.
|
|
124
|
-
|
|
125
|
-
### 3. The dev runner
|
|
126
|
-
|
|
127
|
-
`dev.mjs`, under the project's scripts directory, spawns `@project/app` **by
|
|
128
|
-
name** and will silently never start your second app — the frontend simply is not
|
|
129
|
-
there, with no error to explain it.
|
|
130
|
-
|
|
131
|
-
Make it read the `frontends` map and spawn one child per entry, rather than
|
|
132
|
-
adding a second hardcoded line. Two sources of truth for "which apps exist" is
|
|
133
|
-
the drift this whole file is trying to avoid.
|
|
134
|
-
|
|
135
|
-
### 4. `pikku.config.json` → `environments`
|
|
136
|
-
|
|
137
|
-
`local.appUrl` points at one app. Add an environment per frontend (`local`,
|
|
138
|
-
`local-admin`) so the browser scenario pass can drive either one. A browser
|
|
139
|
-
scenario run against the wrong `appUrl` fails on a missing element and reads like
|
|
140
|
-
a UI bug rather than a config one.
|
|
141
|
-
|
|
142
|
-
### 5. Re-run `bun install`
|
|
143
|
-
|
|
144
|
-
`apps/*` is already globbed in the root workspaces, so this just links the new
|
|
145
|
-
one.
|
|
72
|
+
One command does every step this section used to list by hand: it fetches
|
|
73
|
+
`pikkujs/starter-template`'s `apps/app`, re-points its `package.json` at the
|
|
74
|
+
new name, its own dev/preview port and its own `--tsBuildInfoFile`, stamps
|
|
75
|
+
`app: '<slug>'` onto each named persona in `definePersonas({…})`, adds the
|
|
76
|
+
`frontends` entry, and re-runs `bun install`.
|
|
77
|
+
|
|
78
|
+
**It scaffolds from the starter template, not from the app you already have.**
|
|
79
|
+
Copying the working app drags its screens, routes and nav into an audience that
|
|
80
|
+
never asked for them, and the first hour in the new app goes on deleting
|
|
81
|
+
someone else's product.
|
|
82
|
+
|
|
83
|
+
`--template <source>` scaffolds from something else — any giget source, or a
|
|
84
|
+
path inside the repo for an offline or vendored copy. `--install false` skips
|
|
85
|
+
the install when you are batching several.
|
|
86
|
+
|
|
87
|
+
**The `--tsBuildInfoFile` edit is the one that used to bite.** Two apps sharing
|
|
88
|
+
one incremental cache produce type errors that vanish on a clean build: an hour
|
|
89
|
+
of debugging for a one-word edit. It is handled now, but it is why you should
|
|
90
|
+
not copy by hand.
|
|
91
|
+
|
|
92
|
+
### What it refuses, and why that is the valuable part
|
|
93
|
+
|
|
94
|
+
The scaffolding is five file edits. Getting the audience wrong is a whole
|
|
95
|
+
second app nobody needed, so the command will not create one when:
|
|
96
|
+
|
|
97
|
+
- **`--serves` names a surface.** `dashboard`, `portal`, `admin`, `console`,
|
|
98
|
+
`ui` and friends say nothing — every frontend is an app. Name the people in
|
|
99
|
+
their own word: staff, customer, supplier, patient.
|
|
100
|
+
- **An existing app already serves that audience.** People sharing an audience
|
|
101
|
+
share ONE app and differ by nav and permitted actions. A new app is for a
|
|
102
|
+
group the first app is not for.
|
|
103
|
+
- **A named persona already signs into another app.** A person signs into one
|
|
104
|
+
app; move them out first if they really belong here.
|
|
105
|
+
- **The slug is what the plan calls an app that already exists.** The plan's
|
|
106
|
+
FIRST app is the one the project starts with — only the apps after it get
|
|
107
|
+
created.
|
|
108
|
+
- **A persona is not in `definePersonas({…})`.** The app is built around who
|
|
109
|
+
signs into it, so it is not created for people who do not exist yet.
|
|
110
|
+
|
|
111
|
+
It also repairs its own half-states: a run that died between writing the
|
|
112
|
+
directory and writing the config entry leaves one without the other, and
|
|
113
|
+
neither survives alone, so the next run clears the remains and carries on
|
|
114
|
+
rather than sending you in to do the surgery by hand.
|
|
115
|
+
|
|
116
|
+
### What it does NOT do
|
|
117
|
+
|
|
118
|
+
It stops after `bun install`. Serving the new app — a reverse proxy, a
|
|
119
|
+
supervisor, a dev runner, a deploy target — belongs to whatever is hosting it.
|
|
120
|
+
On a plain checkout, `bun --filter @project/<slug> dev` is enough.
|
|
146
121
|
|
|
147
122
|
## Sessions across two origins
|
|
148
123
|
|