@pikku/skills 0.12.21 → 0.12.25
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 +125 -0
- package/dist/skills.gen.js +1 -1
- package/package.json +1 -1
- package/skills/pikku-addon/SKILL.md +10 -9
- package/skills/pikku-agent/SKILL.md +67 -316
- package/skills/pikku-agent/references/agents.md +299 -0
- package/skills/{pikku-ai-vercel/SKILL.md → pikku-agent/references/runner-vercel.md} +2 -24
- package/skills/{pikku-ai-voice/SKILL.md → pikku-agent/references/voice.md} +1 -22
- package/skills/pikku-architect/SKILL.md +264 -0
- package/skills/pikku-auth/SKILL.md +89 -0
- package/skills/{pikku-better-auth/SKILL.md → pikku-auth/references/better-auth.md} +42 -47
- package/skills/{pikku-jose/SKILL.md → pikku-auth/references/jose.md} +1 -23
- package/skills/{pikku-machine-auth/SKILL.md → pikku-auth/references/machine-auth.md} +0 -23
- package/skills/{pikku-permissions/SKILL.md → pikku-auth/references/permissions.md} +5 -24
- package/skills/{pikku-security/SKILL.md → pikku-auth/references/sessions.md} +3 -23
- package/skills/pikku-build/SKILL.md +87 -0
- package/skills/{pikku-build-app/SKILL.md → pikku-build/references/app.md} +77 -25
- package/skills/{pikku-feature/SKILL.md → pikku-build/references/feature.md} +1 -8
- package/skills/{pikku-build-app → pikku-build}/references/multi-app.md +1 -1
- package/skills/{pikku-build-platform/SKILL.md → pikku-build/references/platform.md} +22 -37
- package/skills/{pikku-template-clone/SKILL.md → pikku-build/references/post-clone.md} +0 -6
- package/skills/{pikku-build-quick/SKILL.md → pikku-build/references/quick.md} +6 -22
- package/skills/{pikku-build-app → pikku-build}/references/ship.md +7 -1
- package/skills/pikku-concepts/SKILL.md +75 -8
- package/skills/pikku-concepts/references/concept-mapping.md +8 -8
- package/skills/pikku-deploy/SKILL.md +158 -0
- package/skills/{pikku-deploy-azure/SKILL.md → pikku-deploy/references/azure.md} +18 -50
- package/skills/pikku-deploy/references/cloudflare.md +104 -0
- package/skills/pikku-deploy/references/express.md +92 -0
- package/skills/{pikku-deploy-fastify/SKILL.md → pikku-deploy/references/fastify.md} +8 -32
- package/skills/{pikku-deploy-lambda/SKILL.md → pikku-deploy/references/lambda.md} +6 -27
- package/skills/{pikku-deploy-nextjs/SKILL.md → pikku-deploy/references/nextjs.md} +9 -33
- package/skills/pikku-deploy/references/uws.md +72 -0
- package/skills/pikku-deploy/references/ws.md +75 -0
- package/skills/pikku-emails/SKILL.md +3 -2
- package/skills/pikku-fabric/SKILL.md +20 -10
- package/skills/{pikku-fabric-debug/SKILL.md → pikku-fabric/references/debugging.md} +0 -6
- package/skills/pikku-i18n/SKILL.md +60 -207
- package/skills/{pikku-paraglide/SKILL.md → pikku-i18n/references/enum-labels.md} +0 -6
- package/skills/pikku-i18n/references/messages.md +218 -0
- package/skills/{pikku-rtl/SKILL.md → pikku-i18n/references/rtl.md} +3 -9
- package/skills/pikku-knowledge/SKILL.md +14 -0
- package/skills/pikku-kysely/SKILL.md +13 -13
- package/skills/pikku-meta/SKILL.md +58 -130
- package/skills/{pikku-deps/SKILL.md → pikku-meta/references/audit.md} +1 -17
- package/skills/pikku-meta/references/meta.md +114 -0
- package/skills/{pikku-versioning/SKILL.md → pikku-meta/references/versioning.md} +0 -26
- package/skills/pikku-middleware/SKILL.md +8 -8
- package/skills/pikku-n8n-import/SKILL.md +0 -1
- package/skills/pikku-react/SKILL.md +50 -298
- package/skills/pikku-react/references/client.md +293 -0
- package/skills/{pikku-react-query/SKILL.md → pikku-react/references/react-query.md} +2 -22
- package/skills/{pikku-workflows-client/SKILL.md → pikku-react/references/workflows.md} +1 -22
- package/skills/pikku-scenario/SKILL.md +64 -49
- package/skills/pikku-scenario/references/persona-run.md +148 -0
- package/skills/pikku-service-backends/SKILL.md +154 -0
- package/skills/pikku-service-backends/references/aws.md +106 -0
- package/skills/pikku-service-backends/references/backblaze.md +57 -0
- package/skills/pikku-service-backends/references/mongodb.md +90 -0
- package/skills/pikku-service-backends/references/redis.md +75 -0
- package/skills/pikku-service-backends/references/schema.md +63 -0
- package/skills/pikku-services/SKILL.md +68 -291
- package/skills/{pikku-audit/SKILL.md → pikku-services/references/audit.md} +0 -22
- package/skills/{pikku-config/SKILL.md → pikku-services/references/config.md} +1 -25
- package/skills/{pikku-pino/SKILL.md → pikku-services/references/pino.md} +0 -20
- package/skills/pikku-services/references/services.md +272 -0
- package/skills/pikku-software-archaeology/README.md +5 -1
- package/skills/pikku-software-archaeology/SKILL.md +15 -2
- package/skills/{pikku-product-second-opinion/example/sample-report.md → pikku-software-archaeology/example/second-opinion-sample-report.md} +1 -1
- package/skills/pikku-software-archaeology/references/blueprint.schema.json +1 -1
- package/skills/pikku-software-archaeology/references/pikku-mapping.md +3 -3
- package/skills/{pikku-product-second-opinion/SKILL.md → pikku-software-archaeology/references/second-opinion.md} +4 -9
- package/skills/pikku-webhook/SKILL.md +199 -0
- package/skills/pikku-wiring/SKILL.md +180 -0
- package/skills/{pikku-websocket/SKILL.md → pikku-wiring/references/channel.md} +2 -35
- package/skills/{pikku-cli/SKILL.md → pikku-wiring/references/cli.md} +1 -33
- package/skills/{pikku-gateway-slack/SKILL.md → pikku-wiring/references/gateway-slack.md} +0 -23
- package/skills/{pikku-http/SKILL.md → pikku-wiring/references/http.md} +4 -40
- package/skills/{pikku-mcp/SKILL.md → pikku-wiring/references/mcp.md} +0 -33
- package/skills/{pikku-queue/SKILL.md → pikku-wiring/references/queue.md} +1 -33
- package/skills/{pikku-realtime/SKILL.md → pikku-wiring/references/realtime.md} +2 -25
- package/skills/{pikku-rpc/SKILL.md → pikku-wiring/references/rpc.md} +0 -32
- package/skills/{pikku-schedule/SKILL.md → pikku-wiring/references/scheduler.md} +1 -35
- package/skills/{pikku-trigger/SKILL.md → pikku-wiring/references/trigger.md} +0 -43
- package/skills/pikku-workflow/SKILL.md +3 -3
- package/skills/pikku-aws/SKILL.md +0 -161
- package/skills/pikku-backblaze/SKILL.md +0 -104
- package/skills/pikku-deploy-cloudflare/SKILL.md +0 -123
- package/skills/pikku-deploy-express/SKILL.md +0 -122
- package/skills/pikku-deploy-uws/SKILL.md +0 -144
- package/skills/pikku-mongodb/SKILL.md +0 -113
- package/skills/pikku-product-second-opinion/README.md +0 -43
- package/skills/pikku-redis/SKILL.md +0 -99
- package/skills/pikku-schema-ajv/SKILL.md +0 -83
- package/skills/pikku-schema-cfworker/SKILL.md +0 -82
- package/skills/pikku-ws/SKILL.md +0 -87
- /package/skills/{pikku-build-app → pikku-build}/references/theming.md +0 -0
- /package/skills/{pikku-product-second-opinion/references/report-template.md → pikku-software-archaeology/references/second-opinion-report-template.md} +0 -0
- /package/skills/{pikku-cli/references/complete-example.md → pikku-wiring/references/cli-complete-example.md} +0 -0
- /package/skills/{pikku-http → pikku-wiring}/references/http-options.md +0 -0
- /package/skills/{pikku-realtime/references/other-routes.md → pikku-wiring/references/realtime-other-routes.md} +0 -0
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
# uWebSockets.js
|
|
2
|
+
|
|
3
|
+
Highest-throughput option among Pikku's runtimes. Handles both HTTP and
|
|
4
|
+
WebSocket automatically.
|
|
5
|
+
|
|
6
|
+
```bash
|
|
7
|
+
yarn add @pikku/uws
|
|
8
|
+
```
|
|
9
|
+
|
|
10
|
+
```typescript
|
|
11
|
+
import { PikkuUWSServer } from '@pikku/uws'
|
|
12
|
+
import './.pikku/pikku-bootstrap.gen.js'
|
|
13
|
+
import { createConfig, createSingletonServices } from './services.js'
|
|
14
|
+
|
|
15
|
+
const config = await createConfig()
|
|
16
|
+
const singletonServices = await createSingletonServices(config)
|
|
17
|
+
|
|
18
|
+
const appServer = new PikkuUWSServer(
|
|
19
|
+
{ ...config, hostname: 'localhost', port: 4002 },
|
|
20
|
+
singletonServices.logger
|
|
21
|
+
)
|
|
22
|
+
appServer.enableExitOnSigInt()
|
|
23
|
+
await appServer.init()
|
|
24
|
+
await appServer.start()
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
The config extends `CoreConfig` with `port`, `hostname` and an optional
|
|
28
|
+
`healthCheckPath`. `app: uWS.App` is exposed for direct access.
|
|
29
|
+
|
|
30
|
+
## What the server does and does not give you
|
|
31
|
+
|
|
32
|
+
`init()` registers three things in order: the health check (`healthCheckPath`,
|
|
33
|
+
default `/health-check`), a catch-all `app.any('/*')` HTTP handler, and a
|
|
34
|
+
catch-all `app.ws('/*')` websocket handler. Nothing is registered by the
|
|
35
|
+
constructor, so nothing answers before `init` runs.
|
|
36
|
+
|
|
37
|
+
**There is no `enableCors`, no static assets and no `content` support** — unlike
|
|
38
|
+
the Express server. The class is explicitly a prototyping convenience; for
|
|
39
|
+
anything that needs extra handlers, use `@pikku/uws-handler` directly and treat
|
|
40
|
+
`pikku-uws-server.ts` as the template (that is what its own JSDoc says).
|
|
41
|
+
|
|
42
|
+
`httpOptions` reaches the HTTP handler only. The websocket handler is
|
|
43
|
+
constructed with a fixed `{ logger, logRoutes: true }`, so per-request options
|
|
44
|
+
do not apply to the upgrade path. `loadSchemas` is also never passed by the
|
|
45
|
+
server, so schemas compile lazily on first use rather than at startup — pass
|
|
46
|
+
`loadSchemas: true` in `httpOptions` if you want the startup cost paid up front.
|
|
47
|
+
|
|
48
|
+
`stop()` closes the listen socket and then waits a fixed 2 seconds for
|
|
49
|
+
connections to drain. Called before `start()`, it throws a bare **string**, not
|
|
50
|
+
an `Error`, so `catch (e) { e.message }` reads `undefined`.
|
|
51
|
+
|
|
52
|
+
## Body limits
|
|
53
|
+
|
|
54
|
+
uWS hands over raw chunks with no limit of its own, so the handler counts the
|
|
55
|
+
bytes itself. A request over `maxBodySize` (default `DEFAULT_MAX_BODY_SIZE`) is
|
|
56
|
+
answered `413` with a `PayloadTooLargeError` body, and the chunks are dropped
|
|
57
|
+
rather than concatenated — an oversized request never accumulates in memory. A
|
|
58
|
+
`content-length` header that already exceeds the limit short-circuits before any
|
|
59
|
+
data arrives.
|
|
60
|
+
|
|
61
|
+
## Handlers directly (own uWS app)
|
|
62
|
+
|
|
63
|
+
```typescript
|
|
64
|
+
import { pikkuHTTPHandler, pikkuWebsocketHandler } from '@pikku/uws-handler'
|
|
65
|
+
|
|
66
|
+
app.any('/*', pikkuHTTPHandler({ logger, logRoutes: true, loadSchemas: true }))
|
|
67
|
+
app.ws('/*', pikkuWebsocketHandler({ logger, logRoutes: true }))
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
Both take `{ logger, logRoutes?, loadSchemas? } & RunHTTPWiringOptions`.
|
|
71
|
+
|
|
72
|
+
For a WebSocket-only server on the `ws` library instead, see `ws.md`.
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
# ws (WebSocket only)
|
|
2
|
+
|
|
3
|
+
`@pikku/ws` connects Pikku's channel system to a Node.js WebSocket server built
|
|
4
|
+
on the [ws](https://github.com/websockets/ws) library. Use it for a
|
|
5
|
+
WebSocket-only server; for HTTP and WebSocket on one port see `uws.md`, and when
|
|
6
|
+
the WebSocket server shares a port with an existing HTTP app see `express.md` or
|
|
7
|
+
`fastify.md`.
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
yarn add @pikku/ws ws
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
The package exports one function, `pikkuWebsocketHandler` — there is no server
|
|
14
|
+
class. You own the `http.Server` and the `WebSocketServer`; the handler attaches
|
|
15
|
+
the upgrade and message plumbing to them.
|
|
16
|
+
|
|
17
|
+
```typescript
|
|
18
|
+
import { DEFAULT_WS_MAX_PAYLOAD, pikkuWebsocketHandler } from '@pikku/ws'
|
|
19
|
+
import { stopSingletonServices } from '@pikku/core'
|
|
20
|
+
import { Server } from 'http'
|
|
21
|
+
import { WebSocketServer } from 'ws'
|
|
22
|
+
|
|
23
|
+
import './.pikku/pikku-bootstrap.gen.js'
|
|
24
|
+
import { createConfig, createSingletonServices } from './services.js'
|
|
25
|
+
|
|
26
|
+
const config = await createConfig()
|
|
27
|
+
const singletonServices = await createSingletonServices(config)
|
|
28
|
+
|
|
29
|
+
const server = new Server()
|
|
30
|
+
const wss = new WebSocketServer({
|
|
31
|
+
noServer: true,
|
|
32
|
+
maxPayload: DEFAULT_WS_MAX_PAYLOAD,
|
|
33
|
+
})
|
|
34
|
+
|
|
35
|
+
pikkuWebsocketHandler({
|
|
36
|
+
server,
|
|
37
|
+
wss,
|
|
38
|
+
logger: singletonServices.logger,
|
|
39
|
+
logRoutes: true, // print the wired channels at startup
|
|
40
|
+
loadSchemas: true, // compile input schemas up front
|
|
41
|
+
})
|
|
42
|
+
|
|
43
|
+
server.listen(4002, 'localhost')
|
|
44
|
+
|
|
45
|
+
process.on('SIGINT', async () => {
|
|
46
|
+
await stopSingletonServices()
|
|
47
|
+
wss.close()
|
|
48
|
+
server.close()
|
|
49
|
+
process.exit(0)
|
|
50
|
+
})
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
## `noServer: true` is required, not stylistic
|
|
54
|
+
|
|
55
|
+
The handler listens for the HTTP server's own `upgrade` event, opens the channel
|
|
56
|
+
— running Pikku's middleware chain, auth and CORS against the upgrade request
|
|
57
|
+
first — and only then calls `wss.handleUpgrade`. A `WebSocketServer` bound to the
|
|
58
|
+
server directly would take the socket before any of that ran.
|
|
59
|
+
|
|
60
|
+
An upgrade the channel rejects gets the socket destroyed, and an auth failure is
|
|
61
|
+
written as a real HTTP response on the raw socket rather than a silent drop.
|
|
62
|
+
|
|
63
|
+
## Services and the event hub
|
|
64
|
+
|
|
65
|
+
Services come from the bootstrap import and the global singleton registry, which
|
|
66
|
+
is why nothing is passed in. The event hub is taken from
|
|
67
|
+
`singletonServices.eventHub` when it is a `LocalEventHubService`, and a local one
|
|
68
|
+
is created otherwise — so a single-process app gets pub/sub for free, while a
|
|
69
|
+
multi-instance deployment must register a distributed hub.
|
|
70
|
+
|
|
71
|
+
The options type also extends `RunHTTPWiringOptions`, so per-request settings
|
|
72
|
+
such as `respondWith404`, `coerceDataFromSchema` and `bubbleErrors` are accepted
|
|
73
|
+
here too.
|
|
74
|
+
|
|
75
|
+
On shutdown, call `stopSingletonServices()` then close `wss` and `server`.
|
|
@@ -9,8 +9,8 @@ description: >-
|
|
|
9
9
|
or emailTemplatesDir in pikku.config.json. TRIGGER when: user asks to add/edit a transactional
|
|
10
10
|
email (verification, password reset, invitation, receipt), wire email sending, or translate an
|
|
11
11
|
email. DO NOT TRIGGER when: user asks about i18n for the app UI (use pikku-i18n) or auth flows
|
|
12
|
-
in general (use pikku-
|
|
13
|
-
installGroups: [
|
|
12
|
+
in general (use pikku-auth).
|
|
13
|
+
installGroups: [core]
|
|
14
14
|
---
|
|
15
15
|
|
|
16
16
|
# Pikku Emails
|
|
@@ -187,6 +187,7 @@ async send(input: SendEmailInput) {
|
|
|
187
187
|
const r = renderEmailTemplate(input.template as RenderEmailInput<EmailTemplateName>)
|
|
188
188
|
return this.delegate.send({
|
|
189
189
|
to: input.to, from: input.from, subject: r.subject, html: r.html,
|
|
190
|
+
attachments: input.attachments,
|
|
190
191
|
...(r.text ? { text: r.text } : {}),
|
|
191
192
|
})
|
|
192
193
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: pikku-fabric
|
|
3
|
-
description: 'Build and
|
|
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.'
|
|
4
4
|
installGroups: [fabric]
|
|
5
5
|
---
|
|
6
6
|
|
|
@@ -21,7 +21,7 @@ Use this skill as an execution checklist, not reference material.
|
|
|
21
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.
|
|
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
|
-
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-
|
|
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.
|
|
25
25
|
|
|
26
26
|
## Before you start
|
|
27
27
|
|
|
@@ -119,7 +119,7 @@ without it in production?"** If yes, it is configuration and belongs in a
|
|
|
119
119
|
migration, however much it looks like sample data. A venue and its rooms, a
|
|
120
120
|
product catalogue, a tenant, a country list, the organization the whole
|
|
121
121
|
deployment hangs off — all configuration. Accounts and role grants are
|
|
122
|
-
provisioning:
|
|
122
|
+
provisioning: the fabric plugin's `personas`, or a migration. What is left
|
|
123
123
|
over is the seed's job — the bookings, orders and messages a demo needs and a real
|
|
124
124
|
environment starts without.
|
|
125
125
|
|
|
@@ -306,13 +306,13 @@ tells you nothing about whether it worked. `--sync` waits for a terminal state
|
|
|
306
306
|
and exits non-zero unless the deployment went live, which is the only form worth
|
|
307
307
|
running in CI:
|
|
308
308
|
|
|
309
|
-
| exit | meaning
|
|
310
|
-
| ---- |
|
|
311
|
-
| 0
|
|
312
|
-
| 1
|
|
313
|
-
| 2
|
|
314
|
-
| 3
|
|
315
|
-
| 4
|
|
309
|
+
| exit | meaning |
|
|
310
|
+
| ---- | ----------------------------------------------------------------------- |
|
|
311
|
+
| 0 | live (or queued, without `--sync`) |
|
|
312
|
+
| 1 | the command could not run — not logged in, unsafe git state, bad flags |
|
|
313
|
+
| 2 | the deployment failed, errored, timed out server-side, or was cancelled |
|
|
314
|
+
| 3 | the deployment is blocked and nothing the CLI can do will unblock it |
|
|
315
|
+
| 4 | the wait hit `--timeout` with the deployment still in flight |
|
|
316
316
|
|
|
317
317
|
Fabric parks every deploy at a gate after the plan phase (`status: suspended`).
|
|
318
318
|
Why it parked is the whole story, and it is `statusReason`, not `status`:
|
|
@@ -440,3 +440,13 @@ Fix every `error` and `warn` in the output before continuing. Then:
|
|
|
440
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`).
|
|
441
441
|
7. **Run `pikku all`** — verify codegen succeeds and there are no type errors.
|
|
442
442
|
8. **Run `pikku fabric validate`** once more to confirm no structural issues remain.
|
|
443
|
+
|
|
444
|
+
## A deployed stage misbehaving
|
|
445
|
+
|
|
446
|
+
Reproduce locally first — a deployed stage adds cost and latency to every
|
|
447
|
+
iteration, and a failure that reproduces locally is a local debugging problem.
|
|
448
|
+
When it only happens deployed, read `references/debugging.md`: start from
|
|
449
|
+
`errors` rather than `logs` (they are already filtered and carry the traceId),
|
|
450
|
+
follow one trace end to end before forming a theory, and confirm the fix against
|
|
451
|
+
the same stage. A deploy that *failed* is a build or config problem and belongs
|
|
452
|
+
above, not there.
|
|
@@ -1,9 +1,3 @@
|
|
|
1
|
-
---
|
|
2
|
-
name: pikku-fabric-debug
|
|
3
|
-
description: 'Debug a deployed Fabric stage from the CLI — read logs, find recent errors, follow a single request end-to-end by traceId, and check request/error/latency metrics. TRIGGER when: a deployed Fabric app is erroring, timing out, or behaving differently than local; the user asks "why is prod failing", "check the logs", "what happened to this request"; or a deploy succeeded but the app misbehaves. DO NOT TRIGGER when: the failure reproduces locally (debug it locally), the deploy itself failed (use pikku-fabric — that is a build/config problem, not a runtime one), or the project is not deployed to Fabric.'
|
|
4
|
-
installGroups: [fabric]
|
|
5
|
-
---
|
|
6
|
-
|
|
7
1
|
# Debugging a deployed Fabric stage
|
|
8
2
|
|
|
9
3
|
## Agent Operating Procedure
|
|
@@ -1,224 +1,77 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: pikku-i18n
|
|
3
|
-
description:
|
|
4
|
-
|
|
3
|
+
description: >-
|
|
4
|
+
Use when writing user-facing text in a Pikku frontend, or making one speak another language.
|
|
5
|
+
Covers Paraglide JS message functions compiled from messages/<locale>.json, adding a second
|
|
6
|
+
language, generated enum-label maps with @pikku/paraglide, and right-to-left support for Arabic,
|
|
7
|
+
Hebrew, Farsi and Urdu. TRIGGER when: scaffolding or editing a frontend and writing display
|
|
8
|
+
text, asked to make copy translatable, adding a language, labelling an enum/status/role value,
|
|
9
|
+
or asked to support RTL / mirror the layout. DO NOT TRIGGER for backend functions, error
|
|
10
|
+
messages thrown from functions, or log output — none of those are display strings.
|
|
11
|
+
installGroups: [client]
|
|
5
12
|
---
|
|
6
13
|
|
|
7
|
-
# Pikku i18n
|
|
14
|
+
# Pikku i18n
|
|
8
15
|
|
|
9
|
-
##
|
|
16
|
+
## Every visible string is a message
|
|
10
17
|
|
|
11
|
-
|
|
18
|
+
Never hardcode display text: add a key to `messages/en.json` and render
|
|
19
|
+
`m.the__key()`. This holds even in an app that will only ever ship English —
|
|
20
|
+
the messages are the seam a second language slots into, and the deploy pipeline
|
|
21
|
+
type-checks them, so an i18n mistake blocks the build rather than the release.
|
|
12
22
|
|
|
13
|
-
|
|
14
|
-
2. One `messages/<locale>.json` per language at the app root (NOT under `src/`), declared in `project.inlang/settings.json`. English (`en`) is `baseLocale` and the only locale until someone adds another. **`baseLocale` stays `en` whatever language the product speaks** — see [The product's language is not the code's language](#the-products-language-is-not-the-codes-language), which is the first thing to read if the brief says the app is not in English.
|
|
15
|
-
3. Messages compile to typed ESM functions in `src/paraglide/` (generated, self-gitignored — never edit or commit it). The Vite plugin compiles during `dev`/`build` with HMR on message edits; run the CLI compile only when you need `tsc` before Vite has ever run.
|
|
16
|
-
4. Validate with the app's own `tsc` then its `build`. The deploy pipeline compiles Paraglide and runs each frontend's `tsc` before building it — an i18n mistake blocks the deploy.
|
|
23
|
+
## Pick the reference
|
|
17
24
|
|
|
18
|
-
|
|
25
|
+
| You are… | Read |
|
|
26
|
+
| --- | --- |
|
|
27
|
+
| Writing copy, wiring Paraglide, or adding a language | `references/messages.md` |
|
|
28
|
+
| Labelling an enum, status, kind or role value | `references/enum-labels.md` |
|
|
29
|
+
| Adding Arabic (or Hebrew, Farsi, Urdu), or writing layout styles | `references/rtl.md` |
|
|
19
30
|
|
|
20
|
-
|
|
21
|
-
is a statement about **one** of three separate things, and reading it as a
|
|
22
|
-
statement about the codebase is the single most expensive mistake available in
|
|
23
|
-
this skill. Three axes:
|
|
31
|
+
## Three axes, and a brief usually means only one
|
|
24
32
|
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
| **Meta** | `description` / `name` / `title` / `template` authored inside the code, which the Pikku Console renders | `metaLocale` in `pikku.config.json`, default `en` |
|
|
29
|
-
| **Product UI** | Every string the app shows a user | `messages/<locale>.json` + `defaultLocale` — **this axis only** |
|
|
33
|
+
"The entire UI is German" is a statement about the product, not about the
|
|
34
|
+
codebase. Reading it as one about the codebase is the most expensive mistake
|
|
35
|
+
available here.
|
|
30
36
|
|
|
31
|
-
|
|
37
|
+
| Axis | What it covers | What sets it |
|
|
38
|
+
| --- | --- | --- |
|
|
39
|
+
| **Identifiers** | Function, component, type and file names; tables and columns | Nothing — always English |
|
|
40
|
+
| **Meta** | `description` / `name` / `title` authored in code, rendered by the console | `metaLocale` in `pikku.config.json` |
|
|
41
|
+
| **Product UI** | Every string the app shows a user | `messages/<locale>.json` + `defaultLocale` |
|
|
32
42
|
|
|
33
|
-
|
|
43
|
+
`baseLocale` stays `en` whatever language the product speaks — it names the
|
|
44
|
+
message *source* catalogue every other locale is derived from, not the language
|
|
45
|
+
the app is in. Set `defaultLocale` instead.
|
|
34
46
|
|
|
35
|
-
|
|
36
|
-
app is in". It names the message **source** — the catalogue every other locale is
|
|
37
|
-
cloned from and translated against. Setting it to the product's language looks
|
|
38
|
-
like it works, because the app does come up in that language, and then:
|
|
47
|
+
## Direction is one setting, not per-component work
|
|
39
48
|
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
template and read by `src/i18n/config.ts`. It is deliberately a separate file
|
|
47
|
-
from `settings.json` for exactly this reason — the source language and the
|
|
48
|
-
served language are different questions.
|
|
49
|
-
|
|
50
|
-
So a German medical portal is **three** settings, not one:
|
|
51
|
-
|
|
52
|
-
```jsonc
|
|
53
|
-
// project.inlang/settings.json — the source catalogue is English
|
|
54
|
-
{ "baseLocale": "en", "locales": ["en", "de"] }
|
|
55
|
-
|
|
56
|
-
// apps/app/src/i18n/active.json — what a visitor opens in
|
|
57
|
-
{ "defaultLocale": "de" }
|
|
58
|
-
|
|
59
|
-
// pikku.config.json — the language the team reads their Console in
|
|
60
|
-
{ "metaLocale": "de" }
|
|
61
|
-
```
|
|
62
|
-
|
|
63
|
-
In the Fabric template both of the first two have a command, so you rarely edit
|
|
64
|
-
them by hand:
|
|
65
|
-
|
|
66
|
-
```sh
|
|
67
|
-
fabric i18n --add-locale de # adds "de" to locales, seeds messages/de.json from en.json
|
|
68
|
-
fabric i18n --default-locale de # writes active.json — the app now OPENS in German
|
|
69
|
-
```
|
|
70
|
-
|
|
71
|
-
### The failure this is written from
|
|
72
|
-
|
|
73
|
-
A real build, from this template. The brief said the UI was German; the agent
|
|
74
|
-
set `baseLocale: "de"` with `locales: ["de"]` and no `en.json`, then carried the
|
|
75
|
-
same reading into the code — RPC functions `getUebersicht` and
|
|
76
|
-
`getPatientendetail`, components `Zeitstrahl` and `AufmerksamkeitStreifen`,
|
|
77
|
-
helpers `datumDeutsch` and `voraussichtlichFertig`, database tables `vorgang`
|
|
78
|
-
and `ereignis` with German columns.
|
|
79
|
-
|
|
80
|
-
The German UI it was asked for needed none of that. It needed German **values**
|
|
81
|
-
in a catalogue whose keys and source stayed English. What it got instead was a
|
|
82
|
-
project that cannot add a second language and cannot be picked up by anyone who
|
|
83
|
-
does not read German.
|
|
84
|
-
|
|
85
|
-
If you find a project in this state, say so plainly rather than working around
|
|
86
|
-
it: `baseLocale` cannot be repointed without re-keying every message, so it is a
|
|
87
|
-
migration someone has to agree to, not a fix to slip in.
|
|
88
|
-
|
|
89
|
-
## The moving parts (starter-template layout)
|
|
90
|
-
|
|
91
|
-
- `messages/en.json` — flat keys, `{param}` interpolation, inlang message-format:
|
|
92
|
-
```json
|
|
93
|
-
{
|
|
94
|
-
"$schema": "https://inlang.com/schema/inlang-message-format",
|
|
95
|
-
"auth__login__title": "Sign in",
|
|
96
|
-
"auth__login__description": "Welcome back to {name}."
|
|
97
|
-
}
|
|
98
|
-
```
|
|
99
|
-
Key convention: lower snake_case, `__` (double underscore) between namespace segments, `_` within a segment — `auth__login__title`, `common__email_placeholder`.
|
|
100
|
-
- `project.inlang/settings.json` — `baseLocale`, `locales`, the `@inlang/plugin-message-format` module, `pathPattern: "./messages/{locale}.json"`.
|
|
101
|
-
- `vite.config.ts` — `paraglideVitePlugin({ project: './project.inlang', outdir: './src/paraglide' })` from `@inlang/paraglide-js` (devDependency), FIRST in the plugins array.
|
|
102
|
-
- `src/paraglide/` — compiled output (`messages.js`, `runtime.js`, per-locale `messages/*.js`). Generated; it writes its own `.gitignore`.
|
|
103
|
-
- `src/i18n/config.ts` — locale plumbing, and the ONLY hand-written i18n module: `supportedLocales`/`defaultLocale` (re-exported from `../paraglide/runtime.js`), `detectLocale`, `localeDir` (RTL for ar/he/fa/ur), a reactive locale store (`overwriteGetLocale` bridged to `useSyncExternalStore`), `setActiveLocale`, `useLocale()`. This is not a wrapper over messages — Paraglide's `getLocale()` is a module global with no React reactivity, and this bridges it. Wire `overwriteGetLocale` or `m.*()` will resolve a different locale than the app thinks is active.
|
|
104
|
-
- `tsconfig.json` — `"allowJs": true, "checkJs": false` so `tsc` can consume Paraglide's JSDoc-typed JS output.
|
|
105
|
-
|
|
106
|
-
## Using messages in components
|
|
107
|
-
|
|
108
|
-
```tsx
|
|
109
|
-
import { m } from '../paraglide/messages.js'
|
|
110
|
-
import { useLocale } from '@/i18n/config'
|
|
111
|
-
|
|
112
|
-
function LoginPage() {
|
|
113
|
-
useLocale() // subscribe: re-render m.*() when the locale switches
|
|
114
|
-
return (
|
|
115
|
-
<>
|
|
116
|
-
<Title>{m.auth__login__title()}</Title>
|
|
117
|
-
<Text>{m.auth__login__description({ name: m.app__name() })}</Text>
|
|
118
|
-
</>
|
|
119
|
-
)
|
|
120
|
-
}
|
|
121
|
-
```
|
|
122
|
-
|
|
123
|
-
- Params: `{name}` in the JSON → `m.auth__login__description({ name })`. Params are typed per message.
|
|
124
|
-
- Any component that renders `m.*()` calls `useLocale()` (bare call is enough); it also returns `{ locale, dir, setLocale }` for switchers.
|
|
125
|
-
- Non-component helpers (formatters, status maps) call `m.some__key()` directly — the functions are plain ESM, no hook needed; the render-time subscription lives in the component that displays the result.
|
|
126
|
-
- Locale switching: the root route persists to localStorage, sets `<html lang dir>` (`localeDir`), and calls `setActiveLocale` — in-SPA re-render, no page reload. Mirror `routes/__root.tsx` in the starter template.
|
|
127
|
-
|
|
128
|
-
## Keys only known at runtime (enum labels, status maps)
|
|
129
|
-
|
|
130
|
-
A DB value picking a label is the one case a generated message can't express.
|
|
131
|
-
Paraglide's README (§ "What about dynamic or CMS-driven keys?") is explicit: use
|
|
132
|
-
an **explicit mapping from value to message function**. Key it on the enum type,
|
|
133
|
-
never `string`:
|
|
134
|
-
|
|
135
|
-
```ts
|
|
136
|
-
import { m } from '../paraglide/messages.js'
|
|
137
|
-
|
|
138
|
-
const DOCUMENT_STATUS_LABEL: Record<DocumentStatus, () => string> = {
|
|
139
|
-
completed: m.enum__document_status__completed,
|
|
140
|
-
in_progress: m.enum__document_status__in_progress,
|
|
141
|
-
required: m.enum__document_status__required,
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
// call site — no fallback, because there is no missing case
|
|
145
|
-
DOCUMENT_STATUS_LABEL[status]()
|
|
146
|
-
```
|
|
147
|
-
|
|
148
|
-
`Record<DocumentStatus, …>` is exhaustive: add a value to the enum without a
|
|
149
|
-
label and the build fails. That is the entire point.
|
|
150
|
-
|
|
151
|
-
**Don't write these maps by hand.** `@pikku/paraglide` generates them from the
|
|
152
|
-
`enum__<group>__<member>` keys in the catalog and types each one against the DB
|
|
153
|
-
enum it mirrors, so a migration adding a status is a compile error rather than a
|
|
154
|
-
map someone forgot. Use the namespace above (singular `enum`, `__` between
|
|
155
|
-
segments) so the generator picks the group up, and read `pikku-paraglide` before
|
|
156
|
-
adding one.
|
|
157
|
-
|
|
158
|
-
Do NOT write `Record<string, () => string>` with a `?? status` fallback, and do
|
|
159
|
-
NOT index the namespace with a computed key (`m[\`enums__${name}__${value}\`]`).
|
|
160
|
-
Both compile, both render the raw identifier to users when a label is missing,
|
|
161
|
-
and both reintroduce exactly the silent-fallback failure Paraglide exists to
|
|
162
|
-
eliminate. If you find yourself writing a `resolveDynamicKey(key: string)`
|
|
163
|
-
helper, stop — that helper IS the bug.
|
|
164
|
-
|
|
165
|
-
## Type safety — and why deploys block on i18n
|
|
166
|
-
|
|
167
|
-
A message IS a function: a typo'd or deleted key (`m.auth__login__titel()`) is a missing export — a **TypeScript error**, not a silent runtime fallback string. Params are typed too. The deploy pipeline compiles Paraglide then runs each frontend's `tsc` (`"tsc": "tsc --noEmit"` script — keep it in every frontend's `package.json`) **before** building; a type error aborts the deploy. `vite build` does not type-check on its own, so this gate is the only thing standing between a broken message and production.
|
|
168
|
-
|
|
169
|
-
The gate catches _invalid_ messages but not _inlined_ strings. The `@pikku/mantine` `I18nNode` prop typing catches those: a raw string literal fails to compile on a gated prop, because `I18nString` is a branded type a bare `string` can't satisfy. Between the two, `tsc` is the whole safety net — there is no runtime fallback to inspect, by design.
|
|
170
|
-
|
|
171
|
-
## Compile step
|
|
172
|
-
|
|
173
|
-
- **Dev/build:** the Vite plugin compiles automatically; editing `messages/*.json` under a running dev server recompiles + HMRs.
|
|
174
|
-
- **Standalone `tsc` before Vite has run** (fresh clone, CI):
|
|
175
|
-
```sh
|
|
176
|
-
npx @inlang/paraglide-js compile --project ./project.inlang --outdir ./src/paraglide
|
|
177
|
-
```
|
|
178
|
-
This is exactly what the deploy CI does before the per-app `tsc`.
|
|
179
|
-
|
|
180
|
-
## Adding a second language
|
|
181
|
-
|
|
182
|
-
1. `messages/fr.json` mirroring `en.json`'s keys (translate the values, keep `{param}` names identical).
|
|
183
|
-
2. Add `"fr"` to `locales` in `project.inlang/settings.json`. **Leave `baseLocale` at `en`** — step 1 only works because there is an English catalogue to mirror.
|
|
184
|
-
3. Recompile (restart/`vite dev` or the CLI compile). A locale file missing keys falls back to the base locale per message.
|
|
185
|
-
4. Content is reachable via the `/<lang>` URL prefix (`detectLocale` already resolves it); the base locale needs no prefix. Expose the switcher via `useLocale().setLocale`.
|
|
186
|
-
5. Only if the app should **open** in the new language rather than merely offer it: set `defaultLocale` (`active.json` / `fabric i18n --default-locale fr`). Adding a locale and changing the default are different asks — do the second only when asked.
|
|
187
|
-
|
|
188
|
-
## i18n debug mode (find inlined strings)
|
|
189
|
-
|
|
190
|
-
`tsc` catches invalid messages, and the `@pikku/mantine` gate catches raw strings on gated props — but neither sees a hardcoded string in plain JSX, an `aria-label`, `alt`, `document.title`, or anything passed to a non-Mantine component. Debug mode covers that gap: render every message as block glyphs (`█`), and whatever is still readable never went through a message.
|
|
191
|
-
|
|
192
|
-
**Build it as a generated locale, never as a runtime wrapper.** Masked text is text, and rendering different text per locale is what Paraglide already does:
|
|
193
|
-
|
|
194
|
-
1. A script generates `messages/zz.json` from `en.json`, replacing `\S` with `█` while leaving `{placeholders}` intact (they are message inputs — mangling them changes the compiled signature). Run it before `paraglide-js compile`; gitignore the output.
|
|
195
|
-
2. Add `"zz"` to `locales` in `project.inlang/settings.json`.
|
|
196
|
-
3. Switch to it in the locale bridge:
|
|
197
|
-
```ts
|
|
198
|
-
overwriteGetLocale(() => (isI18nDebug() ? 'zz' : activeLocale))
|
|
199
|
-
```
|
|
200
|
-
|
|
201
|
-
Keep `zz` out of the app's own `supportedLocales` — that drives URL prefixes, hreflang and any backend `locale` param, none of which should see it.
|
|
202
|
-
|
|
203
|
-
Generate the catalogue in dev only. With `messages/zz.json` absent, Paraglide compiles `zz` to an alias of the base locale (`const zz_x = en_x` — one line per message, no duplicated strings), so a production bundle carries the locale at effectively zero cost.
|
|
204
|
-
|
|
205
|
-
Both the generator and the store bridge are being upstreamed (pikkujs/pikku#1036, #1035).
|
|
206
|
-
|
|
207
|
-
The wrapper alternative — a module that walks the namespace and pipes each message through a `mask()` — is what this replaces. It defeats tree-shaking (touching every export), adds a check on every call, and forces every component to import `m` from the wrapper instead of Paraglide.
|
|
49
|
+
Set `dir` once at the document root from the active locale and the browser (and
|
|
50
|
+
Mantine) mirror everything — provided every custom style is flow-relative
|
|
51
|
+
(`margin-inline-start`, `text-align: start`, Mantine `ms`/`me`) rather than
|
|
52
|
+
physical (`margin-left`, `text-align: left`, `ml`/`me`'s physical twins). Write
|
|
53
|
+
logical properties from the start even in an English-only app; that discipline
|
|
54
|
+
is what makes an RTL language just another locale file.
|
|
208
55
|
|
|
209
56
|
## What NOT to do
|
|
210
57
|
|
|
211
|
-
-
|
|
212
|
-
|
|
213
|
-
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
- **
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
-
|
|
223
|
-
|
|
224
|
-
-
|
|
58
|
+
- **Do not resolve a message key at runtime.** No `mKey('status.' + value)`, no
|
|
59
|
+
`m['enum__' + x]()`, no key-string resolver. A computed key cannot be
|
|
60
|
+
type-checked or tree-shaken, so a renamed message degrades to silent runtime
|
|
61
|
+
text. Where the key is genuinely dynamic, map the discriminant to a message
|
|
62
|
+
*function* — the map is checked, a string is not.
|
|
63
|
+
- **Do not `asI18n()` a hardcoded English string.** `asI18n` exists to pass
|
|
64
|
+
opaque server data (a name, a slug, an id) through the i18n gate. An enum value
|
|
65
|
+
goes through its generated label map.
|
|
66
|
+
- **Do not wrap `m`.** No re-export module, no branding layer. `m.some__key()`
|
|
67
|
+
already satisfies the `I18nNode` gate; a wrapper adds nothing and costs
|
|
68
|
+
per-message tree-shaking.
|
|
69
|
+
- **Do not translate message keys.** `auth__login__title` stays English in
|
|
70
|
+
`de.json`; only the value changes.
|
|
71
|
+
- **Do not edit or commit `src/paraglide/`, `i18n-enum.gen.ts` or `enums.gen.ts`.**
|
|
72
|
+
Change the catalogue or the migration and regenerate.
|
|
73
|
+
- **Do not fake RTL** with `flex-direction: row-reverse`, reversed DOM order, or
|
|
74
|
+
per-locale layout branches. They double-flip the moment direction changes. DOM
|
|
75
|
+
order is logical order; let `dir` decide the visual one.
|
|
76
|
+
- **Do not reach for i18next or a runtime translation loader.** Paraglide's
|
|
77
|
+
compiled functions are the whole delivery mechanism.
|
|
@@ -1,9 +1,3 @@
|
|
|
1
|
-
---
|
|
2
|
-
name: pikku-paraglide
|
|
3
|
-
description: 'Generate typed, static enum-label maps for a Paraglide i18n frontend with `@pikku/paraglide`, and reconcile them against the database enum columns so a label can never silently drift from a DB value. Enum-valued labels live under a reserved `enum__<group>__<member>` message namespace; the generator emits `i18n-enum.gen.ts` typed `satisfies EnumLabel<DbEnum>`. TRIGGER when: labelling an enum/status/kind/role value in a Paraglide app, replacing a dynamic `mKey(...)`/`m[...]` lookup with a static map, wiring `@pikku/paraglide` into Vite, or reconciling i18n against `CHECK (col IN (...))` / Postgres enum columns. DO NOT TRIGGER for plain free-text UI copy (that is a normal `m.some_key()` message), backend errors, or logs.'
|
|
4
|
-
installGroups: [client]
|
|
5
|
-
---
|
|
6
|
-
|
|
7
1
|
# Pikku Paraglide enum labels
|
|
8
2
|
|
|
9
3
|
## Agent Operating Procedure
|