hono-decks 0.2.2 → 0.3.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.ja.md CHANGED
@@ -12,6 +12,8 @@ bunx hono-decks init
12
12
  bunx hono-decks compile
13
13
  ```
14
14
 
15
+ AI agent を使う場合は `npx @tanstack/intent@latest install` を実行してください。公開パッケージには、agent が `node_modules` から検出できるバージョン付きの hono-decks Skill が含まれます。
16
+
15
17
  `init` は次の2ファイルを作ります。既存ファイルは上書きしません。
16
18
 
17
19
  - `hono-decks.config.ts`: CLI と runtime が共有する唯一の設定
package/README.md CHANGED
@@ -14,6 +14,8 @@ bunx hono-decks init
14
14
  bunx hono-decks compile
15
15
  ~~~
16
16
 
17
+ If you use an AI agent, run `npx @tanstack/intent@latest install`. The published package includes a versioned hono-decks skill that the agent can discover from `node_modules`.
18
+
17
19
  <code>init</code> creates two files without overwriting existing files:
18
20
 
19
21
  - <code>hono-decks.config.ts</code>: the shared configuration for the CLI and runtime
package/package.json CHANGED
@@ -1,12 +1,13 @@
1
1
  {
2
2
  "name": "hono-decks",
3
- "version": "0.2.2",
3
+ "version": "0.3.0",
4
4
  "description": "Slide decks for Hono and Cloudflare Workers",
5
5
  "keywords": [
6
6
  "cloudflare-workers",
7
7
  "hono",
8
8
  "mdx",
9
- "slides"
9
+ "slides",
10
+ "tanstack-intent"
10
11
  ],
11
12
  "homepage": "https://github.com/ts-76/hono-decks#readme",
12
13
  "bugs": {
@@ -25,7 +26,8 @@
25
26
  "files": [
26
27
  "dist",
27
28
  "README.md",
28
- "README.ja.md"
29
+ "README.ja.md",
30
+ "skills"
29
31
  ],
30
32
  "type": "module",
31
33
  "sideEffects": false,
@@ -62,9 +64,11 @@
62
64
  "build": "tsdown",
63
65
  "prepack": "bun run build",
64
66
  "decks:compile": "bun run build && node dist/bin.js compile",
67
+ "skills:validate": "intent validate skills",
68
+ "skills:stale": "intent stale .",
65
69
  "typecheck": "bun run build && tsc --noEmit && tsc --noEmit -p tsconfig.worker.json",
66
70
  "test": "bun run build && vp test run",
67
- "check": "bun run typecheck && bun run test"
71
+ "check": "bun run typecheck && bun run test && bun run skills:validate"
68
72
  },
69
73
  "dependencies": {
70
74
  "@mdx-js/mdx": "^3.1.1",
@@ -77,6 +81,7 @@
77
81
  "unified": "^11.0.5"
78
82
  },
79
83
  "devDependencies": {
84
+ "@tanstack/intent": "^0.3.6",
80
85
  "hono": "^4.12.30",
81
86
  "vite": "catalog:",
82
87
  "vite-plus": "catalog:",
@@ -0,0 +1,187 @@
1
+ ---
2
+ name: hono-decks
3
+ description: >
4
+ Create, style, integrate, and troubleshoot hono-decks presentations in Hono and Cloudflare Workers. Load for installation, deck.mdx and frontmatter authoring, Fire reveals, theme.css, generated createDecks modules, Hono routing, Vite or Wrangler builds, R2 assets, PDF or PNG export, OGP generation, embeds, or custom DeckSource pipelines.
5
+ metadata:
6
+ type: core
7
+ library: hono-decks
8
+ library_version: '0.1.0'
9
+ sources:
10
+ - 'ts-76/hono-decks:packages/decks/README.md'
11
+ - 'ts-76/hono-decks:packages/decks/src/deck/frontmatter.ts'
12
+ - 'ts-76/hono-decks:packages/decks/src/generator/mdx/syntax.ts'
13
+ - 'ts-76/hono-decks:packages/decks/src/renderer/jsx-renderer.ts'
14
+ - 'ts-76/hono-decks:packages/decks/src/renderer/presentation-style.ts'
15
+ - 'ts-76/hono-decks:packages/decks/src/server/define-decks.ts'
16
+ - 'ts-76/hono-decks:packages/decks/src/server/external-embed.ts'
17
+ - 'ts-76/hono-decks:packages/decks/src/server/browser-export.ts'
18
+ - 'ts-76/hono-decks:examples/basic/hono-decks.config.ts'
19
+ - 'ts-76/hono-decks:examples/basic/decks/sample/deck.mdx'
20
+ - 'ts-76/hono-decks:examples/basic/decks/motion/deck.mdx'
21
+ - 'ts-76/hono-decks:examples/ogp/README.md'
22
+ ---
23
+
24
+ # hono-decks
25
+
26
+ Use this skill as a router. Inspect the application and the request, then read only the relevant reference files before editing. Combine routes when a request spans topics.
27
+
28
+ ## Route the request
29
+
30
+ | User intent | Read |
31
+ | --- | --- |
32
+ | Install, initialize, first deck, compile failure | [Getting started](references/getting-started.md) |
33
+ | Write or fix `deck.mdx`, frontmatter, Markdown, MDX, Fire, embeds, components, notes | [Authoring MDX](references/authoring-mdx.md) |
34
+ | Create or improve `theme.css`, layout, typography, visual quality, overflow | [Styling](references/styling.md) |
35
+ | Add to an existing Hono, HonoX, Vite, Wrangler, or Worker app; configure routes, presenter, or external embed | [Integrating](references/integrating.md) |
36
+ | Serve deck images or other local assets from R2 | [R2 assets](references/r2-assets.md) plus [Integrating](references/integrating.md) |
37
+ | Generate PDF or PNG with Cloudflare Browser Rendering | [Browser export](references/browser-export.md) plus [Integrating](references/integrating.md) |
38
+ | Generate OGP/social images | [OGP generation](references/ogp-generation.md) |
39
+ | Use a manifest, database, remote store, custom `DeckSource`, or low-level router | [Custom sources](references/custom-sources.md) |
40
+
41
+ If the request is simply “make a deck,” read Authoring MDX and Styling. If it also asks to put the deck in an application, read Integrating. Do not load the advanced references unless the request or inspected code needs them.
42
+
43
+ ## Non-negotiable architecture
44
+
45
+ 1. Compile MDX with Node.js or Bun at build time.
46
+ 2. Import generated TypeScript modules in the Worker runtime.
47
+ 3. Keep `hono-decks.config.ts` as the single mount-path and build configuration.
48
+ 4. Mount with `app.route(decks.mountPath, decks.router())` and obtain URLs from `decks.paths(slug)` or `meta.paths`.
49
+ 5. Edit source decks, deck-local components, assets, and themes. Never hand-edit the generated output directory.
50
+
51
+ ## Authoring and design defaults
52
+
53
+ - hono-decks is not Slidev. Never generate `v-click`, Slidev frontmatter, or UnoCSS utility classes unless the application independently installed and configured those features.
54
+ - Use supported `:::fire` blocks or block-level custom components with `fire`; reveals follow source order.
55
+ - Design for the fixed 1920 × 1080, 16:9 canvas. Responsive slide layouts are not required.
56
+ - Keep body text presentation-sized (the base root size is 32px), preserve strong contrast, and keep every slide inside the canvas.
57
+ - Prefer one idea per slide. Split dense content instead of shrinking text or relying on hidden overflow.
58
+ - Preserve print behavior and respect `prefers-reduced-motion` for custom animation.
59
+
60
+ ## Common mistakes
61
+
62
+ ### Editing generated modules
63
+
64
+ Wrong:
65
+
66
+ ```ts
67
+ // src/generated/decks.ts
68
+ // Hand-edit the generated manifest or routes.
69
+ ```
70
+
71
+ Correct: edit `decks/product/deck.mdx`, `theme.css`, assets, or components, then run `bunx hono-decks compile`.
72
+
73
+ ### Compiling inside the Worker
74
+
75
+ Wrong:
76
+
77
+ ```ts
78
+ import { compileDecks } from "hono-decks/node";
79
+
80
+ app.get("/decks", async (c) => c.json(await compileDecks({ cwd: "." })));
81
+ ```
82
+
83
+ Correct:
84
+
85
+ ```ts
86
+ import { decks } from "./decks";
87
+
88
+ app.route(decks.mountPath, decks.router());
89
+ ```
90
+
91
+ ### Duplicating route strings
92
+
93
+ Wrong:
94
+
95
+ ```ts
96
+ app.route("/slides", decks.router());
97
+ const presenter = `/decks/${slug}/presenter`;
98
+ ```
99
+
100
+ Correct:
101
+
102
+ ```ts
103
+ app.route(decks.mountPath, decks.router());
104
+ const presenter = decks.paths(slug).presenter;
105
+ ```
106
+
107
+ ### Passing executable values to client islands
108
+
109
+ Wrong:
110
+
111
+ ```mdx
112
+ <Counter onChange={() => save()} startedAt={new Date()} />
113
+ ```
114
+
115
+ Correct:
116
+
117
+ ```mdx
118
+ <Counter label="Votes" initial={3} />
119
+ ```
120
+
121
+ Keep event handlers, dates, class instances, and component state inside `components/client/index.tsx`; island props must be JSON-serializable.
122
+
123
+ ### Using unsupported Fire forms
124
+
125
+ Wrong:
126
+
127
+ ```mdx
128
+ <p fire="fade-up">Reveal me</p>
129
+ <Fire order={2}>Later</Fire>
130
+ ```
131
+
132
+ Correct:
133
+
134
+ ```mdx
135
+ :::fire{effect="fade-up"}
136
+ Reveal me
137
+ :::
138
+
139
+ <Chart fire="scale" at="+1" />
140
+ ```
141
+
142
+ ### Treating iframe embedding as CORS
143
+
144
+ Wrong: add `Access-Control-Allow-Origin` and leave `router.embed` without a parent policy.
145
+
146
+ Correct:
147
+
148
+ ```ts
149
+ router: {
150
+ embed: { frameAncestors: ["https://blog.example.com"] },
151
+ }
152
+ ```
153
+
154
+ Iframe navigation uses CSP `frame-ancestors`, not CORS.
155
+
156
+ ### Publishing unprotected browser exports
157
+
158
+ Wrong:
159
+
160
+ ```ts
161
+ export: {
162
+ browser: ({ c }) => c.env.BROWSER,
163
+ pdf: true,
164
+ }
165
+ ```
166
+
167
+ Correct: add an `authorize` resolver backed by a Wrangler secret. Export authorization defaults to allowed when the resolver is absent.
168
+
169
+ ### Generating Slidev or UnoCSS syntax
170
+
171
+ Wrong:
172
+
173
+ ```mdx
174
+ <div v-click class="grid grid-cols-2 gap-4">Content</div>
175
+ ```
176
+
177
+ Correct: use `:::fire` and define project-specific classes explicitly in `theme.css`.
178
+
179
+ ## Verification
180
+
181
+ After changes:
182
+
183
+ 1. Run `hono-decks compile` through the application's package manager.
184
+ 2. Run the application's typecheck and tests.
185
+ 3. Open the viewer and check every slide at 16:9 for readable text and overflow.
186
+ 4. Check presentation, presenter, print, embed, export, or OGP routes that the change affects.
187
+ 5. Verify production policies for drafts, presenter access, CSP `frame-ancestors`, and export authorization.
@@ -0,0 +1,200 @@
1
+ # Authoring MDX
2
+
3
+ Use this route when writing or correcting a `deck.mdx` file. Start with the basic syntax, then load the advanced sections only when the requested deck needs them.
4
+
5
+ ## Basic deck structure
6
+
7
+ The first frontmatter block describes the deck. Each following `---` boundary starts a slide; a slide may immediately include its own frontmatter.
8
+
9
+ ```mdx
10
+ ---
11
+ title: Product update
12
+ description: What changed and why it matters
13
+ author: Product team
14
+ date: 2026-07-17
15
+ tags: [product, engineering]
16
+ theme: default
17
+ transition: fade
18
+ transitionDuration: 300ms
19
+ transitionEasing: ease-out
20
+ draft: false
21
+ presenter: true
22
+ ---
23
+
24
+ ---
25
+ title: Opening
26
+ layout: cover
27
+ class: opening-slide
28
+ background: "#0b1020"
29
+ ---
30
+
31
+ # Product update
32
+
33
+ One clear sentence that frames the talk.
34
+
35
+ {/* Introduce the customer problem before the feature. */}
36
+
37
+ ---
38
+ title: Outcomes
39
+ layout: default
40
+ transition: slide-left
41
+ ---
42
+
43
+ ## Outcomes
44
+
45
+ - Faster setup
46
+ - Fewer production surprises
47
+ - Clearer ownership
48
+ ```
49
+
50
+ Deck frontmatter supports `title`, `description`, `author`, `date`, `tags`, `theme`, `transition`, `transitionDuration`, `transitionEasing`, `assets`, `draft`, and `presenter`.
51
+
52
+ Slide frontmatter supports `title`, `layout`, `class`, `notes`, `background`, `transition`, `transitionDuration`, and `transitionEasing`. Unknown keys are preserved in `meta` and produce a compile warning.
53
+
54
+ Supported transitions are `none`, `fade`, `fade-out`, `slide-left`, `slide-right`, `slide-up`, `slide-down`, and `view-transition`.
55
+
56
+ ## Markdown and MDX basics
57
+
58
+ Use headings, paragraphs, emphasis, links, images, blockquotes, ordered and unordered lists, task lists, tables, inline code, and fenced code. GFM is enabled.
59
+
60
+ ~~~mdx
61
+ ## Release checklist
62
+
63
+ - [x] Compile the deck
64
+ - [ ] Verify the print route
65
+
66
+ | Surface | Path |
67
+ | --- | --- |
68
+ | Viewer | `/decks/product` |
69
+ | Presenter | `/decks/product/presenter` |
70
+
71
+ ```ts
72
+ app.route(decks.mountPath, decks.router())
73
+ ```
74
+
75
+ ![Architecture](./assets/architecture.svg)
76
+ ~~~
77
+
78
+ Local image paths are rewritten during compilation. Put deck-local files under `assets/` and use relative paths.
79
+
80
+ MDX exports, expressions, and JSX are supported:
81
+
82
+ ```mdx
83
+ export const release = "2026.07"
84
+ export const metrics = ["42% faster", "3 fewer steps"]
85
+
86
+ ## Release {release}
87
+
88
+ {metrics.map((metric) => <Badge label={metric} />)}
89
+ ```
90
+
91
+ ## Speaker notes
92
+
93
+ Use the slide `notes` frontmatter field or MDX comments. Comments are removed from visible slide output and collected as presenter notes.
94
+
95
+ ```mdx
96
+ ---
97
+ notes: |
98
+ Pause after the headline.
99
+ Explain the build/runtime boundary.
100
+ ---
101
+
102
+ ## Compile once, serve everywhere
103
+
104
+ {/* Open the presenter route before the live demo. */}
105
+ ```
106
+
107
+ ## Built-in components
108
+
109
+ - `<Hero>`: `title`, `subtitle` or `description`, `eyebrow`, `image` or `src`, and `featured`
110
+ - `<CodeBlock>`: `lang`, `filename` or `title`, and `highlight`
111
+ - `<EmbedFrame>`: iframe with sandbox, fallback, and print behavior
112
+ - `<SocialEmbed>`, `<TweetEmbed>`, and `<LinkCard>`: social and link surfaces
113
+ - `<Fire>`: explicit reveal wrapper; prefer the Markdown directive for ordinary prose
114
+
115
+ ```mdx
116
+ <Hero
117
+ eyebrow="Architecture"
118
+ title="Build-time MDX"
119
+ subtitle="Generated modules run on the Worker"
120
+ image="./assets/pipeline.svg"
121
+ />
122
+
123
+ <CodeBlock lang="ts" filename="src/index.ts" highlight="4">
124
+ import { Hono } from "hono";
125
+ import { decks } from "./decks";
126
+
127
+ const app = new Hono();
128
+ app.route(decks.mountPath, decks.router());
129
+ </CodeBlock>
130
+ ```
131
+
132
+ ## Fire reveals
133
+
134
+ Markdown content uses a container directive:
135
+
136
+ ```mdx
137
+ :::fire{effect="fade-up"}
138
+ This appears on the next reveal step.
139
+ :::
140
+ ```
141
+
142
+ Lists can reveal items in source order:
143
+
144
+ ```mdx
145
+ :::fire{each="item" depth="2" every="1"}
146
+ - First point
147
+ - Nested detail
148
+ - Second point
149
+ :::
150
+ ```
151
+
152
+ Block-level custom components may use `fire` and `at`:
153
+
154
+ ```mdx
155
+ <MetricCard fire="scale" at="+1" label="Activation" value="42%" />
156
+ ```
157
+
158
+ `at` accepts a non-negative integer or a relative value such as `+1`. Do not use `$fire`, `order`, `v-click`, or `fire` on HTML elements or inline custom components.
159
+
160
+ ## Embeds and link cards
161
+
162
+ ```mdx
163
+ @[youtube](https://www.youtube.com/watch?v=dQw4w9WgXcQ)
164
+
165
+ @[x](https://x.com/honojs/status/1659577874821836801)
166
+
167
+ @[card](https://hono.dev/)
168
+
169
+ @[embed](https://example.com/embed/status)
170
+ ```
171
+
172
+ `@[card]` metadata is resolved at compile time when available. `build.ogpCacheFile` stores that external metadata cache; it is not the deck's social share image.
173
+
174
+ ## Deck-local components
175
+
176
+ Use this structure:
177
+
178
+ ```text
179
+ decks/product/
180
+ deck.mdx
181
+ theme.css
182
+ assets/
183
+ diagram.svg
184
+ components/
185
+ index.tsx
186
+ client/
187
+ index.tsx
188
+ ```
189
+
190
+ `components/index.tsx` contains server-rendered Hono JSX components. Mark a component as client-enabled in the server registry and export its browser implementation with the same name from `components/client/index.tsx`.
191
+
192
+ Client props may contain only strings, finite numbers, booleans, `null`, arrays, and plain objects composed from those values. Keep callbacks and interactive state in the client component.
193
+
194
+ ## Authoring quality check
195
+
196
+ - Use a short title and one primary idea per slide.
197
+ - Keep visible body copy sparse enough for a 32px base size.
198
+ - Split long lists, large tables, and multi-screen code into multiple slides.
199
+ - Add useful alt text to images and titles to embeds.
200
+ - Compile after syntax changes and resolve all warnings instead of hiding them.
@@ -0,0 +1,84 @@
1
+ # PDF and PNG browser export
2
+
3
+ Use this route only when the request explicitly asks for server-side PDF or PNG generation with Cloudflare Browser Rendering.
4
+
5
+ Browser export renders the deck print route. The export endpoint is a billable production capability, so always configure authorization.
6
+
7
+ ## Configure bindings and secrets
8
+
9
+ Declare the Browser Rendering binding in the application's Wrangler config and store the token as a secret:
10
+
11
+ ```bash
12
+ wrangler secret put DECK_EXPORT_TOKEN
13
+ ```
14
+
15
+ Use the binding shape generated by the application's current Wrangler version. Regenerate types with `wrangler types`.
16
+
17
+ ## Configure export
18
+
19
+ ```ts
20
+ // hono-decks.config.ts
21
+ import {
22
+ defineDecksConfig,
23
+ type DeckBrowserRunBinding,
24
+ } from "hono-decks";
25
+
26
+ interface Env {
27
+ Bindings: {
28
+ BROWSER?: DeckBrowserRunBinding;
29
+ DECK_EXPORT_TOKEN?: string;
30
+ };
31
+ }
32
+
33
+ export default defineDecksConfig<Env>({
34
+ mountPath: "/decks",
35
+ build: {
36
+ root: "decks",
37
+ outDir: "src/generated",
38
+ },
39
+ router: {
40
+ export: {
41
+ authorize: ({ c }) => {
42
+ const token = c.env.DECK_EXPORT_TOKEN;
43
+ const authorization = c.req.header("authorization");
44
+ const provided = authorization?.match(/^Bearer\s+(.+)$/i)?.[1]?.trim();
45
+ return typeof token === "string" && token.length > 0 && provided === token;
46
+ },
47
+ browser: ({ c }) => c.env.BROWSER,
48
+ pdf: {
49
+ filename: (deck) => `${deck.slug}-slides`,
50
+ },
51
+ png: {
52
+ filename: (deck) => `${deck.slug}-slides`,
53
+ },
54
+ },
55
+ },
56
+ });
57
+ ```
58
+
59
+ The endpoints are available from `decks.paths(slug).exportPdf` and `decks.paths(slug).exportPng`. Export controls are shown only when that request is authorized.
60
+
61
+ ## Request an export
62
+
63
+ ```bash
64
+ curl \
65
+ -H "Authorization: Bearer $DECK_EXPORT_TOKEN" \
66
+ -o product.pdf \
67
+ https://slides.example.com/decks/product/export.pdf
68
+ ```
69
+
70
+ Use an environment variable supplied by the caller; never commit a token or put it in Wrangler `vars`.
71
+
72
+ ## CSP and network checks
73
+
74
+ Browser Rendering must be able to load the absolute print URL and every font, image, stylesheet, and embedded fallback it needs. Review CSP and authentication middleware on the print route. Interactive iframes are replaced by print fallbacks, so provide useful fallback links or posters.
75
+
76
+ ## Verification
77
+
78
+ - no token, empty token, and incorrect token return 403
79
+ - missing Browser binding returns 503 without exposing internals
80
+ - authorized PDF has `application/pdf` and a safe attachment filename
81
+ - authorized PNG has `image/png` and a safe attachment filename
82
+ - draft decks remain unavailable outside development
83
+ - every slide appears in print order with backgrounds and local assets
84
+ - exported pages contain no clipped content; fix the deck rather than increasing the browser viewport to hide overflow
@@ -0,0 +1,95 @@
1
+ # Custom sources and low-level APIs
2
+
3
+ Use this route only when the request needs an existing manifest, database, remote object store, custom `DeckSource`, or a router pipeline that the generated `createDecks(config)` kit cannot express.
4
+
5
+ For normal file-based decks, stay with `hono-decks init`, generated modules, and `createDecks(config)`.
6
+
7
+ ## Custom DeckSource
8
+
9
+ A source lists deck summaries, returns compiled decks, and optionally serves assets:
10
+
11
+ ```ts
12
+ import type {
13
+ CompiledDeck,
14
+ DeckEntry,
15
+ DeckSource,
16
+ } from "hono-decks";
17
+
18
+ export function databaseDeckSource(input: {
19
+ list(): Promise<DeckEntry[]>;
20
+ find(slug: string): Promise<CompiledDeck | null>;
21
+ }): DeckSource {
22
+ return {
23
+ async listDecks() {
24
+ return input.list();
25
+ },
26
+ async getCompiledDeck(_c, slug) {
27
+ return input.find(slug);
28
+ },
29
+ };
30
+ }
31
+ ```
32
+
33
+ Compiled data must satisfy the public `CompiledDeck` model. Do not send raw MDX to the Worker and invoke the Node compiler per request.
34
+
35
+ ## Decorate generated data
36
+
37
+ If only asset lookup, visibility, metadata, or caching changes, prefer `config.source(source)` over replacing the entire generated flow:
38
+
39
+ ```ts
40
+ import { defineDecksConfig, type DeckSource } from "hono-decks";
41
+
42
+ export default defineDecksConfig({
43
+ mountPath: "/decks",
44
+ source(source: DeckSource): DeckSource {
45
+ return {
46
+ async listDecks(c) {
47
+ return (await source.listDecks(c)).filter((deck) => !deck.draft);
48
+ },
49
+ getCompiledDeck: (c, slug) => source.getCompiledDeck(c, slug),
50
+ getAsset: (c, slug, assetPath) => source.getAsset?.(c, slug, assetPath) ?? Promise.resolve(null),
51
+ };
52
+ },
53
+ });
54
+ ```
55
+
56
+ ## Assemble a low-level router
57
+
58
+ ```ts
59
+ import { Hono } from "hono";
60
+ import {
61
+ decksRouter,
62
+ manifestDeckSource,
63
+ type DeckManifest,
64
+ } from "hono-decks/advanced";
65
+
66
+ export function createInternalSlides(manifest: DeckManifest) {
67
+ const app = new Hono();
68
+ const source = manifestDeckSource(manifest);
69
+
70
+ app.route(
71
+ "/internal-slides",
72
+ decksRouter({
73
+ source,
74
+ dev: false,
75
+ presenter: false,
76
+ embed: false,
77
+ export: false,
78
+ }),
79
+ );
80
+
81
+ return app;
82
+ }
83
+ ```
84
+
85
+ At this level the application owns the mount path, option merging, source policy, draft behavior, and security. Use `createDeckPaths(mountPath, slug)` whenever low-level code needs a route map.
86
+
87
+ ## Boundary checks
88
+
89
+ - compile or validate raw MDX before deployment, outside the Worker
90
+ - return 404 for missing or unauthorized decks without leaking their existence
91
+ - keep asset paths normalized and prevent traversal into unrelated objects
92
+ - keep `listDecks` and `getCompiledDeck` visibility policies consistent
93
+ - preserve JSON-serializable client-island props in custom compiled data
94
+ - explicitly configure presenter, embed CSP, and export authorization when enabling those surfaces
95
+ - add contract tests for every custom source method and route surface
@@ -0,0 +1,123 @@
1
+ # Getting started
2
+
3
+ Use this route for installation, initialization, a first deck, or a failed initial compile.
4
+
5
+ ## Inspect before changing
6
+
7
+ Identify the package manager, application entry point, Hono environment type, build tool, Wrangler config format, and existing Vite plugins. Preserve the application's established structure and scripts.
8
+
9
+ ## Install and initialize
10
+
11
+ ```bash
12
+ bun add hono hono-decks
13
+ bunx hono-decks init
14
+ ```
15
+
16
+ `init` creates these files without overwriting existing files:
17
+
18
+ - `hono-decks.config.ts`: shared build and runtime configuration
19
+ - `src/decks.ts`: editable facade around generated modules
20
+
21
+ Use the equivalent `npm`, `pnpm`, or Yarn commands when the repository does not use Bun.
22
+
23
+ ## Configure once
24
+
25
+ ```ts
26
+ // hono-decks.config.ts
27
+ import { defineDecksConfig } from "hono-decks";
28
+
29
+ export default defineDecksConfig({
30
+ mountPath: "/decks",
31
+ build: {
32
+ root: "decks",
33
+ outDir: "src/generated",
34
+ },
35
+ });
36
+ ```
37
+
38
+ ```ts
39
+ // src/decks.ts
40
+ import config from "../hono-decks.config";
41
+ import { createDecks } from "./generated/decks";
42
+
43
+ export const decks = createDecks(config);
44
+ ```
45
+
46
+ Create `decks/welcome/deck.mdx`:
47
+
48
+ ```mdx
49
+ ---
50
+ title: Welcome
51
+ description: My first hono-decks presentation
52
+ ---
53
+
54
+ ---
55
+ layout: cover
56
+ ---
57
+
58
+ # Welcome
59
+
60
+ MDX slides served by Hono.
61
+
62
+ ---
63
+ layout: default
64
+ ---
65
+
66
+ ## Build-time compilation
67
+
68
+ - Author in `decks/`
69
+ - Compile to `src/generated/`
70
+ - Serve generated modules from Hono
71
+ ```
72
+
73
+ Compile and mount it:
74
+
75
+ ```bash
76
+ bunx hono-decks compile
77
+ ```
78
+
79
+ ```ts
80
+ // src/index.ts
81
+ import { Hono } from "hono";
82
+ import { decks } from "./decks";
83
+
84
+ const app = new Hono();
85
+
86
+ app.get("/", (c) => c.redirect(decks.paths("welcome").viewer));
87
+ app.route(decks.mountPath, decks.router());
88
+
89
+ export default app;
90
+ ```
91
+
92
+ ## Add compilation to development
93
+
94
+ For a Wrangler Worker, use the existing JSONC config:
95
+
96
+ ```jsonc
97
+ {
98
+ "build": {
99
+ "command": "hono-decks compile",
100
+ "watch_dir": ["decks"]
101
+ }
102
+ }
103
+ ```
104
+
105
+ For Vite or HonoX, add the plugin to the existing plugin array:
106
+
107
+ ```ts
108
+ import { honoDecks } from "hono-decks/vite";
109
+ import { defineConfig } from "vite";
110
+
111
+ export default defineConfig({
112
+ plugins: [honoDecks()],
113
+ });
114
+ ```
115
+
116
+ ## Diagnose setup failures
117
+
118
+ - Missing config: run `hono-decks init` or create a default-exported `hono-decks.config.ts`.
119
+ - Missing generated import: run `hono-decks compile` before typechecking the application.
120
+ - Deck not discovered: use `decks/<slug>/deck.mdx` or a supported single-file deck in the configured root.
121
+ - Broken routes or assets: remove duplicated strings and use `decks.mountPath` and `decks.paths(slug)`.
122
+ - Worker bundling Node modules: import `hono-decks/node` only from build scripts, never from Worker runtime code.
123
+ - Lost edits after compile: move changes out of `src/generated` and into source decks or configuration.
@@ -0,0 +1,146 @@
1
+ # Integrating with an application
2
+
3
+ Use this route when adding hono-decks to an existing Hono, HonoX, Vite, Wrangler, or Cloudflare Workers application, or when configuring application routes, presenter access, or external embedding.
4
+
5
+ ## Preserve the build/runtime boundary
6
+
7
+ The compiler and `hono-decks/node` use Node filesystem tooling. Run them in a build command. Worker runtime code imports the generated facade and root package only.
8
+
9
+ ```ts
10
+ // src/decks.ts
11
+ import config from "../hono-decks.config";
12
+ import { createDecks } from "./generated/decks";
13
+
14
+ export const decks = createDecks(config);
15
+ ```
16
+
17
+ ```ts
18
+ // src/index.ts
19
+ import { Hono } from "hono";
20
+ import { decks } from "./decks";
21
+
22
+ const app = new Hono();
23
+
24
+ app.route(decks.mountPath, decks.router());
25
+
26
+ export default app;
27
+ ```
28
+
29
+ ## Use the configured kit
30
+
31
+ - `decks.mountPath`: normalized path for `app.route`
32
+ - `decks.source`: configured `DeckSource`
33
+ - `decks.router(overrides?)`: built-in viewer, renderer, presenter, embed, export, and asset routes
34
+ - `decks.context(overrides?)`: middleware for application-owned deck routes
35
+ - `decks.paths(slug)`: viewer, render, print, presentation, presenter, embed, PDF, PNG, OGP, and asset paths
36
+
37
+ Never rebuild these paths with string concatenation.
38
+
39
+ ## Existing Wrangler application
40
+
41
+ Merge this into the existing `wrangler.jsonc`:
42
+
43
+ ```jsonc
44
+ {
45
+ "$schema": "node_modules/wrangler/config-schema.json",
46
+ "main": "src/index.ts",
47
+ "compatibility_date": "2026-07-14",
48
+ "build": {
49
+ "command": "hono-decks compile",
50
+ "watch_dir": ["decks"]
51
+ }
52
+ }
53
+ ```
54
+
55
+ Keep other bindings, assets, routes, and compatibility flags intact. Run `wrangler types` after changing bindings.
56
+
57
+ ## Existing Vite or HonoX application
58
+
59
+ Add the plugin to the existing config rather than replacing other plugins:
60
+
61
+ ```ts
62
+ import { honoDecks } from "hono-decks/vite";
63
+ import { defineConfig } from "vite";
64
+
65
+ export default defineConfig({
66
+ plugins: [honoDecks()],
67
+ });
68
+ ```
69
+
70
+ For HonoX, mount the router from a HonoX route module while keeping the generated facade outside the route tree. Follow the application's established catch-all route convention.
71
+
72
+ ## Application-owned routes
73
+
74
+ Use `decks.context()` to share deck lookup, draft policy, paths, and viewer configuration:
75
+
76
+ ```ts
77
+ import { Hono } from "hono";
78
+ import type { DeckContextVariables } from "hono-decks";
79
+ import { decks } from "./decks";
80
+
81
+ const app = new Hono<{ Variables: DeckContextVariables }>();
82
+
83
+ app.get(
84
+ `${decks.mountPath}/:slug/about`,
85
+ decks.context(),
86
+ (c) => c.json({
87
+ title: c.var.deckMeta.title,
88
+ viewer: c.var.deckMeta.paths.viewer,
89
+ slides: c.var.deckToc,
90
+ }),
91
+ );
92
+
93
+ app.route(decks.mountPath, decks.router());
94
+ ```
95
+
96
+ Register more specific application routes before the mounted deck router.
97
+
98
+ ## Presenter policy
99
+
100
+ Presenter view can contain notes and previews. Enable it intentionally:
101
+
102
+ ```ts
103
+ router: {
104
+ presenter: {
105
+ enabled: ({ dev }) => dev,
106
+ viewerControl: true,
107
+ },
108
+ }
109
+ ```
110
+
111
+ For production, replace the development condition with application authentication or a trusted binding policy.
112
+
113
+ ## External iframe embedding
114
+
115
+ Enable the embed route and explicitly list allowed parent origins:
116
+
117
+ ```ts
118
+ router: {
119
+ embed: {
120
+ frameAncestors: ["https://blog.example.com"],
121
+ robots: false,
122
+ },
123
+ }
124
+ ```
125
+
126
+ ```html
127
+ <iframe
128
+ src="https://slides.example.com/decks/product/embed"
129
+ title="Product deck"
130
+ allow="fullscreen"
131
+ ></iframe>
132
+ ```
133
+
134
+ The response writes CSP `frame-ancestors` and removes conflicting `X-Frame-Options`. Invalid origins do not widen access. Do not use CORS headers to solve iframe permission.
135
+
136
+ Review any application-wide CSP middleware after mounting: it must preserve the embed response's `frame-ancestors` directive and allow the scripts, styles, images, frames, and fonts actually used by the deck.
137
+
138
+ ## Integration verification
139
+
140
+ - Start the normal application dev command and confirm MDX changes recompile.
141
+ - Verify direct navigation and application links use `decks.paths`.
142
+ - Confirm draft decks are unavailable outside development.
143
+ - Test application-owned routes before the deck router catch-all.
144
+ - Check presenter notes are not exposed by an unintended production policy.
145
+ - Test the external embed from an allowed and a disallowed parent origin.
146
+ - Run the deployment dry run so Worker-only bundling errors are caught before publish.
@@ -0,0 +1,85 @@
1
+ # OGP and social image generation
2
+
3
+ Use this route when the request explicitly asks to generate Open Graph or social sharing images.
4
+
5
+ hono-decks exposes OGP paths and metadata but intentionally does not bundle an image renderer. Generate 1200 × 630 PNGs at build time and serve them from Workers Static Assets or another application-owned asset pipeline.
6
+
7
+ ## Enable viewer metadata
8
+
9
+ ```ts
10
+ // hono-decks.config.ts
11
+ import { defineDecksConfig } from "hono-decks";
12
+
13
+ export default defineDecksConfig({
14
+ mountPath: "/decks",
15
+ build: {
16
+ root: "decks",
17
+ outDir: "src/generated",
18
+ },
19
+ router: {
20
+ viewer: {
21
+ openGraph: true,
22
+ },
23
+ },
24
+ });
25
+ ```
26
+
27
+ The default image URL is `decks.paths(slug).ogImage`. The viewer turns it into an absolute Open Graph and Twitter Card URL from the request origin.
28
+
29
+ ## Build-time pipeline
30
+
31
+ Use `compileDecks()` from `hono-decks/node`, then generate one image for each non-draft manifest entry:
32
+
33
+ ```ts
34
+ import { mkdir, writeFile } from "node:fs/promises";
35
+ import { dirname, join } from "node:path";
36
+ import { createDeckPaths } from "hono-decks";
37
+ import { compileDecks } from "hono-decks/node";
38
+ import config from "../hono-decks.config";
39
+ import { renderOgpCard } from "./ogp-card";
40
+
41
+ const cwd = process.cwd();
42
+ const manifest = await compileDecks({
43
+ cwd,
44
+ root: config.build?.root ?? "decks",
45
+ out: config.build?.outDir ?? "src/generated",
46
+ mountPath: config.mountPath,
47
+ ogpCacheFile: config.build?.ogpCacheFile,
48
+ });
49
+
50
+ for (const deck of manifest.decks) {
51
+ if (deck.meta.draft) continue;
52
+ const paths = createDeckPaths(config.mountPath, deck.slug);
53
+ const png = await renderOgpCard({
54
+ title: deck.meta.title ?? deck.slug,
55
+ description: deck.meta.description,
56
+ author: deck.meta.author,
57
+ path: paths.viewer,
58
+ });
59
+ const output = join(cwd, "public", paths.ogImage.replace(/^\//, ""));
60
+ await mkdir(dirname(output), { recursive: true });
61
+ await writeFile(output, png);
62
+ }
63
+ ```
64
+
65
+ Implement `renderOgpCard` with application-selected tooling such as Satori plus resvg. Keep those dependencies in the application, not the hono-decks runtime bundle.
66
+
67
+ Satori accepts TTF, OTF, and WOFF fonts, but not WOFF2. Bundle fonts that cover every language used in deck metadata; builds should not depend on fetching remote fonts.
68
+
69
+ ## Separate concepts
70
+
71
+ - `router.viewer.openGraph`: emits social metadata for the viewer
72
+ - `decks.paths(slug).ogImage`: canonical image output/serving path
73
+ - `build.ogpCacheFile`: caches external page metadata for `@[card](...)`; it does not generate the deck share image
74
+
75
+ ## Verification
76
+
77
+ - generate only non-draft decks
78
+ - output exactly 1200 × 630 PNG files
79
+ - avoid rewriting unchanged bytes so local watchers do not loop
80
+ - verify the viewer emits absolute `og:image` and Twitter Card metadata
81
+ - request the generated image path from the deployed asset setup
82
+ - use an `imagePath` resolver when images live on a separate CDN
83
+ - ensure titles, descriptions, and fonts fit languages with wider glyphs
84
+
85
+ The repository's `examples/ogp` directory is the canonical complete recipe.
@@ -0,0 +1,77 @@
1
+ # R2-backed assets
2
+
3
+ Use this route only when the request explicitly asks to serve deck-local images or other assets from Cloudflare R2.
4
+
5
+ Compilation still discovers and rewrites local asset URLs. `withR2Assets` decorates the generated source so requests prefer R2 and fall back to the original embedded asset source.
6
+
7
+ ## Configure a binding
8
+
9
+ ```jsonc
10
+ {
11
+ "r2_buckets": [
12
+ {
13
+ "binding": "DECK_ASSETS",
14
+ "bucket_name": "production-deck-assets"
15
+ }
16
+ ]
17
+ }
18
+ ```
19
+
20
+ Generate binding types with `wrangler types`.
21
+
22
+ ## Decorate the source
23
+
24
+ ```ts
25
+ // hono-decks.config.ts
26
+ import {
27
+ defineDecksConfig,
28
+ withR2Assets,
29
+ type DeckSource,
30
+ type R2BucketLike,
31
+ } from "hono-decks";
32
+
33
+ interface Env {
34
+ Bindings: {
35
+ DECK_ASSETS?: R2BucketLike;
36
+ };
37
+ }
38
+
39
+ export default defineDecksConfig<Env>({
40
+ mountPath: "/decks",
41
+ build: {
42
+ root: "decks",
43
+ outDir: "src/generated",
44
+ },
45
+ source(source: DeckSource<Env>) {
46
+ return withR2Assets(source, {
47
+ bucket: (c) => c.env.DECK_ASSETS,
48
+ keyPrefix: "slides",
49
+ cacheControl: "public, max-age=31536000, immutable",
50
+ });
51
+ },
52
+ });
53
+ ```
54
+
55
+ By default the R2 key comes from the compiled asset's source path, optionally prefixed by `keyPrefix`. If the upload pipeline uses a different layout, provide a deterministic key resolver:
56
+
57
+ ```ts
58
+ source(source: DeckSource<Env>) {
59
+ return withR2Assets(source, {
60
+ bucket: (c) => c.env.DECK_ASSETS,
61
+ key: ({ slug, assetPath }) => `decks/${slug}/${assetPath}`,
62
+ cacheControl: ({ asset }) =>
63
+ asset.publicPath.includes("/versioned/")
64
+ ? "public, max-age=31536000, immutable"
65
+ : "public, max-age=300",
66
+ });
67
+ }
68
+ ```
69
+
70
+ ## Operational requirements
71
+
72
+ - Upload the same paths that the key resolver produces; `withR2Assets` only reads objects.
73
+ - Set correct R2 HTTP metadata, especially `contentType`. The decorator falls back to the compiled asset type when needed.
74
+ - Use immutable caching only for versioned or content-addressed keys.
75
+ - Keep the generated fallback if local development must work without an R2 binding.
76
+ - Test a present object, missing object, and missing binding. Missing R2 data should fall back to the underlying source.
77
+ - Do not replace slide image URLs with arbitrary public R2 URLs unless bypassing hono-decks asset routes is an explicit requirement.
@@ -0,0 +1,151 @@
1
+ # Styling
2
+
3
+ Use this route for deck-local `theme.css`, layout decisions, typography, visual hierarchy, animation, and overflow correction.
4
+
5
+ ## Canvas contract
6
+
7
+ hono-decks renders a fixed 1920 × 1080 design canvas with a 16:9 aspect ratio and scales it into the viewer. Do not build responsive slide breakpoints. Style the canvas itself, then verify the scaled result and print route.
8
+
9
+ The base root font size is 32px. Treat that as body copy, not a value to shrink globally. A practical hierarchy is:
10
+
11
+ - body: `1rem` (32px)
12
+ - supporting text: no smaller than `.75rem` (24px)
13
+ - slide title: `1.75rem` to `2.5rem`
14
+ - cover title: `3rem` to `5rem`
15
+ - line height: about `1.15` for headings and `1.35` to `1.5` for body text
16
+
17
+ ## Start with deck-scoped CSS
18
+
19
+ Create `decks/product/theme.css`:
20
+
21
+ ```css
22
+ :root {
23
+ --hono-decks-color: #f7f8fb;
24
+ --hono-decks-muted-color: #c3c8d4;
25
+ --hono-decks-accent-color: #8bd3ff;
26
+ --hono-decks-border-color: rgba(255, 255, 255, 0.2);
27
+ --hono-decks-card-background: rgba(12, 18, 34, 0.82);
28
+ --hono-decks-inline-code-background: rgba(12, 18, 34, 0.72);
29
+ --hono-decks-code-background: #0a1020;
30
+ }
31
+
32
+ .layout-cover,
33
+ .layout-default,
34
+ .layout-statement,
35
+ .layout-comparison {
36
+ background: #080d1a;
37
+ color: var(--hono-decks-color);
38
+ }
39
+
40
+ .slide h1,
41
+ .slide h2 {
42
+ margin: 0 0 0.65em;
43
+ max-width: 18ch;
44
+ line-height: 1.08;
45
+ letter-spacing: -0.025em;
46
+ }
47
+
48
+ .slide p,
49
+ .slide li {
50
+ line-height: 1.42;
51
+ }
52
+
53
+ .comparison-grid {
54
+ display: grid;
55
+ grid-template-columns: repeat(2, minmax(0, 1fr));
56
+ gap: 1.5rem;
57
+ align-items: stretch;
58
+ }
59
+
60
+ .comparison-card {
61
+ min-width: 0;
62
+ border: 1px solid var(--hono-decks-border-color);
63
+ border-radius: 20px;
64
+ background: var(--hono-decks-card-background);
65
+ padding: 1.25rem;
66
+ }
67
+ ```
68
+
69
+ Use explicit semantic classes in MDX:
70
+
71
+ ```mdx
72
+ ---
73
+ layout: comparison
74
+ ---
75
+
76
+ ## Build and runtime
77
+
78
+ <div class="comparison-grid">
79
+ <section class="comparison-card">
80
+ <h3>Build</h3>
81
+ <p>Compile MDX with Node.js or Bun.</p>
82
+ </section>
83
+ <section class="comparison-card">
84
+ <h3>Runtime</h3>
85
+ <p>Serve generated modules from Hono.</p>
86
+ </section>
87
+ </div>
88
+ ```
89
+
90
+ Do not assume UnoCSS or Tailwind utility classes exist.
91
+
92
+ ## Readability and density rules
93
+
94
+ - Prefer 20–40 visible words for a typical slide; use more only when the layout is deliberately text-led.
95
+ - Keep lists to about 3–6 short items. Split a long sequence across slides.
96
+ - Use at most two primary columns. More columns usually force unreadable type.
97
+ - Keep code examples focused on the lines needed to explain the idea. Use `CodeBlock` or fenced code, both of which scroll instead of enlarging the canvas.
98
+ - Use images and diagrams at a size that can be interpreted from the back of a room; remove decorative detail before reducing text.
99
+ - Maintain strong foreground/background contrast and never communicate state by color alone.
100
+
101
+ ## Prevent overflow
102
+
103
+ The slide intentionally uses `overflow: hidden`. Hidden content is a failure, not an invitation to add scrolling.
104
+
105
+ When content overflows, fix it in this order:
106
+
107
+ 1. Remove repetition and shorten prose.
108
+ 2. Split the content into two slides.
109
+ 3. Replace a dense table with a focused comparison or chart.
110
+ 4. Reduce gaps or decorative padding modestly.
111
+ 5. Reduce a local type size only when it remains at least 24px.
112
+
113
+ Avoid global transforms, `zoom`, negative margins that pull content outside the safe area, and font sizes chosen only to make one crowded slide fit.
114
+
115
+ ## Motion
116
+
117
+ Built-in slide transitions and Fire reveals already respect `prefers-reduced-motion`. Custom animation must do the same:
118
+
119
+ ```css
120
+ .signal-dot {
121
+ animation: pulse 2.4s ease-in-out infinite;
122
+ }
123
+
124
+ @keyframes pulse {
125
+ 50% {
126
+ transform: scale(1.08);
127
+ opacity: 0.72;
128
+ }
129
+ }
130
+
131
+ @media (prefers-reduced-motion: reduce) {
132
+ .signal-dot {
133
+ animation: none;
134
+ }
135
+ }
136
+ ```
137
+
138
+ Animation should clarify sequence or change. Do not animate every object or make critical information depend on motion.
139
+
140
+ ## Visual QA
141
+
142
+ Inspect every slide in the viewer and check:
143
+
144
+ - no clipped headings, lists, code, images, or footers
145
+ - body text remains readable when the 1920 × 1080 canvas is scaled down
146
+ - titles and key claims dominate the hierarchy
147
+ - repeated layouts align consistently
148
+ - images retain aspect ratio and meaningful content is not cropped
149
+ - Fire states remain understandable at each reveal step
150
+ - print preview contains every slide and does not depend on live embeds
151
+ - custom motion has a reduced-motion fallback