@the-smithy/create-hearth-site 0.28.2 → 0.30.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@the-smithy/create-hearth-site",
3
- "version": "0.28.2",
3
+ "version": "0.30.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",
@@ -30,7 +30,7 @@
30
30
  ],
31
31
  "devDependencies": {
32
32
  "vitest": "^5.0.3",
33
- "@the-smithy/hearth-schema": "^0.28.2"
33
+ "@the-smithy/hearth-schema": "^0.30.0"
34
34
  },
35
35
  "scripts": {
36
36
  "test": "node --test \"test/**/*.test.js\" && vitest run test/blocks.test.ts test/overrides.test.ts",
@@ -87,10 +87,10 @@ Work in this order, and stop at the first step that gets the result:
87
87
  don't reach it, so it falls behind Hearth. Use one only when everything above can't get there, build it from
88
88
  `src/hearth/ui.tsx` and the theme, and say why in `SITE.md`.
89
89
 
90
- To style blocks without setting up pages for them, open `/hearth/blocks` while `pnpm dev` is running: every block with
90
+ To style blocks without setting up pages for them, open `/hearth/blocks` while `pnpm dev` is running: each block with
91
91
  its options and states, in sample content, drawn with this site's theme, so a change in `src/theme` shows at once.
92
- `/hearth/blocks?block=hero` shows one block. Its **Unstyled** switch (`/hearth/blocks/unstyled`) shows them with no design
93
- and each element's slot name. Neither is served in production. Global blocks and the site's custom blocks aren't in it.
92
+ It shows one block at a time: `/hearth/blocks?block=hero` goes to one, and `?block=all` shows the whole list. Its
93
+ **Unstyled** switch (`/hearth/blocks/unstyled`) shows them with no design and each element's slot name. Neither is served in production. Global blocks and the site's custom blocks aren't in it.
94
94
  Without a browser, the unstyled page is `node_modules/@the-smithy/hearth/block-reference.html`, but it's large: for slots,
95
95
  use `src/hearth/theme/defaults.ts`.
96
96
 
@@ -98,7 +98,7 @@ Keep the API key out of chat: the agent never needs to see it, only `.env.local`
98
98
  | `src/theme/theme.css` | Brand tokens (colors, fonts, content width) and the site's own CSS. **Start here** |
99
99
  | `src/theme/index.ts` | The Tailwind classes of any part of any block, page, form, header or footer, by slot |
100
100
  | `src/hearth/theme/defaults.ts` | Every slot and its default classes: read it to find the slot to change |
101
- | `/hearth/blocks` | Not a file: while `pnpm dev` runs, this address shows every block on one page with your theme, without setting up pages for them. `?block=hero` shows one. Its **Unstyled** switch shows them with no design, and each element's slot name |
101
+ | `/hearth/blocks` | Not a file: while `pnpm dev` runs, this address shows each block with your theme, one at a time, without setting up pages for them. `?block=hero` goes to one, and `?block=all` shows the whole list. Its **Unstyled** switch shows them with no design, and each element's slot name |
102
102
  | `src/theme/fonts.ts` | Web fonts (next/font) |
103
103
  | `src/theme/custom-blocks.tsx` | Components for the site's own block types (Settings → Custom blocks in Hearth) |
104
104
  | `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`) |
@@ -246,9 +246,9 @@ async function drift() {
246
246
 
247
247
  // Drift's skill follows its version, so like Hearth's skills it's installed rather than committed.
248
248
  for (const dir of SKILL_DIRS) listSkills(dir, listedSkills(dir));
249
- const install = spawnSync(bin, ["install", "."], { encoding: "utf8" });
249
+ const install = spawnSync(bin, ["install", ".", "--hook"], { encoding: "utf8" });
250
250
  if (install.status !== 0) fail(`Drift couldn't install its skill: ${install.stderr?.trim() || install.error?.message}`);
251
- console.log(`✓ Installed Drift's skill in ${SKILL_DIRS.map((d) => `${d}/`).join(" and ")}.`);
251
+ console.log(`✓ Installed Drift's skill in ${SKILL_DIRS.map((d) => `${d}/`).join(" and ")}, and its Claude Code hook in .claude/settings.json.`);
252
252
  }
253
253
 
254
254
  /** The files in a zip archive, by name. */
@@ -1,10 +1,11 @@
1
1
  import { readFileSync } from "node:fs";
2
2
  import path from "node:path";
3
3
 
4
- const SWITCH = `<span class="view-switch"><span class="view-current">Unstyled</span><a href="/hearth/blocks" onclick="this.href='/hearth/blocks'+location.hash">This site's theme</a></span>`;
4
+ // Stays on the same block, or on the whole list: #block-hero here is ?block=hero there.
5
+ const SWITCH = `<span class="view-switch"><span class="view-current">Unstyled</span><a href="/hearth/blocks" onclick="var id=location.hash.slice(1);if(id&&id!=='top')this.href='/hearth/blocks?block='+id.replace('block-','')">This site's theme</a></span>`;
5
6
 
