@pikku/skills 0.12.43 → 0.12.44

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pikku/skills",
3
- "version": "0.12.43",
3
+ "version": "0.12.44",
4
4
  "repository": {
5
5
  "type": "git",
6
6
  "url": "git+https://github.com/pikkujs/pikku.git",
@@ -500,10 +500,9 @@ value for the address being signed in as and compares, so a credential minted
500
500
  for one persona is refused for every other, and the root itself is never a valid
501
501
  credential. A root under 32 characters refuses the endpoint outright rather than
502
502
  deriving weak credentials from it (the server log names the problem; the client
503
- is not told which). Callers rarely derive by hand — `pikku dev` mints one per
504
- persona into `VITE_DEV_ACTOR_SECRETS` for the browser switcher, `pikku persona
505
- secret <id>` mints them for a run, and the two `PersonaSignIn` implementations
506
- derive on the fly.
503
+ is not told which). Callers rarely derive by hand — `pikku persona secret <id>`
504
+ mints them for a run, and the two `PersonaSignIn` implementations derive on the
505
+ fly. The browser switcher holds none: it signs in through `/sign-in/persona`.
507
506
 
508
507
  **Which command is running decides whether it works, not whether a secret is
509
508
  set.** `pikku dev` sets `PIKKU_DEV_ACTOR_SIGN_IN` and mints an ephemeral
@@ -559,9 +558,11 @@ actor`. So the secret cannot take over a **real user's** account — the blast
559
558
  paragraph above. The comparison is constant-time and length-hiding, so a wrong
560
559
  credential leaks neither the length nor a prefix of the right one.
561
560
 
562
- This is the endpoint `pikku scenario` signs its actors in through, and the one
563
- the frontend dev switcher posts to — see `pikku-scenario` for declaring the
564
- actors and `pikku-react` for `useDevActors()`.
561
+ This is the endpoint `pikku scenario` signs its actors in through. The frontend
562
+ switcher does not use it: it lists from `/sign-in/personas` and posts a persona
563
+ id to `/sign-in/persona`, both served by
564
+ `pikkuActor({ personaSignIn: { personas, featureFlags } })` with no credential — see
565
+ `pikku-scenario` for the setup and `pikku-react` for `useDevActors()`.
565
566
 
566
567
  ### Provisioning personas
567
568
 
@@ -573,17 +573,13 @@ frontend running against a dead API looks exactly like an app bug, so if every
573
573
  request fails, check that both halves came up.
574
574
 
575
575
  **Start the stack through `bun run dev`, not by launching `vite` or `pikku dev`
576
- yourself.** The dev script reads the personas, derives one credential per
577
- persona from `SCENARIO_ACTOR_SECRET`, and hands both to the frontend as
578
- `VITE_DEV_ACTORS` / `VITE_DEV_ACTOR_SECRETS`. Vite reads those once, at boot. A
579
- frontend started any other way — or restarted by hand later — has an empty
580
- actor list, and the switcher silently disappears from every page. If you do
581
- start the frontend on its own (say :3000 is taken by another project), you owe
582
- it three things: the two `VITE_DEV_*` values the dev script would have computed,
583
- and `VITE_API_PROXY` pointing at your API — the dev proxy defaults to
584
- `http://localhost:3000`, so beside another project's server your sign-ins go to
585
- _its_ API and come back `401 Invalid actor secret`, which reads like a bad
586
- credential rather than the wrong server.
576
+ yourself.** The "Sign in as …" switcher asks the API for its personas at
577
+ runtime, so the frontend needs nothing baked in — but it does need to reach
578
+ _your_ API. If you start the frontend on its own (say :3000 is taken by another
579
+ project), point `VITE_API_PROXY` at your API: the dev proxy defaults to
580
+ `http://localhost:3000`, so beside another project's server the switcher lists
581
+ _its_ personas, or none, which reads like a missing switcher rather than the
582
+ wrong server.
587
583
 
588
584
  The `--bun` in `bunx --bun pikku …` is load-bearing — keep it. Without it the
