@kurtbruns/kestrel 1.17.0 → 1.19.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 (45) hide show
  1. package/.dev.vars.example +24 -2
  2. package/CHANGELOG.md +73 -1
  3. package/bin/kestrel.mjs +1 -1
  4. package/cli/dev.mjs +3 -3
  5. package/cli/init.mjs +98 -4
  6. package/dist/public/dashboard/app.js +327 -327
  7. package/dist/public/dashboard/app.js.map +4 -4
  8. package/dist/public/dashboard/index.html +6 -3
  9. package/dist/public/dashboard/styles.css +133 -21
  10. package/dist/worker/index.js +5940 -4434
  11. package/docs/README.md +5 -3
  12. package/docs/get-started/02-deploy.md +25 -21
  13. package/docs/get-started/03-access.md +20 -8
  14. package/docs/get-started/04-aws-account.md +68 -0
  15. package/docs/get-started/05-ses.md +326 -0
  16. package/docs/get-started/06-production-access.md +67 -0
  17. package/docs/get-started/{05-verify.md → 07-verify.md} +16 -26
  18. package/docs/get-started/{06-go-live.md → 08-go-live.md} +4 -2
  19. package/docs/guides/01-allow-claude.md +2 -2
  20. package/docs/guides/02-automate.md +1 -1
  21. package/docs/guides/03-notifications.md +12 -12
  22. package/docs/guides/07-upgrade.md +4 -4
  23. package/docs/guides/09-as-your-list-grows.md +8 -18
  24. package/docs/publishing/02-sending-rules.md +2 -2
  25. package/docs/publishing/03-name-your-publication.md +2 -0
  26. package/docs/publishing/04-write-a-post.md +1 -1
  27. package/docs/publishing/06-troubleshooting.md +37 -9
  28. package/docs/publishing/08-connect-claude.md +1 -1
  29. package/docs/publishing/09-share-your-subscribe-link.md +3 -1
  30. package/docs/publishing/10-check-your-setup.md +15 -9
  31. package/docs/publishing/README.md +8 -3
  32. package/docs/reference/01-configuration.md +18 -16
  33. package/docs/reference/02-sending-domain-dns.md +4 -4
  34. package/migrations/0015_provider_account.sql +18 -0
  35. package/migrations/0016_held_receipts.sql +26 -0
  36. package/migrations/0017_provider_account_rejected.sql +24 -0
  37. package/migrations/0018_notify_addresses.sql +13 -0
  38. package/migrations/0019_sending.sql +67 -0
  39. package/migrations/0020_notifications_not_sending.sql +40 -0
  40. package/package.json +1 -1
  41. package/packages/kestrel/lib/api.mjs +28 -3
  42. package/packages/kestrel/lib/oauth.mjs +5 -3
  43. package/template/claude/CLAUDE.md +5 -3
  44. package/template/wrangler.jsonc +6 -6
  45. package/docs/get-started/04-ses.md +0 -310
@@ -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=2dbe22b0">
8
+ <link rel="stylesheet" href="./styles.css?v=6dc6276a">
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
@@ -18,7 +18,10 @@
18
18
  })();
19
19
  </script>
20
20
  </head>
21
- <body>
21
+ <!-- The body starts signed out, with the publication's chrome hidden, until the boot probe
22
+ (/api/whoami) answers: a session reveals it, and a wall keeps it hidden, so a locked
23
+ editor never flashes the sidebar first. -->
24
+ <body class="signed-out">
22
25
  <!-- Sidebar icon sprite. The primary nav shows icon + label at full width; in the
23
26
  collapsed icon rail (the first step down from the full sidebar) the labels fold
24
27
  to hover tooltips and only these marks remain. Kept out of flow and hidden from
@@ -113,6 +116,6 @@
113
116
  </section>
114
117
  <div id="toasts" role="status" aria-live="polite"></div>
115
118
 
116
- <script src="./app.js?v=abd16d74"></script>
119
+ <script src="./app.js?v=b64bbd22"></script>
117
120
  </body>
118
121
  </html>
@@ -4767,47 +4767,149 @@ button.copy-btn.api-copy-path {
4767
4767
  }
4768
4768
 
