@cparkerwebm/webmonterey 1.0.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/CHANGELOG.md +56 -0
- package/LICENSE +21 -0
- package/README.md +104 -0
- package/agents/.gitkeep +0 -0
- package/dist/webm.mjs +2381 -0
- package/hooks/.gitkeep +0 -0
- package/package.json +101 -0
- package/schema/design.json +118 -0
- package/skills/launch/SKILL.md +183 -0
- package/skills/new-component/SKILL.md +85 -0
- package/skills/start/SKILL.md +117 -0
- package/skills/traps/SKILL.md +333 -0
- package/skills/upgrade/SKILL.md +42 -0
- package/src/actions/index.ts +348 -0
- package/src/cli/checks.test.ts +711 -0
- package/src/cli/checks.ts +822 -0
- package/src/cli/codemods.test.ts +42 -0
- package/src/cli/codemods.ts +51 -0
- package/src/cli/compare.test.ts +144 -0
- package/src/cli/compare.ts +222 -0
- package/src/cli/design-extract.test.ts +96 -0
- package/src/cli/design-extract.ts +229 -0
- package/src/cli/doctor.ts +187 -0
- package/src/cli/mcp.test.ts +57 -0
- package/src/cli/mcp.ts +110 -0
- package/src/cli/new.ts +148 -0
- package/src/cli/package-root.ts +58 -0
- package/src/cli/scaffold.test.ts +230 -0
- package/src/cli/scaffold.ts +424 -0
- package/src/cli/seed.ts +133 -0
- package/src/cli/slug.test.ts +70 -0
- package/src/cli/slug.ts +109 -0
- package/src/cli/sync.test.ts +137 -0
- package/src/cli/sync.ts +266 -0
- package/src/cli/upgrade.ts +93 -0
- package/src/design/__fixtures__/tokens-v1.4.1.css +227 -0
- package/src/design/brand.ts +49 -0
- package/src/design/compile.test.ts +98 -0
- package/src/design/compile.ts +155 -0
- package/src/design/defaults.ts +315 -0
- package/src/design/index.ts +18 -0
- package/src/design/resolve.test.ts +80 -0
- package/src/design/resolve.ts +108 -0
- package/src/design/types.ts +96 -0
- package/src/emails/autoresponse.test.ts +82 -0
- package/src/emails/autoresponse.ts +83 -0
- package/src/emails/footer.test.ts +102 -0
- package/src/emails/footer.ts +91 -0
- package/src/emails/index.ts +24 -0
- package/src/emails/subject.test.ts +66 -0
- package/src/emails/subject.ts +80 -0
- package/src/emails/submission-notification.ts +76 -0
- package/src/env.d.ts +12 -0
- package/src/includes/cloudflare/d1/client.ts +86 -0
- package/src/includes/cloudflare/r2/README.md +68 -0
- package/src/includes/cloudflare/r2/media.ts +41 -0
- package/src/includes/cloudflare/r2/url.test.ts +44 -0
- package/src/includes/cloudflare/r2/url.ts +34 -0
- package/src/includes/cloudflare/turnstile/Turnstile.astro +161 -0
- package/src/includes/cloudflare/turnstile/verify.ts +157 -0
- package/src/includes/cloudflare/workers/env.ts +56 -0
- package/src/includes/google/tag-manager/TagManager.astro +73 -0
- package/src/includes/sinch/mailgun/redirect.test.ts +96 -0
- package/src/includes/sinch/mailgun/redirect.ts +96 -0
- package/src/includes/sinch/mailgun/send.ts +186 -0
- package/src/includes/webmonterey/animations/animations.css +347 -0
- package/src/includes/webmonterey/animations/observe.test.ts +88 -0
- package/src/includes/webmonterey/animations/observe.ts +209 -0
- package/src/includes/webmonterey/compliance/ConsentInit.astro +74 -0
- package/src/includes/webmonterey/compliance/CookieConsent.astro +558 -0
- package/src/includes/webmonterey/compliance/consent-styles.test.ts +83 -0
- package/src/includes/webmonterey/compliance/consent.ts +142 -0
- package/src/includes/webmonterey/config.test.ts +94 -0
- package/src/includes/webmonterey/config.ts +346 -0
- package/src/includes/webmonterey/copy-defaults.ts +148 -0
- package/src/includes/webmonterey/copy.ts +13 -0
- package/src/includes/webmonterey/credits/Credit.astro +80 -0
- package/src/includes/webmonterey/credits/credit.test.ts +111 -0
- package/src/includes/webmonterey/credits/credit.ts +59 -0
- package/src/includes/webmonterey/forms/honeypot.test.ts +40 -0
- package/src/includes/webmonterey/forms/honeypot.ts +66 -0
- package/src/includes/webmonterey/prose/inline.test.ts +94 -0
- package/src/includes/webmonterey/prose/inline.ts +71 -0
- package/src/includes/webmonterey/scroll-top/ScrollTop.astro +209 -0
- package/src/includes/webmonterey/site.ts +136 -0
- package/src/includes/webmonterey/structured-data/nodes.ts +315 -0
- package/src/includes/webmonterey/zoned-hour.test.ts +49 -0
- package/src/integration/adapter.ts +53 -0
- package/src/integration/app-middleware.ts +43 -0
- package/src/integration/config.ts +96 -0
- package/src/integration/content.ts +66 -0
- package/src/integration/image-size.test.ts +83 -0
- package/src/integration/image-size.ts +100 -0
- package/src/integration/index.ts +386 -0
- package/src/integration/virtual.d.ts +101 -0
- package/src/layouts/base.astro +402 -0
- package/src/package.test.ts +121 -0
- package/src/pages/404.astro +33 -0
- package/src/pages/[...slug].astro +126 -0
- package/src/pages/robots.txt.ts +41 -0
- package/src/pages/webm.astro +23 -0
- package/src/scripts/.gitkeep +0 -0
- package/src/styles/base.css +185 -0
- package/src/styles/global.css +47 -0
- package/src/styles/layers.test.ts +32 -0
- package/src/styles/layers.ts +21 -0
- package/src/styles/layout.css +235 -0
- package/src/styles/reset.css +135 -0
- package/src/styles/utilities.css +90 -0
- package/src/worker.ts +76 -0
- package/template/assets/logo.svg +14 -0
- package/template/assets/open-graph.png +0 -0
- package/template/migrations/0001_create_submissions.sql +40 -0
- package/template/migrations/README.md +41 -0
- package/template/public/android-chrome-192x192.png +0 -0
- package/template/public/android-chrome-512x512.png +0 -0
- package/template/public/apple-touch-icon.png +0 -0
- package/template/public/favicon-16x16.png +0 -0
- package/template/public/favicon-32x32.png +0 -0
- package/template/public/favicon.ico +0 -0
- package/template/public/favicon.svg +4 -0
- package/template/public/open-graph.png +0 -0
- package/template/scripts/check-node.mjs +72 -0
- package/template/scripts/test-hooks.mjs +96 -0
- package/template/site/.editorconfig +22 -0
- package/template/site/.prettierignore +15 -0
- package/template/site/.prettierrc.json +17 -0
- package/template/site/CLAUDE.md +245 -0
- package/template/site/CONTENT.md +135 -0
- package/template/site/PRIVACY-POLICY-DRAFT.md +81 -0
- package/template/site/public/_headers +70 -0
- package/template/site/src/forms/contact.json +45 -0
- package/template/workflows/ci.yml +52 -0
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
{
|
|
2
|
+
"printWidth": 100,
|
|
3
|
+
"singleQuote": true,
|
|
4
|
+
"semi": true,
|
|
5
|
+
"trailingComma": "all",
|
|
6
|
+
"plugins": ["prettier-plugin-astro"],
|
|
7
|
+
"overrides": [
|
|
8
|
+
{
|
|
9
|
+
"files": "*.astro",
|
|
10
|
+
"options": { "parser": "astro" }
|
|
11
|
+
},
|
|
12
|
+
{
|
|
13
|
+
"files": "*.jsonc",
|
|
14
|
+
"options": { "parser": "jsonc", "trailingComma": "none" }
|
|
15
|
+
}
|
|
16
|
+
]
|
|
17
|
+
}
|
|
@@ -0,0 +1,245 @@
|
|
|
1
|
+
# CLAUDE.md
|
|
2
|
+
|
|
3
|
+
This is a **WebMonterey client site**. It is a thin repo: the framework lives in the
|
|
4
|
+
`@cparkerwebm/webmonterey` package, and what is here is what makes this client's site
|
|
5
|
+
different from every other one.
|
|
6
|
+
|
|
7
|
+
The relationship is a WordPress parent theme and child theme.
|
|
8
|
+
|
|
9
|
+
| | Parent (the package) | Child (this repo) |
|
|
10
|
+
| ------------- | -------------------------------------------------------- | ---------------------------------- |
|
|
11
|
+
| Owns | layouts, styles, form pipeline, emails, routes, includes | components, content, palette, copy |
|
|
12
|
+
| Changes by | `npm update @cparkerwebm/webmonterey` | editing files here |
|
|
13
|
+
| Overridden by | this repo, always | — |
|
|
14
|
+
|
|
15
|
+
**A fix to the package reaches this site on `npm update`.** That is the entire point of the
|
|
16
|
+
package existing, and it is why the default answer to "the shared behavior is wrong" is to fix
|
|
17
|
+
it upstream rather than to work around it here.
|
|
18
|
+
|
|
19
|
+
## The one rule that protects that
|
|
20
|
+
|
|
21
|
+
**Prefer overriding to editing around.** When something in the package is not right for this
|
|
22
|
+
client, express it as an override in this repo — a token in `design.json`, a rule in
|
|
23
|
+
`src/styles/custom/`, a wrapper in `src/actions/index.ts`. Do not reach into `node_modules` and
|
|
24
|
+
do not copy a package file here so you can change two lines: a copy stops receiving fixes the
|
|
25
|
+
moment it is made, silently, and nothing will ever tell you.
|
|
26
|
+
|
|
27
|
+
Copying a package file here is legitimate exactly once: when this client genuinely needs
|
|
28
|
+
different behavior, not a different value. Say so in a comment at the top of the copy, naming
|
|
29
|
+
what it forked from and why, so the next person knows it is deliberate.
|
|
30
|
+
|
|
31
|
+
## Commands
|
|
32
|
+
|
|
33
|
+
| Command | Runs |
|
|
34
|
+
| ----------------- | ---------------------------------------------------------- |
|
|
35
|
+
| `npm run dev` | dev server — fast, and blind to half the things that break |
|
|
36
|
+
| `npm run preview` | **a real build on real workerd** |
|
|
37
|
+
| `npm run check` | types and content schema |
|
|
38
|
+
| `npx webm doctor` | the things that fail silently |
|
|
39
|
+
| `npm run format` | prettier |
|
|
40
|
+
|
|
41
|
+
**Run `npm run preview`, not `dev`, before merging anything** that touches styles,
|
|
42
|
+
`wrangler.jsonc`, a route's `prerender` flag, or the form pipeline. `dev` cannot see CSS
|
|
43
|
+
bundling, cascade order, or the asset router. Every trap in `/webm:traps` was invisible in
|
|
44
|
+
`dev` and visible in `preview`.
|
|
45
|
+
|
|
46
|
+
## Hard rules
|
|
47
|
+
|
|
48
|
+
### 1. Vanilla CSS and vanilla JS only
|
|
49
|
+
|
|
50
|
+
**Banned outright:** Tailwind, Bootstrap, Sass/Less/Stylus, CSS-in-JS, PostCSS plugin chains,
|
|
51
|
+
React, Vue, Svelte, Solid, Preact, Alpine, htmx, jQuery, Lodash, and any UI or utility
|
|
52
|
+
framework.
|
|
53
|
+
|
|
54
|
+
**Allowed:** plain `.css` using `@layer` and custom properties, plain `.js`/`.ts` modules,
|
|
55
|
+
`.astro` components, and `zod` (build-time validation only, never shipped).
|
|
56
|
+
|
|
57
|
+
TypeScript is allowed and expected — the rule targets frameworks and preprocessors, not types.
|
|
58
|
+
|
|
59
|
+
Do not add a dependency to solve something CSS or the web platform already does.
|
|
60
|
+
|
|
61
|
+
### 2. Style through tokens, and set tokens in `design.json`
|
|
62
|
+
|
|
63
|
+
Every color, space, size, radius, shadow, z-index, duration and easing is a `--webm-*` custom
|
|
64
|
+
property. Component CSS contains **no literal values** for these.
|
|
65
|
+
|
|
66
|
+
The palette lives in [design.json](design.json), which compiles to the token layer at build
|
|
67
|
+
time. Retheming this client is editing that file — one value cascades everywhere.
|
|
68
|
+
|
|
69
|
+
For a rule rather than a value, [src/styles/custom/](src/styles/custom/) is the seam:
|
|
70
|
+
`webm.components.custom` beats `webm.components.core` at identical specificity, so an override
|
|
71
|
+
lands without `!important` and stays legible as an override.
|
|
72
|
+
|
|
73
|
+
**Do not edit the package's stylesheets.** That is the change from generation 2, where a client
|
|
74
|
+
repo was a copy of the starter and editing it in place was correct. Here it is a copy-forward
|
|
75
|
+
that stops receiving fixes.
|
|
76
|
+
|
|
77
|
+
### 3. Never restyle for accessibility unless asked
|
|
78
|
+
|
|
79
|
+
A client's colors, contrast, focus rings, type sizes and spacing are **configured**, not
|
|
80
|
+
accidental. Do not "fix" any of them because an audit or a WCAG level says they fall short —
|
|
81
|
+
including darkening a color, enlarging text, or swapping a token as a side effect of unrelated
|
|
82
|
+
work.
|
|
83
|
+
|
|
84
|
+
**Report it, do not change it.** State the measured number and the element, then wait:
|
|
85
|
+
|
|
86
|
+
> The secondary consent buttons are 1.19:1 against the card. Want me to change that?
|
|
87
|
+
|
|
88
|
+
Two things this is not. It does not license shipping markup that is broken rather than merely
|
|
89
|
+
low-contrast — semantics, labels, roles, keyboard operability and focus ORDER are correctness,
|
|
90
|
+
and they get fixed like any other bug. And it does not apply once you have been asked: a
|
|
91
|
+
request to fix a contrast problem is the permission, for that problem.
|
|
92
|
+
|
|
93
|
+
A client signs off on a palette. A change made on our own initiative between one deploy and the
|
|
94
|
+
next is a change they never approved and will not think to look for.
|
|
95
|
+
|
|
96
|
+
### 4. Page content is JSON, never hardcoded
|
|
97
|
+
|
|
98
|
+
Copy, headings, links and block ordering live in `src/content/pages/*.json` — one file per
|
|
99
|
+
page. Never hardcode client copy into an `.astro` file. If content cannot be expressed in the
|
|
100
|
+
block schema, the component's `schema.ts` is what changes, not the markup.
|
|
101
|
+
|
|
102
|
+
Private data — user logins, form submissions — goes to **Cloudflare D1**, never to JSON.
|
|
103
|
+
|
|
104
|
+
### 5. The prefix is `webm-`, never `wm-`
|
|
105
|
+
|
|
106
|
+
Custom properties (`--webm-action`), class names (`.webm-section`), Cloudflare resource names
|
|
107
|
+
(`webm-<slug>`), the CLI, the skills namespace. No exceptions.
|
|
108
|
+
|
|
109
|
+
### 6. Cloudflare bindings come from `cloudflare:workers`
|
|
110
|
+
|
|
111
|
+
```ts
|
|
112
|
+
import { env } from 'cloudflare:workers';
|
|
113
|
+
const { results } = await env.DB.prepare('SELECT 1').run();
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
Request metadata is `Astro.request.cf`. The execution context is `Astro.locals.cfContext`.
|
|
117
|
+
|
|
118
|
+
### 7. Content edits happen on a branch
|
|
119
|
+
|
|
120
|
+
Never edit page JSON directly on `main`. Branch, edit, push — Workers Builds gives the branch
|
|
121
|
+
its own preview URL for client review, and a schema-breaking edit fails there instead of on the
|
|
122
|
+
live site. Merge when approved.
|
|
123
|
+
|
|
124
|
+
### 8. Every third party is consent-gated
|
|
125
|
+
|
|
126
|
+
Anything loaded from outside the site — pixel, analytics, embed, widget — must be gated on the
|
|
127
|
+
visitor's cookie choice. Categories: `essential` (never gated), `functional`, `analytics`,
|
|
128
|
+
`marketing`. Unsure between the last two? Choose `marketing`; it is what US privacy law treats
|
|
129
|
+
as "sale or sharing".
|
|
130
|
+
|
|
131
|
+
```ts
|
|
132
|
+
import { whenConsented } from '@cparkerwebm/webmonterey/webmonterey/compliance';
|
|
133
|
+
|
|
134
|
+
whenConsented('marketing', () => {
|
|
135
|
+
/* create the script element here, not before */
|
|
136
|
+
});
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
Loading a script and "not using it" is not gating — the request itself sets the cookie.
|
|
140
|
+
|
|
141
|
+
**Write the gated version even when `features.compliance` is `false`.** With compliance off,
|
|
142
|
+
`whenConsented` fires immediately for every category, so the same code works on both kinds of
|
|
143
|
+
site and turning consent on later needs no changes.
|
|
144
|
+
|
|
145
|
+
**Never read `document.cookie` to check consent.** That misses the Global Privacy Control path,
|
|
146
|
+
and a visitor sending GPC has legally opted out — from January 2027 every browser must offer
|
|
147
|
+
that setting, so it stops being an edge case.
|
|
148
|
+
|
|
149
|
+
**Prefer Google Tag Manager for pixels and analytics.** `TagManager` is wired into the layout and
|
|
150
|
+
`ConsentInit` sets Consent Mode v2 before it loads, so a tag added in the GTM UI inherits the
|
|
151
|
+
visitor's choice with no code change. Write code only for an embed or widget that must render
|
|
152
|
+
into a specific element. Accessibility overlays (UserWay, accessiBe) are rejected on liability,
|
|
153
|
+
not price.
|
|
154
|
+
|
|
155
|
+
### 9. Look it up before you use it — four servers, one per question
|
|
156
|
+
|
|
157
|
+
All four are declared in [.mcp.json](.mcp.json) and pre-approved in
|
|
158
|
+
[.claude/settings.json](.claude/settings.json), so they work on any machine without anyone
|
|
159
|
+
clicking Approve. `webm doctor` fails if a site has lost one.
|
|
160
|
+
|
|
161
|
+
| Question | Server |
|
|
162
|
+
| ---------------------------------------------------------------------- | -------------- |
|
|
163
|
+
| What does this Astro API do, and is it still called that? | `astro-docs` |
|
|
164
|
+
| What does this web platform API do, and which browsers have it? | `mdn` |
|
|
165
|
+
| Is this the right way to do SEO, a11y, performance, privacy, security? | `website-spec` |
|
|
166
|
+
| Will this email render, arrive, and be readable? | `email-spec` |
|
|
167
|
+
|
|
168
|
+
**Astro moves faster than any training corpus and the web platform never stops.** Much of what a
|
|
169
|
+
model recalls about both is two majors out of date, and the recalled version is always confidently
|
|
170
|
+
wrong rather than obviously wrong. Look it up.
|
|
171
|
+
|
|
172
|
+
**`astro-docs` and `mdn` outrank the two specification servers on facts.** The Website and Email
|
|
173
|
+
Specifications are opinionated best-practice guides — genuinely good ones, and the source for
|
|
174
|
+
_what a good site or email does_ — but they are not normative standards despite the name, and
|
|
175
|
+
they are at 0.x. On what an API is or does, MDN and the Astro docs win.
|
|
176
|
+
|
|
177
|
+
**`email-spec` before touching `src/emails/`.** An email template that looks right in a browser is
|
|
178
|
+
evidence of nothing: the rendering quirks and the deliverability rules are the whole problem, and
|
|
179
|
+
they are exactly what recall gets wrong.
|
|
180
|
+
|
|
181
|
+
If a server is unavailable in a session — `mdn` is explicitly an experiment Mozilla may withdraw —
|
|
182
|
+
**say so and cite the documentation site directly.** Never fall back to recall silently. A session
|
|
183
|
+
that quietly stops checking is the failure all four are here to prevent.
|
|
184
|
+
|
|
185
|
+
### 10. A class used by more than one page belongs in a shared stylesheet
|
|
186
|
+
|
|
187
|
+
Astro scopes a component's `<style>` to that component's markup. A class defined in one page's
|
|
188
|
+
`<style>` block and used in another renders **completely unstyled** — no error, no warning, just
|
|
189
|
+
a bare element. It is invisible until someone looks at the second page.
|
|
190
|
+
|
|
191
|
+
Shared class → `src/styles/custom/`. A page's own `<style>` block is only for markup that page
|
|
192
|
+
alone renders.
|
|
193
|
+
|
|
194
|
+
### 11. Never edit inside `node_modules`
|
|
195
|
+
|
|
196
|
+
A change there survives until the next install and not one second longer. If the package is
|
|
197
|
+
wrong, fix the package — see the top of this file.
|
|
198
|
+
|
|
199
|
+
## Structure
|
|
200
|
+
|
|
201
|
+
```
|
|
202
|
+
design.json this client's palette. Compiles to --webm-* tokens.
|
|
203
|
+
webmonterey.json domain, client name, environment, features.
|
|
204
|
+
src/
|
|
205
|
+
components/ EVERY visible component. The package ships none.
|
|
206
|
+
registry.ts maps a block `type` to its component. A block whose type is
|
|
207
|
+
not here renders as nothing, silently.
|
|
208
|
+
content/pages/*.json the words. One file per route; home.json is `/`.
|
|
209
|
+
forms/*.json form definitions. Filename is the form id.
|
|
210
|
+
actions/index.ts re-exports the package pipeline. Wrap to customize.
|
|
211
|
+
pages/webapp/ the web app, if this site grows one. Folder is fixed; the public
|
|
212
|
+
URL is `app.path` in webmonterey.json. Every page `prerender = false`.
|
|
213
|
+
styles/custom/ per-client CSS overrides.
|
|
214
|
+
assets/ images processed at build time.
|
|
215
|
+
public/ served verbatim. Favicons, _headers.
|
|
216
|
+
migrations/ D1 schema. Additive only — never edit an applied migration.
|
|
217
|
+
```
|
|
218
|
+
|
|
219
|
+
## Component IDs
|
|
220
|
+
|
|
221
|
+
Components are numbered, not named for what they look like: `content-000001`, not `hero-split`.
|
|
222
|
+
A name describing the current design stops being true the first time the design changes, and
|
|
223
|
+
renaming it means touching every content file that references it.
|
|
224
|
+
|
|
225
|
+
The `type` in a page JSON file must match a key in `registry.ts`. Inventing one — or copying it
|
|
226
|
+
from another project — makes the block render as nothing, with no error.
|
|
227
|
+
|
|
228
|
+
## Skills
|
|
229
|
+
|
|
230
|
+
`.claude/skills/webm/` is materialized from the package on every install. Do not edit it; it is
|
|
231
|
+
replaced wholesale. This client's own skills go beside it at `.claude/skills/<name>/`.
|
|
232
|
+
|
|
233
|
+
| | |
|
|
234
|
+
| --------------------- | ------------------------------------------------------------------------- |
|
|
235
|
+
| `/webm:traps` | the things that fail silently — read this before debugging anything weird |
|
|
236
|
+
| `/webm:new-component` | adding a block type |
|
|
237
|
+
| `/webm:start` | standing a new site up: repo, Cloudflare resources, first deploy |
|
|
238
|
+
| `/webm:launch` | launch checklist: structured data, sending domain, secrets, DNS, cutover |
|
|
239
|
+
| `/webm:upgrade` | taking a new package version |
|
|
240
|
+
|
|
241
|
+
Content edits: branch, edit `src/content/pages/*.json`, `npm run check`, push — the branch
|
|
242
|
+
preview is the client's review link. Merge when approved. Never change a block's `type`.
|
|
243
|
+
|
|
244
|
+
Palette: `design.json`. Fonts are full CSS stacks; a self-hosted face still needs its
|
|
245
|
+
`@font-face` in `src/styles/custom/`. The `overrides` map takes any `--webm-*` token raw.
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
# Editing this site's content
|
|
2
|
+
|
|
3
|
+
For anyone changing words on the site — no coding required.
|
|
4
|
+
|
|
5
|
+
> **Using Claude Cowork?** Paste this whole file into the folder's **Folder instructions**
|
|
6
|
+
> when you connect the site. Cowork does not read it automatically.
|
|
7
|
+
|
|
8
|
+
---
|
|
9
|
+
|
|
10
|
+
## Where the words live
|
|
11
|
+
|
|
12
|
+
All page content is in **`src/content/pages/`**. One file per page. The filename is the web
|
|
13
|
+
address:
|
|
14
|
+
|
|
15
|
+
| File | Page on the site |
|
|
16
|
+
| ------------------------------------- | ------------------ |
|
|
17
|
+
| `src/content/pages/home.json` | the home page, `/` |
|
|
18
|
+
| `src/content/pages/about.json` | `/about` |
|
|
19
|
+
| `src/content/pages/contact.json` | `/contact` |
|
|
20
|
+
| `src/content/pages/services/seo.json` | `/services/seo` |
|
|
21
|
+
|
|
22
|
+
`home.json` is the one exception — it becomes `/`, not `/home`.
|
|
23
|
+
|
|
24
|
+
Adding a new file adds a new page. Deleting one removes that page.
|
|
25
|
+
|
|
26
|
+
---
|
|
27
|
+
|
|
28
|
+
## What a page file looks like
|
|
29
|
+
|
|
30
|
+
```json
|
|
31
|
+
{
|
|
32
|
+
"title": "About",
|
|
33
|
+
"description": "Family-run since 1994.",
|
|
34
|
+
"blocks": [
|
|
35
|
+
{
|
|
36
|
+
"type": "content-000001",
|
|
37
|
+
"heading": "Who we are",
|
|
38
|
+
"body": "We have served the Monterey Bay area for thirty years."
|
|
39
|
+
}
|
|
40
|
+
]
|
|
41
|
+
}
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
- **`title`** — the browser tab, and what search results show as the headline. Required.
|
|
45
|
+
- **`description`** — the grey summary line in Google results. One or two sentences.
|
|
46
|
+
- **`blocks`** — the sections of the page, top to bottom.
|
|
47
|
+
|
|
48
|
+
---
|
|
49
|
+
|
|
50
|
+
## What you can change
|
|
51
|
+
|
|
52
|
+
**Any text.** Headings, body copy, button labels, link addresses, `title`, `description`.
|
|
53
|
+
|
|
54
|
+
**The order of sections.** Move a whole `{ ... }` block up or down inside `blocks` and the
|
|
55
|
+
section moves up or down on the page.
|
|
56
|
+
|
|
57
|
+
**Remove a section.** Delete its whole `{ ... }` block, including the comma that separates it
|
|
58
|
+
from the next one.
|
|
59
|
+
|
|
60
|
+
---
|
|
61
|
+
|
|
62
|
+
## What you must not change
|
|
63
|
+
|
|
64
|
+
**Never invent or edit a `"type"` value.** That is not a label — it is the name of a building
|
|
65
|
+
block that has to already exist. If you make one up, or copy one from another site, that
|
|
66
|
+
section renders as **nothing at all** and the page silently loses content.
|
|
67
|
+
|
|
68
|
+
To add a kind of section that does not exist yet, ask for a new component to be built.
|
|
69
|
+
|
|
70
|
+
**Do not touch these at all:**
|
|
71
|
+
|
|
72
|
+
- `src/components/` — the building blocks themselves
|
|
73
|
+
- `src/styles/` — fonts, colors, spacing
|
|
74
|
+
- `src/layouts/`, `src/pages/`, `src/includes/`
|
|
75
|
+
- anything ending in `.astro`, `.ts`, `.css`
|
|
76
|
+
- `package.json`, `astro.config.mjs`, `wrangler.jsonc`, `tsconfig.json`
|
|
77
|
+
- `webmonterey.json` — the site's identity. Changing `domain` breaks search listings.
|
|
78
|
+
- `.dev.vars` — passwords and keys, never to be opened, copied, or pasted anywhere
|
|
79
|
+
|
|
80
|
+
**The cookie banner and privacy dialog are not page content.** They are legally significant:
|
|
81
|
+
the wording describes what the site actually does with data, and the categories map to real
|
|
82
|
+
behavior. Changing that text on your own can make the site's disclosure inaccurate. If a
|
|
83
|
+
client wants different wording, raise it — do not edit it here.
|
|
84
|
+
|
|
85
|
+
---
|
|
86
|
+
|
|
87
|
+
## Getting JSON right
|
|
88
|
+
|
|
89
|
+
The format is strict, and one wrong character stops the whole site from building.
|
|
90
|
+
|
|
91
|
+
- Every name and every piece of text is in `"double quotes"`. Never `'single'`, never curly
|
|
92
|
+
`"smart"` quotes — if you draft in Word or Google Docs, it will substitute those silently.
|
|
93
|
+
- A comma between items, but **no comma after the last one** in a list or block.
|
|
94
|
+
- Every `{` needs a `}`, every `[` needs a `]`.
|
|
95
|
+
- An apostrophe inside text is fine: `"We're open"`. A double quote inside text needs a
|
|
96
|
+
backslash: `"They said \"yes\""`.
|
|
97
|
+
|
|
98
|
+
---
|
|
99
|
+
|
|
100
|
+
## How to publish a change
|
|
101
|
+
|
|
102
|
+
**Always work on a branch. Never edit the live site directly.**
|
|
103
|
+
|
|
104
|
+
```sh
|
|
105
|
+
git checkout -b content/homepage-copy # 1. start a branch
|
|
106
|
+
# 2. make your edits
|
|
107
|
+
npm run check # 3. confirm nothing is broken
|
|
108
|
+
git add -A
|
|
109
|
+
git commit -m "Update homepage copy"
|
|
110
|
+
git push -u origin HEAD # 4. push
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
`npm run check` is the safety net — it catches a missing comma or a wrong field before it can
|
|
114
|
+
reach anyone. **If it reports an error, fix it before pushing.**
|
|
115
|
+
|
|
116
|
+
Pushing gives the branch its own preview web address, something like
|
|
117
|
+
`content-homepage-copy-webm-example-com.workers.dev`. Send that link for review.
|
|
118
|
+
|
|
119
|
+
When it is approved, merge the branch into `main`. That publishes it to the real site.
|
|
120
|
+
|
|
121
|
+
---
|
|
122
|
+
|
|
123
|
+
## If something goes wrong
|
|
124
|
+
|
|
125
|
+
**The build failed.** Almost always a JSON typo — a missing comma, a smart quote, an extra
|
|
126
|
+
comma after the last item. The error message names the file.
|
|
127
|
+
|
|
128
|
+
**A section vanished.** Its `"type"` does not match a real component. Check the spelling
|
|
129
|
+
against the folder names in `src/components/`.
|
|
130
|
+
|
|
131
|
+
**A change is not showing up.** Confirm you edited the right file for that page, that the
|
|
132
|
+
branch was pushed, and that you are looking at the preview URL for _that_ branch.
|
|
133
|
+
|
|
134
|
+
Nothing here can be broken permanently — every change is on a branch until it is merged, and
|
|
135
|
+
anything merged can be reverted.
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
# Privacy policy — DRAFT
|
|
2
|
+
|
|
3
|
+
**NOT READY TO PUBLISH.** Every `{{PLACEHOLDER}}` below must be filled in, and every section
|
|
4
|
+
describing something this site does not do must be deleted, before launch. This is a drafting
|
|
5
|
+
aid, not legal advice — the client, and their counsel if they have any, must review it.
|
|
6
|
+
|
|
7
|
+
## Why this is markdown and not a page
|
|
8
|
+
|
|
9
|
+
The package ships zero components, so it cannot ship page JSON: a block whose `type` is not in
|
|
10
|
+
this site's registry fails the content schema and the build stops. Once the site has a prose
|
|
11
|
+
block, `/webm:edit-content` turns this into `src/content/pages/privacy.json` — the text below is
|
|
12
|
+
already in reading order, one heading per section.
|
|
13
|
+
|
|
14
|
+
---
|
|
15
|
+
|
|
16
|
+
**Effective date:** {{DATE}}
|
|
17
|
+
|
|
18
|
+
{{LEGAL_ENTITY_NAME}} ("we", "us") operates {{DOMAIN}}. This policy explains what the site collects, why, and the choices you have.
|
|
19
|
+
|
|
20
|
+
## What we collect
|
|
21
|
+
|
|
22
|
+
**Information you give us.** If you submit a form, we receive what you typed into it — typically your name, email address, phone number and message.
|
|
23
|
+
|
|
24
|
+
**Information collected automatically.** Our hosting provider, Cloudflare, processes technical information required to serve the site: your IP address, browser user-agent, the pages you request, and the country your request came from. This is standard server logging and is used to deliver the site, keep it available, and defend it against abuse.
|
|
25
|
+
|
|
26
|
+
## Cookies and similar technologies
|
|
27
|
+
|
|
28
|
+
We group these into four categories:
|
|
29
|
+
|
|
30
|
+
- **Essential** — required for the site to work and to resist abuse. These are never optional.
|
|
31
|
+
- **Functional** — remember preferences, such as your cookie choices.
|
|
32
|
+
- **Analytics** — help us understand which pages are used.
|
|
33
|
+
- **Marketing** — used for advertising measurement or audience building.
|
|
34
|
+
|
|
35
|
+
When you first visit, a banner lets you accept or reject each optional category. Nothing in the functional, analytics or marketing categories loads until you choose. You can change your choices at any time from the cookie settings link in the site footer.
|
|
36
|
+
|
|
37
|
+
**Global Privacy Control.** If your browser sends a GPC signal, we treat it as an opt-out of analytics and marketing automatically, and you will not be asked again. You do not need to interact with the banner for this to take effect.
|
|
38
|
+
|
|
39
|
+
## Who we share information with
|
|
40
|
+
|
|
41
|
+
We do not sell your personal information. We do not share it for cross-context behavioral advertising except as described under Analytics below, and only where you have consented.
|
|
42
|
+
|
|
43
|
+
The site relies on these processors:
|
|
44
|
+
|
|
45
|
+
- **Cloudflare** — hosting, content delivery, and security. Processes technical request data as described above. _Essential._
|
|
46
|
+
- **Cloudflare Turnstile** — spam and abuse protection on forms. Runs only on pages with a form and is treated as essential, because without it the form cannot resist automated abuse.
|
|
47
|
+
- **Mailgun (Sinch)** — delivers form submissions to us by email.
|
|
48
|
+
- **Google Tag Manager / Google Analytics** — measures site usage. _Loads only with your consent to the analytics category._ Google Consent Mode v2 is enabled, so Google is told your choice directly.
|
|
49
|
+
- {{ADD_OR_REMOVE_PROCESSORS_TO_MATCH_THE_SITE}}
|
|
50
|
+
|
|
51
|
+
## How long we keep things
|
|
52
|
+
|
|
53
|
+
Form submissions are retained for {{RETENTION_PERIOD}} and then deleted. Server logs are retained by Cloudflare under their own retention schedule.
|
|
54
|
+
|
|
55
|
+
## Your rights
|
|
56
|
+
|
|
57
|
+
Depending on where you live — including under the California Consumer Privacy Act as amended by the CPRA — you may have the right to:
|
|
58
|
+
|
|
59
|
+
- know what personal information we hold about you and how it is used;
|
|
60
|
+
- request a copy of it;
|
|
61
|
+
- request that we correct or delete it;
|
|
62
|
+
- opt out of its sale or sharing (we do not sell it, and the GPC signal and cookie banner both control sharing);
|
|
63
|
+
- not be discriminated against for exercising any of these rights.
|
|
64
|
+
|
|
65
|
+
To make a request, contact us at {{CONTACT_EMAIL}}. We may need to verify your identity before acting on it.
|
|
66
|
+
|
|
67
|
+
## Children
|
|
68
|
+
|
|
69
|
+
The site is not directed at children under 13 and we do not knowingly collect their information.
|
|
70
|
+
|
|
71
|
+
## Changes
|
|
72
|
+
|
|
73
|
+
We will update this page if our practices change, and revise the effective date above.
|
|
74
|
+
|
|
75
|
+
## Contact
|
|
76
|
+
|
|
77
|
+
{{LEGAL_ENTITY_NAME}}
|
|
78
|
+
|
|
79
|
+
{{POSTAL_ADDRESS}}
|
|
80
|
+
|
|
81
|
+
{{CONTACT_EMAIL}}
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
# Cloudflare Workers static-asset headers.
|
|
2
|
+
#
|
|
3
|
+
# The adapter MERGES this with its own generated rules (it adds immutable Cache-Control for
|
|
4
|
+
# /_astro/*), so do not add caching rules for hashed assets here.
|
|
5
|
+
#
|
|
6
|
+
# Deliberately conservative: these are safe for every client site as shipped. The full CSP
|
|
7
|
+
# ships REPORT-ONLY — it enforces nothing and cannot break a site. See the notes below before
|
|
8
|
+
# promoting it to enforcing.
|
|
9
|
+
|
|
10
|
+
/*
|
|
11
|
+
# Stop browsers guessing a MIME type and executing an upload as script.
|
|
12
|
+
X-Content-Type-Options: nosniff
|
|
13
|
+
|
|
14
|
+
# Nobody may frame this site. Replaces the legacy X-Frame-Options.
|
|
15
|
+
Content-Security-Policy: frame-ancestors 'none'
|
|
16
|
+
|
|
17
|
+
# Send the origin to other sites, the full URL to our own. Keeps paths out of referrers.
|
|
18
|
+
Referrer-Policy: strict-origin-when-cross-origin
|
|
19
|
+
|
|
20
|
+
# Turn off APIs the site does not use, for itself and anything it embeds.
|
|
21
|
+
Permissions-Policy: geolocation=(), microphone=(), camera=(), payment=(), usb=()
|
|
22
|
+
|
|
23
|
+
# Two years, and eligible for preload. See the caveat below before uncommenting.
|
|
24
|
+
# Strict-Transport-Security: max-age=63072000; includeSubDomains; preload
|
|
25
|
+
|
|
26
|
+
# Report-only. Violations appear in the browser console and NOTHING is blocked. See below.
|
|
27
|
+
Content-Security-Policy-Report-Only: default-src 'self'; base-uri 'self'; object-src 'none'; form-action 'self'; frame-ancestors 'none'; img-src 'self' data: https://www.googletagmanager.com https://www.google-analytics.com; style-src 'self' 'unsafe-inline'; script-src 'self' 'unsafe-inline' https://www.googletagmanager.com https://challenges.cloudflare.com; frame-src https://challenges.cloudflare.com; connect-src 'self' https://*.google-analytics.com https://*.analytics.google.com https://*.googletagmanager.com https://challenges.cloudflare.com
|
|
28
|
+
|
|
29
|
+
# THE REPORT-ONLY CSP
|
|
30
|
+
#
|
|
31
|
+
# Report-only enforces nothing, so this is safe to ship unconfigured: a client site with an
|
|
32
|
+
# untuned policy gets console warnings, never a broken page. Read the console on a real
|
|
33
|
+
# browser — a status-code check cannot see a CSP violation.
|
|
34
|
+
#
|
|
35
|
+
# WHAT IT COVERS. The hosts above are the starter's complete third-party list: Google Tag
|
|
36
|
+
# Manager and Google Analytics (only ever loaded with analytics consent) and Cloudflare
|
|
37
|
+
# Turnstile. Everything else is 'self'.
|
|
38
|
+
#
|
|
39
|
+
# WHAT YOU MUST ADD PER CLIENT:
|
|
40
|
+
# - R2 media — `img-src`/`media-src https://media.<client-domain>` if the site uses it.
|
|
41
|
+
# Not included here because the host is derived from the client's own domain.
|
|
42
|
+
# - Any third party added through the `add-third-party` skill. Adding the script without
|
|
43
|
+
# adding its host here means the policy stops describing the site.
|
|
44
|
+
#
|
|
45
|
+
# WHY 'unsafe-inline' IS IN BOTH script-src AND style-src. Not laziness — it is structural,
|
|
46
|
+
# and measured on a real build. Every page carries three inline scripts and three inline
|
|
47
|
+
# styles:
|
|
48
|
+
# - ConsentInit's blocking script, which must run before any third party and therefore
|
|
49
|
+
# cannot be an external file.
|
|
50
|
+
# - The `@layer` order statement, which base.astro emits inline BECAUSE a bare @layer
|
|
51
|
+
# statement does not survive bundling (see CLAUDE.md Traps — moving it out of the
|
|
52
|
+
# document silently inverts the cascade).
|
|
53
|
+
# - Astro's own inlining of small component scripts and scoped styles, which changes hash
|
|
54
|
+
# every build, so static hashes in this file would go stale on the next deploy.
|
|
55
|
+
# Nonces would be the real fix and need a server-rendered response; most routes here are
|
|
56
|
+
# static. So 'unsafe-inline' stays until that changes — but note it weakens ONLY the inline
|
|
57
|
+
# case. default-src, form-action, base-uri, object-src and the host allowlists still catch an
|
|
58
|
+
# injected external script, a form retargeted at another origin, and a rewritten <base>.
|
|
59
|
+
#
|
|
60
|
+
# PROMOTING IT TO ENFORCING. Per client, once the console is clean on every page including
|
|
61
|
+
# forms: rename the header to `Content-Security-Policy`. Do it on a preview URL first, click
|
|
62
|
+
# through every route, and submit a form — Turnstile renders in an iframe, so `frame-src` is
|
|
63
|
+
# the directive most likely to bite, and it fails in a way that looks like the widget simply
|
|
64
|
+
# not appearing.
|
|
65
|
+
#
|
|
66
|
+
# HSTS
|
|
67
|
+
#
|
|
68
|
+
# Only enable once the custom domain is live and HTTPS is confirmed working, INCLUDING every
|
|
69
|
+
# subdomain. `includeSubDomains` will break any subdomain still served over plain HTTP, and
|
|
70
|
+
# `preload` is effectively irreversible for two years.
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
{
|
|
2
|
+
"//": "A form definition. Copy this file to add another form; the filename is the form's id, and that id is what lands in the `form` column in D1.",
|
|
3
|
+
"name": "Contact",
|
|
4
|
+
"//notify": "WHO GETS THE EMAIL. Ships EMPTY on purpose — an unset recipient is obvious, whereas a placeholder address silently swallows every enquiry. Set this before launch.",
|
|
5
|
+
"//subject": "TOPIC ONLY. The `[Client Name] ` prefix is added by src/emails/subject.ts from webmonterey.json — do not write it here or it renders twice. {{field}} placeholders interpolate submitted values; an unmatched one becomes empty rather than showing up literally.",
|
|
6
|
+
"notify": {
|
|
7
|
+
"to": [],
|
|
8
|
+
"subject": "New contact enquiry from {{name}}"
|
|
9
|
+
},
|
|
10
|
+
"//autoresponse": "THE CONFIRMATION SENT BACK TO THE VISITOR. Omit this whole block to send nothing — that is the default, because an autoresponder that goes out before the client has agreed to its wording is a promise the agency made on their behalf. `subject` is TOPIC ONLY, same as notify.subject: the `[Client Name] ` prefix is added by src/emails/subject.ts. It is sent to the address in the form's `email` field, with Reply-To set to the FIRST notify.to recipient, so a visitor replying reaches a human. It never sends if notify.to is empty.",
|
|
11
|
+
"autoresponse": {
|
|
12
|
+
"subject": "We received your enquiry",
|
|
13
|
+
"body": "Thanks for getting in touch. We have received your message and will get back to you shortly."
|
|
14
|
+
},
|
|
15
|
+
"//fields": "Rendered in order. `type` maps to an input type; `required` is enforced server-side, not just in the browser.",
|
|
16
|
+
"fields": [
|
|
17
|
+
{
|
|
18
|
+
"name": "name",
|
|
19
|
+
"label": "Your name",
|
|
20
|
+
"type": "text",
|
|
21
|
+
"required": true,
|
|
22
|
+
"autocomplete": "name"
|
|
23
|
+
},
|
|
24
|
+
{
|
|
25
|
+
"name": "email",
|
|
26
|
+
"label": "Email address",
|
|
27
|
+
"type": "email",
|
|
28
|
+
"required": true,
|
|
29
|
+
"autocomplete": "email"
|
|
30
|
+
},
|
|
31
|
+
{
|
|
32
|
+
"name": "phone",
|
|
33
|
+
"label": "Phone",
|
|
34
|
+
"type": "tel",
|
|
35
|
+
"required": false,
|
|
36
|
+
"autocomplete": "tel"
|
|
37
|
+
},
|
|
38
|
+
{
|
|
39
|
+
"name": "message",
|
|
40
|
+
"label": "How can we help?",
|
|
41
|
+
"type": "textarea",
|
|
42
|
+
"required": true
|
|
43
|
+
}
|
|
44
|
+
]
|
|
45
|
+
}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
# PACKAGE-MANAGED. Rewritten by `webm sync` on every install - edit it in the package
|
|
2
|
+
# (template/workflows/ci.yml), never here, or your change disappears on the next `npm update`.
|
|
3
|
+
#
|
|
4
|
+
# WHY THIS EXISTS. Sites once shipped with no CI at all, and a dropped action, an absent
|
|
5
|
+
# migration and a route with no middleware all sat in main for weeks because the only thing that
|
|
6
|
+
# would have run `astro check` was a person remembering to.
|
|
7
|
+
#
|
|
8
|
+
# `webm doctor` is in here deliberately. The type checker catches what TypeScript can see; doctor
|
|
9
|
+
# catches what it cannot - an action nothing exports, a table with no migration, a D1 flag with no
|
|
10
|
+
# binding, an on-demand route the asset router will 404.
|
|
11
|
+
name: CI
|
|
12
|
+
|
|
13
|
+
on:
|
|
14
|
+
push:
|
|
15
|
+
pull_request:
|
|
16
|
+
|
|
17
|
+
# One run per branch. Keyed on head_ref||ref_name rather than github.ref, which differs between
|
|
18
|
+
# push and pull_request events and so runs a branch with an open PR twice.
|
|
19
|
+
concurrency:
|
|
20
|
+
group: ci-${{ github.head_ref || github.ref_name }}
|
|
21
|
+
cancel-in-progress: true
|
|
22
|
+
|
|
23
|
+
permissions:
|
|
24
|
+
contents: read
|
|
25
|
+
|
|
26
|
+
jobs:
|
|
27
|
+
verify:
|
|
28
|
+
name: Check, test, build
|
|
29
|
+
runs-on: ubuntu-latest
|
|
30
|
+
timeout-minutes: 15
|
|
31
|
+
steps:
|
|
32
|
+
- uses: actions/checkout@v5
|
|
33
|
+
- uses: actions/setup-node@v6
|
|
34
|
+
with:
|
|
35
|
+
node-version-file: .nvmrc
|
|
36
|
+
cache: npm
|
|
37
|
+
|
|
38
|
+
# No registry credential. The framework is a public package on npmjs.
|
|
39
|
+
- run: npm ci
|
|
40
|
+
|
|
41
|
+
# astro check. This is the one that fails on a missing prop, a dropped action or an
|
|
42
|
+
# ambient type that never came across.
|
|
43
|
+
- run: npm run check
|
|
44
|
+
|
|
45
|
+
- run: npm test --if-present
|
|
46
|
+
|
|
47
|
+
# The checks a compiler cannot make. Non-blocking for now so it reports rather than
|
|
48
|
+
# gates - a doctor that fails a repo on its first run gets switched off.
|
|
49
|
+
- run: npx webm doctor
|
|
50
|
+
continue-on-error: true
|
|
51
|
+
|
|
52
|
+
- run: npm run build
|