hono-svelte 0.6.1 → 0.7.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/CHANGELOG.md CHANGED
@@ -1,277 +1,321 @@
1
- # Changelog
2
-
3
- ## 0.6.1
4
-
5
- - **fix(shell): nested layouts nest outermost first.** With a root
6
- `layout.svelte` plus `<dir>/layout.svelte`, the server wrapped the page in
7
- the root layout first, so the directory layout ended up outside.
8
- - **fix(vite): client pages mount the whole layout chain.** The client entry
9
- imported every layout but only mounted `Layout0`; inner layouts vanished.
10
- Client entries are never server-rendered, so the chain is now mounted
11
- (no hydration of an empty root).
12
- - **fix(vite): components imported by static pages and layouts keep their
13
- `<style>`.** Only the page/layout files were compiled with
14
- `css: "injected"` for the server; a layout's nav or a component-library
15
- component lost its CSS on zero-JS pages.
16
- - **fix(handlers): `errorHandler` keeps the headers of `HTTPException`
17
- responses** — `basicAuth`/`bearerAuth` 401s now carry `WWW-Authenticate`.
18
- - **fix(shell): prefetch.** `prefetch: "all"` no longer preloads static pages
19
- (no JS chunk → 404); `"hover"` dedupes its links (it looked for
20
- `rel="prefetch"` but created `modulepreload`) and maps `/` → `index` and
21
- `/dir` → `dir/index`.
22
- - **fix(shell):** the global stylesheet fallback follows `assetsBase`; a
23
- throwing `nonce()` is logged instead of silently dropping the CSP nonce.
24
- - **fix(vite):** `pagesDir` outside the Vite root (including another drive
25
- on Windows) fails with a clear English error.
26
- - **test:** `test/regressions.test.ts` (one test per fix) and a Playwright
27
- contract suite in `e2e/` that runs the same browser tests against
28
- hono-svelte on Node, on Bun, and against SvelteKit as the reference. CI
29
- adds the e2e job and a peer-dependency matrix (declared minimums and
30
- latest within the majors).
31
-
32
- ## 0.6.0
33
-
34
- - **feat(shell): `htmlAttributes` option.** Only `lang` could be set on
35
- `<html>`, so themes driven by a root attribute (DaisyUI `data-theme`,
36
- `class="dark"`) needed CSS workarounds. `htmlAttributes` takes a record
37
- or `(c) => record` (per-request, e.g. from a cookie); values are escaped,
38
- invalid names (and `lang`, which has its own option) throw.
39
-
40
- - **feat(handlers): error pages get `{ status, message }`; JSON for API
41
- paths.** `notFoundHandler`/`errorHandler` rendered the page without any
42
- data, always with 404/500, and had no way to keep API routes on JSON.
43
- Now: pages receive `{ status, message }` (merged under `opts.data`);
44
- `errorHandler` uses the status carried by the error (`HTTPException`
45
- and friends), exposes the error message only for 4xx (`expose` to
46
- customize, `message` for the generic text) and logs only 5xx; new
47
- `json: (c) => boolean` answers `{ message }` JSON with the same status.
48
- `notFoundHandler` takes a `message`. `ErrorPageData` type exported.
49
-
50
- - **feat: `getPage()` from `hono-svelte/page`.** Layouts had no way to
51
- know which route rendered them (active-link logic needed the route
52
- threaded through every page's `data`). `getPage()` returns `{ path,
53
- route }` (request pathname + Hono route pattern, e.g. `/users/:id`) in
54
- server-rendered components and client pages alike: the shell provides it
55
- as Svelte context on SSR and writes `data-hs-path`/`data-hs-route` on the
56
- root element, which the generated client entry passes as `context` to
57
- `mount`/`hydrate`. New `./page` export; `PageInfo` type exported from the
58
- main entry.
59
-
60
- - **feat(shell): global stylesheet resolved from the manifest
61
- (`stylesEntry`).** With `assets: manifest`, the global CSS kept pointing at
62
- the unhashed `/static/styles.css`, so hashed client builds needed a
63
- hand-written `stylesHref: "/" + manifest["src/styles.css"].file`. The
64
- shell now resolves it from the manifest. New `stylesEntry` option
65
- (default `"src/styles.css"`) names the source file for both modes: dev
66
- links `/<stylesEntry>`, prod the hashed file (fallback
67
- `/static/styles.css`). `stylesHref` still wins when set.
68
-
69
- - **feat(types): `Entry` type exported from `hono-svelte`.** The generated
70
- `.d.ts` now also augments the package (`interface Register { entries }`),
71
- so helpers that forward an entry to `c.render` can be typed with
72
- `import type { Entry } from "hono-svelte"` instead of importing the
73
- generated file by relative path. Without the `.d.ts`, `Entry` is
74
- `string`. A new `test/types.test.ts` runs `tsc` against the generated
75
- declarations.
76
-
77
- - **fix(shell): the page's `<svelte:head>` title and description win.**
78
- The shell always emitted its default `<title>` first and browsers use the
79
- first one, so a static page's `<svelte:head><title>` was ignored (and
80
- `shell({ head })` descriptions were duplicated). The document now keeps a
81
- single title — `c.render(entry, { title })` > page/layout `<svelte:head>`
82
- > `shell({ title })` — and a page description (`<svelte:head>` or
83
- `head.description`) removes the one from `shell({ head })`.
84
-
85
- - **fix(assets): CSS of shared chunks is linked in production.** Vite
86
- attaches a module's CSS to the chunk that owns it, so a layout or
87
- component shared by several pages gets its own chunk + CSS file.
88
- `manifestCssFor`/`manifestImportsFor` (and therefore `shell({ assets:
89
- manifest })`) only read the entry chunk: pages rendered without their
90
- layout's styles. Both now walk the static `imports` graph (shared chunks
91
- first, cycles handled, `dynamicImports` skipped). `ViteManifestChunk`
92
- gains the optional `src`, `name` and `dynamicImports` fields.
93
- - **fix(vite): static pages and layouts keep their `<style>` after `vite
94
- build`.** Server-rendered components have no client bundle, but with
95
- vite-plugin-svelte's default `emitCss: true` the compiler emits their CSS
96
- as an import that SSR drops — zero-JS pages shipped unstyled. `pages()`
97
- now loads static pages and layouts for the server with
98
- `css: "injected"` (merged into an existing `<svelte:options>`, explicit
99
- `css` respected), so `render()` returns the styles in `head`. New
100
- `test/build.test.ts` exercises the real `vite build --ssr` pipeline.
101
-
102
- - **fix(scaffold): `isSafeAppPath` normalizes absolute targets.** An
103
- absolute path such as `/app/../evil.ts` passed the in-root prefix check
104
- on POSIX (only relative targets went through `resolve()`). Every target is
105
- now resolved before the comparison.
106
- - **CI green on main**: the Windows-only `isSafeAppPath` assertions run only
107
- on win32 (drive letters are relative segments on POSIX); the nested-layout
108
- test uses its temp dir as the Vite root (it only passed on Windows because
109
- tmpdir sits on another drive); the `example` job installs Bun, which its
110
- build script calls.
111
-
112
- ## 0.5.1
113
-
114
- - **Scaffold hardening** (`hono-svelte init`):
115
- - the generated `package.json` pins the security floors
116
- (`hono ^4.13.8`, `svelte ^5.57.0`, `vite ^8.3.0`) — the previous
117
- `^4`/`^5`/`^8` ranges resolved versions with known advisories; a
118
- regression test keeps the floors in sync with the package peers.
119
- - `init` writes an initial `src/hono-svelte-entries.d.ts` stub
120
- (plain-string renderer): `tsc` right after `init` used to fail on
121
- `c.render(entry, props)` (2 args vs hono's 1-arg default renderer)
122
- because the typed dts only exists after the first vite run;
123
- `pages({ dts: true })` replaces the stub with the typed union on the
124
- first `dev`/`build`.
125
- - **`c.render()` coexistence contract**: 6 tests pin the shell against
126
- hono's renderer semantics — `c.render(string)` (default renderer) is
127
- reinterpreted as an entry name under the shell (strict 500); renderer
128
- middlewares on the same path pattern are last-registered-wins; and
129
- **path-scoped coexistence works**: default-render routes and
130
- jsxRenderer-style middlewares keep working next to a `shell()` mounted on
131
- its own path (`app.use("/pages/*", shell())`). Documented in the README
132
- ("Coexistence with other renderers").
133
- - **CI**: new `scaffold` job smoke-tests `init --full` end to end (install
134
- with the local package via `file:` → typecheck → build), guarding the
135
- inline templates against drift from the (unpublished) playground example.
136
-
137
- ## 0.5.0
138
-
139
- - **Per-page data typing (opt-in, `pages({ dts: true })`)**: declare
140
- `export type Data` (or `interface Data`) in a page's `<script module>` and
141
- the generated `.d.ts` adds a typed overload per page —
142
- `c.render("dashboard", { data })` is checked by `tsc` (a missing/mistyped
143
- field fails the typecheck instead of shipping `undefined`). The declaration
144
- is inlined verbatim, so it must be self-contained; types that reference
145
- imports are skipped with a one-time warning and keep
146
- `Record<string, unknown>` data. `RenderProps` is now generic
147
- (`RenderProps<TData = Record<string, unknown>>`) and `pages().dataTypes()`
148
- exposes the extractions. The dts also regenerates when a page's `Data`
149
- type changes (dev watcher + `buildStart`).
150
- - **Neutral `<body>`**: the shell no longer hardcodes DaisyUI classes
151
- (`bg-base-200 min-h-screen text-base-content`) — it emits a plain,
152
- CSS-framework-agnostic document. Style `body`/`html` from your global
153
- stylesheet instead (`body { background: var(--color-base-200); ... }`, or
154
- `@apply` with Tailwind — the playground does exactly that in
155
- `styles.css`). **Breaking** for apps relying on the hardcoded classes.
156
- - **`shell({ ssrFallback })`**: when SSR of a page throws at request time,
157
- log the error and fall back to client rendering (entry script) instead of
158
- a 500. Default `false` keeps the previous behavior.
159
- - `prefetch: "all"` / `"hover"` now emit `rel="modulepreload"` (prefetch on a
160
- module URL could double-fetch in some browsers; modulepreload also warms
161
- the entry's import graph).
162
- - `dataLimit` warning fires once per **(page, limit)** instead of once per
163
- process — a second oversized page is reported too, with the entry name in
164
- the message.
165
- - **Removed dead stubs**: `versionsMatch()` (always returned `true`), the
166
- unused `hasClient` export from the ssr-manifest stub/virtual manifest, and
167
- the no-op `checkPagesDir`. **Breaking: `versionsMatch` is no longer
168
- exported.**
169
- - Fully typed handlers/middleware: `notFoundHandler()` / `errorHandler()`
170
- match Hono's `NotFoundHandler`/`ErrorHandler` signatures and `shell()` no
171
- longer uses `any` internally.
172
- - **CLI: `hono-svelte add page <name>`** — scaffolds
173
- `src/pages/<name>.svelte` (static by default, skip-if-exists, traversal
174
- refused). `isValidPageName()` / `newPageFile()` exported for tests.
175
- - Metadata: `engines` now declares `bun >= 1.4` and `node >= 26`, the
176
- `./package.json` export subpath was added, and TypeScript 7 builds the
177
- package.
178
- - **Security floors in `peerDependencies`**: `hono ^4.13.8`, `svelte ^5.57.0`,
179
- `vite ^8.3.0` — every published advisory sits outside these floors
180
- (hono < 4.12.12 and 4.12.0–4.12.33, patched in 4.12.34; svelte ≤ 5.55.6,
181
- patched in 5.55.7; vite ≤ 8.0.15, patched in 8.0.16; transitive `devalue`
182
- resolves to the patched 5.9.2, which also closes CVE-2026-81176). The
183
- versions installed in the repo and in the example are the latest published
184
- and none is affected by a known advisory — the ranges just avoid
185
- advertising vulnerable versions to consumers and scanners.
186
- - CI: ubuntu + windows runners (Windows-only regressions like the old
187
- `isSafeAppPath` bug are caught now), coverage via `@vitest/coverage-v8`
188
- with thresholds (`npm test` runs it), all on the officially supported
189
- runtimes (Node 26, TS 7, Vite 8, Bun 1.4+).
190
- - `hono-svelte init` now scaffolds TypeScript 7 + `@types/node` 26 in the
191
- generated app.
192
-
193
- ## 0.4.2
194
-
195
- - `hono-svelte init` now scaffolds a complete runnable app (works with
196
- `npx`, `bunx` and `pnpm dlx` — writes text files only, no install/network).
197
- Minimal flavor (default): `package.json`, `tsconfig.json`, `.gitignore`,
198
- `vite.config.ts` (with `pages({ dts: true })`), two pages (`index` zero-JS,
199
- `hello` with `data`), `src/routes/index.ts`, `src/styles.css`.
200
- `init --full` adds auth (cookie session), dashboard with nested layout,
201
- and typed RPC (`hc<AppType>`) — `/auth` → login → `/dashboard`.
202
- Generated scripts (`dev`/`build`/`typecheck`/`start`) run under npm and bun.
203
- `InitFlavor` type exported.
204
-
205
- ## 0.4.1
206
-
207
- - Fix `hono-svelte init` refusing every file on Windows: `isSafeAppPath()`
208
- now normalizes `\` vs `/` and compares case-insensitively on win32
209
- (path traversal still blocked).
210
- - Fix `bin` path without `./` prefix — npm was silently stripping the CLI
211
- from the published tarball.
212
-
213
- ## 0.4.0
214
-
215
- - **Typed `c.render` entries (opt-in)**: `pages({ dts })` writes a `.d.ts` file
216
- with the entry union (`HonoSvelteEntries`) + `declare module "hono"`
217
- augmentation. `true` writes `src/hono-svelte-entries.d.ts` (relative to the
218
- Vite root); a string sets a custom path. Regenerated on `buildStart` and by
219
- the dev watcher when entries change. `dtsPath()` / `writeDts()` exposed on
220
- the plugin. The playground wires it (`pages({ dts: true })` +
221
- `/// <reference path="./hono-svelte-entries.d.ts" />` in `env.d.ts`), so a
222
- typo in `c.render("...")` fails `tsc` instead of 500ing at runtime.
223
- `data` stays `Record<string, unknown>` (per-page payload types are a future
224
- feature).
225
- - **Layouts, single source of truth**: `<dir>/layout.svelte` wraps pages
226
- automatically (SSR inside-out + client hydrate, `layouts()` /
227
- `layoutChain()` to inspect, `layouts: false` = legacy ignore). Pages must
228
- NOT import their layout manually — the plugin fails fast at build time
229
- (`manually imports ... but the layout is applied automatically`) instead of
230
- rendering the layout twice. Layout props (`title`, `description`) come from
231
- the route via `c.render(entry, { data })`, with `title = "Dashboard"`
232
- fallback in the layout. Rule shared by plugin and shell: static means
233
- "no `<script>`", layouts don't matter; `__clientEntries` travels in the
234
- virtual manifest so the shell picks the SSR branch for static+layouts pages.
235
- - **Runtime fixes**: static `import.meta.env.PROD/DEV` access (Vite module
236
- runner forbids dynamic access — dev 500 fixed); static
237
- `createRawSnippet` import in layout entries (no duplicated Svelte runtime —
238
- `Illegal invocation` fixed); layout children snippet renders `<div>` wrapper
239
- (empty render broke hydration — `Cannot set properties of null` fixed);
240
- bundled server defaults to prod so plain `node ./dist/index.js` works on
241
- Windows with no `NODE_ENV` prefix.
242
- - `c.render(entry, { status, headers, head })`: per-render status/headers
243
- (merged over shell defaults) and CSP-safe per-page head tags
244
- (`description`, `canonical`, `og`, `twitter`, `meta`, `extra`).
245
- - SSR static pages now receive `data` as component props (plus the embedded JSON).
246
- - Strict entries: unknown entryNames throw a dev-friendly error (`Available pages: ...`, generic in prod); `shell({ strict: false })` disables; `availableEntries()` exposes the list.
247
- - `pages().types()` / `typeDeclarations()` generate the entry union + hono module snippet.
248
- - Nested layouts: `<dir>/layout.svelte` wraps pages (SSR inside-out, `layouts()`/`layoutChain()`, `layouts: false` = legacy).
249
- - `notFoundHandler()` / `errorHandler()` for conventional `404.svelte` / `500.svelte` pages.
250
- - `hono-svelte init|doctor` CLI (`bin`) with `--app` flag.
251
- - `pages().validateConfig()` fails fast on missing svelte plugin / pagesDir.
252
- - `shell({ assets })` accepts a Vite manifest (hashed URLs, CSS, shared-chunk preload) with fallback; `preload`/`prefetch` controls; `styles` extra CSS.
253
- - `shell({ nonce })` CSP support on shell tags; `svelte/server` CSP passthrough for SSR.
254
- - Cache helpers: `immutableHeaders()`, `CACHE_IMMUTABLE`, `CACHE_NO_STORE`.
255
- - `data` size one-time warning (`shell({ dataLimit })`, default 100KB).
256
-
257
- ## 0.3.1
258
-
259
- - Package metadata: repository/homepage/author, publishConfig, LICENSE + CHANGELOG in files.
260
- - No code changes since 0.3.0.
261
-
262
- ## 0.3.0
263
-
264
- - Fully in-memory package: entries and manifest are virtual modules (no files written to disk).
265
- - Automatic zero-JS: `.svelte` pages without `<script>` render on the server (`svelte/server`)
266
- and download no JS on the client.
267
- - Deterministic opaque IDs derived from the entryName (no salt, no env, no disk).
268
- - Zero config: the shell resolves the SSR map on its own (relative import redirected by the plugin).
269
- - Test suite (vitest) and CI (typecheck + tests + build + publint).
270
-
271
- ## 0.2.0
272
-
273
- - Opaque IDs (`r-` + hash) replace configurable `rootId`/`dataId`.
274
-
275
- ## 0.1.0
276
-
1
+ # Changelog
2
+
3
+ ## 0.7.0
4
+
5
+ - **feat(vite): per-page `Data` can use imported types.** The generated dts
6
+ rewrites references to types imported in the page's `<script module>` as
7
+ `import("…")` type queries (named, aliased, default and namespace
8
+ imports; alias/package specifiers kept, relative ones re-resolved from the
9
+ dts). Pages whose data comes from the app's own types — every real app —
10
+ can now use per-page typing instead of being skipped with a warning.
11
+ - **fix(vite): typed `c.render` overloads are actually enforced.** The
12
+ catch-all overload accepted every entry, so a wrong `data` for a typed
13
+ page failed its overload and silently matched the catch-all
14
+ (`Record<string, unknown>`). The catch-all now excludes typed entries.
15
+ Behavior change: rendering through a value typed as the whole `Entry`
16
+ union no longer compiles once a page declares `Data`. New `UntypedEntry`
17
+ (pages without `Data`) and `TypedEntry` types: generic helpers take
18
+ `UntypedEntry`.
19
+ - Imported types inside template literal types (`\`row-${RowId}\``) are
20
+ qualified too; a `//` inside an import specifier (URL) no longer cuts it.
21
+ - New `test/types.test.ts` case runs `tsc` on a page whose `Data` imports
22
+ an app type: correct data compiles, a wrong field type and a missing field
23
+ fail.
24
+
25
+ ## 0.6.2
26
+
27
+ - **fix(shell): unknown-entry errors never list the app's pages in
28
+ production** (as documented for `knownEntries`); dev keeps the list.
29
+ - **refactor:** the layout chain lives in one module (`src/layouts.ts`)
30
+ shared by the Vite plugin and the shell — the 0.6.1 nesting bug came from
31
+ two diverging copies. Removed dead code (`isDev`, an unused prefetch
32
+ parameter).
33
+ - **perf(shell):** the Vite manifest is resolved once per shell and its
34
+ per-entry CSS/preload lists are cached, instead of walking the chunk graph
35
+ on every request.
36
+ - **getPage().route** keeps reading the request's own `routePath`: the
37
+ `hono/route` helper breaks with duplicate hono copies (found by the new
38
+ playground e2e run). A test guards against runtime hono imports.
39
+ - **Node 24 (LTS) supported**: `engines.node` is `>=24` (package and
40
+ `init` template); CI runs Node 24 and 26.
41
+ - **CI/release:** `release.yml` publishes tags with npm trusted publishing
42
+ (OIDC, provenance) and creates the GitHub release from this file;
43
+ Dependabot for npm and actions; coverage floors raised to the measured
44
+ level; e2e adds `vite` dev and the playground app (sign-in, session,
45
+ dashboard).
46
+
47
+ ## 0.6.1
48
+
49
+ - **fix(shell): nested layouts nest outermost first.** With a root
50
+ `layout.svelte` plus `<dir>/layout.svelte`, the server wrapped the page in
51
+ the root layout first, so the directory layout ended up outside.
52
+ - **fix(vite): client pages mount the whole layout chain.** The client entry
53
+ imported every layout but only mounted `Layout0`; inner layouts vanished.
54
+ Client entries are never server-rendered, so the chain is now mounted
55
+ (no hydration of an empty root).
56
+ - **fix(vite): components imported by static pages and layouts keep their
57
+ `<style>`.** Only the page/layout files were compiled with
58
+ `css: "injected"` for the server; a layout's nav or a component-library
59
+ component lost its CSS on zero-JS pages.
60
+ - **fix(handlers): `errorHandler` keeps the headers of `HTTPException`
61
+ responses** — `basicAuth`/`bearerAuth` 401s now carry `WWW-Authenticate`.
62
+ - **fix(shell): prefetch.** `prefetch: "all"` no longer preloads static pages
63
+ (no JS chunk → 404); `"hover"` dedupes its links (it looked for
64
+ `rel="prefetch"` but created `modulepreload`) and maps `/` → `index` and
65
+ `/dir` → `dir/index`.
66
+ - **fix(shell):** the global stylesheet fallback follows `assetsBase`; a
67
+ throwing `nonce()` is logged instead of silently dropping the CSP nonce.
68
+ - **fix(vite):** `pagesDir` outside the Vite root (including another drive
69
+ on Windows) fails with a clear English error.
70
+ - **test:** `test/regressions.test.ts` (one test per fix) and a Playwright
71
+ contract suite in `e2e/` that runs the same browser tests against
72
+ hono-svelte on Node, on Bun, and against SvelteKit as the reference. CI
73
+ adds the e2e job and a peer-dependency matrix (declared minimums and
74
+ latest within the majors).
75
+
76
+ ## 0.6.0
77
+
78
+ - **feat(shell): `htmlAttributes` option.** Only `lang` could be set on
79
+ `<html>`, so themes driven by a root attribute (DaisyUI `data-theme`,
80
+ `class="dark"`) needed CSS workarounds. `htmlAttributes` takes a record
81
+ or `(c) => record` (per-request, e.g. from a cookie); values are escaped,
82
+ invalid names (and `lang`, which has its own option) throw.
83
+
84
+ - **feat(handlers): error pages get `{ status, message }`; JSON for API
85
+ paths.** `notFoundHandler`/`errorHandler` rendered the page without any
86
+ data, always with 404/500, and had no way to keep API routes on JSON.
87
+ Now: pages receive `{ status, message }` (merged under `opts.data`);
88
+ `errorHandler` uses the status carried by the error (`HTTPException`
89
+ and friends), exposes the error message only for 4xx (`expose` to
90
+ customize, `message` for the generic text) and logs only 5xx; new
91
+ `json: (c) => boolean` answers `{ message }` JSON with the same status.
92
+ `notFoundHandler` takes a `message`. `ErrorPageData` type exported.
93
+
94
+ - **feat: `getPage()` from `hono-svelte/page`.** Layouts had no way to
95
+ know which route rendered them (active-link logic needed the route
96
+ threaded through every page's `data`). `getPage()` returns `{ path,
97
+ route }` (request pathname + Hono route pattern, e.g. `/users/:id`) in
98
+ server-rendered components and client pages alike: the shell provides it
99
+ as Svelte context on SSR and writes `data-hs-path`/`data-hs-route` on the
100
+ root element, which the generated client entry passes as `context` to
101
+ `mount`/`hydrate`. New `./page` export; `PageInfo` type exported from the
102
+ main entry.
103
+
104
+ - **feat(shell): global stylesheet resolved from the manifest
105
+ (`stylesEntry`).** With `assets: manifest`, the global CSS kept pointing at
106
+ the unhashed `/static/styles.css`, so hashed client builds needed a
107
+ hand-written `stylesHref: "/" + manifest["src/styles.css"].file`. The
108
+ shell now resolves it from the manifest. New `stylesEntry` option
109
+ (default `"src/styles.css"`) names the source file for both modes: dev
110
+ links `/<stylesEntry>`, prod the hashed file (fallback
111
+ `/static/styles.css`). `stylesHref` still wins when set.
112
+
113
+ - **feat(types): `Entry` type exported from `hono-svelte`.** The generated
114
+ `.d.ts` now also augments the package (`interface Register { entries }`),
115
+ so helpers that forward an entry to `c.render` can be typed with
116
+ `import type { Entry } from "hono-svelte"` instead of importing the
117
+ generated file by relative path. Without the `.d.ts`, `Entry` is
118
+ `string`. A new `test/types.test.ts` runs `tsc` against the generated
119
+ declarations.
120
+
121
+ - **fix(shell): the page's `<svelte:head>` title and description win.**
122
+ The shell always emitted its default `<title>` first and browsers use the
123
+ first one, so a static page's `<svelte:head><title>` was ignored (and
124
+ `shell({ head })` descriptions were duplicated). The document now keeps a
125
+ single title — `c.render(entry, { title })` > page/layout `<svelte:head>`
126
+ > `shell({ title })` — and a page description (`<svelte:head>` or
127
+ `head.description`) removes the one from `shell({ head })`.
128
+
129
+ - **fix(assets): CSS of shared chunks is linked in production.** Vite
130
+ attaches a module's CSS to the chunk that owns it, so a layout or
131
+ component shared by several pages gets its own chunk + CSS file.
132
+ `manifestCssFor`/`manifestImportsFor` (and therefore `shell({ assets:
133
+ manifest })`) only read the entry chunk: pages rendered without their
134
+ layout's styles. Both now walk the static `imports` graph (shared chunks
135
+ first, cycles handled, `dynamicImports` skipped). `ViteManifestChunk`
136
+ gains the optional `src`, `name` and `dynamicImports` fields.
137
+ - **fix(vite): static pages and layouts keep their `<style>` after `vite
138
+ build`.** Server-rendered components have no client bundle, but with
139
+ vite-plugin-svelte's default `emitCss: true` the compiler emits their CSS
140
+ as an import that SSR drops — zero-JS pages shipped unstyled. `pages()`
141
+ now loads static pages and layouts for the server with
142
+ `css: "injected"` (merged into an existing `<svelte:options>`, explicit
143
+ `css` respected), so `render()` returns the styles in `head`. New
144
+ `test/build.test.ts` exercises the real `vite build --ssr` pipeline.
145
+
146
+ - **fix(scaffold): `isSafeAppPath` normalizes absolute targets.** An
147
+ absolute path such as `/app/../evil.ts` passed the in-root prefix check
148
+ on POSIX (only relative targets went through `resolve()`). Every target is
149
+ now resolved before the comparison.
150
+ - **CI green on main**: the Windows-only `isSafeAppPath` assertions run only
151
+ on win32 (drive letters are relative segments on POSIX); the nested-layout
152
+ test uses its temp dir as the Vite root (it only passed on Windows because
153
+ tmpdir sits on another drive); the `example` job installs Bun, which its
154
+ build script calls.
155
+
156
+ ## 0.5.1
157
+
158
+ - **Scaffold hardening** (`hono-svelte init`):
159
+ - the generated `package.json` pins the security floors
160
+ (`hono ^4.13.8`, `svelte ^5.57.0`, `vite ^8.3.0`) — the previous
161
+ `^4`/`^5`/`^8` ranges resolved versions with known advisories; a
162
+ regression test keeps the floors in sync with the package peers.
163
+ - `init` writes an initial `src/hono-svelte-entries.d.ts` stub
164
+ (plain-string renderer): `tsc` right after `init` used to fail on
165
+ `c.render(entry, props)` (2 args vs hono's 1-arg default renderer)
166
+ because the typed dts only exists after the first vite run;
167
+ `pages({ dts: true })` replaces the stub with the typed union on the
168
+ first `dev`/`build`.
169
+ - **`c.render()` coexistence contract**: 6 tests pin the shell against
170
+ hono's renderer semantics — `c.render(string)` (default renderer) is
171
+ reinterpreted as an entry name under the shell (strict 500); renderer
172
+ middlewares on the same path pattern are last-registered-wins; and
173
+ **path-scoped coexistence works**: default-render routes and
174
+ jsxRenderer-style middlewares keep working next to a `shell()` mounted on
175
+ its own path (`app.use("/pages/*", shell())`). Documented in the README
176
+ ("Coexistence with other renderers").
177
+ - **CI**: new `scaffold` job smoke-tests `init --full` end to end (install
178
+ with the local package via `file:` → typecheck → build), guarding the
179
+ inline templates against drift from the (unpublished) playground example.
180
+
181
+ ## 0.5.0
182
+
183
+ - **Per-page data typing (opt-in, `pages({ dts: true })`)**: declare
184
+ `export type Data` (or `interface Data`) in a page's `<script module>` and
185
+ the generated `.d.ts` adds a typed overload per page —
186
+ `c.render("dashboard", { data })` is checked by `tsc` (a missing/mistyped
187
+ field fails the typecheck instead of shipping `undefined`). The declaration
188
+ is inlined verbatim, so it must be self-contained; types that reference
189
+ imports are skipped with a one-time warning and keep
190
+ `Record<string, unknown>` data. `RenderProps` is now generic
191
+ (`RenderProps<TData = Record<string, unknown>>`) and `pages().dataTypes()`
192
+ exposes the extractions. The dts also regenerates when a page's `Data`
193
+ type changes (dev watcher + `buildStart`).
194
+ - **Neutral `<body>`**: the shell no longer hardcodes DaisyUI classes
195
+ (`bg-base-200 min-h-screen text-base-content`) — it emits a plain,
196
+ CSS-framework-agnostic document. Style `body`/`html` from your global
197
+ stylesheet instead (`body { background: var(--color-base-200); ... }`, or
198
+ `@apply` with Tailwind — the playground does exactly that in
199
+ `styles.css`). **Breaking** for apps relying on the hardcoded classes.
200
+ - **`shell({ ssrFallback })`**: when SSR of a page throws at request time,
201
+ log the error and fall back to client rendering (entry script) instead of
202
+ a 500. Default `false` keeps the previous behavior.
203
+ - `prefetch: "all"` / `"hover"` now emit `rel="modulepreload"` (prefetch on a
204
+ module URL could double-fetch in some browsers; modulepreload also warms
205
+ the entry's import graph).
206
+ - `dataLimit` warning fires once per **(page, limit)** instead of once per
207
+ process — a second oversized page is reported too, with the entry name in
208
+ the message.
209
+ - **Removed dead stubs**: `versionsMatch()` (always returned `true`), the
210
+ unused `hasClient` export from the ssr-manifest stub/virtual manifest, and
211
+ the no-op `checkPagesDir`. **Breaking: `versionsMatch` is no longer
212
+ exported.**
213
+ - Fully typed handlers/middleware: `notFoundHandler()` / `errorHandler()`
214
+ match Hono's `NotFoundHandler`/`ErrorHandler` signatures and `shell()` no
215
+ longer uses `any` internally.
216
+ - **CLI: `hono-svelte add page <name>`** — scaffolds
217
+ `src/pages/<name>.svelte` (static by default, skip-if-exists, traversal
218
+ refused). `isValidPageName()` / `newPageFile()` exported for tests.
219
+ - Metadata: `engines` now declares `bun >= 1.4` and `node >= 26`, the
220
+ `./package.json` export subpath was added, and TypeScript 7 builds the
221
+ package.
222
+ - **Security floors in `peerDependencies`**: `hono ^4.13.8`, `svelte ^5.57.0`,
223
+ `vite ^8.3.0` — every published advisory sits outside these floors
224
+ (hono < 4.12.12 and 4.12.0–4.12.33, patched in 4.12.34; svelte ≤ 5.55.6,
225
+ patched in 5.55.7; vite ≤ 8.0.15, patched in 8.0.16; transitive `devalue`
226
+ resolves to the patched 5.9.2, which also closes CVE-2026-81176). The
227
+ versions installed in the repo and in the example are the latest published
228
+ and none is affected by a known advisory — the ranges just avoid
229
+ advertising vulnerable versions to consumers and scanners.
230
+ - CI: ubuntu + windows runners (Windows-only regressions like the old
231
+ `isSafeAppPath` bug are caught now), coverage via `@vitest/coverage-v8`
232
+ with thresholds (`npm test` runs it), all on the officially supported
233
+ runtimes (Node 26, TS 7, Vite 8, Bun 1.4+).
234
+ - `hono-svelte init` now scaffolds TypeScript 7 + `@types/node` 26 in the
235
+ generated app.
236
+
237
+ ## 0.4.2
238
+
239
+ - `hono-svelte init` now scaffolds a complete runnable app (works with
240
+ `npx`, `bunx` and `pnpm dlx` — writes text files only, no install/network).
241
+ Minimal flavor (default): `package.json`, `tsconfig.json`, `.gitignore`,
242
+ `vite.config.ts` (with `pages({ dts: true })`), two pages (`index` zero-JS,
243
+ `hello` with `data`), `src/routes/index.ts`, `src/styles.css`.
244
+ `init --full` adds auth (cookie session), dashboard with nested layout,
245
+ and typed RPC (`hc<AppType>`) — `/auth` → login → `/dashboard`.
246
+ Generated scripts (`dev`/`build`/`typecheck`/`start`) run under npm and bun.
247
+ `InitFlavor` type exported.
248
+
249
+ ## 0.4.1
250
+
251
+ - Fix `hono-svelte init` refusing every file on Windows: `isSafeAppPath()`
252
+ now normalizes `\` vs `/` and compares case-insensitively on win32
253
+ (path traversal still blocked).
254
+ - Fix `bin` path without `./` prefix — npm was silently stripping the CLI
255
+ from the published tarball.
256
+
257
+ ## 0.4.0
258
+
259
+ - **Typed `c.render` entries (opt-in)**: `pages({ dts })` writes a `.d.ts` file
260
+ with the entry union (`HonoSvelteEntries`) + `declare module "hono"`
261
+ augmentation. `true` writes `src/hono-svelte-entries.d.ts` (relative to the
262
+ Vite root); a string sets a custom path. Regenerated on `buildStart` and by
263
+ the dev watcher when entries change. `dtsPath()` / `writeDts()` exposed on
264
+ the plugin. The playground wires it (`pages({ dts: true })` +
265
+ `/// <reference path="./hono-svelte-entries.d.ts" />` in `env.d.ts`), so a
266
+ typo in `c.render("...")` fails `tsc` instead of 500ing at runtime.
267
+ `data` stays `Record<string, unknown>` (per-page payload types are a future
268
+ feature).
269
+ - **Layouts, single source of truth**: `<dir>/layout.svelte` wraps pages
270
+ automatically (SSR inside-out + client hydrate, `layouts()` /
271
+ `layoutChain()` to inspect, `layouts: false` = legacy ignore). Pages must
272
+ NOT import their layout manually — the plugin fails fast at build time
273
+ (`manually imports ... but the layout is applied automatically`) instead of
274
+ rendering the layout twice. Layout props (`title`, `description`) come from
275
+ the route via `c.render(entry, { data })`, with `title = "Dashboard"`
276
+ fallback in the layout. Rule shared by plugin and shell: static means
277
+ "no `<script>`", layouts don't matter; `__clientEntries` travels in the
278
+ virtual manifest so the shell picks the SSR branch for static+layouts pages.
279
+ - **Runtime fixes**: static `import.meta.env.PROD/DEV` access (Vite module
280
+ runner forbids dynamic access — dev 500 fixed); static
281
+ `createRawSnippet` import in layout entries (no duplicated Svelte runtime —
282
+ `Illegal invocation` fixed); layout children snippet renders `<div>` wrapper
283
+ (empty render broke hydration — `Cannot set properties of null` fixed);
284
+ bundled server defaults to prod so plain `node ./dist/index.js` works on
285
+ Windows with no `NODE_ENV` prefix.
286
+ - `c.render(entry, { status, headers, head })`: per-render status/headers
287
+ (merged over shell defaults) and CSP-safe per-page head tags
288
+ (`description`, `canonical`, `og`, `twitter`, `meta`, `extra`).
289
+ - SSR static pages now receive `data` as component props (plus the embedded JSON).
290
+ - Strict entries: unknown entryNames throw a dev-friendly error (`Available pages: ...`, generic in prod); `shell({ strict: false })` disables; `availableEntries()` exposes the list.
291
+ - `pages().types()` / `typeDeclarations()` generate the entry union + hono module snippet.
292
+ - Nested layouts: `<dir>/layout.svelte` wraps pages (SSR inside-out, `layouts()`/`layoutChain()`, `layouts: false` = legacy).
293
+ - `notFoundHandler()` / `errorHandler()` for conventional `404.svelte` / `500.svelte` pages.
294
+ - `hono-svelte init|doctor` CLI (`bin`) with `--app` flag.
295
+ - `pages().validateConfig()` fails fast on missing svelte plugin / pagesDir.
296
+ - `shell({ assets })` accepts a Vite manifest (hashed URLs, CSS, shared-chunk preload) with fallback; `preload`/`prefetch` controls; `styles` extra CSS.
297
+ - `shell({ nonce })` CSP support on shell tags; `svelte/server` CSP passthrough for SSR.
298
+ - Cache helpers: `immutableHeaders()`, `CACHE_IMMUTABLE`, `CACHE_NO_STORE`.
299
+ - `data` size one-time warning (`shell({ dataLimit })`, default 100KB).
300
+
301
+ ## 0.3.1
302
+
303
+ - Package metadata: repository/homepage/author, publishConfig, LICENSE + CHANGELOG in files.
304
+ - No code changes since 0.3.0.
305
+
306
+ ## 0.3.0
307
+
308
+ - Fully in-memory package: entries and manifest are virtual modules (no files written to disk).
309
+ - Automatic zero-JS: `.svelte` pages without `<script>` render on the server (`svelte/server`)
310
+ and download no JS on the client.
311
+ - Deterministic opaque IDs derived from the entryName (no salt, no env, no disk).
312
+ - Zero config: the shell resolves the SSR map on its own (relative import redirected by the plugin).
313
+ - Test suite (vitest) and CI (typecheck + tests + build + publint).
314
+
315
+ ## 0.2.0
316
+
317
+ - Opaque IDs (`r-` + hash) replace configurable `rootId`/`dataId`.
318
+
319
+ ## 0.1.0
320
+
277
321
  - First version: `shell()` middleware + `pages()` plugin with disk-generated entries.