4769
4769
  /* Setup steps: the first-run checklist, and the regular dashboard's Finish setting up card
4770
- (one component, two layouts). The card is the compact one. */
4770
+ (one component, two layouts). The card is the compact one. Finished steps fold into one
4771
+ line above the list, behind a solid check; each step left carries a small ring beside its
4772
+ title that says where it stands: plain to take, dashed while AWS has it, dotted while it
4773
+ waits on another step, solid red while a problem above is in its way. */
4771
4774
  .setup-title {
4772
4775
  margin-top: 0;
4773
4776
  }
4777
+ /* The done steps' toggle: one "N done" line that shows them in place in the list, and hides
4778
+ them again. A plain text control, so it reads as a line of the card, not a button. */
4779
+ .setup-fold {
4780
+ display: inline-flex;
4781
+ align-items: center;
4782
+ gap: 12px;
4783
+ margin: 10px 0 2px;
4784
+ padding: 2px;
4785
+ border: 0;
4786
+ background: none;
4787
+ color: var(--fg);
4788
+ font: inherit;
4789
+ font-size: 13px;
4790
+ font-weight: 600;
4791
+ cursor: pointer;
4792
+ border-radius: 6px;
4793
+ }
4794
+ .setup-fold:hover {
4795
+ background: none;
4796
+ color: var(--fg);
4797
+ }
4798
+ .setup-fold:focus-visible {
4799
+ outline: 2px solid var(--ring);
4800
+ outline-offset: 2px;
4801
+ }
4802
+ /* A chevron that points down while the done steps are hidden and up while they show. */
4803
+ .setup-fold::after {
4804
+ content: "";
4805
+ width: 6px;
4806
+ height: 6px;
4807
+ margin: -3px 0 0 -4px;
4808
+ border-right: 1.5px solid var(--muted);
4809
+ border-bottom: 1.5px solid var(--muted);
4810
+ transform: rotate(45deg);
4811
+ }
4812
+ .setup-fold[aria-expanded="true"]::after {
4813
+ margin-top: 3px;
4814
+ transform: rotate(-135deg);
4815
+ }
4774
4816
  .setup-steps {
4775
4817
  list-style: none;
4776
- counter-reset: step;
4777
4818
  margin: 8px 0 0;
4778
4819
  padding: 0;
4779
4820
  }
4821
+ /* Every row is as tall as one with a button, so a row with only a title, or no control, doesn't
4822
+ read as squeezed beside its neighbours. */
4780
4823
  .setup-steps li {
4781
- counter-increment: step;
4782
4824
  display: flex;
4783
4825
  align-items: center;
4784
- gap: 14px;
4826
+ gap: 12px;
4827
+ min-height: 59px;
4828
+ box-sizing: border-box;
4785
4829
  padding: 12px 2px;
4786
4830
  border-top: 1px solid var(--line);
4787
4831
  }
4788
4832
  .setup-steps li:first-child {
4789
4833
  border-top: 0;
4790
4834
  }
4835
+ /* The mark: beside the title's first line, whatever the hint below it. */
4836
+ .setup-fold-mark,
4791
4837
  .setup-steps li::before {
4792
- content: counter(step);
4793
4838
  flex: 0 0 auto;
4794
- width: 26px;
4795
- height: 26px;
4839
+ align-self: flex-start;
4840
+ box-sizing: border-box;
4841
+ width: 16px;
4842
+ height: 16px;
4843
+ margin-top: 2px;
4796
4844
  display: inline-flex;
4797
4845
  align-items: center;
4798
4846
  justify-content: center;
4799
4847
  border-radius: 999px;
4800
- border: 1px solid var(--line);
4801
- color: var(--muted);
4802
- font-size: 13px;
4803
- font-variant-numeric: tabular-nums;
4848
+ font-size: 10px;
4849
+ font-weight: 700;
4850
+ line-height: 1;
4851
+ }
4852
+ .setup-fold-mark {
4853
+ margin-top: 0;
4854
+ align-self: center;
4855
+ }
4856
+ .setup-steps li::before {
4857
+ content: "";
4858
+ border: 1.5px solid var(--muted);
4804
4859
  }
