@kurtbruns/kestrel 1.19.0 → 1.21.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.
Files changed (33) hide show
  1. package/CHANGELOG.md +69 -1
  2. package/bin/kestrel.mjs +1 -1
  3. package/cli/seed.mjs +2 -8
  4. package/dist/public/dashboard/app.js +307 -306
  5. package/dist/public/dashboard/app.js.map +4 -4
  6. package/dist/public/dashboard/index.html +2 -2
  7. package/dist/public/dashboard/styles.css +90 -51
  8. package/dist/worker/index.js +890 -559
  9. package/docs/get-started/02-deploy.md +3 -3
  10. package/docs/get-started/03-access.md +8 -4
  11. package/docs/get-started/04-aws-account.md +2 -0
  12. package/docs/get-started/05-ses.md +4 -2
  13. package/docs/get-started/07-verify.md +8 -6
  14. package/docs/get-started/08-go-live.md +12 -18
  15. package/docs/guides/01-allow-claude.md +7 -2
  16. package/docs/guides/03-notifications.md +17 -13
  17. package/docs/guides/07-upgrade.md +16 -2
  18. package/docs/publishing/03-name-your-publication.md +8 -0
  19. package/docs/publishing/04-write-a-post.md +2 -2
  20. package/docs/publishing/06-troubleshooting.md +52 -26
  21. package/docs/publishing/11-email-template.md +1 -1
  22. package/docs/publishing/README.md +1 -1
  23. package/docs/reference/02-sending-domain-dns.md +3 -3
  24. package/migrations/0021_notifications_outage_receipts.sql +46 -0
  25. package/package.json +10 -10
  26. package/packages/kestrel/lib/api.mjs +19 -12
  27. package/packages/kestrel/lib/login.mjs +2 -2
  28. package/packages/kestrel/lib/media-types.mjs +34 -0
  29. package/packages/kestrel/lib/oauth.mjs +19 -2
  30. package/packages/kestrel/lib/openapi.mjs +1 -1
  31. package/template/README.md +1 -1
  32. package/template/claude/CLAUDE.md +1 -1
  33. package/template/claude/skills/upgrade/SKILL.md +1 -0
@@ -5,7 +5,7 @@
5
5
  <meta name="viewport" content="width=device-width, initial-scale=1">
6
6
  <title>Kestrel</title>
7
7
  <link rel="icon" href="/favicon.svg">
8
- <link rel="stylesheet" href="./styles.css?v=6dc6276a">
8
+ <link rel="stylesheet" href="./styles.css?v=aacdaf66">
9
9
  <script>
10
10
  // Sidebar mode guard — set the width-based data-nav on <html> before first paint so the
11
11
  // sidebar renders in its final width with no flash. The collapse toggle's override is
@@ -116,6 +116,6 @@
116
116
  </section>
117
117
  <div id="toasts" role="status" aria-live="polite"></div>
118
118
 
119
- <script src="./app.js?v=b64bbd22"></script>
119
+ <script src="./app.js?v=e3dbd207"></script>
120
120
  </body>
121
121
  </html>
@@ -2,7 +2,13 @@
2
2
  prefers-color-scheme (see the dark block below). This is the admin surface's own
3
3
  palette — independent of, and never loaded alongside, the reader/email-card
4
4
  `TOKENS` in src/lib/page.ts. The two reuse names like --muted and --bg with