589
585
  CLI's `#!/usr/bin/env node` shebang hands the process to whatever Node is on
@@ -709,6 +705,21 @@ new RPC and does not re-run `afterStart`, so a fresh function answers 404 and
709
705
  anything provisioned at boot is missing — failures that read like a wiring bug
710
706
  and are nothing but a stale process.
711
707
 
708
+ **Run the whole suite, not the milestone's own scenarios.** The milestone's
709
+ scenarios are the ones you wrote to pass; the regression lives in someone
710
+ else's. Tightening what "archived" means is a one-function change that reads as
711
+ local and quietly breaks the milestone-01 scenario nobody re-ran.
712
+
713
+ **Restart the server after adding a function, and never edit one while a run is
714
+ in flight.** Hot reload does not register a new RPC and does not re-run
715
+ `afterStart`, so a fresh function answers 404 and anything provisioned at boot
716
+ is missing — failures that read like a wiring bug and are nothing but a stale
717
+ process. The same reload is what makes a run unrepeatable if you edit during
718
+ it: a browser pass is long enough to feel like free time, and a schema touched
719
+ at minute four hot-reloads into a half-generated contract, so every scenario
720
+ after that point fails on something you have already fixed. Wait for the run or
721
+ kill it — a run you edited under is not a result.
722
+
712
723
  ### 7a. Coverage — which functions have actually been run
713
724
 
714
725
  Green scenarios tell you the journeys you wrote still work. They say nothing
@@ -477,17 +477,16 @@ reviewer has no seed password, so without the control they are locked out of the
477
477
  app they were asked to look at.
478
478
 
479
479
  Satisfy it with `<DevActorSwitcher />` from `@pikku/mantine/dev`, or with your
480
- own UI built on `useDevActors()` from `@pikku/react` — validate accepts either
481
- call site as evidence, so custom rendering passes. See **pikku-react** for the
482
- props and **pikku-scenario** for where the actor list comes from.
480
+ own UI built on `useDevActors()` or `signInAsPersona()` from `@pikku/react` —
481
+ validate accepts any of those call sites as evidence, so custom rendering
482
+ passes. Either way the server needs `personaSignIn` on `pikkuActor`; see
483
+ **pikku-scenario** for it and **pikku-react** for the props.
483
484
 
484
485
  The validator also accepts the shapes that predate the package — a hand-rolled
485
486
  `signInAsActor()` or a literal `POST /auth/sign-in/actor` — so an older app does
486
487
  not fail the build. **Treat that as a grace period, not the target: migrate those
487
- to `<DevActorSwitcher />`.** The hand-copied version is exactly the duplication
488
- the package exists to remove, and the copies drift — the ones that prompted this
489
- had already diverged on the `import.meta.env.DEV` gate that keeps the shared
490
- secret out of production bundles.
488
+ to `<DevActorSwitcher />`.** They put a per-persona credential in the frontend
489
+ bundle, which the persona endpoint exists to avoid.
491
490
 
492
491
  Do **not** satisfy it with Better Auth's `/dev/quick-login`. That is a different
493
492
  endpoint with a different purpose — one fixed admin, not the declared personas —
@@ -6,7 +6,7 @@ description: >-
6
6
  direct usePikkuRPC / usePikkuFetch calls, realtime subscriptions, agent and workflow hooks, and
7
7
  the dev actor switcher. TRIGGER when: writing a React component that fetches or mutates backend
8
8
  data, wiring PikkuProvider, paginating, running or tracking a workflow from the client, or
9
- asking about useDevActors / VITE_DEV_ACTORS / quick login. DO NOT TRIGGER when: working on the
9
+ asking about useDevActors / DevActorSwitcher / quick login. DO NOT TRIGGER when: working on the
10
10
  backend (use pikku-wiring), defining the workflow itself (use pikku-workflow), or writing
11
11
  user-facing copy (use pikku-i18n).
12
12
  installGroups: [client]
