@pikku/skills 0.12.34 → 0.12.37

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.
Files changed (43) hide show
  1. package/README.md +9 -4
  2. package/dist/index.d.ts +7 -4
  3. package/dist/index.js +9 -5
  4. package/dist/skills.gen.d.ts +1 -0
  5. package/dist/skills.gen.js +5 -3
  6. package/dist/snippets.d.ts +26 -0
  7. package/dist/snippets.js +148 -0
  8. package/package.json +2 -2
  9. package/skills/pikku-addon/SKILL.md +41 -30
  10. package/skills/pikku-addon/references/addon-package-manifest.md +9 -4
  11. package/skills/pikku-addon/references/openapi.md +99 -0
  12. package/skills/pikku-agent/references/agents.md +3 -1
  13. package/skills/pikku-architect/SKILL.md +12 -0
  14. package/skills/pikku-auth/references/better-auth.md +16 -0
  15. package/skills/pikku-build/SKILL.md +29 -0
  16. package/skills/pikku-build/references/app.md +52 -4
  17. package/skills/pikku-build/references/feature.md +23 -96
  18. package/skills/pikku-build/references/quick.md +12 -3
  19. package/skills/pikku-changes/SKILL.md +172 -0
  20. package/skills/pikku-concepts/SKILL.md +33 -138
  21. package/skills/pikku-concepts/references/bootstrap.md +58 -0
  22. package/skills/pikku-concepts/references/concept-mapping.md +16 -0
  23. package/skills/pikku-concepts/references/language.md +87 -0
  24. package/skills/pikku-deploy/SKILL.md +1 -1
  25. package/skills/pikku-fabric/SKILL.md +26 -13
  26. package/skills/pikku-guide/SKILL.md +264 -0
  27. package/skills/pikku-knowledge/SKILL.md +10 -0
  28. package/skills/pikku-kysely/SKILL.md +1 -1
  29. package/skills/pikku-mantine/SKILL.md +80 -0
  30. package/skills/pikku-n8n-import/SKILL.md +4 -3
  31. package/skills/pikku-react/references/client.md +12 -0
  32. package/skills/pikku-realtime/SKILL.md +6 -6
  33. package/skills/pikku-report/SKILL.md +143 -0
  34. package/skills/pikku-scenario/SKILL.md +71 -563
  35. package/skills/pikku-scenario/references/browser.md +59 -0
  36. package/skills/pikku-scenario/references/coverage.md +70 -0
  37. package/skills/pikku-scenario/references/personas.md +87 -0
  38. package/skills/pikku-scenario/references/steps.md +366 -0
  39. package/skills/pikku-service-backends/SKILL.md +1 -1
  40. package/skills/pikku-wiring/SKILL.md +1 -1
  41. package/skills/pikku-wiring/references/http.md +8 -0
  42. package/skills/pikku-wiring/references/mcp.md +59 -0
  43. package/skills/pikku-workflow/SKILL.md +7 -8
