@kurtbruns/kestrel 1.20.0 → 1.22.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
@@ -10,6 +10,62 @@ To move a running instance from one version to another, follow [Upgrade to a new
10
10
 
11
11
  <!-- Add entries under Added / Changed / Fixed / Breaking. One operator-facing line each; see .claude/rules/changelog.md. Upgrade steps (a migration, a new setting) go once, in the Upgrading paragraph, never on an entry. -->
12
12
 
13
+ ## [1.22.0] - 2026-11-06
14
+
15
+ **Upgrading from 1.21.0.** No database migrations, so `npm run sync-migrations` brings in nothing and there's nothing to apply. In your instance's repository, start with nothing uncommitted, as [Upgrade to a new release](https://github.com/kurtbruns/kestrel/blob/v1.21.0/docs/guides/07-upgrade.md) says, and install the release with `npm install @kurtbruns/kestrel@1.22.0`. This release's gate before the first change arrives with that install, so it first applies to your next upgrade. This one starts on the guide and skill you have now. Once installed, the new guide's first section asks for a clean repository, which the install has just changed, so carry on from the step after the install, where the steps are the ones you had. Run `npm run check-context`: this release changes one file of the operator's Claude context, the upgrade skill, whose change bears on safety, since it now has Claude stop until `git status` lists nothing and ask for a go-ahead before any change, and a copy you've edited is merged by hand. Then run `npm run typecheck`, commit the upgrade, note a restore point, and deploy with `npx wrangler deploy --env production`, as the guide lays out.
16
+
17
+ ### Added
18
+
19
+ - Each section's title in Settings is now a link to that section's address, so you can copy a link to a section and send it to someone else.
20
+
21
+ ### Changed
22
+
23
+ - Upgrade to a new release now requires a clean repository before any change, and leaves how to get there to you, since the check used to sit among the install's steps, where Claude could see something uncommitted and install anyway. In the operator's Claude context, a change that bears on safety, the upgrade skill (`template/claude/skills/upgrade/SKILL.md`) has Claude stop until `git status` lists nothing, then name what the upgrade involves and ask whether to go ahead.
24
+ - The Template page's sample email no longer sits on a gray stage with rounded corners and a shadow, which read as though an email arrives as a card. It and Settings' sample now have the real email's margins, and its width toggle reads Desktop | Phone, as Reader pages' does, in place of 640 / 375.
25
+ - Troubleshooting now covers `claude mcp login` failing with `Dynamic Client Registration rejected (HTTP 404)`: nothing on the instance is wrong, and signing in through `/mcp` inside Claude Code connects.
26
+
27
+ ### Fixed
28
+
29
+ - A reader who opens a public address that doesn't exist now gets a "No page here" page in the publication's own look with a 404, not raw JSON. You can see it under Settings → Reader pages. A public page's address now works with a trailing slash, so a shared `/subscribe/` opens the subscribe page. `/api` paths, and anything that doesn't ask for a page, still answer JSON.
30
+ - The editor on a deployed instance no longer sends a request for a local development token that the instance can't answer, so a load no longer adds that error to its console.
31
+ - A WebP post image now gets its width and height, so its email states a size as a PNG's or JPEG's does; one uploaded earlier has none until it's deleted and uploaded again. A post image whose file says it has no size is refused with a message saying to export it again, where it used to answer a server error or be stored and not load (SPEC §4).
32
+ - The link icon beside a heading in the API reference now shows in dark mode, where it was black on a near-black page.
33
+
34
+ ## [1.21.0] - 2026-11-06
35
+
36
+ **Upgrading from 1.20.0.** No database migrations, so `npm run sync-migrations` brings in nothing and there's nothing to apply. In your instance's repository, start with nothing uncommitted, as [Upgrade to a new release](https://github.com/kurtbruns/kestrel/blob/v1.21.0/docs/guides/07-upgrade.md) now says, and install the release with `npm install @kurtbruns/kestrel@1.21.0`. If your `package.json` holds wrangler below 4.156.0, with an exact version or a `~` range, npm refuses that with `ERESOLVE`, so run `npm install @kurtbruns/kestrel@1.21.0 "wrangler@^4.156.0"` instead. Run `npm run check-context`: this release changes two files of the operator's Claude context, `CLAUDE.md` and the upgrade skill, the skill's change bearing on safety, and a copy you've edited is merged by hand. If a script of yours uploads images or a logo, make it send each file's real type and a name that matches it before you deploy (see Breaking). Then run `npm run typecheck`, commit the upgrade, note a restore point, and deploy with `npx wrangler deploy --env production`, as the guide lays out. If you alert on `error` in your logs, alert on `d1.unavailable` repeating as well, since a database outage can now show only as that warning. If you use Claude Code, update it to v2.1.172 or later, or its changes read "via the Kestrel CLI" and the dashboard can stop saying Claude is connected.
37
+
38
+ ### Added
39
+
40
+ - Each section of Settings has its own address, such as `#/settings/notifications`, and the dashboard's failed-notification line, the Get notified step, the test notification's Open Settings link, and the sections the Docs tab's pages name now open the section they mean.
41
+ - A new log line, `mcp.initialize` (info), records the name and version a connector says it is each time it opens. Nothing a call records changes, so a change made through the connector is still recorded as `claude` (SPEC §12).
42
+ - You can drop an image on the logo under Settings → Publication identity to upload it, the same as choosing it in the file picker, and Name your publication now says what format, size, and shape a logo works best in. One logo change now runs at a time: a pick, a drop, or Remove while another is saving is refused, with a message to try again once it's done.
43
+
44
+ ### Changed
45
+
46
+ - Kestrel now needs wrangler 4.156.0 or later, which carries fixes for security advisories in the local runtime it bundles. A new instance starts on it, and installing this release moves an existing instance's wrangler up on its own, with no change to its `package.json`, unless that `package.json` holds it below 4.156.0 with an exact version or a `~` range.
47
+ - Upgrade to a new release now starts from a clean repository, with anything `git status` lists committed on its own or moved out first, and the operator's Claude context (`template/claude/skills/upgrade/SKILL.md`) has Claude ask before committing it and otherwise wait. This bears on safety: unrelated work swept into the upgrade's commit would be undone by going back. The same page and Deploy the app now say that the migration command, run by Claude or in any other shell that isn't interactive, applies migrations without asking.
48
+ - The operator's Claude context (`template/claude/CLAUDE.md`) gives a corrected reason for asking for an image's original file: in the Claude desktop app a pasted image can be uploaded, but it's a converted copy under a generated name, not the file the person had. The advice to ask for the original is unchanged.
49
+ - A brief D1 stall the next sweep tick makes up for now logs one `d1.unavailable` line at `warn`, not `error` lines and a failed invocation, and a failure the sweep catches now carries a `stack` field (SPEC §12).
50
+ - The setup guide's Connect Amazon SES and Sending-domain DNS pages now say that `p=none` only monitors, so it doesn't stop anyone sending as your domain, and that DMARC reports (`rua`) are optional, and the optional step that forwards reports now has you check that they arrive.
51
+ - The setup guide's Notifications through Cloudflare's email page now shows the `send_email` binding in place in `env.production`, with only its name, so no notifications address is committed to your repository. It also says how the dashboard shows the DNS records once they're added, no longer promises a DKIM record for the hostname, points to Email Routing's **Activity log** for a notification's status, and has you check the spam folder for the test. An instance set up from the old page, with addresses in its binding, keeps working.
52
+ - An update to the Markdown parser can change a new render in a few edge cases: a link with an unbalanced parenthesis, such as `[a](https://example.com/foo(bar)`, now shows as typed instead of as a link that lost its last parenthesis; a numeric character reference such as `&#8212;` shows as its character in the plain-text part too; `www.` inside a word no longer links; and lazy lines after a blockquote, a `#` with no space in a list item, and HTML right after a paragraph can land in a different block. A post already sent or scheduled keeps its email, unless a template or identity change re-makes a scheduled one (SPEC §6).
53
+ - On a phone, the text and images in a post's email sit closer to the edges, in mail apps that support it, such as Mail on iPhone; other apps and wide displays show what they did. This applies to emails made from now on and their post pages: a post already sent or scheduled keeps its email and page, unless a template or identity change re-makes a scheduled one (SPEC §6).
54
+ - The confirmation email now looks like a post's email, with the same white page, column, and margins on every display, in place of a card on a gray page. It's made when it's sent, so this applies as soon as you deploy. Its preview in Settings shows it the same way.
55
+
56
+ ### Fixed
57
+
58
+ - `kestrel api` now names Claude Code only when Claude Code runs it, where it named Claude Code whoever ran it. Run any other way, by you in a terminal or by a script of yours that doesn't pass `--client`, its changes read "via the Kestrel CLI" in the editor and its client is `kestrel-cli`. So do Claude Code's own changes on a version older than v2.1.172, and the dashboard can stop saying Claude is connected. Changes already recorded keep the name they have.
59
+ - The API reference now says what each client name read back as `client` or `author_client` means (`editor`, `claude-code`, `kestrel-cli`, and `claude`, the name a change made through the connector is recorded under), so Claude can tell a publisher who scheduled a send; it also says no client of Kestrel's but the web editor claims `editor`.
60
+ - `npx kestrel api ... --form file=@cover.png` uploads a post image or a logo, where every file was refused as not a PNG, JPEG, WebP, or GIF: it now states the file's type, read from its extension.
61
+ - A `HEAD` request now answers like the `GET` for the same path, without the body, where every path answered 404: an uptime monitor pointed at `/health` no longer reports the instance down, and link checkers and `curl -I` see pages and images as they are.
62
+ - A send to one person now reads "1 recipient", not "1 recipients", on its scheduled card on the dashboard and the Sent page, and on its page while sending or held and once sent.
63
+ - A reader's confirm or unsubscribe token, and a post's unlisted link, no longer reach Cloudflare's invocation log in the `Referer` header their browser sent from those pages. Reader pages, post pages included, now tell the browser to send no referrer, so a site a post page links to also stops seeing where the visit came from. Lines already stored age out with log retention (SPEC §5, §12).
64
+
65
+ ### Breaking
66
+
67
+ - Image and logo uploads read a file's format from its bytes (SPEC §4). A file that isn't a PNG, JPEG, WebP, or GIF is refused even under an image's name, where it used to upload and show as a broken image, and so is one named or declared as a different one of those formats than it is, like a JPEG called `photo.png`, with a message saying what it really is. So a script that always sends `--type image/png`, or always names its uploads `cover.png`, is now refused on any file of another format.
68
+
13
69
  ## [1.20.0] - 2026-11-04
14
70
 
15
71
  **Upgrading from 1.19.0.** In your instance's repository, run `npm run sync-migrations`, which brings in one database migration, `0021_notifications_outage_receipts`: it rebuilds the notifications table for the new notices, carrying every row over, and closes any import notice not yet sent, since this release sends none. Run `npm run check-context`: this release changes the operator's Claude context, and a copy you've edited is merged by hand. Apply the migration before you deploy, with `npx wrangler d1 migrations apply DB --remote --env production` (and `--local` for local development). Then deploy with `npx wrangler deploy --env production`. A script of yours that replaced an image by uploading its name again needs changing (see Breaking); nothing else does.
@@ -762,7 +818,9 @@ The first tagged release: a self-contained newsletter app on a Cloudflare Worker
762
818
  - A build-version stamp: every instance reports its version, commit, and build time, read-only in the editor and at `GET /api/version`.
763
819
  - This changelog and a semantic-versioning release process.
764
820
 
765
- [Unreleased]: https://github.com/kurtbruns/kestrel/compare/v1.20.0...HEAD
821
+ [Unreleased]: https://github.com/kurtbruns/kestrel/compare/v1.22.0...HEAD
822
+ [1.22.0]: https://github.com/kurtbruns/kestrel/releases/tag/v1.22.0
823
+ [1.21.0]: https://github.com/kurtbruns/kestrel/releases/tag/v1.21.0
766
824
  [1.20.0]: https://github.com/kurtbruns/kestrel/releases/tag/v1.20.0
767
825
  [1.19.0]: https://github.com/kurtbruns/kestrel/releases/tag/v1.19.0
768
826
  [1.18.0]: https://github.com/kurtbruns/kestrel/releases/tag/v1.18.0
package/bin/kestrel.mjs CHANGED
@@ -36,7 +36,7 @@ Usage: kestrel <command> [options]
36
36
 
37
37
  login <url> [--no-browser] sign in to a deployed instance as you, in your browser
38
38
  logout [url] sign out of it on this computer
39
- api <method> <path> [opts] call the API as you, via Claude Code (kestrel api for options)
39
+ api <method> <path> [opts] call the API as you (kestrel api for options)
40
40
  api --list | help <m> <path> list the instance's routes, or describe one
41
41
 
42
42
  help, --help show this
package/cli/seed.mjs CHANGED
@@ -22,6 +22,7 @@ import { readdir, readFile } from "node:fs/promises";
22
22
  import { dirname, join } from "node:path";
23
23
  import { fileURLToPath } from "node:url";
24
24
  import { devTarget, fail, parseArgs } from "../packages/kestrel/lib/dev-api.mjs";
25
+ import { fileMediaType } from "../packages/kestrel/lib/media-types.mjs";
25
26
 
26
27
  // The demo ships beside the CLI, in this repository and in the package.
27
28
  const root = join(dirname(fileURLToPath(import.meta.url)), "..");
@@ -43,13 +44,6 @@ export async function seedDemo(base, token, { size, seed } = {}, tag = "seed") {
43
44
  const url = `${base}/api/dev/seed${qs ? `?${qs}` : ""}`;
44
45
 
45
46
  const form = new FormData();
46
- const CONTENT_TYPE = {
47
- ".webp": "image/webp",
48
- ".jpg": "image/jpeg",
49
- ".jpeg": "image/jpeg",
50
- ".png": "image/png",
51
- ".gif": "image/gif",
52
- };
53
47
  // The images are the files in `demo/` that the demo refers to, so those files are the only
54
48
  // place to change them. The worker parses the posts and front matter for everything else.
55
49
  const demoDir = join(root, "demo");
@@ -65,7 +59,7 @@ export async function seedDemo(base, token, { size, seed } = {}, tag = "seed") {
65
59
  console.warn(`[${tag}] no demo/${rel} — seeding without the ${what}.`);
66
60
  return;
67
61
  }
68
- const type = CONTENT_TYPE[rel.slice(rel.lastIndexOf("."))] || "application/octet-stream";
62
+ const type = fileMediaType(rel);
69
63
  form.append(field, new Blob([await readFile(path)], { type }), name);
70
64
  };
71
65