@pikku/skills 0.12.18 → 0.12.20
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +99 -0
- package/dist/skills.gen.js +1 -1
- package/package.json +1 -1
- package/skills/pikku-better-auth/SKILL.md +19 -4
- package/skills/pikku-build-app/SKILL.md +17 -9
- package/skills/pikku-build-platform/SKILL.md +1 -1
- package/skills/pikku-build-quick/SKILL.md +6 -3
- package/skills/pikku-concepts/SKILL.md +7 -7
- package/skills/pikku-concepts/references/concept-mapping.md +1 -1
- package/skills/pikku-fabric/SKILL.md +8 -4
- package/skills/pikku-feature/SKILL.md +1 -1
- package/skills/pikku-i18n/SKILL.md +2 -2
- package/skills/pikku-react/SKILL.md +39 -5
- package/skills/pikku-scenario/SKILL.md +24 -23
package/package.json
CHANGED
|
@@ -474,8 +474,21 @@ plugins: [actor({ secret: SCENARIO_ACTOR_SECRET })]
|
|
|
474
474
|
```
|
|
475
475
|
|
|
476
476
|
`POST ${basePath}/sign-in/actor` `{ email, secret, name? }` → 200 + the normal
|
|
477
|
-
session cookie. `secret` may
|
|
478
|
-
off the secrets service instead of a captured
|
|
477
|
+
session cookie. The plugin's `secret` is the **root**, and it may be a (possibly
|
|
478
|
+
async) function so it can come off the secrets service instead of a captured
|
|
479
|
+
value.
|
|
480
|
+
|
|
481
|
+
**What a caller presents is not the root.** It is
|
|
482
|
+
`deriveActorSecret(root, email)` from `@pikku/core/services` — HKDF-expanded
|
|
483
|
+
HMAC-SHA256 over the lowercased address. The endpoint re-derives the expected
|
|
484
|
+
value for the address being signed in as and compares, so a credential minted
|
|
485
|
+
for one persona is refused for every other, and the root itself is never a valid
|
|
486
|
+
credential. A root under 32 characters refuses the endpoint outright rather than
|
|
487
|
+
deriving weak credentials from it (the server log names the problem; the client
|
|
488
|
+
is not told which). Callers rarely derive by hand — `pikku dev` mints one per
|
|
489
|
+
persona into `VITE_DEV_ACTOR_SECRETS` for the browser switcher, `pikku persona
|
|
490
|
+
secret <id>` mints them for a run, and the two `PersonaSignIn` implementations
|
|
491
|
+
derive on the fly.
|
|
479
492
|
|
|
480
493
|
**Which command is running decides whether it works, not whether a secret is
|
|
481
494
|
set.** `pikku dev` sets `PIKKU_DEV_ACTOR_SIGN_IN` and mints an ephemeral
|
|
@@ -516,8 +529,10 @@ actor`. So the secret cannot take over a **real user's** account — the blast
|
|
|
516
529
|
- **Unknown emails are created only under `pikku dev`**, flagged `actor: true`,
|
|
517
530
|
so a local scenario declaring a new persona needs no seed step. Anywhere else
|
|
518
531
|
the account has to have been provisioned at boot first.
|
|
519
|
-
- **
|
|
520
|
-
|
|
532
|
+
- **A credential is bound to one address**, so a leaked one is one synthetic
|
|
533
|
+
account rather than the whole actor population; only the root is worth the
|
|
534
|
+
paragraph above. The comparison is constant-time and length-hiding, so a wrong
|
|
535
|
+
credential leaks neither the length nor a prefix of the right one.
|
|
521
536
|
|
|
522
537
|
This is the endpoint `pikku scenario` signs its actors in through, and the one
|
|
523
538
|
the frontend dev switcher posts to — see `pikku-scenario` for declaring the
|
|
@@ -28,7 +28,7 @@ project shaped so `pikku fabric init` later adopts it with zero rework.
|
|
|
28
28
|
## Agent Operating Procedure
|
|
29
29
|
|
|
30
30
|
1. Discover before editing. Run `pikku info functions --verbose --silent` and
|
|
31
|
-
read `AGENTS.md` before your first change. Read `
|
|
31
|
+
read `AGENTS.md` before your first change. Read `metaLocale` in
|
|
32
32
|
`pikku.config.json` too: it is the language every `description`, `title` and
|
|
33
33
|
step `template` you write must be in (§1a). Identifiers stay English whatever
|
|
34
34
|
it says.
|
|
@@ -92,21 +92,21 @@ settle all three explicitly before you write code.
|
|
|
92
92
|
| Axis | What it covers | Where it goes |
|
|
93
93
|
| --------------- | -------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------- |
|
|
94
94
|
| **Identifiers** | Function, component, type, variable and file names. Database tables and columns. Commit messages. | Nowhere — **always English**, no setting, not negotiable |
|
|
95
|
-
| **Meta** | `description` on functions and steps, `name`/`title` on features and scenarios, step `template`, role and persona descriptions | `
|
|
95
|
+
| **Meta** | `description` on functions and steps, `name`/`title` on features and scenarios, step `template`, role and persona descriptions | `metaLocale` in `pikku.config.json`, default `en` |
|
|
96
96
|
| **Product UI** | Every string the app shows a user | `messages/<locale>.json`, and `defaultLocale` for what a first-time visitor opens in |
|
|
97
97
|
|
|
98
98
|
**Identifiers are English.** The product's market does not change this and
|
|
99
|
-
neither does `
|
|
99
|
+
neither does `metaLocale`. Identifiers are the surface the generated `#pikku/*`
|
|
100
100
|
clients, `pikku info`, the typed RPC map and the Kysely types all bind to, and
|
|
101
101
|
unlike a string an identifier cannot be translated later — renaming one is a
|
|
102
102
|
migration. A German practice management tool gets `getWorklist`, `case`,
|
|
103
103
|
`event`, not `getUebersicht`, `vorgang`, `ereignis`.
|
|
104
104
|
|
|
105
|
-
**Meta follows `
|
|
105
|
+
**Meta follows `metaLocale`.** Write the team's answer into `pikku.config.json` in
|
|
106
106
|
this phase, before there is any meta to be wrong:
|
|
107
107
|
|
|
108
108
|
```json
|
|
109
|
-
{ "
|
|
109
|
+
{ "metaLocale": "de" }
|
|
110
110
|
```
|
|
111
111
|
|
|
112
112
|
It exists for the Pikku Console. Meta is the one part of a project the Console
|
|
@@ -114,7 +114,7 @@ renders back to a human, so a team working in German reads their own functions,
|
|
|
114
114
|
features and scenario reports in German. Default `en` and do not ask when the
|
|
115
115
|
project is obviously English. **On every later run, read this field first and
|
|
116
116
|
author descriptions, titles and step templates in it** — a project whose
|
|
117
|
-
`
|
|
117
|
+
`metaLocale` you ignored reports half in one language and half in another.
|
|
118
118
|
|
|
119
119
|
**Product UI is the message catalogue.** `messages/<locale>.json` via
|
|
120
120
|
`pikku-i18n`, with `defaultLocale` deciding what a visitor opens in.
|
|
@@ -131,7 +131,7 @@ A German medical portal, correctly:
|
|
|
131
131
|
// apps/app/src/i18n/active.json (or: fabric i18n --default-locale de)
|
|
132
132
|
{ "defaultLocale": "de" }
|
|
133
133
|
// pikku.config.json
|
|
134
|
-
{ "
|
|
134
|
+
{ "metaLocale": "de" }
|
|
135
135
|
```
|
|
136
136
|
|
|
137
137
|
Record the two non-obvious answers as a `decisions/` note in §2 — neither is
|
|
@@ -464,8 +464,16 @@ built-in module: node:sqlite`.
|
|
|
464
464
|
|
|
465
465
|
Open it, sign up, and click through what you built. **HTTP 200 is not evidence.**
|
|
466
466
|
The pages are client-rendered: the server returns 200 with an empty shell, so a
|
|
467
|
-
page whose component throws still looks fine to `curl`.
|
|
468
|
-
|
|
467
|
+
page whose component throws still looks fine to `curl`.
|
|
468
|
+
|
|
469
|
+
That click-through is a smoke check, and it is the only thing it is. **Do not
|
|
470
|
+
hand-drive a browser tool in place of a test.** Steering Playwright yourself
|
|
471
|
+
proves a page rendered once, on your machine, in an order only you remember —
|
|
472
|
+
nothing about it re-runs, so the next agent inherits a claim rather than a test,
|
|
473
|
+
and a regression lands silently. When a journey is worth driving through the UI,
|
|
474
|
+
it is worth writing as a browser step on §7's scenario and running
|
|
475
|
+
`pikku scenario run local --spawn --run browser`: same clicks, same assertions,
|
|
476
|
+
in the repo, green or red on every future run.
|
|
469
477
|
|
|
470
478
|
## 7. Prove it — scenarios
|
|
471
479
|
|
|
@@ -158,7 +158,7 @@ that is the finding.
|
|
|
158
158
|
every added locale is cloned from, so three locales is `locales: ["en", …]` and
|
|
159
159
|
never a repointed base. Shipping locales is also not a reason for anything in
|
|
160
160
|
the code to stop being English: identifiers are English in every project, and
|
|
161
|
-
the language of `description`/`title`/`template` is `
|
|
161
|
+
the language of `description`/`title`/`template` is `metaLocale` in
|
|
162
162
|
`pikku.config.json`. See `pikku-build-app` §1a.
|
|
163
163
|
|
|
164
164
|
### Emails — `pikku-emails`
|
|
@@ -54,7 +54,7 @@ defaults are the point.
|
|
|
54
54
|
|
|
55
55
|
Do not ask about language either — take the defaults and note them in §6.
|
|
56
56
|
Identifiers are English in every project, whatever the product's market;
|
|
57
|
-
`
|
|
57
|
+
`metaLocale` in `pikku.config.json` (the language of `description`/`title`/step
|
|
58
58
|
`template`, which the Console renders) stays `en` unless the user already told
|
|
59
59
|
you otherwise. If the request says the app's UI is not English, that is the
|
|
60
60
|
message catalogue only: add the locale and set `defaultLocale`, and leave
|
|
@@ -178,8 +178,11 @@ exactly like an app bug, so if every request fails, check both came up.
|
|
|
178
178
|
|
|
179
179
|
Sign up, click every screen. **HTTP 200 is not evidence:** pages are
|
|
180
180
|
client-rendered, so the server returns 200 with an empty shell and a page whose
|
|
181
|
-
component throws still looks fine to `curl`.
|
|
182
|
-
|
|
181
|
+
component throws still looks fine to `curl`.
|
|
182
|
+
|
|
183
|
+
Looking is for the layout — the part only eyes catch. Assertions belong in the
|
|
184
|
+
smoke scenario, not in a browser session you steered by hand: that session proves
|
|
185
|
+
a screen rendered once, here, and nothing about it re-runs.
|
|
183
186
|
|
|
184
187
|
**Screenshot at 390px too.** A layout that is fine at 1440 routinely breaks on a
|
|
185
188
|
phone — an overflowing table, a row of buttons wrapped into a pile, a modal
|
|
@@ -138,7 +138,7 @@ Services can be destructured inline in the `func` signature (e.g. `async ({ logg
|
|
|
138
138
|
|
|
139
139
|
```typescript
|
|
140
140
|
pikkuFunc({
|
|
141
|
-
// Identity and documentation — prose, so it follows `
|
|
141
|
+
// Identity and documentation — prose, so it follows `metaLocale` in
|
|
142
142
|
// pikku.config.json (default `en`). The identifier does not; see
|
|
143
143
|
// "What Language You Write In".
|
|
144
144
|
title?: string, // Human-readable name
|
|
@@ -333,12 +333,12 @@ bottom.
|
|
|
333
333
|
| Axis | What it covers | What decides it |
|
|
334
334
|
| --------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------- |
|
|
335
335
|
| **Identifiers** | Function, component, type, variable and file names. Database tables and columns. Branch names and commit messages. | Nothing. **Always English.** There is no setting. |
|
|
336
|
-
| **Meta** | The prose authored _inside_ the code: `description` on functions and steps, `name`/`title` on features and scenarios, step `template`, role/persona descriptions. | `
|
|
336
|
+
| **Meta** | The prose authored _inside_ the code: `description` on functions and steps, `name`/`title` on features and scenarios, step `template`, role/persona descriptions. | `metaLocale` in `pikku.config.json`. Defaults to `en`. |
|
|
337
337
|
| **Product UI** | Every string the app shows a user. | `messages/<locale>.json`, with `active.json`'s `defaultLocale` choosing what a first-time visitor opens in. |
|
|
338
338
|
|
|
339
339
|
### Identifiers are English, and nothing changes that
|
|
340
340
|
|
|
341
|
-
Not the product's market, not the team's working language, and **not `
|
|
341
|
+
Not the product's market, not the team's working language, and **not `metaLocale`**.
|
|
342
342
|
A German medical practice, an Arabic marketplace and a Japanese logistics tool
|
|
343
343
|
all get `getOverview`, `AttentionStripe`, `case`, `event`.
|
|
344
344
|
|
|
@@ -350,10 +350,10 @@ A `vorgang` table types as `Vorgang` in Kysely and reads as noise to everyone wh
|
|
|
350
350
|
did not name it, and unlike a string it cannot be translated later — renaming an
|
|
351
351
|
identifier is a migration, not an edit.
|
|
352
352
|
|
|
353
|
-
### Meta follows `
|
|
353
|
+
### Meta follows `metaLocale`, and that is what the field is for
|
|
354
354
|
|
|
355
355
|
```json
|
|
356
|
-
{ "
|
|
356
|
+
{ "metaLocale": "de" }
|
|
357
357
|
```
|
|
358
358
|
|
|
359
359
|
Meta is the one part of a project the **Pikku Console** renders back to a human.
|
|
@@ -366,7 +366,7 @@ Read it before you author meta, and write descriptions, titles and templates in
|
|
|
366
366
|
it. Absent, it is `en`. It is a BCP-47 tag (`en`, `de`, `pt-BR` — a hyphen, not
|
|
367
367
|
an underscore), and the CLI rejects anything else by name.
|
|
368
368
|
|
|
369
|
-
`
|
|
369
|
+
`metaLocale` is **not** licence to rename anything. `metaLocale: "de"` buys a German
|
|
370
370
|
`description: 'Zeigt die Arbeitsliste'` on a function still called
|
|
371
371
|
`getWorklist`.
|
|
372
372
|
|
|
@@ -405,7 +405,7 @@ settings, each on its own axis:
|
|
|
405
405
|
{ "defaultLocale": "de" }
|
|
406
406
|
|
|
407
407
|
// pikku.config.json — the language the team reads their Console in
|
|
408
|
-
{ "
|
|
408
|
+
{ "metaLocale": "de" }
|
|
409
409
|
```
|
|
410
410
|
|
|
411
411
|
Identifiers stay English throughout. When a brief tells you the product speaks a
|
|
@@ -449,7 +449,7 @@ src/
|
|
|
449
449
|
|
|
450
450
|
// Use tags for cross-cutting concerns:
|
|
451
451
|
wireHTTP({ route: '/todos', func: listTodos, tags: ['todos', 'public'] })
|
|
452
|
-
|
|
452
|
+
addTagMiddleware('todos', [loggingMiddleware]) // Applies to all 'todos'-tagged functions
|
|
453
453
|
```
|
|
454
454
|
|
|
455
455
|
---
|
|
@@ -268,8 +268,12 @@ bun run dev
|
|
|
268
268
|
Then open the app, sign up as a real user, and click through what you built.
|
|
269
269
|
**HTTP 200 is not evidence.** These are client-rendered pages: the server returns
|
|
270
270
|
200 with an empty shell, so a page whose component throws still looks fine to
|
|
271
|
-
curl.
|
|
272
|
-
|
|
271
|
+
curl.
|
|
272
|
+
|
|
273
|
+
That pass is a smoke check. Anything you would otherwise verify by hand-driving a
|
|
274
|
+
browser tool belongs in a scenario's browser step, run with
|
|
275
|
+
`pikku scenario run local --spawn --run browser` — a browser session you steered
|
|
276
|
+
yourself proves nothing that re-runs.
|
|
273
277
|
|
|
274
278
|
Secrets come from `process.env`, which the CLI populates from a `.env` in the
|
|
275
279
|
working directory. `BETTER_AUTH_SECRET` is required — without it the first
|
|
@@ -416,7 +420,7 @@ These apply in every Fabric app:
|
|
|
416
420
|
- **No hand-editing `.pikku/db/schema.gen.ts`** — write a migration and re-run `pikku db migrate`.
|
|
417
421
|
- **One runtime unit per file** — never define multiple functions/workflows in a single source file.
|
|
418
422
|
- **Workflow steps don't need manual wiring** — `pikkuSessionlessFunc` step functions in `*.steps.ts` files are auto-discovered by codegen.
|
|
419
|
-
- **Identifiers are English** — functions, components, types, files, database tables and columns, in every app whatever market it serves. The team's language is `
|
|
423
|
+
- **Identifiers are English** — functions, components, types, files, database tables and columns, in every app whatever market it serves. The team's language is `metaLocale` in `pikku.config.json` and reaches `description`/`title`/`template` only; the app's language is the message catalogue, where `baseLocale` stays `en` and `defaultLocale` decides what a visitor opens in. `pikku fabric validate` warns (`app-base-locale-not-english-<app>`) when an app repoints its base.
|
|
420
424
|
|
|
421
425
|
## Converting an existing app to Fabric format
|
|
422
426
|
|
|
@@ -432,7 +436,7 @@ Fix every `error` and `warn` in the output before continuing. Then:
|
|
|
432
436
|
2. **Replace route handlers with pikkuFuncs**: extract business logic into `pikkuFunc`/`pikkuSessionlessFunc`, add `wireHTTP` or `expose: true` for transport.
|
|
433
437
|
3. **Replace DI/IoC with pikkuServices**: move service construction to `createSingletonServices` in `services.ts`.
|
|
434
438
|
4. **Replace `process.env` calls**: plain config becomes `defineVariable` + `variables.get()`, anything sensitive becomes `defineSecret` + `secrets.getSecret()`.
|
|
435
|
-
5. **Add `pikku.config.json`** at project root with `srcDirectories`, `outDir`, and `clientFiles` — plus `
|
|
439
|
+
5. **Add `pikku.config.json`** at project root with `srcDirectories`, `outDir`, and `clientFiles` — plus `metaLocale` if the team does not work in English, which is the language every `description`, `title` and step `template` is then authored in.
|
|
436
440
|
6. **Add `pikkufabric.config.json`** at project root with `projectId`, `production.domain`, and `frontends` (production is always `main`, so there is no `production.branch`).
|
|
437
441
|
7. **Run `pikku all`** — verify codegen succeeds and there are no type errors.
|
|
438
442
|
8. **Run `pikku fabric validate`** once more to confirm no structural issues remain.
|
|
@@ -41,7 +41,7 @@ schemas (`yarn pikku meta functions get <id>`) or workflow steps
|
|
|
41
41
|
**Capability rule:** do not introduce new wires of a type whose
|
|
42
42
|
`capabilities.<type>` is `false` unless the user explicitly asked for it.
|
|
43
43
|
|
|
44
|
-
**Language rule:** read `
|
|
44
|
+
**Language rule:** read `metaLocale` in `pikku.config.json` (default `en`). It is
|
|
45
45
|
the language of every `description`, `title` and step `template` you author —
|
|
46
46
|
the meta the Pikku Console renders back to the team. It renames nothing:
|
|
47
47
|
functions, components, types, files, tables and columns are English in every
|
|
@@ -25,7 +25,7 @@ this skill. Three axes:
|
|
|
25
25
|
| Axis | What it covers | What sets it |
|
|
26
26
|
| --------------- | ------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------- |
|
|
27
27
|
| **Identifiers** | Function, component, type and file names; database tables and columns | Nothing — always English, no setting |
|
|
28
|
-
| **Meta** | `description` / `name` / `title` / `template` authored inside the code, which the Pikku Console renders | `
|
|
28
|
+
| **Meta** | `description` / `name` / `title` / `template` authored inside the code, which the Pikku Console renders | `metaLocale` in `pikku.config.json`, default `en` |
|
|
29
29
|
| **Product UI** | Every string the app shows a user | `messages/<locale>.json` + `defaultLocale` — **this axis only** |
|
|
30
30
|
|
|
31
31
|
A non-English product moves the third row and nothing else.
|
|
@@ -57,7 +57,7 @@ So a German medical portal is **three** settings, not one:
|
|
|
57
57
|
{ "defaultLocale": "de" }
|
|
58
58
|
|
|
59
59
|
// pikku.config.json — the language the team reads their Console in
|
|
60
|
-
{ "
|
|
60
|
+
{ "metaLocale": "de" }
|
|
61
61
|
```
|
|
62
62
|
|
|
63
63
|
In the Fabric template both of the first two have a command, so you rarely edit
|
|
@@ -240,11 +240,11 @@ their own sandbox.
|
|
|
240
240
|
import { useDevActors } from '@pikku/react'
|
|
241
241
|
|
|
242
242
|
const { actors, signInAs, pendingEmail, isPending, error } = useDevActors({
|
|
243
|
-
// Gate both reads on the bundler's dev flag so
|
|
243
|
+
// Gate both reads on the bundler's dev flag so no credential can reach a
|
|
244
244
|
// production bundle. The sandbox dev server bakes them from your personas.
|
|
245
245
|
actors: import.meta.env.DEV ? import.meta.env.VITE_DEV_ACTORS : undefined,
|
|
246
|
-
|
|
247
|
-
? import.meta.env.
|
|
246
|
+
secrets: import.meta.env.DEV
|
|
247
|
+
? import.meta.env.VITE_DEV_ACTOR_SECRETS
|
|
248
248
|
: undefined,
|
|
249
249
|
apiUrl: apiUrl(),
|
|
250
250
|
onSignedIn: () => navigate({ to: '/' }),
|
|
@@ -255,8 +255,11 @@ const { actors, signInAs, pendingEmail, isPending, error } = useDevActors({
|
|
|
255
255
|
`<DevActorSwitcher />` from `@pikku/mantine/dev` — a separate entry point from
|
|
256
256
|
`@pikku/mantine/core`, whose contract is "drop-in alias for `@mantine/core`"
|
|
257
257
|
and so must not export components Mantine has no counterpart for.
|
|
258
|
-
- **`
|
|
259
|
-
|
|
258
|
+
- **`secrets` is `{ address: credential }`, not one shared value** — a
|
|
259
|
+
credential opens the one persona it was minted for (see
|
|
260
|
+
**pikku-better-auth**). `actors` is empty unless the host supplied both a list
|
|
261
|
+
and the credentials for it, and an actor with no credential is not offered, so
|
|
262
|
+
a production build renders nothing without you testing for it.
|
|
260
263
|
- **It takes `onSignedIn` rather than a router**, and takes the env values rather
|
|
261
264
|
than reading them, because how env is spelled is a bundler fact
|
|
262
265
|
(`import.meta.env.VITE_*` vs `process.env.NEXT_PUBLIC_*`).
|
|
@@ -268,6 +271,37 @@ Do not hand-write the `devActors()` / `signInAsActor()` pair per app; that
|
|
|
268
271
|
copy-paste, including the `import.meta.env.DEV` gate, is exactly what this
|
|
269
272
|
replaced.
|
|
270
273
|
|
|
274
|
+
### Linking from a Mantine element: `renderRoot`, not `component`
|
|
275
|
+
|
|
276
|
+
Handing TanStack's `Link` to a Mantine element as `component={Link}` compiles,
|
|
277
|
+
renders, and navigates — and silently unties the type. Mantine's polymorphic
|
|
278
|
+
`component` prop widens the router generic to `AnyRouter`, so `to` and
|
|
279
|
+
`params` stop being checked against your actual routes. Renaming a route then
|
|
280
|
+
breaks the running app instead of the build, which is the one thing the typed
|
|
281
|
+
router exists to prevent.
|
|
282
|
+
|
|
283
|
+
Wrap the typed `Link` once and reach it through `renderRoot`, which passes the
|
|
284
|
+
props through without re-typing the element:
|
|
285
|
+
|
|
286
|
+
```tsx
|
|
287
|
+
// components/links.tsx — one wrapper the whole app links through
|
|
288
|
+
import { Link } from '@tanstack/react-router'
|
|
289
|
+
|
|
290
|
+
export const AssessmentLink = (props: { assessmentId: string; children: React.ReactNode }) => (
|
|
291
|
+
<Link to="/assessments/$assessmentId" params={{ assessmentId: props.assessmentId }}>
|
|
292
|
+
{props.children}
|
|
293
|
+
</Link>
|
|
294
|
+
)
|
|
295
|
+
```
|
|
296
|
+
|
|
297
|
+
```tsx
|
|
298
|
+
<Button renderRoot={(p) => <AssessmentLink assessmentId={id} {...p} />}>
|
|
299
|
+
Open
|
|
300
|
+
</Button>
|
|
301
|
+
```
|
|
302
|
+
|
|
303
|
+
The wrapper is where `to` and `params` are checked, and it is checked once.
|
|
304
|
+
|
|
271
305
|
## What NOT to do
|
|
272
306
|
|
|
273
307
|
- Don't instantiate `PikkuFetch`/`PikkuRPC` inside a component — `createPikku`
|
|
@@ -213,14 +213,14 @@ The **feature is the run unit**: `--flows` on a scenario whose every feature ent
|
|
|
213
213
|
|
|
214
214
|
A scenario records what someone was **trying to do**, never the keystrokes they used to do it. This is the one decision that determines whether a suite survives its first redesign, and it applies to every step name you write.
|
|
215
215
|
|
|
216
|
-
| Action ladder — wrong | Intent ladder — right
|
|
217
|
-
| ----------------------------------- |
|
|
218
|
-
| `Given opens /shop` | `Given
|
|
219
|
-
| `When clicks the category filter` | `When
|
|
220
|
-
| `And clicks "Drinks"` | `Then it is in their basket`
|
|
221
|
-
| `And clicks the first product card` |
|
|
222
|
-
| `And clicks Add to basket` |
|
|
223
|
-
| `Then sees "1 item"` |
|
|
216
|
+
| Action ladder — wrong | Intent ladder — right |
|
|
217
|
+
| ----------------------------------- | ----------------------------------------------- |
|
|
218
|
+
| `Given opens /shop` | `Given shopper is browsing the shop` |
|
|
219
|
+
| `When clicks the category filter` | `When shopper buys the £5 strawberry milkshake` |
|
|
220
|
+
| `And clicks "Drinks"` | `Then it is in their basket` |
|
|
221
|
+
| `And clicks the first product card` | |
|
|
222
|
+
| `And clicks Add to basket` | |
|
|
223
|
+
| `Then sees "1 item"` | |
|
|
224
224
|
|
|
225
225
|
Three things go wrong with the left-hand column, and all three are expensive:
|
|
226
226
|
|
|
@@ -313,7 +313,7 @@ await scenario.when(
|
|
|
313
313
|
{ name: '£5 strawberry milkshake' },
|
|
314
314
|
{ actor: actors.shopper }
|
|
315
315
|
)
|
|
316
|
-
// reporter renders: When
|
|
316
|
+
// reporter renders: When shopper buys the £5 strawberry milkshake ✓ 1.2s
|
|
317
317
|
```
|
|
318
318
|
|
|
319
319
|
**Every intent step begins by arriving.** `ensureOnShop` is not defensive noise — it is what lets a scenario start at any step, run alone, and be reordered without touching it. It checks first and navigates only if needed, so a scenario already on the shop pays nothing. This is about the _browser's_ starting position, not the database: there is still no state reset (see above), and you still scope what you create.
|
|
@@ -367,7 +367,7 @@ string the generated step map is keyed by — the file name, and every helper in
|
|
|
367
367
|
are English in every project regardless of who the product is for or what
|
|
368
368
|
language the team speaks. There is no setting that changes this.
|
|
369
369
|
|
|
370
|
-
**Prose follows `
|
|
370
|
+
**Prose follows `metaLocale` in `pikku.config.json`** (default `en`). That is a
|
|
371
371
|
step's `description` and `template`, a feature's `name` and `description`, a
|
|
372
372
|
scenario's `title`, and the positional step names passed to
|
|
373
373
|
`scenario.given/when/then`. Read the field before you write any of them.
|
|
@@ -378,7 +378,7 @@ language. The report is the deliverable, and it is read by the team; the
|
|
|
378
378
|
identifier is an API, and it is read by the toolchain.
|
|
379
379
|
|
|
380
380
|
```typescript
|
|
381
|
-
// pikku.config.json: { "
|
|
381
|
+
// pikku.config.json: { "metaLocale": "de" }
|
|
382
382
|
export const buysAnApple = pikkuScenarioStep<{ qty: number }, { orderId: string }>({
|
|
383
383
|
name: 'buysAnApple', // identifier — English, always
|
|
384
384
|
description: 'kauft einen Apfel', // prose — follows locale
|
|
@@ -392,19 +392,19 @@ export const buysAnApple = pikkuScenarioStep<{ qty: number }, { orderId: string
|
|
|
392
392
|
Note what does **not** change: `placeOrder` is still `placeOrder`, and the file
|
|
393
393
|
is still `apple.scenario.ts`.
|
|
394
394
|
|
|
395
|
-
A product with a non-English UI is not on its own a reason to set `
|
|
395
|
+
A product with a non-English UI is not on its own a reason to set `metaLocale` — that
|
|
396
396
|
is the app's language, not the team's. Ask, or leave it `en`.
|
|
397
397
|
|
|
398
|
-
**Where a non-`en` `
|
|
398
|
+
**Where a non-`en` `metaLocale` still shows English, today.** The reporter composes a
|
|
399
399
|
sentence as `<Keyword> the <actor> <template>` (`composeStepProse`), and both the
|
|
400
400
|
keyword and the article `the` are English literals. The Console translates the
|
|
401
401
|
Given/When/Then keywords into its own UI language; the CLI reporter does not, and
|
|
402
|
-
nothing translates `the`. So `
|
|
402
|
+
nothing translates `the`. So `metaLocale: "de"` gives you German step prose inside an
|
|
403
403
|
English frame — `Given the shopper kauft 1 Äpfel`. Write templates that read
|
|
404
404
|
acceptably in that frame rather than trying to defeat it. A second gap: where a
|
|
405
405
|
function or scenario declares no `title`, the Console falls back to splitting the
|
|
406
406
|
**identifier** into an English-looking label (`toEnglishName`), so under a
|
|
407
|
-
non-`en` `
|
|
407
|
+
non-`en` `metaLocale` meta is worth authoring rather than leaving to the fallback.
|
|
408
408
|
|
|
409
409
|
### `then` bindings are witnesses, not alternatives
|
|
410
410
|
|
|
@@ -489,7 +489,7 @@ await scenario.given(
|
|
|
489
489
|
{ qty: 1 },
|
|
490
490
|
{ actor: actors.shopper }
|
|
491
491
|
)
|
|
492
|
-
// reporter renders: Given
|
|
492
|
+
// reporter renders: Given shopper buys 1 apples ✓ 412ms
|
|
493
493
|
```
|
|
494
494
|
|
|
495
495
|
Rules that bite:
|
|
@@ -606,7 +606,7 @@ await page.getByRole('button', { name: t('jobs_apply_submit'), exact: true }).cl
|
|
|
606
606
|
|
|
607
607
|
- Type off `messages/<baseLocale>.json`, **not** the generated Paraglide output — `i18n/paraglide/` is build output, so typing against it makes the tests unbuildable until the app has been built. The JSON is the tracked source.
|
|
608
608
|
- Fall back to the base locale for a key a locale has not translated. That is what Paraglide does at run time, so a helper that throws instead would disagree with the screen the test is looking at.
|
|
609
|
-
- This is not only about locators. A copy literal passed to a **project helper** (`pick('Where would you like to work?', …)`) reaches the DOM the same way, and so does a pane name quoted back in a failure message. `pikku fabric validate` scans every string in a `*.steps.ts` / `*.scenario.ts` against the base catalogue and errors on any verbatim match, wherever it sits.
|
|
609
|
+
- This is not only about locators. A copy literal passed to a **project helper** (`pick('Where would you like to work?', …)`) reaches the DOM the same way, and so does a pane name quoted back in a failure message. `pikku fabric validate` scans every string in a `*.steps.ts` / `*.scenario.ts` against the base catalogue and errors on any verbatim match, wherever it sits — except comments, and the `name` / `description` / `template` declared directly on a `pikkuFeature`, `pikkuScenario` or `pikkuScenarioStep`, which are Console meta written in the project's `locale` rather than app copy.
|
|
610
610
|
- A regex locator (`{ name: /^Next$/i }`) hides the literal but not the problem. `{ name: t('key'), exact: true }` is both stricter and locale-correct.
|
|
611
611
|
- Strings the catalogue does not own — a test id, a fixture filename, a seeded value — stay literal. The catalogue is the test for whether something is copy.
|
|
612
612
|
|
|
@@ -709,11 +709,12 @@ frontend gets a one-click "Sign in as …" switcher over the **same** list, and
|
|
|
709
709
|
app can be reviewed as each kind of user without anyone knowing a seed password.
|
|
710
710
|
|
|
711
711
|
The sandbox dev server bakes both halves into the frontend from the declared
|
|
712
|
-
personas: `VITE_DEV_ACTORS` (the JSON actor list) and
|
|
713
|
-
`
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
712
|
+
personas: `VITE_DEV_ACTORS` (the JSON actor list) and `VITE_DEV_ACTOR_SECRETS`
|
|
713
|
+
(`{ email: credential }`, one per persona — `SCENARIO_ACTOR_SECRET` itself never
|
|
714
|
+
goes in a bundle; see **pikku-better-auth**). Neither var is set in a production
|
|
715
|
+
build, so the control renders nothing there — but gate the reads on your
|
|
716
|
+
bundler's dev flag anyway (`import.meta.env.DEV ? … : undefined`) so no
|
|
717
|
+
credential reaches a production bundle in the first place.
|
|
717
718
|
|
|
718
719
|
Do not hand-roll the switcher: `useDevActors()` (`pikku-react`, a separate install) is the logic and
|
|
719
720
|
`<DevActorSwitcher />` from `@pikku/mantine/dev` is a ready rendering of it.
|
|
@@ -833,7 +834,7 @@ Services are plain objects — a Pikku function is pure business logic, so a moc
|
|
|
833
834
|
| Assuming a clean database | There is no state reset — it may be a staging server. Scope what you create. |
|
|
834
835
|
| `sleep()` before asserting | Use `expectEventually`. |
|
|
835
836
|
| A step named `clicksAddToBasket` / `opensThePage` | That is an action, not an intent. Name the step for what the actor wanted; put the clicking in a utility. |
|
|
836
|
-
| A step named `kauftEinenApfel` / a `vorgang` table | Identifiers are English in every project. The German belongs in `description` / `template`, and only when `pikku.config.json` sets `
|
|
837
|
+
| A step named `kauftEinenApfel` / a `vorgang` table | Identifiers are English in every project. The German belongs in `description` / `template`, and only when `pikku.config.json` sets `metaLocale`. |
|
|
837
838
|
| A browser step that assumes it is already on a page | It can then only run mid-flow. Arrive first — check the URL, navigate if needed. |
|
|
838
839
|
| `getByLabel('Full Name')` in a translated app | Passes only in the base locale, and a copy edit breaks it as an unexplained timeout. Locate by message key. |
|
|
839
840
|
| A `browser` binding guarding `if (!browser)` | The binding guarantees it. The guard hides the real error, which is a missing actor (`PKU677`). |
|