@pikku/skills 0.12.52 → 0.12.55
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/dist/skills.gen.js +2 -2
- package/dist/snippets.js +1 -1
- package/package.json +1 -1
- package/skills/pikku-a11y/SKILL.md +22 -15
- package/skills/pikku-addon/SKILL.md +5 -5
- package/skills/pikku-addon/references/addon-package-manifest.md +7 -0
- package/skills/pikku-addon/references/openapi.md +11 -0
- package/skills/pikku-agent/SKILL.md +6 -6
- package/skills/pikku-agent/references/agents.md +8 -1
- package/skills/pikku-agent/references/runner-vercel.md +2 -2
- package/skills/pikku-agent/references/voice.md +7 -1
- package/skills/pikku-architect/SKILL.md +2 -2
- package/skills/pikku-architect/references/plan-defects.md +9 -7
- package/skills/pikku-auth/SKILL.md +15 -36
- package/skills/pikku-auth/references/better-auth.md +106 -133
- package/skills/pikku-auth/references/jose.md +6 -0
- package/skills/pikku-auth/references/machine-auth.md +6 -0
- package/skills/pikku-auth/references/sessions.md +6 -0
- package/skills/pikku-build/SKILL.md +37 -34
- package/skills/pikku-build/references/app.md +113 -99
- package/skills/pikku-build/references/design.md +43 -31
- package/skills/pikku-build/references/feature.md +17 -3
- package/skills/pikku-build/references/mocks.md +29 -0
- package/skills/pikku-build/references/multi-app.md +25 -18
- package/skills/pikku-build/references/native-app.md +9 -0
- package/skills/pikku-build/references/openapi.md +41 -34
- package/skills/pikku-build/references/platform.md +23 -14
- package/skills/pikku-build/references/post-clone.md +1 -1
- package/skills/pikku-build/references/quick.md +76 -39
- package/skills/pikku-build/references/scenarios.md +28 -16
- package/skills/pikku-build/references/ship.md +19 -16
- package/skills/pikku-build/references/theming.md +76 -127
- package/skills/pikku-changes/SKILL.md +7 -10
- package/skills/pikku-concepts/SKILL.md +34 -44
- package/skills/pikku-concepts/references/concept-mapping.md +18 -0
- package/skills/pikku-concepts/references/language.md +6 -6
- package/skills/pikku-convert-screen/SKILL.md +45 -0
- package/skills/pikku-deploy/references/cloudflare.md +8 -0
- package/skills/pikku-deploy/references/lambda.md +8 -0
- package/skills/pikku-deploy/references/nextjs.md +1 -1
- package/skills/pikku-deploy/references/uws.md +1 -1
- package/skills/pikku-deploy/references/ws.md +3 -3
- package/skills/pikku-emails/SKILL.md +3 -9
- package/skills/pikku-guide/SKILL.md +28 -27
- package/skills/pikku-i18n/SKILL.md +7 -7
- package/skills/pikku-i18n/references/enum-labels.md +9 -0
- package/skills/pikku-i18n/references/messages.md +21 -9
- package/skills/pikku-i18n/references/rtl.md +18 -38
- package/skills/pikku-knowledge/SKILL.md +12 -13
- package/skills/pikku-kysely/SKILL.md +3 -21
- package/skills/pikku-list-query/SKILL.md +2 -2
- package/skills/pikku-meta/references/audit.md +10 -1
- package/skills/pikku-meta/references/meta.md +6 -0
- package/skills/pikku-meta/references/versioning.md +10 -0
- package/skills/pikku-middleware/SKILL.md +7 -16
- package/skills/pikku-n8n-import/SKILL.md +9 -1
- package/skills/pikku-n8n-import/references/addon-mapping.md +6 -0
- package/skills/pikku-n8n-import/references/code-translation.md +7 -0
- package/skills/pikku-permissions/SKILL.md +41 -10
- package/skills/pikku-permissions/references/scopes.md +59 -0
- package/skills/pikku-react/SKILL.md +20 -22
- package/skills/pikku-react/references/client.md +2 -70
- package/skills/pikku-react/references/react-query.md +11 -0
- package/skills/pikku-react/references/workflows.md +11 -0
- package/skills/pikku-realtime/SKILL.md +9 -3
- package/skills/pikku-report/SKILL.md +17 -16
- package/skills/pikku-scenario/SKILL.md +72 -81
- package/skills/pikku-scenario/references/browser.md +5 -5
- package/skills/pikku-scenario/references/coverage.md +4 -4
- package/skills/pikku-scenario/references/steps.md +31 -30
- package/skills/pikku-scenario/references/{persona-run.md → virtual-user-run.md} +47 -42
- package/skills/pikku-scenario/references/virtual-users.md +114 -0
- package/skills/pikku-seo/SKILL.md +12 -16
- package/skills/pikku-service-backends/SKILL.md +0 -10
- package/skills/pikku-service-backends/references/aws.md +6 -0
- package/skills/pikku-service-backends/references/mongodb.md +7 -0
- package/skills/pikku-service-backends/references/redis.md +6 -0
- package/skills/pikku-services/references/audit-wire-service.md +2 -2
- package/skills/pikku-services/references/audit.md +10 -1
- package/skills/pikku-services/references/config.md +6 -0
- package/skills/pikku-services/references/services.md +9 -2
- package/skills/pikku-software-archaeology/SKILL.md +21 -27
- package/skills/pikku-software-archaeology/references/blueprint.schema.json +16 -16
- package/skills/pikku-software-archaeology/references/pikku-mapping.md +3 -3
- package/skills/pikku-software-archaeology/references/second-opinion.md +13 -1
- package/skills/pikku-tailwind/SKILL.md +111 -0
- package/skills/pikku-theme/SKILL.md +187 -0
- package/skills/pikku-theme/references/from-a-reference.md +44 -0
- package/skills/pikku-theme/references/presets.md +40 -0
- package/skills/pikku-ui/SKILL.md +41 -0
- package/skills/pikku-wiring/SKILL.md +13 -12
- package/skills/pikku-wiring/references/channel.md +7 -1
- package/skills/pikku-wiring/references/cli.md +7 -1
- package/skills/pikku-wiring/references/gateway-slack.md +6 -0
- package/skills/pikku-wiring/references/http.md +10 -3
- package/skills/pikku-wiring/references/mcp.md +8 -0
- package/skills/pikku-wiring/references/queue.md +6 -0
- package/skills/pikku-wiring/references/realtime.md +34 -4
- package/skills/pikku-wiring/references/rpc.md +6 -0
- package/skills/pikku-wiring/references/scheduler.md +6 -0
- package/skills/pikku-wiring/references/trigger.md +8 -0
- package/skills/pikku-workflow/SKILL.md +9 -7
- package/skills/pikku-admin-to-fabric/SKILL.md +0 -212
- package/skills/pikku-auth/references/permissions.md +0 -261
- package/skills/pikku-blueprint-to-fabric/SKILL.md +0 -377
- package/skills/pikku-blueprint-to-fabric/scripts/inventory.mjs +0 -367
- package/skills/pikku-fabric/SKILL.md +0 -619
- package/skills/pikku-fabric/references/debugging.md +0 -110
- package/skills/pikku-mantine/SKILL.md +0 -80
- package/skills/pikku-scenario/references/personas.md +0 -117
- package/skills/pikku-wiring/references/realtime-other-routes.md +0 -23
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: pikku-software-archaeology
|
|
3
|
-
description: 'Use when reverse-engineering an existing repository into a Product Blueprint — recovering what product an undocumented or organically-grown codebase implements so it can be rebuilt cleanly (e.g. as a Pikku app) — and when turning that blueprint into a plain-language second opinion for the non-technical owner
|
|
3
|
+
description: 'Use when reverse-engineering an existing repository into a `.knowledge/` Product Blueprint — recovering what product an undocumented or organically-grown codebase implements so it can be rebuilt cleanly (e.g. as a Pikku app) — and when turning that blueprint into a plain-language second opinion for the non-technical owner of the app. TRIGGER when: user says "extract a blueprint", "reverse engineer this app", "what does this codebase actually do as a product", "prepare this repo for a rewrite/migration", points at a legacy repo (any language) and asks for its domains, workflows, business rules or a rebuild plan, or asks "explain how my app works" / "what would you do differently" / "is this built well?" for a founder, PM or operator. Not the `knowledge/` notes of pikku-knowledge. DO NOT TRIGGER for: documenting code structure, API docs for a clean codebase, an engineer-facing code review, or rebuilding from an existing blueprint.'
|
|
4
4
|
installGroups: [core]
|
|
5
5
|
---
|
|
6
6
|
|
|
@@ -25,7 +25,7 @@ Everything goes in `<repo>/.knowledge/` (or a caller-specified directory):
|
|
|
25
25
|
|
|
26
26
|
```
|
|
27
27
|
.knowledge/
|
|
28
|
-
├── product.json # purpose,
|
|
28
|
+
├── product.json # purpose, virtualUsers, capabilities, terminology
|
|
29
29
|
├── domains.json # business domains (NEVER folder names)
|
|
30
30
|
├── entities.json # domain entities: attributes, relationships, states, transitions
|
|
31
31
|
├── commands.json # state-changing actions (SendInvoice, not POST /invoices/:id/send)
|
|
@@ -45,9 +45,9 @@ Everything goes in `<repo>/.knowledge/` (or a caller-specified directory):
|
|
|
45
45
|
│ # frontend and/or non-HTTP consumer channels. A backend-only repo omits them
|
|
46
46
|
│ # and the validator does not complain.
|
|
47
47
|
├── interfaces.json # every way the product is consumed: web-ui, cli, mcp, openapi-rest, sdk, realtime, webhooks
|
|
48
|
-
├── frontend.json # web-UI app shape: framework, router, styling, data layer, auth (e.g. TanStack Start + better-auth +
|
|
48
|
+
├── frontend.json # web-UI app shape: framework, router, styling, data layer, auth (e.g. TanStack Start + better-auth + Tailwind)
|
|
49
49
|
├── frontend-routes.json # the page/route tree — what a user navigates to, its data + components
|
|
50
|
-
└── frontend-components.json# component inventory; `rebuild` splits trivial-
|
|
50
|
+
└── frontend-components.json# component inventory; `rebuild` splits trivial-library from custom-logic-to-port
|
|
51
51
|
```
|
|
52
52
|
|
|
53
53
|
The exact field shapes live in `references/blueprint.schema.json` (in this skill's directory — read it before writing any output file). After writing, ALWAYS run:
|
|
@@ -77,21 +77,21 @@ Build an inventory before interpreting anything:
|
|
|
77
77
|
|
|
78
78
|
Where intent hides, per ecosystem (read these first):
|
|
79
79
|
|
|
80
|
-
| Ecosystem | Highest-yield locations
|
|
81
|
-
| ------------------------- |
|
|
82
|
-
| Rails | `config/routes.rb`, model validations + callbacks + `aasm`/state machines, `app/policies` (Pundit) / `ability.rb` (CanCan), Sidekiq/ActiveJob workers, `db/schema.rb`, specs (esp. request + model specs)
|
|
83
|
-
| Express/Node | route registration files, middleware chains (auth!), inline `if` guards in handlers, SQL/ORM models, `jobs/`+crontab refs, webhook handlers
|
|
84
|
-
| Django | `urls.py`, model `Meta`/constraints/`clean()`, DRF serializers + permissions classes, celery tasks, admin.py (reveals internal workflows)
|
|
85
|
-
| Laravel | `routes/`, FormRequests (validation), Policies/Gates, Jobs + scheduler in `Kernel.php`, migrations
|
|
86
|
-
| Go | mux/router setup, middleware, struct tags, `cmd/` binaries (each is a component)
|
|
87
|
-
| Frontend (React/Vue/etc.) | router config / file-based routes (pages a user reaches), the component tree, the design-system import (`@
|
|
80
|
+
| Ecosystem | Highest-yield locations |
|
|
81
|
+
| ------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
82
|
+
| Rails | `config/routes.rb`, model validations + callbacks + `aasm`/state machines, `app/policies` (Pundit) / `ability.rb` (CanCan), Sidekiq/ActiveJob workers, `db/schema.rb`, specs (esp. request + model specs) |
|
|
83
|
+
| Express/Node | route registration files, middleware chains (auth!), inline `if` guards in handlers, SQL/ORM models, `jobs/`+crontab refs, webhook handlers |
|
|
84
|
+
| Django | `urls.py`, model `Meta`/constraints/`clean()`, DRF serializers + permissions classes, celery tasks, admin.py (reveals internal workflows) |
|
|
85
|
+
| Laravel | `routes/`, FormRequests (validation), Policies/Gates, Jobs + scheduler in `Kernel.php`, migrations |
|
|
86
|
+
| Go | mux/router setup, middleware, struct tags, `cmd/` binaries (each is a component) |
|
|
87
|
+
| Frontend (React/Vue/etc.) | router config / file-based routes (pages a user reaches), the component tree, the design-system import (`@mui/*`, Tailwind config, shadcn `components.json`) to judge consistency, the data layer (react-query/tRPC/fetch wrappers) to tie UI back to backend queries, charts/tables/editors (the `custom-logic` port risk), auth wiring |
|
|
88
88
|
|
|
89
89
|
### Phase 2 — Test excavation (do not skip)
|
|
90
90
|
|
|
91
91
|
Tests are the closest thing to an executable product spec. For every test file:
|
|
92
92
|
|
|
93
93
|
- `describe`/`context`/`it` names → **scenarios** (attach to the matching workflow in `workflows.json` under `scenarios[]`, with `fromTest` set).
|
|
94
|
-
- User-flow/journey harnesses (`pikkuUserFlow` stories,
|
|
94
|
+
- User-flow/journey harnesses (`pikkuUserFlow` stories, `.feature` files, Playwright journeys) are the highest-grade scenario source — they already ARE given/when/outcome sequences; extract them verbatim.
|
|
95
95
|
- Assertions → confirmations of policies and invariants (upgrade their `confidence` to `high`, add the test as evidence).
|
|
96
96
|
- Fixtures/factories → entity attribute shapes and realistic example data.
|
|
97
97
|
- Edge-case tests → business rules that exist **nowhere else in the code** (e.g. "replayed webhook events are idempotent" may only be stated in a test).
|
|
@@ -105,9 +105,9 @@ Run each lens over the surveyed material. Rules that counter the classic failure
|
|
|
105
105
|
|
|
106
106
|
**Domains** — infer from data ownership, workflows, and vocabulary; NEVER from folder names. `controllers/` is not a domain; "Billing" is. A domain owns entities and the commands that mutate them.
|
|
107
107
|
|
|
108
|
-
**Entities** — domain concepts, not tables. Include: attributes (from schema + serializers + fixtures), relationships, **lifecycle states and transitions** (grep status/state columns, then find every write to them — each write site is a transition with a trigger), ownership (which
|
|
108
|
+
**Entities** — domain concepts, not tables. Include: attributes (from schema + serializers + fixtures), relationships, **lifecycle states and transitions** (grep status/state columns, then find every write to them — each write site is a transition with a trigger), ownership (which virtual user's rows), constraints.
|
|
109
109
|
|
|
110
|
-
**Commands** — every way state changes: routes, jobs, webhooks, CLI, admin consoles, DB triggers. Name them imperative `VerbNoun` in domain language: `POST /api/users/:id/status` → `ActivateUser`. For each:
|
|
110
|
+
**Commands** — every way state changes: routes, jobs, webhooks, CLI, admin consoles, DB triggers. Name them imperative `VerbNoun` in domain language: `POST /api/users/:id/status` → `ActivateUser`. For each: virtual user, preconditions (every `if (...) return 4xx` guard is a precondition or policy), effects, events produced. Convention: authentication/token issuance is a command (`LogInUser`, effect: "issues a session/JWT") even though it writes no rows — it changes the caller's security state.
|
|
111
111
|
|
|
112
112
|
**Queries** — reads and views, `GetX`/`ListX`/`SearchX`. Record the tenancy scoping each applies (a missing `WHERE user_id=` that exists elsewhere is a gaps.json security entry).
|
|
113
113
|
|
|
@@ -131,18 +131,18 @@ Run each lens over the surveyed material. Rules that counter the classic failure
|
|
|
131
131
|
|
|
132
132
|
**Interfaces** (`interfaces.json`, optional) — every way the product is CONSUMED, one entry per channel, not per route. A product is usually driven through several: a **web UI** (humans), a **CLI** (developers/operators), an **MCP server** (AI agents — in a Pikku app each MCP tool IS a `pikkuFunc`), an **OpenAPI/REST** surface (developers/external systems, often _generated_ from the routes), a **generated SDK**, **realtime** (websocket/SSE), and **webhooks** (in/out). For each: `kind`, `audience`, `purpose`, roughly how many ops it exposes, whether it's `generated` vs hand-written, which domains it serves, and `status` (complete/partial/stub — an MCP server with two tools is `stub`). This layer answers "who can drive this, and how" — it is the map the second-opinion skill needs to explain that the app is usable by people, developers, and agents.
|
|
133
133
|
|
|
134
|
-
**Frontend** (`frontend.json` + `frontend-routes.json` + `frontend-components.json`, optional) — the web UI, which needs its own treatment because frontends vary wildly (framework, router, styling, state, data, auth) and the rebuild target is opinionated: **everything in one component system (
|
|
134
|
+
**Frontend** (`frontend.json` + `frontend-routes.json` + `frontend-components.json`, optional) — the web UI, which needs its own treatment because frontends vary wildly (framework, router, styling, state, data, auth) and the rebuild target is opinionated: **everything in one component system (shadcn), one data layer, one auth**.
|
|
135
135
|
|
|
136
136
|
- `frontend.json` records the stack as FACTS: framework (e.g. TanStack Start), rendering (SSR/streaming/SPA), router, styling/design system, `designSystemConsistency`, state management, data layer (e.g. pikku-react vs REST helpers), auth (e.g. better-auth), i18n. Name the real technologies — the second-opinion skill weighs their tradeoffs, so record them precisely (do NOT editorialize here; this file is facts).
|
|
137
137
|
- `frontend-routes.json` is the page tree: each route's `purpose` in product terms, `auth`, the `dataFrom` (query/command names it reads — reuse the backend concept names so the UI ties back to the domain), the `usesComponents`, and the `userFlows` it belongs to.
|
|
138
138
|
- `frontend.json.designFindings` captures **broken/inconsistent design patterns** as concrete, cited observations (not taste). Actively hunt for: _interaction inconsistency_ (the same job done as a modal in one place and a drawer in another; inconsistent confirm dialogs); _theming not tokenized_ (hardcoded hex colors, magic spacing/font sizes, inline styles instead of theme tokens/variables — grep for `#[0-9a-f]{3,6}`, `style={{`, raw `px` values); _cross-page inconsistency_ (the same element — button, page header, card — styled differently across routes); _component duplication_ (three near-identical cards/tables for one purpose); _design-system bypass_ (raw HTML/CSS where a library component exists). Each finding gets an example, its impact (feels unpolished / a color change means hunting every file), and a fix (standardize on one pattern / move to tokens / extract one shared component). These are almost always cheap cleanups, and they are exactly what a non-technical owner perceives as "the app looks off" without being able to say why.
|
|
139
|
-
- `frontend-components.json` is where the frontend's real migration cost lives, in the **`rebuild`** field: `
|
|
139
|
+
- `frontend-components.json` is where the frontend's real migration cost lives, in the **`rebuild`** field: `library-standard` (maps 1:1 to a shadcn component — trivial), `library-composition` (built from shadcn primitives — straightforward), `custom-style` (diverges only visually — normalize to shadcn), or **`custom-logic`** (bespoke behavior — a custom chart, a virtualized/complex table, a canvas, drag-and-drop, a rich editor — that must be **ported**, not re-skinned). A `custom-logic` component MUST fill `customLogic` explaining the behavior, and should list the `dependencies` (charting/table/editor libs) that make it a real port. This split — "trivially re-shadcn-able" vs "carries logic that must survive the port" — is the single most useful thing the frontend extraction produces.
|
|
140
140
|
- **Server-rendered / non-React frontends still get all three files — do NOT skip them.** The `rebuild` enum is named for the target stack, but the distinction it draws is target-agnostic: _trivial stock element_ vs _composed from primitives_ vs _visual divergence only_ vs **bespoke behavior that must be ported**. A Rails app (Slim/ERB + ViewComponents + Hotwire/Stimulus), a Django app (templates + HTMX), or a Laravel app (Blade + Livewire) all have that same split, and it is just as load-bearing there. Use the enum values verbatim (the validator enforces them), classify by what the thing actually _does_, and record the vocabulary mismatch in `frontend.json.notes`. Concretely: treat a template partial + its behavior controller (Stimulus/Alpine/Livewire) as ONE component and classify the pair; a server-rendered app's `custom-logic` is the same list as a SPA's (maps, charts, video players, payment elements, drag-to-reorder, rich text, QR, live-updating regions), plus anything whose behavior rides on a streaming/partial-update contract (Turbo Streams, HTMX swaps) — get that mapping wrong on rebuild and pages show stale data. Omitting these files because "it isn't a React app" hides the frontend's entire migration cost, which is the one thing this file exists to expose.
|
|
141
141
|
- The `designFindings` grep hints above are React-flavored; the server-rendered equivalents are inline `style=` attributes in templates, hardcoded hex in the stylesheet tree, per-locale forked templates instead of i18n, and design-system bypass = raw markup where a component/partial already exists. Same findings, different needles.
|
|
142
142
|
|
|
143
143
|
### Phase 4 — Cross-check, validate, synthesize
|
|
144
144
|
|
|
145
|
-
1. Cross-checks before writing: every command has
|
|
145
|
+
1. Cross-checks before writing: every command has a virtual user and at least one precondition or policy (or you explain why it is genuinely unguarded); every state transition appears in some command/workflow — if a state is only reachable by manual/DB intervention, record the transition with trigger `"none (manual/DB-only)"` AND add a gaps.json entry; every event has a producer; every api surface maps to a defined concept; every integration is used by some command/workflow.
|
|
146
146
|
2. Write all 14 JSON files, sorting **unordered identity collections** by `name` (or `path`) — on first extraction too, not just re-runs. Arrays whose order carries meaning (`workflows[].steps`, and any other observed sequence) stay in their observed order: sorting them would rewrite the behavior you extracted. Run the validator: done means `0 error(s)` and exit 0, and every WARN explicitly reviewed and either fixed or justified in your summary.
|
|
147
147
|
3. Write `blueprint.md`: product summary → domain map → per-domain narrative (entities/commands/events with the interesting rules) → workflows → integrations/architecture → invariants → gaps and open decisions → rebuild recommendation. Write it for the engineer who will rebuild the product and has never seen the legacy code.
|
|
148
148
|
|
|
@@ -177,17 +177,11 @@ Run each lens over the surveyed material. Rules that counter the classic failure
|
|
|
177
177
|
| "A component list is enough" | Without the `rebuild` split, you've hidden the frontend's real cost. Flag every `custom-logic` component (chart/table/canvas/editor) and say what the logic is — that's the port work. |
|
|
178
178
|
| "I'll skip the validator, the JSON looks right" | Run it. Missing domains refs, dangling event names, and undescribed custom-logic components are exactly what it catches. |
|
|
179
179
|
|
|
180
|
-
##
|
|
180
|
+
## Reference files
|
|
181
181
|
|
|
182
|
-
|
|
183
|
-
# 1. survey + excavate + extract (you, with Read/Grep/subagents)
|
|
184
|
-
# 2. write <repo>/.knowledge/*.json + blueprint.md per references/blueprint.schema.json
|
|
185
|
-
# 3. validate:
|
|
186
|
-
node <skill-dir>/scripts/validate.mjs <repo>/.knowledge
|
|
187
|
-
```
|
|
188
|
-
|
|
189
|
-
- Schema/contract: `references/blueprint.schema.json`
|
|
182
|
+
- Schema and contract for every output file: `references/blueprint.schema.json` (large JSON; open it with a search for the file you are writing rather than reading it whole)
|
|
190
183
|
- How Pikku consumes the blueprint: `references/pikku-mapping.md`
|
|
184
|
+
- Validator: `node <skill-dir>/scripts/validate.mjs <repo>/.knowledge`
|
|
191
185
|
|
|
192
186
|
## The second phase — a report the owner can act on
|
|
193
187
|
|
|
@@ -36,7 +36,7 @@
|
|
|
36
36
|
"required": [
|
|
37
37
|
"name",
|
|
38
38
|
"purpose",
|
|
39
|
-
"
|
|
39
|
+
"virtualUsers",
|
|
40
40
|
"capabilities",
|
|
41
41
|
"terminology",
|
|
42
42
|
"evidence",
|
|
@@ -49,7 +49,7 @@
|
|
|
49
49
|
"description": "1-3 sentences: what problem, for whom"
|
|
50
50
|
},
|
|
51
51
|
"targetUsers": { "type": "array", "items": { "type": "string" } },
|
|
52
|
-
"
|
|
52
|
+
"virtualUsers": {
|
|
53
53
|
"type": "array",
|
|
54
54
|
"minItems": 1,
|
|
55
55
|
"items": {
|
|
@@ -212,7 +212,7 @@
|
|
|
212
212
|
},
|
|
213
213
|
"ownership": {
|
|
214
214
|
"type": "string",
|
|
215
|
-
"description": "which
|
|
215
|
+
"description": "which virtual user/tenant owns rows of this entity"
|
|
216
216
|
},
|
|
217
217
|
"constraints": { "type": "array", "items": { "type": "string" } },
|
|
218
218
|
"evidence": { "$ref": "#/$defs/evidence" },
|
|
@@ -233,7 +233,7 @@
|
|
|
233
233
|
"required": [
|
|
234
234
|
"name",
|
|
235
235
|
"domain",
|
|
236
|
-
"
|
|
236
|
+
"virtualUser",
|
|
237
237
|
"input",
|
|
238
238
|
"preconditions",
|
|
239
239
|
"effects",
|
|
@@ -247,7 +247,7 @@
|
|
|
247
247
|
"description": "imperative VerbNoun: ApproveInvoice"
|
|
248
248
|
},
|
|
249
249
|
"domain": { "$ref": "#/$defs/conceptName" },
|
|
250
|
-
"
|
|
250
|
+
"virtualUser": { "type": "string" },
|
|
251
251
|
"trigger": {
|
|
252
252
|
"type": "string",
|
|
253
253
|
"description": "how it is invoked today (route, job, webhook, cron)"
|
|
@@ -305,7 +305,7 @@
|
|
|
305
305
|
"required": [
|
|
306
306
|
"name",
|
|
307
307
|
"domain",
|
|
308
|
-
"
|
|
308
|
+
"virtualUser",
|
|
309
309
|
"description",
|
|
310
310
|
"returns",
|
|
311
311
|
"evidence",
|
|
@@ -317,7 +317,7 @@
|
|
|
317
317
|
"description": "GetX / ListX / SearchX"
|
|
318
318
|
},
|
|
319
319
|
"domain": { "$ref": "#/$defs/conceptName" },
|
|
320
|
-
"
|
|
320
|
+
"virtualUser": { "type": "string" },
|
|
321
321
|
"description": { "type": "string" },
|
|
322
322
|
"input": {
|
|
323
323
|
"type": "array",
|
|
@@ -448,7 +448,7 @@
|
|
|
448
448
|
"required": [
|
|
449
449
|
"name",
|
|
450
450
|
"kind",
|
|
451
|
-
"
|
|
451
|
+
"virtualUser",
|
|
452
452
|
"trigger",
|
|
453
453
|
"steps",
|
|
454
454
|
"result",
|
|
@@ -458,7 +458,7 @@
|
|
|
458
458
|
"properties": {
|
|
459
459
|
"name": { "type": "string" },
|
|
460
460
|
"kind": { "type": "string", "enum": ["user", "admin", "system"] },
|
|
461
|
-
"
|
|
461
|
+
"virtualUser": { "type": "string" },
|
|
462
462
|
"trigger": { "type": "string" },
|
|
463
463
|
"steps": {
|
|
464
464
|
"type": "array",
|
|
@@ -879,7 +879,7 @@
|
|
|
879
879
|
"frontend.json": {
|
|
880
880
|
"x-optional": true,
|
|
881
881
|
"type": "object",
|
|
882
|
-
"description": "Web-UI app-level shape. Names the framework/router/styling/data/auth so the rebuild can weigh their tradeoffs (e.g. TanStack Start + better-auth +
|
|
882
|
+
"description": "Web-UI app-level shape. Names the framework/router/styling/data/auth so the rebuild can weigh their tradeoffs (e.g. TanStack Start + better-auth + Tailwind).",
|
|
883
883
|
"required": [
|
|
884
884
|
"framework",
|
|
885
885
|
"styling",
|
|
@@ -903,12 +903,12 @@
|
|
|
903
903
|
},
|
|
904
904
|
"styling": {
|
|
905
905
|
"type": "string",
|
|
906
|
-
"description": "the design system, e.g.
|
|
906
|
+
"description": "the design system, e.g. shadcn, Tailwind, MUI, CSS modules"
|
|
907
907
|
},
|
|
908
908
|
"designSystemConsistency": {
|
|
909
909
|
"type": "string",
|
|
910
910
|
"enum": ["single-system", "mostly-consistent", "mixed", "ad-hoc"],
|
|
911
|
-
"description": "how uniformly ONE component/theme system is used — the rebuild target is 'everything
|
|
911
|
+
"description": "how uniformly ONE component/theme system is used — the rebuild target is 'everything shadcn', so divergence is porting work"
|
|
912
912
|
},
|
|
913
913
|
"designFindings": {
|
|
914
914
|
"type": "array",
|
|
@@ -1036,7 +1036,7 @@
|
|
|
1036
1036
|
"frontend-components.json": {
|
|
1037
1037
|
"x-optional": true,
|
|
1038
1038
|
"type": "object",
|
|
1039
|
-
"description": "Component inventory. The load-bearing field is `rebuild`: it separates components that are trivially rebuildable in the target design system (
|
|
1039
|
+
"description": "Component inventory. The load-bearing field is `rebuild`: it separates components that are trivially rebuildable in the target design system (shadcn) from those carrying bespoke logic that must be carefully PORTED (custom charts, complex tables, canvas, drag/drop). That split is the frontend's real migration cost.",
|
|
1040
1040
|
"required": ["components"],
|
|
1041
1041
|
"properties": {
|
|
1042
1042
|
"components": {
|
|
@@ -1071,12 +1071,12 @@
|
|
|
1071
1071
|
"rebuild": {
|
|
1072
1072
|
"type": "string",
|
|
1073
1073
|
"enum": [
|
|
1074
|
-
"
|
|
1075
|
-
"
|
|
1074
|
+
"library-standard",
|
|
1075
|
+
"library-composition",
|
|
1076
1076
|
"custom-style",
|
|
1077
1077
|
"custom-logic"
|
|
1078
1078
|
],
|
|
1079
|
-
"description": "
|
|
1079
|
+
"description": "library-standard = maps 1:1 to a shadcn component (trivial); library-composition = built from shadcn primitives (straightforward); custom-style = diverges visually from the design system (normalize to shadcn); custom-logic = bespoke behavior (chart/table/canvas/drag/editor) that must be PORTED, not re-skinned"
|
|
1080
1080
|
},
|
|
1081
1081
|
"customLogic": {
|
|
1082
1082
|
"type": "string",
|
|
@@ -27,11 +27,11 @@ The `.knowledge/` blueprint is designed so each concept maps onto exactly one Pi
|
|
|
27
27
|
| `interfaces.json` kind=mcp | `wireMCP` — each MCP tool IS a `pikkuFunc` (reuse the command/query funcs; don't author tool duplicates) |
|
|
28
28
|
| `interfaces.json` kind=openapi-rest / sdk | generated, not hand-written: the OpenAPI spec + typed client SDK fall out of the `wireHTTP` routes + codegen |
|
|
29
29
|
| `interfaces.json` kind=websocket-realtime | `pikku-wiring` EventHub topics / channels |
|
|
30
|
-
| `frontend.json` | `apps/app` (TanStack Start) shell: router,
|
|
30
|
+
| `frontend.json` | `apps/app` (TanStack Start) shell: router, `packages/theme` theme, `pikku-react` data layer, `better-auth` client — the target stack the legacy UI is rebuilt onto |
|
|
31
31
|
| `frontend-routes.json` | TanStack Router routes under `apps/app/src/routes/**` (thin data containers calling `usePikkuQuery`); `dataFrom` names become the generated hooks; subpath routes for rich detail views |
|
|
32
|
-
| `frontend-components.json` rebuild=`
|
|
32
|
+
| `frontend-components.json` rebuild=`library-standard`/`library-composition` | components in `packages/components` composed from shadcn components — the trivial/straightforward bulk |
|
|
33
33
|
| `frontend-components.json` rebuild=`custom-logic` | the PORT list — each becomes a `packages/components` component that reimplements the bespoke behavior (chart/table/editor); its `dependencies` inform whether the lib is kept or replaced. These are the frontend's real work items |
|
|
34
|
-
| `frontend-components.json` rebuild=`custom-style` | normalize to
|
|
34
|
+
| `frontend-components.json` rebuild=`custom-style` | normalize to shadcn/theme tokens; usually deleted-and-recomposed, not ported |
|
|
35
35
|
|
|
36
36
|
## Order of generation
|
|
37
37
|
|
|
@@ -1,5 +1,17 @@
|
|
|
1
1
|
# Product Second Opinion
|
|
2
2
|
|
|
3
|
+
## Contents
|
|
4
|
+
|
|
5
|
+
- Overview
|
|
6
|
+
- The cardinal rule: translate, don't dump
|
|
7
|
+
- Report structure (layered — skim or dive)
|
|
8
|
+
- Rewire vs rebuild (say which)
|
|
9
|
+
- The frontend and the other ways your app is used
|
|
10
|
+
- Technology choices — the honest tradeoffs (don't be cheap on the cons)
|
|
11
|
+
- Delivery
|
|
12
|
+
- Red flags — you're writing the wrong report
|
|
13
|
+
- Relationship to the extraction phase
|
|
14
|
+
|
|
3
15
|
## Overview
|
|
4
16
|
|
|
5
17
|
Turn an extracted product blueprint into a **report a non-technical owner can act on**. Two jobs, in one voice: (1) explain, in plain language, how the app they're stuck with actually works; (2) give an honest, opinionated second opinion — what's solid, what's holding them back, and how you'd build it better, argued in business outcomes, not architecture.
|
|
@@ -110,7 +122,7 @@ Worth naming when it applies: adopting one coherent system in place of hand-roll
|
|
|
110
122
|
|
|
111
123
|
### The stack a rebuild would land on
|
|
112
124
|
|
|
113
|
-
Include this section **only if you are actually recommending a rebuild** onto it — and then give every part of it the same both-sides treatment you gave the app's own bets, per the "hold your own recommendation to the same bar" rule above. These are not choices the app made; they are choices you are proposing, which is exactly why their costs are the reader's to weigh. The Pikku target stack is Pikku + Better Auth + TanStack Start +
|
|
125
|
+
Include this section **only if you are actually recommending a rebuild** onto it — and then give every part of it the same both-sides treatment you gave the app's own bets, per the "hold your own recommendation to the same bar" rule above. These are not choices the app made; they are choices you are proposing, which is exactly why their costs are the reader's to weigh. The Pikku target stack is Pikku + Better Auth + TanStack Start + shadcn; the framings below are reference material for the parts you actually recommend, not a script to recite.
|
|
114
126
|
|
|
115
127
|
**Better Auth (self-hosted sign-in) — instead of a paid service like Auth0/Clerk.**
|
|
116
128
|
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: pikku-tailwind
|
|
3
|
+
description: >-
|
|
4
|
+
Use when building a Tailwind + shadcn/ui interface on top of a Pikku backend — rendering dates that
|
|
5
|
+
came back from a generated client, keeping layout flow-relative so the app survives an RTL locale,
|
|
6
|
+
branching on colour scheme without hardcoding a shade, and choosing components. TRIGGER when:
|
|
7
|
+
putting a value from usePikkuQuery or an RPC response on screen, writing margins/padding/alignment
|
|
8
|
+
classes, choosing a date input or any shadcn component, handling light/dark, or fixing a
|
|
9
|
+
`@shadcn/lint` error. DO NOT TRIGGER when: the data does not come from a Pikku client (this is only
|
|
10
|
+
about what the generated clients hand you), or for user-facing copy (use pikku-i18n).
|
|
11
|
+
installGroups: [client]
|
|
12
|
+
---
|
|
13
|
+
|
|
14
|
+
# Tailwind and shadcn/ui on a Pikku client
|
|
15
|
+
|
|
16
|
+
The UI is Tailwind v4 utility classes over shadcn/ui components that live in the app's own
|
|
17
|
+
`src/components/ui/`. They are source you edit, not a dependency you configure. Every colour, radius
|
|
18
|
+
and font comes from CSS variables in the app's `theme.css`; change the look by editing the theme presets in `packages/theme/themes` (the theme command ships with the UI addon).
|
|
19
|
+
|
|
20
|
+
## Components
|
|
21
|
+
|
|
22
|
+
- Use the shadcn component for the job (`Button`, `Input`, `Select`, `Sheet`, `Table`, `Tabs`,
|
|
23
|
+
`Calendar`) before writing markup. Compose it with utility classes for layout.
|
|
24
|
+
- Need a size or look the component lacks? Add a `variant` or `size` to the component file with
|
|
25
|
+
`cva`, then use it. Do not restyle one instance with a pile of classes.
|
|
26
|
+
- A raw `<div onClick>` or `<span role="button">` gets none of Radix's keyboard and ARIA handling.
|
|
27
|
+
Use `Button`, `Toggle` or the matching primitive.
|
|
28
|
+
- Merge classes with `cn()` from `@/lib/utils`, never string concatenation.
|
|
29
|
+
- Forms take a visible `<Label htmlFor>` on every input; a placeholder is not a label.
|
|
30
|
+
|
|
31
|
+
## Lint
|
|
32
|
+
|
|
33
|
+
`@shadcn/lint` runs in ESLint/Oxlint and fails the build. It reads the app's components, variants and
|
|
34
|
+
theme, and every error names what to use instead. Fix the cause the message points at:
|
|
35
|
+
|
|
36
|
+
- an arbitrary value (`w-[313px]`, `bg-[#2F5D62]`) → a spacing step or a theme token
|
|
37
|
+
- a raw palette class (`bg-blue-500`, `text-gray-400`) → a semantic token (`bg-primary`,
|
|
38
|
+
`text-muted-foreground`)
|
|
39
|
+
- a missing variant → add it to the component, then use it
|
|
40
|
+
|
|
41
|
+
Never silence a rule with a disable comment to get past it.
|
|
42
|
+
|
|
43
|
+
## Dates — always format before rendering
|
|
44
|
+
|
|
45
|
+
The generated clients run `transformDates`, which revives **fully-zoned ISO-8601 instants** —
|
|
46
|
+
`2026-03-14T08:12:00Z`, `2026-03-14T08:12:00.000+01:00` — into `Date` objects and touches nothing
|
|
47
|
+
else. A bare `2026-03-14`, a zoneless `2026-03-14T08:12:00` and an impossible `2026-02-31T00:00:00Z`
|
|
48
|
+
all stay the strings the server sent. So a field's runtime type follows the VALUE, not the schema:
|
|
49
|
+
one column can arrive as a `Date` from one row and a string from the next.
|
|
50
|
+
|
|
51
|
+
Two consequences, and both compile:
|
|
52
|
+
|
|
53
|
+
- **A string method on one white-screens the page.** `row.createdAt.split('T')[0]` type-checks
|
|
54
|
+
against nothing useful and blows up at runtime. There is no string to slice.
|
|
55
|
+
- **A raw `Date` dropped into JSX crashes the route.** `<span>{row.createdAt}</span>`, a table cell,
|
|
56
|
+
a `<Badge>` — React throws `Objects are not valid as a React child (found: [object Date])` and the
|
|
57
|
+
page falls into its error boundary. Nothing catches it before the screen is white, which makes it
|
|
58
|
+
the most common broken page in a build.
|
|
59
|
+
|
|
60
|
+
**Format with dayjs.** It takes either a `Date` or a string. Never `toLocaleDateString`, `date-fns`
|
|
61
|
+
or `luxon`.
|
|
62
|
+
|
|
63
|
+
```tsx
|
|
64
|
+
import dayjs from 'dayjs'
|
|
65
|
+
|
|
66
|
+
<span>{dayjs(row.dueOn).format('D MMM YYYY')}</span>
|
|
67
|
+
<span>{dayjs(row.createdAt).format('D MMM YYYY, HH:mm')}</span>
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
A relative "2 days ago" via dayjs `relativeTime` is fine. Coercing instead of formatting
|
|
71
|
+
(`` `${d}` ``, `String(d)`, `d + ''`) does not crash but prints
|
|
72
|
+
`Mon Jun 15 2026 02:00:00 GMT+0200` — a different bug, equally wrong.
|
|
73
|
+
|
|
74
|
+
Date **inputs** are the shadcn `Calendar` inside a `Popover` (the date-picker recipe), never a raw
|
|
75
|
+
`<input type="date">`. A calendar is not a schedule: there is no week/time-grid component, so a
|
|
76
|
+
diary, rota, timetable or booking week is a grid you build. `Calendar` with a custom day cell IS
|
|
77
|
+
right for "a few things on each day of a month", like a content calendar or a holiday planner.
|
|
78
|
+
|
|
79
|
+
All of this applies to stub and fixture dates exactly as it does to real data.
|
|
80
|
+
|
|
81
|
+
## RTL-safe styles
|
|
82
|
+
|
|
83
|
+
Write layout classes flow-relative so the UI works in both LTR and RTL languages. Pikku's i18n ships
|
|
84
|
+
Arabic, Hebrew, Farsi and Urdu support, and a physical margin is what breaks under it.
|
|
85
|
+
|
|
86
|
+
| Avoid | Use instead |
|
|
87
|
+
| ------------------------ | --------------------------- |
|
|
88
|
+
| `ml-*`, `mr-*` | `ms-*`, `me-*` |
|
|
89
|
+
| `pl-*`, `pr-*` | `ps-*`, `pe-*` |
|
|
90
|
+
| `left-*`, `right-*` | `start-*`, `end-*` |
|
|
91
|
+
| `text-left`, `text-right`| `text-start`, `text-end` |
|
|
92
|
+
| `border-l`, `border-r` | `border-s`, `border-e` |
|
|
93
|
+
| `rounded-l`, `rounded-r` | `rounded-s`, `rounded-e` |
|
|
94
|
+
| `flex-row-reverse` | the `dir` attribute |
|
|
95
|
+
|
|
96
|
+
Directional icons (chevrons, arrows) flip with `rtl:rotate-180`.
|
|
97
|
+
|
|
98
|
+
## Dark mode
|
|
99
|
+
|
|
100
|
+
Colours come from semantic tokens that already change per scheme: `bg-background`,
|
|
101
|
+
`text-foreground`, `bg-card`, `border-border`, `text-muted-foreground`. They need no `dark:` prefix.
|
|
102
|
+
Use a `dark:` variant only to pick between two theme tokens, never to introduce a literal colour or a
|
|
103
|
+
palette shade for one scheme.
|
|
104
|
+
|
|
105
|
+
```tsx
|
|
106
|
+
// Correct
|
|
107
|
+
<div className="bg-background text-foreground">
|
|
108
|
+
|
|
109
|
+
// Wrong — scheme branch with hardcoded palette shades
|
|
110
|
+
<div className="bg-gray-50 dark:bg-zinc-900">
|
|
111
|
+
```
|
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: pikku-theme
|
|
3
|
+
description: >-
|
|
4
|
+
Use when giving a Pikku Tailwind + shadcn/ui app its look: picking and applying a theme with `pikku
|
|
5
|
+
ui theme list|apply`, composing brand colours, fonts, page and ink over a preset's structure, matching
|
|
6
|
+
an existing look by hand, declaring component variants in the component's `cva` definition,
|
|
7
|
+
replacing template names (`pikku validate` flags them), and keeping emails/theme.json on-brand.
|
|
8
|
+
Also the light/dark and WCAG contrast rules a theme must clear. TRIGGER when: the user asks to
|
|
9
|
+
theme, restyle, rebrand or recolour an app; the project has packages/theme (themes/*.json,
|
|
10
|
+
active.json); a screen needs a colour, font, radius or shadow; or the app still shows a template
|
|
11
|
+
name. DO NOT TRIGGER when: rendering data or RTL layout (use pikku-tailwind),
|
|
12
|
+
writing email templates (use pikku-emails), or accessibility beyond colour (use pikku-a11y).
|
|
13
|
+
installGroups: [client]
|
|
14
|
+
---
|
|
15
|
+
|
|
16
|
+
# Theming a Pikku Tailwind app
|
|
17
|
+
|
|
18
|
+
The look of the app lives in one package, `packages/theme`, and the CLI owns it. Do not hand-edit
|
|
19
|
+
the theme JSON or the generated `theme.css` to change the look; run `pikku ui theme apply`. Every
|
|
20
|
+
colour in app code then comes from the theme, never a literal.
|
|
21
|
+
|
|
22
|
+
## The theme package
|
|
23
|
+
|
|
24
|
+
```
|
|
25
|
+
packages/theme/
|
|
26
|
+
themes/
|
|
27
|
+
default.json # one file per theme: { name, description, brand, structure }
|
|
28
|
+
<id>.json # written by `pikku ui theme apply`
|
|
29
|
+
active.json # { "id": "<id>" } — which theme the app renders
|
|
30
|
+
base.json # optional: spacing / radius / fontSizes scales shared by every theme
|
|
31
|
+
theme.css # GENERATED on every apply/switch — never edit
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
The app imports `theme.css` once, after Tailwind. It holds the shadcn tokens as CSS variables in
|
|
35
|
+
OKLCH, a `:root` block for light and a `.dark` block for dark, and the font variables. Tailwind's
|
|
36
|
+
`@theme inline` maps those variables to utilities, so `bg-primary`, `text-muted-foreground` and
|
|
37
|
+
`rounded-lg` follow the active theme with no JavaScript.
|
|
38
|
+
|
|
39
|
+
A theme is ONE set of tokens in two halves:
|
|
40
|
+
|
|
41
|
+
- **brand** — `colors` (`primary`, `secondary`, `accent`, as hex) and `fonts` (`heading`, `body`,
|
|
42
|
+
optional `mono`, each a Google Fonts family). This is what makes two apps look different.
|
|
43
|
+
- **structure** — the depth: `radius` (the base for `rounded-*`), `shadows` (`xs`..`xl`), `density`
|
|
44
|
+
(the spacing scale), `page` (light page colour), `ink` (light text colour), `defaultColorScheme`,
|
|
45
|
+
and an optional `darkSurface` ramp. This is what makes styles read as genuinely different rather
|
|
46
|
+
than recoloured.
|
|
47
|
+
|
|
48
|
+
`pikku ui theme apply` generates the whole token set from those inputs: each brand colour becomes
|
|
49
|
+
`--primary`, `--secondary`, `--accent` with a matching `-foreground` picked for contrast, the neutral
|
|
50
|
+
ramp (`--background`, `--card`, `--muted`, `--border`, `--input`, `--ring`) is tinted toward the
|
|
51
|
+
primary's hue unless `darkSurface` is given, and `--muted-foreground` is darkened in light mode so it
|
|
52
|
+
clears AA. The frontend writes plain `<Card>` and `<Button>` and inherits all of it.
|
|
53
|
+
|
|
54
|
+
## Presets, structures and brands
|
|
55
|
+
|
|
56
|
+
A **preset** pairs a brand and a structure under one id and tags the kinds of app it suits.
|
|
57
|
+
`pikku ui theme list` prints the themes on disk, the active id, the listed presets (with `suits` tags)
|
|
58
|
+
and their structures. The full set, including the presets `list` hides but `apply` still accepts,
|
|
59
|
+
is in `references/presets.md`.
|
|
60
|
+
|
|
61
|
+
Choosing:
|
|
62
|
+
|
|
63
|
+
1. **Default to light.** A near-black UI flattens the palette, the depth and the type scale at
|
|
64
|
+
once and is the most common "this looks cheap" tell. Pick a dark-first preset (`terminal`,
|
|
65
|
+
`blueprint`, `neon`, `matrix`) only when the domain asks for it: developer and terminal tools,
|
|
66
|
+
code and DevOps, media, music, observability.
|
|
67
|
+
2. **Tool or experience?** An app opened because a job requires it (CRM, booking, admin, finance)
|
|
68
|
+
takes a workplace preset. An app someone opens because they want to (a story, a journal, a
|
|
69
|
+
meditation or kids' app, a recipe box) takes a character preset (`storybook`, `stillness`),
|
|
70
|
+
which carries a warm page colour and a reading body face. A story on `monopro` or `linear`
|
|
71
|
+
comes out looking like a changelog, and later styling does not recover it.
|
|
72
|
+
3. **Lean bold.** The structure cascades to every component, so this one choice decides the look.
|
|
73
|
+
4. **Compose the app's own look on top.** Presets are starting points, not a menu. Take the preset
|
|
74
|
+
whose STRUCTURE carries the right depth, then set colours, fonts, page and ink from what the app
|
|
75
|
+
actually is.
|
|
76
|
+
|
|
77
|
+
## `pikku ui theme apply`
|
|
78
|
+
|
|
79
|
+
```bash
|
|
80
|
+
pikku ui theme list
|
|
81
|
+
pikku ui theme apply --preset breeze
|
|
82
|
+
pikku ui theme apply --preset storybook \
|
|
83
|
+
--primary '#2F5D62' --secondary '#A7774B' --accent '#D9A441' \
|
|
84
|
+
--font-heading 'Young Serif' --font-body 'Literata' \
|
|
85
|
+
--page '#FBF5EA' --ink '#2A2118'
|
|
86
|
+
pikku ui theme apply --preset quorum --structure monopro
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
| Flag | Sets |
|
|
90
|
+
| --- | --- |
|
|
91
|
+
| `--preset <id>` | starting brand + structure; the theme is written as `themes/<id>.json` (no preset → `custom`, based on the first preset) |
|
|
92
|
+
| `--structure <id>` | swap in another preset's structure (depth) under this brand |
|
|
93
|
+
| `--primary`, `--secondary`, `--accent` | brand colours, hex |
|
|
94
|
+
| `--font-heading`, `--font-body` | Google Fonts families |
|
|
95
|
+
| `--page` | light-mode page colour (`structure.page`) — paper, linen, bone instead of white |
|
|
96
|
+
| `--ink` | light-mode text colour (`structure.ink`) |
|
|
97
|
+
|
|
98
|
+
Flags are flat scalars, one value each; there is no nested `--colors` object. One call writes the
|
|
99
|
+
theme, makes it active, regenerates `themes/index.ts` (so the running app reloads) and re-brands
|
|
100
|
+
`emails/theme.json`. There is no separate activate step. Re-running with the same preset overwrites
|
|
101
|
+
that theme file.
|
|
102
|
+
|
|
103
|
+
Fine-grained structure tweaks the flags do not cover (a shadow scale, the radius base) are edits to
|
|
104
|
+
the active `themes/<id>.json` `structure`, followed by `pikku ui theme apply` to regenerate
|
|
105
|
+
`theme.css`; keep them in the theme, not in components.
|
|
106
|
+
|
|
107
|
+
## Colour rule
|
|
108
|
+
|
|
109
|
+
Every colour in app code comes from the theme: a semantic utility (`bg-primary`, `text-foreground`,
|
|
110
|
+
`bg-muted`, `border-border`, `text-destructive`) or a token your theme exports. No hex, `rgb()`,
|
|
111
|
+
`hsl()`, named CSS colour, arbitrary value (`bg-[#2F5D62]`), raw palette class (`bg-blue-500`),
|
|
112
|
+
ad-hoc alpha or one-scheme shade in components or CSS. Gradients, tinted borders and shadows follow
|
|
113
|
+
the same rule: define them as tokens in the theme and reference them. `@shadcn/lint` enforces this
|
|
114
|
+
and names the token to use instead. Before finishing UI work, run it and scan the changed files for
|
|
115
|
+
colour literals.
|
|
116
|
+
|
|
117
|
+
## Light, dark and contrast
|
|
118
|
+
|
|
119
|
+
- Always set `structure.defaultColorScheme`. It decides which block the page boots in; a theme
|
|
120
|
+
without it boots dark.
|
|
121
|
+
- A dark-first structure ships an explicit `darkSurface` ramp (lightest text to deepest background).
|
|
122
|
+
Without one, the dark ramp is tinted toward the primary's hue; a near-grey primary stays neutral.
|
|
123
|
+
- Semantic tokens already switch per scheme, so most classes need no `dark:` prefix. Use `dark:` only
|
|
124
|
+
to choose between theme tokens. Never hardcode a shade for one scheme; see pikku-tailwind.
|
|
125
|
+
- **Text clears 4.5:1 (WCAG AA) on every surface it can land on**, in both schemes. Verify, do not
|
|
126
|
+
estimate. If you need a colour dimmer than the dimmest text token, change the layout.
|
|
127
|
+
- **Control boundaries clear 3:1** (WCAG 1.4.11): input borders, toggles, focus rings. A
|
|
128
|
+
translucent fill cannot reach 3:1 on a dark surface; the border carries it.
|
|
129
|
+
- **Two tiers.** Text-tier colours (words, links, status labels) must clear 4.5:1. Graphic-tier
|
|
130
|
+
colours (dots, bars, chart series, icons) need 3:1 and may be more chromatic, but never carry
|
|
131
|
+
words. On white no amber that still reads as amber clears 4.5:1, so light-mode warnings are
|
|
132
|
+
carried by a tinted fill with normal text, and the amber is only the mark.
|
|
133
|
+
- **Accent means state.** The accent marks selection, current item, active state, and means the
|
|
134
|
+
same thing in both schemes. On an element regardless of its state, it is decoration.
|
|
135
|
+
- Every `-foreground` token is generated from its surface, so a filled button gets a readable
|
|
136
|
+
label. The page/ink you pass with `--page`/`--ink` must clear 4.5:1 against each other, and the
|
|
137
|
+
primary must still read on the page.
|
|
138
|
+
- One status vocabulary: red, amber/yellow, green, plus a neutral. Do not add a second spelling of
|
|
139
|
+
a colour that already exists.
|
|
140
|
+
|
|
141
|
+
## Custom variants and component metadata
|
|
142
|
+
|
|
143
|
+
Read a component's `cva` definition in `src/components/ui/` for the variants and sizes it accepts before guessing a `variant` or `size` value.
|
|
144
|
+
|
|
145
|
+
A custom variant is added where the component defines its variants, in the component file:
|
|
146
|
+
|
|
147
|
+
```js
|
|
148
|
+
const buttonVariants = cva('...', {
|
|
149
|
+
variants: {
|
|
150
|
+
variant: {
|
|
151
|
+
default: 'bg-primary text-primary-foreground hover:bg-primary/90',
|
|
152
|
+
brand: 'bg-accent text-accent-foreground hover:bg-accent/90',
|
|
153
|
+
},
|
|
154
|
+
},
|
|
155
|
+
})
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
`@shadcn/lint` then accepts `variant="brand"` and its error messages suggest it. Use theme tokens only inside a variant. Add it
|
|
159
|
+
once and use it; do not restyle one button inline.
|
|
160
|
+
|
|
161
|
+
## Names and emails
|
|
162
|
+
|
|
163
|
+
The starter templates ship with placeholder names. Replace them before anything else ships.
|
|
164
|
+
`pikku validate` reports each place still using a template's name (the `placeholder-brand` checks):
|
|
165
|
+
`app__name` in the app's `messages/en.json` (the wordmark in the shell and auth screens), the
|
|
166
|
+
`title` / `og:title` / `og:site_name` meta in `src/app-meta.ts` or `__root.tsx`, and `appName` in
|
|
167
|
+
`emails/theme.json`. Update other app-identity strings (tagline, description) at the same time.
|
|
168
|
+
|
|
169
|
+
**Emails.** `pikku ui theme apply` rewrites `emails/theme.json`
|
|
170
|
+
from the applied theme: neutrals from the scheme (or the `darkSurface` ramp for a dark theme),
|
|
171
|
+
`accent`, `button` from the primary, `buttonText` black or white by luminance, `fonts.body` from
|
|
172
|
+
the brand. It merges, so `appName` and any project keys survive. It cannot know the app's name: set
|
|
173
|
+
`appName` yourself right after the first apply, because confirm-address and reset-password emails
|
|
174
|
+
send from the first signup. Then read `emails/locales/en.json` and fix any line that names the
|
|
175
|
+
template. `pikku emails catalog` lists ready-made emails (invitation, magic-link, password-reset,
|
|
176
|
+
receipt, welcome) and `pikku emails add <name>` copies one in; they use the same theme keys. See
|
|
177
|
+
pikku-emails.
|
|
178
|
+
|
|
179
|
+
## Matching an existing design
|
|
180
|
+
|
|
181
|
+
When the user names a live site or a design-tokens file, read its colours, fonts and depth yourself
|
|
182
|
+
and pass them to `pikku ui theme apply`. The recipe is in `references/from-a-reference.md`.
|
|
183
|
+
|
|
184
|
+
## Hosted only
|
|
185
|
+
|
|
186
|
+
Interactive theme pickers, design extraction, stock images, site crawling, favicon generation, AI-generated logos and
|
|
187
|
+
agent-driven build flows are not part of the CLI; a hosting platform may offer them.
|