@@ -0,0 +1,58 @@
1
+ # Server bootstrap
2
+
3
+ There are two ways to start a Pikku app. Pick based on whether you need to own the HTTP server.
4
+
5
+ **1. Let Pikku own the server (preferred when you don't need a specific runtime)**
6
+
7
+ `pikku dev` and `pikku serve` create the config and singleton services, start the server, and shut it down cleanly. You write no bootstrap code at all — startup and shutdown work goes in lifecycle hooks:
8
+
9
+ ```typescript
10
+ // src/lifecycle.ts
11
+ import { pikkuServerLifecycle } from '@pikku/core'
12
+ import type { SingletonServices } from '../types/application-types.js'
13
+
14
+ export const lifecycle = pikkuServerLifecycle<SingletonServices>({
15
+ beforeStart: async ({ kysely }) => {
16
+ await runMigrations(kysely)
17
+ },
18
+ afterStart: async ({ logger }) => {
19
+ logger.info('accepting traffic')
20
+ },
21
+ beforeStop: async ({ queueService }) => {
22
+ await queueService.drain()
23
+ },
24
+ })
25
+ ```
26
+
27
+ Export exactly one `pikkuServerLifecycle` from anywhere in `srcDirectories` — the inspector finds it by the wrapper call. Every hook is optional and receives the already-created singleton services. See pikku-services for the ordering and the `afterStop` caveat.
28
+
29
+ **Only `pikku dev` and `pikku serve` invoke these hooks.** No deploy runtime does, so anything a Workers or serverless stage needs done cannot live here — put it on the request path that needs it, guarded by a cheap check.
30
+
31
+ **2. Bootstrap it yourself (required for a specific runtime)**
32
+
33
+ Express, Fastify, uWS, Lambda, Cloudflare and Next.js need their own entrypoint, because Pikku is embedded in a server you own:
34
+
35
+ ```typescript
36
+ import '../../functions/.pikku/pikku-bootstrap.gen.js' // Generated — registers all wirings
37
+
38
+ const config = await createConfig()
39
+ const singletonServices = await createSingletonServices(config)
40
+
41
+ // Pick your runtime:
42
+ const server = new PikkuFastifyServer(
43
+ config,
44
+ singletonServices,
45
+ createWireServices
46
+ )
47
+ // or: new PikkuExpressServer(config, singletonServices, createWireServices)
48
+ // or: pikkuAWSLambdaHandler(singletonServices)
49
+ // or: PikkuCloudflareHandler(singletonServices)
50
+ // or: pikkuNextHandler(singletonServices)
51
+
52
+ await server.init()
53
+ await server.start()
54
+ ```
55
+
56
+ **Lifecycle hooks do not run on this path** — only `pikku dev` and `pikku serve` invoke them. Do your startup work directly in the entrypoint instead.
57
+
58
+ `pikku validate` warns when a project starts a server by hand _and_ depends on no runtime adapter, since that combination means path 1 was available and unused. Silence it with `"lint": { "customServerBootstrap": "off" }` in `pikku.config.json`.
@@ -2,6 +2,22 @@
2
2
 
3
3
  Authoritative mapping table plus side-by-side code examples showing how common backend patterns translate to Pikku.
4
4
 
5
+ - [Quick Reference Table](#quick-reference-table)
6
+ - [Route Handler / Controller → pikkuFunc](#route-handler--controller--pikkufunc)
7
+ - [Route Parameters → Merged into Data](#route-parameters--merged-into-data)
8
+ - [Middleware → pikkuMiddleware](#middleware--pikkumiddleware)
9
+ - [Auth Guard → Built-in Auth Middleware](#auth-guard--built-in-auth-middleware)
10
+ - [Authorization / Role Checks → pikkuPermission](#authorization--role-checks--pikkupermission)
11
+ - [DTO / Request Validation → Standard Schema](#dto--request-validation--standard-schema)
12
+ - [Dependency Injection → Service Factories](#dependency-injection--service-factories)
13
+ - [WebSocket Handlers → wireChannel](#websocket-handlers--wirechannel)
14
+ - [Job Queue Workers → wireQueueWorker](#job-queue-workers--wirequeueworker)
15
+ - [Cron / Scheduled Tasks → wireScheduler](#cron--scheduled-tasks--wirescheduler)
16
+ - [Module / Feature Grouping → Tags + File Organization](#module--feature-grouping--tags--file-organization)
17
+ - [Error Handling → Typed Errors](#error-handling--typed-errors)
18
+ - [Session Management](#session-management)
19
+ - [API Client Generation](#api-client-generation)
20
+
5
21
  ## Quick Reference Table
6
22
 
7
23
  | Generic Backend Concept | Pikku Equivalent | Skill |
@@ -0,0 +1,87 @@
1
+ # What language you write in
2
+
3
+ Three different things in a Pikku project have a human language, and they are
4
+ **not** the same language. Collapsing them is the mistake this section exists to
5
+ prevent, and it has already shipped in a real product — the failure is at the
6
+ bottom.
7
+
8
+ | Axis | What it covers | What decides it |
9
+ | --------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------- |
10
+ | **Identifiers** | Function, component, type, variable and file names. Database tables and columns. Branch names and commit messages. | Nothing. **Always English.** There is no setting. |
11
+ | **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`. |
12
+ | **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. |
13
+
14
+ ## Identifiers are English, and nothing changes that
15
+
16
+ Not the product's market, not the team's working language, and **not `metaLocale`**.
17
+ A German medical practice, an Arabic marketplace and a Japanese logistics tool
18
+ all get `getOverview`, `AttentionStripe`, `case`, `event`.
19
+
20
+ This is not linguistic preference, it is mechanics. Identifiers are the surface
21
+ every other tool binds to: the generated `#pikku/*` clients, `pikku info` and
22
+ `pikku meta`, the RPC map a scenario's `actor.invoke` is typed over, the
23
+ generated SQL types, every skill and every agent that ever picks the project up.
24
+ A `vorgang` table types as `Vorgang` in Kysely and reads as noise to everyone who
25
+ did not name it, and unlike a string it cannot be translated later — renaming an
26
+ identifier is a migration, not an edit.
27
+
28
+ ## Meta follows `metaLocale`, and that is what the field is for
29
+
30
+ ```json
31
+ { "metaLocale": "de" }
32
+ ```
33
+
34
+ Meta is the one part of a project the **Pikku Console** renders back to a human.
35
+ A team reviewing their own functions, features and scenario reports in the
36
+ Console is reading meta and nothing else, so a team whose working language is
37
+ German should be able to read their Console in German. That is the entire reason
38
+ the field exists.
39
+
40
+ Read it before you author meta, and write descriptions, titles and templates in
41
+ it. Absent, it is `en`. It is a BCP-47 tag (`en`, `de`, `pt-BR` — a hyphen, not
42
+ an underscore), and the CLI rejects anything else by name.
43
+
44
+ `metaLocale` is **not** licence to rename anything. `metaLocale: "de"` buys a German
45
+ `description: 'Zeigt die Arbeitsliste'` on a function still called
46
+ `getWorklist`.
47
+
48
+ ## Product UI language lives in the catalogue, and only there
49
+
50
+ What the app says to its users is a translation concern, not a code concern. It
51
+ belongs in `messages/<locale>.json`; `pikku-i18n` owns the details. The one rule
52
+ worth repeating here: **`baseLocale` in `project.inlang/settings.json` stays
53
+ `en`.** It names the message _source_ — the catalogue every other language is
54
+ cloned from and translated against — so a project that sets it to anything else
55
+ has no English catalogue to translate from and can never gain a second language
56
+ without re-authoring every key.
57
+
58
+ ## The failure this comes from
59
+
60
+ An agent was asked to build a doctor's portal for a German practice. The brief
61
+ said "the entire UI is German, no English strings visible anywhere". The agent
62
+ read one sentence about the product's users as an instruction about the
63
+ codebase, and produced:
64
+
65
+ - `project.inlang/settings.json` with `baseLocale: "de"` and `locales: ["de"]`,
66
+ no `en.json` at all — which silently broke `--add-locale` forever
67
+ - RPC functions `getUebersicht` and `getPatientendetail`
68
+ - React components `Zeitstrahl` and `AufmerksamkeitStreifen`
69
+ - database tables `vorgang` and `ereignis`, with German columns
70
+
71
+ Every one of those is wrong, and the brief was satisfied by none of them: a
72
+ German UI needs German _messages_. What that project actually wanted was three
73
+ settings, each on its own axis:
74
+
75
+ ```jsonc
76
+ // project.inlang/settings.json — the message source stays English
77
+ { "baseLocale": "en", "locales": ["en", "de"] }
78
+
79
+ // apps/app/src/i18n/active.json — what a first-time visitor opens in
80
+ { "defaultLocale": "de" }
81
+
82
+ // pikku.config.json — the language the team reads their Console in
83
+ { "metaLocale": "de" }
84
+ ```
85
+
86
+ Identifiers stay English throughout. When a brief tells you the product speaks a
87
+ language, it is telling you about axis three and nothing else.
@@ -21,7 +21,7 @@ Use this skill as an execution checklist, not reference material.
21
21
  1. Discover before editing. Run the relevant `pikku meta ... --json` command and inspect only the focused output you need.
22
22
  2. Identify the source files that own the behavior. Do not start by reading generated output, `.pikku`, `node_modules`, vendored packages, or broad build artifacts.
23
23
  3. Make the smallest source change that satisfies the task. Keep generated files generated, and avoid hand-editing SDKs, schema output, or typegen.
24
- 4. Validate with the narrowest relevant command first, then run `pikku-verify` or `pikku all` when functions, wirings, schemas, or generated clients may have changed.
24
+ 4. Validate with the narrowest relevant command first, then run `pikku all` when functions, wirings, schemas, or generated clients may have changed.
25
25
  5. If validation fails, fix the source cause and rerun validation. Do not paper over generated errors by editing generated files.
26
26
 
27
27
  Signatures and option keys come from `pikku doc` — run `pikku doc --ai` for the
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: pikku-fabric
3
- description: 'Build, convert and debug apps on the Pikku Fabric platform. Covers SQLite/libSQL database setup with Kysely, fabric project layout, deploy provider config, `fabric.config.json`, the pikku-verify workflow, and reading logs, traces and metrics from a deployed stage. TRIGGER when: user is working on a Fabric-hosted Pikku project, converting an app to Fabric format, asking about Fabric deployment, database or project conventions, asking about a `pikku fabric validate` finding including app-missing-actor-quick-login, or a deployed stage is erroring, timing out or behaving differently than local ("why is prod failing", "check the logs"). DO NOT TRIGGER when: user is working on a generic (non-Fabric) Pikku deployment — use pikku-deploy instead — or the failure reproduces locally, which is where to debug it.'
3
+ description: 'Build, convert and debug apps on the Pikku Fabric platform. Covers SQLite/libSQL database setup with Kysely, fabric project layout, deploy provider config, `pikkufabric.config.json`, the `pikku all` + `tsc` verification loop, and reading logs, traces and metrics from a deployed stage. TRIGGER when: user is working on a Fabric-hosted Pikku project, converting an app to Fabric format, asking about Fabric deployment, database or project conventions, asking about a `pikku fabric validate` finding including app-missing-actor-quick-login, or a deployed stage is erroring, timing out or behaving differently than local ("why is prod failing", "check the logs"). DO NOT TRIGGER when: user is working on a generic (non-Fabric) Pikku deployment — use pikku-deploy instead — or the failure reproduces locally, which is where to debug it.'
4
4
  installGroups: [fabric]
5
5
  ---
6
6
 
@@ -18,7 +18,7 @@ Use this skill as an execution checklist, not reference material.
18
18
  2. Discover before editing. Run the relevant `pikku meta ... --json` command and inspect only the focused output you need.
19
19
  3. Identify the source files that own the behavior. Do not start by reading generated output, `.pikku`, `node_modules`, vendored packages, or broad build artifacts.
20
20
  4. Make the smallest source change that satisfies the task. Keep generated files generated, and avoid hand-editing SDKs, schema output, or typegen.
21
- 5. Validate with the narrowest relevant command first, then run `pikku-verify` or `pikku all` when functions, wirings, schemas, or generated clients may have changed.
21
+ 5. Validate with the narrowest relevant command first, then run `pikku all` when functions, wirings, schemas, or generated clients may have changed.
22
22
  6. If validation fails, fix the source cause and rerun validation. Do not paper over generated errors by editing generated files.
23
23
 
24
24
  Fabric is a serverless deployment platform for Pikku apps. Every Fabric app runs on Cloudflare Workers with a SQLite database (via libSQL/Turso). This skill covers what's unique to Fabric. For general Pikku concepts, function authoring, HTTP wiring, and more, see `pikku-concepts`, `pikku-wiring`, `pikku-services`, etc.
@@ -31,20 +31,20 @@ Always run project discovery first:
31
31
  yarn pikku meta context --json
32
32
  ```
33
33
 
34
- Call the `pikku-meta` tool before grepping or editing a Fabric app.
34
+ Run `pikku meta` before grepping or editing a Fabric app.
35
35
 
36
- - Use `section: "context"` for the project map: functions, wires, workflows, capabilities, and source files.
37
- - Use `section: "clients"` before frontend/RPC work.
38
- - Use `section: "functions"` to list function ids, then `section: "function", id: "<functionId>"` for one function.
39
- - Use `section: "schemas"` to list schema names. Only request full JSON Schema bodies with `schemas: ["SchemaName"]` for the specific schemas needed.
36
+ - `pikku meta context --json` for the project map: functions, wires, workflows, capabilities, and source files.
37
+ - `pikku meta clients --json` before frontend/RPC work.
38
+ - `pikku meta functions --json` to list function ids, then `pikku meta functions get <id> --json` for one function.
39
+ - `pikku meta schemas --json` to list schema names. Only request a full schema body with `pikku meta schemas get <name> --json` when you need it.
40
40
 
41
41
  Do not load every schema body by default; that wastes context and usually makes the model worse.
42
42
 
43
43
  For database work:
44
44
 
45
- - Use `pikku-db` for the actual attached Fabric database state: tables, columns, foreign keys, and applied migrations.
46
- - Use `pikku-meta` `section: "schemas"` for code-level JSON Schema contracts, not database introspection.
47
- - Do not inspect database credentials or connect to the database directly; Fabric Control already exposes the safe introspection surface.
45
+ - Use `pikku fabric db schema [--branch <branch>]` for the actual attached Fabric database state: tables and columns.
46
+ - Use `pikku meta schemas` for code-level JSON Schema contracts, not database introspection.
47
+ - Do not inspect database credentials or connect to the database directly; Fabric already exposes the safe introspection surface.
48
48
 
49
49
  ## Database: SQLite via libSQL
50
50
 
@@ -327,6 +327,19 @@ pikku fabric validate # must pass clean
327
327
  pikku fabric deploy apply --production -y
328
328
  ```
329
329
 
330
+ `init` and `link` import into whichever organization your session is in. When
331
+ you belong to several — a personal one and a company one, say — name the target
332
+ with `--organization`, taking a slug, a display name or an id:
333
+
334
+ ```bash
335
+ pikku fabric link --organization vlandor
336
+ ```
337
+
338
+ You have to be a member of the organization you name, and its GitHub account
339
+ has to be connected already: importing a `github.com/<owner>/<repo>` repo needs
340
+ the Fabric GitHub App installed on `<owner>` _and_ linked to that organization,
341
+ or the import refuses by name.
342
+
330
343
  The branch is positional and defaults to the checked-out one, and `-y` is the
331
344
  short form of `--auto-approve`, so a one-shot deploy is:
332
345
 
@@ -424,16 +437,16 @@ the deploy with "local HEAD … ≠ remote …" even though your code is pushed.
424
437
 
425
438
  Functions with `expose: true` are versioned via `versions.pikku.json`. When you change a function's input or output schema, you must bump its version number — otherwise `pikku all` will report a breaking change and callers' generated clients become stale.
426
439
 
427
- The `pikku-verify` tool catches this automatically.
440
+ `pikku all` catches this automatically.
428
441
 
429
442
  ## After every code change
430
443
 
431
- Always call the `pikku-verify` tool after modifying functions, wirings, or schemas. It runs:
444
+ Run `pikku all` after modifying functions, wirings, or schemas, then `tsc --noEmit`:
432
445
 
433
446
  1. `pikku all` — regenerates all codegen, checks version compliance
434
447
  2. `tsc --noEmit` — validates TypeScript types
435
448
 
436
- The output card shows whether any breaking changes were detected.
449
+ Breaking changes are reported by the version check in step 1.
437
450
 
438
451
  ### `app-missing-actor-quick-login-<app>`
439
452
 
@@ -0,0 +1,264 @@
1
+ ---
2
+ name: pikku-guide
3
+ description: >-
4
+ Use when writing or regenerating a Pikku project's user guide — the end-user documentation
5
+ built from the scenario suite with `pikku scenario guide`. Pages are hand-written markdown that
6
+ cite features (pikkuFeature); the recordings and screenshots a passing `--screenshots` run
7
+ filed for each cited feature are merged in as figures. Covers writing the prose, the
8
+ `<!-- pikku:guide feature=… -->` markers, the capture step, `.guide.lock` staleness,
9
+ `document: false`, `--allow-undocumented`, `--artifact-base`, and the traps that make a guide
10
+ come out empty or refused. TRIGGER when: user asks for a user guide, help pages, docs with
11
+ screenshots, or "document the app". DO NOT TRIGGER when: user asks about API reference docs,
12
+ README files, or writing the scenarios themselves (use pikku-scenario).
13
+ installGroups: [core]
14
+ ---
15
+
16
+ # Pikku Guide
17
+
18
+ A guide is the app explained to the people who use it, with the scenario suite
19
+ as its evidence. The suite proves what the product does and photographs it
20
+ doing it; the pages say what that means for the reader and how to do it.
21
+
22
+ Three inputs, one output:
23
+
24
+ | Input | Comes from | Owned by |
25
+ |---|---|---|
26
+ | Structure | `pikkuFeature` meta — which features exist, and that every one is cited | the suite |
27
+ | Evidence | the latest passing run under `.pikku/scenario-runs/` — recordings and screenshots | the run |
28
+ | Prose | markdown pages under `docs/` (or `--docs <dir>`) — **every word the reader reads** | a human, or you |
29
+
30
+ **The run contributes pictures, never words.** Step sentences, scenario
31
+ descriptions and feature names are written to prove a test, and none of them
32
+ reaches the page. A page that is a two-line intro and a marker publishes as a
33
+ wall of videos with no instructions — the most common way a guide comes out
34
+ bad. The writing in §2 is the job; the rest of this skill is plumbing.
35
+
36
+ `pikku scenario guide` writes one markdown file per source page into
37
+ `.pikku/guide/` (or `--output`). It renders no HTML, resolves no asset URLs and
38
+ calls no model. Images are ordinary relative `![caption](path)` references into
39
+ the run directory; whoever hosts the markdown rewrites them, or you pass
40
+ `--artifact-base /docs/_media/` for a host that serves them at a fixed address.
41
+
42
+ Read **pikku-scenario** first if the project has no features or browser steps
43
+ yet — the guide cannot be better than the suite under it.
44
+
45
+ ## 1. A page cites a feature
46
+
47
+ A page is a markdown file with frontmatter and a marker pair where the block
48
+ belongs:
49
+
50
+ ```markdown
51
+ ---
52
+ title: Booking a course
53
+ description: Finding a course, taking a place, and what happens after.
54
+ ---
55
+
56
+ Courses run one evening a week for eight weeks. Book when you know which
57
+ evening suits you; Intro to Improv is the place to start if you have never
58
+ done improv before.
59
+
60
+ 1. Open **Courses**. Each course shows its evening and how many places are left.
61
+ 2. Choose a course, then **Book a place**.
62
+ 3. Confirm your details and choose **Book**.
63
+
64
+ Your place appears under **My bookings**, and a confirmation email follows.
65
+
66
+ <!-- pikku:guide feature=bookingsFeature -->
67
+ <!-- /pikku:guide -->
68
+
69
+ ## The course says it is full
70
+
71
+ A full course keeps a waiting list. Choose **Join the waiting list** and we
72
+ email you the moment a place frees up — you are not charged until then.
73
+
74
+ ## Can I switch to another evening?
75
+
76
+ Email us before the second week and we will move you if there is a place.
77
+ ```
78
+
79
+ - `feature=` is the **exported identifier id** of the `pikkuFeature`, not its
80
+ display name.
81
+ - A rebuild rewrites only the region between the markers. Everything around
82
+ them is yours and survives every run.
83
+ - One page may cite several features; one feature may be cited by several
84
+ pages. The mapping is the union of every marker in the tree.
85
+ - Frontmatter the compiler does not own (`slug`, `sidebar_position`, `draft`)
86
+ passes through untouched.
87
+
88
+ The generated block is the feature's **figures and nothing else**: for each
89
+ scenario, its recordings first (one per actor), then its screenshots, deduped
90
+ across data-driven rows. Two strings from the suite do reach the page, as
91
+ captions:
92
+
93
+ | Figure | Caption |
94
+ |---|---|
95
+ | Recording | the scenario's `title`, then ` — ` and the actor's name |
96
+ | Screenshot | the `name` it was taken under |
97
+
98
+ So those two are user-facing copy: "Take a place on a course", "the course list,
99
+ with places left on each ticket" — not "mira books c-intro-1024" or
100
+ "courses /app/courses at 1440px". The scenario `description` and the steps are
101
+ never rendered.
102
+
103
+ A block is indivisible: all of a feature's figures land together, where the
104
+ marker sits. Place the marker after the steps it illustrates, not before them.
105
+ If one page needs figures beside two separate steps, those steps are two
106
+ features.
107
+
108
+ Organise pages by who reads them, not by feature: `docs/using/`,
109
+ `docs/teaching/`, `docs/organising/`. Use the project's own vocabulary, the one
110
+ on its screens — not internal table names.
111
+
112
+ ## 2. Writing the page
113
+
114
+ Write it so a reader can do the task **with every figure removed**. The figures
115
+ confirm; they do not instruct. A reader skims for the step they are stuck on,
116
+ and cannot search a video.
117
+
118
+ Before writing, read the screen's component and its copy, then the feature's
119
+ scenarios. The screen is what renders; the scenario is what is proven, and its
120
+ steps are the user's journey already in order. Write only what you have seen
121
+ in one of them — a fluent page describing a flow that does not exist is worse
122
+ than no page.
123
+
124
+ Each task page carries, in the reader's language (the app's, not English by
125
+ default):
126
+
127
+ - **Why and when** — one short paragraph: what this is for, and when the reader
128
+ would reach for it. Never "This page documents…".
129
+ - **The steps** — a numbered list, each one an action in the words on the
130
+ screen: "Open **Patienten** and choose **Patient anlegen**." Name buttons and
131
+ fields exactly as they read.
132
+ - **What you see afterwards** — the state that means it worked.
133
+ - **What goes wrong** — the refusal, the empty state, the thing that looks
134
+ broken but is not. Every empty state a scenario lands in and every
135
+ `expectError` in the feature is a candidate; this is usually the paragraph
136
+ readers came for.
137
+ - **Where next** — links to the pages a reader goes to from here.
138
+
139
+ Then the marker, after the steps it shows.
140
+
141
+ A page that exists only to cite a feature — "every page loads", "acceptance",
142
+ a smoke suite — is not a page. Cite that feature from the page whose screens it
143
+ covers, or mark it `document: false`.
144
+
145
+ Reassurance is content: "Codes held in reserve cost nothing until a patient
146
+ uses one" is what stops a reader hesitating over the button. Explain the
147
+ confusing thing, not the impressive one.
148
+
149
+ ## 3. Every feature is accounted for
150
+
151
+ Every registered feature must be cited by some page. An uncited feature fails
152
+ the command by name:
153
+
154
+ ```
155
+ Feature 'barFeature' is cited by no page. Place `<!-- pikku:guide feature=barFeature -->` …
156
+ ```
157
+
158
+ Two ways out, both deliberate:
159
+
160
+ - Write the page. This is the normal answer.
161
+ - The feature is plumbing nobody reads about (a session-health check, an
162
+ internal sync): `pikkuFeature({ …, document: false })`. Citing a
163
+ `document: false` feature is itself an error.
164
+
165
+ `--allow-undocumented` downgrades the uncited-feature error to a warning, for a
166
+ guide that is mid-way through being written. Do not hand one over with it on.
167
+
168
+ A page citing an id that is not a registered feature is always an error — it
169
+ describes something that no longer exists.
170
+
171
+ ## 4. Screenshots come from a capture step
172
+
173
+ The run only files screenshots a step asks for. Add one browser step that opens
174
+ a page and takes a shot, and call it from a scenario each feature owns:
175
+
176
+ ```ts snippet:guideCaptureStep
177
+ ```
178
+
179
+ - The screenshot `name` is the figure caption. Write it as a caption: "the
180
+ course list, with places left on each ticket".
181
+ - `{ showcase: true }` marks a shot fit to publish outside the run. `{ fullPage:
182
+ true }` photographs the whole scrollable page.
183
+ - Contexts open at a pinned 1440×900 viewport with animations off, so two runs
184
+ photograph the same thing. Override with `E2E_VIEWPORT_WIDTH` /
185
+ `E2E_VIEWPORT_HEIGHT` or the playwright config, or call
186
+ `page.setViewportSize` inside the step for a phone-width shot.
187
+ - Prefer a shot at the end of a real flow step (after the booking succeeds) over
188
+ a standalone "open and photograph" scenario — the figure then shows the state
189
+ the section describes.
190
+
191
+ ### Traps that make a block come out empty
192
+
193
+ - **A feature whose scenarios are RPC-only files no screenshot.** The command
194
+ warns `whose run filed no screenshot — the block renders empty`. Fold that
195
+ scenario into a feature that has browser coverage, or add a browser capture
196
+ to it; do not invent a page just to photograph.
197
+ - **A capture loop must iterate a named const.** `for (const s of [ … ])` with
198
+ an inline array literal cannot be extracted (PKU679) and the scenario becomes
199
+ silently empty. `const screens = [ … ] as const` then `for (const s of screens)`.
200
+ - **A closing assertion runs wherever the last capture left the browser.**
201
+ Captures appended to the end of a scenario move the page out from under a
202
+ `then` that follows them. Capture after the last assertion, or re-navigate.
203
+ - **Locale.** Playwright reports `navigator.language` as `en-US`. An app that
204
+ picks its locale from the browser renders English, and every copy assertion
205
+ in another language fails. Set the app's own stored locale with
206
+ `page.addInitScript` in **every** step that navigates, not only the first.
207
+
208
+ ## 5. Build it
209
+
210
+ ```sh
211
+ # 1. a full, passing browser run that writes the shots to disk
212
+ bunx --bun pikku scenario run local --run browser --screenshots
213
+
214
+ # 2. merge prose + evidence → .pikku/guide/
215
+ bunx --bun pikku scenario guide --docs docs
216
+ ```
217
+
218
+ - `--screenshots` is what writes files. Without it `browser.screenshot()`
219
+ still returns bytes and nothing lands on disk, so every block is empty.
220
+ - The run must have **passed**. A failed or killed run is refused — a page is a
221
+ claim that the product does what it says.
222
+ - The run must be **the whole suite**. A run narrowed with `--flows`,
223
+ `--features`, `--tags` or `--exclude-tags` is refused, because pages built
224
+ from it would describe missing flows as though they did not exist. An
225
+ exclusion that matches nothing still counts — keep every narrowing flag out
226
+ of a CI invocation whose run feeds the guide. `--run-id <id>` picks an older
227
+ full run.
228
+
229
+ ## 6. `.guide.lock` — keeping prose honest
230
+
231
+ `docs/.guide.lock` records, per feature, a hash of its scenarios' **step
232
+ sentences and artifact ids** — deliberately not the image bytes. Restyling the
233
+ UI changes every screenshot and no sentence, so it does not stale a page.
234
+ Inserting, renaming or reordering a step changes what the prose around the
235
+ block was describing, and the page is reported:
236
+
237
+ ```
238
+ docs/using/booking.md was written against 'bookingsFeature' at 3f1a…, which is now 9c0e… — the flow moved, so re-read the prose around that block.
239
+ ```
240
+
241
+ Re-read that page's hand-written text, fix what the flow change made untrue,
242
+ and rebuild. The lock is rewritten on every successful build.
243
+
244
+ - **Commit the lock.** It is generated, never hand-edited, never hand-merged. A
245
+ tree whose lock is untracked reports every page as current forever.
246
+ - The first build after adding pages prints stale warnings for every feature
247
+ (there was no lock); they clear on the second build.
248
+
249
+ ## 7. Hand-over checklist
250
+
251
+ - [ ] Every feature is cited, or declares `document: false` with a reason.
252
+ - [ ] No `--allow-undocumented` in the command you report as done.
253
+ - [ ] Built from a full, passing `--run browser --screenshots` run.
254
+ - [ ] No "block renders empty" warnings.
255
+ - [ ] No stale warnings left after the second build.
256
+ - [ ] `docs/` pages and `docs/.guide.lock` committed; `.pikku/guide/` is output.
257
+ - [ ] Every page reads as instructions with its figures removed: why and when,
258
+ numbered steps naming the controls as they read on screen, what goes
259
+ wrong.
260
+ - [ ] No page exists only to cite a smoke or acceptance feature.
261
+ - [ ] Scenario titles and screenshot names read as captions — no routes,
262
+ viewport sizes or test ids.
263
+ - [ ] Open two generated pages and look at them: the figures are the screens the
264
+ text describes, in the app's language.
@@ -14,6 +14,16 @@ description: >-
14
14
  functions, routes, tables or permissions exist (that is `pikku meta` / `pikku info`, never a
15
15
  note), or to write a scenario test (use pikku-scenario).
16
16
  installGroups: [core]
17
+ agent:
18
+ tools: read, write, edit, bash, grep
19
+ timeoutMs: 1800000
20
+ acceptance:
21
+ level: verified
22
+ evidence: [changed-files, validation-output]
23
+ verify:
24
+ - id: knowledge-consistent
25
+ command: pikku knowledge validate
26
+
17
27
  ---
18
28
 
19
29
  # Pikku Knowledge
@@ -24,7 +24,7 @@ Use this skill as an execution checklist, not reference material.
24
24
  1. Discover before editing. Run the relevant `pikku meta ... --json` command and inspect only the focused output you need.
25
25
  2. Identify the source files that own the behavior. Do not start by reading generated output, `.pikku`, `node_modules`, vendored packages, or broad build artifacts.
26
26
  3. Make the smallest source change that satisfies the task. Keep generated files generated, and avoid hand-editing SDKs, schema output, or typegen.
27
- 4. Validate with the narrowest relevant command first, then run `pikku-verify` or `pikku all` when functions, wirings, schemas, or generated clients may have changed.
27
+ 4. Validate with the narrowest relevant command first, then run `pikku all` when functions, wirings, schemas, or generated clients may have changed.
28
28
  5. If validation fails, fix the source cause and rerun validation. Do not paper over generated errors by editing generated files.
29
29
 
30
30
  ## Writing Queries — the Kysely query builder
@@ -0,0 +1,80 @@
1
+ ---
2
+ name: pikku-mantine
3
+ description: >-
4
+ Use when building a Mantine UI on top of a Pikku backend — rendering dates that came back from a
5
+ generated client, keeping layout flow-relative so the app survives an RTL locale, and branching on
6
+ colour scheme without hardcoding a shade. TRIGGER when: putting a value from usePikkuQuery or an
7
+ RPC response on screen, writing margins/padding/alignment in Mantine props or CSS, choosing a date
8
+ input, or handling light/dark. DO NOT TRIGGER when: the data does not come from a Pikku client
9
+ (this is only about what the generated clients hand you), or for user-facing copy (use pikku-i18n).
10
+ installGroups: [client]
11
+ ---
12
+
13
+ # Mantine on a Pikku client
14
+
15
+ ## Dates — always format before rendering
16
+
17
+ The generated clients run `transformDates`, which revives **fully-zoned ISO-8601 instants** —
18
+ `2026-03-14T08:12:00Z`, `2026-03-14T08:12:00.000+01:00` — into `Date` objects and touches nothing
19
+ else. A bare `2026-03-14`, a zoneless `2026-03-14T08:12:00` and an impossible `2026-02-31T00:00:00Z`
20
+ all stay the strings the server sent. So a field's runtime type follows the VALUE, not the schema:
21
+ one column can arrive as a `Date` from one row and a string from the next.
22
+
23
+ Two consequences, and both compile:
24
+
25
+ - **A string method on one white-screens the page.** `row.createdAt.split('T')[0]` type-checks
26
+ against nothing useful and blows up at runtime. There is no string to slice.
27
+ - **A raw `Date` dropped into JSX crashes the route.** `<Text>{row.createdAt}</Text>`, a table cell,
28
+ a `<Badge>` — React throws `Objects are not valid as a React child (found: [object Date])` and the
29
+ page falls into its error boundary. Nothing catches it before the screen is white, which makes it
30
+ the most common broken page in a build.
31
+
32
+ **Format with dayjs.** It is Mantine's own date library, already shipped alongside `@mantine/dates`,
33
+ and it takes either a `Date` or a string. Never `toLocaleDateString`, `date-fns` or `luxon`.
34
+
35
+ ```tsx
36
+ import dayjs from 'dayjs'
37
+
38
+ <Text>{dayjs(row.dueOn).format('D MMM YYYY')}</Text> // 15 Jun 2026
39
+ <Text>{dayjs(row.createdAt).format('D MMM YYYY, HH:mm')}</Text>
40
+ ```
41
+
42
+ A relative "2 days ago" via dayjs `relativeTime` is fine. Coercing instead of formatting
43
+ (`` `${d}` ``, `String(d)`, `d + ''`) does not crash but prints
44
+ `Mon Jun 15 2026 02:00:00 GMT+0200` — a different bug, equally wrong.
45
+
46
+ Date **inputs** are `@mantine/dates` — `DatePickerInput`, `DatePicker`, `Calendar` — never a raw
47
+ `<TextInput type="date">`. Those are pickers and they are not a schedule: Mantine ships no
48
+ week/time-grid component, so a diary, rota, timetable or booking week is a grid you build, not a
49
+ `Calendar` with the time-of-day left out. `Calendar` with `renderDay` IS right for "a few things on
50
+ each day of a month", like a content calendar or a holiday planner.
51
+
52
+ All of this applies to stub and fixture dates exactly as it does to real data.
53
+
54
+ ## RTL-safe styles
55
+
56
+ Write layout styles flow-relative so the UI works in both LTR and RTL languages. Pikku's i18n ships
57
+ Arabic, Hebrew, Farsi and Urdu support, and a physical margin is what breaks under it.
58
+
59
+ | Avoid | Use instead |
60
+ | ----------------------------- | ------------------------------------------ |
61
+ | `ml`, `mr`, `pl`, `pr` | `ms`, `me`, `ps`, `pe` |
62
+ | `text-align: left/right` | `text-align: start/end` |
63
+ | `margin-left`, `margin-right` | `margin-inline-start`, `margin-inline-end` |
64
+ | `flex-direction: row-reverse` | `dir` attribute or logical properties |
65
+
66
+ Mantine shorthand: `ms` = margin-inline-start, `me` = margin-inline-end, `ps` = padding-inline-start,
67
+ `pe` = padding-inline-end.
68
+
69
+ ## Dark mode
70
+
71
+ Use Mantine's `light-dark()` utility or `useMantineColorScheme`, and only with colours that already
72
+ come from the theme — never introduce a literal colour or a shade string for one scheme.
73
+
74
+ ```tsx
75
+ // Correct
76
+ <Box bg="var(--mantine-color-body)">
77
+
78
+ // Wrong — scheme branch with hardcoded Mantine shades
79
+ <Box bg={theme.colorScheme === 'dark' ? 'dark.6' : 'gray.0'}>
80
+ ```