@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.
- package/CHANGELOG.md +69 -1
- package/bin/kestrel.mjs +1 -1
- package/cli/seed.mjs +2 -8
- package/dist/public/dashboard/app.js +307 -306
- package/dist/public/dashboard/app.js.map +4 -4
- package/dist/public/dashboard/index.html +2 -2
- package/dist/public/dashboard/styles.css +90 -51
- package/dist/worker/index.js +890 -559
- package/docs/get-started/02-deploy.md +3 -3
- package/docs/get-started/03-access.md +8 -4
- package/docs/get-started/04-aws-account.md +2 -0
- package/docs/get-started/05-ses.md +4 -2
- package/docs/get-started/07-verify.md +8 -6
- package/docs/get-started/08-go-live.md +12 -18
- package/docs/guides/01-allow-claude.md +7 -2
- package/docs/guides/03-notifications.md +17 -13
- package/docs/guides/07-upgrade.md +16 -2
- package/docs/publishing/03-name-your-publication.md +8 -0
- package/docs/publishing/04-write-a-post.md +2 -2
- package/docs/publishing/06-troubleshooting.md +52 -26
- package/docs/publishing/11-email-template.md +1 -1
- package/docs/publishing/README.md +1 -1
- package/docs/reference/02-sending-domain-dns.md +3 -3
- package/migrations/0021_notifications_outage_receipts.sql +46 -0
- package/package.json +10 -10
- package/packages/kestrel/lib/api.mjs +19 -12
- package/packages/kestrel/lib/login.mjs +2 -2
- package/packages/kestrel/lib/media-types.mjs +34 -0
- package/packages/kestrel/lib/oauth.mjs +19 -2
- package/packages/kestrel/lib/openapi.mjs +1 -1
- package/template/README.md +1 -1
- package/template/claude/CLAUDE.md +1 -1
- 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=
|
|
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=
|
|
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
|
-
|
|
4992
|
-
|
|
4993
|
-
|
|
4994
|
-
|
|
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
|
|
5526
|
-
The
|
|
5527
|
-
(its `prefers-color-scheme` block). The
|
|
5528
|
-
|
|
5529
|
-
theme and
|
|
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
|
-
|
|
5532
|
-
|
|
5567
|
+
border-top: 1px solid var(--line);
|
|
5568
|
+
background: #ffffff;
|
|
5569
|
+
padding: 8px 12px 48px;
|
|
5533
5570
|
}
|
|
5534
|
-
.set-ce-
|
|
5535
|
-
max-width:
|
|
5571
|
+
.set-ce-col {
|
|
5572
|
+
max-width: 664px;
|
|
5536
5573
|
margin: 0 auto;
|
|
5537
|
-
|
|
5538
|
-
|
|
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
|
|
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
|
|
5660
|
-
|
|
5661
|
-
|
|
5662
|
-
|
|
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-
|
|
5669
|
-
background: #232327;
|
|
5708
|
+
.set-ce-col {
|
|
5670
5709
|
color: #ededed;
|
|
5671
5710
|
}
|
|
5672
5711
|
.set-ce-mast {
|