6
7
  /**
7
- * Every block unstyled, with its options, states and slot names, to build against while developing. Only under
8
+ * Each block unstyled, with its options, states and slot names, to build against while developing. Only under
8
9
  * `pnpm dev`: the page comes from the SDK, so it matches this site's release, and visitors never see it.
9
10
  */
10
11
  export function GET() {
@@ -24,15 +24,6 @@ export function SampleGuard() {
24
24
  return null;
25
25
  }
26
26
 
27
- /** A link to the block library's other view, staying on the same block. */
28
- export function ViewLink({ href, children }: { href: string; children: ReactNode }) {
29
- return (
30
- <a href={href} className="hearth-library-view" onClick={(e) => (e.currentTarget.href = href + window.location.hash)}>
31
- {children}
32
- </a>
33
- );
34
- }
35
-
36
27
  /** One sample failing (a site without Giving has no fundraisers to offer) leaves the rest of the page working. */
37
28
  export class SampleBoundary extends Component<{ children: ReactNode }, { message?: string }> {
38
29
  state: { message?: string } = {};
@@ -3,13 +3,17 @@ import Link from "next/link";
3
3
  import { notFound } from "next/navigation";
4
4
  import { Fragment, Suspense, type ReactNode } from "react";
5
5
  import { Blocks } from "../blocks";
6
- import { SampleBoundary, SampleGuard, ViewLink } from "./controls";
6
+ import { SampleBoundary, SampleGuard } from "./controls";
7
7
  import { FormStates, Ground, GROUND_NAMES } from "./pieces";
8
8
  import { blockSamples, libraryGroups } from "./samples";
9
9
 
10
10
  export const metadata: Metadata = { title: "Block library", robots: { index: false, follow: false } };
11
11
 
12
12
  const TONES = ["default", "muted", "inverse", "accent"] as const;
13
+ const START_HERE = [
14
+ { type: "grounds", label: "Grounds and buttons" },
15
+ { type: "form-states", label: "Form states" },
16
+ ];
13
17
 
14
18
  // Its own plain styles, so that nothing in a site's theme changes the tools around the samples.
15
19
  const CSS = `
@@ -20,6 +24,9 @@ const CSS = `
20
24
  .hearth-library-switch > * { padding: 4px 12px; }
21
25
  .hearth-library-switch > * + * { border-left: 1px solid #d8d8d4; }
22
26
  .hearth-library-current { background: #1f4fd8; color: #fff; }
27
+ .hearth-library-steps { display: inline-flex; gap: 8px; }
28
+ .hearth-library-steps > * { padding: 4px 12px; border: 1px solid #d8d8d4; border-radius: 6px; }
29
+ .hearth-library-steps > span { color: #5c5c58; }
23
30
  .hearth-library-jump { position: relative; }
24
31
  .hearth-library-jump summary { cursor: pointer; padding: 4px 12px; border: 1px solid #d8d8d4; border-radius: 6px; list-style: none; }
25
32
  .hearth-library-jump summary::after { content: " ▾"; }
@@ -53,18 +60,26 @@ function Sample({ id, name, label, action, children }: { id?: string; name: stri
53
60
  }
54
61
 
55
62
  /**
56
- * Every block on one page, drawn by the real components with this site's theme and overrides, so that a change in
57
- * src/theme shows here at once. For `pnpm dev` only. Its samples stand in for a site's content, so nothing depends on
58
- * which pages exist, and nothing here is saved. ?block=hero shows one block's samples.
63
+ * The blocks, drawn by the real components with this site's theme and overrides, so that a change in src/theme shows
64
+ * here at once. For `pnpm dev` only. Its samples stand in for a site's content, so nothing depends on which pages
65
+ * exist, and nothing here is saved. It shows one block at a time, from ?block=hero; ?block=all shows the whole list.
59
66
  */
60
67
  export default async function BlockLibrary({ searchParams }: { searchParams: Promise<{ block?: string | string[] }> }) {
61
68
  if (process.env.NODE_ENV !== "development") notFound();
62
69
  const { block } = await searchParams;
63
70
  const samples = blockSamples(new Date());
64
- const all = libraryGroups.flatMap((g) => g.blocks);
65
- const only = all.find((b) => b.type === block);
66
- const unstyled = `/hearth/blocks/unstyled${only ? `#block-${only.type}` : ""}`;
67
- const showing = only ? [only] : all;
71
+ const blocks = libraryGroups.flatMap((g) => g.blocks);
72
+ const items: { type: string; label: string }[] = [...START_HERE, ...blocks];
73
+ const everything = block === "all";
74
+ const index = Math.max(
75
+ items.findIndex((item) => item.type === block),
76
+ 0,
77
+ );
78
+ const current = items[index];
79
+ const anchor = START_HERE.includes(current) ? current.type : `block-${current.type}`;
80
+ const unstyled = `/hearth/blocks/unstyled#${everything ? "all" : anchor}`;
81
+ const only = (type: string) => (everything ? <Link href={`/hearth/blocks?block=${type}`}>Only this block</Link> : undefined);
82
+ const showing = everything ? blocks : blocks.filter((b) => b.type === current.type);
68
83
  return (
69
84
  <>
70
85
  <style>{CSS}</style>
@@ -72,14 +87,17 @@ export default async function BlockLibrary({ searchParams }: { searchParams: Pro
72
87
  <div className="hearth-library-bar">
73
88
  <b>Block library</b>
74
89
  <span className="hearth-library-switch">
75
- <ViewLink href={unstyled}>Unstyled</ViewLink>
90
+ <a href={unstyled}>Unstyled</a>
76
91
  <span className="hearth-library-current">This site&apos;s theme</span>
77
92
  </span>
78
- <details key={only?.type ?? "all"} className="hearth-library-jump">
79
- <summary>{only ? only.label : "All blocks"}</summary>
93
+ <span className="hearth-library-switch">
94
+ {everything ? <Link href="/hearth/blocks">One block</Link> : <span className="hearth-library-current">One block</span>}
95
+ {everything ? <span className="hearth-library-current">All blocks</span> : <Link href={`/hearth/blocks?block=all#${anchor}`}>All blocks</Link>}
96
+ </span>
97
+ <details key={everything ? "all" : current.type} className="hearth-library-jump">
98
+ <summary>{everything ? "Choose a block" : current.label}</summary>
80
99
  <nav aria-label="Blocks">
81
- <Link href="/hearth/blocks">Every block on one page</Link>
82
- {libraryGroups.map((g) => (
100
+ {[{ group: "Start here", blocks: START_HERE }, ...libraryGroups].map((g) => (
83
101
  <Fragment key={g.group}>
84
102
  <p>{g.group}</p>
85
103
  {g.blocks.map((b) => (
@@ -91,23 +109,29 @@ export default async function BlockLibrary({ searchParams }: { searchParams: Pro
91
109
  ))}
92
110
  </nav>
93
111
  </details>
112
+ {everything ? null : (
113
+ <span className="hearth-library-steps">
114
+ {index > 0 ? <Link href={`/hearth/blocks?block=${items[index - 1].type}`}>← Previous</Link> : <span>← Previous</span>}
115
+ {index < items.length - 1 ? <Link href={`/hearth/blocks?block=${items[index + 1].type}`}>Next →</Link> : <span>Next →</span>}
116
+ </span>
117
+ )}
94
118
  <span className="hearth-library-note">Sample content. Links and forms here do nothing.</span>
95
119
  </div>
96
- {only
97
- ? null
98
- : TONES.map((tone) => (
99
- <Sample key={tone} id={tone === "default" ? "grounds" : undefined} name="Grounds" label={`${GROUND_NAMES[tone]} ground`}>
120
+ {everything || current.type === "grounds"
121
+ ? TONES.map((tone) => (
122
+ <Sample key={tone} id={tone === "default" ? "grounds" : undefined} name="Grounds" label={`${GROUND_NAMES[tone]} ground`} action={only("grounds")}>
100
123
  <Ground tone={tone} />
101
124
  </Sample>
102
- ))}
103
- {only ? null : (
104
- <Sample id="form-states" name="Form states" label="Errors, filled-in fields, options and the success message">
125
+ ))
126
+ : null}
127
+ {everything || current.type === "form-states" ? (
128
+ <Sample id="form-states" name="Form states" label="Errors, filled-in fields, options and the success message" action={only("form-states")}>
105
129
  <FormStates />
106
130
  </Sample>
107
- )}
131
+ ) : null}
108
132
  {showing.map((b) =>
109
133
  samples[b.type].map((s, i) => (
110
- <Sample key={s.block.id} id={i ? undefined : `block-${b.type}`} name={b.label} label={s.label} action={only ? <Link href="/hearth/blocks">Show every block</Link> : <Link href={`/hearth/blocks?block=${b.type}`}>Only this block</Link>}>
134
+ <Sample key={s.block.id} id={i ? undefined : `block-${b.type}`} name={b.label} label={s.label} action={only(b.type)}>
111
135
  <Blocks blocks={[s.block]} />
112
136
  </Sample>
113
137
  )),