5
- different values on purpose; don't assume they share meaning. */
5
+ different values on purpose; don't assume they share meaning.
6
+
7
+ Specificity, not source order, decides this file's cascade, so rules stay grouped by
8
+ component. That is why Biome's noDescendingSpecificity is off in biome.json, which takes no
9
+ comments: it compares selectors by their last element alone, so `#settingsBtn svg` outranks
10
+ every later `svg` rule, components that never nest included, and satisfying it would mean
11
+ moving rules out of their sections, changing the cascade to fix nothing. */
6
12
  :root {
7
13
  color-scheme: light dark;
8
14
  /* Layering — one named z-index ladder (DESIGN §9). Every fixed or floating surface
@@ -1197,7 +1203,6 @@ body.tool-mode .content {
1197
1203
  }
1198
1204
  }
1199
1205
 
1200
- /* biome-ignore lint/style/noDescendingSpecificity: specificity (not source order) decides the cascade; base rule grouped here intentionally */
1201
1206
  button {
1202
1207
  font-family: inherit;
1203
1208
  font-size: 14px;
@@ -1524,7 +1529,6 @@ td a {
1524
1529
  text-decoration: none;
1525
1530
  font-weight: 500;
1526
1531
  }
1527
- /* biome-ignore lint/style/noDescendingSpecificity: specificity (not source order) decides the cascade; hover rule grouped with its base */
1528
1532
  td a:hover {
1529
1533
  text-decoration: underline;
1530
1534
  }
@@ -1942,6 +1946,24 @@ th.num .th-sort {
1942
1946
  cursor: default;
1943
1947
  }
1944
1948
 
1949
+ /* A labelled block (a code block, or the API reference's query and content-type tables):
1950
+ its label line, then the block. Ahead of the sections below, which refine it. */
1951
+ .block {
1952
+ margin-top: 10px;
1953
+ }
1954
+ .block-head {
1955
+ display: flex;
1956
+ align-items: center;
1957
+ justify-content: space-between;
1958
+ margin: 8px 0 4px;
1959
+ }
1960
+ .block-label {
1961
+ display: block;
1962
+ font-size: 12px;
1963
+ font-weight: 600;
1964
+ color: var(--muted);
1965
+ }
1966
+
1945
1967
  /* A route's or an object's sections (query, body, returns, refusals, fields) as Stripe's and
1946
1968
  Mux's references set theirs: a heading over a rule, then unboxed rows, each with the name,
1947
1969
  its type, and Required on one line and what it means under it, so a long name never
@@ -4148,6 +4170,11 @@ button.api-filter-clear svg {
4148
4170
  max-width: 1440px;
4149
4171
  line-height: 1.5;
4150
4172
  }
4173
+ /* A resource: its heading over a rule, scrolling with its routes. */
4174
+ .api-res {
4175
+ margin-top: 16px;
4176
+ scroll-margin-top: var(--bar-h);
4177
+ }
4151
4178
  /* One reading measure for the page, a doc page's (720px): the intro, the topics, and each
4152
4179
  route's and object's prose and tables keep it, and only a route's examples, beside its
4153
4180
  prose on a wide screen, reach past it. */
@@ -4192,11 +4219,6 @@ button.api-filter-clear svg {
4192
4219
  margin: 0 0 10px;
4193
4220
  font-size: 14px;
4194
4221
  }
4195
- /* A resource: its heading over a rule, scrolling with its routes. */
4196
- .api-res {
4197
- margin-top: 16px;
4198
- scroll-margin-top: var(--bar-h);
4199
- }
4200
4222
  /* A resource's heading, set like the page's other headings, flush with the text: it only
4201
4223
  pins under the bar, its vertical padding the band the rows scroll beneath. */
4202
4224
  .api-res-head {
@@ -4947,6 +4969,10 @@ button.copy-btn.api-copy-path {
4947
4969
  }
4948
4970
  .setup-card .setup-title {
4949
4971
  font-size: 16px;
4972
+ margin-bottom: 4px;
4973
+ }
4974
+ .setup-note {
4975
+ margin: 0;
4950
4976
  }
4951
4977
  .setup-card .setup-steps li {
4952
4978
  min-height: 51px;
@@ -4988,20 +5014,10 @@ button.copy-btn.api-copy-path {
4988
5014
  }
4989
5015
 
4990
5016
  /* --- copying text out of the app (DESIGN §7, ui/copy.ts) ---
4991
- A labelled block (a code block, or the API reference's query and content-type tables):
4992
- its label line, then the block. The Copy is a control outside the five roles: the word
4993
- and its glyph, no fill or border, quiet on the line it shares; the glyph turns to a
4994
- check in the ok hue for a moment once a copy has landed, and the word stays. A Copy
4995
- beside a single value (an address) is the same control. --- */
4996
- .block {
4997
- margin-top: 10px;
4998
- }
4999
- .block-head {
5000
- display: flex;
5001
- align-items: center;
5002
- justify-content: space-between;
5003
- margin: 8px 0 4px;
5004
- }
5017
+ The Copy is a control outside the five roles: the word and its glyph, no fill or border,
5018
+ quiet on the line it shares; the glyph turns to a check in the ok hue for a moment once a
5019
+ copy has landed, and the word stays. A Copy beside a single value (an address) is the same
5020
+ control. --- */
5005
5021
  button.copy-btn {
5006
5022
  display: inline-flex;
5007
5023
  align-items: center;
@@ -5033,12 +5049,6 @@ button.copy-btn svg {
5033
5049
  button.copied svg {
5034
5050
  color: var(--ok-fg);
5035
5051
  }
5036
- .block-label {
5037
- display: block;
5038
- font-size: 12px;
5039
- font-weight: 600;
5040
- color: var(--muted);
5041
- }
5042
5052
  /* A code block (an example, a curl command, a template): a raised card with its label and Copy
5043
5053
  in a header inside it, so the code stands apart from the page in both themes. */
5044
5054
  .code-block {
@@ -5118,6 +5128,13 @@ button.copied svg {
5118
5128
  align-items: center;
5119
5129
  gap: 12px;
5120
5130
  margin: 0 0 14px;
5131
+ scroll-margin-top: 20px; /* a link to the section leaves its title room at the top */
5132
+ }
5133
+ @media (max-width: 539px) {
5134
+ /* The phone's floating menu button, 52px tall from the top, would cover the title. */
5135
+ .set-sec-head {
5136
+ scroll-margin-top: 60px;
5137
+ }
5121
5138
  }
5122
5139
  .set-sec-title {
5123
5140
  margin: 0;
@@ -5230,6 +5247,7 @@ button.copied svg {
5230
5247
  min-width: 0;
5231
5248
  }
5232
5249
  .set-logo-tile {
5250
+ position: relative;
5233
5251
  width: 100%;
5234
5252
  aspect-ratio: 1;
5235
5253
  border-radius: 10px;
@@ -5249,6 +5267,22 @@ button.copied svg {
5249
5267
  .set-logo-tile:hover {
5250
5268
  border-color: var(--accent);
5251
5269
  }
5270
+ .set-logo-tile.dragover::after {
5271
+ content: "Drop to upload";
5272
+ position: absolute;
5273
+ inset: 0;
5274
+ display: flex;
5275
+ align-items: center;
5276
+ justify-content: center;
5277
+ padding: 8px;
5278
+ border: 2px dashed var(--ring);
5279
+ border-radius: inherit;
5280
+ background: color-mix(in srgb, var(--ring) 14%, var(--card));
5281
+ color: var(--fg);
5282
+ font-size: 11px;
5283
+ font-weight: 600;
5284
+ text-align: center;
5285
+ }
5252
5286
  .set-logo-ph {
5253
5287
  display: flex;
5254
5288
  flex-direction: column;
@@ -5522,29 +5556,38 @@ button.set-field-remove {
5522
5556
  margin-top: 5px;
5523
5557
  }
5524
5558
 
5525
- /* --- confirmation email: the rendered card + the words-only edit body ---
5526
- The card colors are LITERALS matching src/emails/system.ts, which now ships light+dark
5527
- (its `prefers-color-scheme` block). The light literals are here; a dark @media block
5528
- below overrides them with the email's dark palette, so the preview tracks the dashboard
5529
- theme and stays byte-honest about what a light- or dark-mode inbox receives. */
5559
+ /* --- confirmation email: the rendered preview + the words-only edit body ---
5560
+ The stage and column copy the email's frame in src/emails/system.ts, and their colors are
5561
+ LITERALS matching its light and dark palettes (its `prefers-color-scheme` block). The
5562
+ light literals are here; a dark @media block below overrides them, so the preview tracks
5563
+ the dashboard theme and shows what a light- or dark-mode inbox receives. */
5564
+ /* The top border is the app's, not the email's: it keeps the inbox row above from reading as
5565
+ part of an email whose page is the same white. */
5530
5566
  .set-ce-stage {
5531
- background: #f4f4f5;
5532
- padding: 22px 18px;
5567
+ border-top: 1px solid var(--line);
5568
+ background: #ffffff;
5569
+ padding: 8px 12px 48px;
5533
5570
  }
5534
- .set-ce-card {
5535
- max-width: 440px;
5571
+ .set-ce-col {
5572
+ max-width: 664px;
5536
5573
  margin: 0 auto;
5537
- background: #ffffff;
5538
- border-radius: 8px;
5539
- padding: 28px 26px;
5540
- font-size: 15px;
5574
+ padding: 24px 32px 32px;
5575
+ font-size: 16px;
5541
5576
  line-height: 1.6;
5542
5577
  color: #18181b;
5578
+ word-break: break-word;
5579
+ }
5580
+ /* The email's narrow rule, at its width, so a phone sees what its inbox will show. */
5581
+ @media (max-width: 560px) {
5582
+ .set-ce-col {
5583
+ padding-left: 12px;
5584
+ padding-right: 12px;
5585
+ }
5543
5586
  }
5544
5587
  .set-ce-msg {
5545
5588
  margin: 0 0 20px;
5546
5589
  }
5547
- /* Publication masthead inside the preview card, matching masthead() in
5590
+ /* Publication masthead inside the preview, matching masthead() in
5548
5591
  src/emails/system.ts (logo beside name + tagline, over a hairline rule). Light
5549
5592
  literals here; the dark @media block below carries the dark overrides. */
5550
5593
  .set-ce-mast {
@@ -5634,8 +5677,6 @@ button.set-field-remove {
5634
5677
  color: #ffffff;
5635
5678
  border-radius: 6px;
5636
5679
  text-decoration: none;
5637
- font-size: 15px;
5638
- font-weight: 500;
5639
5680
  }
5640
5681
  .set-ce-foot {
5641
5682
  color: #71717a;
@@ -5656,17 +5697,15 @@ button.set-field-remove {
5656
5697
  .set-ce-editfoot .grow {
5657
5698
  flex: 1;
5658
5699
  }
5659
- /* Dark rendering of the preview card — the email's dark palette (the reader card pages'
5660
- tokens: page #18181b, card #232327, ink #ededed, muted #a1a1aa, rule #2e2e33, accent
5661
- button #e5e7eb/#18181b), matching the `prefers-color-scheme` block in
5662
- src/emails/system.ts. Overrides only the card literals; the edit body + chrome around
5663
- it already theme via the --* tokens. */
5700
+ /* Dark rendering of the preview: the email's dark palette (page #18181b, ink #ededed,
5701
+ muted #a1a1aa, rule #2e2e33, accent button #e5e7eb/#18181b), matching the
5702
+ `prefers-color-scheme` block in src/emails/system.ts. Overrides only the preview's
5703
+ literals; the edit body + chrome around it already theme via the --* tokens. */
5664
5704
  @media (prefers-color-scheme: dark) {
5665
5705
  .set-ce-stage {
5666
5706
  background: #18181b;
5667
5707
  }
5668
- .set-ce-card {
5669
- background: #232327;
5708
+ .set-ce-col {
5670
5709
  color: #ededed;
5671
5710
  }
5672
5711
  .set-ce-mast {