@getvitops/create 0.9.0-alpha.4 → 0.9.0-alpha.5
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 +37 -0
- package/package.json +2 -2
- package/templates/emdash/.agents/skills/setup/SKILL.md +7 -0
- package/templates/emdash/.claude/settings.json +7 -1
- package/templates/emdash/.github/workflows/deploy-prod.yml +1 -1
- package/templates/emdash/.playwright/cli.config.json +7 -0
- package/templates/emdash/AGENTS.md +48 -174
- package/templates/emdash/README.md +21 -21
- package/templates/emdash/_gitignore +8 -8
- package/templates/emdash/package.json +6 -5
- package/templates/emdash/pnpm-workspace.yaml +7 -14
- package/templates/emdash/public/_headers +1 -1
- package/templates/emdash/scripts/pull-content.mjs +1 -1
- package/templates/emdash/site.json +11 -0
- package/templates/emdash/src/layouts/Layout.astro +15 -16
- package/templates/emdash/src/pages/[...slug].astro +1 -1
- package/templates/emdash/src/pages/api/track.ts +1 -1
- package/templates/emdash/src/pages/index.astro +1 -1
- package/templates/static/.agents/skills/setup/SKILL.md +7 -0
- package/templates/static/.claude/settings.json +6 -0
- package/templates/static/.playwright/cli.config.json +7 -0
- package/templates/static/AGENTS.md +36 -124
- package/templates/static/_gitignore +8 -8
- package/templates/static/package.json +4 -3
- package/templates/static/pnpm-workspace.yaml +5 -10
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,42 @@
|
|
|
1
1
|
# @getvitops/create
|
|
2
2
|
|
|
3
|
+
## 0.9.0-alpha.5
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- 6b5ea17: **Leaner scaffolded `AGENTS.md`, and skills named by scope.** The EmDash template's `AGENTS.md` went from 212 to about 70 lines and the static one from 141 to about 50: only always-true rules stay (source of truth, generated CSS, branches, `vp`/`vpr`, where to look), and task-specific guidance moved into skills loaded when the task matches.
|
|
8
|
+
|
|
9
|
+
- **Breaking, `@getvitops/skills`: skills are renamed by scope.** `custom-blocks` → `emdash-custom-blocks`, `content-and-deploys` → `emdash-content-and-deploys`, `contact-form` → `emdash-contact-form`, `csp` → `vitops-csp`, `see-the-page` → `vitops-see-the-page`, `ads` → `vitops-ads`. `emdash-*` skills only apply to an EmDash site; `vitops-*` apply anywhere, and both prefixes are reserved for toolkit skills. New: `emdash-building-pages` (blocks, nesting, page opener, widget areas, escape hatches, layout primitives, `site.chrome`) and `vitops-consent` (split out of `vitops-design-system`, whose description and body now cover styling and tokens only). Existing sites: run `vp install` so `skills experimental_sync` relinks, and replace the per-skill `.agents/skills/<name>` / `.claude/skills/<name>` lines in `.gitignore` with `.agents/skills/vitops-*`, `.agents/skills/emdash-*` and the same two under `.claude/skills/`. A site's own skill must not use those prefixes.
|
|
10
|
+
- **`vitops agents sync`:** the block lists only skills that apply (a project without `emdash` in its dependencies gets no `emdash-*` ones, and they are not linked), summaries are cut at a clause rather than with an ellipsis, the pointer says `vp exec vitops docs`, and the version stamp is gone so the file no longer changes on every CLI bump.
|
|
11
|
+
- **`@getvitops/create`:** both templates ship the trimmed `AGENTS.md`; the project-naming steps moved into the `setup` skill and EmDash 1.x behaviours, legal-page replacement and the audit prerequisites into `emdash-content-and-deploys`. Existing sites: nothing to change beyond the skill rename; delete the sections you no longer need.
|
|
12
|
+
|
|
13
|
+
- 7069f93: **Agents now look at rendered pages with `playwright-cli`.** Both templates add `playwright` as a devDependency, run `playwright cli install --skills` from `prepare` (writing the upstream command reference to `.claude/skills/playwright-cli/`), ship `.playwright/cli.config.json`, allow `pnpm exec playwright cli`, and deny `mcp__claude-in-chrome__*` and `mcp__playwright__*` in `.claude/settings.json`. `@getvitops/skills` adds a short `see-the-page` skill (serve first, snapshot before screenshot, dark mode, mobile, no-JS) that defers to the upstream one. Existing sites: add `playwright` as a devDependency, extend `prepare`, and copy the settings. `vitops audit` now needs only `@axe-core/playwright lighthouse` added.
|
|
14
|
+
- 4d94c8b: **Added: `site.chrome` — a configurable masthead and nav behaviour.** Non-breaking.
|
|
15
|
+
|
|
16
|
+
- `site.chrome.masthead` (new `@getvitops/config` schema): `position` (`static|sticky|fixed`), `behavior` (`always|hide-on-scroll`), `surface` (`solid|glass|transparent|transparent-until-scrolled`), `tone` (a `colors.roles` key), `layout` (`full|inset|floating`), `width`, `elevation` (`shadow`, `border`, `onScroll`), `height` and `inset`. `height` and `inset` take fluid lengths (`calc()`, `clamp()`, `min()`, `max()`, with `cqi`/`vw`/… units), checked against a closed grammar because they are written into CSS. `validateConfig` rejects a `tone` or `shadow` the design system does not define (only when the default theme states them directly, not via `extends`).
|
|
17
|
+
- `site.chrome.nav`: `compact` (`drawer|dropdown`), `drawerSide`, `breakpoint`/`mobileBreakpoint`/`megaBreakpoint`, `depth`, `megaDepth`.
|
|
18
|
+
- `<Masthead config={site}>` (`@getvitops/astro`, slots `brand`/default/`actions`) and `resolveMasthead()` / `resolveNavOptions()`. A prop overrides one field per layout. The key is `chrome`/`masthead` on purpose: `<header>` is valid in any section and `banner` already means the announcement bar and the landmark role.
|
|
19
|
+
- `<SiteNav>` gains `compact="dropdown"` (a columnar panel anchored under the toggle), `drawerSide` (previously hardcoded to the end edge in Astro), `megaDepth` and `megaPanel`. `.sitenav--mega` now styles second-level items as column headings and has an optional featured column.
|
|
20
|
+
- `@getvitops/emdash`: `VitopsMegaPanel` fills that column from the widget area `mega-<item url slug>` (or a `mega-area:<name>` class on the menu item). No area means no column.
|
|
21
|
+
- `@getvitops/create` (emdash template): the layout renders through `<Masthead>`; `site.json` ships a sticky glass masthead with a shadow once scrolled. The hand-written `.navbar { block-size: 64px }` is gone (use `masthead.height`).
|
|
22
|
+
- CSS (`patterns/masthead.css`, `patterns/sitenav.css`): **all formats**, including tailwind, where the patterns are inlined into `@layer components`. Hide-on-scroll and the `*-on-scroll` states are `scroll-state()` container queries that only subtract from a settled (solid, elevated, visible) bar, so a browser without them (Firefox today) shows that bar rather than a transparent one. A sticky or fixed masthead sets `--header-offset` / `--top-offset` so anchors land below it.
|
|
23
|
+
- `@getvitops/generator`: new `TIERS.masthead`; `TIERS.sitenav` documents the new props.
|
|
24
|
+
|
|
25
|
+
### Patch Changes
|
|
26
|
+
|
|
27
|
+
- bb61a35: **Call tracking numbers: show an ad visitor a number that attributes the phone call.** Two additive config blocks, no CSS, no breaking change — all formats unaffected.
|
|
28
|
+
|
|
29
|
+
- **`site.ads.google.callTracking`** — Google Ads _website call conversions_. `{ label, number, cache? }` makes the consent-gated Google tag fetch a Google forwarding number (Canada supported) and swap it in for visitors who arrived from a Google ad. Google replaces text only, so the capture script also rewrites the matching `tel:` href. Needs `pixelId` and `site.tracking.enabled`; rejected on any other platform (only Google can put a number on the website — Microsoft's exist only inside the ad, in the US/UK/AU).
|
|
30
|
+
- **`site.tracking.phoneNumbers`** — per-platform numbers for every other platform: the real `number`, `sources` keyed by `site.ads` platform (plus `default-paid`), and an informational `provider`. You provision the numbers with a call-tracking service (Twilio, CallRail, …). The landing URL decides first, then the `_ac` cookie, so a visitor who declines `marketing` sees a tracking number on the landing page only. An organic visitor is never swapped, and the server-rendered HTML always carries the real number.
|
|
31
|
+
- The `tel:` tap beacon now carries which number was shown (`phoneSource`), and the conversion notification says so.
|
|
32
|
+
- `vitops ads lint` flags a call-tracking provider that is not in `site.legal.privacyPolicy.processors`, and notes that `callTracking` plus `conversionLabel.call` can count one call twice (make one secondary in Google Ads).
|
|
33
|
+
- `@getvitops/utils/tracking` exports the pure `selectPhoneNumber()` and helpers; the capture script mirrors it and `tracking.test.ts` pins the two.
|
|
34
|
+
- `@getvitops/skills` gains a **`vitops-ads`** skill (overview plus a `call-tracking.md` topic). The templates gitignore synced skills by prefix (`vitops-*`, `emdash-*`), so it needs no `.gitignore` line of its own.
|
|
35
|
+
|
|
36
|
+
`@getvitops/core`, `@getvitops/vite` and `@getvitops/emdash` — no consumer-facing change in these packages.
|
|
37
|
+
|
|
38
|
+
- 74a87ed: **Scaffolded sites no longer exempt third-party tooling from the 3-day release cooldown.** The templates floated `vite-plus` and `emdash` on `latest` and exempted them (plus `@voidzero-dev/*`, `oxlint`, `oxfmt`) from `minimumReleaseAge`. An exempt package's exact-pinned dependencies are not exempt, so `vp create` then failed with `ERR_PNPM_NO_MATURE_MATCHING_VERSION` whenever vite-plus had just pinned a fresh `@oxc-project/*`. Now only `@getvitops/*` is exempt; `vite-plus` and `vite` (the `vite-plus-core` alias) are `^1.5.1` (the security fix for 1.5.0) and `emdash` and `@emdash-cms/cloudflare` are `^1.4.0`, all ranges, so pnpm resolves the newest version that has cleared the cooldown. Existing sites: in `pnpm-workspace.yaml` reduce `minimumReleaseAgeExclude` to `'@getvitops/*'` and change the `catalog` entries to `^1.5.1`; in `package.json` change those four dependencies from `latest` to those ranges (`vite` as `npm:@voidzero-dev/vite-plus-core@^1.5.1`).
|
|
39
|
+
|
|
3
40
|
## 0.9.0-alpha.4
|
|
4
41
|
|
|
5
42
|
### Minor Changes
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@getvitops/create",
|
|
3
|
-
"version": "0.9.0-alpha.
|
|
3
|
+
"version": "0.9.0-alpha.5",
|
|
4
4
|
"description": "Project templates for the Vitops design system — scaffold wired-up sites with `vp create @getvitops`.",
|
|
5
5
|
"homepage": "https://docs.vitops.ca",
|
|
6
6
|
"bugs": "https://github.com/getvitops/design-system/issues",
|
|
@@ -19,7 +19,7 @@
|
|
|
19
19
|
"access": "public"
|
|
20
20
|
},
|
|
21
21
|
"devDependencies": {
|
|
22
|
-
"@getvitops/generator": "8.3.0-alpha.
|
|
22
|
+
"@getvitops/generator": "8.3.0-alpha.5"
|
|
23
23
|
},
|
|
24
24
|
"engines": {
|
|
25
25
|
"node": ">=24"
|
|
@@ -59,3 +59,10 @@ report that verbatim rather than guessing at a fix. A step reported as
|
|
|
59
59
|
"blocked" is not a failure to retry; it is waiting on one of the hard stops
|
|
60
60
|
above, or on a `site.json` field the user still needs to fill in (stage 5,
|
|
61
61
|
Configuration).
|
|
62
|
+
|
|
63
|
+
## Naming the project
|
|
64
|
+
|
|
65
|
+
`vp create` takes the package name from the target directory and invents a random slug when none
|
|
66
|
+
is given, so pass one: `vp create @getvitops:<template> --directory <client-slug>`. `wrangler.jsonc`'s
|
|
67
|
+
`name` (and on EmDash the D1 `database_name` and R2 `bucket_name`) are separate and still say what
|
|
68
|
+
the template shipped; rename them to match before the first deploy.
|
|
@@ -1,6 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"permissions": {
|
|
3
|
-
"allow": [
|
|
3
|
+
"allow": [
|
|
4
|
+
"mcp__astro-docs__search_astro_docs",
|
|
5
|
+
"mcp__emdash-docs__search_docs",
|
|
6
|
+
"Bash(pnpm exec playwright cli:*)",
|
|
7
|
+
"Bash(npx --no-install playwright cli:*)"
|
|
8
|
+
],
|
|
9
|
+
"deny": ["mcp__claude-in-chrome__*", "mcp__playwright__*"]
|
|
4
10
|
},
|
|
5
11
|
"hooks": {
|
|
6
12
|
"SessionStart": [
|
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
#
|
|
11
11
|
# Also runs on a `repository_dispatch` of type `emdash-publish`: pages are
|
|
12
12
|
# prerendered, so a publish in the EmDash admin only goes live after a rebuild,
|
|
13
|
-
# and an EmDash `content:afterPublish` hook POSTs that event (.agents/skills/content-and-deploys,
|
|
13
|
+
# and an EmDash `content:afterPublish` hook POSTs that event (.agents/skills/emdash-content-and-deploys,
|
|
14
14
|
# "Publish-triggered rebuilds"). A dispatch runs this file from the DEFAULT branch
|
|
15
15
|
# (`dev`), so `ref` pins the build to `main` — production's code, not dev's. It
|
|
16
16
|
# still goes through the `production` environment, so it still waits for the
|
|
@@ -3,198 +3,72 @@
|
|
|
3
3
|
Built with the Vitops design system (`@getvitops/*`); tokens and site facts live in `site.json`.
|
|
4
4
|
Prefer its classes and patterns over hand-written CSS. Load the skill that matches the task:
|
|
5
5
|
|
|
6
|
-
- [
|
|
7
|
-
- [
|
|
8
|
-
- [
|
|
9
|
-
- [custom-blocks](.agents/skills/custom-blocks/SKILL.md) — Use before creating, renaming or deleting any block type, any `src/components/blocks/*`
|
|
10
|
-
- [vitops-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
6
|
+
- [emdash-building-pages](.agents/skills/emdash-building-pages/SKILL.md) — Use when authoring or changing a page's layout or content on this EmDash site — composing `vitops_*` blocks
|
|
7
|
+
- [emdash-contact-form](.agents/skills/emdash-contact-form/SKILL.md) — Use when adding or changing a form, fixing submissions that don't arrive, setting up notifications, CRM or Turnstile
|
|
8
|
+
- [emdash-content-and-deploys](.agents/skills/emdash-content-and-deploys/SKILL.md) — Use for any deploy or build problem, stale or missing content, "my publish isn't live", scheduled posts, previews
|
|
9
|
+
- [emdash-custom-blocks](.agents/skills/emdash-custom-blocks/SKILL.md) — Use before creating, renaming or deleting any block type, any `src/components/blocks/*` renderer
|
|
10
|
+
- [vitops-ads](.agents/skills/vitops-ads/SKILL.md) — Use when adding or changing a Google/Meta/LinkedIn/TikTok/Microsoft/Reddit/Pinterest/Snapchat pixel
|
|
11
|
+
- [vitops-consent](.agents/skills/vitops-consent/SKILL.md) — Use before gating, adding or changing any non-essential third-party tag, cookie, embed
|
|
12
|
+
- [vitops-csp](.agents/skills/vitops-csp/SKILL.md) — Use when changing `public/_headers`, adding an embed, tracking snippet or third-party script, turning the CSP on
|
|
13
|
+
- [vitops-design-system](.agents/skills/vitops-design-system/SKILL.md) — Use when styling or authoring pages or components (choosing utility/pattern classes, colours, spacing, typography)
|
|
14
|
+
- [vitops-see-the-page](.agents/skills/vitops-see-the-page/SKILL.md) — How to look at a rendered page of this site
|
|
15
|
+
|
|
16
|
+
Reference, rendered from this project: `vp exec vitops docs [topic]` (no topic lists them).
|
|
14
17
|
<!-- vitops:end -->
|
|
15
18
|
|
|
16
|
-
This is an EmDash CMS website (Astro on Cloudflare Workers, D1 + R2
|
|
17
|
-
|
|
19
|
+
This is an EmDash CMS website (Astro on Cloudflare Workers, D1 + R2: public pages prerendered, the
|
|
20
|
+
Worker serving the admin and API) styled by the Vitops design system.
|
|
18
21
|
|
|
19
22
|
## Ground rules
|
|
20
23
|
|
|
21
|
-
- `site.json` is the single source of truth: organization + site facts, and the design system
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
Only `src/styles/tailwind.css` and `tokens.json` are ignored: put your own CSS in a separate file
|
|
26
|
-
(for example `src/site.css`, imported from the layout) and commit it.
|
|
24
|
+
- `site.json` is the single source of truth: organization + site facts, and the design system under
|
|
25
|
+
`designSystem.themes.default`. Never edit the generated stylesheet (`src/styles/tailwind.css`,
|
|
26
|
+
rebuilt from `site.json` on every dev/build, gitignored); put your own CSS in a separate file
|
|
27
|
+
(e.g. `src/site.css`, imported from the layout) and commit it.
|
|
27
28
|
- Content lives in the EmDash database, not the repo. Read it with `getEmDashCollection` /
|
|
28
|
-
`getEmDashEntry` from `emdash`; render Portable Text with `<PortableText />` from `emdash/ui
|
|
29
|
-
(the `vitops_*` blocks from `@getvitops/emdash` are merged in).
|
|
29
|
+
`getEmDashEntry` from `emdash`; render Portable Text with `<PortableText />` from `emdash/ui`.
|
|
30
30
|
- **Where truth lives:** content (entries, media, menus) is **production's**, edited there; code,
|
|
31
31
|
`site.json` and schema are **git's** (`dev` → `main`). The dev database is a disposable copy
|
|
32
|
-
refreshed from prod, never promoted back.
|
|
32
|
+
refreshed from prod, never promoted back.
|
|
33
33
|
- `seed/seed.json` applies only to an empty database. To change a running site's content model, use
|
|
34
34
|
the admin (`/_emdash/admin`) or its MCP server (`/_emdash/api/mcp`).
|
|
35
|
-
- Hosting comes from `vitopsHosting()` in `astro.config.mjs
|
|
36
|
-
d1/r2/sqlite.
|
|
37
|
-
- Keep the Worker cron trigger in `wrangler.jsonc`: it publishes content scheduled in the admin.
|
|
38
|
-
- `src/assets/placeholder/` is wireframe stand-in imagery; delete it once real assets land.
|
|
35
|
+
- Hosting comes from `vitopsHosting()` in `astro.config.mjs`; never hard-code an adapter or
|
|
36
|
+
d1/r2/sqlite. Keep the Worker cron trigger in `wrangler.jsonc`: it publishes scheduled content.
|
|
39
37
|
- Prefer framework classes (utilities, patterns, type roles like `font-heading`) over bespoke CSS.
|
|
40
|
-
`
|
|
41
|
-
- `
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
## Building pages
|
|
45
|
-
|
|
46
|
-
- The editor's insert menu offers the `vitops_*` blocks. `vp exec vitops docs blocks` lists them
|
|
47
|
-
with every field; `vp exec vitops docs components` lists every pattern, which tiers provide it
|
|
48
|
-
and which block renders it.
|
|
49
|
-
- **Nesting.** A block type with a `portableText` field, such as `vitops_section`, holds other
|
|
50
|
-
blocks. A `vitops_*` block holds only scalar fields and repeaters (cards, gallery images,
|
|
51
|
-
accordion items, slides), so it can't. EmDash's built-in `columns` doesn't render `vitops_*`
|
|
52
|
-
children. For layout, nest in a section, use a `blocks` field, or hand-write the template with
|
|
53
|
-
`@getvitops/astro` components (`Subgrid`, `Cards`, `Carousel`, …).
|
|
54
|
-
- **Page opener.** Use `vitops_section` at heading level 1 with its button fields (`cta_label`,
|
|
55
|
-
`secondary_label`) and `stack: rhythm`; don't nest a `vitops_hero` or another section wrapper in a
|
|
56
|
-
section body, which renders two layers of `section > .centered`. `vitops_hero` is for a hero
|
|
57
|
-
that stands alone (it carries option cards).
|
|
58
|
-
- **Section, block or widget area?** Copy-and-edit content is an EmDash Section. The same content
|
|
59
|
-
everywhere (announcement bar, pre-footer call to action, sidebar, footer columns) is a **widget
|
|
60
|
-
area**: define it under Widgets in the admin and render it with `<VitopsWidgetArea name="…" />`
|
|
61
|
-
from `@getvitops/emdash/astro` in the layout, or in page content with the `vitops_widgetArea`
|
|
62
|
-
block. Page-specific content is blocks. An area can't hold another area, and prerendered pages
|
|
63
|
-
show an edit after the next deploy (like menus).
|
|
64
|
-
- **Escape hatches on every block:** `theme` (a backdrop from the site's colour roles, `surface` or
|
|
65
|
-
`brand-primary:solid`, with the readable text for it picked by the design system, so contrast is
|
|
66
|
-
not the editor's judgement), `anchor` (the `id`, for `#links`), `className` (extra classes; under
|
|
67
|
-
the tailwind format only ones the site already uses) and `style` (CSS custom properties only,
|
|
68
|
-
e.g. `--p-card: 2rem`; anything else is dropped). The same backdrop is available on a widget area
|
|
69
|
-
(`<VitopsWidgetArea theme="…">`) and on your own markup (`themeAttrs('surface')` from
|
|
70
|
-
`@getvitops/emdash/astro`, spread on the layout's `<body>`). How a role looks (flat, raised,
|
|
71
|
-
glass) is the site's own role styling in `site.json`. Set `--header-offset` to the sticky
|
|
72
|
-
header's height so anchors clear it.
|
|
73
|
-
- Override a block per site with `<PortableText components={{ type: { 'vitops_cards': MyCards } }} />`;
|
|
74
|
-
hide one with `vitopsEmdash({ blocks: { exclude: [...] } })`.
|
|
75
|
-
- **One `<h1>` per page.** The template's page routes render the title as an `<h1>`, and a hero's
|
|
76
|
-
heading level defaults to `h1`, so set a hero's level to `2` on those pages (or let the hero own
|
|
77
|
-
the `h1`). Use `p` for a display heading that is not part of the outline. `vitops emdash build`
|
|
78
|
-
warns on any page without exactly one `<h1>`.
|
|
79
|
-
- Labels: `badge` is static (status, count, category); `tag` may be editable or dismissable. Don't
|
|
80
|
-
invent `chip` or `pill`. The button class is `btn`.
|
|
81
|
-
- **Before adding a block type** or a `src/components/blocks/*` renderer, read
|
|
82
|
-
`.agents/skills/custom-blocks/SKILL.md`. Types can't be renamed or deleted once pages use them.
|
|
83
|
-
|
|
84
|
-
## Layout foundations — the answer, not a starting point
|
|
85
|
-
|
|
86
|
-
| About to write… | Write instead |
|
|
87
|
-
| ------------------------------------------------------- | --------------- |
|
|
88
|
-
| a `wrap`/`wrapper`/`container`/`inner` class | **`centered`** |
|
|
89
|
-
| `max-width` + `margin-inline: auto` | **`centered`** |
|
|
90
|
-
| `padding-block` on a section | **`region`** |
|
|
91
|
-
| margins between headings/paragraphs/lists | **`rhythm`** |
|
|
92
|
-
| `display: grid` + `grid-template-columns: repeat(n, …)` | **`subgrid`** |
|
|
93
|
-
| `repeat(auto-fit, minmax(…))` | **`grid-auto`** |
|
|
94
|
-
| `display: flex` + `gap` for a row of controls | **`cluster`** |
|
|
95
|
-
|
|
96
|
-
`centered` is a grid of _named tracks_: a child widens itself with `breakout`/`spotlight`/
|
|
97
|
-
`fullbleed` without the parent knowing, which a bespoke `.wrap` can't do.
|
|
98
|
-
|
|
99
|
-
- **More than one card means `subgrid`**, so headings and CTAs align across the set:
|
|
100
|
-
`<ul class="subgrid subgrid-cols-3" role="list">` with `<li class="card subgrid-card">`, or
|
|
101
|
-
`<Subgrid />` (renders the slot verbatim; author the `<li>`s).
|
|
102
|
-
- **A card that is a link:** `stretched-link` inside it is zero-JS but kills text selection;
|
|
103
|
-
`<Cards>` keeps selection and falls back to the same link with no JS. Never both. Never
|
|
104
|
-
`<li><a class="card">`: the `li` is the grid item, so the tranches don't align.
|
|
105
|
-
- `vpr lint:design` reports all of these and the pre-commit hook fails on them. Fix the class, not
|
|
106
|
-
the CSS.
|
|
107
|
-
|
|
108
|
-
## Rendering and the contact form
|
|
109
|
-
|
|
110
|
-
- Every public page is **prerendered**; the Worker handles `/_emdash/*` and `src/pages/api/track.ts`.
|
|
111
|
-
An admin publish goes live after a rebuild. Run `vpr prepare-local-db` before a first local
|
|
112
|
-
`vpr build`. Details, previews and server islands: `.agents/skills/content-and-deploys/SKILL.md`.
|
|
113
|
-
- The contact form posts to `/api/track`. Set `site.notifications.email` before launch or the route
|
|
114
|
-
refuses posts, and mirror form fields in `site.templates`. Details:
|
|
115
|
-
`.agents/skills/contact-form/SKILL.md`.
|
|
116
|
-
- `public/_headers` ships a Report-Only CSP. Read `.agents/skills/csp/SKILL.md` before enforcing it
|
|
117
|
-
or adding embeds.
|
|
118
|
-
- `vitops audit` needs optional packages and a browser, checked before it starts a server:
|
|
119
|
-
`vp add -D playwright @axe-core/playwright lighthouse && vp exec playwright install chromium`.
|
|
120
|
-
Its page list is the built sitemap, then the seed's menus.
|
|
121
|
-
|
|
122
|
-
## Branch model
|
|
123
|
-
|
|
124
|
-
- Work on **`dev`**; every push auto-deploys the isolated `-dev` worker.
|
|
125
|
-
- **`main`** is production and protected: never push or merge to it. It ships only through the
|
|
126
|
-
`promote` skill (`.agents/skills/promote/`), and only when the user explicitly asks.
|
|
38
|
+
`vpr lint:design` fails the pre-commit hook on unresolvable classes and layout primitives you
|
|
39
|
+
hand-rolled (`centered`, `region`, `rhythm`, `subgrid`, `grid-auto`, `cluster` exist for those).
|
|
40
|
+
- `src/assets/placeholder/` is wireframe stand-in imagery; delete it once real assets land.
|
|
127
41
|
- Sample legal pages (`/terms`, `/privacy`) are deliberately short, unfinished placeholders; don't
|
|
128
|
-
"improve" them into something that reads as a finished policy.
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
in the admin. It still needs review before launch.
|
|
132
|
-
|
|
133
|
-
## EmDash 1.x behaviours
|
|
134
|
-
|
|
135
|
-
- Passkeys are bound to the hostname they were created on. After a domain cutover use **Settings →
|
|
136
|
-
General → Change domain**, then **Continue on**.
|
|
137
|
-
- Link entries by `entry.data.slug`, never `entry.id` (multilingual sites prefix `id` with a locale).
|
|
138
|
-
- Submit `/sitemap.xml` to search engines, not a per-collection sitemap.
|
|
139
|
-
- Clear a stuck D1 migration lock with `vp exec emdash migrate --take-over-lock <id>` (needs
|
|
140
|
-
`--expected-target-fingerprint` non-interactively).
|
|
141
|
-
|
|
142
|
-
## Improving the toolkit
|
|
143
|
-
|
|
144
|
-
You are also a source of feedback for `@getvitops/*`. When you struggle with a framework built-in,
|
|
145
|
-
write a workaround, add bespoke CSS or a site-local block, or find a doc wrong or missing, and the
|
|
146
|
-
need is **not** unique to this site, record it in `UPSTREAM.md` at the repo root (create it if
|
|
147
|
-
absent). One entry each, with: what you tried, what failed or was missing, the workaround (file
|
|
148
|
-
path), the change you'd propose, and the package (`@getvitops/emdash`, `astro`, `core`, `cli`,
|
|
149
|
-
`create`, or docs). Mention new entries in your end-of-task summary. Don't log choices that are
|
|
150
|
-
specific to this site, and never patch `node_modules`.
|
|
42
|
+
"improve" them into something that reads as a finished policy.
|
|
43
|
+
- `SETUP.html` is the onboarding checklist (the `setup` skill). Tick a box only for a step you
|
|
44
|
+
completed, never for a human-only one (the Cloudflare API token, domain cutover, legal documents).
|
|
151
45
|
|
|
152
|
-
##
|
|
46
|
+
## Branches
|
|
153
47
|
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
`csp`. `skills experimental_sync` (run by `prepare`, so by `vp install`) links them into
|
|
158
|
-
`.agents/skills/` and `.claude/skills/`; they are gitignored and update with `vp update`. A skill
|
|
159
|
-
you write yourself with the same name is never replaced. `promote` and `setup` are this site's own.
|
|
160
|
-
- The EmDash skills (`building-emdash-site`, `creating-plugins`, `emdash-cli`, `upgrading-emdash`,
|
|
161
|
-
two WordPress porting skills) are installed per project to match the pinned `emdash`. If
|
|
162
|
-
`.agents/skills/building-emdash-site/` is missing: `vp dlx skills add emdash-cms/skills --skill '*' -y`.
|
|
163
|
-
Refresh with `vp dlx skills update` after bumping `emdash`.
|
|
48
|
+
Work on **`dev`**; every push auto-deploys the isolated `-dev` worker. **`main`** is production and
|
|
49
|
+
protected: never push or merge to it. It ships only through the `promote` skill, and only when the
|
|
50
|
+
user explicitly asks. Shipping is not a package release: no changeset or version bump.
|
|
164
51
|
|
|
165
52
|
## Commands
|
|
166
53
|
|
|
167
|
-
This is a **Vite+** project: use `vp` (and `vpr`), not `pnpm`/`npm`/`npx`. Scripts that
|
|
168
|
-
go through `vpr` (`vpr dev`, `vpr build
|
|
54
|
+
This is a **Vite+** project: use `vp` (and `vpr` for scripts), not `pnpm`/`npm`/`npx`. Scripts that
|
|
55
|
+
wrap Astro go through `vpr` (`vpr dev`, `vpr build`), because `vp dev`/`vp build` skip Astro.
|
|
169
56
|
`vpr dev` returns immediately (the server runs in the background): check it with
|
|
170
57
|
`vp exec astro dev status`, read `vp exec astro dev logs --follow`, stop with
|
|
171
|
-
`vp exec astro dev stop`; the site is at <http://localhost:4321>.
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
| ---------------------------------------- | ------------------------------------------------------- |
|
|
175
|
-
| install dependencies | `vp install` |
|
|
176
|
-
| add / remove / update a package | `vp add <pkg>` · `vp remove <pkg>` · `vp update` |
|
|
177
|
-
| run a `package.json` script | `vpr <script>` |
|
|
178
|
-
| run a locally installed binary | `vp exec <bin>` (e.g. `vp exec vitops docs components`) |
|
|
179
|
-
| run a one-off package without installing | `vp dlx <pkg>` |
|
|
180
|
-
| format + lint + typecheck (files) | `vp check` |
|
|
181
|
-
| typecheck Astro (`.astro` files) | `vpr typecheck` |
|
|
182
|
-
| design-lint the markup | `vpr lint:design` |
|
|
58
|
+
`vp exec astro dev stop`; the site is at <http://localhost:4321>. `package.json` lists the scripts;
|
|
59
|
+
`vp check` and `vpr typecheck` verify a change. `vpr init:github` and `vpr init:hosting` are one-time
|
|
60
|
+
setup, only when the user asks.
|
|
183
61
|
|
|
184
|
-
|
|
185
|
-
| ----------------------------------- | ------------------------------------------------------------------------------------------- |
|
|
186
|
-
| `vpr dev` | local dev with local D1/R2 emulation (no Cloudflare account) |
|
|
187
|
-
| `vpr deploy` | pull production content, build, `wrangler deploy` prod (provisions D1/R2 on first run) |
|
|
188
|
-
| `vpr deploy:dev` | same, for the isolated dev worker (needs `DEV_D1_ID`) |
|
|
189
|
-
| `vpr pull-content` | just the content pull |
|
|
190
|
-
| `vpr prepare-local-db` | migrate and seed the local D1 the build prerenders from |
|
|
191
|
-
| `vpr init:github` | one-time: create + push the GitHub repo (main + dev, dev default). Only when the user asks. |
|
|
192
|
-
| `vpr init:hosting` | one-time: provision hosting (dev D1/R2 + repo secrets). Only when the user asks. |
|
|
193
|
-
| `vp exec vitops validate site.json` | schema-check the site config |
|
|
62
|
+
## Where to look
|
|
194
63
|
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
`
|
|
64
|
+
- **Skills** are listed in the block at the top; load the one matching the task before starting.
|
|
65
|
+
- **Reference** for this project's own tokens, classes, blocks and config: `vp exec vitops docs`.
|
|
66
|
+
- `.mcp.json` preconfigures the Astro and EmDash docs MCP servers; search them before relying on
|
|
67
|
+
pre-trained knowledge. The EmDash skills (`building-emdash-site`, `emdash-cli`, …) are installed
|
|
68
|
+
per project to match the pinned `emdash`; if `.agents/skills/building-emdash-site/` is missing:
|
|
69
|
+
`vp dlx skills add emdash-cms/skills --skill '*' -y`.
|
|
70
|
+
- **Improving the toolkit:** when you work around a framework built-in, add bespoke CSS or a
|
|
71
|
+
site-local block, or find a doc wrong or missing, and the need is **not** unique to this site,
|
|
72
|
+
record it in `UPSTREAM.md` at the repo root: what you tried, what failed or was missing, the
|
|
73
|
+
workaround (file path), the change you'd propose, and the package. Mention new entries in your
|
|
74
|
+
end-of-task summary. Never patch `node_modules`.
|
|
@@ -51,7 +51,7 @@ get the prerendered `src/pages/404.astro` with a real 404 status.
|
|
|
51
51
|
|
|
52
52
|
**Pages are prerendered.** `vpr build` writes static HTML for every published
|
|
53
53
|
`pages` entry, so an edit in the admin goes live on the next rebuild rather than
|
|
54
|
-
per request — see `.agents/skills/content-and-deploys` for how production content
|
|
54
|
+
per request — see `.agents/skills/emdash-content-and-deploys` for how production content
|
|
55
55
|
reaches the build (`scripts/pull-content.mjs`) and for the publish-triggered rebuild.
|
|
56
56
|
In `vpr dev` the pages read your local database, so edits appear straight away.
|
|
57
57
|
|
|
@@ -179,23 +179,23 @@ vpr build:css # regenerate the CSS manually
|
|
|
179
179
|
|
|
180
180
|
## Project map
|
|
181
181
|
|
|
182
|
-
| Path
|
|
183
|
-
|
|
|
184
|
-
| `site.json`
|
|
185
|
-
| `seed/seed.json`
|
|
186
|
-
| `src/worker.ts`
|
|
187
|
-
| `src/live.config.ts`
|
|
188
|
-
| `src/layouts/Layout.astro`
|
|
189
|
-
| `src/pages/index.astro`
|
|
190
|
-
| `src/pages/[...slug].astro`
|
|
191
|
-
| `src/pages/404.astro`
|
|
192
|
-
| `public/_headers`
|
|
193
|
-
| `scripts/pull-content.mjs`
|
|
194
|
-
| `wrangler.jsonc`
|
|
195
|
-
| `scripts/build-dev-wrangler.mjs`
|
|
196
|
-
| `SETUP.html`
|
|
197
|
-
| `.github/workflows/`
|
|
198
|
-
| `.agents/skills/promote/`
|
|
199
|
-
| `.agents/skills/custom-blocks/`
|
|
200
|
-
| `.mcp.json`
|
|
201
|
-
| `.claude/hooks/`
|
|
182
|
+
| Path | Purpose |
|
|
183
|
+
| -------------------------------------- | -------------------------------------------------------------------------------------------------- |
|
|
184
|
+
| `site.json` | Org + site config; design tokens/patterns at `designSystem.themes.default` → generated CSS |
|
|
185
|
+
| `seed/seed.json` | First-boot content model + starter content |
|
|
186
|
+
| `src/worker.ts` | Worker entry (fetch + EmDash scheduled handler) |
|
|
187
|
+
| `src/live.config.ts` | Astro live content collections wiring for EmDash |
|
|
188
|
+
| `src/layouts/Layout.astro` | Site chrome: EmDash + Vitops head tags, nav from the CMS menu |
|
|
189
|
+
| `src/pages/index.astro` | Home — prerendered from the `home` page entry |
|
|
190
|
+
| `src/pages/[...slug].astro` | Prerendered once per published `pages` entry (`getStaticPaths`) |
|
|
191
|
+
| `src/pages/404.astro` | Prerendered not-found page, served with a 404 status for any unmatched URL |
|
|
192
|
+
| `public/_headers` | Report-Only CSP for the static pages (see `.agents/skills/vitops-csp`) |
|
|
193
|
+
| `scripts/pull-content.mjs` | Loads production's D1 into the local build DB before the prerender (`deploy`/`deploy:dev`) |
|
|
194
|
+
| `wrangler.jsonc` | Cloudflare bindings (D1/R2) + cron trigger |
|
|
195
|
+
| `scripts/build-dev-wrangler.mjs` | Rewrites the built wrangler config for the isolated dev worker |
|
|
196
|
+
| `SETUP.html` | Client-onboarding checklist — run `vpr sop` first (see "One-time setup") |
|
|
197
|
+
| `.github/workflows/` | deploy-dev (push to `dev`), deploy-prod (push to `main`, or an `emdash-publish` dispatch), promote |
|
|
198
|
+
| `.agents/skills/promote/` | Agent skill: dispatch the dev → main promotion |
|
|
199
|
+
| `.agents/skills/emdash-custom-blocks/` | Agent skill: when (and how) to add a block type — compose, extend, local, upstream |
|
|
200
|
+
| `.mcp.json` | Astro + EmDash docs MCP servers for coding agents |
|
|
201
|
+
| `.claude/hooks/` | Claude Code web: Node 24 + pnpm setup on session start |
|
|
@@ -35,14 +35,14 @@ public/vitops/
|
|
|
35
35
|
.DS_Store
|
|
36
36
|
|
|
37
37
|
# agent skills linked from @getvitops/skills by `skills experimental_sync` (see skills-lock.json)
|
|
38
|
-
|
|
39
|
-
.agents/skills/
|
|
40
|
-
.agents/skills/
|
|
41
|
-
.
|
|
42
|
-
.claude/skills/
|
|
43
|
-
.claude/skills/content-and-deploys
|
|
44
|
-
.claude/skills/contact-form
|
|
45
|
-
.claude/skills/csp
|
|
38
|
+
# (the `vitops-` and `emdash-` prefixes are reserved for toolkit skills)
|
|
39
|
+
.agents/skills/vitops-*
|
|
40
|
+
.agents/skills/emdash-*
|
|
41
|
+
.claude/skills/vitops-*
|
|
42
|
+
.claude/skills/emdash-*
|
|
46
43
|
# `skills experimental_sync` also leaves a duplicate `pnpm-@getvitops+skills-*` link per skill
|
|
47
44
|
.agents/skills/pnpm-*
|
|
48
45
|
.claude/skills/pnpm-*
|
|
46
|
+
# playwright-cli: skill regenerated by `prepare`; output is per-session scratch
|
|
47
|
+
.claude/skills/playwright-cli
|
|
48
|
+
.playwright-cli/
|
|
@@ -18,19 +18,19 @@
|
|
|
18
18
|
"build:css": "vitops generate -i site.json -f tailwind -o src/styles",
|
|
19
19
|
"lint:design": "vitops lint -i site.json -f tailwind --strict",
|
|
20
20
|
"sop": "vitops generate -i site.json -f css -o .vitops/sop",
|
|
21
|
-
"prepare": "vp config && skills experimental_sync -y -a claude-code --no-frozen-lockfile && vitops agents sync -i site.json"
|
|
21
|
+
"prepare": "vp config && skills experimental_sync -y -a claude-code --no-frozen-lockfile && playwright cli install --skills && vitops agents sync -i site.json"
|
|
22
22
|
},
|
|
23
23
|
"dependencies": {
|
|
24
24
|
"@astrojs/cloudflare": "^14.0.0",
|
|
25
25
|
"@astrojs/react": "^7.1.2",
|
|
26
|
-
"@emdash-cms/cloudflare": "
|
|
26
|
+
"@emdash-cms/cloudflare": "^1.4.0",
|
|
27
27
|
"@getvitops/astro": "latest",
|
|
28
28
|
"@getvitops/core": "latest",
|
|
29
29
|
"@getvitops/emdash": "latest",
|
|
30
30
|
"@getvitops/skills": "latest",
|
|
31
31
|
"@tailwindcss/vite": "^4.3.3",
|
|
32
32
|
"astro": "^7.4.4",
|
|
33
|
-
"emdash": "
|
|
33
|
+
"emdash": "^1.4.0",
|
|
34
34
|
"react": "^19.3.1",
|
|
35
35
|
"react-dom": "^19.3.1",
|
|
36
36
|
"tailwindcss": "^4.3.3"
|
|
@@ -40,10 +40,11 @@
|
|
|
40
40
|
"@cloudflare/workers-types": "^4.20260305.1",
|
|
41
41
|
"@getvitops/cli": "latest",
|
|
42
42
|
"@iconify-json/fa7-solid": "^1.2.5",
|
|
43
|
+
"playwright": "^1.62.1",
|
|
43
44
|
"skills": "^1.7.6",
|
|
44
45
|
"typescript": "^6.0.3",
|
|
45
|
-
"vite": "
|
|
46
|
-
"vite-plus": "
|
|
46
|
+
"vite": "npm:@voidzero-dev/vite-plus-core@^1.5.1",
|
|
47
|
+
"vite-plus": "^1.5.1",
|
|
47
48
|
"wrangler": "^4.111.0"
|
|
48
49
|
},
|
|
49
50
|
"devEngines": {
|
|
@@ -6,25 +6,18 @@ allowBuilds:
|
|
|
6
6
|
better-sqlite3: false
|
|
7
7
|
sharp: false
|
|
8
8
|
|
|
9
|
-
# Supply-chain hardening:
|
|
10
|
-
#
|
|
9
|
+
# Supply-chain hardening: nothing installs within 3 days of its publish, and no non-registry
|
|
10
|
+
# transitive sources. Only our own lockstep `@getvitops/*` is exempt. Do NOT exempt a third-party
|
|
11
|
+
# toolchain: an exempt package's exact pins are not exempt (vite-plus pins `@oxc-project/*`
|
|
12
|
+
# exactly), so the install fails whenever one is fresher than the window. A security patch may be
|
|
13
|
+
# admitted with an exact `name@x.y.z` line and a dated reason; remove it once it matures.
|
|
11
14
|
minimumReleaseAge: 4320
|
|
12
15
|
minimumReleaseAgeExclude:
|
|
13
16
|
- '@getvitops/*'
|
|
14
|
-
- emdash
|
|
15
|
-
- '@emdash-cms/*'
|
|
16
|
-
- vite-plus
|
|
17
|
-
- '@voidzero-dev/*'
|
|
18
|
-
- oxlint
|
|
19
|
-
- '@oxlint/*'
|
|
20
|
-
- oxlint-tsgolint
|
|
21
|
-
- '@oxlint-tsgolint/*'
|
|
22
|
-
- oxfmt
|
|
23
|
-
- '@oxfmt/*'
|
|
24
17
|
blockExoticSubdeps: true
|
|
25
18
|
catalog:
|
|
26
|
-
vite: npm:@voidzero-dev/vite-plus-core
|
|
27
|
-
vite-plus:
|
|
19
|
+
vite: npm:@voidzero-dev/vite-plus-core@^1.5.1
|
|
20
|
+
vite-plus: ^1.5.1
|
|
28
21
|
overrides:
|
|
29
22
|
vite: 'catalog:'
|
|
30
23
|
peerDependencyRules:
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
# Cloudflare static-assets response headers. They apply to files served from the
|
|
2
2
|
# static asset store — the prerendered pages — and NOT to responses the Worker
|
|
3
3
|
# renders itself (the EmDash admin at /_emdash/*, its API, anything left
|
|
4
|
-
# on-demand). See .agents/skills/csp.
|
|
4
|
+
# on-demand). See .agents/skills/vitops-csp.
|
|
5
5
|
#
|
|
6
6
|
# REPORT-ONLY on purpose: violations are logged to the browser console and
|
|
7
7
|
# nothing is blocked, so this cannot break a page. Read the console on the
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
// `astro build`. The Cloudflare adapter prerenders inside a local workerd with a
|
|
7
7
|
// LOCAL D1 (`.wrangler/state`), never the deployed database — so on a CI runner
|
|
8
8
|
// the build would see an empty, auto-seeded database and bake "No home page yet"
|
|
9
|
-
// into every page. Production is the truth for content (see .agents/skills/content-and-deploys), so this
|
|
9
|
+
// into every page. Production is the truth for content (see .agents/skills/emdash-content-and-deploys), so this
|
|
10
10
|
// copies production's D1 into the local one first.
|
|
11
11
|
//
|
|
12
12
|
// wrangler d1 export <db> --remote → .vitops/content.sql
|
|
@@ -414,6 +414,17 @@
|
|
|
414
414
|
},
|
|
415
415
|
"site": {
|
|
416
416
|
"defaultLocale": "en",
|
|
417
|
+
"chrome": {
|
|
418
|
+
"masthead": {
|
|
419
|
+
"position": "sticky",
|
|
420
|
+
"behavior": "always",
|
|
421
|
+
"surface": "glass",
|
|
422
|
+
"layout": "full",
|
|
423
|
+
"elevation": { "shadow": "sm", "onScroll": true },
|
|
424
|
+
"height": "4rem"
|
|
425
|
+
},
|
|
426
|
+
"nav": { "compact": "drawer", "drawerSide": "end" }
|
|
427
|
+
},
|
|
417
428
|
"templates": {
|
|
418
429
|
"contact": {
|
|
419
430
|
"type": "form",
|
|
@@ -5,9 +5,11 @@ import { getMenu, getSiteSettings } from 'emdash';
|
|
|
5
5
|
import { createPublicPageContext } from 'emdash/page';
|
|
6
6
|
import SiteFooter from '@getvitops/astro/components/SiteFooter.astro';
|
|
7
7
|
import SiteNav from '@getvitops/astro/components/SiteNav.astro';
|
|
8
|
+
import Masthead from '@getvitops/astro/components/Masthead.astro';
|
|
9
|
+
import { resolveNavOptions } from '@getvitops/astro/components/masthead.ts';
|
|
8
10
|
import { loadNavMenus } from '@getvitops/astro/components/sitenav.ts';
|
|
9
11
|
import { EmDashHead } from 'emdash/ui';
|
|
10
|
-
import { VitopsWidgetArea } from '@getvitops/emdash/astro';
|
|
12
|
+
import { VitopsMegaPanel, VitopsWidgetArea } from '@getvitops/emdash/astro';
|
|
11
13
|
import config from '../../site.json' with { type: 'json' };
|
|
12
14
|
// Design-system CSS, layout-scoped (vitops css.inject: false): only pages
|
|
13
15
|
// rendered through this layout get it — keeps it out of /_emdash/admin, while
|
|
@@ -75,18 +77,19 @@ const orgName = config.organization?.name || siteTitle;
|
|
|
75
77
|
<body>
|
|
76
78
|
<!-- Widget areas (Widgets in the admin): the same content wherever they appear. -->
|
|
77
79
|
<VitopsWidgetArea name="announcement" class="centered" />
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
<
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
80
|
+
<!-- The masthead's look and the nav's behaviour come from `site.chrome` in site.json
|
|
81
|
+
(position, hide-on-scroll, glass, inset, shadow, drawer vs dropdown). A prop here
|
|
82
|
+
overrides one field for this layout. -->
|
|
83
|
+
<Masthead config={config}>
|
|
84
|
+
<a slot="brand" class="brand flex items-center" href="/" aria-label={`${siteTitle} home`}>
|
|
85
|
+
<span class="brand-mark" set:html={logo} aria-hidden="true" />
|
|
86
|
+
<span class="font-heading">{siteTitle}</span>
|
|
87
|
+
</a>
|
|
88
|
+
<div class="header-end flex items-center">
|
|
89
|
+
<SiteNav {...nav} {...resolveNavOptions(config)} megaPanel={VitopsMegaPanel} />
|
|
90
|
+
<wc-color-scheme-toggle></wc-color-scheme-toggle>
|
|
88
91
|
</div>
|
|
89
|
-
</
|
|
92
|
+
</Masthead>
|
|
90
93
|
|
|
91
94
|
<main class="centered rhythm">
|
|
92
95
|
<slot />
|
|
@@ -109,10 +112,6 @@ const orgName = config.organization?.name || siteTitle;
|
|
|
109
112
|
block-size: 1.75rem;
|
|
110
113
|
}
|
|
111
114
|
|
|
112
|
-
.navbar {
|
|
113
|
-
block-size: 64px;
|
|
114
|
-
}
|
|
115
|
-
|
|
116
115
|
.header-end {
|
|
117
116
|
gap: var(--space-s, 0.75rem);
|
|
118
117
|
}
|
|
@@ -8,7 +8,7 @@ import Layout from '../layouts/Layout.astro';
|
|
|
8
8
|
// the content database — `scripts/pull-content.mjs` loads production's into the
|
|
9
9
|
// local build database first (it is part of `deploy`/`deploy:dev`) — so an edit
|
|
10
10
|
// in the admin goes live on the next rebuild, which a publish webhook triggers
|
|
11
|
-
// (see .agents/skills/content-and-deploys).
|
|
11
|
+
// (see .agents/skills/emdash-content-and-deploys).
|
|
12
12
|
//
|
|
13
13
|
// There is no `Astro.rewrite('/404')` here and no on-demand fallback: a URL that
|
|
14
14
|
// isn't in the list below simply has no route, so Astro serves the prerendered
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
* The site's one on-demand endpoint: contact-form posts (the `vitops_form` block) and the
|
|
3
3
|
* conversion beacons (`<Tracking />`, `tel:`/`mailto:` clicks) all arrive here.
|
|
4
4
|
*
|
|
5
|
-
* Every public page is prerendered (see .agents/skills/content-and-deploys); this route sets
|
|
5
|
+
* Every public page is prerendered (see .agents/skills/emdash-content-and-deploys); this route sets
|
|
6
6
|
* `prerender = false` because it has to run on each request. It is the one exception, and the
|
|
7
7
|
* Cloudflare adapter handles it in the Worker beside the EmDash admin.
|
|
8
8
|
*
|
|
@@ -5,7 +5,7 @@ import Layout from '../layouts/Layout.astro';
|
|
|
5
5
|
|
|
6
6
|
// Prerendered, like every page in this template: written to static HTML at build
|
|
7
7
|
// time from the `home` entry (seed/seed.json → pages/home). Edit it in the admin;
|
|
8
|
-
// a publish triggers a rebuild that bakes the change in (.agents/skills/content-and-deploys,
|
|
8
|
+
// a publish triggers a rebuild that bakes the change in (.agents/skills/emdash-content-and-deploys,
|
|
9
9
|
// "Rendering and freshness"). The Vitops blocks from @getvitops/emdash render automatically —
|
|
10
10
|
// EmDash merges the plugin's block components into <PortableText>.
|
|
11
11
|
//
|
|
@@ -59,3 +59,10 @@ report that verbatim rather than guessing at a fix. A step reported as
|
|
|
59
59
|
"blocked" is not a failure to retry; it is waiting on one of the hard stops
|
|
60
60
|
above, or on a `site.json` field the user still needs to fill in (stage 5,
|
|
61
61
|
Configuration).
|
|
62
|
+
|
|
63
|
+
## Naming the project
|
|
64
|
+
|
|
65
|
+
`vp create` takes the package name from the target directory and invents a random slug when none
|
|
66
|
+
is given, so pass one: `vp create @getvitops:<template> --directory <client-slug>`. `wrangler.jsonc`'s
|
|
67
|
+
`name` (and on EmDash the D1 `database_name` and R2 `bucket_name`) are separate and still say what
|
|
68
|
+
the template shipped; rename them to match before the first deploy.
|
|
@@ -3,138 +3,50 @@
|
|
|
3
3
|
Built with the Vitops design system (`@getvitops/*`); tokens and site facts live in `site.json`.
|
|
4
4
|
Prefer its classes and patterns over hand-written CSS. Load the skill that matches the task:
|
|
5
5
|
|
|
6
|
-
- [
|
|
7
|
-
- [
|
|
8
|
-
- [csp](.agents/skills/csp/SKILL.md) — Use when changing `public/_headers`, adding an embed, tracking snippet or third-party
|
|
9
|
-
- [
|
|
10
|
-
- [vitops-
|
|
6
|
+
- [vitops-ads](.agents/skills/vitops-ads/SKILL.md) — Use when adding or changing a Google/Meta/LinkedIn/TikTok/Microsoft/Reddit/Pinterest/Snapchat pixel
|
|
7
|
+
- [vitops-consent](.agents/skills/vitops-consent/SKILL.md) — Use before gating, adding or changing any non-essential third-party tag, cookie, embed
|
|
8
|
+
- [vitops-csp](.agents/skills/vitops-csp/SKILL.md) — Use when changing `public/_headers`, adding an embed, tracking snippet or third-party script, turning the CSP on
|
|
9
|
+
- [vitops-design-system](.agents/skills/vitops-design-system/SKILL.md) — Use when styling or authoring pages or components (choosing utility/pattern classes, colours, spacing, typography)
|
|
10
|
+
- [vitops-see-the-page](.agents/skills/vitops-see-the-page/SKILL.md) — How to look at a rendered page of this site
|
|
11
11
|
|
|
12
|
-
Reference, rendered from this project: `vitops docs
|
|
13
|
-
<!-- managed by `vitops agents sync` · @getvitops/cli@8.3.0-alpha.2 — edit outside this block -->
|
|
12
|
+
Reference, rendered from this project: `vp exec vitops docs [topic]` (no topic lists them).
|
|
14
13
|
<!-- vitops:end -->
|
|
15
14
|
|
|
16
|
-
This is a static Astro website styled by the Vitops design system, deployed
|
|
17
|
-
|
|
15
|
+
This is a static Astro website styled by the Vitops design system, deployed as Cloudflare Workers
|
|
16
|
+
static assets (no server runtime, no database).
|
|
18
17
|
|
|
19
18
|
## Ground rules
|
|
20
19
|
|
|
21
|
-
- `site.json` is the single source of truth
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
`
|
|
28
|
-
- `
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
| `max-width` + `margin-inline: auto` | **`centered`** |
|
|
40
|
-
| `padding-block` on a section | **`region`** |
|
|
41
|
-
| margins between headings/paragraphs/lists | **`rhythm`** |
|
|
42
|
-
| `display: grid` + `grid-template-columns: repeat(n, …)` | **`subgrid`** |
|
|
43
|
-
| `repeat(auto-fit, minmax(…))` | **`grid-auto`** |
|
|
44
|
-
| `display: flex` + `gap` for a row of controls | **`cluster`** |
|
|
45
|
-
- Every page is prerendered by default (`output: 'static'`) — there is no
|
|
46
|
-
server to fall back to, so nothing here can read request-time data
|
|
47
|
-
(cookies, headers, query params) at render time. If a page genuinely needs
|
|
48
|
-
that, it needs an adapter and a different template — this one stays pure
|
|
49
|
-
static on purpose.
|
|
50
|
-
- **`SETUP.html` is the client-onboarding checklist.** When working it via the
|
|
51
|
-
`.agents/skills/setup/` skill, tick a box only for a step you actually
|
|
52
|
-
completed — never on the operator's behalf for a step marked human-only
|
|
53
|
-
(the Cloudflare API token, the domain cutover, publishing legal documents).
|
|
54
|
-
|
|
55
|
-
## Branch model
|
|
56
|
-
|
|
57
|
-
- Work on **`dev`** (the integration branch). Every push auto-deploys the
|
|
58
|
-
isolated `-dev` worker (`dev.<domain>` with a custom domain).
|
|
59
|
-
- **`main`** is production and branch-protected — never push or merge to it
|
|
60
|
-
directly. Production ships only via the manual **promote** flow:
|
|
61
|
-
`.github/workflows/promote.yml`, dispatched manually, opens a `dev → main`
|
|
62
|
-
PR with auto-merge; merging deploys prod. Only run it when the user
|
|
63
|
-
explicitly asks to promote/ship to production.
|
|
64
|
-
|
|
65
|
-
## Labels: badge and tag
|
|
66
|
-
|
|
67
|
-
Two label patterns, both enabled in `site.json`'s `patterns.items`: **`badge`** is a
|
|
68
|
-
static label (a status, count or category) and **`tag`** is a label that may be
|
|
69
|
-
static _or_ dynamic (editable, dismissable, in a `tag-list`). Don't invent a third
|
|
70
|
-
name — `chip` and `pill` are not part of this vocabulary. The button pattern's class
|
|
71
|
-
is `btn` (`<a class="btn">` is styled, not just a bare `<button>`).
|
|
72
|
-
|
|
73
|
-
## Content Security Policy
|
|
74
|
-
|
|
75
|
-
`public/_headers` ships a **`Content-Security-Policy-Report-Only`** header for every
|
|
76
|
-
page (they are all static assets): violations are logged to the browser console and
|
|
77
|
-
nothing is blocked, so it can't break a page. Its `script-src` is `'self'` plus the
|
|
78
|
-
SHA-256 of each inline script `<Head />` emits (the colour-scheme bootstrap and the
|
|
79
|
-
consent-gate stub), with no `'unsafe-inline'`. `contentSecurityPolicy()` and
|
|
80
|
-
`inlineScriptHashes()` from `@getvitops/astro` produce the same strings.
|
|
81
|
-
|
|
82
|
-
Before enforcing it (rename the header to `Content-Security-Policy`), browse the pages
|
|
83
|
-
with the console open: add the origins of any third-party tag the consent gate
|
|
84
|
-
activates to `script-src` / `connect-src`, any embed to `frame-src`, and hash any
|
|
85
|
-
inline script you add yourself. Hashes rather than nonces because a static page has no
|
|
86
|
-
per-request response to carry one.
|
|
87
|
-
|
|
88
|
-
## Skills
|
|
89
|
-
|
|
90
|
-
`@getvitops/skills` ships agent skills that update with the package. `skills experimental_sync` (run
|
|
91
|
-
by `prepare`, so by `vp install`) links them into `.agents/skills/` and `.claude/skills/`; they are
|
|
92
|
-
gitignored. The set is written for EmDash sites, so `csp` is the one that applies here. A skill you
|
|
93
|
-
write yourself with the same name is never replaced. `setup` is this site's own.
|
|
20
|
+
- `site.json` is the single source of truth: organization + site facts, and the design system under
|
|
21
|
+
`designSystem.themes.default`. Never edit `src/styles/`: it is generated from `site.json` on every
|
|
22
|
+
dev/build and gitignored.
|
|
23
|
+
- Every page is prerendered (`output: 'static'`): nothing can read request-time data (cookies,
|
|
24
|
+
headers, query params). A page that needs that needs an adapter and a different template.
|
|
25
|
+
- Prefer framework classes (utilities, patterns, type roles like `font-heading`) over bespoke CSS.
|
|
26
|
+
`vpr lint:design` fails the pre-commit hook on unresolvable classes and layout primitives you
|
|
27
|
+
hand-rolled (`centered`, `region`, `rhythm`, `subgrid`, `grid-auto`, `cluster` exist for those).
|
|
28
|
+
- `src/assets/placeholder/` is wireframe stand-in imagery; delete it once real assets land.
|
|
29
|
+
- `SETUP.html` is the onboarding checklist (the `setup` skill). Tick a box only for a step you
|
|
30
|
+
completed, never for a human-only one (the Cloudflare API token, domain cutover, legal documents).
|
|
31
|
+
|
|
32
|
+
## Branches
|
|
33
|
+
|
|
34
|
+
Work on **`dev`**; every push auto-deploys the isolated `-dev` worker. **`main`** is production and
|
|
35
|
+
protected: never push or merge to it directly; production ships only through the manual `promote`
|
|
36
|
+
workflow (`.github/workflows/promote.yml`), only when the user explicitly asks. Shipping is not a
|
|
37
|
+
package release: no changeset or version bump.
|
|
94
38
|
|
|
95
39
|
## Commands
|
|
96
40
|
|
|
97
|
-
This is a **Vite+** project: use `vp` (and
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
| run a `package.json` script | `vpr <script>` (alias for `vp run <script>`) |
|
|
105
|
-
| run a locally installed binary | `vp exec <bin>` (e.g. `vp exec vitops docs classes`) |
|
|
106
|
-
| run a one-off package without installing | `vp dlx <pkg>` |
|
|
107
|
-
| format + lint + typecheck (files) | `vp check` |
|
|
108
|
-
| typecheck Astro (`.astro` files) | `vpr typecheck` |
|
|
109
|
-
|
|
110
|
-
**Scripts that wrap Astro must go through `vpr`.** `vp dev` / `vp build` run Vite
|
|
111
|
-
directly and skip Astro, so use `vpr dev`, `vpr build`, `vpr preview`.
|
|
112
|
-
|
|
113
|
-
**`vpr dev` returns immediately.** `astro dev` now starts the server in the
|
|
114
|
-
background. Check it with `vp exec astro dev status`, read its output with
|
|
115
|
-
`vp exec astro dev logs --follow`, and stop it with `vp exec astro dev stop`; the
|
|
116
|
-
site is at <http://localhost:4321>. Don't wait on it as if it were a foreground
|
|
117
|
-
process.
|
|
118
|
-
|
|
119
|
-
| Script | What it does |
|
|
120
|
-
| ----------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------- |
|
|
121
|
-
| `vpr dev` | local dev |
|
|
122
|
-
| `vpr build:css` | regenerate the framework CSS from `site.json` |
|
|
123
|
-
| `vpr deploy` | build + `wrangler deploy` to prod |
|
|
124
|
-
| `vpr deploy:dev` | build + deploy the isolated dev worker |
|
|
125
|
-
| `vpr init:github` | one-time: create + push the GitHub repo (main + dev, dev default, branch protection + environment policy). Only when the user asks. |
|
|
126
|
-
| `vpr init:hosting` | one-time: read the Cloudflare account id and set it as a repo secret. Only when the user asks. |
|
|
127
|
-
| `vp exec vitops validate site.json` | schema-check the site config |
|
|
128
|
-
| `vp exec vitops lint -i site.json -f css` | find framework classes in source that resolve to nothing under this format |
|
|
41
|
+
This is a **Vite+** project: use `vp` (and `vpr` for scripts), not `pnpm`/`npm`/`npx`. Scripts that
|
|
42
|
+
wrap Astro go through `vpr` (`vpr dev`, `vpr build`), because `vp dev`/`vp build` skip Astro.
|
|
43
|
+
`vpr dev` returns immediately (the server runs in the background): check it with
|
|
44
|
+
`vp exec astro dev status`, read `vp exec astro dev logs --follow`, stop with
|
|
45
|
+
`vp exec astro dev stop`; the site is at <http://localhost:4321>. `package.json` lists the scripts;
|
|
46
|
+
`vp check` and `vpr typecheck` verify a change. `vpr init:github` and `vpr init:hosting` are one-time
|
|
47
|
+
setup, only when the user asks.
|
|
129
48
|
|
|
130
|
-
|
|
131
|
-
version bump: ship by committing to `dev` (auto-deploys the dev worker) and then
|
|
132
|
-
running the **promote** workflow. `vp` has no `vp change` / `vp pm change` command
|
|
133
|
-
(checked against vp 1.5.0); for a _package_ it does have `vp pm version`,
|
|
134
|
-
`vp pm publish` and `vp pm stage`.
|
|
49
|
+
## Where to look
|
|
135
50
|
|
|
136
|
-
**
|
|
137
|
-
|
|
138
|
-
`vp create @getvitops:static --directory <client-slug>`. `wrangler.jsonc`'s `name`
|
|
139
|
-
is separate and still says what the template shipped — rename it to match before
|
|
140
|
-
the first deploy.
|
|
51
|
+
- **Skills** are listed in the block at the top; load the one matching the task before starting.
|
|
52
|
+
- **Reference** for this project's own tokens, classes and config: `vp exec vitops docs`.
|
|
@@ -27,11 +27,11 @@ public/vitops/
|
|
|
27
27
|
.DS_Store
|
|
28
28
|
|
|
29
29
|
# agent skills linked from @getvitops/skills by `skills experimental_sync` (see skills-lock.json)
|
|
30
|
-
|
|
31
|
-
.agents/skills/
|
|
32
|
-
.agents/skills/
|
|
33
|
-
.
|
|
34
|
-
.claude/skills/
|
|
35
|
-
|
|
36
|
-
.claude/skills/
|
|
37
|
-
.
|
|
30
|
+
# (the `vitops-` and `emdash-` prefixes are reserved for toolkit skills)
|
|
31
|
+
.agents/skills/vitops-*
|
|
32
|
+
.agents/skills/emdash-*
|
|
33
|
+
.claude/skills/vitops-*
|
|
34
|
+
.claude/skills/emdash-*
|
|
35
|
+
# playwright-cli: skill regenerated by `prepare`; output is per-session scratch
|
|
36
|
+
.claude/skills/playwright-cli
|
|
37
|
+
.playwright-cli/
|
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
"build:css": "vitops generate -i site.json -f css -o src/styles",
|
|
17
17
|
"lint:design": "vitops lint -i site.json -f css --strict",
|
|
18
18
|
"sop": "vitops generate -i site.json -f css -o .vitops/sop",
|
|
19
|
-
"prepare": "vp config && skills experimental_sync -y -a claude-code --no-frozen-lockfile && vitops agents sync -i site.json"
|
|
19
|
+
"prepare": "vp config && skills experimental_sync -y -a claude-code --no-frozen-lockfile && playwright cli install --skills && vitops agents sync -i site.json"
|
|
20
20
|
},
|
|
21
21
|
"dependencies": {
|
|
22
22
|
"@getvitops/astro": "latest",
|
|
@@ -26,10 +26,11 @@
|
|
|
26
26
|
"devDependencies": {
|
|
27
27
|
"@astrojs/check": "^0.9.12",
|
|
28
28
|
"@getvitops/cli": "latest",
|
|
29
|
+
"playwright": "^1.62.1",
|
|
29
30
|
"skills": "^1.7.6",
|
|
30
31
|
"typescript": "^6.0.3",
|
|
31
|
-
"vite": "
|
|
32
|
-
"vite-plus": "
|
|
32
|
+
"vite": "npm:@voidzero-dev/vite-plus-core@^1.5.1",
|
|
33
|
+
"vite-plus": "^1.5.1",
|
|
33
34
|
"wrangler": "^4.111.0"
|
|
34
35
|
},
|
|
35
36
|
"devEngines": {
|
|
@@ -5,17 +5,12 @@ allowBuilds:
|
|
|
5
5
|
workerd: true
|
|
6
6
|
sharp: false
|
|
7
7
|
|
|
8
|
-
# Supply-chain hardening: nothing installs within 3 days of its publish
|
|
9
|
-
#
|
|
8
|
+
# Supply-chain hardening: nothing installs within 3 days of its publish, and no non-registry
|
|
9
|
+
# transitive sources. Only our own lockstep `@getvitops/*` is exempt. Do NOT exempt a third-party
|
|
10
|
+
# toolchain: an exempt package's exact pins are not exempt (vite-plus pins `@oxc-project/*`
|
|
11
|
+
# exactly), so the install fails whenever one is fresher than the window. A security patch may be
|
|
12
|
+
# admitted with an exact `name@x.y.z` line and a dated reason; remove it once it matures.
|
|
10
13
|
minimumReleaseAge: 4320
|
|
11
14
|
minimumReleaseAgeExclude:
|
|
12
15
|
- '@getvitops/*'
|
|
13
|
-
- vite-plus
|
|
14
|
-
- '@voidzero-dev/*'
|
|
15
|
-
- oxlint
|
|
16
|
-
- '@oxlint/*'
|
|
17
|
-
- oxlint-tsgolint
|
|
18
|
-
- '@oxlint-tsgolint/*'
|
|
19
|
-
- oxfmt
|
|
20
|
-
- '@oxfmt/*'
|
|
21
16
|
blockExoticSubdeps: true
|