@pikku/skills 0.12.42 → 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/README.md +3 -2
- package/dist/skills.gen.js +2 -2
- package/package.json +1 -1
- package/skills/pikku-auth/references/better-auth.md +8 -7
- package/skills/pikku-build/references/app.md +22 -11
- package/skills/pikku-changes/SKILL.md +74 -122
- package/skills/pikku-fabric/SKILL.md +6 -7
- package/skills/pikku-react/SKILL.md +1 -1
- package/skills/pikku-react/references/client.md +15 -22
- package/skills/pikku-scenario/references/personas.md +20 -52
- package/skills/pikku-wiring/references/trigger.md +12 -7
package/package.json
CHANGED
|
@@ -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
|
|
504
|
-
|
|
505
|
-
|
|
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
|
|
563
|
-
|
|
564
|
-
|
|
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
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
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
|
|
@@ -1,172 +1,124 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: pikku-changes
|
|
3
|
-
description: 'Work a project''s changes queue — the todo list someone filed by
|
|
3
|
+
description: 'Work a Fabric project''s changes queue — the todo list someone filed by circling things on a deployed stage. Covers `pikku fabric changes next|claim|show|ask|shot|done`: waiting for work without polling, asking instead of guessing, offering options as images, one commit per item. TRIGGER when: the user says "run the pikkufabric changes", "run the changes against <stage>", "work the changes (queue)", "watch the changes", "pick up the changes", names a change by its #number, or you are otherwise idle in a repo that has a pikkufabric.config.json. DO NOT TRIGGER for git changes, diffs or changelogs, and not for deploying or debugging a stage — use pikku-fabric for those.'
|
|
4
4
|
installGroups: [fabric]
|
|
5
5
|
---
|
|
6
6
|
|
|
7
7
|
# Working a changes queue
|
|
8
8
|
|
|
9
|
-
Someone walked the deployed app and circled
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
them regret filing.
|
|
9
|
+
Someone walked the deployed app and circled things. Each item is their words, a
|
|
10
|
+
screenshot of what they saw, and the elements the circle enclosed. You have the repo.
|
|
11
|
+
Empty the queue without making them regret filing.
|
|
13
12
|
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
Every argument is a flag; nothing is positional. `--json` works on any of them. The
|
|
17
|
-
project comes from the local `pikkufabric.config.json`, so `--project-id` is only needed
|
|
18
|
-
when you are not in the checkout.
|
|
13
|
+
Run every command from the checkout: the project comes from `pikkufabric.config.json`.
|
|
14
|
+
`--json` works on all of them. Items are addressed as `2`, `#2` or their uuid.
|
|
19
15
|
|
|
20
|
-
|
|
21
|
-
pikku fabric changes list --pickup-only --json
|
|
22
|
-
pikku fabric changes claim --change-ids <id>,<id> --title "Checkout pass" --claimed-by claude-code
|
|
23
|
-
pikku fabric changes show --change-id <id>
|
|
24
|
-
pikku fabric changes ask --change-id <id> --question "…" --option "…" --option "…" --author-name claude-code
|
|
25
|
-
pikku fabric changes shot --change-id <id> --label "Bigger" --kind option --image a.png
|
|
26
|
-
pikku fabric changes done --change-id <id> --note "What you did"
|
|
27
|
-
```
|
|
16
|
+
## Which stage
|
|
28
17
|
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
18
|
+
The queue is per project. "Against develop" or a pasted stage URL narrows it:
|
|
19
|
+
`--stage` takes a branch, the stage URL (as filed, path optional) or a stage id. With
|
|
20
|
+
no stage named, work the whole project. An unknown name prints the stages there are.
|
|
32
21
|
|
|
33
|
-
|
|
34
|
-
forms the group. Claim an existing one with `--group-id`.
|
|
22
|
+
## The loop
|
|
35
23
|
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
forever — but a second harness picking up something you are halfway through is the failure
|
|
39
|
-
this prevents.
|
|
24
|
+
**Never poll.** No `sleep` loops, no repeated `list`, no re-running `show` to see if
|
|
25
|
+
something changed. `next` does the waiting and exits only when there is work.
|
|
40
26
|
|
|
41
|
-
|
|
27
|
+
1. Start `next` as a **background** command, and stop there until it exits:
|
|
42
28
|
|
|
43
|
-
|
|
29
|
+
```bash
|
|
30
|
+
pikku fabric changes next --stage develop --claim --claimed-by claude-code
|
|
31
|
+
```
|
|
44
32
|
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
straight to a component because it is the i18n message key.
|
|
50
|
-
4. **The source anchor**, printed as `src/routes/app.orders.tsx:42 as of a91c4e2`. That line
|
|
51
|
-
number is where the JSX was **at that commit**. Read it as a starting point and find
|
|
52
|
-
today's equivalent; never edit line 42 of today's file because the anchor said 42.
|
|
33
|
+
It waits out the grace window (a just-filed item is held about a minute so a batch
|
|
34
|
+
being typed arrives together), claims what is ready as one group, prints it, and
|
|
35
|
+
exits. It also wakes when someone answers a question you asked under that
|
|
36
|
+
`--claimed-by`.
|
|
53
37
|
|
|
54
|
-
|
|
55
|
-
different things, believe the circle.
|
|
38
|
+
2. When it exits, read the exit code:
|
|
56
39
|
|
|
57
|
-
|
|
40
|
+
| code | meaning | do |
|
|
41
|
+
| --- | --- | --- |
|
|
42
|
+
| 0 | work printed (claimed, and/or `Answered`) | work it, then step 3 |
|
|
43
|
+
| 2 | `--timeout`/`--once` found nothing | stop, or restart `next` |
|
|
44
|
+
| 3 | session refused | tell the user to run `pikku fabric login`; stop |
|
|
45
|
+
| 1 | anything else (bad `--stage`, fabric down for minutes) | report the message; stop |
|
|
58
46
|
|
|
59
|
-
|
|
60
|
-
|
|
47
|
+
3. For each item: `show` → fix → commit → `done`, or `ask` and move on. Then start
|
|
48
|
+
`next` again, in the background.
|
|
61
49
|
|
|
62
|
-
|
|
63
|
-
- "Make the total stand out" — bigger, bolder, coloured, or moved above the fold?
|
|
64
|
-
- "This should be faster" — is it the spinner, the request, or the number of steps?
|
|
65
|
-
- Anything that changes what data is stored, what an existing user sees, or what something costs.
|
|
50
|
+
Without `--claim` it only reports what is claimable; claim it yourself:
|
|
66
51
|
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
- "Can you confirm you want the button blue?" — they said blue.
|
|
52
|
+
```bash
|
|
53
|
+
pikku fabric changes claim --change-ids 3,4 --title "Checkout pass" --claimed-by claude-code
|
|
54
|
+
```
|
|
71
55
|
|
|
72
|
-
A
|
|
56
|
+
A `claim` refused with a 409 says per item why (held, claimed by someone else, done).
|
|
57
|
+
For held items, run `next --claim` rather than retrying. The lease is 30 minutes
|
|
58
|
+
(`--lease-minutes`); an abandoned claim returns to the queue by itself.
|
|
73
59
|
|
|
74
|
-
##
|
|
60
|
+
## Reading an item
|
|
75
61
|
|
|
76
|
-
|
|
77
|
-
the sentence.
|
|
62
|
+
`pikku fabric changes show 3` gives, most trustworthy first:
|
|
78
63
|
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
64
|
+
1. **Their words.** The title and body are the requirement. Everything else is evidence.
|
|
65
|
+
2. **The screenshot.** What they saw, at their width, with their data. When the other
|
|
66
|
+
addresses disagree with the picture, the picture is right.
|
|
67
|
+
3. **The circled elements**: a testid (greps straight to a component, it is the i18n
|
|
68
|
+
key), a source anchor, a CSS path.
|
|
69
|
+
4. **The source anchor**, `src/routes/app.orders.tsx:42 as of a91c4e2`, is where the JSX
|
|
70
|
+
was at *that* commit. Find today's equivalent; never edit line 42 because it said 42.
|
|
84
71
|
|
|
85
|
-
|
|
86
|
-
the panel and the console, and clicking one records the answer; a question that says
|
|
87
|
-
"(a) build it, (b) leave existing bookings, (c) hold" makes them re-type in free text what
|
|
88
|
-
they should have been able to click, and leaves you parsing prose to find out which one
|
|
89
|
-
they meant. If you can enumerate them in the sentence, you can pass them as flags.
|
|
72
|
+
## When to ask
|
|
90
73
|
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
74
|
+
Ask when the item admits more than one reasonable implementation and you would be
|
|
75
|
+
picking for them — "make the total stand out" (bigger? bolder? moved?), anything that
|
|
76
|
+
changes stored data, what an existing user sees, or what something costs. Do not ask
|
|
77
|
+
what the item already says, or implementation choices that are yours.
|
|
94
78
|
|
|
95
|
-
|
|
96
|
-
|
|
79
|
+
One decision, in their vocabulary, with the choices as `--option` flags — each becomes
|
|
80
|
+
a button. Include "hold until I check" when it is real. Batch questions per group.
|
|
97
81
|
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
82
|
+
```bash
|
|
83
|
+
pikku fabric changes ask --change-id 3 --question "Make the total stand out — which way?" \
|
|
84
|
+
--option "Bigger" --option "Move it above the delivery line" --author-name claude-code
|
|
85
|
+
```
|
|
101
86
|
|
|
102
|
-
|
|
87
|
+
Then **park it** and move on. The answer wakes `next` (same `--claimed-by`); it prints
|
|
88
|
+
under `Answered`, and `show` has the reply.
|
|
103
89
|
|
|
104
|
-
If the answer is visual and you can build it, build
|
|
90
|
+
If the answer is visual and you can build it, build each variant, screenshot all of
|
|
91
|
+
them in one pass at one width (baseline included), and attach them — the panel turns
|
|
92
|
+
`--kind option` shots into a pick-one:
|
|
105
93
|
|
|
106
94
|
```bash
|
|
107
|
-
pikku fabric changes shot --change-id
|
|
108
|
-
pikku fabric changes shot --change-id <id> --label "Above the line" --kind option --image b.png
|
|
95
|
+
pikku fabric changes shot --change-id 3 --label "Bigger" --kind option --image a.png
|
|
109
96
|
```
|
|
110
97
|
|
|
111
|
-
|
|
112
|
-
picking one writes the choice into the thread. Capture every variant in **one pass at one
|
|
113
|
-
width**, including the baseline — variants shot at different sizes are not comparable, and
|
|
114
|
-
comparing is the whole point.
|
|
115
|
-
|
|
116
|
-
`--kind evidence` is the other use: a picture that proves something, rendered inline rather
|
|
117
|
-
than as a choice.
|
|
98
|
+
`--kind evidence` is a picture that proves something, shown inline.
|
|
118
99
|
|
|
119
100
|
## Committing
|
|
120
101
|
|
|
121
|
-
One item, one commit
|
|
122
|
-
|
|
123
|
-
undone without taking the other three with it. Land unrelated work separately.
|
|
124
|
-
|
|
125
|
-
The subject carries the short id the way a GitHub issue number does, and the uuid goes in a
|
|
126
|
-
trailer so `git log --grep` has an exact handle:
|
|
102
|
+
One item, one commit — `done` records one sha, and that is what a human reverts. The
|
|
103
|
+
subject carries the short id; the uuid goes in a trailer:
|
|
127
104
|
|
|
128
105
|
```
|
|
129
|
-
|
|
106
|
+
fix(booking): #7 stop the date picker closing on the first click
|
|
130
107
|
|
|
131
108
|
Change-Id: 0f3c8a12-9b44-4d2e-8f01-27c6a1d9e5b3
|
|
132
109
|
```
|
|
133
110
|
|
|
134
|
-
|
|
135
|
-
`feat`, `fix`, `style`, `refactor` — with the scope naming the screen or area they were
|
|
136
|
-
looking at, not the file you edited.
|
|
137
|
-
|
|
138
|
-
More:
|
|
139
|
-
|
|
140
|
-
```
|
|
141
|
-
fix(booking): #7 stop the date picker closing on the first click
|
|
142
|
-
style(nav): #12 tighten the spacing around the logo
|
|
143
|
-
```
|
|
144
|
-
|
|
145
|
-
Reverting one later is then:
|
|
146
|
-
|
|
147
|
-
```bash
|
|
148
|
-
git revert $(git log --grep="Change-Id: <uuid>" --format=%H -1)
|
|
149
|
-
```
|
|
111
|
+
Scope names the screen they were looking at, not the file you edited.
|
|
150
112
|
|
|
151
113
|
## Finishing
|
|
152
114
|
|
|
153
|
-
`done` records the branch and commit that closed it, which is what strikes the item through
|
|
154
|
-
on the page it was filed on and tells them where the fix landed. Both default to the
|
|
155
|
-
checkout you are standing in, so run it from there and let it read git:
|
|
156
|
-
|
|
157
115
|
```bash
|
|
158
|
-
pikku fabric changes done --change-id
|
|
159
|
-
--note "What you did, for whoever reads the thread later"
|
|
116
|
+
pikku fabric changes done --change-id 7 --note "What you did, for whoever reads the thread"
|
|
160
117
|
```
|
|
161
118
|
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
An item you decided not to do is not `done`. Say why in the thread and leave it for a human
|
|
167
|
-
to dismiss.
|
|
168
|
-
|
|
169
|
-
## Scope
|
|
119
|
+
Branch and commit default to the checkout you are in — run it there, never type a sha.
|
|
120
|
+
An item you decided not to do is not `done`: say why in the thread and leave it for a
|
|
121
|
+
human to dismiss.
|
|
170
122
|
|
|
171
|
-
Writes need the `changes:project:write` scope
|
|
172
|
-
|
|
123
|
+
Writes need the `changes:project:write` scope; `list`, `show` and `next` without
|
|
124
|
+
`--claim` are reads.
|
|
@@ -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` —
|
|
481
|
-
call
|
|
482
|
-
|
|
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 />`.**
|
|
488
|
-
|
|
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 /
|
|
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,
|
|
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
|
-
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
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
|
|
87
|
-
personas
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
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
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
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
|
|
111
|
-
|
|
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`.
|
|
@@ -105,6 +105,9 @@ subscribe to its events as `<source>:<event>`:
|
|
|
105
105
|
```
|
|
106
106
|
|
|
107
107
|
- The route is `POST /webhooks/<name>` unless `method`/`route` say otherwise.
|
|
108
|
+
`method` may be a list, e.g. `['get', 'post']` for a provider that verifies
|
|
109
|
+
the URL with a GET and delivers events with a POST, or `['head', 'post']`
|
|
110
|
+
for one that checks the URL with a HEAD.
|
|
108
111
|
It needs no session.
|
|
109
112
|
- `events` maps each event name to a schema. An event that fails its schema is
|
|
110
113
|
logged and dropped; so is one no `wireTrigger` listens for. Both still get a
|
|
@@ -126,9 +129,12 @@ id?, data }] }`, or `{ respond: { status, body } }` for a handshake. Throwing
|
|
|
126
129
|
even on queues that ignore job ids, and records each attempt and its last
|
|
127
130
|
error. Its `webhookReceipt` table comes from `pikku db generate`; `pikku dev`
|
|
128
131
|
and `pikku serve` use it when a Kysely database is configured.
|
|
129
|
-
- `receive` sees singleton services without `secrets
|
|
130
|
-
|
|
131
|
-
|
|
132
|
+
- `receive` sees singleton services without `secrets`. Declare the signing
|
|
133
|
+
secret with `defineCredential({ type: 'singleton', ... })` and hold it as
|
|
134
|
+
`WebhookSigningSecret.fromCredential(provider, credentialService, name)`
|
|
135
|
+
from `@pikku/core/hmac`; `receive` calls `await signingSecret.load()` and
|
|
136
|
+
checks against what it returns. A handshake that hands over the secret
|
|
137
|
+
(Asana) stores it with `credentialService.set`.
|
|
132
138
|
|
|
133
139
|
`check`, `setup` and `teardown` register the route with the provider. Each gets
|
|
134
140
|
`{ url, label, events, previous? }`, where `events` are only the ones some
|
|
@@ -142,10 +148,9 @@ pikku webhooks teardown --url https://api.example.com --labelPrefix shop:prod --
|
|
|
142
148
|
|
|
143
149
|
Each prints one JSON line per source (`ok`, `missing`, `drifted`, `created`,
|
|
144
150
|
`updated`, `unchanged`, `manual`, `deleted`, `absent`, `skipped`, `failed`). `setup` only
|
|
145
|
-
runs where `check` does not report `ok
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
`{ secretName: secret }` and left out of stdout. Any step can be
|
|
151
|
+
runs where `check` does not report `ok`. A `setup` that gets a signing secret
|
|
152
|
+
from the provider stores it with `credentialService.set`, and `teardown`
|
|
153
|
+
deletes it, so a new secret needs no deploy and never reaches stdout. Any step can be
|
|
149
154
|
`ref('<addon>:<fn>')` to use an addon's implementation.
|
|
150
155
|
|
|
151
156
|
## Usage Patterns
|