@the-smithy/hearth 0.13.0 → 0.14.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 +20 -5
- package/dist/react.d.ts +55 -2
- package/dist/react.js +143 -6
- package/llms.md +18 -0
- package/package.json +3 -2
package/README.md
CHANGED
|
@@ -9,12 +9,15 @@ nothing without a Hearth account. Developer access is by invitation: request it
|
|
|
9
9
|
|
|
10
10
|
## Getting started
|
|
11
11
|
|
|
12
|
-
Most sites start from the starter template, which has this package set up already
|
|
12
|
+
Most sites start from the starter template, which has this package set up already. For example, for a site
|
|
13
|
+
whose address in Hearth is `acme` (the part after `/admin/sites/` in Hearth's address bar):
|
|
13
14
|
|
|
14
15
|
```sh
|
|
15
|
-
pnpm create @the-smithy/hearth-site
|
|
16
|
+
pnpm create @the-smithy/hearth-site@latest acme-website --site acme
|
|
16
17
|
```
|
|
17
18
|
|
|
19
|
+
`acme-website` is the folder it creates. Keep `@latest`: pnpm otherwise skips a release in its first day.
|
|
20
|
+
|
|
18
21
|
To add it to an existing project instead:
|
|
19
22
|
|
|
20
23
|
```sh
|
|
@@ -22,9 +25,9 @@ pnpm add @the-smithy/hearth
|
|
|
22
25
|
```
|
|
23
26
|
|
|
24
27
|
```dotenv
|
|
25
|
-
# .env.local
|
|
26
|
-
HEARTH_SITE
|
|
27
|
-
HEARTH_API_KEY
|
|
28
|
+
# .env.local (never committed)
|
|
29
|
+
HEARTH_SITE=acme
|
|
30
|
+
HEARTH_API_KEY=hk_… # Hearth → the site → API & webhooks → API keys
|
|
28
31
|
```
|
|
29
32
|
|
|
30
33
|
```ts
|
|
@@ -37,6 +40,18 @@ export const hearth = createHearthClient({
|
|
|
37
40
|
});
|
|
38
41
|
```
|
|
39
42
|
|
|
43
|
+
## With an AI coding assistant
|
|
44
|
+
|
|
45
|
+
The package ships `llms.md`, a short reference written for coding agents: point yours at
|
|
46
|
+
`node_modules/@the-smithy/hearth/llms.md`. Once the key is in `.env.local`, the full API guide can be saved for
|
|
47
|
+
the agent too (sites made from the starter have `pnpm hearth:docs` for this):
|
|
48
|
+
|
|
49
|
+
```sh
|
|
50
|
+
curl -H "Authorization: Bearer $HEARTH_API_KEY" https://hearth.thesmithy.io/admin/docs/api.md -o hearth-api.md
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
Keep `hearth-api.md` out of git, and the key out of chat: the agent only needs it in `.env.local`.
|
|
54
|
+
|
|
40
55
|
## Documentation
|
|
41
56
|
|
|
42
57
|
Once you have developer access, the full documentation (every method, Next.js helpers, rendering blocks,
|
package/dist/react.d.ts
CHANGED
|
@@ -1,7 +1,60 @@
|
|
|
1
1
|
import * as react from 'react';
|
|
2
|
-
import {
|
|
2
|
+
import { ReactNode, ComponentType } from 'react';
|
|
3
3
|
import { KnownBlock, ResolvedBlock } from '@the-smithy/hearth-schema';
|
|
4
4
|
|
|
5
|
+
/** Class names for each part of a guide. Leave any out to render that part unstyled. */
|
|
6
|
+
type GuideTheme = {
|
|
7
|
+
/** Wraps the blocks (spacing between them). */
|
|
8
|
+
root?: string;
|
|
9
|
+
/** A block's heading (h2). */
|
|
10
|
+
heading?: string;
|
|
11
|
+
/** Intros, captions, small print. */
|
|
12
|
+
muted?: string;
|
|
13
|
+
/** Wraps rendered Markdown. */
|
|
14
|
+
prose?: string;
|
|
15
|
+
/** Surfaces: steps, cards, the question list, numbers. */
|
|
16
|
+
card?: string;
|
|
17
|
+
/** The number on a step. */
|
|
18
|
+
stepNumber?: string;
|
|
19
|
+
/** A step's or card's title. */
|
|
20
|
+
itemTitle?: string;
|
|
21
|
+
/** Text links. */
|
|
22
|
+
link?: string;
|
|
23
|
+
/** The main button in a call to action. */
|
|
24
|
+
button?: string;
|
|
25
|
+
/** The call-to-action box. */
|
|
26
|
+
callout?: string;
|
|
27
|
+
/** A quote. */
|
|
28
|
+
quote?: string;
|
|
29
|
+
/** Images and video frames. */
|
|
30
|
+
media?: string;
|
|
31
|
+
/** The "+" on an expandable question. */
|
|
32
|
+
accent?: string;
|
|
33
|
+
};
|
|
34
|
+
type GuideLinkProps = {
|
|
35
|
+
href: string;
|
|
36
|
+
className?: string;
|
|
37
|
+
children: ReactNode;
|
|
38
|
+
};
|
|
39
|
+
type GuideBlocksProps = {
|
|
40
|
+
/** The guide page's blocks, as the API returns them. Types a guide doesn't use are skipped. */
|
|
41
|
+
blocks: readonly unknown[];
|
|
42
|
+
theme?: GuideTheme;
|
|
43
|
+
/** Render Markdown (e.g. with react-markdown). Without it, paragraphs are shown as plain text. */
|
|
44
|
+
renderMarkdown?: (markdown: string) => ReactNode;
|
|
45
|
+
/** Your router's link (e.g. next/link) for links within the product. External links always use <a>. */
|
|
46
|
+
linkComponent?: ComponentType<GuideLinkProps>;
|
|
47
|
+
};
|
|
48
|
+
/** Block types a guide can use; anything else is skipped. */
|
|
49
|
+
declare const GUIDE_BLOCK_TYPES: readonly ["hero", "richText", "mediaText", "schedule", "cardGrid", "faq", "gallery", "video", "quote", "stats", "cta"];
|
|
50
|
+
/**
|
|
51
|
+
* Render a guide's blocks with your product's styles.
|
|
52
|
+
*
|
|
53
|
+
* <GuideBlocks blocks={page.blocks} theme={{ card: "rounded-xl bg-white ring-1 ring-ash", link: "underline" }}
|
|
54
|
+
* renderMarkdown={(md) => <ReactMarkdown>{md}</ReactMarkdown>} linkComponent={Link} />
|
|
55
|
+
*/
|
|
56
|
+
declare function GuideBlocks({ blocks, theme, renderMarkdown, linkComponent: InternalLink }: GuideBlocksProps): react.JSX.Element;
|
|
57
|
+
|
|
5
58
|
type KnownType = KnownBlock["type"];
|
|
6
59
|
/** One component per block type you render; each receives exactly that block. */
|
|
7
60
|
type BlockComponents = {
|
|
@@ -42,4 +95,4 @@ declare function HearthEmbed({ block, className }: {
|
|
|
42
95
|
className?: string;
|
|
43
96
|
}): react.JSX.Element;
|
|
44
97
|
|
|
45
|
-
export { type BlockComponents, HearthBlocks, HearthEmbed, JsonLd };
|
|
98
|
+
export { type BlockComponents, GUIDE_BLOCK_TYPES, GuideBlocks, type GuideBlocksProps, type GuideLinkProps, type GuideTheme, HearthBlocks, HearthEmbed, JsonLd };
|
package/dist/react.js
CHANGED
|
@@ -1,26 +1,161 @@
|
|
|
1
1
|
// src/react.tsx
|
|
2
2
|
import { Fragment } from "react";
|
|
3
3
|
import { EMBED_ALLOW, EMBED_SANDBOX, embedDocument } from "@the-smithy/hearth-schema";
|
|
4
|
-
|
|
4
|
+
|
|
5
|
+
// src/guide.tsx
|
|
6
|
+
import { videoEmbedUrl } from "@the-smithy/hearth-schema";
|
|
7
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
8
|
+
var GUIDE_BLOCK_TYPES = ["hero", "richText", "mediaText", "schedule", "cardGrid", "faq", "gallery", "video", "quote", "stats", "cta"];
|
|
9
|
+
var stack = (gap) => ({ display: "flex", flexDirection: "column", gap });
|
|
10
|
+
var grid = (min) => ({ display: "grid", gap: "0.75rem", gridTemplateColumns: `repeat(auto-fit, minmax(min(100%, ${min}), 1fr))` });
|
|
11
|
+
var fill = { display: "block", width: "100%", height: "auto" };
|
|
12
|
+
var str = (v) => typeof v === "string" && v ? v : null;
|
|
13
|
+
function plainMarkdown(markdown) {
|
|
14
|
+
return markdown.split(/\n{2,}/).map((p, i) => /* @__PURE__ */ jsx("p", { style: { whiteSpace: "pre-line" }, children: p }, i));
|
|
15
|
+
}
|
|
16
|
+
function GuideBlocks({ blocks, theme = {}, renderMarkdown = plainMarkdown, linkComponent: InternalLink }) {
|
|
17
|
+
const t = theme;
|
|
18
|
+
const md = (markdown) => /* @__PURE__ */ jsx("div", { className: t.prose, children: renderMarkdown(String(markdown ?? "")) });
|
|
19
|
+
const heading = (v) => str(v) ? /* @__PURE__ */ jsx("h2", { className: t.heading, children: str(v) }) : null;
|
|
20
|
+
const intro = (v) => str(v) ? /* @__PURE__ */ jsx("p", { className: t.muted, children: str(v) }) : null;
|
|
21
|
+
const link = (l, className = t.link) => /^https?:\/\//.test(l.href) || !InternalLink ? /* @__PURE__ */ jsx("a", { href: l.href, className, .../^https?:\/\//.test(l.href) ? { target: "_blank", rel: "noopener noreferrer" } : {}, children: l.label }) : /* @__PURE__ */ jsx(InternalLink, { href: l.href, className, children: l.label });
|
|
22
|
+
const img = (i, className = t.media) => /* @__PURE__ */ jsx("img", { src: i.src, alt: i.alt ?? "", className, style: fill, loading: "lazy" });
|
|
23
|
+
function render(block) {
|
|
24
|
+
const d = block.data ?? {};
|
|
25
|
+
switch (block.type) {
|
|
26
|
+
case "hero":
|
|
27
|
+
return /* @__PURE__ */ jsxs("div", { style: stack("0.5rem"), children: [
|
|
28
|
+
heading(d.heading),
|
|
29
|
+
str(d.body) ? md(d.body) : null
|
|
30
|
+
] });
|
|
31
|
+
case "richText":
|
|
32
|
+
return md(d.markdown);
|
|
33
|
+
case "mediaText": {
|
|
34
|
+
const image = d.image;
|
|
35
|
+
return /* @__PURE__ */ jsxs("div", { style: { ...grid("16rem"), alignItems: "start", gap: "1.25rem" }, children: [
|
|
36
|
+
/* @__PURE__ */ jsxs("div", { style: { ...stack("0.75rem"), order: d.imagePosition === "left" ? 2 : 1 }, children: [
|
|
37
|
+
heading(d.heading),
|
|
38
|
+
md(d.markdown),
|
|
39
|
+
d.cta ? /* @__PURE__ */ jsx("p", { children: link(d.cta) }) : null
|
|
40
|
+
] }),
|
|
41
|
+
image ? /* @__PURE__ */ jsx("figure", { style: { margin: 0, order: d.imagePosition === "left" ? 1 : 2 }, children: img(image) }) : null
|
|
42
|
+
] });
|
|
43
|
+
}
|
|
44
|
+
case "schedule": {
|
|
45
|
+
const items = d.items ?? [];
|
|
46
|
+
return /* @__PURE__ */ jsxs("div", { style: stack("0.75rem"), children: [
|
|
47
|
+
heading(d.heading),
|
|
48
|
+
intro(d.intro),
|
|
49
|
+
/* @__PURE__ */ jsx("ol", { style: { ...stack("0.75rem"), listStyle: "none", margin: 0, padding: 0 }, children: items.map((s, i) => /* @__PURE__ */ jsxs("li", { className: t.card, style: { display: "flex", gap: "1rem", alignItems: "flex-start" }, children: [
|
|
50
|
+
/* @__PURE__ */ jsx("span", { className: t.stepNumber, style: { flexShrink: 0 }, children: s.time }),
|
|
51
|
+
/* @__PURE__ */ jsxs("span", { style: { minWidth: 0 }, children: [
|
|
52
|
+
/* @__PURE__ */ jsx("span", { className: t.itemTitle, style: { display: "block" }, children: s.title }),
|
|
53
|
+
s.details ? /* @__PURE__ */ jsx("span", { className: t.muted, style: { display: "block", whiteSpace: "pre-line" }, children: s.details }) : null
|
|
54
|
+
] })
|
|
55
|
+
] }, i)) })
|
|
56
|
+
] });
|
|
57
|
+
}
|
|
58
|
+
case "cardGrid": {
|
|
59
|
+
const items = d.items ?? [];
|
|
60
|
+
return /* @__PURE__ */ jsxs("div", { style: stack("0.75rem"), children: [
|
|
61
|
+
heading(d.heading),
|
|
62
|
+
intro(d.intro),
|
|
63
|
+
/* @__PURE__ */ jsx("div", { style: grid("14rem"), children: items.map((c, i) => /* @__PURE__ */ jsxs("div", { className: t.card, style: stack("0.375rem"), children: [
|
|
64
|
+
c.image ? img(c.image) : null,
|
|
65
|
+
/* @__PURE__ */ jsx("p", { className: t.itemTitle, children: c.title }),
|
|
66
|
+
c.body ? /* @__PURE__ */ jsx("p", { className: t.muted, style: { whiteSpace: "pre-line" }, children: c.body }) : null,
|
|
67
|
+
c.link ? /* @__PURE__ */ jsx("p", { children: link(c.link) }) : null
|
|
68
|
+
] }, i)) })
|
|
69
|
+
] });
|
|
70
|
+
}
|
|
71
|
+
case "faq": {
|
|
72
|
+
const items = d.items ?? [];
|
|
73
|
+
return /* @__PURE__ */ jsxs("div", { style: stack("0.75rem"), children: [
|
|
74
|
+
heading(d.heading),
|
|
75
|
+
/* @__PURE__ */ jsx("div", { style: stack("0.5rem"), children: items.map((q, i) => /* @__PURE__ */ jsxs("details", { className: t.card, children: [
|
|
76
|
+
/* @__PURE__ */ jsxs("summary", { style: { display: "flex", justifyContent: "space-between", gap: "1rem", cursor: "pointer", listStyle: "none" }, className: t.itemTitle, children: [
|
|
77
|
+
q.question,
|
|
78
|
+
/* @__PURE__ */ jsx("span", { "aria-hidden": true, className: t.accent, children: "+" })
|
|
79
|
+
] }),
|
|
80
|
+
/* @__PURE__ */ jsx("div", { style: { marginTop: "0.5rem" }, children: md(q.answer) })
|
|
81
|
+
] }, i)) })
|
|
82
|
+
] });
|
|
83
|
+
}
|
|
84
|
+
case "gallery": {
|
|
85
|
+
const images = d.images ?? [];
|
|
86
|
+
return /* @__PURE__ */ jsxs("div", { style: stack("0.75rem"), children: [
|
|
87
|
+
heading(d.heading),
|
|
88
|
+
intro(d.intro),
|
|
89
|
+
/* @__PURE__ */ jsx("div", { style: grid("14rem"), children: images.map((g, i) => /* @__PURE__ */ jsxs("figure", { style: { margin: 0 }, children: [
|
|
90
|
+
img(g.image),
|
|
91
|
+
g.caption ? /* @__PURE__ */ jsx("figcaption", { className: t.muted, style: { marginTop: "0.25rem" }, children: g.caption }) : null
|
|
92
|
+
] }, i)) })
|
|
93
|
+
] });
|
|
94
|
+
}
|
|
95
|
+
case "video": {
|
|
96
|
+
const src = typeof d.url === "string" ? videoEmbedUrl(d.url) : null;
|
|
97
|
+
if (!src) return null;
|
|
98
|
+
return /* @__PURE__ */ jsxs("figure", { style: { ...stack("0.5rem"), margin: 0 }, children: [
|
|
99
|
+
heading(d.heading),
|
|
100
|
+
/* @__PURE__ */ jsx("div", { className: t.media, style: { aspectRatio: "16 / 9", overflow: "hidden" }, children: /* @__PURE__ */ jsx("iframe", { src, title: String(d.title ?? "Video"), style: { width: "100%", height: "100%", border: 0 }, allow: "autoplay; fullscreen; picture-in-picture", allowFullScreen: true, loading: "lazy" }) }),
|
|
101
|
+
str(d.caption) ? /* @__PURE__ */ jsx("figcaption", { className: t.muted, children: str(d.caption) }) : null
|
|
102
|
+
] });
|
|
103
|
+
}
|
|
104
|
+
case "quote":
|
|
105
|
+
return /* @__PURE__ */ jsxs("blockquote", { className: t.quote, style: { margin: 0 }, children: [
|
|
106
|
+
/* @__PURE__ */ jsx("p", { children: String(d.quote ?? "") }),
|
|
107
|
+
str(d.name) ? /* @__PURE__ */ jsx("footer", { className: t.muted, children: [d.name, d.role].filter(Boolean).join(", ") }) : null
|
|
108
|
+
] });
|
|
109
|
+
case "stats": {
|
|
110
|
+
const items = d.items ?? [];
|
|
111
|
+
return /* @__PURE__ */ jsxs("div", { style: stack("0.75rem"), children: [
|
|
112
|
+
heading(d.heading),
|
|
113
|
+
/* @__PURE__ */ jsx("dl", { style: { ...grid("10rem"), margin: 0 }, children: items.map((s, i) => /* @__PURE__ */ jsxs("div", { className: t.card, children: [
|
|
114
|
+
/* @__PURE__ */ jsx("dt", { className: t.muted, children: s.label }),
|
|
115
|
+
/* @__PURE__ */ jsx("dd", { className: t.itemTitle, style: { margin: 0 }, children: s.value })
|
|
116
|
+
] }, i)) })
|
|
117
|
+
] });
|
|
118
|
+
}
|
|
119
|
+
case "cta":
|
|
120
|
+
return /* @__PURE__ */ jsxs("div", { className: t.callout, style: stack("0.5rem"), children: [
|
|
121
|
+
/* @__PURE__ */ jsx("p", { className: t.itemTitle, children: String(d.heading ?? "") }),
|
|
122
|
+
str(d.body) ? /* @__PURE__ */ jsx("p", { className: t.muted, children: str(d.body) }) : null,
|
|
123
|
+
d.primary || d.secondary ? /* @__PURE__ */ jsxs("div", { style: { display: "flex", flexWrap: "wrap", alignItems: "center", gap: "1rem", marginTop: "0.25rem" }, children: [
|
|
124
|
+
d.primary ? link(d.primary, t.button) : null,
|
|
125
|
+
d.secondary ? link(d.secondary) : null
|
|
126
|
+
] }) : null
|
|
127
|
+
] });
|
|
128
|
+
default:
|
|
129
|
+
return null;
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
return /* @__PURE__ */ jsx("div", { className: t.root, style: t.root ? void 0 : stack("2rem"), children: blocks.map((b, i) => {
|
|
133
|
+
const out = render(b);
|
|
134
|
+
return out ? /* @__PURE__ */ jsx("section", { children: out }, b.id ?? i) : null;
|
|
135
|
+
}) });
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
// src/react.tsx
|
|
139
|
+
import { Fragment as Fragment2, jsx as jsx2 } from "react/jsx-runtime";
|
|
5
140
|
function HearthBlocks({
|
|
6
141
|
blocks,
|
|
7
142
|
components,
|
|
8
143
|
fallback
|
|
9
144
|
}) {
|
|
10
|
-
return /* @__PURE__ */
|
|
145
|
+
return /* @__PURE__ */ jsx2(Fragment2, { children: blocks.map((block) => {
|
|
11
146
|
const Component = components[block.type];
|
|
12
|
-
if (Component) return /* @__PURE__ */
|
|
147
|
+
if (Component) return /* @__PURE__ */ jsx2(Component, { block }, block.id);
|
|
13
148
|
if (process.env.NODE_ENV !== "production" && !fallback) {
|
|
14
149
|
console.warn(`[hearth] No component for block type "${block.type}"; it was skipped.`);
|
|
15
150
|
}
|
|
16
|
-
return /* @__PURE__ */
|
|
151
|
+
return /* @__PURE__ */ jsx2(Fragment, { children: fallback?.({ block }) }, block.id);
|
|
17
152
|
}) });
|
|
18
153
|
}
|
|
19
154
|
function JsonLd({ data }) {
|
|
20
|
-
return /* @__PURE__ */
|
|
155
|
+
return /* @__PURE__ */ jsx2(Fragment2, { children: data.map((item, i) => /* @__PURE__ */ jsx2("script", { type: "application/ld+json", dangerouslySetInnerHTML: { __html: JSON.stringify(item).replace(/</g, "\\u003c") } }, i)) });
|
|
21
156
|
}
|
|
22
157
|
function HearthEmbed({ block, className }) {
|
|
23
|
-
return /* @__PURE__ */
|
|
158
|
+
return /* @__PURE__ */ jsx2(
|
|
24
159
|
"iframe",
|
|
25
160
|
{
|
|
26
161
|
srcDoc: embedDocument(block.data.html),
|
|
@@ -36,6 +171,8 @@ function HearthEmbed({ block, className }) {
|
|
|
36
171
|
);
|
|
37
172
|
}
|
|
38
173
|
export {
|
|
174
|
+
GUIDE_BLOCK_TYPES,
|
|
175
|
+
GuideBlocks,
|
|
39
176
|
HearthBlocks,
|
|
40
177
|
HearthEmbed,
|
|
41
178
|
JsonLd
|
package/llms.md
CHANGED
|
@@ -11,6 +11,21 @@ client components: the API key is secret.
|
|
|
11
11
|
`export const hearth = createHearthClient({ site: process.env.HEARTH_SITE!, apiKey: process.env.HEARTH_API_KEY! })`
|
|
12
12
|
`export const hearthNext = createHearthNext(hearth)` (from `@the-smithy/hearth/next`)
|
|
13
13
|
|
|
14
|
+
## Working with the developer (read first)
|
|
15
|
+
- Start by confirming the setup, and say what you found. In a site made from the starter: `pnpm hearth:check`
|
|
16
|
+
(connection, published pages, block types in use and any without a component). Elsewhere: call
|
|
17
|
+
`hearth.site.get()` and `hearth.pages.list()` from a server-side script.
|
|
18
|
+
- Full guide (every block's fields, events, forms, previews, SEO): `pnpm hearth:docs` in the starter, or
|
|
19
|
+
`GET https://hearth.thesmithy.io/admin/docs/api.md` with `Authorization: Bearer $HEARTH_API_KEY`. Save it to
|
|
20
|
+
a gitignored file and read the parts you need.
|
|
21
|
+
- Never print, log or commit the API key, and don't ask the developer to paste it into chat. Read it only from
|
|
22
|
+
env. If it's missing or rejected (`HearthError` code `unauthorized`), ask them to create one in Hearth → the
|
|
23
|
+
site → API & webhooks → API keys and put it in `.env.local`.
|
|
24
|
+
- Only published content comes back. A new site whose pages are drafts returns no pages, so every route 404s:
|
|
25
|
+
that's expected. Tell the developer to publish in Hearth; never add placeholder content to the code.
|
|
26
|
+
- Content (text, menus, contact details, SEO) is edited in Hearth. The code owns design: tokens, layout and one
|
|
27
|
+
component per block type.
|
|
28
|
+
|
|
14
29
|
## Client (`@the-smithy/hearth`)
|
|
15
30
|
- `site.get(): Site`
|
|
16
31
|
- `pages.list(): PageSummary[]`
|
|
@@ -71,6 +86,9 @@ client components: the API key is secret.
|
|
|
71
86
|
- `<HearthEmbed block={block} />`: renders an Embed block's `html` in a sandboxed iframe. Never inject `html` into the page.
|
|
72
87
|
- Custom blocks: `{ type: "custom", data: { block: "<key>", values } }`, the site's own block types; map keys to components, skip unknown ones. `customBlocks.list()` / `hearthNext.getCustomBlocks()` describe their fields.
|
|
73
88
|
- `<JsonLd data={page.meta.jsonLd} />`
|
|
89
|
+
- `<GuideBlocks blocks={page.blocks} theme={{ card, heading, link, ... }} renderMarkdown={(md) => ...} linkComponent={Link} />`:
|
|
90
|
+
how-to guides (a "guides" collection) read inside The Smithy's products. Same blocks and structure everywhere;
|
|
91
|
+
each product passes its own class names (`GuideTheme`) and Markdown renderer. No CSS ships with it.
|
|
74
92
|
|
|
75
93
|
## Other entry points
|
|
76
94
|
- `@the-smithy/hearth/webhooks`: `verifyWebhook(raw, header, secret)`, `parseWebhook(request, secret)`,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@the-smithy/hearth",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.14.0",
|
|
4
4
|
"description": "Typed client for the Hearth Delivery API: content, SEO, forms and publish webhooks.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "SEE LICENSE IN LICENSE",
|
|
@@ -45,7 +45,7 @@
|
|
|
45
45
|
"llms.md"
|
|
46
46
|
],
|
|
47
47
|
"dependencies": {
|
|
48
|
-
"@the-smithy/hearth-schema": "^0.
|
|
48
|
+
"@the-smithy/hearth-schema": "^0.14.0"
|
|
49
49
|
},
|
|
50
50
|
"peerDependencies": {
|
|
51
51
|
"next": ">=15",
|
|
@@ -62,6 +62,7 @@
|
|
|
62
62
|
"devDependencies": {
|
|
63
63
|
"@types/node": "^22.20.5",
|
|
64
64
|
"@types/react": "^19.3.0",
|
|
65
|
+
"@types/react-dom": "^19.3.0",
|
|
65
66
|
"next": "16.3.8",
|
|
66
67
|
"react": "19.2.8",
|
|
67
68
|
"react-dom": "19.2.8",
|