4805
- /* A step already done (the list has subscribers): a check in place of its number. */
4860
+ /* A step with only a title: the mark sits level with it, as the title does with its button. */
4861
+ .setup-steps li:not(:has(.setup-hint))::before {
4862
+ align-self: center;
4863
+ margin-top: 0;
4864
+ }
4865
+ /* Done: a solid check, in the fold's line and on each step it opens to. */
4866
+ .setup-fold-mark,
4806
4867
  .setup-steps li.setup-done::before {
4807
4868
  content: "✓";
4808
- color: var(--ok-fg);
4809
- border-color: var(--ok-line);
4810
- background: var(--ok-bg);
4869
+ border: 0;
4870
+ background: var(--ok-fg);
4871
+ color: var(--card);
4872
+ }
4873
+ /* Waiting on AWS, not on the publisher. */
4874
+ .setup-steps li.setup-waiting::before {
4875
+ border-style: dashed;
4876
+ border-color: var(--accent);
4877
+ }
4878
+ /* Can't be taken until an earlier step is: muted, its hint the step it waits for. Its ring is
4879
+ ten even dashes drawn by an SVG mask over the muted color, since a dotted border this small
4880
+ renders as faint specks that vary with the screen. */
4881
+ .setup-steps li.setup-blocked::before {
4882
+ border: 0;
4883
+ background: var(--muted);
4884
+ -webkit-mask:
4885
+ url("data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'><circle cx='8' cy='8' r='7.25' fill='none' stroke='black' stroke-width='1.5' stroke-dasharray='2.4 2.155'/></svg>")
4886
+ center / 100% 100% no-repeat;
4887
+ mask:
4888
+ url("data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'><circle cx='8' cy='8' r='7.25' fill='none' stroke='black' stroke-width='1.5' stroke-dasharray='2.4 2.155'/></svg>")
4889
+ center / 100% 100% no-repeat;
4890
+ }
4891
+ .setup-steps li.setup-blocked strong {
4892
+ color: var(--muted);
4893
+ font-weight: 500;
4894
+ }
4895
+ /* A problem the health block states is in its way. */
4896
+ .setup-steps li.setup-attention::before {
4897
+ content: "!";
4898
+ color: var(--danger-on);
4899
+ background: var(--danger-fg);
4900
+ border-color: var(--danger-fg);
4901
+ }
4902
+ .setup-steps li.setup-attention .setup-hint {
4903
+ color: var(--danger-fg);
4904
+ }
4905
+ /* A failed notification is an amber health line, so the step pointing at it is amber too. */
4906
+ .setup-steps li.setup-notify.setup-attention::before {
4907
+ background: var(--warn-fg);
4908
+ border-color: var(--warn-fg);
4909
+ color: var(--card);
4910
+ }
4911
+ .setup-steps li.setup-notify.setup-attention .setup-hint {
4912
+ color: var(--warn-fg);
4811
4913
  }
4812
4914
  .setup-step-main {
4813
4915
  flex: 1 1 auto;
@@ -4847,13 +4949,9 @@ button.copy-btn.api-copy-path {
4847
4949
  font-size: 16px;
4848
4950
  }
4849
4951
  .setup-card .setup-steps li {
4952
+ min-height: 51px;
4850
4953
  padding: 8px 2px;
4851
4954
  }
4852
- .setup-card .setup-steps li::before {
4853
- width: 22px;
4854
- height: 22px;
4855
- font-size: 12px;
4856
- }
4857
4955
 
4858
4956
  /* ============================================================
4859
4957
  Settings page. Grouped by intent, made visible: each section
@@ -5338,7 +5436,21 @@ button.set-field-remove {
5338
5436
  color: var(--ok-fg);
5339
5437
  border-color: var(--ok-line);
5340
5438
  }
5341
- /* A notification channel that has stopped delivering: the one red in the settings reflection. */
5439
+ /* SES still in its sandbox: worth a look, so amber (DESIGN §8). */
5440
+ .set-pill.warn {
5441
+ background: var(--warn-bg);
5442
+ color: var(--warn-fg);
5443
+ border-color: var(--warn-line);
5444
+ }
5445
+ /* Words in the settings reflection that are worth a look: the sandbox's limit, a denied review, a warning. */
5446
+ .settings .set-warn {
5447
+ color: var(--warn-fg);
5448
+ }
5449
+ /* A provider warning that stops every email: red, beside the amber ones (DESIGN §8). */
5450
+ .settings .set-error {
5451
+ color: var(--danger-fg);
5452
+ }
5453
+ /* A notification channel that has stopped delivering: red in the settings reflection. */
5342
5454
  .set-pill.danger {
5343
5455
  background: var(--danger-bg);
5344
5456
  color: var(--danger-fg);