@pikku/skills 0.12.62 → 0.12.64
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/dist/skills.gen.js +1 -1
- package/package.json +1 -1
- package/skills/pikku-addon/SKILL.md +4 -4
- package/skills/pikku-addon/references/addon-package-manifest.md +57 -42
- package/skills/pikku-build/references/mocks.md +12 -0
- package/skills/pikku-build/references/phase-4-build.md +22 -20
- package/skills/pikku-build/references/ship.md +10 -6
- package/skills/pikku-concepts/references/concept-mapping.md +1 -1
- package/skills/pikku-meta/references/versioning.md +4 -4
- package/skills/pikku-middleware/SKILL.md +2 -2
- package/skills/pikku-react/references/client.md +18 -0
- package/skills/pikku-theme/SKILL.md +24 -2
- package/skills/pikku-theme/references/from-a-reference.md +3 -0
- package/skills/pikku-wiring/references/channel.md +1 -1
- package/skills/pikku-wiring/references/http.md +2 -2
package/package.json
CHANGED
|
@@ -52,7 +52,7 @@ wireAddon({
|
|
|
52
52
|
mcp?: boolean | string[], // true: every function the addon declared mcp: true; a list: the tools this app offers, typed against the addon's function names
|
|
53
53
|
expose?: boolean | string[], // opt-in: what rpc.exposed / POST /rpc may reach. true = every function the addon declared expose: true, a list = those of them named (typed; PKU343 on unknown or undeclared names), unset/false = none
|
|
54
54
|
tags?: string[], // Tags applied to all addon functions
|
|
55
|
-
ui?: boolean, // Mount the screens the addon declares with
|
|
55
|
+
ui?: boolean, // Mount the screens the addon declares with definePlugin
|
|
56
56
|
scopes?: string[], // Required of every function, on top of its own
|
|
57
57
|
secretOverrides?: Record<string, string>, // Remap secret names (and grant them)
|
|
58
58
|
variableOverrides?: Record<string, string>, // Remap variable names
|
|
@@ -377,14 +377,14 @@ At runtime an unmapped alias, or the wired name called directly, throws
|
|
|
377
377
|
|
|
378
378
|
### Studio screens
|
|
379
379
|
|
|
380
|
-
An addon declares the screens it adds to Pikku Studio with `
|
|
380
|
+
An addon declares the screens it adds to Pikku Studio with `definePlugin`, in
|
|
381
381
|
`src/screens.ts` (`pikku new addon-ui <name>` scaffolds one). It replaces
|
|
382
382
|
`defineScreens`, which no longer exists.
|
|
383
383
|
|
|
384
384
|
```typescript
|
|
385
|
-
import {
|
|
385
|
+
import { definePlugin } from '@pikku/core/addon'
|
|
386
386
|
|
|
387
|
-
export default
|
|
387
|
+
export default definePlugin({
|
|
388
388
|
title: 'invoices__title', // message key: the addon's name in Studio
|
|
389
389
|
screens: [
|
|
390
390
|
{
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
- Package Structure
|
|
6
6
|
- pikku.config.json
|
|
7
7
|
- package.json (key fields)
|
|
8
|
-
- Platform service contributors (`
|
|
8
|
+
- Platform service contributors (`defineContributors`)
|
|
9
9
|
- tsconfig.json (key fields)
|
|
10
10
|
|
|
11
11
|
`npx pikku new addon` scaffolds these files. You rarely hand-edit them — consult this when wiring exports or config by hand.
|
|
@@ -105,57 +105,72 @@ live in a file that never imports `#pikku` — see "Functions" in the skill.
|
|
|
105
105
|
writes `@my-org/addon-todos/.pikku/rpc/...`, exactly as it would in an
|
|
106
106
|
application, and the leaf stays the package's own business.
|
|
107
107
|
|
|
108
|
-
## Platform service contributors (`
|
|
108
|
+
## Platform service contributors (`defineContributors`)
|
|
109
109
|
|
|
110
|
-
An addon can ship code that
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
110
|
+
An addon can ship code that builds services for the **host's** target — a log
|
|
111
|
+
source over the host's database, a client built from a host secret. The addon
|
|
112
|
+
registers them in code with `defineContributors`; nothing goes in `package.json`
|
|
113
|
+
and `wireAddon` of the addon itself has no field for it. A host that wants one
|
|
114
|
+
opts in by name with `wireAddon({ contributors: ['log-sources'] })`, and gets
|
|
115
|
+
nothing it did not list.
|
|
114
116
|
|
|
115
|
-
```
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
},
|
|
123
|
-
|
|
124
|
-
"contributors": [
|
|
125
|
-
{ "module": "@my-org/addon-logs/contributors", "export": "logSources" }
|
|
126
|
-
]
|
|
127
|
-
}
|
|
128
|
-
}
|
|
117
|
+
```ts
|
|
118
|
+
// src/contributors.ts
|
|
119
|
+
import { defineContributors } from '@pikku/core/contributors'
|
|
120
|
+
import { logSourcesDev } from './log-sources-dev.js'
|
|
121
|
+
import { logSourcesDeploy } from './log-sources-deploy.js'
|
|
122
|
+
|
|
123
|
+
export const contributors = defineContributors({
|
|
124
|
+
'log-sources': { dev: logSourcesDev, deploy: logSourcesDeploy },
|
|
125
|
+
})
|
|
129
126
|
```
|
|
130
127
|
|
|
131
|
-
`module` resolves from the addon's package directory. Every app that wires the
|
|
132
|
-
addon (not `wireRemoteAddon`) picks the contributors up: `pikku all` emits them
|
|
133
|
-
into `.pikku/pikku-dev-platform-services.gen.ts` for `pikku dev`, and
|
|
134
|
-
`pikku deploy plan` / `apply` hands them to the provider adapter, after the
|
|
135
|
-
app's own `deploy.contributors`.
|
|
136
|
-
|
|
137
|
-
Declare what a contributor reads and sets, rather than relying on list order:
|
|
138
|
-
|
|
139
128
|
```ts
|
|
140
|
-
|
|
141
|
-
|
|
129
|
+
// src/log-sources-deploy.ts — each variant in its OWN module
|
|
130
|
+
import type { Contributor } from '@pikku/core/contributors'
|
|
131
|
+
import { kyselyLogSource } from './log-source.js'
|
|
132
|
+
|
|
133
|
+
export const logSourcesDeploy: Contributor = {
|
|
142
134
|
needs: ['kysely'], // runtime.kysely, or a contributor's services.kysely
|
|
143
135
|
provides: ['logSources', 'logSources.workspace'],
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
],
|
|
136
|
+
run: (services, _env, runtime) => {
|
|
137
|
+
services.logSources = { workspace: kyselyLogSource(runtime.kysely) }
|
|
138
|
+
},
|
|
148
139
|
}
|
|
149
140
|
```
|
|
150
141
|
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
`
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
142
|
+
Rules the inspector and the build enforce:
|
|
143
|
+
|
|
144
|
+
- `defineContributors` takes an object literal keyed by contributor name; each
|
|
145
|
+
`dev` / `deploy` value must be a reference to an `export const`. Anything else
|
|
146
|
+
is PKU360; a name defined twice is PKU361.
|
|
147
|
+
- **Each variant lives in its own module.** The build imports the module that
|
|
148
|
+
exports the selected variant, so a `dev` and a `deploy` variant sharing a file
|
|
149
|
+
both reach every bundle, production included.
|
|
150
|
+
- A contributor with no variant for a target is simply not part of it.
|
|
151
|
+
- Run `pikku all` in the addon: it writes
|
|
152
|
+
`.pikku/contributors/pikku-contributors-meta.gen.json`, which a host reads to
|
|
153
|
+
opt in (an unknown name is PKU363). Ship `.pikku` in `files`.
|
|
154
|
+
- `pikku.contributors` in `package.json` is removed and is now an error
|
|
155
|
+
(PKU364), not silently ignored.
|
|
156
|
+
- Contributors run after every contributor providing one of their `needs`; a
|
|
157
|
+
dotted need (`logSources.stage`) is met by a provider of the key or its
|
|
158
|
+
prefix. `run` reads services the target built before it as `runtime.<name>` —
|
|
159
|
+
`pikku dev` has logger, variables, secrets, schema and (with `db`) kysely; the
|
|
160
|
+
standalone entry has logger, kysely (with a database) and its
|
|
161
|
+
scheduler/queue/workflow/trigger services and event hub.
|
|
162
|
+
- Two contributors providing the same key (after `when` / `requires` filtering)
|
|
163
|
+
is a build error naming both, except that a target's baseline services may be
|
|
164
|
+
replaced. A decorator declares `provides: []`. A contributor providing what it
|
|
165
|
+
needs is a cycle. An unmet `needs` is a build error.
|
|
166
|
+
- `requires: ['cloudflare']` on a contributor a host opted in to, for a target
|
|
167
|
+
that cannot supply it, is a build error; use `when` to say "not here".
|
|
168
|
+
- `onServerStart` / `onServerStop` run only where the server lifecycle runs
|
|
169
|
+
(dev, serve, standalone, generic server entry, Cloudflare container). On a
|
|
170
|
+
Worker `onServerStart` is a build error and `onServerStop` a warning.
|
|
171
|
+
- Fail closed: a contributor whose bindings are missing at startup throws.
|
|
172
|
+
|
|
173
|
+
Full reference: `docs/contributors.md` in the pikku repo.
|
|
159
174
|
|
|
160
175
|
## tsconfig.json (key fields)
|
|
161
176
|
|
|
@@ -10,6 +10,18 @@ Read this when a screen needs data no function returns yet, or returns in a diff
|
|
|
10
10
|
|
|
11
11
|
The app loads its mocks with `registerPikkuMocks()` from the generated `mocks.gen.ts` beside `api.gen.ts`, called when `import.meta.env.DEV || import.meta.env.VITE_MOCK`. It covers the app's `.mocks/` and every wired addon's, so never glob `.mocks` or `node_modules` by hand.
|
|
12
12
|
|
|
13
|
+
## Adding a mock
|
|
14
|
+
|
|
15
|
+
`pikku mocks add <rpc>` does the whole job. It checks the RPC is a real function, writes `.mocks/<rpc with : as .>/<state>.json` and `<state>.meta.json`, keeps exactly one default (the first mock is the default, `--default` moves it) and prints the stub hook to use. The response comes from `--body '<json>'` or `--from <file>`; an error mock and an empty mock of a list have a default body.
|
|
16
|
+
|
|
17
|
+
```
|
|
18
|
+
pikku mocks add classes:month --body '[{"id":"c1","name":"Yoga"}]' --flag classMonth
|
|
19
|
+
pikku mocks add classes:month --state empty
|
|
20
|
+
pikku mocks add classes:month --state error --status 500
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
`--flag <name>` declares the flag in the project's `defineFeatureFlags({ ... })` file when it is not there yet, or prints the entry to add by hand. Nothing is overwritten without `--force`. A body that differs from the function's output is written anyway and reported, because a stub may stand in for data the function does not return yet.
|
|
24
|
+
|
|
13
25
|
## Addon mocks
|
|
14
26
|
|
|
15
27
|
An addon ships default mocks in its own package as `.mocks/<function>/<scenario>.json`, named without a namespace, and lists `.mocks` in `files`. An app that wires the addon as `studio` gets them as `studio:<function>`. The app's `.mocks/studio.<function>/` adds scenarios; one with the same name replaces the addon's, and a `default: true` in the app wins over the addon's default. Put mocks for an addon's functions in the addon, and only the app's own variations in the app.
|
|
@@ -166,29 +166,31 @@ it is worth writing as a browser step on §7's scenario and running
|
|
|
166
166
|
`pikku scenario run local --spawn --run browser`: same clicks, same assertions,
|
|
167
167
|
in the repo, green or red on every future run.
|
|
168
168
|
|
|
169
|
-
**A
|
|
170
|
-
`
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
}
|
|
169
|
+
**A project can take platform services from an addon's contributors.** The addon
|
|
170
|
+
registers them with `defineContributors` (an object keyed by name, each with an
|
|
171
|
+
optional `dev` and `deploy` variant that is a plain `Contributor`), and the
|
|
172
|
+
project opts in by name:
|
|
173
|
+
|
|
174
|
+
```ts
|
|
175
|
+
wireAddon({
|
|
176
|
+
name: 'inbox',
|
|
177
|
+
package: '@my-org/addon-inbox',
|
|
178
|
+
contributors: ['inbox'],
|
|
179
|
+
})
|
|
180
180
|
```
|
|
181
181
|
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
generated `createPlatformServices(env, runtime)` gets the local services as `runtime`;
|
|
187
|
-
an unmet need, a key provided twice or a cycle fails `pikku all`. `pikku all` writes `.pikku/pikku-dev-platform-services.gen.ts`
|
|
188
|
-
from them, and `pikku dev` merges what it returns over the local services before
|
|
182
|
+
Nothing is injected without that list. `pikku all` resolves the `dev` variants
|
|
183
|
+
(dropping what `when` or `requires` rules out, ordering by `needs`/`provides`)
|
|
184
|
+
and writes `.pikku/contributors/pikku-dev-contributors.gen.ts`; `pikku dev` and
|
|
185
|
+
`pikku serve` merge what it builds over the local services before
|
|
189
186
|
`createSingletonServices` runs, so a contributed `emailService` beats
|
|
190
|
-
`LocalEmailService` (and the test-mode spy).
|
|
191
|
-
|
|
187
|
+
`LocalEmailService` (and the test-mode spy). Both skip the file under
|
|
188
|
+
`NODE_ENV=production`. A contributor's `onServerStart` runs before the app's
|
|
189
|
+
`beforeStart`; its `onServerStop` after the app's `afterStop`. An unmet need, a
|
|
190
|
+
key provided twice or a cycle fails `pikku all`, and so does a declared database
|
|
191
|
+
(`createConfig` or `db/<dialect>`) with no `db` in the pikku config. A host that
|
|
192
|
+
injects an addon (CI, Studio) writes a gitignored wiring file containing
|
|
193
|
+
`wireAddon({ name, package, contributors })`. Reference: `docs/contributors.md`.
|
|
192
194
|
|
|
193
195
|
## 6a. Close the changeset against its plan, not against your memory
|
|
194
196
|
|
|
@@ -25,12 +25,16 @@ with Node, or a self-contained executable compiled with `bun build --compile`.
|
|
|
25
25
|
factories the deploy codegen expects, and hand-rolling an `ExportedHandler` is
|
|
26
26
|
how a worker deploy fails at runtime instead of at build.
|
|
27
27
|
|
|
28
|
-
Platform services
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
28
|
+
Platform services an addon builds for the bundle come from contributors the
|
|
29
|
+
project opted in to with `wireAddon({ contributors: [...] })`; the addon
|
|
30
|
+
registers them with `defineContributors`. `plan` and `apply` select the `deploy`
|
|
31
|
+
variant of each, drop what `when` rules out, order them by `needs`/`provides`
|
|
32
|
+
and, on standalone, run them after the database is open, with `runtime.logger`
|
|
33
|
+
and `runtime.kysely` in reach. Standalone, the generic server entry and the
|
|
34
|
+
Cloudflare adapter support contributors; a provider without `contributorSupport`
|
|
35
|
+
fails the build when a project opts in to one with a deploy variant. On a
|
|
36
|
+
Cloudflare Worker a contributor with `onServerStart` is a build error. See
|
|
37
|
+
`docs/contributors.md`.
|
|
34
38
|
|
|
35
39
|
Always run `plan` before `apply`, and read it. It names what will be created,
|
|
36
40
|
updated and deleted — the deletions are the reason to look.
|
|
@@ -197,7 +197,7 @@ function authMiddleware(req, res, next) {
|
|
|
197
197
|
**Pikku:**
|
|
198
198
|
|
|
199
199
|
```typescript
|
|
200
|
-
import { authBearer } from '
|
|
200
|
+
import { authBearer } from '#pikku/middleware'
|
|
201
201
|
|
|
202
202
|
// One line - handles token extraction, JWT verification, session population
|
|
203
203
|
addHTTPMiddleware('*', [authBearer({})])
|
|
@@ -137,11 +137,11 @@ the manifest alone. Fix the contract or bump the version, then run it again.
|
|
|
137
137
|
`check` is a within-repo gate — "you changed a contract without bumping
|
|
138
138
|
`version:`". `release` is a release question — "what does this build owe the
|
|
139
139
|
clients of the last release, and how do we ship it?". The baseline is
|
|
140
|
-
`surface.
|
|
140
|
+
`pikku.surface.json`, the snapshot committed by the previous release.
|
|
141
141
|
|
|
142
142
|
```bash
|
|
143
143
|
npx pikku release init # first run: snapshot + CHANGELOG.md
|
|
144
|
-
npx pikku release diff # vs surface.
|
|
144
|
+
npx pikku release diff # vs pikku.surface.json
|
|
145
145
|
npx pikku release diff --against https://api.acme.com/surface.json # vs any baseline
|
|
146
146
|
npx pikku release diff --fail-on major # PR gate
|
|
147
147
|
npx pikku release snapshot --out surface.json # write a snapshot
|
|
@@ -162,7 +162,7 @@ writes it; it never commits, tags or pushes. Who does that is your call — you
|
|
|
162
162
|
with plain git, or a platform with its own credentials.
|
|
163
163
|
|
|
164
164
|
`prepare` runs on a checkout of `origin/staging` after `pikku all`. It diffs the
|
|
165
|
-
surface against `surface.
|
|
165
|
+
surface against `pikku.surface.json`, reads the commits production does not have
|
|
166
166
|
yet, bumps `package.json`, prepends a `CHANGELOG.md` section and rewrites the
|
|
167
167
|
snapshot. No commits since the last release means nothing to release. It
|
|
168
168
|
refuses when `main` has commits `staging` lacks (merge `main` into `staging`
|
|
@@ -174,7 +174,7 @@ Shipping is then one commit on top of that trunk sha:
|
|
|
174
174
|
|
|
175
175
|
```bash
|
|
176
176
|
npx pikku release prepare
|
|
177
|
-
git add package.json CHANGELOG.md surface.
|
|
177
|
+
git add package.json CHANGELOG.md pikku.surface.json
|
|
178
178
|
git commit -m "release: v0.4.0"
|
|
179
179
|
git tag v0.4.0
|
|
180
180
|
git push --atomic origin HEAD:staging HEAD:main v0.4.0
|
|
@@ -82,7 +82,7 @@ wireHTTP({
|
|
|
82
82
|
Runs before everything else, across every wire type: HTTP, Queue, Channel, Trigger, Scheduler, Workflow, Agent, CLI, MCP. Use it for cross-cutting concerns (e.g. telemetry) that must wrap every invocation regardless of transport.
|
|
83
83
|
|
|
84
84
|
```typescript
|
|
85
|
-
import { addGlobalMiddleware } from '
|
|
85
|
+
import { addGlobalMiddleware } from '#pikku/middleware'
|
|
86
86
|
import { telemetryOuter, telemetryInner } from '@pikku/core/middleware'
|
|
87
87
|
|
|
88
88
|
addGlobalMiddleware([telemetryOuter({ environmentId: env.STAGE_ID })]) // wraps the full call
|
|
@@ -95,7 +95,7 @@ addGlobalMiddleware([telemetryInner({ environmentId: env.STAGE_ID })]) // closes
|
|
|
95
95
|
|
|
96
96
|
```typescript
|
|
97
97
|
import { addHTTPMiddleware } from '@pikku/core/http'
|
|
98
|
-
import { cors, authBearer } from '
|
|
98
|
+
import { cors, authBearer } from '#pikku/middleware'
|
|
99
99
|
|
|
100
100
|
// All routes
|
|
101
101
|
addHTTPMiddleware('*', [
|
|
@@ -239,11 +239,29 @@ pikku.fetch.setHeader('x-tenant', tenantId)
|
|
|
239
239
|
`authHeaders.jwt` becomes `Authorization: Bearer …` and `authHeaders.apiKey`
|
|
240
240
|
becomes `X-API-KEY`; setting a JWT takes precedence over an API key.
|
|
241
241
|
|
|
242
|
+
## Generated client files
|
|
243
|
+
|
|
244
|
+
Name any of these under `clientFiles` in `pikku.config.json` and `pikku all` writes them, with
|
|
245
|
+
import paths computed from the config:
|
|
246
|
+
|
|
247
|
+
- `typesFile` — `RPCInput<'name'>`, `RPCOutput<'name'>`, `FlattenedRPCMap` (and `AnalyticsEvent`
|
|
248
|
+
when `scaffold.analytics` is on). Use these instead of reaching into the functions package.
|
|
249
|
+
- `envFile` — `apiUrl()`, `basePath()`, `appHref(path)`.
|
|
250
|
+
- `rpcFile` — `rpc()`, a client for code outside React such as a route `beforeLoad`. It forwards
|
|
251
|
+
the request cookie during SSR (TanStack Start). Needs `envFile`.
|
|
252
|
+
- `authClientFile` — the Better Auth client glue from `@pikku/better-auth/client`. Needs
|
|
253
|
+
`rpcFile`, `envFile` and a `getSession` function; `clientAuth` sets the login and home paths.
|
|
254
|
+
|
|
255
|
+
In `vite.config.ts`, `pikkuVite()` from `@pikku/vite` adds `testIds()` and, with Better Auth, the
|
|
256
|
+
dev cross-site session plugin. `pikku dev` runs every frontend that sets `dev.command`
|
|
257
|
+
(`--frontend a,b`, `--no-frontends`) and creates `BETTER_AUTH_SECRET` in `.env` on first run.
|
|
258
|
+
|
|
242
259
|
## What NOT to do
|
|
243
260
|
|
|
244
261
|
- Don't instantiate `PikkuFetch`/`PikkuRPC` inside a component — `createPikku`
|
|
245
262
|
goes once at the app root, the instance flows through Context.
|
|
246
263
|
- Don't call `usePikkuRPC()` outside a `<PikkuProvider>` — it throws.
|
|
264
|
+
- Don't copy a `rpc.ts` or `env.ts` between apps — generate them with `rpcFile` and `envFile`.
|
|
247
265
|
- Don't write a custom RPC client. The generated one already covers every
|
|
248
266
|
exposed function with full types.
|
|
249
267
|
- Don't hardcode user-facing strings. Every display string goes through an
|
|
@@ -90,8 +90,9 @@ pikku ui theme apply --preset quorum --structure monopro
|
|
|
90
90
|
| `--structure <id>` | swap in another preset's structure (depth) under this brand |
|
|
91
91
|
| `--primary`, `--secondary`, `--accent` | brand colours, hex |
|
|
92
92
|
| `--font-heading`, `--font-body` | Google Fonts families |
|
|
93
|
-
| `--
|
|
94
|
-
| `--
|
|
93
|
+
| `--mode light\|dark` | `light` (default). `dark` makes `--page`/`--ink` the dark ground and its text (see Dark designs) |
|
|
94
|
+
| `--page` | page colour (`structure.page`) — paper, linen, bone instead of white; with `--mode dark`, the dark ground |
|
|
95
|
+
| `--ink` | text colour (`structure.ink`); with `--mode dark`, the text on the dark ground |
|
|
95
96
|
|
|
96
97
|
Flags are flat scalars, one value each; there is no nested `--colors` object. One call writes the
|
|
97
98
|
theme, makes it active, regenerates `themes/index.ts` (so the running app reloads) and re-brands
|
|
@@ -102,6 +103,27 @@ Fine-grained structure tweaks the flags do not cover (a shadow scale, the radius
|
|
|
102
103
|
the active `themes/<id>.json` `structure`, followed by `pikku ui theme apply` to regenerate
|
|
103
104
|
`theme.css`; keep them in the theme, not in components.
|
|
104
105
|
|
|
106
|
+
## Dark designs
|
|
107
|
+
|
|
108
|
+
`--page`/`--ink` are light-mode colours unless you pass `--mode dark`. For a design that is dark
|
|
109
|
+
(a dark reference, a dark ground in the brief) always pass `--mode dark` with its ground and text:
|
|
110
|
+
|
|
111
|
+
```bash
|
|
112
|
+
pikku ui theme apply --preset linear --mode dark \
|
|
113
|
+
--primary '#c6ff3d' --secondary '#22262d' --page '#14161a' --ink '#eef1f4'
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
Every other token is derived for that ground: card and popover a step lighter, muted a further
|
|
117
|
+
step, muted text a mid tone of the ink at 4.5:1 or better, border and input visibly above the card,
|
|
118
|
+
and each `-foreground` black or white by contrast against its actual fill (so a lime primary gets a
|
|
119
|
+
dark label). The same values are written to `:root` and `.dark` (with `color-scheme: dark`), so a
|
|
120
|
+
stored scheme or `class="dark"` never switches to the preset's unrelated greys. Without
|
|
121
|
+
`--mode dark`, a dark `--page` is derived as a light ground and comes out wrong.
|
|
122
|
+
|
|
123
|
+
After every apply the command prints a contrast report (text/page, muted-text/page,
|
|
124
|
+
primary-foreground/primary, border/page, input/page) and warns on any pair under its target; fix
|
|
125
|
+
the colours and re-run, the exit code stays 0.
|
|
126
|
+
|
|
105
127
|
## Colour rule
|
|
106
128
|
|
|
107
129
|
Every colour in app code comes from the theme: a semantic utility (`bg-primary`, `text-foreground`,
|
|
@@ -20,6 +20,9 @@ has no extractor: you read the design off the source and pass it to `pikku ui th
|
|
|
20
20
|
pikku ui theme apply --preset breeze --primary '#0B6E99' --font-heading 'Manrope' --font-body 'Manrope' --page '#F7F5F0' --ink '#1C2A33'
|
|
21
21
|
```
|
|
22
22
|
|
|
23
|
+
If the reference is dark, add `--mode dark` (then `--page`/`--ink` are the dark ground and its
|
|
24
|
+
text); without it a dark page is derived as a light one.
|
|
25
|
+
|
|
23
26
|
You are the art director: what you read is signal, not a finished theme. Check page and ink
|
|
24
27
|
against each other and against the primary for 4.5:1 before applying.
|
|
25
28
|
|
|
@@ -44,7 +44,7 @@ permissions were removed in #972. Authorization lives on the function's own
|
|
|
44
44
|
### `pikkuChannelMiddleware(fn)`
|
|
45
45
|
|
|
46
46
|
```typescript
|
|
47
|
-
import { pikkuChannelMiddleware } from '
|
|
47
|
+
import { pikkuChannelMiddleware } from '#pikku/middleware'
|
|
48
48
|
|
|
49
49
|
const middleware = pikkuChannelMiddleware(async (services, event, next) => {
|
|
50
50
|
// Transform or filter events before/after
|
|
@@ -98,7 +98,7 @@ Authorization is not a wiring concern — declare it on the function via `permis
|
|
|
98
98
|
### Middleware
|
|
99
99
|
|
|
100
100
|
```typescript
|
|
101
|
-
import { cors, authBearer } from '
|
|
101
|
+
import { cors, authBearer } from '#pikku/middleware'
|
|
102
102
|
|
|
103
103
|
// Global middleware
|
|
104
104
|
addHTTPMiddleware('*', [
|
|
@@ -206,7 +206,7 @@ export const getBook = pikkuFunc({
|
|
|
206
206
|
|
|
207
207
|
// wirings/books.http.ts — same defineHTTPRoutes/wireHTTPRoutes shape as the Route Groups example above
|
|
208
208
|
import { addHTTPMiddleware } from '#pikku/middleware'
|
|
209
|
-
import { cors, authBearer } from '
|
|
209
|
+
import { cors, authBearer } from '#pikku/middleware'
|
|
210
210
|
|
|
211
211
|
addHTTPMiddleware('*', [cors(), authBearer()])
|
|
212
212
|
```
|