saasaloy 0.5.2 → 0.6.0
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 +7 -7
- package/dist/index.js +637 -522
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/schemas/registry-item.schema.json +31 -1
- package/templates/base/AGENTS.md +180 -53
- package/templates/base/README.md +20 -8
- package/templates/base/_agents/skills/saasaloy-design/SKILL.md +26 -15
- package/templates/base/_agents/skills/saasaloy-env/SKILL.md +130 -0
- package/templates/base/_agents/skills/saasaloy-landing-copy/SKILL.md +63 -46
- package/templates/base/_agents/skills/saasaloy-setup/SKILL.md +1 -1
- package/templates/base/_gitignore +0 -3
- package/templates/base/_saasaloy-base.json +8 -1
- package/templates/base/{DESIGN.md → apps/web/DESIGN.md} +36 -12
- package/templates/base/apps/web/astro.config.mjs +36 -6
- package/templates/base/apps/web/package.json +2 -1
- package/templates/base/apps/web/src/env.ts +41 -0
- package/templates/base/apps/web/src/layouts/Layout.astro +39 -11
- package/templates/base/apps/web/src/pages/index.astro +33 -24
- package/templates/base/apps/web/src/pages/privacy.astro +10 -2
- package/templates/base/apps/web/src/pages/terms.astro +10 -2
- package/templates/base/oxlint.config.mjs +91 -4
- package/templates/base/package.json +7 -3
- package/templates/base/packages/env/_env.example +54 -0
- package/templates/base/packages/env/package.json +35 -0
- package/templates/base/packages/env/scripts/env-setup.ts +87 -0
- package/templates/base/packages/env/src/check.test.ts +41 -0
- package/templates/base/packages/env/src/check.ts +50 -0
- package/templates/base/packages/env/src/define.test.ts +206 -0
- package/templates/base/packages/env/src/define.ts +315 -0
- package/templates/base/packages/env/src/deploy.test.ts +438 -0
- package/templates/base/packages/env/src/deploy.ts +518 -0
- package/templates/base/packages/env/src/dotenv.test.ts +179 -0
- package/templates/base/packages/env/src/dotenv.ts +173 -0
- package/templates/base/packages/env/src/env-file.ts +51 -0
- package/templates/base/packages/env/src/error.ts +38 -0
- package/templates/base/packages/env/src/example.test.ts +86 -0
- package/templates/base/packages/env/src/example.ts +90 -0
- package/templates/base/packages/env/src/index.ts +15 -0
- package/templates/base/packages/env/src/schema.ts +63 -0
- package/templates/base/packages/env/src/services.test.ts +83 -0
- package/templates/base/packages/env/src/services.ts +110 -0
- package/templates/base/packages/env/src/setup.test.ts +254 -0
- package/templates/base/packages/env/src/setup.ts +177 -0
- package/templates/base/packages/env/src/source.ts +20 -0
- package/templates/base/packages/env/src/sources.test.ts +137 -0
- package/templates/base/packages/env/src/sources.ts +93 -0
- package/templates/base/packages/env/src/zod.ts +8 -0
- package/templates/base/packages/env/tsconfig.json +13 -0
- package/templates/base/packages/tsconfig/base.json +7 -0
- package/templates/base/packages/ui/components.json +1 -1
- package/templates/base/packages/ui/package.json +1 -0
- package/templates/base/packages/ui/src/blocks/audience.tsx +73 -0
- package/templates/base/packages/ui/src/blocks/cta.tsx +41 -30
- package/templates/base/packages/ui/src/blocks/error-state.tsx +2 -0
- package/templates/base/packages/ui/src/blocks/faq.tsx +64 -36
- package/templates/base/packages/ui/src/blocks/feature-grid.tsx +48 -146
- package/templates/base/packages/ui/src/blocks/footer.tsx +129 -70
- package/templates/base/packages/ui/src/blocks/hero.tsx +183 -26
- package/templates/base/packages/ui/src/blocks/how-it-works.tsx +75 -0
- package/templates/base/packages/ui/src/blocks/logo-cloud.tsx +118 -0
- package/templates/base/packages/ui/src/blocks/navbar.tsx +204 -88
- package/templates/base/packages/ui/src/blocks/pain-points.tsx +131 -0
- package/templates/base/packages/ui/src/blocks/pricing-table.tsx +112 -94
- package/templates/base/packages/ui/src/blocks/showcase.tsx +185 -0
- package/templates/base/packages/ui/src/blocks/testimonials.tsx +85 -0
- package/templates/base/packages/ui/src/blocks/theme-toggle.tsx +16 -12
- package/templates/base/packages/ui/src/components/badge.tsx +1 -1
- package/templates/base/packages/ui/src/components/button.tsx +2 -2
- package/templates/base/packages/ui/src/components/tabs.tsx +1 -1
- package/templates/base/packages/ui/src/content/landing.ts +590 -125
- package/templates/base/packages/ui/src/index.ts +6 -2
- package/templates/base/packages/ui/src/lib/external-link.ts +36 -0
- package/templates/base/packages/ui/src/lib/icons.ts +131 -0
- package/templates/base/packages/ui/src/lib/inline-code.ts +29 -0
- package/templates/base/packages/ui/src/lib/theme.ts +140 -56
- package/templates/base/packages/ui/src/styles/globals.css +47 -125
- package/templates/base/packages/ui/src/styles/web.css +359 -0
- package/templates/base/pnpm-workspace.yaml +0 -6
- package/templates/base/saasaloy.json +1 -0
- package/templates/base/stylelint.config.js +3 -3
- package/templates/base/turbo.json +13 -0
- package/templates/base/vitest.config.ts +21 -0
package/package.json
CHANGED
|
@@ -117,9 +117,39 @@
|
|
|
117
117
|
"type": "string"
|
|
118
118
|
}
|
|
119
119
|
},
|
|
120
|
+
"envServices": {
|
|
121
|
+
"type": "object",
|
|
122
|
+
"description": "Which services read each of this module's `envVars` — a service being a workspace that reads a `.env` (`api`, `web`, `admin`), or `deploy`, the Cloudflare credentials `pnpm deploy:production` loads with no file. `default` covers every key the module declares; a key named alongside it overrides that list for itself. `saasaloy add` groups the key list in `packages/env/.env.example` under a `# @services` line per distinct set. Omit it and the `PUBLIC_` prefix rule decides instead, with a warning naming the module: a prefixed key goes to every client service, everything else to the api.",
|
|
123
|
+
"properties": {
|
|
124
|
+
"default": {
|
|
125
|
+
"type": "array",
|
|
126
|
+
"uniqueItems": true,
|
|
127
|
+
"minItems": 1,
|
|
128
|
+
"items": {
|
|
129
|
+
"type": "string",
|
|
130
|
+
"enum": ["api", "web", "admin", "deploy"]
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
},
|
|
134
|
+
"propertyNames": {
|
|
135
|
+
"pattern": "^(default|[A-Z][A-Z0-9_]*)$"
|
|
136
|
+
},
|
|
137
|
+
"additionalProperties": {
|
|
138
|
+
"type": "array",
|
|
139
|
+
"uniqueItems": true,
|
|
140
|
+
"minItems": 1,
|
|
141
|
+
"items": { "type": "string", "enum": ["api", "web", "admin", "deploy"] }
|
|
142
|
+
}
|
|
143
|
+
},
|
|
144
|
+
"envOptional": {
|
|
145
|
+
"type": "array",
|
|
146
|
+
"description": "Keys from `envVars` that may stay empty. `saasaloy add` writes a `# Blank on purpose` line above each one in `packages/env/.env.example`, which is what stops `pnpm env:setup` failing on a key the capability's schema gives a default or marks optional. Every name here must also appear in `envVars`.",
|
|
147
|
+
"uniqueItems": true,
|
|
148
|
+
"items": { "type": "string", "pattern": "^[A-Z][A-Z0-9_]*$" }
|
|
149
|
+
},
|
|
120
150
|
"devVars": {
|
|
121
151
|
"type": "object",
|
|
122
|
-
"description": "Local-development values for a subset of `envVars`: NAME → the value `saasaloy add` pre-fills into the generated `
|
|
152
|
+
"description": "Local-development values for a subset of `envVars`: NAME → the value `saasaloy add` pre-fills into the generated `packages/env/.env.example`. Only for a value that is the same on every machine and is not a secret (a loopback URL, a fixed port). Every key here must also appear in `envVars`; a key absent here is written with an empty value for the owner to fill in.",
|
|
123
153
|
"propertyNames": {
|
|
124
154
|
"pattern": "^[A-Z][A-Z0-9_]*$"
|
|
125
155
|
},
|
package/templates/base/AGENTS.md
CHANGED
|
@@ -10,9 +10,6 @@ This is a **pnpm workspace monorepo** managed by **Turborepo**.
|
|
|
10
10
|
- **Root**: Configuration files, shared tooling
|
|
11
11
|
- **`apps/*`**: Applications (Next.js, Astro apps - currently empty, will be added)
|
|
12
12
|
- **`packages/*`**: Shared packages
|
|
13
|
-
- **`infra`**: Centralized Cloudflare deployment (Pulumi), if the `infra` capability is installed —
|
|
14
|
-
a root-level workspace, not nested under `apps/*` or `packages/*` (it's neither an app nor a
|
|
15
|
-
shared package)
|
|
16
13
|
|
|
17
14
|
### Dev Port Map
|
|
18
15
|
|
|
@@ -25,7 +22,7 @@ drifting port turns into an unexplained CORS rejection.
|
|
|
25
22
|
| `apps/web` (Astro) | 3000 | `http://localhost:3000` | `apps/web/astro.config.mjs` (`server.port`) |
|
|
26
23
|
| `apps/admin` (TanStack Start) | 3001 | `http://localhost:3001` | `apps/admin/vite.config.ts` (`server.port`, `strictPort`) |
|
|
27
24
|
| `apps/api` (Hono on Workers) | 4000 | `http://localhost:4000` | `apps/api/wrangler.jsonc` (`dev.port`) |
|
|
28
|
-
| Postgres (local, `database-postgres`) | 5432 | `postgres://postgres:postgres@127.0.0.1:5432/app` | `DATABASE_URL` in
|
|
25
|
+
| Postgres (local, `database-postgres`) | 5432 | `postgres://postgres:postgres@127.0.0.1:5432/app` | `DATABASE_URL` in `packages/env/.env.example` |
|
|
29
26
|
|
|
30
27
|
The `apps/admin` and `apps/api` rows apply once you run `saasaloy add admin` or
|
|
31
28
|
`saasaloy add api`.
|
|
@@ -36,8 +33,9 @@ Rules for a new app or service:
|
|
|
36
33
|
from 4000. Add a row to this table in the same change.
|
|
37
34
|
- **Set `strictPort` (or the framework's equivalent).** A busy port must fail loudly. A
|
|
38
35
|
silent `+1` moves the app to an origin nothing allows.
|
|
39
|
-
- **Name the origin, don't infer it.** A browser-side caller reads `PUBLIC_API_URL`
|
|
40
|
-
|
|
36
|
+
- **Name the origin, don't infer it.** A browser-side caller reads `PUBLIC_API_URL`
|
|
37
|
+
through `webEnv()` or `adminEnv()`, with no fallback — an unset key fails the build
|
|
38
|
+
naming the key. A server-side caller reads its own key through `apiEnv(c.env)`.
|
|
41
39
|
- **Update both allow-lists when you add a browser origin**: `DEV_ORIGINS` in
|
|
42
40
|
`apps/api/src/index.ts` and `DEV_ORIGINS` in `packages/auth/src/auth.ts`.
|
|
43
41
|
|
|
@@ -90,16 +88,70 @@ without one is silently skipped by `turbo run clean` and leaves stale build outp
|
|
|
90
88
|
and expecting it to mount: nothing globs that folder, and the entry file's `AppType` is
|
|
91
89
|
what the typed `hc` client reads.
|
|
92
90
|
|
|
93
|
-
### The `@repo/
|
|
91
|
+
### The `@repo/env` Environment Layer
|
|
92
|
+
|
|
93
|
+
`packages/env` owns every environment value this project reads. One tracked key list, one
|
|
94
|
+
command that distributes it, one typed gate in front of the capabilities.
|
|
95
|
+
|
|
96
|
+
- **`packages/env/.env.example` is the key list**, and it is the only example file. It is
|
|
97
|
+
tracked, and `saasaloy add` regenerates it from the installed modules' own descriptions.
|
|
98
|
+
A value you type in it survives that, and nothing is ever removed.
|
|
99
|
+
- **`packages/env/.env` holds the values**, and it is gitignored. `saasaloy env` prompts
|
|
100
|
+
for each unset key and writes it.
|
|
101
|
+
- **`pnpm env:setup` writes every service's own `.env`** from those two files. A *service*
|
|
102
|
+
is a workspace that reads a `.env` — `api`, `web`, `admin` — and
|
|
103
|
+
`packages/env/src/services.ts` is the table. The `deploy` row has no file: it holds the
|
|
104
|
+
Cloudflare credentials a deploy loads in memory.
|
|
105
|
+
- **`pnpm deploy:production` deploys every Worker.** It checks every service's production
|
|
106
|
+
values first, then runs `turbo run deploy`, where each deployable workspace runs
|
|
107
|
+
`env-deploy <service>`: build with the production values in the environment, then
|
|
108
|
+
`wrangler deploy --secrets-file`. The `local` source reads production values from the
|
|
109
|
+
gitignored `packages/env/.env.prod`. Nothing writes a production value under `apps/`.
|
|
110
|
+
- **`createEnv` is the gate.** `apiEnv(c.env)` on the server, `webEnv()` in a frontend.
|
|
111
|
+
It validates on the first call and throws once naming every failing key, its module and
|
|
112
|
+
what the key is for.
|
|
113
|
+
- **A `client` key must start with `PUBLIC_`**, and the type system enforces it. Client
|
|
114
|
+
code reading a server key throws by name.
|
|
115
|
+
- **There is no `.dev.vars`.** Every runtime here reads `.env`. A leftover `.dev.vars`
|
|
116
|
+
hides `.env` from wrangler without warning, so `saasaloy doctor` reports one and
|
|
117
|
+
`pnpm env:setup` carries its values across and deletes it.
|
|
118
|
+
- **Never read `process.env`** in a file a Worker imports. Node tooling reads it freely.
|
|
119
|
+
|
|
120
|
+
The `saasaloy-env` skill is the runbook: adding a key, the section rule, the server/client
|
|
121
|
+
split, blank-on-purpose keys, and the offline rule.
|
|
94
122
|
|
|
95
|
-
|
|
96
|
-
the `cn()` helper, the vendored [shadcn](https://ui.shadcn.com) primitives in
|
|
97
|
-
`src/components/`, the marketing **blocks** in `src/blocks/`, the store-bound
|
|
98
|
-
**containers** in `src/containers/`, and the landing page's copy in `src/content/`.
|
|
99
|
-
`apps/web` pulls the theme in once, through the shared layout that imports
|
|
100
|
-
`@repo/ui/globals.css`.
|
|
123
|
+
### The `@repo/ui` Design Layer
|
|
101
124
|
|
|
102
|
-
`
|
|
125
|
+
`packages/ui` owns the design layer: the Tailwind 4 theme in `src/styles/`, the `cn()`
|
|
126
|
+
helper, the vendored [shadcn](https://ui.shadcn.com) primitives in `src/components/`, the
|
|
127
|
+
marketing **blocks** in `src/blocks/`, the store-bound **containers** in
|
|
128
|
+
`src/containers/`, and the landing page's copy in `src/content/`.
|
|
129
|
+
|
|
130
|
+
Those layer folders are the whole top level of `src/`: `assets` (images, SVGs and icons, added when the first one arrives), `blocks`, `components`, `containers`, `content`, `lib` and `styles`. Do not add an app folder such as `src/admin/` or `src/web/` beside them. When one app needs its own files inside a layer, give that layer a child folder (`blocks/admin/`, `components/shared/`).
|
|
131
|
+
|
|
132
|
+
The theme is one shared file plus one theme file per app:
|
|
133
|
+
|
|
134
|
+
- **`src/styles/globals.css`** is the shared layer: the Tailwind imports, the `@source`
|
|
135
|
+
globs and the base layer. It holds no token values, and no app imports it directly.
|
|
136
|
+
- **`src/styles/web.css`** is the web app's theme: the `dark` custom variant, its `:root`
|
|
137
|
+
and `.dark` token values, its `@theme inline` colour mapping and its radius scale. It
|
|
138
|
+
imports `globals.css`, and `apps/web` pulls it in once, through the shared layout that
|
|
139
|
+
imports `@repo/ui/styles/web.css`. `components.json` points here, so a shadcn preset
|
|
140
|
+
lands here. The landing page's layout, type and motion utilities sit at the end of the
|
|
141
|
+
file, after the colour tokens, and a preset leaves them alone.
|
|
142
|
+
- **`src/styles/admin.css`** is the admin app's theme, in the same shape, written by
|
|
143
|
+
`saasaloy add admin`.
|
|
144
|
+
|
|
145
|
+
Each theme file repeats the `dark` variant and the `@theme inline` mapping on purpose.
|
|
146
|
+
shadcn merges a preset in place only when the target file itself holds them, and it does
|
|
147
|
+
not look through an `@import`. Move either one into `globals.css` and a preset appends a
|
|
148
|
+
duplicate to `web.css` instead.
|
|
149
|
+
|
|
150
|
+
**Each app has its own `DESIGN.md`, in its own folder.** `apps/web/DESIGN.md` records the
|
|
151
|
+
web tokens in `web.css` and the rules behind them, and fingerprints `web.css`. An app
|
|
152
|
+
that carries its own theme file gets its own `apps/<app>/DESIGN.md`, which fingerprints
|
|
153
|
+
that file. There is no `DESIGN.md` at the repository root. Read the document for the app
|
|
154
|
+
you are changing before you add or change UI.
|
|
103
155
|
|
|
104
156
|
Primitives, blocks and containers are reached by subpath — none is re-exported from the
|
|
105
157
|
package root, so importing one never drags in the rest. The root export is project-wide
|
|
@@ -152,7 +204,7 @@ pnpm --filter @repo/ui exec shadcn add dialog
|
|
|
152
204
|
imports and a blanket glob charges every app for every primitive. A primitive the
|
|
153
205
|
landing page or `src/blocks/` renders gets an `@source "../components/<name>.tsx";` line
|
|
154
206
|
in `globals.css`; a primitive only one app renders gets its `@source` line in that app's
|
|
155
|
-
own
|
|
207
|
+
own theme file, the way `src/styles/admin.css` takes the whole directory.
|
|
156
208
|
Nothing errors when you skip this — the classes just never reach the CSS.
|
|
157
209
|
- `style` is `base-nova` (Base UI) and is fixed at init — the CLI cannot change it later.
|
|
158
210
|
- `rsc` is `false`, so the CLI strips the `"use client"` directive for you. It means
|
|
@@ -160,8 +212,10 @@ pnpm --filter @repo/ui exec shadcn add dialog
|
|
|
160
212
|
|
|
161
213
|
Primitives are source you own. Edit them in place rather than wrapping them.
|
|
162
214
|
|
|
215
|
+
**No UI kit or component library as a dependency.** When a design comes from a kit (Tailark, coss ui, a licensed dashboard kit), copy or rebuild the component into `src/components/` or `src/blocks/` as source this project owns. Do not install the kit's package, and keep no link to its repository for later updates. A copied component changes by hand, like any other file here. The shadcn CLI above is the one sanctioned way to fetch a primitive, because it writes source and leaves no runtime dependency behind.
|
|
216
|
+
|
|
163
217
|
**Swapping the whole theme for a preset.** The token set in
|
|
164
|
-
`packages/ui/src/styles/
|
|
218
|
+
`packages/ui/src/styles/web.css` is shadcn's `neutral` with `cssVariables: true`, and
|
|
165
219
|
any shadcn **`registry:style`** item is a drop-in replacement for it. The same
|
|
166
220
|
`--filter … exec` form applies — there is no separate theme command:
|
|
167
221
|
|
|
@@ -170,10 +224,11 @@ pnpm --filter @repo/ui exec shadcn add https://tweakcn.com/r/themes/modern-minim
|
|
|
170
224
|
```
|
|
171
225
|
|
|
172
226
|
The CLI merges the preset's `:root`, `.dark` and `@theme inline` blocks **into** the
|
|
173
|
-
existing ones
|
|
174
|
-
`@
|
|
175
|
-
touched.
|
|
176
|
-
|
|
227
|
+
existing ones in `web.css` and keeps its `@custom-variant dark`, so the hand-written
|
|
228
|
+
parts in `globals.css` — the `@source` rules and the `@layer base` rules — stay put, and
|
|
229
|
+
`components.json` is not touched. The preset reaches the web app only; the admin theme
|
|
230
|
+
in `admin.css` keeps its own values. It usually *extends* `@theme inline` with mappings
|
|
231
|
+
the base does not carry (fonts, tracking, shadows); that is expected.
|
|
177
232
|
|
|
178
233
|
Two places to get an item from:
|
|
179
234
|
|
|
@@ -185,19 +240,78 @@ Two places to get an item from:
|
|
|
185
240
|
Neither is special: the mechanism is the `registry:style` shape, so any URL that serves
|
|
186
241
|
one works.
|
|
187
242
|
|
|
188
|
-
**This edits a
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
overwrote. Diff the file after running the
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
which every preset keeps.
|
|
197
|
-
|
|
198
|
-
**
|
|
199
|
-
|
|
200
|
-
`
|
|
243
|
+
**This edits a file the project owns.** `web.css` is an owned base file: `saasaloy
|
|
244
|
+
update` never overwrites it, and a later template change to it reaches you only through
|
|
245
|
+
the merge plan. A swapped theme is yours to maintain, including re-applying anything you
|
|
246
|
+
had customised in `web.css` that the preset overwrote. Diff the file after running the
|
|
247
|
+
command rather than assuming.
|
|
248
|
+
|
|
249
|
+
A preset swap invalidates `apps/web/DESIGN.md`, which records the token values the old theme had. The `saasaloy-design` skill's `theme` flow runs the command above for you and re-derives the contract from the merged result, so prefer it over running the command by hand. Pick one or the other — running both applies the preset twice.
|
|
250
|
+
|
|
251
|
+
Theme switching is unaffected by any of this. It keys off the `.dark` class, which every preset keeps. The toggle has two states, light and dark: `system` is only the starting state for a visitor who never pressed it, and no press steps back to `system`. Do not give the toggle a third state or a light/dark/system menu.
|
|
252
|
+
|
|
253
|
+
**A project made before the theme split.** Older projects kept the web tokens in
|
|
254
|
+
`globals.css` and the design document at the repository root. `saasaloy update` creates
|
|
255
|
+
`packages/ui/src/styles/web.css` and lists `globals.css` and `Layout.astro` in the merge
|
|
256
|
+
plan, because the project owns both; it never moves your tokens for you. Finish the split
|
|
257
|
+
by hand, in this order:
|
|
258
|
+
|
|
259
|
+
1. Move the `@custom-variant dark` line, the `:root` block, the `.dark` block and the
|
|
260
|
+
`@theme inline` block from `globals.css` into `web.css`, replacing the template's
|
|
261
|
+
copies there, below its `@import "./globals.css";`. Keep your own values, not the
|
|
262
|
+
template's.
|
|
263
|
+
2. Add `"./styles/*": "./src/styles/*"` to the `exports` of `packages/ui/package.json`,
|
|
264
|
+
if the update did not.
|
|
265
|
+
3. In `apps/web/src/layouts/Layout.astro`, import `@repo/ui/styles/web.css` in place of
|
|
266
|
+
`@repo/ui/globals.css`.
|
|
267
|
+
4. Set `tailwind.css` in `packages/ui/components.json` to `src/styles/web.css`, if the
|
|
268
|
+
update did not.
|
|
269
|
+
5. Move `DESIGN.md` to `apps/web/DESIGN.md`, and run the `saasaloy-design` skill's
|
|
270
|
+
`update` flow so its fingerprint names `web.css`.
|
|
271
|
+
6. Run `pnpm build` and check that `globals.css` and `web.css` hold one `@theme inline`
|
|
272
|
+
and one `@custom-variant dark` between them.
|
|
273
|
+
|
|
274
|
+
With the admin app installed, `saasaloy update` also moves its theme: it creates
|
|
275
|
+
`packages/ui/src/styles/admin.css` and deletes `apps/admin/src/styles/admin.css`, or
|
|
276
|
+
keeps it with a warning when you edited it. Carry any edit across to the new file, then
|
|
277
|
+
delete the old one. The new admin app imports `@repo/ui/styles/admin.css`, so its build
|
|
278
|
+
fails until step 2 adds the `./styles/*` export. Update the base and finish step 2 before
|
|
279
|
+
you build admin, and before you run `saasaloy add admin` on a project made before the
|
|
280
|
+
split.
|
|
281
|
+
|
|
282
|
+
**Blocks — the page-level compositions.** `src/blocks/` holds the thirteen marketing
|
|
283
|
+
blocks the landing page is built from: `navbar`, `hero`, `logo-cloud`, `pain-points`,
|
|
284
|
+
`how-it-works`, `feature-grid`, `showcase`, `audience`, `testimonials`, `pricing-table`,
|
|
285
|
+
`faq`, `cta` and `footer`. `logo-cloud`, `pain-points`, `how-it-works`, `showcase`,
|
|
286
|
+
`audience` and `testimonials` are new; a project scaffolded before them gets the files
|
|
287
|
+
through `saasaloy update`, and adds each to its own `index.astro` by hand, since the page
|
|
288
|
+
is the owner's. The blocks need the landing utilities at the end of `web.css`. `web.css`
|
|
289
|
+
is an owned file, so `update` lists it in the merge plan and never adds them for you.
|
|
290
|
+
Copy the template's block, from the comment that opens "The landing page's layout" to
|
|
291
|
+
the `stylelint-enable` line, to the end of your `web.css`. A project made before the
|
|
292
|
+
theme split skips this: `update` creates its `web.css` from the template, block included.
|
|
293
|
+
|
|
294
|
+
**The section recipe.** `index.astro` composes the page from explicit imports, one line
|
|
295
|
+
per section. To remove a section, delete its line and its import, delete its file in
|
|
296
|
+
`src/blocks/`, and blank its navbar and footer labels in `landing.ts`. To reorder, move
|
|
297
|
+
the lines. Every block uses the same container (`section-container`, `-narrow`, `-wide`)
|
|
298
|
+
and spacing (`section-y`) utilities from `src/styles/web.css`, so any order keeps one
|
|
299
|
+
rhythm. A block sets no width, no section padding and no keyframes of its own; motion
|
|
300
|
+
comes from the named utilities there (`blur-in`, `beam-in`, `reveal`, `marquee`,
|
|
301
|
+
`shine-text`, `nav-float`, `hover-lift`), and every one stops under reduced motion.
|
|
302
|
+
|
|
303
|
+
**The logo recipe.** `landing.logos.items[]` is `{ id, name, mark }`. `mark` is a key in
|
|
304
|
+
`LOGO_MARKS` at the top of `blocks/logo-cloud.tsx`. Paste a customer's SVG there under a
|
|
305
|
+
kebab-case key with `fill="currentColor"`, then name the key in content. An unknown key
|
|
306
|
+
renders `name` as a text wordmark.
|
|
307
|
+
|
|
308
|
+
**The production bar.** The page holds to a rubric: no axe violation at WCAG 2.1 AA in
|
|
309
|
+
either theme, no sideways scroll from 360px up, nothing animating under reduced motion, a
|
|
310
|
+
skip link to `<main>` on every page, link-preview metadata on `/`, and exactly two islands
|
|
311
|
+
(`Navbar`, `PricingTable`). Keep each true when you change a block: a new section with
|
|
312
|
+
state is a third island and needs a reason.
|
|
313
|
+
|
|
314
|
+
The rules below are not stylistic — each one prevents a real failure:
|
|
201
315
|
|
|
202
316
|
- **One block, one file, one component export (plus its prop types).**
|
|
203
317
|
`pricing-table.tsx` exports `PricingTable` alongside `PricingTier` and
|
|
@@ -222,13 +336,13 @@ landing page is built from: `navbar`, `hero`, `feature-grid`, `pricing-table`, `
|
|
|
222
336
|
`client:load` on the page hydrates everything and throws away the reason this is a
|
|
223
337
|
static site — see `apps/web/src/pages/index.astro` for the worked example.
|
|
224
338
|
- **`theme-toggle` is the exception that proves the rule.** It sits in `src/blocks/`
|
|
225
|
-
beside the marketing blocks but is chrome, not copy: `
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
339
|
+
beside the marketing blocks but is chrome, not copy: `navbar.tsx` renders it inside the
|
|
340
|
+
bar, and it needs **no** client directive of its own despite being interactive. It has
|
|
341
|
+
no `onClick` and no state — the pre-paint inline script the shared layout emits
|
|
342
|
+
(`THEME_INIT_SCRIPT`, from `packages/ui/src/lib/theme.ts`) drives every
|
|
229
343
|
`[data-theme-toggle]` on the page through one delegated listener, and CSS picks the
|
|
230
344
|
icon off `<html data-theme>`. Move it, restyle it via its `className`, or delete the
|
|
231
|
-
one line in `
|
|
345
|
+
one line in `navbar.tsx` to drop it. Do **not** "fix" it by adding `client:*` or an
|
|
232
346
|
`onClick`, and do not paste a second copy of the boot script into a page: any document
|
|
233
347
|
that renders the block must inline that one constant in its `<head>`, or the control
|
|
234
348
|
stays hidden.
|
|
@@ -259,6 +373,8 @@ landing page is built from: `navbar`, `hero`, `feature-grid`, `pricing-table`, `
|
|
|
259
373
|
React file under `apps/web/src/components/` builds the client and passes the function
|
|
260
374
|
down. Astro serializes island props, so the page renders **that** file, never the block
|
|
261
375
|
directly.
|
|
376
|
+
- **A link that leaves the site opens a new tab.** Spread `externalLinkProps(href)` from `@repo/ui/lib/external-link` onto the anchor and render `newTabHint(href)` inside it. The href decides: an absolute URL gets `target="_blank"`, `rel="noopener noreferrer"` and a screen-reader hint, and a path or a same-page anchor stays in the tab. Do not write `target="_blank"` by hand.
|
|
377
|
+
- **A value that waits on a request shows a skeleton.** Render the `Skeleton` primitive at the size the value will take until the response arrives, never a blank gap or a spinner. Add it with `pnpm --filter @repo/ui exec shadcn add skeleton` the first time a block needs it.
|
|
262
378
|
- **A block never imports a store either.** Local state is fine; shared state is not. See
|
|
263
379
|
the containers section below.
|
|
264
380
|
- **A module's UI arrives here too, and you wire it up by hand.** `saasaloy add <module>`
|
|
@@ -328,20 +444,27 @@ record and nothing else. Follow them when you add a block or a key:
|
|
|
328
444
|
messages (`ui.pricing.perMonth`, `ui.pricing.perMonthAnnual`) — word order does not
|
|
329
445
|
survive the seam in every language.
|
|
330
446
|
5. **Only user-visible strings move.** Section `id`s and the same-page anchors pointing at
|
|
331
|
-
them are structure and stay in the block.
|
|
447
|
+
them are structure and stay in the block. A few things break the rule, all because a
|
|
332
448
|
thing rewritten *with* the copy belongs *near* the copy: the whole `tiers` array (prices
|
|
333
|
-
and `ctaHref`s included);
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
non-message data.
|
|
449
|
+
and `ctaHref`s included); every `icon`, held as a registry *name* like `"zap"` and
|
|
450
|
+
resolved to a component by `packages/ui/src/lib/icons.ts`, and every logo `mark`,
|
|
451
|
+
resolved by `blocks/logo-cloud.tsx`; every outbound link (`*Href`), which leaves the
|
|
452
|
+
page and so cannot break a section link; and the code the page quotes (terminal
|
|
453
|
+
commands, module names, file paths). A translation layer reads `id`, `icon`, `mark`,
|
|
454
|
+
every `*Href` and the quoted code as non-message data. A pair of backticks inside a
|
|
455
|
+
message renders as `<code>` through `inlineCode()` from `@repo/ui/lib/inline-code`.
|
|
456
|
+
|
|
457
|
+
**No copy that goes stale.** Do not write a version number, a release date or a count into a message. Say something general and link to where the current value lives: the hero pill says "See what's new" and links to the changelog, so it stays true across every release.
|
|
339
458
|
|
|
340
459
|
Two consequences worth knowing. Blanking a navbar or footer link's label in content drops
|
|
341
460
|
that link, which is how a removed section loses its nav entry without editing a block. And
|
|
342
461
|
the theme toggle's labels deliberately stay in `packages/ui/src/lib/theme.ts`: that file is
|
|
343
462
|
inlined verbatim into a pre-paint `<script>` and is import-free on purpose.
|
|
344
463
|
|
|
464
|
+
**The default copy is Saasaloy's own.** A fresh project's page describes Saasaloy, and
|
|
465
|
+
`siteName` in `packages/ui/src/index.ts` defaults to `"Saasaloy"`, so the brand and the
|
|
466
|
+
words agree until you replace both. The two skills below replace them in one pass.
|
|
467
|
+
|
|
345
468
|
**Making this project yours.** Three skills ship with the base (linked at
|
|
346
469
|
`.claude/skills/`, real files in `.agents/skills/`). The first two run in order:
|
|
347
470
|
|
|
@@ -356,12 +479,12 @@ inlined verbatim into a pre-paint `<script>` and is import-free on purpose.
|
|
|
356
479
|
|
|
357
480
|
The third has no place in that order, because it runs whenever the UI moves:
|
|
358
481
|
|
|
359
|
-
3. **`saasaloy-design`** keeps `DESIGN.md` true. Its `theme` flow swaps the
|
|
360
|
-
re-derives the contract; `update` re-derives after you change `
|
|
361
|
-
components; `audit` reports where the code and the contract disagree. It reads the
|
|
482
|
+
3. **`saasaloy-design`** keeps `apps/web/DESIGN.md` true. Its `theme` flow swaps the
|
|
483
|
+
preset and re-derives the contract; `update` re-derives after you change `web.css` or
|
|
484
|
+
add components; `audit` reports where the code and the contract disagree. It reads the
|
|
362
485
|
product brief when one exists and never writes it.
|
|
363
486
|
|
|
364
|
-
Invoke them rather than editing
|
|
487
|
+
Invoke them rather than editing thirteen blocks by hand — and if you do edit by hand, keep the
|
|
365
488
|
strings in the content module so the next pass finds them.
|
|
366
489
|
|
|
367
490
|
### Naming Conventions
|
|
@@ -418,13 +541,15 @@ hook (which is also how you keep hooks out of CI).
|
|
|
418
541
|
- Run type checking: `pnpm typecheck` (must pass before commits)
|
|
419
542
|
- Run linting: `pnpm lint` (see above — it reports, `pnpm lint:fix` fixes)
|
|
420
543
|
- Check formatting: `pnpm format:check`, or `pnpm format` to rewrite
|
|
421
|
-
-
|
|
544
|
+
- Run the tests: `pnpm test`. It runs `turbo run test` (each workspace's own `test` script), then `vitest run` from the root, whose `vitest.config.ts` treats every workspace under `apps/*` and `packages/*` as a project. Write new tests for Vitest: put a `*.test.ts` file beside the code, and give the workspace a `vitest.config.ts` only when it needs setup of its own. Do not add a per-workspace `test` script that calls Vitest, or the file runs twice. `packages/env` is the exception: its tests run on `node --test` through its own `test` script, and the root config leaves it out.
|
|
545
|
+
- Before you hand over UI work, run `pnpm dev`, load the pages you changed, and read the dev server's terminal output. Fix every error and warning it prints. A clean build does not prove a clean dev server.
|
|
422
546
|
|
|
423
547
|
## Boundaries
|
|
424
548
|
|
|
425
549
|
### ✅ Always Do
|
|
426
550
|
|
|
427
|
-
- Read `DESIGN.md` before writing or
|
|
551
|
+
- Read the app's `DESIGN.md` (`apps/web/DESIGN.md` for the web app) before writing or
|
|
552
|
+
changing UI
|
|
428
553
|
- Run `pnpm typecheck` before committing code changes
|
|
429
554
|
- Run `pnpm lint` and fix all errors
|
|
430
555
|
- Give every new app or package a `clean` script backed by `rimraf` (see above)
|
|
@@ -434,6 +559,7 @@ hook (which is also how you keep hooks out of CI).
|
|
|
434
559
|
### ⚠️ Ask First
|
|
435
560
|
|
|
436
561
|
- Adding new dependencies (especially to root `package.json`)
|
|
562
|
+
- Building new UI before the owner has approved a mockup. Show screenshots of the mockup first and get approval. A change after approval gets a new mockup, not a fix after the work lands
|
|
437
563
|
- Modifying Turborepo configuration (`turbo.json`)
|
|
438
564
|
- Changing TypeScript strictness settings
|
|
439
565
|
- Modifying the husky hooks (`.husky/`), `lint-staged.config.js`, or `commitlint.config.js`
|
|
@@ -447,11 +573,12 @@ hook (which is also how you keep hooks out of CI).
|
|
|
447
573
|
|
|
448
574
|
- Never use `npm` or `npx`, instead use `pnpm` & `pnpm dlx`
|
|
449
575
|
- Never use `rm -rf` in a package script — it breaks on Windows; use `rimraf`
|
|
450
|
-
- Commit secrets
|
|
576
|
+
- Commit secrets or API keys. `packages/env/.env` is gitignored; `packages/env/.env.example`
|
|
577
|
+
is tracked and holds local defaults only
|
|
451
578
|
- Modify `node_modules/` or `pnpm-lock.yaml` manually (use `pnpm install`)
|
|
452
579
|
- Remove or disable TypeScript strict mode
|
|
453
580
|
- Remove or disable the lint-staged or commitlint hooks, or commit with `--no-verify`
|
|
454
581
|
as a habit rather than an emergency
|
|
455
582
|
- Use `any` type without explicit `@ts-expect-error` or `@ts-ignore` with justification
|
|
456
|
-
- Break the workspace structure (don't move packages outside `apps
|
|
583
|
+
- Break the workspace structure (don't move packages outside `apps/*` or `packages/*`)
|
|
457
584
|
- Commit without running type checks and linting
|
package/templates/base/README.md
CHANGED
|
@@ -30,7 +30,7 @@ saasaloy list # what the registry offers, and what you already hav
|
|
|
30
30
|
|
|
31
31
|
## UI components
|
|
32
32
|
|
|
33
|
-
`DESIGN.md` records the
|
|
33
|
+
`apps/web/DESIGN.md` records the web app's design tokens and UI rules for people and agents.
|
|
34
34
|
|
|
35
35
|
`packages/ui` ships a Tailwind 4 theme, a small set of [shadcn](https://ui.shadcn.com)
|
|
36
36
|
primitives, and the marketing blocks the landing page is built from. Import primitives and
|
|
@@ -54,14 +54,26 @@ Components land in `packages/ui/src/components/` as source you own and can edit.
|
|
|
54
54
|
|
|
55
55
|
## Landing page
|
|
56
56
|
|
|
57
|
-
`apps/web/src/pages/index.astro` composes
|
|
58
|
-
`navbar`, `hero`, `feature-grid`, `pricing-table`, `faq`, `cta`, `footer`. Each is one
|
|
59
|
-
self-contained `.tsx` with its copy as in-file defaults, so editing the file is how you
|
|
60
|
-
change the page.
|
|
57
|
+
`apps/web/src/pages/index.astro` composes thirteen blocks from `packages/ui/src/blocks/`: `navbar`, `hero`, `logo-cloud`, `pain-points`, `how-it-works`, `feature-grid`, `showcase`, `audience`, `testimonials`, `pricing-table`, `faq`, `cta` and `footer`. The page ships describing Saasaloy itself, so it reads as a finished product on day one. You make it yours with two edits and no others:
|
|
61
58
|
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
59
|
+
1. **Rewrite the words** in `packages/ui/src/content/landing.ts`: every heading, link, price and icon name. The `saasaloy-landing-copy` skill drafts it from your product brief. The brand is `siteName` in `packages/ui/src/index.ts`, which `saasaloy-setup` sets.
|
|
60
|
+
2. **Choose the sections** in `index.astro`. Each section is one import and one line.
|
|
61
|
+
|
|
62
|
+
### The section recipe
|
|
63
|
+
|
|
64
|
+
- **Remove a section:** delete its line and its import in `index.astro`, then delete its file in `packages/ui/src/blocks/`. Blank its navbar and footer labels in `landing.ts` so no link points at a section that is gone.
|
|
65
|
+
- **Reorder:** move the lines. Every block uses the same container and spacing utilities, so any order keeps one rhythm.
|
|
66
|
+
- **Bring one back:** copy the block file in from a fresh `saasaloy init`, then add the import and the line.
|
|
67
|
+
|
|
68
|
+
### The logo recipe
|
|
69
|
+
|
|
70
|
+
The logo row names each logo in `landing.logos.items` as `{ id, name, mark }`. `mark` is a key in the `LOGO_MARKS` registry at the top of `packages/ui/src/blocks/logo-cloud.tsx`. To add a customer's logo, paste its SVG into that registry under a kebab-case key with `fill="currentColor"`, then put the key in the item's `mark`. A key the registry doesn't know renders the item's `name` as a text wordmark, which is what the default row does.
|
|
71
|
+
|
|
72
|
+
### What the page promises
|
|
73
|
+
|
|
74
|
+
Blocks render to static HTML by default. Only two carry a client directive: `Navbar` (`client:idle`) and `PricingTable` (`client:visible`). The FAQ and the pain points open with native `<details>`, all motion is CSS and stops under reduced motion, and the footer adds one inline script that writes the current year. Don't reach for `client:load`.
|
|
75
|
+
|
|
76
|
+
The page is checked against a written rubric: no axe violation at WCAG 2.1 AA in either theme, no sideways scroll from 360px up, a skip link on every page, and link-preview metadata on `/`. Keep those true when you change a block.
|
|
65
77
|
|
|
66
78
|
The page composes itself from explicit imports, and nothing discovers a file behind your
|
|
67
79
|
back. A module that ships UI writes its block into `packages/ui/src/blocks/` like every
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: saasaloy-design
|
|
3
|
-
description: Keep DESIGN.md true of the Saasaloy design layer. Use theme to choose and apply a registry style, update after packages/ui changes, or audit the design contract for token drift.
|
|
3
|
+
description: Keep apps/web/DESIGN.md true of the Saasaloy design layer. Use theme to choose and apply a registry style, update after packages/ui changes, or audit the design contract for token drift.
|
|
4
4
|
derived-from: "designkit (MIT), narrowed 2026-08-09"
|
|
5
5
|
---
|
|
6
6
|
|
|
@@ -8,7 +8,9 @@ derived-from: "designkit (MIT), narrowed 2026-08-09"
|
|
|
8
8
|
|
|
9
9
|
This skill records the design system that the project already uses. It writes no components and no pages. Use `uikit` for that work.
|
|
10
10
|
|
|
11
|
-
The token home is `packages/ui/src/styles/
|
|
11
|
+
The token home is `packages/ui/src/styles/web.css`. The design contract is `apps/web/DESIGN.md`. `web.css` imports `packages/ui/src/styles/globals.css`, which holds the shared Tailwind setup and no token values.
|
|
12
|
+
|
|
13
|
+
This skill covers the web app only. Another app's theme file, such as the admin module's `packages/ui/src/styles/admin.css`, is out of scope. Do not read its tokens into this contract, and do not edit it.
|
|
12
14
|
|
|
13
15
|
The grounding rule controls every mode. Every token value must exist in the project code or in the Tailwind utility that the project uses. Do not choose a nearby value because it looks cleaner.
|
|
14
16
|
|
|
@@ -24,15 +26,15 @@ If the user does not name a mode, ask which mode to use. Do not choose between a
|
|
|
24
26
|
|
|
25
27
|
| Mode | Files |
|
|
26
28
|
|------|-------|
|
|
27
|
-
| `theme` | `packages/ui/src/styles/
|
|
28
|
-
| `update` | `DESIGN.md` only |
|
|
29
|
+
| `theme` | `packages/ui/src/styles/web.css`, `apps/web/DESIGN.md`, and `docs/product-brief.md` only when the brief is absent |
|
|
30
|
+
| `update` | `apps/web/DESIGN.md` only |
|
|
29
31
|
| `audit` | none |
|
|
30
32
|
|
|
31
33
|
Do not edit `components.json` during a theme change. Do not add a dependency, script, component, page, or Tailwind class.
|
|
32
34
|
|
|
33
35
|
## Shared rules
|
|
34
36
|
|
|
35
|
-
Read `DESIGN.md`, `packages/ui/src/styles/globals.css`, and all files under `packages/ui/src/` before you derive tokens. Read the application files that use `@repo/ui` when you need to confirm usage.
|
|
37
|
+
Read `apps/web/DESIGN.md`, `packages/ui/src/styles/web.css`, `packages/ui/src/styles/globals.css`, and all files under `packages/ui/src/` before you derive tokens. Read the application files that use `@repo/ui` when you need to confirm usage.
|
|
36
38
|
|
|
37
39
|
Find the system in this order. Stop at the first match.
|
|
38
40
|
|
|
@@ -50,6 +52,15 @@ Write elevation as prose under `## Elevation & Depth`. Never add `elevation` to
|
|
|
50
52
|
|
|
51
53
|
Write motion as prose under `## Motion`. The alpha schema has no motion token group and no component transition token.
|
|
52
54
|
|
|
55
|
+
The landing page's layout, type and motion vocabulary lives at the end of `web.css`, after the colour tokens, and `DESIGN.md` must name all of it:
|
|
56
|
+
|
|
57
|
+
- The type ramp in `@theme`: `--text-display`, `--text-section-title` and `--text-lead`. Record the largest size of each fluid `clamp()` step as the token's `fontSize`, and describe the fluid range in the Typography prose.
|
|
58
|
+
- The container widths in `@theme` (`--container-page`, `--container-narrow`, `--container-wide`) and the `section-container*` and `section-y` utilities. Describe them under `## Layout`. Spacing stays omitted, because these are named widths and one section rhythm, not a spacing scale.
|
|
59
|
+
- The motion utilities: `blur-in`, `beam-in`, `reveal`, `marquee`, `shine-text`, `nav-float` and `hover-lift`. Describe each under `## Motion`, and say that each one stops under reduced motion.
|
|
60
|
+
- The decoration utility `grid-pattern`. Describe it under `## Components` with the landing blocks it decorates.
|
|
61
|
+
|
|
62
|
+
A theme preset replaces the colour tokens and leaves this vocabulary alone. After a `theme` run, check that the vocabulary survived and that `DESIGN.md` still describes it.
|
|
63
|
+
|
|
53
64
|
Use only these component token keys: `backgroundColor`, `textColor`, `typography`, `rounded`, `padding`, `size`, `height`, and `width`.
|
|
54
65
|
|
|
55
66
|
Keep the sections in this order: Overview, Colors, Typography, Layout, Elevation & Depth, Shapes, Components, Do's and Don'ts, Motion, Dark Mode.
|
|
@@ -58,24 +69,24 @@ The seed is a one-time base file. ADR 0022 means Saasaloy has no update path for
|
|
|
58
69
|
|
|
59
70
|
## Fingerprint
|
|
60
71
|
|
|
61
|
-
The fingerprint covers only `packages/ui/src/styles/
|
|
72
|
+
The fingerprint covers only `packages/ui/src/styles/web.css`. It does not cover `globals.css`, components, blocks, or other files.
|
|
62
73
|
|
|
63
74
|
Compute the fingerprint from the file bytes. Use the first 12 lowercase hexadecimal characters of SHA-256.
|
|
64
75
|
|
|
65
76
|
```sh
|
|
66
|
-
node --input-type=module -e "import { createHash } from 'node:crypto'; import { readFileSync } from 'node:fs'; process.stdout.write(createHash('sha256').update(readFileSync('packages/ui/src/styles/
|
|
77
|
+
node --input-type=module -e "import { createHash } from 'node:crypto'; import { readFileSync } from 'node:fs'; process.stdout.write(createHash('sha256').update(readFileSync('packages/ui/src/styles/web.css')).digest('hex').slice(0, 12))"
|
|
67
78
|
```
|
|
68
79
|
|
|
69
80
|
The scaffold seed ends with this stamp.
|
|
70
81
|
|
|
71
82
|
```markdown
|
|
72
|
-
_Seeded from the saasaloy base template · CLI <version> · tokens sha256:<12 hex> of packages/ui/src/styles/
|
|
83
|
+
_Seeded from the saasaloy base template · CLI <version> · tokens sha256:<12 hex> of packages/ui/src/styles/web.css_
|
|
73
84
|
```
|
|
74
85
|
|
|
75
86
|
After `theme` or `update`, replace the seed stamp with this stamp.
|
|
76
87
|
|
|
77
88
|
```markdown
|
|
78
|
-
_Updated from packages/ui on YYYY-MM-DD · tokens sha256:<12 hex> of packages/ui/src/styles/
|
|
89
|
+
_Updated from packages/ui on YYYY-MM-DD · tokens sha256:<12 hex> of packages/ui/src/styles/web.css_
|
|
79
90
|
```
|
|
80
91
|
|
|
81
92
|
Do not change the fingerprint during `audit`.
|
|
@@ -85,7 +96,7 @@ Do not change the fingerprint during `audit`.
|
|
|
85
96
|
Run the official linter after each write.
|
|
86
97
|
|
|
87
98
|
```sh
|
|
88
|
-
pnpm dlx @google/design.md lint DESIGN.md
|
|
99
|
+
pnpm dlx @google/design.md lint apps/web/DESIGN.md
|
|
89
100
|
```
|
|
90
101
|
|
|
91
102
|
The format is alpha. Read the current schema when a lint finding conflicts with this skill.
|
|
@@ -126,13 +137,13 @@ Use the package-local executable from the project root.
|
|
|
126
137
|
pnpm --filter @repo/ui exec shadcn add <registry-style-url>
|
|
127
138
|
```
|
|
128
139
|
|
|
129
|
-
Confirm that the `@source` rules
|
|
140
|
+
`components.json` points shadcn at `web.css`, so the preset merges into the `:root`, `.dark` and `@theme inline` blocks there. Confirm that `globals.css` did not change and still holds the `@source` rules and `@layer base`. Confirm that `web.css` and `globals.css` hold one `@theme inline` and one `@custom-variant dark` between them. Confirm that `components.json` did not change.
|
|
130
141
|
|
|
131
142
|
Stop and report the failure when the preset removes a required rule or changes `components.json`. Do not repair an unknown preset merge without approval.
|
|
132
143
|
|
|
133
144
|
### 4. Re-derive the contract
|
|
134
145
|
|
|
135
|
-
Run the shared extraction process against the merged `
|
|
146
|
+
Run the shared extraction process against the merged `web.css` and its usage. Update the token front matter and only the prose sections that the new tokens affect.
|
|
136
147
|
|
|
137
148
|
Use the product brief to update Overview and Do's and Don'ts. Do not add product claims that the brief does not support.
|
|
138
149
|
|
|
@@ -168,7 +179,7 @@ Audit is read-only. Do not edit or format any file.
|
|
|
168
179
|
|
|
169
180
|
### 1. Check the fingerprint offline
|
|
170
181
|
|
|
171
|
-
Read the recorded 12-character fingerprint from the final stamp. Compute the current fingerprint from `
|
|
182
|
+
Read the recorded 12-character fingerprint from the final stamp. Compute the current fingerprint from `web.css`.
|
|
172
183
|
|
|
173
184
|
- `current` means the two fingerprints match.
|
|
174
185
|
- `stale` means they differ.
|
|
@@ -182,7 +193,7 @@ Use `orphaned` for a documented token with no current source. Use `uncovered` fo
|
|
|
182
193
|
|
|
183
194
|
### 3. Lint when possible
|
|
184
195
|
|
|
185
|
-
Run `pnpm dlx @google/design.md lint DESIGN.md` when the tool and network are available. If it cannot run, state that the fingerprint check ran offline and structural lint did not run.
|
|
196
|
+
Run `pnpm dlx @google/design.md lint apps/web/DESIGN.md` when the tool and network are available. If it cannot run, state that the fingerprint check ran offline and structural lint did not run.
|
|
186
197
|
|
|
187
198
|
### 4. Report
|
|
188
199
|
|
|
@@ -192,7 +203,7 @@ Open with the file count that the audit scanned. Give one verdict for the finger
|
|
|
192
203
|
|
|
193
204
|
- Never write a component or a page. Use `uikit` for that work.
|
|
194
205
|
- Never add a token value that the project does not contain.
|
|
195
|
-
- Never change `globals.css` in `update` or `audit`.
|
|
206
|
+
- Never change `globals.css` in any mode, or `web.css` in `update` or `audit`.
|
|
196
207
|
- Never write during `audit`.
|
|
197
208
|
- Never claim that lint passed when the linter did not run.
|
|
198
209
|
- Never treat a matching fingerprint as proof that component usage has no drift.
|