@@ -251,14 +251,9 @@ their own sandbox.
251
251
  ```tsx
252
252
  import { useDevActors } from '@pikku/react'
253
253
 
254
- const { actors, signInAs, pendingEmail, isPending, error } = useDevActors({
255
- // Gate both reads on the bundler's dev flag so no credential can reach a
256
- // production bundle. The sandbox dev server bakes them from your personas.
257
- actors: import.meta.env.DEV ? import.meta.env.VITE_DEV_ACTORS : undefined,
258
- secrets: import.meta.env.DEV
259
- ? import.meta.env.VITE_DEV_ACTOR_SECRETS
260
- : undefined,
254
+ const { actors, signInAs, pendingId, isPending, error } = useDevActors({
261
255
  apiUrl: apiUrl(),
256
+ app: appSlug,
262
257
  onSignedIn: () => navigate({ to: '/' }),
263
258
  })
264
259
  ```
@@ -267,21 +262,19 @@ const { actors, signInAs, pendingEmail, isPending, error } = useDevActors({
267
262
  `<DevActorSwitcher />` from `@pikku/mantine/dev` — a separate entry point from
268
263
  `@pikku/mantine/core`, whose contract is "drop-in alias for `@mantine/core`"
269
264
  and so must not export components Mantine has no counterpart for.
270
- - **`secrets` is `{ address: credential }`, not one shared value** — a
271
- credential opens the one persona it was minted for (see
272
- **pikku-auth**). `actors` is empty unless the host supplied both a list
273
- and the credentials for it, and an actor with no credential is not offered, so
274
- a production build renders nothing without you testing for it.
275
- - **It takes `onSignedIn` rather than a router**, and takes the env values rather
276
- than reading them, because how env is spelled is a bundler fact
277
- (`import.meta.env.VITE_*` vs `process.env.NEXT_PUBLIC_*`).
278
- - The underlying `signInAsActor()` and `parseDevActors()` are exported too, for a
279
- non-React caller. The endpoint only accepts rows flagged `actor: true`, so it
280
- can never impersonate a real user — see **pikku-auth**.
281
-
282
- Do not hand-write the `devActors()` / `signInAsActor()` pair per app; that
283
- copy-paste, including the `import.meta.env.DEV` gate, is exactly what this
284
- replaced.
265
+ - **No credential reaches the bundle.** It lists from `/auth/sign-in/personas`
266
+ and `signInAs(id)` posts only the persona id to `/auth/sign-in/persona`. The
267
+ server decides who is offered and who may sign in, and offers nobody in
268
+ production — see **pikku-scenario** for `personaSignIn`.
269
+ - **It takes `onSignedIn` rather than a router**, since every app lands
270
+ somewhere different.
271
+ - `listDevActors()` and `signInAsPersona()` are exported too, for a non-React
272
+ caller. The endpoint only
273
+ signs in rows flagged `actor: true`, so it can never impersonate a real user —
274
+ see **pikku-auth**.
275
+
276
+ Do not hand-write the list-and-sign-in pair per app; that copy-paste is exactly
277
+ what this replaced.
285
278
 
286
279
  ### Linking from a Mantine element: `renderRoot`, not `component`
287
280
 
@@ -83,13 +83,17 @@ Declared actors are not only for automated runs. `signInPath` is Better Auth's
83
83
  frontend gets a one-click "Sign in as …" switcher over the **same** list, and an
84
84
  app can be reviewed as each kind of user without anyone knowing a seed password.
85
85
 
86
- The dev server bakes both halves into the frontend from the declared
87
- personas — the sandbox's, or the template's `bun run dev`, never `pikku dev`: `VITE_DEV_ACTORS` (the JSON actor list) and `VITE_DEV_ACTOR_SECRETS`
88
- (`{ email: credential }`, one per persona — `SCENARIO_ACTOR_SECRET` itself never
89
- goes in a bundle; see **pikku-auth**). Neither var is set in a production
90
- build, so the control renders nothing there — but gate the reads on your
91
- bundler's dev flag anyway (`import.meta.env.DEV ? … : undefined`) so no
92
- credential reaches a production bundle in the first place.
86
+ The switcher holds no credential. It lists personas from
87
+ `/auth/sign-in/personas` and signs in by posting only a persona id to
88
+ `/auth/sign-in/persona`; the server resolves the address. One server piece
89
+ serves both, in the auth config:
90
+
91
+ ```ts snippet:personaSignIn
92
+ ```
93
+
94
+ It is always open under `pikku dev`. A deployed stage needs actor
95
+ sign-in opted in **and** its `devSwitcher` feature flag on; production never
96
+ has the opt-in, so it lists nobody and refuses every persona sign-in.
93
97
 
94
98
  Do not hand-roll the switcher: `useDevActors()` (`pikku-react`, a separate install) is the logic and
95
99
  `<DevActorSwitcher />` from `@pikku/mantine/dev` is a ready rendering of it.
@@ -98,52 +102,16 @@ one — without it a reviewer is locked out of their own sandbox.
98
102
  When the switcher is missing, it is one of three things, and none of them
99
103
  errors:
100
104
 
101
- - **The frontend was not started by the dev script.** The two `VITE_DEV_*` vars
102
- are computed by `bun run dev` and read by vite once, at boot. A bare `vite dev`
103
- — including one restarted by hand — has an empty list and renders nothing.
104
- - **`SCENARIO_ACTOR_SECRET` is not in `.env`.** No root secret, no per-persona
105
- credentials, and the switcher filters out every actor it cannot sign in.
105
+ - **The list is empty.** On a deployed stage that is the gate
106
+ doing its job — check the opt-in and the `devSwitcher` flag. Locally, check
107
+ the personas declare an `email` (via `scenarios.emailDomain`) and are not
108
+ `runnable: false`.
109
+ - **`personaSignIn` is missing, or the frontend calls another API.** A dev
110
+ proxy (`VITE_API_PROXY`, default `http://localhost:3000`) that points at
111
+ another project's API lists that project's personas, or none.
106
112
  - **It is not mounted on the page you are looking at.** The template mounts it
107
113
  on the login screen. A public homepage that replaces the `/` → `/app`
108
114
  redirect needs its own `<DevActorSwitcher />` in the public layout.
109
115
 
110
- When the switcher is there but signing in fails with `401 Invalid actor
111
- secret`, check which server answered before checking the secret: a frontend
112
- whose dev proxy (`VITE_API_PROXY`, default `http://localhost:3000`) points at
113
- another project's API sends the sign-in there.
114
-
115
- **A runner of your own that starts vite has to bake them itself**, from the
116
- generated persona meta (`<outDir>/workflow/personas.gen.json`, which already
117
- carries the derived `email`):
118
-
119
- ```js
120
- const personas = Object.values(JSON.parse(readFileSync(personasPath, 'utf8')))
121
-
122
- env.VITE_DEV_ACTORS = JSON.stringify(
123
- personas.map(({ id, email, name, jobTitle }) => ({
124
- key: id,
125
- email,
126
- name,
127
- jobTitle: jobTitle ?? '',
128
- }))
129
- )
130
- env.VITE_DEV_ACTOR_SECRETS = JSON.stringify(
131
- Object.fromEntries(
132
- await Promise.all(
133
- personas.map(async ({ email }) => [
134
- email,
135
- await deriveActorSecret(env.SCENARIO_ACTOR_SECRET, email),
136
- ])
137
- )
138
- )
139
- )
140
- ```
141
-
142
- **Set `SCENARIO_ACTOR_SECRET` yourself**, at least 32 characters, in the
143
- environment both processes read. Left unset, `pikku dev` mints an ephemeral root
144
- for its own run that a separately spawned vite cannot see, so the two derive
145
- from different roots: the switcher renders every persona and each click is
146
- refused, which reads as a broken login rather than missing configuration. On a
147
- brand-new project the persona file does not exist until the first `pikku dev`
148
- codegen, after vite has baked an empty list — watch it and restart the frontend
149
- when it changes.
116
+ When the switcher lists personas but a click 404s, that persona has no `email`
117
+ or is `runnable: false`.