saasaloy 0.1.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/dist/index.js +8455 -0
- package/dist/index.js.map +1 -0
- package/package.json +69 -0
- package/schemas/manifest.schema.json +107 -0
- package/schemas/registry-item.schema.json +336 -0
- package/schemas/saasaloy-lock.schema.json +86 -0
- package/schemas/saasaloy.schema.json +42 -0
- package/templates/base/AGENTS.md +450 -0
- package/templates/base/CLAUDE.md +1 -0
- package/templates/base/DESIGN.md +209 -0
- package/templates/base/README.md +71 -0
- package/templates/base/_agents/skills/saasaloy-design/SKILL.md +198 -0
- package/templates/base/_agents/skills/saasaloy-landing-copy/SKILL.md +395 -0
- package/templates/base/_agents/skills/saasaloy-setup/SKILL.md +273 -0
- package/templates/base/_gitignore +32 -0
- package/templates/base/_husky/commit-msg +1 -0
- package/templates/base/_husky/pre-commit +1 -0
- package/templates/base/_prettierignore +31 -0
- package/templates/base/_saasaloy-base.json +9 -0
- package/templates/base/apps/web/astro.config.mjs +61 -0
- package/templates/base/apps/web/package.json +30 -0
- package/templates/base/apps/web/public/favicon.svg +4 -0
- package/templates/base/apps/web/src/layouts/Layout.astro +52 -0
- package/templates/base/apps/web/src/pages/404.astro +24 -0
- package/templates/base/apps/web/src/pages/500.astro +33 -0
- package/templates/base/apps/web/src/pages/index.astro +61 -0
- package/templates/base/apps/web/src/pages/privacy.astro +15 -0
- package/templates/base/apps/web/src/pages/terms.astro +14 -0
- package/templates/base/apps/web/tsconfig.json +11 -0
- package/templates/base/apps/web/wrangler.jsonc +27 -0
- package/templates/base/commitlint.config.js +9 -0
- package/templates/base/lint-staged.config.js +17 -0
- package/templates/base/oxlint.config.mjs +155 -0
- package/templates/base/package.json +44 -0
- package/templates/base/packages/tsconfig/base.json +17 -0
- package/templates/base/packages/tsconfig/package.json +18 -0
- package/templates/base/packages/ui/components.json +19 -0
- package/templates/base/packages/ui/package.json +39 -0
- package/templates/base/packages/ui/src/blocks/cta.tsx +82 -0
- package/templates/base/packages/ui/src/blocks/error-state.tsx +144 -0
- package/templates/base/packages/ui/src/blocks/faq.tsx +64 -0
- package/templates/base/packages/ui/src/blocks/feature-grid.tsx +185 -0
- package/templates/base/packages/ui/src/blocks/footer.tsx +99 -0
- package/templates/base/packages/ui/src/blocks/hero.tsx +84 -0
- package/templates/base/packages/ui/src/blocks/navbar.tsx +159 -0
- package/templates/base/packages/ui/src/blocks/pricing-table.tsx +175 -0
- package/templates/base/packages/ui/src/blocks/theme-toggle.tsx +51 -0
- package/templates/base/packages/ui/src/components/accordion.tsx +78 -0
- package/templates/base/packages/ui/src/components/badge.tsx +53 -0
- package/templates/base/packages/ui/src/components/button.tsx +59 -0
- package/templates/base/packages/ui/src/components/card.tsx +103 -0
- package/templates/base/packages/ui/src/components/input.tsx +20 -0
- package/templates/base/packages/ui/src/components/label.tsx +18 -0
- package/templates/base/packages/ui/src/components/separator.tsx +23 -0
- package/templates/base/packages/ui/src/containers/README.md +11 -0
- package/templates/base/packages/ui/src/content/errors.ts +58 -0
- package/templates/base/packages/ui/src/content/landing.ts +304 -0
- package/templates/base/packages/ui/src/index.ts +8 -0
- package/templates/base/packages/ui/src/lib/interpolate.ts +31 -0
- package/templates/base/packages/ui/src/lib/sentinel.ts +11 -0
- package/templates/base/packages/ui/src/lib/theme.ts +167 -0
- package/templates/base/packages/ui/src/lib/utils.ts +11 -0
- package/templates/base/packages/ui/src/styles/globals.css +164 -0
- package/templates/base/packages/ui/tsconfig.json +7 -0
- package/templates/base/pnpm-workspace.yaml +23 -0
- package/templates/base/prettier.config.js +10 -0
- package/templates/base/saasaloy.json +8 -0
- package/templates/base/stylelint.config.js +46 -0
- package/templates/base/turbo.json +20 -0
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
# {{PROJECT_NAME}}
|
|
2
|
+
|
|
3
|
+
A Cloudflare-native SaaS, scaffolded with [Saasaloy](https://github.com/mimukit/saasaloy). The base is a
|
|
4
|
+
near-inert marketing shell; everything churny (API, database, auth, admin, features) installs on demand.
|
|
5
|
+
|
|
6
|
+
## Develop
|
|
7
|
+
|
|
8
|
+
```sh
|
|
9
|
+
pnpm install
|
|
10
|
+
pnpm dev # astro dev on apps/web
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
## Deploy
|
|
14
|
+
|
|
15
|
+
```sh
|
|
16
|
+
pnpm --filter @repo/web build
|
|
17
|
+
pnpm --filter @repo/web run deploy # wrangler deploy (Cloudflare Workers static assets)
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
## Add features
|
|
21
|
+
|
|
22
|
+
```sh
|
|
23
|
+
saasaloy add waitlist # pulls api, logger, validators, database and a driver
|
|
24
|
+
saasaloy add auth # pulls api, database and a driver
|
|
25
|
+
saasaloy list # what the registry offers, and what you already have
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
`saasaloy list` names every installable module and marks the ones this project already has.
|
|
29
|
+
`saasaloy add <name> --dry-run` shows exactly what one would write before it writes it.
|
|
30
|
+
|
|
31
|
+
## UI components
|
|
32
|
+
|
|
33
|
+
`DESIGN.md` records the project's design tokens and UI rules for people and agents.
|
|
34
|
+
|
|
35
|
+
`packages/ui` ships a Tailwind 4 theme, a small set of [shadcn](https://ui.shadcn.com)
|
|
36
|
+
primitives, and the marketing blocks the landing page is built from. Import primitives and
|
|
37
|
+
blocks by subpath — neither is re-exported from the package root, which carries
|
|
38
|
+
project-wide constants only:
|
|
39
|
+
|
|
40
|
+
```ts
|
|
41
|
+
import { siteName } from "@repo/ui";
|
|
42
|
+
import { Button } from "@repo/ui/components/button";
|
|
43
|
+
import { PricingTable } from "@repo/ui/blocks/pricing-table";
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
To add a primitive the base doesn't include, run the pinned CLI in `packages/ui` — that's
|
|
47
|
+
where `components.json` lives:
|
|
48
|
+
|
|
49
|
+
```sh
|
|
50
|
+
pnpm --filter @repo/ui exec shadcn add dialog
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
Components land in `packages/ui/src/components/` as source you own and can edit.
|
|
54
|
+
|
|
55
|
+
## Landing page
|
|
56
|
+
|
|
57
|
+
`apps/web/src/pages/index.astro` composes the blocks in `packages/ui/src/blocks/` —
|
|
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.
|
|
61
|
+
|
|
62
|
+
Blocks render to static HTML by default. Only the three that need browser state carry a
|
|
63
|
+
client directive (`Navbar` is `client:idle`; `PricingTable` and `Faq` are
|
|
64
|
+
`client:visible`) — the rest ship no JavaScript at all. Don't reach for `client:load`.
|
|
65
|
+
|
|
66
|
+
The page composes itself from explicit imports, and nothing discovers a file behind your
|
|
67
|
+
back. A module that ships UI writes its block into `packages/ui/src/blocks/` like every
|
|
68
|
+
block above, plus a small island under `apps/web/src/components/` that feeds the block
|
|
69
|
+
whatever it needs to talk to (blocks stay presentational and take behaviour as props).
|
|
70
|
+
`saasaloy add <module>` then prints a pointer to the module's skill, which carries the
|
|
71
|
+
import line and the suggested spot. Where it actually goes is your call.
|
|
@@ -0,0 +1,198 @@
|
|
|
1
|
+
---
|
|
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.
|
|
4
|
+
derived-from: "designkit (MIT), narrowed 2026-08-09"
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# saasaloy-design
|
|
8
|
+
|
|
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
|
+
|
|
11
|
+
The token home is `packages/ui/src/styles/globals.css`. The design contract is `DESIGN.md` at the project root.
|
|
12
|
+
|
|
13
|
+
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
|
+
|
|
15
|
+
## Modes
|
|
16
|
+
|
|
17
|
+
- `theme` reads the product brief, selects a registry style with the owner, applies it, updates `DESIGN.md`, writes a new fingerprint, and lints the result.
|
|
18
|
+
- `update` re-derives the contract after a design layer change, writes a new fingerprint, and lints the result.
|
|
19
|
+
- `audit` checks the fingerprint and the contract. It never writes.
|
|
20
|
+
|
|
21
|
+
If the user does not name a mode, ask which mode to use. Do not choose between a write and an audit.
|
|
22
|
+
|
|
23
|
+
## Write surface
|
|
24
|
+
|
|
25
|
+
| Mode | Files |
|
|
26
|
+
|------|-------|
|
|
27
|
+
| `theme` | `packages/ui/src/styles/globals.css`, `DESIGN.md`, and `docs/product-brief.md` only when the brief is absent |
|
|
28
|
+
| `update` | `DESIGN.md` only |
|
|
29
|
+
| `audit` | none |
|
|
30
|
+
|
|
31
|
+
Do not edit `components.json` during a theme change. Do not add a dependency, script, component, page, or Tailwind class.
|
|
32
|
+
|
|
33
|
+
## Shared rules
|
|
34
|
+
|
|
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.
|
|
36
|
+
|
|
37
|
+
Find the system in this order. Stop at the first match.
|
|
38
|
+
|
|
39
|
+
1. Use the Tailwind 4 `@theme` block.
|
|
40
|
+
2. Use the `:root` custom properties.
|
|
41
|
+
3. Report that the expected Saasaloy token home is missing.
|
|
42
|
+
|
|
43
|
+
Read declarations and usage. Count color values, type utilities, spacing utilities, radii, shadows, interaction states, and dark mode variants. A declared value with no use is not enough evidence for a design token.
|
|
44
|
+
|
|
45
|
+
Classify each proposed token as `extracted`, `consolidated`, or `omitted`. List the source values for each consolidation. Use `omitted` when the project does not define a scale.
|
|
46
|
+
|
|
47
|
+
Keep spacing omitted while the project uses Tailwind's default scale unchanged. Use this exact reason in the front matter: `Tailwind's default scale is used unchanged`.
|
|
48
|
+
|
|
49
|
+
Write elevation as prose under `## Elevation & Depth`. Never add `elevation` to `omitted`. The official linter does not accept that omission name.
|
|
50
|
+
|
|
51
|
+
Write motion as prose under `## Motion`. The alpha schema has no motion token group and no component transition token.
|
|
52
|
+
|
|
53
|
+
Use only these component token keys: `backgroundColor`, `textColor`, `typography`, `rounded`, `padding`, `size`, `height`, and `width`.
|
|
54
|
+
|
|
55
|
+
Keep the sections in this order: Overview, Colors, Typography, Layout, Elevation & Depth, Shapes, Components, Do's and Don'ts, Motion, Dark Mode.
|
|
56
|
+
|
|
57
|
+
The seed is a one-time base file. ADR 0022 means Saasaloy has no update path for it. The fingerprint detects drift, and this skill repairs the contract.
|
|
58
|
+
|
|
59
|
+
## Fingerprint
|
|
60
|
+
|
|
61
|
+
The fingerprint covers only `packages/ui/src/styles/globals.css`. It does not cover components, blocks, or other files.
|
|
62
|
+
|
|
63
|
+
Compute the fingerprint from the file bytes. Use the first 12 lowercase hexadecimal characters of SHA-256.
|
|
64
|
+
|
|
65
|
+
```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/globals.css')).digest('hex').slice(0, 12))"
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
The scaffold seed ends with this stamp.
|
|
70
|
+
|
|
71
|
+
```markdown
|
|
72
|
+
_Seeded from the saasaloy base template · CLI <version> · tokens sha256:<12 hex> of packages/ui/src/styles/globals.css_
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
After `theme` or `update`, replace the seed stamp with this stamp.
|
|
76
|
+
|
|
77
|
+
```markdown
|
|
78
|
+
_Updated from packages/ui on YYYY-MM-DD · tokens sha256:<12 hex> of packages/ui/src/styles/globals.css_
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
Do not change the fingerprint during `audit`.
|
|
82
|
+
|
|
83
|
+
## Official linter
|
|
84
|
+
|
|
85
|
+
Run the official linter after each write.
|
|
86
|
+
|
|
87
|
+
```sh
|
|
88
|
+
pnpm dlx @google/design.md lint DESIGN.md
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
The format is alpha. Read the current schema when a lint finding conflicts with this skill.
|
|
92
|
+
|
|
93
|
+
```sh
|
|
94
|
+
pnpm dlx @google/design.md spec --format json
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
Do not hide a warning. Fix it when the code supports the fix. Report it with the reason when the project intentionally keeps it.
|
|
98
|
+
|
|
99
|
+
If the network or `pnpm dlx` is unavailable, continue with the fingerprint and local extraction. State that structural lint did not run. Never call an unchecked file clean.
|
|
100
|
+
|
|
101
|
+
## `theme`
|
|
102
|
+
|
|
103
|
+
### 1. Read the product context
|
|
104
|
+
|
|
105
|
+
Read `docs/product-brief.md` first. Use its product, audience, differentiator, tone, and language answers. Do not ask those questions again.
|
|
106
|
+
|
|
107
|
+
If the brief is absent, offer to run `saasaloy-setup`:
|
|
108
|
+
|
|
109
|
+
> There's no `docs/product-brief.md`, so I'd be picking a theme for a product I know nothing about. `/saasaloy-setup` asks ten questions and leaves the brief behind; then this picks up where it stops. Want me to run it now?
|
|
110
|
+
|
|
111
|
+
Invoke that skill rather than reading its files. Do **not** improvise your own interview, and do not write `docs/product-brief.md` yourself. `saasaloy-setup` owns the brief and its format, and two skills asking overlapping questions is how an owner answers the same thing twice.
|
|
112
|
+
|
|
113
|
+
Ask only for design facts that the brief does not contain. Cover the color mood, the preset direction or registry URL, and the desired density.
|
|
114
|
+
|
|
115
|
+
### 2. Choose the registry style
|
|
116
|
+
|
|
117
|
+
Accept a `registry:style` URL from `https://ui.shadcn.com/create`, `https://tweakcn.com`, or another compatible registry. Do not invent a Saasaloy palette catalogue.
|
|
118
|
+
|
|
119
|
+
Preview the URL and the files that the command can change. Get approval before you run the command.
|
|
120
|
+
|
|
121
|
+
### 3. Apply the preset
|
|
122
|
+
|
|
123
|
+
Use the package-local executable from the project root.
|
|
124
|
+
|
|
125
|
+
```sh
|
|
126
|
+
pnpm --filter @repo/ui exec shadcn add <registry-style-url>
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
Confirm that the three `@source` rules, `@custom-variant dark`, and `@layer base` remain in `globals.css`. Confirm that `components.json` did not change.
|
|
130
|
+
|
|
131
|
+
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
|
+
|
|
133
|
+
### 4. Re-derive the contract
|
|
134
|
+
|
|
135
|
+
Run the shared extraction process against the merged `globals.css` and its usage. Update the token front matter and only the prose sections that the new tokens affect.
|
|
136
|
+
|
|
137
|
+
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
|
+
|
|
139
|
+
Recompute and replace the fingerprint stamp. Run the official linter.
|
|
140
|
+
|
|
141
|
+
### 5. Report
|
|
142
|
+
|
|
143
|
+
Name the registry style URL. List the changed token groups and the new fingerprint. State the linter result. Tell the user to use `uikit` for component or page work.
|
|
144
|
+
|
|
145
|
+
## `update`
|
|
146
|
+
|
|
147
|
+
### 1. Find the change
|
|
148
|
+
|
|
149
|
+
Read `git status --short`. Read the working tree diff when it exists. Otherwise, compare the current branch with its base branch.
|
|
150
|
+
|
|
151
|
+
Identify changes under `packages/ui/`. State which DESIGN.md sections they affect. State which sections remain unchanged.
|
|
152
|
+
|
|
153
|
+
### 2. Re-derive with restraint
|
|
154
|
+
|
|
155
|
+
Run the shared extraction process. Edit existing tokens and affected prose only.
|
|
156
|
+
|
|
157
|
+
Ask before you add or delete a section. Do not rewrite the full file because one token changed.
|
|
158
|
+
|
|
159
|
+
Recompute and replace the fingerprint stamp. Run the official linter.
|
|
160
|
+
|
|
161
|
+
### 3. Report
|
|
162
|
+
|
|
163
|
+
List each changed token with its old and new value. Name the sections left unchanged. State the new fingerprint and linter result.
|
|
164
|
+
|
|
165
|
+
## `audit`
|
|
166
|
+
|
|
167
|
+
Audit is read-only. Do not edit or format any file.
|
|
168
|
+
|
|
169
|
+
### 1. Check the fingerprint offline
|
|
170
|
+
|
|
171
|
+
Read the recorded 12-character fingerprint from the final stamp. Compute the current fingerprint from `globals.css`.
|
|
172
|
+
|
|
173
|
+
- `current` means the two fingerprints match.
|
|
174
|
+
- `stale` means they differ.
|
|
175
|
+
- `unverified` means the stamp or source file is missing.
|
|
176
|
+
|
|
177
|
+
### 2. Check token drift
|
|
178
|
+
|
|
179
|
+
Scan `packages/ui/src/` and the application usage. Report tokens whose values no longer exist. Report repeated values that no token covers.
|
|
180
|
+
|
|
181
|
+
Use `orphaned` for a documented token with no current source. Use `uncovered` for a repeated source value with no documented token.
|
|
182
|
+
|
|
183
|
+
### 3. Lint when possible
|
|
184
|
+
|
|
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.
|
|
186
|
+
|
|
187
|
+
### 4. Report
|
|
188
|
+
|
|
189
|
+
Open with the file count that the audit scanned. Give one verdict for the fingerprint and one verdict for token coverage. Name the worst drift and recommend `saasaloy-design update` when repair is needed.
|
|
190
|
+
|
|
191
|
+
## Boundaries
|
|
192
|
+
|
|
193
|
+
- Never write a component or a page. Use `uikit` for that work.
|
|
194
|
+
- Never add a token value that the project does not contain.
|
|
195
|
+
- Never change `globals.css` in `update` or `audit`.
|
|
196
|
+
- Never write during `audit`.
|
|
197
|
+
- Never claim that lint passed when the linter did not run.
|
|
198
|
+
- Never treat a matching fingerprint as proof that component usage has no drift.
|
|
@@ -0,0 +1,395 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: saasaloy-landing-copy
|
|
3
|
+
description: Write the scaffolded landing page's copy from docs/product-brief.md — as a markdown draft the owner reviews first, then into the content module. Use when the landing page still says "Acme" or "The SaaS you meant to build", when the owner asks to write, rewrite, improve or update the landing copy, headline, features, pricing or FAQ, and when re-running after the brief changed. Needs docs/product-brief.md; run saasaloy-setup first if there isn't one.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# saasaloy-landing-copy — draft it in markdown, then write the page
|
|
7
|
+
|
|
8
|
+
Copy is a chore an agent does well **if** it is first made to understand the product, and
|
|
9
|
+
badly if it guesses. Understanding the product is not this skill's job: `saasaloy-setup`
|
|
10
|
+
already interviewed the owner and left `docs/product-brief.md` behind. This skill turns
|
|
11
|
+
that brief into words.
|
|
12
|
+
|
|
13
|
+
It does it in two passes, and the order matters. **First a markdown draft** the owner can
|
|
14
|
+
read end to end in one screen and edit in place. **Then** the content module, once they say
|
|
15
|
+
yes. A page of thirty keys reviewed as thirty terminal messages is a page nobody actually
|
|
16
|
+
reviewed.
|
|
17
|
+
|
|
18
|
+
## The write surface
|
|
19
|
+
|
|
20
|
+
| Path | What you change |
|
|
21
|
+
|------|-----------------|
|
|
22
|
+
| `docs/landing-copy-draft.md` | The draft. Written every run, deleted once the copy lands. |
|
|
23
|
+
| `packages/ui/src/content/landing.ts` | Everything under `landing.*`. Plus `ui.*`, **translated only**, and only when the page is not in English — see [Language](#language-and-the-ui-namespace). |
|
|
24
|
+
| `apps/web/src/pages/index.astro` | Only to drop a block, and only behind its own confirmation. |
|
|
25
|
+
| `docs/product-brief.md` | Only to append what you learned filling a gap the brief left, and only its **Known gaps** section otherwise. |
|
|
26
|
+
|
|
27
|
+
Nothing else — in particular not `siteName` and not the page's `lang` attribute. Those are
|
|
28
|
+
project identity, `saasaloy-setup` owns them, and [Step 0](#step-0--before-you-write-anything)
|
|
29
|
+
says what to do when they disagree with the brief.
|
|
30
|
+
|
|
31
|
+
## Step 0 — before you write anything
|
|
32
|
+
|
|
33
|
+
1. **Read `docs/product-brief.md`.** No brief, no copy. Say so and offer to run
|
|
34
|
+
`saasaloy-setup`:
|
|
35
|
+
|
|
36
|
+
> There's no `docs/product-brief.md`, so I'd be writing about a product I know nothing
|
|
37
|
+
> about. `/saasaloy-setup` asks ten questions and leaves the brief behind; then this
|
|
38
|
+
> takes about a minute. Want me to run it now?
|
|
39
|
+
|
|
40
|
+
Do **not** improvise your own interview. Two skills asking overlapping questions is how
|
|
41
|
+
an owner ends up answering the same thing twice and getting two different pages.
|
|
42
|
+
2. **Read `packages/ui/src/content/landing.ts`.** You need the current copy to diff against,
|
|
43
|
+
and its comments carry the shape rules you have to keep.
|
|
44
|
+
3. **Read the icon registry** at the top of `packages/ui/src/blocks/feature-grid.tsx`.
|
|
45
|
+
Reading a block is fine; editing one is not. Its keys are the whole vocabulary available
|
|
46
|
+
to `landing.features.items[].icon`, and they are the actual list rather than one copied
|
|
47
|
+
into this file that went stale.
|
|
48
|
+
4. **Check the two facts you do not own.** If `siteName` in `packages/ui/src/index.ts` is
|
|
49
|
+
still the scaffold's directory slug, or `lang` in `apps/web/src/layouts/Layout.astro` is
|
|
50
|
+
`en` while the brief names another language, say so now and carry both into the draft's
|
|
51
|
+
**Not mine to fix** list. Writing Bangla copy into a page that declares itself English is
|
|
52
|
+
a real defect, and one attribute long. Do not fix it yourself.
|
|
53
|
+
5. **Look at git, and treat it as advice.** `git status --short` if there is a repo. A dirty
|
|
54
|
+
tree or no repo is worth one sentence — "your changes aren't committed, so you can't undo
|
|
55
|
+
this with git; the draft is reviewed before anything is written anyway" — and then you
|
|
56
|
+
carry on. **Neither state is a reason to stop.**
|
|
57
|
+
6. **If a draft is already on disk**, an earlier run was abandoned or is waiting on the
|
|
58
|
+
owner. Say it is there, summarise it in a line, and ask whether to build on it or start
|
|
59
|
+
over. Never silently overwrite someone's edits.
|
|
60
|
+
7. **If the content module has already been rewritten by hand** and the brief does not
|
|
61
|
+
explain it, say so plainly: someone wrote this themselves. Ask whether to work from what
|
|
62
|
+
is there or start over.
|
|
63
|
+
|
|
64
|
+
## Step 1 — fill only the gaps the brief left
|
|
65
|
+
|
|
66
|
+
The interview happened. Your questions are limited to what the brief genuinely does not
|
|
67
|
+
answer and the page genuinely needs — usually the FAQ, occasionally a feature that has no
|
|
68
|
+
evidence behind it. Ask in one batch, and carry **three sample answers plus "write your
|
|
69
|
+
own"** on every question, the same way `saasaloy-setup` does: derived from the brief rather
|
|
70
|
+
than generic, shapes rather than invented values wherever the answer becomes a claim the
|
|
71
|
+
page makes.
|
|
72
|
+
|
|
73
|
+
Every `weak:` tag in the brief is a standing question, and re-asking one is always fair:
|
|
74
|
+
|
|
75
|
+
> Last time "it's faster" had no number behind it, so the hero avoided the claim. Do you
|
|
76
|
+
> have a benchmark now?
|
|
77
|
+
|
|
78
|
+
If a gap stays a gap, write around it. Describe what the product does and leave the claim
|
|
79
|
+
out. A page that says less is recoverable; a page that says something false about the
|
|
80
|
+
product is not.
|
|
81
|
+
|
|
82
|
+
## Step 2 — write the draft
|
|
83
|
+
|
|
84
|
+
`docs/landing-copy-draft.md`. Every key, current value beside proposed, in reading order —
|
|
85
|
+
so the owner reviews a page, not a data structure.
|
|
86
|
+
|
|
87
|
+
Three things belong in it besides the copy, because they are the ones an owner catches and
|
|
88
|
+
an agent does not:
|
|
89
|
+
|
|
90
|
+
- **The non-copy decisions.** Each feature's icon name, and where the two calls to action
|
|
91
|
+
point. A wrong icon is invisible in a list of sentences and obvious in a list of icon
|
|
92
|
+
names.
|
|
93
|
+
- **What you were working from.** One line per section tying the choice back to the brief.
|
|
94
|
+
This is what makes "no, that's not what I meant" a two-second correction.
|
|
95
|
+
- **What you are least sure about**, and **what is not yours to fix.**
|
|
96
|
+
|
|
97
|
+
```md
|
|
98
|
+
# Landing copy draft — Ledgerly
|
|
99
|
+
|
|
100
|
+
Generated 2026-08-09 from `docs/product-brief.md`.
|
|
101
|
+
Edit anything here and tell me to apply it, or tell me what to change and I'll redo it.
|
|
102
|
+
This file is deleted once the copy lands in `packages/ui/src/content/landing.ts`.
|
|
103
|
+
|
|
104
|
+
## Tab and search result
|
|
105
|
+
| Key | Now | Proposed |
|
|
106
|
+
|-----|-----|----------|
|
|
107
|
+
| `meta.title` | {siteName} — ship your SaaS, not your scaffolding | {siteName} — close the month in one place |
|
|
108
|
+
|
|
109
|
+
`meta.description` (155 char limit, currently 148):
|
|
110
|
+
> Ledgerly closes the month for bookkeepers carrying 5–20 client accounts, without four
|
|
111
|
+
> spreadsheets per client.
|
|
112
|
+
|
|
113
|
+
## Hero
|
|
114
|
+
| Key | Now | Proposed |
|
|
115
|
+
|-----|-----|----------|
|
|
116
|
+
| `hero.eyebrow` | Now in early access | In beta with two firms |
|
|
117
|
+
| `hero.primaryActionLabel` | Get started | Join the beta |
|
|
118
|
+
|
|
119
|
+
`hero.title`:
|
|
120
|
+
> ~~The SaaS you meant to build, already scaffolded.~~
|
|
121
|
+
> **Close the month without four spreadsheets per client.**
|
|
122
|
+
|
|
123
|
+
*From the brief: the problem line, and "Excel plus a shared Dropbox folder" as the
|
|
124
|
+
alternative. The eyebrow uses the two named beta firms rather than a stage name.*
|
|
125
|
+
|
|
126
|
+
## Features
|
|
127
|
+
| # | Icon | Title | About |
|
|
128
|
+
|---|------|-------|-------|
|
|
129
|
+
| 1 | `list-checks` (was `zap`) | One close, one checklist | … |
|
|
130
|
+
|
|
131
|
+
## Where the buttons go
|
|
132
|
+
| Key | Now | Proposed |
|
|
133
|
+
|-----|-----|----------|
|
|
134
|
+
| `navbar.ctaHref` | `#cta` | https://ledgerly.com.bd/waitlist |
|
|
135
|
+
| `cta.primaryActionHref` | `/` | https://ledgerly.com.bd/waitlist |
|
|
136
|
+
|
|
137
|
+
## Least sure about
|
|
138
|
+
- The eyebrow names your two beta firms. Fine to say publicly?
|
|
139
|
+
- FAQ 4 says data exports as CSV. The brief doesn't mention exports; I inferred it.
|
|
140
|
+
|
|
141
|
+
## Not mine to fix
|
|
142
|
+
- `lang` in `apps/web/src/layouts/Layout.astro` is still `en` and the brief says Bangla.
|
|
143
|
+
Run `/saasaloy-setup`, or change the one attribute yourself.
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
If the owner would rather skip the review — "just write it" — **still write the draft
|
|
147
|
+
file.** The draft is the record of what you propose, so it is not theirs to skip. Say once
|
|
148
|
+
that you are treating their go-ahead as approval, write `docs/landing-copy-draft.md`, name
|
|
149
|
+
the path, then go to [Step 4](#step-4--write-the-content-module) and show the diff there.
|
|
150
|
+
|
|
151
|
+
## Step 3 — the owner reviews
|
|
152
|
+
|
|
153
|
+
Hand them the path and stop. Do not narrate the whole draft back into the terminal; the
|
|
154
|
+
point of the file is that it is not the terminal.
|
|
155
|
+
|
|
156
|
+
> Draft's at `docs/landing-copy-draft.md`. Read it, edit anything you'd rather word
|
|
157
|
+
> yourself, then tell me to apply it.
|
|
158
|
+
|
|
159
|
+
When they come back, **re-read the file from disk** before applying. They may have edited
|
|
160
|
+
it, and their wording wins over yours every time.
|
|
161
|
+
|
|
162
|
+
## Step 4 — write the content module
|
|
163
|
+
|
|
164
|
+
Show the diff, key by key, old value then new. One confirmation for the file is enough; do
|
|
165
|
+
not ask per key. Then write.
|
|
166
|
+
|
|
167
|
+
Every key below is under `landing.` in `packages/ui/src/content/landing.ts`. Fill all of
|
|
168
|
+
them; the layout is built for copy of roughly the length already there, so match it rather
|
|
169
|
+
than doubling it.
|
|
170
|
+
|
|
171
|
+
| Key | What goes there |
|
|
172
|
+
|-----|-----------------|
|
|
173
|
+
| `meta.title` | Browser tab. `{siteName} — <what it does>`, under 60 characters. |
|
|
174
|
+
| `meta.description` | Search result and link preview. One sentence, under 155 characters. |
|
|
175
|
+
| `navbar.linkFeatures` `.linkPricing` `.linkFaq` | Nav labels. One or two words. They point at the sections of the same name — keep the meaning, or the anchor lies. |
|
|
176
|
+
| `navbar.ctaLabel` `.ctaHref` | Two or three words, a verb first, and where it goes. See [Destinations](#destinations). |
|
|
177
|
+
| `hero.eyebrow` | The one-line status above the headline ("Now in early access"). Empty string hides it. |
|
|
178
|
+
| `hero.title` | The headline. Under ten words, one idea, concrete. This is the sentence the whole page is judged on. |
|
|
179
|
+
| `hero.description` | One or two sentences saying what it does for whom. |
|
|
180
|
+
| `hero.primaryActionLabel` `.secondaryActionLabel` | Button labels, a verb first. Both scroll down the page; their hrefs are anchors and stay in the block. |
|
|
181
|
+
| `features.title` `.description` | The section's heading and one supporting line. |
|
|
182
|
+
| `features.items[]` | Six by default: `{ id, icon, title, description }`. Title 2–4 words; description one sentence about what the owner can *do*, not about the technology. See [Icons](#icons). |
|
|
183
|
+
| `pricing.title` `.description` | Heading plus one line. |
|
|
184
|
+
| `pricing.annualNote` `.currencySymbol` | See [Pricing](#pricing). |
|
|
185
|
+
| `pricing.tiers[]` | `{ id, name, description, monthlyPrice, annualPrice, features, ctaLabel, ctaHref }`. Prices are whole units; `null` renders "Custom". Set `featured` on at most one. A tier's `ctaHref` stays `#cta` unless the brief gives that tier its own destination. |
|
|
186
|
+
| `faq.items[]` | Five by default: `{ id, question, answer }`. Write the questions a buyer asks before paying — pricing, migration, lock-in, data, what happens when they outgrow it. One to three sentences, and answer the question. |
|
|
187
|
+
| `cta.title` `.description` | The closing ask, plus one line removing a reason to hesitate. |
|
|
188
|
+
| `cta.primaryActionHref` `.secondaryActionHref` | See [Destinations](#destinations). |
|
|
189
|
+
| `footer.tagline` | One line under the brand. |
|
|
190
|
+
| `footer.groupProduct` `.groupLegal` `.link*` | Footer labels. One or two words each. |
|
|
191
|
+
|
|
192
|
+
Three mechanical rules the code depends on:
|
|
193
|
+
|
|
194
|
+
- **`{siteName}` is the only placeholder, and it works in exactly four strings**:
|
|
195
|
+
`meta.title`, `meta.description`, `hero.description`, `cta.description`. Anywhere else it
|
|
196
|
+
renders as the literal text `{siteName}`. Never a template literal — copy is data.
|
|
197
|
+
- **Feature and FAQ `id`s are stable keys**, not positions. Rewriting a `title` under an
|
|
198
|
+
existing id is free. Renaming an id is allowed but it is a new string to a translation
|
|
199
|
+
layer, so do it only when the item genuinely became a different item.
|
|
200
|
+
- **Deleting an item is fine**, in any of the three lists. Fewer than six features and fewer
|
|
201
|
+
than five questions both lay out correctly.
|
|
202
|
+
|
|
203
|
+
### Icons
|
|
204
|
+
|
|
205
|
+
Each feature carries an `icon` naming a glyph from the registry at the top of
|
|
206
|
+
`packages/ui/src/blocks/feature-grid.tsx`. Rewrite what a feature is about and move its
|
|
207
|
+
icon in the same edit — that is the whole reason the name lives in content. A page whose
|
|
208
|
+
"practice all four modules" card renders a terminal prompt reads as machine-assembled,
|
|
209
|
+
because it was.
|
|
210
|
+
|
|
211
|
+
- Use a key the registry actually has. Read the map; do not guess from memory. An unknown
|
|
212
|
+
name silently renders the fallback sparkle, which looks like a choice and is not.
|
|
213
|
+
- If nothing in the registry fits, say so in the draft and name the lucide icon you would
|
|
214
|
+
want. Widening the map is a two-line edit **the owner makes**, in a block, which is not
|
|
215
|
+
yours.
|
|
216
|
+
|
|
217
|
+
### Destinations
|
|
218
|
+
|
|
219
|
+
Three of the page's hrefs are content, and they are the ones that leave the page:
|
|
220
|
+
`navbar.ctaHref`, `cta.primaryActionHref`, `cta.secondaryActionHref`. The brief's
|
|
221
|
+
**Where "sign up" goes** section is where the answer lives.
|
|
222
|
+
|
|
223
|
+
- **A URL in the brief.** Write it into `navbar.ctaHref` and `cta.primaryActionHref`.
|
|
224
|
+
- **Nothing yet.** Leave them as shipped (`#cta` and `/`) and write labels the page can
|
|
225
|
+
honestly satisfy — "See how it works" rather than "Start your free trial". Note it in the
|
|
226
|
+
draft and in the brief's **Known gaps**. A primary button that reloads the page is the
|
|
227
|
+
single most visible defect this workflow can leave behind, so it does not get to be
|
|
228
|
+
silent.
|
|
229
|
+
- **`cta.secondaryActionHref`** is usually docs or an about page. `/` is honest for a
|
|
230
|
+
one-page site; a label promising docs that do not exist is not.
|
|
231
|
+
|
|
232
|
+
Everything else stays put. `#features`, `#pricing`, `#faq` and `#cta` are anchors matching
|
|
233
|
+
section ids, they live in the blocks, and changing one breaks a link.
|
|
234
|
+
|
|
235
|
+
### Pricing
|
|
236
|
+
|
|
237
|
+
The brief's **Pricing** section decides this, and it has already been through
|
|
238
|
+
`saasaloy-setup`'s "extracted, never invented" rule. Your job is to carry it across without
|
|
239
|
+
softening it.
|
|
240
|
+
|
|
241
|
+
- **Real prices** — write them, and set `currencySymbol` if it is not USD.
|
|
242
|
+
- **Placeholder** — leave the shipped tiers exactly as they are and say so in the draft.
|
|
243
|
+
- **None yet** — offer to drop the pricing block. That is a
|
|
244
|
+
[block removal](#dropping-a-block-is-its-own-confirmation), with its own confirmation.
|
|
245
|
+
|
|
246
|
+
`annualNote` is a claim about the two numbers beside it. If there is an annual discount, set
|
|
247
|
+
each tier's `annualPrice` to the effective monthly cost when billed annually and make the
|
|
248
|
+
note arithmetically true. If there is not, set `annualPrice` equal to `monthlyPrice` and
|
|
249
|
+
`annualNote` to `""`. The monthly/annual switch stays on the page — it is chrome, and it now
|
|
250
|
+
claims nothing untrue.
|
|
251
|
+
|
|
252
|
+
Never round, convert currencies, or fill in a typical number.
|
|
253
|
+
|
|
254
|
+
### The bar this copy has to clear
|
|
255
|
+
|
|
256
|
+
You are writing without a copy editor, and the failure mode is not bad grammar. It is
|
|
257
|
+
fluent, confident, empty text that reads as machine-written. Refuse these, in the copy you
|
|
258
|
+
write and in the copy you keep:
|
|
259
|
+
|
|
260
|
+
- **Abstract benefit nouns as the subject.** "solutions", "experiences", "workflows",
|
|
261
|
+
"innovation", "efficiency". Name what the person does instead.
|
|
262
|
+
- **The AI vocabulary.** leverage, unlock, empower, seamless, robust, streamline, elevate,
|
|
263
|
+
cutting-edge, game-changing, effortless, revolutionary, harness, delve.
|
|
264
|
+
- **Rule-of-three cadence.** "Faster, simpler, smarter." Three-item lists everywhere is the
|
|
265
|
+
loudest tell there is. Use two, or four, or a sentence.
|
|
266
|
+
- **"Not just X — it's Y."** Also "more than just", "isn't only about".
|
|
267
|
+
- **Claims nobody could disagree with.** "Built for modern teams." "Designed for how you
|
|
268
|
+
work today." If the opposite is absurd, the sentence is empty.
|
|
269
|
+
- **Superlatives with no number behind them.** "Blazing fast", "enterprise-grade",
|
|
270
|
+
"world-class", "industry-leading".
|
|
271
|
+
- **Em-dash tic.** At most one em-dash per section. Full stops are free.
|
|
272
|
+
- **Every sentence the same length.** Vary it. Let one land in four words.
|
|
273
|
+
- **Invented proof.** No statistic, customer name, logo, award, rating or "trusted by
|
|
274
|
+
thousands" that did not come out of the brief. Not one.
|
|
275
|
+
- **Second-person promises the product cannot keep.** "You'll never worry about invoices
|
|
276
|
+
again."
|
|
277
|
+
|
|
278
|
+
Two habits carry more weight than that whole list: use the **owner's own nouns** from the
|
|
279
|
+
brief, and **name the current alternative**. "Close the month without four spreadsheets per
|
|
280
|
+
client" is a sentence no generator produces, because it required the interview.
|
|
281
|
+
|
|
282
|
+
### Language, and the `ui.*` namespace
|
|
283
|
+
|
|
284
|
+
Write in the language the brief names, not English with a translation to follow.
|
|
285
|
+
|
|
286
|
+
When that language is not English, `ui.*` gets **translated** in the same pass. It is chrome
|
|
287
|
+
— "Monthly", "Most popular", "Close menu", the copyright line — and leaving it in English
|
|
288
|
+
under Bangla marketing copy ships a visibly broken page. No translation layer exists in the
|
|
289
|
+
base to come back for it later, so this is the only pass it gets.
|
|
290
|
+
|
|
291
|
+
Translated is not rewritten. Four rules:
|
|
292
|
+
|
|
293
|
+
- **Preserve every `{token}` exactly**: `{currencySymbol}`, `{price}`, `{year}`,
|
|
294
|
+
`{siteName}`. A dropped token is a silently broken price or copyright line.
|
|
295
|
+
- **Never rename, add or remove a key.**
|
|
296
|
+
- **Never reword a string that stays English.** In an English run, `ui.*` is untouched.
|
|
297
|
+
- **Leave `packages/ui/src/lib/theme.ts` alone** regardless. It is inlined verbatim into a
|
|
298
|
+
pre-paint `<script>`, is import-free on purpose, and its labels are a separate decision.
|
|
299
|
+
|
|
300
|
+
Then record in the brief's **Known gaps**, in one line, that `ui.*` was translated by a copy
|
|
301
|
+
agent rather than a translator and is worth a native speaker's read. Also record — once —
|
|
302
|
+
that the template loads **no webfont**: `font-sans` is the system stack, so a non-Latin
|
|
303
|
+
script renders in whatever face the visitor's device provides. Both are real gaps and
|
|
304
|
+
neither is a reason to write the page in English instead.
|
|
305
|
+
|
|
306
|
+
## Step 5 — prove it, then clean up
|
|
307
|
+
|
|
308
|
+
A page that does not compile is not a deliverable:
|
|
309
|
+
|
|
310
|
+
```sh
|
|
311
|
+
pnpm --filter @repo/ui typecheck
|
|
312
|
+
pnpm build
|
|
313
|
+
```
|
|
314
|
+
|
|
315
|
+
Broken build, broken types: fix it, or revert the write and say what happened. Do not hand
|
|
316
|
+
back a red tree.
|
|
317
|
+
|
|
318
|
+
Then **delete `docs/landing-copy-draft.md`.** It has done its job, and a stale draft beside
|
|
319
|
+
a rewritten page is a second source of truth that will disagree within a week. The brief is
|
|
320
|
+
the one that persists.
|
|
321
|
+
|
|
322
|
+
Finally, tell the owner what to look at: `pnpm dev`, the two or three lines you are least
|
|
323
|
+
sure about, and anything still on the **Not mine to fix** list.
|
|
324
|
+
|
|
325
|
+
### Dropping a block is its own confirmation
|
|
326
|
+
|
|
327
|
+
When the brief yields nothing a block can honestly carry — no pricing, no proof for the FAQ
|
|
328
|
+
— you may propose removing that block. **Separately.** Never bundled into the copy write,
|
|
329
|
+
never silent.
|
|
330
|
+
|
|
331
|
+
It is three edits, and all three are allowed:
|
|
332
|
+
|
|
333
|
+
1. Delete that block's line from `apps/web/src/pages/index.astro`.
|
|
334
|
+
2. Blank the matching label in content (`landing.navbar.linkPricing = ""`,
|
|
335
|
+
`landing.footer.linkPricing = ""`). A blank label drops the link, so the page keeps no
|
|
336
|
+
nav entry pointing at a section that is gone.
|
|
337
|
+
3. **Retarget or drop any hero action pointing at the removed section.** A blank label
|
|
338
|
+
does not save you here: the hero's `href` lives in the block, so it survives an empty
|
|
339
|
+
label and the button still points at nothing. The hero's secondary action ships as
|
|
340
|
+
`href: "#pricing"`, so when you remove the pricing block, pass `secondaryAction={null}`
|
|
341
|
+
to `<Hero>` in `index.astro` to drop the button, or pass a whole action pointing at a
|
|
342
|
+
section that still exists. Confirm which one with the owner along with the removal.
|
|
343
|
+
|
|
344
|
+
Removing a block is not redesigning one. You are editing lines in a page, not editing
|
|
345
|
+
`packages/ui/src/blocks/`.
|
|
346
|
+
|
|
347
|
+
## Re-running
|
|
348
|
+
|
|
349
|
+
Copy on disk plus a brief means this has run before.
|
|
350
|
+
|
|
351
|
+
1. **Read the brief and the current copy**, and say in a few lines what the page claims
|
|
352
|
+
today.
|
|
353
|
+
2. **Ask what changed**, and ask about every `weak:` tag.
|
|
354
|
+
3. **Draft again.** The draft's "Now" column is the current copy, so a re-run shows the
|
|
355
|
+
owner exactly what a second pass would move — which is the whole value of the second
|
|
356
|
+
pass.
|
|
357
|
+
4. If the copy on disk does not match what the brief would produce, someone edited it by
|
|
358
|
+
hand: say so, show the difference, and ask which wins.
|
|
359
|
+
|
|
360
|
+
## Boundaries to honor
|
|
361
|
+
|
|
362
|
+
- **[The write surface](#the-write-surface) is the whole list of files you may touch** —
|
|
363
|
+
nothing outside that table. Two of them carry copy: `packages/ui/src/content/landing.ts`
|
|
364
|
+
(`landing.*` always, `ui.*` translated on a non-English run) and
|
|
365
|
+
`docs/landing-copy-draft.md`, which you write on every run and delete at the end. The
|
|
366
|
+
other two are a consented block removal in `apps/web/src/pages/index.astro` and appended
|
|
367
|
+
gaps in `docs/product-brief.md`.
|
|
368
|
+
- **`siteName` and `lang` are not yours.** `packages/ui/src/index.ts` and
|
|
369
|
+
`apps/web/src/layouts/Layout.astro` belong to `saasaloy-setup`. Report a mismatch; do not
|
|
370
|
+
fix it.
|
|
371
|
+
- **Never edit a block.** `packages/ui/src/blocks/*.tsx` is off limits: no markup, no
|
|
372
|
+
classes, no structure, no widening the icon registry. Read them freely. If copy will not
|
|
373
|
+
fit a block, say so and let the owner decide.
|
|
374
|
+
- **Never touch the design layer** — `packages/ui/src/styles/globals.css`,
|
|
375
|
+
`components.json`, `packages/ui/src/components/*`, or any Tailwind class anywhere.
|
|
376
|
+
- **Never rewrite `ui.*`**, in any language. Translate it or leave it.
|
|
377
|
+
- **Leave anchors alone.** `#features`, `#pricing`, `#faq`, `#cta` and a tier's default
|
|
378
|
+
`ctaHref` match section ids. The three outbound destinations in
|
|
379
|
+
[Destinations](#destinations) are the exception, and they are the only one.
|
|
380
|
+
- **Leave `apps/web/src/pages/terms.astro` and `privacy.astro` alone.** Boilerplate legal
|
|
381
|
+
text is not a copywriting job.
|
|
382
|
+
- **No images, logos, icons or screenshots.** Not generated, not sourced, not referenced.
|
|
383
|
+
Choosing an existing icon name is not this.
|
|
384
|
+
- **No dependencies, no i18n machinery, no webfonts.** Report the gap; do not close it.
|
|
385
|
+
- **Never invent pricing or proof**, and never quietly leave the shipped demo numbers in
|
|
386
|
+
place as if they were real.
|
|
387
|
+
- **No repeated setup interview.** The threshold is presence, not depth. Hand back to
|
|
388
|
+
`saasaloy-setup` only when `docs/product-brief.md` is absent, or names no product at
|
|
389
|
+
all. A brief that exists and names the product is usable however thin it is: proceed
|
|
390
|
+
under the next bullet, and for a page-specific gap ask only the questions
|
|
391
|
+
[Step 1](#step-1--fill-only-the-gaps-the-brief-left) allows. Never ask the setup
|
|
392
|
+
interview's own questions yourself.
|
|
393
|
+
- **A dirty tree, a missing repo, or a thin brief is not a blocker.** Warn, write less, and
|
|
394
|
+
say what you left out. (This is about those three conditions only — anything unsafe or
|
|
395
|
+
outside this skill's scope you decline as you normally would.)
|