hono-decks 0.2.1 → 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.
@@ -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