@the-smithy/create-hearth-site 0.25.0 → 0.26.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -1
- package/bin/create-hearth-site.js +1 -1
- package/package.json +3 -2
- package/template/AGENTS.md +190 -12
- package/template/CLAUDE.md +2 -186
- package/template/GEMINI.md +3 -0
- package/template/HOSTING.md +88 -0
- package/template/README.md +13 -26
- package/template/SITE.md +1 -1
- package/template/_gitignore +2 -1
- package/template/scripts/hearth.mjs +26 -23
- package/template/src/hearth/manifest.json +3 -1
package/README.md
CHANGED
|
@@ -39,7 +39,8 @@ cd acme-website
|
|
|
39
39
|
```
|
|
40
40
|
|
|
41
41
|
Add an API key to `.env.local` (Hearth → the site → API & webhooks → API keys), and run `pnpm dev`. The
|
|
42
|
-
new site's README walks through the first run, previews
|
|
42
|
+
new site's README walks through the first run, previews and troubleshooting. Its `HOSTING.md` covers deploying to
|
|
43
|
+
Netlify or Vercel, and its `AGENTS.md` holds the instructions for coding agents.
|
|
43
44
|
|
|
44
45
|
## Documentation
|
|
45
46
|
|
|
@@ -19,7 +19,7 @@ const self = JSON.parse(await readFile(path.join(here, "..", "package.json"), "u
|
|
|
19
19
|
|
|
20
20
|
/** Files that exist in the template folder during development but never belong in a new site. */
|
|
21
21
|
// The template's lockfile is the Hearth workspace's (the SDK as a workspace link); a new site makes its own.
|
|
22
|
-
const SKIP = new Set(["node_modules", ".next", ".netlify", ".env.local", "next-env.d.ts", "tsconfig.tsbuildinfo", "pnpm-lock.yaml"]);
|
|
22
|
+
const SKIP = new Set(["node_modules", ".next", ".netlify", ".vercel", ".env.local", "next-env.d.ts", "tsconfig.tsbuildinfo", "pnpm-lock.yaml"]);
|
|
23
23
|
/** Dotfiles npm won't publish (or that would confuse the monorepo) are stored with a leading underscore. */
|
|
24
24
|
const RENAMES = { _gitignore: ".gitignore", "_pnpm-workspace.yaml": "pnpm-workspace.yaml" };
|
|
25
25
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@the-smithy/create-hearth-site",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.26.0",
|
|
4
4
|
"description": "Start a new client website powered by Hearth: pnpm create @the-smithy/hearth-site",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "SEE LICENSE IN LICENSE",
|
|
@@ -21,6 +21,7 @@
|
|
|
21
21
|
"!template/node_modules",
|
|
22
22
|
"!template/.next",
|
|
23
23
|
"!template/.netlify",
|
|
24
|
+
"!template/.vercel",
|
|
24
25
|
"!template/.env.local",
|
|
25
26
|
"!template/next-env.d.ts",
|
|
26
27
|
"!template/tsconfig.tsbuildinfo",
|
|
@@ -29,7 +30,7 @@
|
|
|
29
30
|
],
|
|
30
31
|
"devDependencies": {
|
|
31
32
|
"vitest": "^5.0.3",
|
|
32
|
-
"@the-smithy/hearth-schema": "^0.
|
|
33
|
+
"@the-smithy/hearth-schema": "^0.26.0"
|
|
33
34
|
},
|
|
34
35
|
"scripts": {
|
|
35
36
|
"test": "node --test \"test/**/*.test.js\" && vitest run test/blocks.test.ts test/overrides.test.ts"
|
package/template/AGENTS.md
CHANGED
|
@@ -1,17 +1,195 @@
|
|
|
1
1
|
# Notes for coding agents
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
3
|
+
This is a client website whose content comes from Hearth, The Smithy's headless CMS. This file is Hearth's and is
|
|
4
|
+
replaced by `pnpm hearth:update`: notes about this particular site go in `SITE.md`, which you should read too.
|
|
5
|
+
`CLAUDE.md` and `GEMINI.md` only point here, for the assistants that look for those names.
|
|
5
6
|
|
|
6
|
-
|
|
7
|
-
replaced by `pnpm hearth:update`, so never edit it. A site is styled in `src/theme/`: tokens in `theme.css`, and the
|
|
8
|
-
Tailwind classes of each element's slot in `index.ts` (every slot and its default is in
|
|
9
|
-
`src/hearth/theme/defaults.ts`).
|
|
7
|
+
## How this site is built: Hearth's code, the site's styling
|
|
10
8
|
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
9
|
+
Everything that works with Hearth is already here, and it's the same in every site Hearth runs: every block type,
|
|
10
|
+
the event, fundraiser and donation pages, forms (contact, volunteer sign-up, registration, donations), the
|
|
11
|
+
header and phone menu, the footer, previews, SEO, analytics, redirects and the sitemap. **A site is built by styling
|
|
12
|
+
it, not by writing code.** It has two parts:
|
|
14
13
|
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
`
|
|
14
|
+
| Folder | Whose | What's in it |
|
|
15
|
+
| -------------- | ---------- | ---------------------------------------------------------------------------------------------- |
|
|
16
|
+
| `src/hearth/` | Hearth's | All the components and pages, and the theme's slots and defaults. **Never edit it.** |
|
|
17
|
+
| `src/theme/` | The site's | Its look: tokens, slot classes, fonts, wording, custom blocks. **All design work goes here.** |
|
|
18
|
+
|
|
19
|
+
`src/app/` holds one-line route files that point at `src/hearth/pages/`; they're Hearth's too (see
|
|
20
|
+
`src/hearth/manifest.json` for every file Hearth manages). Pages the site needs that Hearth doesn't serve can be
|
|
21
|
+
added in `src/app/` as the site's own.
|
|
22
|
+
|
|
23
|
+
`pnpm hearth:update` brings in a new release of Hearth's code: it replaces the managed files and leaves `src/theme`
|
|
24
|
+
and the site's own files alone. That's how every site gets new blocks and fixes at once, so an edit to a managed file
|
|
25
|
+
is lost at the next update, and stops the update (it refuses to overwrite uncommitted changes there).
|
|
26
|
+
|
|
27
|
+
## Styling a site
|
|
28
|
+
|
|
29
|
+
Work in this order, and stop at the first step that gets the result:
|
|
30
|
+
|
|
31
|
+
1. **Tokens** in `src/theme/theme.css`: colors, fonts, content width. Every slot uses these names (`bg-accent`,
|
|
32
|
+
`text-highlight`, `bg-muted`, `border-field`…), so most of a brand is set here. Hearth's tones (default, muted,
|
|
33
|
+
inverse, accent) map onto them.
|
|
34
|
+
2. **Fonts** in `src/theme/fonts.ts` (next/font), used from the tokens.
|
|
35
|
+
3. **Slots** in `src/theme/index.ts`. Every element Hearth's code draws takes its Tailwind classes from one slot.
|
|
36
|
+
`src/hearth/theme/defaults.ts` lists every part (`hero`, `cardGrid`, `header`, `form`, `event`…), every slot
|
|
37
|
+
and its default classes: read it before styling. Set only the slots the design changes:
|
|
38
|
+
|
|
39
|
+
```ts
|
|
40
|
+
export const theme = defineTheme({
|
|
41
|
+
hero: { title: "font-display text-7xl uppercase", inner: (base) => `${base} text-center` },
|
|
42
|
+
button: { primary: "inline-block bg-accent px-6 py-3 font-mono uppercase text-accent-ink hover:bg-ink" },
|
|
43
|
+
stats: { tone: "accent" },
|
|
44
|
+
});
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
- A string **replaces** the slot's default; a function **extends** it (`(base) => …`). Replace when you change a
|
|
48
|
+
property the default already sets (a radius, a size), since two classes for the same property don't reliably
|
|
49
|
+
resolve in the order written.
|
|
50
|
+
- Each slot styles one element, so slots never fight. States are attributes on the element, so they're plain
|
|
51
|
+
Tailwind variants in the slot: `on-dark:` (on an inverse or accent ground), `data-[size=full]:`,
|
|
52
|
+
`data-highlight:`, `aria-invalid:`, `aria-[current=page]:`, `has-checked:`, `in-data-[…]:` and others; the top
|
|
53
|
+
of `defaults.ts` lists them and each slot's comment says which apply.
|
|
54
|
+
- Slots named `tone` pick a block's ground (default, muted, inverse or accent). `text` holds the words the code
|
|
55
|
+
shows ("Get directions", "Register"…), with `{name}` for what the code fills in. `map.style` is the map's MapLibre
|
|
56
|
+
style; `emberForm` shapes Ember forms. A few slots in `page` are settings: `themeColor` (the browser's color around
|
|
57
|
+
the page), `externalLinks` ("new-tab"), `navMatch` ("exact" for menu links) and `dateStyle` ("long" for event dates).
|
|
58
|
+
- Icons and marks drawn with CSS (`before:content-['✓']`, the FAQ's `+`, link arrows) live in slots too. A card's
|
|
59
|
+
Icon in Hearth arrives as `data-icon="graduation-cap"`, for the site's CSS to draw.
|
|
60
|
+
4. **Site CSS** under the tokens in `theme.css`: keyframes, background patterns, `@utility` classes your slots use,
|
|
61
|
+
and rules for Hearth's rich text (`.prose`), a block (`[data-block="hero"]`), a page the code serves
|
|
62
|
+
(`[data-page="events"]`, so the same blocks elsewhere stay as they are) or one card (`[data-card="our-history"]`, from
|
|
63
|
+
its title).
|
|
64
|
+
5. **A page in Hearth** for words the code draws on its own pages: see "Pages at the code's addresses" below.
|
|
65
|
+
6. **Overrides**, last: `src/theme/overrides.tsx` (see "Overrides" below). An override is the site's own code: updates
|
|
66
|
+
don't reach it, so it falls behind Hearth. Use one only when everything above can't get there, build it from
|
|
67
|
+
`src/hearth/ui.tsx` and the theme, and say why in `SITE.md`.
|
|
68
|
+
|
|
69
|
+
Never copy a component out of `src/hearth/` to restyle it. If a design needs a slot or an option Hearth doesn't have,
|
|
70
|
+
say so: it belongs in Hearth's template for every site (tell the developer to raise it with The Smithy), and an
|
|
71
|
+
override can bridge the gap meanwhile.
|
|
72
|
+
|
|
73
|
+
### Pages at the code's addresses
|
|
74
|
+
|
|
75
|
+
`/events`, `/fundraisers`, `/donate`, `/donate/thank-you`, `/registered` and the 404 are served by Hearth's code, with
|
|
76
|
+
default words. A published page in Hearth at that address (`events`, `donate/thank-you`, `not-found` for the 404…)
|
|
77
|
+
replaces those words: its blocks, its SEO and its structured data. **So a page's text never needs its own route or an
|
|
78
|
+
override: make the page in Hearth.** `/events` and the fundraiser and donation addresses still need the site's Events
|
|
79
|
+
or Giving feature. The site's `reserved_slugs` in Hearth mustn't list these, since that refuses the page.
|
|
80
|
+
|
|
81
|
+
The thank-you pages and the 404 are never indexable and never in the sitemap or llms.txt. The 404 always answers 404,
|
|
82
|
+
never serves at `/not-found`, and redirects run before it.
|
|
83
|
+
|
|
84
|
+
### Overrides
|
|
85
|
+
|
|
86
|
+
Everything is optional. A site's file only exports what it uses, and an update never replaces it. They can import
|
|
87
|
+
Hearth's components, `Blocks` too. The types are in `src/hearth/overrides.ts`. They're used everywhere the template draws
|
|
88
|
+
these things:
|
|
89
|
+
|
|
90
|
+
| Export | Replaces | Given |
|
|
91
|
+
| --- | --- | --- |
|
|
92
|
+
| `blocks` | A whole block's component, by type | `{ block }`. An anchor from Hearth keeps working around it |
|
|
93
|
+
| `Logo` | The logo in the header, and in the footer once the `footer.logo` slot has classes | `{ site, place }` |
|
|
94
|
+
| `Footer` | The footer | `{ site }` |
|
|
95
|
+
| `EventPage` | An event's page, at `/events/…` and in collections, so a site needs no route of its own | `{ event }` |
|
|
96
|
+
| `FundraiserPage` | A fundraiser's page | `{ fundraiser }` |
|
|
97
|
+
| `EventCard`, `FundraiserCard` | The cards in lists; each draws an `<li>`, and may be async | `{ event }` or `{ fundraiser }`, and `heading`: `"h2"` or `"h3"`, for its title |
|
|
98
|
+
| `DonateForm` | The donation form | The amounts, `fundraiser`, `fundraisers` (active ones to choose from) and `submitLabel` |
|
|
99
|
+
|
|
100
|
+
`EventPage` and `FundraiserPage` have their structured data drawn around them. To build on Hearth's: `EventContent`,
|
|
101
|
+
`EventRegisterSection` and `EventVolunteerSection` from `components/EventView.tsx`, `FundraiserContent` from
|
|
102
|
+
`components/FundraiserView.tsx`. A site's own routes are for pages Hearth has no address for, not for an event's or
|
|
103
|
+
a fundraiser's page.
|
|
104
|
+
|
|
105
|
+
`own` in the manifest (managed files `pnpm hearth:update` leaves alone) is for a site inside an app, with its own root
|
|
106
|
+
layout, not for keeping a page: it stops that file getting fixes. Such a site imports `src/hearth/hearth.css` alone in
|
|
107
|
+
its own CSS, and `src/hearth/base.css` if it wants Hearth's rich text and focus rings.
|
|
108
|
+
|
|
109
|
+
**Custom blocks** (Settings → Custom blocks in Hearth) are the one place a site writes components: in
|
|
110
|
+
`src/theme/custom-blocks.tsx`, built from `src/hearth/ui.tsx` so they take the theme.
|
|
111
|
+
|
|
112
|
+
## Staying on the template and up to date
|
|
113
|
+
|
|
114
|
+
Every site Hearth runs has the same code, so a fix or a new block reaches them all at once. That only holds while
|
|
115
|
+
each site stays on the template and on current releases:
|
|
116
|
+
|
|
117
|
+
- **Check for new releases from The Smithy at the start of every piece of work.** `pnpm hearth:check` lists every
|
|
118
|
+
`@the-smithy/*` package and Hearth's code in this site with a newer release on npm, even with no API key. Tell the
|
|
119
|
+
developer what it found.
|
|
120
|
+
- **Update before you build on old code.** `pnpm hearth:update` brings Hearth's code and SDK to the latest release;
|
|
121
|
+
`pnpm add @the-smithy/<name>@latest` does any other Smithy package, such as the Ember SDK. Read the package's
|
|
122
|
+
`CHANGELOG.md`, run the checks below, look at the site, and commit the update on its own, with
|
|
123
|
+
`pnpm-workspace.yaml` if pnpm added the new versions to it.
|
|
124
|
+
- **Don't drift.** A design change goes in `src/theme/`. Don't copy a component out of `src/hearth/`, replace a route
|
|
125
|
+
file, or write a site's own data layer, SEO or forms. A bug or a gap in Hearth's code gets fixed in Hearth's
|
|
126
|
+
template and reaches the site by an update; say so to the developer, and if it can't wait, bridge it with an override
|
|
127
|
+
and note it in `SITE.md`.
|
|
128
|
+
- **After an update, look through `SITE.md`'s overrides** and compare each with the component it replaced in
|
|
129
|
+
`src/hearth/blocks`, since updates don't reach them. Delete any that the theme can now do.
|
|
130
|
+
|
|
131
|
+
## First steps in a new checkout
|
|
132
|
+
|
|
133
|
+
Do these before writing code, and tell the developer what you found:
|
|
134
|
+
|
|
135
|
+
1. **Check the setup:** `pnpm hearth:check`. It reports any newer releases from The Smithy (see above), reads
|
|
136
|
+
`.env.local`, connects to Hearth, lists the published pages and the block types they use, and flags types with no
|
|
137
|
+
component. It never prints the API key; you shouldn't either, so don't open `.env.local` to read the key or echo
|
|
138
|
+
it. If it fails, its message says what to fix (missing key, wrong `HEARTH_SITE`, revoked key). Ask the developer to
|
|
139
|
+
fix `.env.local`; don't guess values.
|
|
140
|
+
2. **Get the full reference:** `pnpm hearth:docs` saves Hearth's API guide (every block and its fields, events,
|
|
141
|
+
forms, previews, SEO) to `hearth-api.md`. Read the parts you need from it, and from
|
|
142
|
+
`node_modules/@the-smithy/hearth/llms.md` for the SDK. It also installs any skills Hearth offers this site (see
|
|
143
|
+
"Skills" below), so run it again when they're missing. These aren't committed, so some assistants' search skips
|
|
144
|
+
them: open them by their exact path, or from the terminal if your tool won't.
|
|
145
|
+
3. **"Nothing is published"** means every page is a 404 until editors publish in Hearth. That's not a code
|
|
146
|
+
problem: say so, and don't add placeholder content to work around it.
|
|
147
|
+
4. **Then style the site** as above, checking with `pnpm dev` against the published pages, on a phone width too.
|
|
148
|
+
|
|
149
|
+
## Skills
|
|
150
|
+
|
|
151
|
+
`pnpm hearth:docs` installs the skills Hearth offers this site (which ones depends on the site) in `.agents/skills/`
|
|
152
|
+
and, as the same copies, in `.claude/skills/`, since Claude Code only reads that one. Each `SKILL.md` says what it's
|
|
153
|
+
for and when to use it. If your assistant doesn't load them by itself, read the ones that fit the task and follow them.
|
|
154
|
+
Use them for that work, design work in particular, rather than starting from scratch. They aren't committed (a
|
|
155
|
+
`.gitignore` in each folder lists them), so a fresh checkout gets them by running `pnpm hearth:docs`; never edit them,
|
|
156
|
+
because the next run replaces them.
|
|
157
|
+
|
|
158
|
+
## Hosting
|
|
159
|
+
|
|
160
|
+
The site runs on Netlify or Vercel with no code changes: `HOSTING.md` has the steps and the settings for each. Don't
|
|
161
|
+
add code for one host, or an image optimizer; if a host seems to need one, say so to the developer.
|
|
162
|
+
|
|
163
|
+
## Rules
|
|
164
|
+
|
|
165
|
+
- **Content comes from Hearth.** Don't hard-code page text, menus, contact details or SEO tags. They're edited
|
|
166
|
+
in Hearth and arrive through `src/hearth/client.ts`. The site's code owns design only.
|
|
167
|
+
- **The API key is secret.** Only import `@/hearth/client` from server components, route handlers and server
|
|
168
|
+
actions. Never from a `"use client"` file.
|
|
169
|
+
- **Use `hearthNext.*` for reads.** It caches and tags requests so the publish webhook can refresh them.
|
|
170
|
+
Calling `hearth.*` directly skips the cache.
|
|
171
|
+
- **Keep every option working.** Blocks have tones, image positions, sizes and optional fields; check a restyle
|
|
172
|
+
with each, on light and dark grounds, and check text contrast on every ground (form field borders need 3:1).
|
|
173
|
+
- **No bundled content.** Every page comes from Hearth; an address with no published page is a 404. Don't add
|
|
174
|
+
fallback or sample content to the code.
|
|
175
|
+
- **Previews, analytics and forms come from Hearth's code.** Don't build a preview banner, add Google Analytics or
|
|
176
|
+
other trackers (Hearth's analytics is cookieless; marketing tags need a consent banner and are a separate, opt-in
|
|
177
|
+
feature), or post forms anywhere else. Use `track("name")` from `@the-smithy/hearth/analytics` in the site's own
|
|
178
|
+
code for meaningful actions.
|
|
179
|
+
|
|
180
|
+
## Checks
|
|
181
|
+
|
|
182
|
+
`pnpm typecheck` and `pnpm build`. The build needs the `.env.local` keys, because it reads content from Hearth.
|
|
183
|
+
`pnpm hearth:check` shows any block type the published content uses without a component (run `pnpm hearth:update`).
|
|
184
|
+
After `pnpm hearth:update`: `pnpm install`, then the checks; a type error in `src/theme/index.ts` means a slot was
|
|
185
|
+
renamed or removed (see `defaults.ts`).
|
|
186
|
+
|
|
187
|
+
<!-- BEGIN:nextjs-agent-rules -->
|
|
188
|
+
|
|
189
|
+
# This is NOT the Next.js you know
|
|
190
|
+
|
|
191
|
+
This version has breaking changes — APIs, conventions, and file structure may all differ from your training data. Read the relevant guide in `node_modules/next/dist/docs/` (resolved from this file's directory; in monorepos the `next` package may not be visible from the repo root) before writing any code. Heed deprecation notices.
|
|
192
|
+
|
|
193
|
+
This block is written and re-added by `next dev` — verify at `node_modules/next/dist/server/lib/generate-agent-files.js`. Removing it from a diff only re-creates the uncommitted change; committing it with your work keeps the tree clean.
|
|
194
|
+
|
|
195
|
+
<!-- END:nextjs-agent-rules -->
|
package/template/CLAUDE.md
CHANGED
|
@@ -1,187 +1,3 @@
|
|
|
1
|
-
|
|
1
|
+
The instructions for coding agents in this project are in `AGENTS.md`:
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
replaced by `pnpm hearth:update`: notes about this particular site go in `SITE.md`, which you should read too.
|
|
5
|
-
|
|
6
|
-
## How this site is built: Hearth's code, the site's styling
|
|
7
|
-
|
|
8
|
-
Everything that works with Hearth is already here, and it's the same in every site Hearth runs: every block type,
|
|
9
|
-
the event, fundraiser and donation pages, forms (contact, volunteer sign-up, registration, donations), the
|
|
10
|
-
header and phone menu, the footer, previews, SEO, analytics, redirects and the sitemap. **A site is built by styling
|
|
11
|
-
it, not by writing code.** It has two parts:
|
|
12
|
-
|
|
13
|
-
| Folder | Whose | What's in it |
|
|
14
|
-
| -------------- | ---------- | ---------------------------------------------------------------------------------------------- |
|
|
15
|
-
| `src/hearth/` | Hearth's | All the components and pages, and the theme's slots and defaults. **Never edit it.** |
|
|
16
|
-
| `src/theme/` | The site's | Its look: tokens, slot classes, fonts, wording, custom blocks. **All design work goes here.** |
|
|
17
|
-
|
|
18
|
-
`src/app/` holds one-line route files that point at `src/hearth/pages/`; they're Hearth's too (see
|
|
19
|
-
`src/hearth/manifest.json` for every file Hearth manages). Pages the site needs that Hearth doesn't serve can be
|
|
20
|
-
added in `src/app/` as the site's own.
|
|
21
|
-
|
|
22
|
-
`pnpm hearth:update` brings in a new release of Hearth's code: it replaces the managed files and leaves `src/theme`
|
|
23
|
-
and the site's own files alone. That's how every site gets new blocks and fixes at once, so an edit to a managed file
|
|
24
|
-
is lost at the next update, and stops the update (it refuses to overwrite uncommitted changes there).
|
|
25
|
-
|
|
26
|
-
## Styling a site
|
|
27
|
-
|
|
28
|
-
Work in this order, and stop at the first step that gets the result:
|
|
29
|
-
|
|
30
|
-
1. **Tokens** in `src/theme/theme.css`: colors, fonts, content width. Every slot uses these names (`bg-accent`,
|
|
31
|
-
`text-highlight`, `bg-muted`, `border-field`…), so most of a brand is set here. Hearth's tones (default, muted,
|
|
32
|
-
inverse, accent) map onto them.
|
|
33
|
-
2. **Fonts** in `src/theme/fonts.ts` (next/font), used from the tokens.
|
|
34
|
-
3. **Slots** in `src/theme/index.ts`. Every element Hearth's code draws takes its Tailwind classes from one slot.
|
|
35
|
-
`src/hearth/theme/defaults.ts` lists every part (`hero`, `cardGrid`, `header`, `form`, `event`…), every slot
|
|
36
|
-
and its default classes: read it before styling. Set only the slots the design changes:
|
|
37
|
-
|
|
38
|
-
```ts
|
|
39
|
-
export const theme = defineTheme({
|
|
40
|
-
hero: { title: "font-display text-7xl uppercase", inner: (base) => `${base} text-center` },
|
|
41
|
-
button: { primary: "inline-block bg-accent px-6 py-3 font-mono uppercase text-accent-ink hover:bg-ink" },
|
|
42
|
-
stats: { tone: "accent" },
|
|
43
|
-
});
|
|
44
|
-
```
|
|
45
|
-
|
|
46
|
-
- A string **replaces** the slot's default; a function **extends** it (`(base) => …`). Replace when you change a
|
|
47
|
-
property the default already sets (a radius, a size), since two classes for the same property don't reliably
|
|
48
|
-
resolve in the order written.
|
|
49
|
-
- Each slot styles one element, so slots never fight. States are attributes on the element, so they're plain
|
|
50
|
-
Tailwind variants in the slot: `on-dark:` (on an inverse or accent ground), `data-[size=full]:`,
|
|
51
|
-
`data-highlight:`, `aria-invalid:`, `aria-[current=page]:`, `has-checked:`, `in-data-[…]:` and others; the top
|
|
52
|
-
of `defaults.ts` lists them and each slot's comment says which apply.
|
|
53
|
-
- Slots named `tone` pick a block's ground (default, muted, inverse or accent). `text` holds the words the code
|
|
54
|
-
shows ("Get directions", "Register"…), with `{name}` for what the code fills in. `map.style` is the map's MapLibre
|
|
55
|
-
style; `emberForm` shapes Ember forms. A few slots in `page` are settings: `themeColor` (the browser's color around
|
|
56
|
-
the page), `externalLinks` ("new-tab"), `navMatch` ("exact" for menu links) and `dateStyle` ("long" for event dates).
|
|
57
|
-
- Icons and marks drawn with CSS (`before:content-['✓']`, the FAQ's `+`, link arrows) live in slots too. A card's
|
|
58
|
-
Icon in Hearth arrives as `data-icon="graduation-cap"`, for the site's CSS to draw.
|
|
59
|
-
4. **Site CSS** under the tokens in `theme.css`: keyframes, background patterns, `@utility` classes your slots use,
|
|
60
|
-
and rules for Hearth's rich text (`.prose`), a block (`[data-block="hero"]`), a page the code serves
|
|
61
|
-
(`[data-page="events"]`, so the same blocks elsewhere stay as they are) or one card (`[data-card="our-history"]`, from
|
|
62
|
-
its title).
|
|
63
|
-
5. **A page in Hearth** for words the code draws on its own pages: see "Pages at the code's addresses" below.
|
|
64
|
-
6. **Overrides**, last: `src/theme/overrides.tsx` (see "Overrides" below). An override is the site's own code: updates
|
|
65
|
-
don't reach it, so it falls behind Hearth. Use one only when everything above can't get there, build it from
|
|
66
|
-
`src/hearth/ui.tsx` and the theme, and say why in `SITE.md`.
|
|
67
|
-
|
|
68
|
-
Never copy a component out of `src/hearth/` to restyle it. If a design needs a slot or an option Hearth doesn't have,
|
|
69
|
-
say so: it belongs in Hearth's template for every site (tell the developer to raise it with The Smithy), and an
|
|
70
|
-
override can bridge the gap meanwhile.
|
|
71
|
-
|
|
72
|
-
### Pages at the code's addresses
|
|
73
|
-
|
|
74
|
-
`/events`, `/fundraisers`, `/donate`, `/donate/thank-you`, `/registered` and the 404 are served by Hearth's code, with
|
|
75
|
-
default words. A published page in Hearth at that address (`events`, `donate/thank-you`, `not-found` for the 404…)
|
|
76
|
-
replaces those words: its blocks, its SEO and its structured data. **So a page's text never needs its own route or an
|
|
77
|
-
override: make the page in Hearth.** `/events` and the fundraiser and donation addresses still need the site's Events
|
|
78
|
-
or Giving feature. The site's `reserved_slugs` in Hearth mustn't list these, since that refuses the page.
|
|
79
|
-
|
|
80
|
-
The thank-you pages and the 404 are never indexable and never in the sitemap or llms.txt. The 404 always answers 404,
|
|
81
|
-
never serves at `/not-found`, and redirects run before it.
|
|
82
|
-
|
|
83
|
-
### Overrides
|
|
84
|
-
|
|
85
|
-
Everything is optional. A site's file only exports what it uses, and an update never replaces it. They can import
|
|
86
|
-
Hearth's components, `Blocks` too. The types are in `src/hearth/overrides.ts`. They're used everywhere the template draws
|
|
87
|
-
these things:
|
|
88
|
-
|
|
89
|
-
| Export | Replaces | Given |
|
|
90
|
-
| --- | --- | --- |
|
|
91
|
-
| `blocks` | A whole block's component, by type | `{ block }`. An anchor from Hearth keeps working around it |
|
|
92
|
-
| `Logo` | The logo in the header, and in the footer once the `footer.logo` slot has classes | `{ site, place }` |
|
|
93
|
-
| `Footer` | The footer | `{ site }` |
|
|
94
|
-
| `EventPage` | An event's page, at `/events/…` and in collections, so a site needs no route of its own | `{ event }` |
|
|
95
|
-
| `FundraiserPage` | A fundraiser's page | `{ fundraiser }` |
|
|
96
|
-
| `EventCard`, `FundraiserCard` | The cards in lists; each draws an `<li>`, and may be async | `{ event }` or `{ fundraiser }`, and `heading`: `"h2"` or `"h3"`, for its title |
|
|
97
|
-
| `DonateForm` | The donation form | The amounts, `fundraiser`, `fundraisers` (active ones to choose from) and `submitLabel` |
|
|
98
|
-
|
|
99
|
-
`EventPage` and `FundraiserPage` have their structured data drawn around them. To build on Hearth's: `EventContent`,
|
|
100
|
-
`EventRegisterSection` and `EventVolunteerSection` from `components/EventView.tsx`, `FundraiserContent` from
|
|
101
|
-
`components/FundraiserView.tsx`. A site's own routes are for pages Hearth has no address for, not for an event's or
|
|
102
|
-
a fundraiser's page.
|
|
103
|
-
|
|
104
|
-
`own` in the manifest (managed files `pnpm hearth:update` leaves alone) is for a site inside an app, with its own root
|
|
105
|
-
layout, not for keeping a page: it stops that file getting fixes. Such a site imports `src/hearth/hearth.css` alone in
|
|
106
|
-
its own CSS, and `src/hearth/base.css` if it wants Hearth's rich text and focus rings.
|
|
107
|
-
|
|
108
|
-
**Custom blocks** (Settings → Custom blocks in Hearth) are the one place a site writes components: in
|
|
109
|
-
`src/theme/custom-blocks.tsx`, built from `src/hearth/ui.tsx` so they take the theme.
|
|
110
|
-
|
|
111
|
-
## Staying on the template and up to date
|
|
112
|
-
|
|
113
|
-
Every site Hearth runs has the same code, so a fix or a new block reaches them all at once. That only holds while
|
|
114
|
-
each site stays on the template and on current releases:
|
|
115
|
-
|
|
116
|
-
- **Check for new releases from The Smithy at the start of every piece of work.** `pnpm hearth:check` lists every
|
|
117
|
-
`@the-smithy/*` package and Hearth's code in this site with a newer release on npm, even with no API key. Tell the
|
|
118
|
-
developer what it found.
|
|
119
|
-
- **Update before you build on old code.** `pnpm hearth:update` brings Hearth's code and SDK to the latest release;
|
|
120
|
-
`pnpm add @the-smithy/<name>@latest` does any other Smithy package, such as the Ember SDK. Read the package's
|
|
121
|
-
`CHANGELOG.md`, run the checks below, look at the site, and commit the update on its own, with
|
|
122
|
-
`pnpm-workspace.yaml` if pnpm added the new versions to it.
|
|
123
|
-
- **Don't drift.** A design change goes in `src/theme/`. Don't copy a component out of `src/hearth/`, replace a route
|
|
124
|
-
file, or write a site's own data layer, SEO or forms. A bug or a gap in Hearth's code gets fixed in Hearth's
|
|
125
|
-
template and reaches the site by an update; say so to the developer, and if it can't wait, bridge it with an override
|
|
126
|
-
and note it in `SITE.md`.
|
|
127
|
-
- **After an update, look through `SITE.md`'s overrides** and compare each with the component it replaced in
|
|
128
|
-
`src/hearth/blocks`, since updates don't reach them. Delete any that the theme can now do.
|
|
129
|
-
|
|
130
|
-
## First steps in a new checkout
|
|
131
|
-
|
|
132
|
-
Do these before writing code, and tell the developer what you found:
|
|
133
|
-
|
|
134
|
-
1. **Check the setup:** `pnpm hearth:check`. It reports any newer releases from The Smithy (see above), reads
|
|
135
|
-
`.env.local`, connects to Hearth, lists the published pages and the block types they use, and flags types with no
|
|
136
|
-
component. It never prints the API key; you shouldn't either, so don't open `.env.local` to read the key or echo
|
|
137
|
-
it. If it fails, its message says what to fix (missing key, wrong `HEARTH_SITE`, revoked key). Ask the developer to
|
|
138
|
-
fix `.env.local`; don't guess values.
|
|
139
|
-
2. **Get the full reference:** `pnpm hearth:docs` saves Hearth's API guide (every block and its fields, events,
|
|
140
|
-
forms, previews, SEO) to `hearth-api.md`. Read the parts you need from it, and from
|
|
141
|
-
`node_modules/@the-smithy/hearth/llms.md` for the SDK. It also installs any skills Hearth offers this site in
|
|
142
|
-
`.claude/skills/` (see "Skills" below), so run it again when they're missing.
|
|
143
|
-
3. **"Nothing is published"** means every page is a 404 until editors publish in Hearth. That's not a code
|
|
144
|
-
problem: say so, and don't add placeholder content to work around it.
|
|
145
|
-
4. **Then style the site** as above, checking with `pnpm dev` against the published pages, on a phone width too.
|
|
146
|
-
|
|
147
|
-
## Skills
|
|
148
|
-
|
|
149
|
-
`pnpm hearth:docs` installs the skills Hearth offers this site in `.claude/skills/` (which ones depends on the
|
|
150
|
-
site). Each `SKILL.md` says what it's for and when to use it. Claude Code loads them by itself; other assistants
|
|
151
|
-
should read the ones that fit the task and follow them. Use them for that work, design work in particular, rather
|
|
152
|
-
than starting from scratch. They aren't committed (`.claude/skills/.gitignore` lists them), so a fresh checkout gets
|
|
153
|
-
them by running `pnpm hearth:docs`; never edit them, because the next run replaces them.
|
|
154
|
-
|
|
155
|
-
## Rules
|
|
156
|
-
|
|
157
|
-
- **Content comes from Hearth.** Don't hard-code page text, menus, contact details or SEO tags. They're edited
|
|
158
|
-
in Hearth and arrive through `src/hearth/client.ts`. The site's code owns design only.
|
|
159
|
-
- **The API key is secret.** Only import `@/hearth/client` from server components, route handlers and server
|
|
160
|
-
actions. Never from a `"use client"` file.
|
|
161
|
-
- **Use `hearthNext.*` for reads.** It caches and tags requests so the publish webhook can refresh them.
|
|
162
|
-
Calling `hearth.*` directly skips the cache.
|
|
163
|
-
- **Keep every option working.** Blocks have tones, image positions, sizes and optional fields; check a restyle
|
|
164
|
-
with each, on light and dark grounds, and check text contrast on every ground (form field borders need 3:1).
|
|
165
|
-
- **No bundled content.** Every page comes from Hearth; an address with no published page is a 404. Don't add
|
|
166
|
-
fallback or sample content to the code.
|
|
167
|
-
- **Previews, analytics and forms come from Hearth's code.** Don't build a preview banner, add Google Analytics or
|
|
168
|
-
other trackers (Hearth's analytics is cookieless; marketing tags need a consent banner and are a separate, opt-in
|
|
169
|
-
feature), or post forms anywhere else. Use `track("name")` from `@the-smithy/hearth/analytics` in the site's own
|
|
170
|
-
code for meaningful actions.
|
|
171
|
-
|
|
172
|
-
## Checks
|
|
173
|
-
|
|
174
|
-
`pnpm typecheck` and `pnpm build`. The build needs the `.env.local` keys, because it reads content from Hearth.
|
|
175
|
-
`pnpm hearth:check` shows any block type the published content uses without a component (run `pnpm hearth:update`).
|
|
176
|
-
After `pnpm hearth:update`: `pnpm install`, then the checks; a type error in `src/theme/index.ts` means a slot was
|
|
177
|
-
renamed or removed (see `defaults.ts`).
|
|
178
|
-
|
|
179
|
-
<!-- BEGIN:nextjs-agent-rules -->
|
|
180
|
-
|
|
181
|
-
# This is NOT the Next.js you know
|
|
182
|
-
|
|
183
|
-
This version has breaking changes — APIs, conventions, and file structure may all differ from your training data. Read the relevant guide in `node_modules/next/dist/docs/` (resolved from this file's directory; in monorepos the `next` package may not be visible from the repo root) before writing any code. Heed deprecation notices.
|
|
184
|
-
|
|
185
|
-
This block is written and re-added by `next dev` — verify at `node_modules/next/dist/server/lib/generate-agent-files.js`. Removing it from a diff only re-creates the uncommitted change; committing it with your work keeps the tree clean.
|
|
186
|
-
|
|
187
|
-
<!-- END:nextjs-agent-rules -->
|
|
3
|
+
@AGENTS.md
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
# Hosting a Hearth site
|
|
2
|
+
|
|
3
|
+
This site runs on Netlify or Vercel with no code changes. Its pages, webhook, previews and SEO use standard Next.js
|
|
4
|
+
features, and images come straight from Hearth's CDN, so neither host's image optimization is used or billed. The
|
|
5
|
+
sites The Smithy runs are on Netlify.
|
|
6
|
+
|
|
7
|
+
This file is Hearth's and is replaced by `pnpm hearth:update`. Notes about this site's hosting go in `SITE.md`.
|
|
8
|
+
|
|
9
|
+
## Setting up, on either host
|
|
10
|
+
|
|
11
|
+
Do these in order: the webhook's signing secret only exists once the webhook address is saved.
|
|
12
|
+
|
|
13
|
+
1. **Create a project from this repo,** with `pnpm-lock.yaml` committed. The host-specific settings are below.
|
|
14
|
+
2. **Add the environment variables** before the first deploy, because the build reads content from Hearth and fails
|
|
15
|
+
without them. Never put them in `netlify.toml` or anything else in git.
|
|
16
|
+
|
|
17
|
+
| Variable | Value | Where it's needed |
|
|
18
|
+
| --- | --- | --- |
|
|
19
|
+
| `HEARTH_SITE` | The site's address in Hearth, e.g. `acme` | Production and previews |
|
|
20
|
+
| `HEARTH_API_KEY` | A key made for this host (API & webhooks → API keys), so you can revoke it alone. Mark it secret | Production and previews |
|
|
21
|
+
| `HEARTH_WEBHOOK_SECRET` | Added in step 5 | Production |
|
|
22
|
+
|
|
23
|
+
3. **Deploy,** then connect your domain and choose its primary address (see "Domains").
|
|
24
|
+
4. **Set the web address in Hearth** to that primary address, under **Settings → Your site → Web address**. Search
|
|
25
|
+
engines are kept out until it's set.
|
|
26
|
+
5. **Connect the publish webhook.** In **API & webhooks → Webhook and preview**, set **Revalidation webhook** to
|
|
27
|
+
`https://<primary domain>/api/revalidate` and save. A **Signing secret** appears: add it as
|
|
28
|
+
`HEARTH_WEBHOOK_SECRET`, then **deploy again**. Both hosts apply a changed variable only to new deploys.
|
|
29
|
+
6. **Clear the Preview address** if you set it to localhost. Empty means the live site's `/api/preview`.
|
|
30
|
+
7. **Check it:** publish a small change in Hearth. It should show on the live site within a few seconds.
|
|
31
|
+
|
|
32
|
+
Without the webhook, published changes still appear, but can take up to an hour.
|
|
33
|
+
|
|
34
|
+
## Netlify
|
|
35
|
+
|
|
36
|
+
The template's `netlify.toml` sets the build command and Node 22, and Netlify's
|
|
37
|
+
[Next.js adapter](https://docs.netlify.com/build/frameworks/framework-setup-guides/nextjs/overview/) needs no other
|
|
38
|
+
configuration.
|
|
39
|
+
|
|
40
|
+
| Setting | Use | Why |
|
|
41
|
+
| --- | --- | --- |
|
|
42
|
+
| Adapter version | Don't pin it | Netlify then updates it on each build, and tests it with every Next.js release |
|
|
43
|
+
| Function region | Leave on the default, Ohio (`cmh`) | It's the same AWS region as Hearth. Older sites may differ: see Cloud compute → Functions → Region (Pro and up) |
|
|
44
|
+
| `NETLIFY_NEXT_SKEW_PROTECTION` | `true` | Forms use server actions. This keeps a page opened before a deploy able to submit after it |
|
|
45
|
+
| Variable scopes (Pro and up) | Include Builds and Functions | The build and the running site both read Hearth |
|
|
46
|
+
|
|
47
|
+
## Vercel
|
|
48
|
+
|
|
49
|
+
Vercel detects Next.js and pnpm by itself, so there's no config file, and `netlify.toml` is ignored. If the site
|
|
50
|
+
isn't at the top of its repo, set the project's Root Directory.
|
|
51
|
+
|
|
52
|
+
| Setting | Use | Why |
|
|
53
|
+
| --- | --- | --- |
|
|
54
|
+
| Plan | Pro for commercial work | [Hobby is non-commercial only](https://vercel.com/docs/limits/fair-use-guidelines). Taking payment from visitors, tickets for example, counts; asking for donations doesn't. Treat a site you're paid to build as commercial |
|
|
55
|
+
| `ENABLE_EXPERIMENTAL_COREPACK` | `1` | Builds with the exact pnpm version in `package.json` rather than one [picked from the lockfile](https://vercel.com/docs/package-managers) |
|
|
56
|
+
| Node version | Set `"engines": { "node": "22.x" }` in `package.json` to match Netlify | [`engines` overrides the project setting](https://vercel.com/docs/functions/runtimes/node-js/node-js-versions), and the template's `>=20` names no version. Your own machine then needs Node 22 too |
|
|
57
|
+
| Function region | Leave on the default, Washington, D.C. (`iad1`) | It's in the US East, near Hearth. Pages are served from the CDN, and a region further away still fetches from Hearth in the US |
|
|
58
|
+
| Skew Protection | Leave on | Forms use server actions. It's [on by default](https://vercel.com/docs/skew-protection) for new projects, on Pro and up |
|
|
59
|
+
| Enable access to System Environment Variables | Leave on | The site reads `VERCEL_ENV` to keep preview deployments out of search engines, and Skew Protection needs it |
|
|
60
|
+
| `HEARTH_API_KEY` type | Secret | [Write-only after saving](https://vercel.com/docs/environment-variables/sensitive-environment-variables) |
|
|
61
|
+
| Deployment Protection | Standard | [Protects everything except production domains](https://vercel.com/docs/deployment-protection), so the webhook and Hearth's preview must use the production domain, never a `*.vercel.app` deployment address |
|
|
62
|
+
|
|
63
|
+
**Redeploying doesn't refresh content on Vercel.** The
|
|
64
|
+
[Data Cache](https://vercel.com/docs/caching/runtime-cache/data-cache) that holds Hearth's responses keeps them across
|
|
65
|
+
deployments, and production and previews each have their own. The webhook clears production's when something
|
|
66
|
+
publishes; previews catch up within the hour. To clear it by hand, use CDN → Caches → Purge cache → All content →
|
|
67
|
+
Runtime and Data Cache, but on Hobby and Pro that clears every project in the team.
|
|
68
|
+
|
|
69
|
+
## Domains
|
|
70
|
+
|
|
71
|
+
Add both the bare domain and `www` to the project, with the one Hearth uses as primary, so the other redirects to it.
|
|
72
|
+
Hearth's web address, the webhook and the primary domain must all be the same address. Netlify and Vercel both
|
|
73
|
+
recommend `www` as primary, Netlify especially when DNS is hosted elsewhere.
|
|
74
|
+
|
|
75
|
+
- **Netlify:** add both under Domain management and set the primary there.
|
|
76
|
+
- **Vercel:** under Settings → Domains, choose Edit on the one to redirect, then **Redirect to**.
|
|
77
|
+
|
|
78
|
+
## When something's wrong
|
|
79
|
+
|
|
80
|
+
| What you see | Why, and what to do |
|
|
81
|
+
| --- | --- |
|
|
82
|
+
| The build fails with ``Hearth: `apiKey` is required`` | The variables are missing for that deploy's environment, previews included |
|
|
83
|
+
| Published changes don't show | The webhook isn't set, or `HEARTH_WEBHOOK_SECRET` doesn't match Hearth's signing secret. Deploy again after changing it |
|
|
84
|
+
| The webhook gets a 401 and it isn't the secret | On Vercel, it points at a protected deployment address rather than the production domain |
|
|
85
|
+
| Published changes show on production but not a preview | Expected: the webhook only reaches production |
|
|
86
|
+
| A page stays stale on Netlify | Run `curl -D - -o /dev/null -H "x-nf-debug-logging: 1" <url>`. `Debug-Netlify-Cache-Tag` should list `hearth`. If it doesn't, the site's SDK is older than 0.24.1: run `pnpm hearth:update` |
|
|
87
|
+
| "Preview on the website" opens the wrong place or a login | Check **Preview address** in API & webhooks. On Vercel it must be the production domain |
|
|
88
|
+
| A form fails in a page opened before a deploy, and works after a reload | Skew protection is off: see the host's table above |
|
package/template/README.md
CHANGED
|
@@ -41,17 +41,17 @@ without touching your styling. See "Styling the site" below.
|
|
|
41
41
|
|
|
42
42
|
## Working with an AI coding assistant
|
|
43
43
|
|
|
44
|
-
The project comes with instructions for coding agents
|
|
45
|
-
`
|
|
44
|
+
The project comes with instructions for coding agents in `AGENTS.md`, which Codex, Cursor, Copilot and others read on
|
|
45
|
+
their own. `CLAUDE.md` and `GEMINI.md` point Claude Code and Gemini CLI to it. Once your API key is in `.env.local`:
|
|
46
46
|
|
|
47
47
|
1. **Check the connection yourself, or have the agent do it:** `pnpm hearth:check` confirms the key works and
|
|
48
48
|
lists the published pages and the block types they use. It never prints the key.
|
|
49
49
|
2. **Give the agent the full reference:** `pnpm hearth:docs` saves Hearth's API guide to `hearth-api.md` (not
|
|
50
|
-
committed), and installs any coding-assistant skills Hearth offers your site in `.
|
|
51
|
-
instructions already point it at both.
|
|
50
|
+
committed), and installs any coding-assistant skills Hearth offers your site in `.agents/skills/` and
|
|
51
|
+
`.claude/skills/`. The agent's instructions already point it at both.
|
|
52
52
|
3. **Then tell it what to build.** Prompts that work well:
|
|
53
53
|
|
|
54
|
-
- *"Run the first steps in
|
|
54
|
+
- *"Run the first steps in AGENTS.md and tell me what's published and which blocks the site uses."*
|
|
55
55
|
- *"Restyle the site to match the brand: colors `#1F3A5F` and `#C8A15A`, font Lora for headings. Start with
|
|
56
56
|
the tokens in `src/theme/theme.css`, then the hero, card grid and media-text slots in `src/theme/index.ts`."*
|
|
57
57
|
- *"Match the current website at https://example.org: compare each published page with the same page there
|
|
@@ -83,8 +83,8 @@ Keep the API key out of chat: the agent never needs to see it, only `.env.local`
|
|
|
83
83
|
- Use `<Img priority>` for the image at the top of a page, and keep one `<h1>` per page (the Hero block renders it).
|
|
84
84
|
- Give `<Img>` the `crop` of an image that fills a frame (`crop={4 / 3}` for `aspect-[4/3] object-cover`) so it keeps
|
|
85
85
|
the focal point set in Hearth's media library in view.
|
|
86
|
-
- Add both the bare domain and `www` to the site
|
|
87
|
-
redirects
|
|
86
|
+
- Add both the bare domain and `www` to the site at your host, with the one Hearth uses as primary, so the other
|
|
87
|
+
redirects to it (see `HOSTING.md`).
|
|
88
88
|
|
|
89
89
|
### Styling the site
|
|
90
90
|
|
|
@@ -95,7 +95,7 @@ Keep the API key out of chat: the agent never needs to see it, only `.env.local`
|
|
|
95
95
|
| `src/hearth/theme/defaults.ts` | Every slot and its default classes: read it to find the slot to change |
|
|
96
96
|
| `src/theme/fonts.ts` | Web fonts (next/font) |
|
|
97
97
|
| `src/theme/custom-blocks.tsx` | Components for the site's own block types (Settings → Custom blocks in Hearth) |
|
|
98
|
-
| `src/theme/overrides.tsx` | Last resort: replace a whole block's component, the logo, the footer, the event or fundraiser page, their cards or the donation form (see `
|
|
98
|
+
| `src/theme/overrides.tsx` | Last resort: replace a whole block's component, the logo, the footer, the event or fundraiser page, their cards or the donation form (see `AGENTS.md`) |
|
|
99
99
|
| `SITE.md` | Notes about this site's design, for the next person or coding agent |
|
|
100
100
|
|
|
101
101
|
A slot holds the classes of one element, e.g. `hero.title` or `cardGrid.card`. Set a string to replace the default,
|
|
@@ -143,24 +143,11 @@ import { GlobalBlock } from "@/hearth/blocks/global";
|
|
|
143
143
|
`hearthNext.getGlobalBlocks()` lists every global block (key, name, type, pop-up, the pages and menu items it's on,
|
|
144
144
|
the resolved block); `hearthNext.getGlobalBlock(keyOrId)` gets one.
|
|
145
145
|
|
|
146
|
-
## Deploying
|
|
146
|
+
## Deploying
|
|
147
147
|
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
2. **Add environment variables** in Netlify: `HEARTH_SITE`, and `HEARTH_API_KEY` as a secret. Use a separate key
|
|
152
|
-
named "Netlify", so you can revoke one without breaking the other. Set them **before the first deploy**: the
|
|
153
|
-
build reads content from Hearth and fails without them. Nothing else is needed; the packages are public on npm.
|
|
154
|
-
3. **Deploy**, and note the site's domain.
|
|
155
|
-
4. **Set the web address in Hearth.** The site's admin sets it under **Settings → Your site → Web address**.
|
|
156
|
-
Until then, search engines are kept out.
|
|
157
|
-
5. **Connect the publish webhook.** In **API & webhooks → Webhook and preview**, set **Revalidation
|
|
158
|
-
webhook** to `https://<domain>/api/revalidate` and save. A **Signing secret** appears: add it in Netlify as
|
|
159
|
-
`HEARTH_WEBHOOK_SECRET`, then **redeploy**, since Netlify only applies new variables on the next deploy.
|
|
160
|
-
6. **Clear the Preview address** if you set it to localhost. Empty means the live site's `/api/preview`.
|
|
161
|
-
7. **Check it:** publish a small change in Hearth. It should show on the live site within a few seconds.
|
|
162
|
-
|
|
163
|
-
Without the webhook, published changes still appear, but can take up to an hour.
|
|
148
|
+
The site runs on Netlify or Vercel with no code changes. **`HOSTING.md` has the steps and the best settings for each.**
|
|
149
|
+
Set `HEARTH_SITE` and `HEARTH_API_KEY` before the first deploy, because the build reads content from Hearth. The
|
|
150
|
+
packages are public on npm, so nothing else is needed to install them.
|
|
164
151
|
|
|
165
152
|
## When something's wrong
|
|
166
153
|
|
|
@@ -168,7 +155,7 @@ Without the webhook, published changes still appear, but can take up to an hour.
|
|
|
168
155
|
| --- | --- |
|
|
169
156
|
| "Page not found" on every page | Nothing is published yet, or `HEARTH_SITE` is wrong. Publish the homepage in Hearth; check the address against Hearth's address bar |
|
|
170
157
|
| Not sure what's wrong | Run `pnpm hearth:check`: it checks `.env.local` and the connection and says what to fix |
|
|
171
|
-
| ``Hearth: `apiKey` is required`` (dev or build) | `HEARTH_API_KEY` is missing from `.env.local`, or from
|
|
158
|
+
| ``Hearth: `apiKey` is required`` (dev or build) | `HEARTH_API_KEY` is missing from `.env.local`, or from your host's variables |
|
|
172
159
|
| An `unauthorized` (401) error from Hearth | The key is mistyped, revoked, or belongs to another site. Create a new one for this site |
|
|
173
160
|
| Published changes don't show locally | Hard refresh (Ctrl+Shift+R). The webhook can't reach your computer |
|
|
174
161
|
| Published changes don't show live | The webhook isn't set, or `HEARTH_WEBHOOK_SECRET` doesn't match Hearth's signing secret (redeploy after changing it) |
|
package/template/SITE.md
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
# This site
|
|
2
2
|
|
|
3
3
|
Notes for whoever works on this site next, people and coding agents alike: the design's intent, decisions, and
|
|
4
|
-
anything unusual. Hearth's own instructions are in `
|
|
4
|
+
anything unusual. Hearth's own instructions are in `AGENTS.md` (replaced by `pnpm hearth:update`); this file is the
|
|
5
5
|
site's and stays as it is.
|
|
6
6
|
|
|
7
7
|
## Design
|
package/template/_gitignore
CHANGED
|
@@ -2,11 +2,12 @@ node_modules/
|
|
|
2
2
|
/.next/
|
|
3
3
|
/out/
|
|
4
4
|
.netlify
|
|
5
|
+
.vercel
|
|
5
6
|
.DS_Store
|
|
6
7
|
*.tsbuildinfo
|
|
7
8
|
next-env.d.ts
|
|
8
9
|
|
|
9
|
-
# Keys live here locally and in
|
|
10
|
+
# Keys live here locally and in your host's environment variables, never in git.
|
|
10
11
|
.env*
|
|
11
12
|
!.env.example
|
|
12
13
|
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
// pnpm hearth:check says whether The Smithy has released anything newer than this site uses, checks .env.local and
|
|
3
3
|
// the connection to Hearth, then lists what's published
|
|
4
4
|
// pnpm hearth:docs saves Hearth's full API guide to hearth-api.md (not committed) for an AI assistant to read,
|
|
5
|
-
// and installs any coding-assistant skills Hearth offers this site
|
|
5
|
+
// and installs any coding-assistant skills Hearth offers this site
|
|
6
6
|
// pnpm hearth:update updates Hearth's code in this site (src/hearth and the other files its manifest lists) to the
|
|
7
7
|
// latest release, and the packages it needs; src/theme and your own files stay as they are
|
|
8
8
|
import { spawnSync } from "node:child_process";
|
|
@@ -18,6 +18,7 @@ const fail = (message) => {
|
|
|
18
18
|
};
|
|
19
19
|
|
|
20
20
|
const PACKAGE = "@the-smithy/create-hearth-site";
|
|
21
|
+
const SKILL_DIRS = [".agents/skills", ".claude/skills"];
|
|
21
22
|
const MANIFEST = "src/hearth/manifest.json";
|
|
22
23
|
/** The packages hearth:update raises; it leaves the site's other Smithy packages to it. */
|
|
23
24
|
const UPDATED_TOGETHER = [PACKAGE, "@the-smithy/hearth", "@the-smithy/hearth-schema"];
|
|
@@ -151,36 +152,38 @@ async function docs() {
|
|
|
151
152
|
}
|
|
152
153
|
|
|
153
154
|
/**
|
|
154
|
-
* Installs the coding-assistant skills Hearth offers this site
|
|
155
|
-
*
|
|
156
|
-
*
|
|
155
|
+
* Installs the coding-assistant skills Hearth offers this site, replacing the ones it installed before, in
|
|
156
|
+
* .agents/skills/ (the shared place Codex, Gemini CLI, Cursor and Copilot look) and .claude/skills/ (the only one
|
|
157
|
+
* Claude Code reads). Their names are listed in each folder's .gitignore, so they're never committed and the site's
|
|
158
|
+
* own skills there are left alone.
|
|
157
159
|
*/
|
|
158
160
|
async function skills(env) {
|
|
159
161
|
const res = await fetch(`${env.baseUrl}/admin/docs/skills.json`, { headers: { Authorization: `Bearer ${env.apiKey}` } });
|
|
160
162
|
if (!res.ok) return console.log(` Hearth didn't return the coding-assistant skills (${res.status}); skipped them.`);
|
|
161
163
|
const { skills: offered = [] } = await res.json();
|
|
162
|
-
const dir = path.join(".claude", "skills");
|
|
163
|
-
const listFile = path.join(dir, ".gitignore");
|
|
164
|
-
const before = existsSync(listFile)
|
|
165
|
-
? readFileSync(listFile, "utf8").split(/\r?\n/).map((l) => l.replace(/^\/|\/$/g, "")).filter((l) => l && !l.startsWith("#") && l !== ".gitignore")
|
|
166
|
-
: [];
|
|
167
164
|
const safe = (p) => /^[a-z0-9][a-z0-9-]*$/.test(p);
|
|
168
165
|
const names = offered.map((s) => s.name).filter(safe);
|
|
169
|
-
for (const
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
166
|
+
for (const dir of SKILL_DIRS) {
|
|
167
|
+
const listFile = path.join(dir, ".gitignore");
|
|
168
|
+
const before = existsSync(listFile)
|
|
169
|
+
? readFileSync(listFile, "utf8").split(/\r?\n/).map((l) => l.replace(/^\/|\/$/g, "")).filter((l) => l && !l.startsWith("#") && l !== ".gitignore")
|
|
170
|
+
: [];
|
|
171
|
+
for (const name of before) if (safe(name) && !names.includes(name)) rmSync(path.join(dir, name), { recursive: true, force: true });
|
|
172
|
+
for (const skill of offered.filter((s) => safe(s.name))) {
|
|
173
|
+
rmSync(path.join(dir, skill.name), { recursive: true, force: true });
|
|
174
|
+
for (const file of skill.files) {
|
|
175
|
+
if (!file.path.split("/").every((part) => /^[A-Za-z0-9._-]+$/.test(part) && part !== "..")) continue;
|
|
176
|
+
const target = path.join(dir, skill.name, ...file.path.split("/"));
|
|
177
|
+
mkdirSync(path.dirname(target), { recursive: true });
|
|
178
|
+
writeFileSync(target, file.content);
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
if (names.length || before.length) {
|
|
182
|
+
mkdirSync(dir, { recursive: true });
|
|
183
|
+
writeFileSync(listFile, ["# Installed by pnpm hearth:docs from Hearth; refreshed each run.", ".gitignore", ...names.map((n) => `/${n}/`)].join("\n") + "\n");
|
|
177
184
|
}
|
|
178
185
|
}
|
|
179
|
-
if (names.length
|
|
180
|
-
mkdirSync(dir, { recursive: true });
|
|
181
|
-
writeFileSync(listFile, ["# Installed by pnpm hearth:docs from Hearth; refreshed each run.", ".gitignore", ...names.map((n) => `/${n}/`)].join("\n") + "\n");
|
|
182
|
-
}
|
|
183
|
-
if (names.length) console.log(`✓ Installed ${names.length === 1 ? "a skill" : `${names.length} skills`} for your coding assistant in .claude/skills/: ${names.join(", ")}.`);
|
|
186
|
+
if (names.length) console.log(`✓ Installed ${names.length === 1 ? "a skill" : `${names.length} skills`} for your coding assistant in ${SKILL_DIRS.map((d) => `${d}/`).join(" and ")}: ${names.join(", ")}.`);
|
|
184
187
|
}
|
|
185
188
|
|
|
186
189
|
|
|
@@ -237,7 +240,7 @@ async function update() {
|
|
|
237
240
|
const force = args.includes("--force");
|
|
238
241
|
const wanted = args.includes("--version") ? args[args.indexOf("--version") + 1] : "latest";
|
|
239
242
|
if (!wanted) fail("Give a version after --version, e.g. --version 0.21.0.");
|
|
240
|
-
if (!existsSync(MANIFEST)) fail(`There's no ${MANIFEST}, so this site predates hearth:update. Move it onto the template's structure first (see
|
|
243
|
+
if (!existsSync(MANIFEST)) fail(`There's no ${MANIFEST}, so this site predates hearth:update. Move it onto the template's structure first (see AGENTS.md).`);
|
|
241
244
|
const old = JSON.parse(readFileSync(MANIFEST, "utf8"));
|
|
242
245
|
const own = old.own ?? [];
|
|
243
246
|
|
|
@@ -22,13 +22,15 @@
|
|
|
22
22
|
"src/app/api/revalidate/route.ts",
|
|
23
23
|
"scripts/hearth.mjs",
|
|
24
24
|
"CLAUDE.md",
|
|
25
|
-
"AGENTS.md"
|
|
25
|
+
"AGENTS.md",
|
|
26
|
+
"HOSTING.md"
|
|
26
27
|
],
|
|
27
28
|
"seeded": [
|
|
28
29
|
"src/theme/",
|
|
29
30
|
"src/app/icon.tsx",
|
|
30
31
|
"src/app/apple-icon.tsx",
|
|
31
32
|
"SITE.md",
|
|
33
|
+
"GEMINI.md",
|
|
32
34
|
"README.md",
|
|
33
35
|
"next.config.ts",
|
|
34
36
|
"netlify.toml",
|