astroidjs 0.12.1 → 0.14.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 (136) hide show
  1. package/README.md +42 -42
  2. package/bin/astroid.mjs +35 -29
  3. package/dist/analytics/index.d.ts +3 -3
  4. package/dist/analytics/index.js +11 -11
  5. package/dist/astro/csp.d.ts +5 -5
  6. package/dist/astro/csp.js +6 -6
  7. package/dist/astro/index.js +1 -1
  8. package/dist/auth/index.d.ts +2 -2
  9. package/dist/auth/index.js +5 -5
  10. package/dist/commerce/adapters.d.ts +6 -6
  11. package/dist/commerce/adapters.js +9 -9
  12. package/dist/commerce/checkout-scaffold.d.ts +5 -5
  13. package/dist/commerce/checkout-scaffold.js +27 -27
  14. package/dist/commerce/checkout.d.ts +12 -12
  15. package/dist/commerce/checkout.js +7 -7
  16. package/dist/commerce/loader.d.ts +2 -2
  17. package/dist/commerce/loader.js +3 -3
  18. package/dist/commerce/mirror.d.ts +4 -4
  19. package/dist/commerce/mirror.js +12 -12
  20. package/dist/commerce/roles.d.ts +10 -10
  21. package/dist/commerce/roles.js +13 -13
  22. package/dist/commerce/secrets.d.ts +9 -9
  23. package/dist/commerce/secrets.js +9 -9
  24. package/dist/commerce/sync.d.ts +7 -7
  25. package/dist/commerce/sync.js +5 -5
  26. package/dist/components/sections.d.ts +9 -9
  27. package/dist/components/sections.js +12 -12
  28. package/dist/config.d.ts +89 -62
  29. package/dist/config.js +18 -18
  30. package/dist/email/inquiry.d.ts +2 -2
  31. package/dist/email/inquiry.js +1 -1
  32. package/dist/email/send.d.ts +4 -4
  33. package/dist/email/send.js +7 -7
  34. package/dist/email/templates.js +3 -3
  35. package/dist/email/theme.d.ts +1 -1
  36. package/dist/email/theme.js +4 -4
  37. package/dist/errors.d.ts +1 -1
  38. package/dist/errors.js +1 -1
  39. package/dist/index.js +1 -1
  40. package/dist/map/pmtiles.d.ts +5 -5
  41. package/dist/map/pmtiles.js +5 -5
  42. package/dist/map/scaffold.d.ts +2 -2
  43. package/dist/map/scaffold.js +8 -8
  44. package/dist/map/style.d.ts +4 -4
  45. package/dist/map/style.js +1 -1
  46. package/dist/portal/config.d.ts +3 -3
  47. package/dist/portal/config.js +11 -10
  48. package/dist/portal/guard.d.ts +4 -4
  49. package/dist/portal/guard.js +4 -4
  50. package/dist/portal/nav.js +2 -2
  51. package/dist/portal/scaffold.d.ts +4 -4
  52. package/dist/portal/scaffold.js +13 -13
  53. package/dist/portal/session.d.ts +11 -5
  54. package/dist/portal/session.js +10 -5
  55. package/dist/portfolio/scaffold.d.ts +1 -1
  56. package/dist/portfolio/scaffold.js +8 -8
  57. package/dist/project/actions.d.ts +1 -1
  58. package/dist/project/actions.js +18 -12
  59. package/dist/project/generate.d.ts +4 -4
  60. package/dist/project/generate.js +26 -26
  61. package/dist/project/index.d.ts +1 -0
  62. package/dist/project/index.js +2 -1
  63. package/dist/project/scaffold.d.ts +2 -2
  64. package/dist/project/scaffold.js +69 -13
  65. package/dist/project/seed.d.ts +22 -0
  66. package/dist/project/seed.js +214 -0
  67. package/dist/pwa/generate.d.ts +11 -11
  68. package/dist/pwa/generate.js +19 -19
  69. package/dist/queues/consumer.d.ts +3 -3
  70. package/dist/queues/consumer.js +2 -2
  71. package/dist/queues/messages.d.ts +4 -4
  72. package/dist/queues/messages.js +2 -2
  73. package/dist/queues/scaffold.d.ts +4 -4
  74. package/dist/queues/scaffold.js +17 -17
  75. package/dist/queues/webhook.d.ts +5 -5
  76. package/dist/queues/webhook.js +3 -3
  77. package/dist/realtime/scaffold.d.ts +4 -4
  78. package/dist/realtime/scaffold.js +12 -12
  79. package/dist/schema/collections.d.ts +42 -11
  80. package/dist/schema/collections.js +43 -42
  81. package/dist/schema/framework.d.ts +1 -1
  82. package/dist/schema/framework.js +2 -2
  83. package/dist/schema/generate.js +6 -6
  84. package/dist/schema/index.js +1 -1
  85. package/dist/secrets.d.ts +6 -6
  86. package/dist/secrets.js +6 -6
  87. package/dist/security/csp-origins.d.ts +1 -1
  88. package/dist/security/csp-origins.js +3 -3
  89. package/dist/security/rate-rules.d.ts +2 -2
  90. package/dist/security/rate-rules.js +8 -8
  91. package/dist/seo/resolve.d.ts +5 -5
  92. package/dist/seo/resolve.js +2 -2
  93. package/dist/seo/routes.d.ts +5 -5
  94. package/dist/seo/routes.js +3 -3
  95. package/dist/seo/structured-data.d.ts +6 -6
  96. package/dist/seo/structured-data.js +7 -7
  97. package/dist/status.d.ts +5 -5
  98. package/dist/status.js +7 -7
  99. package/dist/tenancy/index.d.ts +3 -3
  100. package/dist/tenancy/index.js +11 -11
  101. package/dist/worker/generate.d.ts +2 -2
  102. package/dist/worker/generate.js +91 -57
  103. package/dist/worker/index.js +1 -1
  104. package/dist/worker/routes.js +11 -11
  105. package/dist/workflow/advance.d.ts +3 -3
  106. package/dist/workflow/advance.js +6 -6
  107. package/dist/workflow/config.d.ts +4 -4
  108. package/dist/workflow/config.js +4 -4
  109. package/dist/workflow/generate.d.ts +2 -2
  110. package/dist/workflow/generate.js +11 -11
  111. package/package.json +3 -4
  112. package/src/components/Collection.tsx +5 -5
  113. package/src/components/Editable.astro +9 -9
  114. package/src/components/JustifiedGallery.astro +8 -8
  115. package/src/components/MediaSlot.astro +12 -12
  116. package/src/components/PortalShell.astro +4 -4
  117. package/src/components/RegisterSW.astro +3 -3
  118. package/src/components/Section.astro +8 -8
  119. package/src/components/Sections.astro +6 -6
  120. package/src/components/Seo.astro +3 -3
  121. package/src/components/StageBar.astro +3 -3
  122. package/src/components/StructuredData.astro +2 -2
  123. package/src/components/justify.ts +9 -9
  124. package/src/components/media-meta.ts +10 -10
  125. package/src/components/sections/AboutIntro.astro +1 -1
  126. package/src/components/sections/Contact.astro +1 -1
  127. package/src/components/sections/Cta.astro +1 -1
  128. package/src/components/sections/Faq.astro +1 -1
  129. package/src/components/sections/FeatureGrid.astro +2 -2
  130. package/src/components/sections/Hero.astro +1 -1
  131. package/src/components/sections/PricingTiers.astro +1 -1
  132. package/src/components/sections/ProductGrid.astro +1 -1
  133. package/src/components/sections/SplitImage.astro +1 -1
  134. package/src/components/sections/Steps.astro +1 -1
  135. package/src/components/sections/Testimonial.astro +1 -1
  136. package/src/components/sections.ts +17 -17
@@ -3,7 +3,7 @@ import type { SecretSource } from "../secrets.js";
3
3
  import type { EmailSender } from "./send.js";
4
4
  import { type DeliveryResult } from "./send.js";
5
5
  import { type MailThemeOverrides } from "./theme.js";
6
- /** The bindings the inquiry hook reads. All optional — an unprovisioned mail
6
+ /** The bindings the inquiry hook reads. All optional—an unprovisioned mail
7
7
  * setup logs instead of sending, per the dormant-until-provisioned convention. */
8
8
  export interface AstroidMailEnv {
9
9
  /** Cloudflare Email Sending binding. */
@@ -11,7 +11,7 @@ export interface AstroidMailEnv {
11
11
  /**
12
12
  * Envelope sender; its domain must be onboarded for Email Sending. A
13
13
  * `SecretSource` rather than a plain string so a Secrets Store binding works
14
- * here too — and so the placeholder sentinel reads as unconfigured.
14
+ * here too—and so the placeholder sentinel reads as unconfigured.
15
15
  */
16
16
  MAIL_FROM?: SecretSource;
17
17
  /** Where owner notifications go. Also the first editor's address. */
@@ -7,7 +7,7 @@
7
7
  // the submission is already durable, so mail is a notification of something that
8
8
  // already happened and can fail without the visitor ever knowing.
9
9
  //
10
- // Two messages, not one — every site converged on the pair. The owner needs the
10
+ // Two messages, not one—every site converged on the pair. The owner needs the
11
11
  // message; the visitor needs to know it arrived, because a contact form with no
12
12
  // acknowledgement is indistinguishable from one that's broken.
13
13
  import { resolveMailer, sendTransactional } from "./send.js";
@@ -18,7 +18,7 @@ export interface MailerStatus {
18
18
  /** True only when a binding AND a real sender address are both present. */
19
19
  configured: boolean;
20
20
  values: ModuleSecrets<"MAIL_FROM">["values"];
21
- /** What's unprovisioned — secret names and/or `"EMAIL"`. */
21
+ /** What's unprovisioned—secret names and/or `"EMAIL"`. */
22
22
  missing: string[];
23
23
  /** Whether an Email Sending binding is present at all. */
24
24
  hasBinding: boolean;
@@ -34,7 +34,7 @@ export interface MailerEnv {
34
34
  * Both halves are required, and for the same reason: a binding with no sender
35
35
  * address can't build an envelope, and a sender address with no binding has
36
36
  * nothing to send through. Either one missing means log-and-continue, which
37
- * under `wrangler dev` (no EMAIL binding at all) is the normal case — and the
37
+ * under `wrangler dev` (no EMAIL binding at all) is the normal case—and the
38
38
  * reason the magic-link flow is still workable locally.
39
39
  */
40
40
  export declare function resolveMailerStatus(env: MailerEnv): Promise<MailerStatus>;
@@ -54,7 +54,7 @@ export declare function resolveMailer(env: MailerEnv, overrides?: Partial<Omit<M
54
54
  export interface OutgoingMail {
55
55
  to: string;
56
56
  content: MailContent;
57
- /** Reply-To — for an inquiry notification, the visitor's own address, so the
57
+ /** Reply-To—for an inquiry notification, the visitor's own address, so the
58
58
  * owner can just hit reply. */
59
59
  replyTo?: string;
60
60
  }
@@ -64,7 +64,7 @@ export interface DeliveryResult {
64
64
  subject: string;
65
65
  delivered: boolean;
66
66
  messageId?: string;
67
- /** Why it wasn't delivered — `"not-configured"`, `"log-only"`, or the error. */
67
+ /** Why it wasn't delivered—`"not-configured"`, `"log-only"`, or the error. */
68
68
  reason?: string;
69
69
  }
70
70
  export interface MailerOptions {
@@ -5,11 +5,11 @@
5
5
  // Transactional mail in this stack is always store-and-forward: the inquiry row
6
6
  // is already in D1, the account already exists. Mail is the *notification* of
7
7
  // something that happened, so a mail failure must never fail the request that
8
- // caused it — and never throw into a `waitUntil` where it becomes an unhandled
8
+ // caused it—and never throw into a `waitUntil` where it becomes an unhandled
9
9
  // rejection. Every path here resolves.
10
10
  //
11
11
  // The second job is the dev story. There is no EMAIL binding under `wrangler
12
- // dev`, and the single most common local task is "click the magic link" — so an
12
+ // dev`, and the single most common local task is "click the magic link"—so an
13
13
  // unconfigured mailer LOGS the message instead of silently dropping it, and it
14
14
  // logs the plaintext body, which is where the link is. That is the whole reason
15
15
  // every template renders a text alternative.
@@ -32,7 +32,7 @@ export const EMAIL_SECRET_NAMES = ["MAIL_FROM"];
32
32
  * Both halves are required, and for the same reason: a binding with no sender
33
33
  * address can't build an envelope, and a sender address with no binding has
34
34
  * nothing to send through. Either one missing means log-and-continue, which
35
- * under `wrangler dev` (no EMAIL binding at all) is the normal case — and the
35
+ * under `wrangler dev` (no EMAIL binding at all) is the normal case—and the
36
36
  * reason the magic-link flow is still workable locally.
37
37
  */
38
38
  export async function resolveMailerStatus(env) {
@@ -67,11 +67,11 @@ export async function resolveMailer(env, overrides = {}) {
67
67
  /**
68
68
  * The console rendering of an unsent message.
69
69
  *
70
- * The body is the whole point in dev — that's where a sign-in link actually is,
70
+ * The body is the whole point in dev—that's where a sign-in link actually is,
71
71
  * and printing it is what lets you sign in with no mail provider configured.
72
72
  *
73
73
  * It is also a credential. `logOnly` turns on whenever `MAIL_FROM` is unset, and
74
- * that can happen in PRODUCTION — a secret that didn't get set, or a Secrets
74
+ * that can happen in PRODUCTION—a secret that didn't get set, or a Secrets
75
75
  * Store read that failed. The body then went to `console.info`, which means
76
76
  * `wrangler tail` and every Logpush sink, carrying live single-use magic links
77
77
  * and password-reset URLs. Anyone with read access to observability could take
@@ -105,7 +105,7 @@ function describe(mail, reason, includeBody) {
105
105
  /**
106
106
  * Best-effort "are we in development?".
107
107
  *
108
- * Deliberately conservative — it decides whether a credential is printed, so an
108
+ * Deliberately conservative—it decides whether a credential is printed, so an
109
109
  * unknown environment must read as production. Workers has no `NODE_ENV`, so we
110
110
  * look at the signals that do exist and let a caller override explicitly.
111
111
  */
@@ -176,7 +176,7 @@ export async function sendTransactional(options, mails) {
176
176
  // A genuine delivery failure is LOGGED, not just returned.
177
177
  //
178
178
  // The result array was the only record of it, and the one caller that
179
- // matters — the generated inquiry handler — discards it by design (the row
179
+ // matters—the generated inquiry handler—discards it by design (the row
180
180
  // is already durable, and the visitor must not see a 500 because the owner's
181
181
  // notification bounced). So a dead Email Sending domain or an exhausted
182
182
  // quota produced silence everywhere: a success page for the visitor, nothing
@@ -4,7 +4,7 @@
4
4
  // inquiry pair (notify the owner, confirm to the sender).
5
5
  //
6
6
  // All three consuming sites wrote these four, with the same structure and
7
- // near-identical copy — only the brand name differed, which is exactly what
7
+ // near-identical copy—only the brand name differed, which is exactly what
8
8
  // makes them first-party rather than site-side. The brand-agnostic *frame*
9
9
  // (card, colour band, CTA button, paste-this-link fallback) already lives in
10
10
  // `louise-toolkit/email`; this file owns the wording and the layout inside it.
@@ -24,7 +24,7 @@ const label = (theme, text, margin = "0 0 10px") => `<p style="font-family:${the
24
24
  /** A quoted block for user-authored text (a message body). */
25
25
  const quote = (theme, text) => `<div style="font-family:${theme.fonts.sans};font-size:15px;line-height:1.65;color:${theme.palette.ink};padding:16px 18px;background:${theme.palette.bgSoft};border:1px solid ${theme.palette.rule};border-radius:6px;">${escapeMultiline(text)}</div>`;
26
26
  /**
27
- * A one-time link email — the shared shape behind sign-in and password reset.
27
+ * A one-time link email—the shared shape behind sign-in and password reset.
28
28
  * Both are "here is a URL, it expires, ignore this if it wasn't you", and the
29
29
  * only differences are the words.
30
30
  */
@@ -146,7 +146,7 @@ ${i.regarding?.trim() ? row("Regarding", escapeHtml(i.regarding.trim())) : ""}
146
146
  /** Confirmation back to whoever submitted the contact form. */
147
147
  export function inquiryConfirmationEmail(theme, i) {
148
148
  const brand = theme.brand.name;
149
- // Only the given name — "Hi Jane Smith" reads like a form letter, which is
149
+ // Only the given name—"Hi Jane Smith" reads like a form letter, which is
150
150
  // precisely what this is trying not to.
151
151
  const first = i.name.trim().split(/\s+/)[0] || "there";
152
152
  const bodyHtml = [
@@ -19,6 +19,6 @@ export interface MailThemeOverrides {
19
19
  * ```
20
20
  *
21
21
  * An invalid or missing brand colour falls back to the ink neutral rather than
22
- * throwing — a malformed hex in settings should not take out password reset.
22
+ * throwing—a malformed hex in settings should not take out password reset.
23
23
  */
24
24
  export declare function astroidMailTheme(config: AstroidConfig, overrides?: MailThemeOverrides): MailTheme;
@@ -2,7 +2,7 @@
2
2
  //
3
3
  // Deriving a `MailTheme` from the project's brand.
4
4
  //
5
- // The toolkit's email shell takes a fully-specified theme — ten palette slots, a
5
+ // The toolkit's email shell takes a fully-specified theme—ten palette slots, a
6
6
  // colour band, three font stacks. Every site hand-picked all of it, which is
7
7
  // exactly the kind of work a config should absorb: an Astroid project already
8
8
  // declares `theme.colors`, and that is enough to produce a mail theme that looks
@@ -11,7 +11,7 @@
11
11
  // Two decisions here are load-bearing:
12
12
  //
13
13
  // 1. **Neutrals are fixed, brand colours are derived.** Page background, ink,
14
- // rules — those are typography choices, not brand ones, and a site that
14
+ // rules—those are typography choices, not brand ones, and a site that
15
15
  // wants different ones passes an override. What varies per brand is the
16
16
  // accent and the colour band, and both come from `theme.colors`.
17
17
  // 2. **The accent is contrast-corrected.** A pale brand colour used verbatim
@@ -60,7 +60,7 @@ function contrast(a, b) {
60
60
  /**
61
61
  * Darken `color` until it clears `minRatio` against `bg`. A brand colour is
62
62
  * chosen to look good on a website, and plenty of good ones (yellows, pale
63
- * teals) are illegible as 11px uppercase text on a near-white email card — mail
63
+ * teals) are illegible as 11px uppercase text on a near-white email card—mail
64
64
  * clients offer no dark-mode escape hatch, so this is corrected up front.
65
65
  */
66
66
  function readableOn(color, bg, minRatio = 4.5) {
@@ -118,7 +118,7 @@ function buildFonts(font) {
118
118
  * ```
119
119
  *
120
120
  * An invalid or missing brand colour falls back to the ink neutral rather than
121
- * throwing — a malformed hex in settings should not take out password reset.
121
+ * throwing—a malformed hex in settings should not take out password reset.
122
122
  */
123
123
  export function astroidMailTheme(config, overrides = {}) {
124
124
  const cardBg = hexToRgb(NEUTRALS.bg) ?? WHITE;
package/dist/errors.d.ts CHANGED
@@ -10,7 +10,7 @@ export declare class AstroidConfigError extends Error {
10
10
  * Distinct from {@link AstroidConfigError}, which is a build-time contract: this
11
11
  * one fires on a live request, so it must be something a handler can catch and
12
12
  * turn into a 5xx rather than something that reads like a misconfigured project.
13
- * Reserved for cases where carrying on would be worse than failing — a checkout
13
+ * Reserved for cases where carrying on would be worse than failing—a checkout
14
14
  * whose idempotency key collides with another customer's, say, where the damage
15
15
  * (a buyer who is never charged) is invisible at the call site.
16
16
  */
package/dist/errors.js CHANGED
@@ -18,7 +18,7 @@ export class AstroidConfigError extends Error {
18
18
  * Distinct from {@link AstroidConfigError}, which is a build-time contract: this
19
19
  * one fires on a live request, so it must be something a handler can catch and
20
20
  * turn into a 5xx rather than something that reads like a misconfigured project.
21
- * Reserved for cases where carrying on would be worse than failing — a checkout
21
+ * Reserved for cases where carrying on would be worse than failing—a checkout
22
22
  * whose idempotency key collides with another customer's, say, where the damage
23
23
  * (a buyer who is never charged) is invisible at the call site.
24
24
  */
package/dist/index.js CHANGED
@@ -1,6 +1,6 @@
1
1
  // Copyright (c) 2026 BowenLabs. Astroid is MIT licensed.
2
2
  //
3
- // astroidjs — the opinionated meta-framework over Louise Toolkit + Astro.
3
+ // astroidjs—the opinionated meta-framework over Louise Toolkit + Astro.
4
4
  // Public entry. The configuration surface (`defineAstroid`) is the first
5
5
  // inhabitant; the generator, theme system, and section library follow.
6
6
  export * from "./analytics/index.js";
@@ -14,7 +14,7 @@ export type RangeSpec = {
14
14
  /**
15
15
  * How the archive is read. A function rather than a bucket interface, and
16
16
  * deliberately so: `R2Bucket.get` is overloaded, and its first overload
17
- * *requires* an options argument — which means no structural interface with an
17
+ * *requires* an options argument—which means no structural interface with an
18
18
  * optional second parameter can accept a real `R2Bucket`. Taking a reader lets
19
19
  * the call site use R2's own types and resolves the mismatch at the source, and
20
20
  * incidentally makes this work over any storage rather than only R2.
@@ -29,7 +29,7 @@ export interface RangeObject {
29
29
  body?: ReadableStream | null;
30
30
  /** Size of the WHOLE object, not the returned slice. */
31
31
  size: number;
32
- /** What R2 actually returned — it clamps a range that runs past the end. */
32
+ /** What R2 actually returned—it clamps a range that runs past the end. */
33
33
  range?: {
34
34
  offset?: number;
35
35
  length?: number;
@@ -58,7 +58,7 @@ export type ParsedRange = {
58
58
  * Handles the three forms that matter:
59
59
  * `bytes=0-1023` a bounded window
60
60
  * `bytes=1024-` open-ended, to the end
61
- * `bytes=-20000` the LAST n bytes — the one the reference dropped
61
+ * `bytes=-20000` the LAST n bytes—the one the reference dropped
62
62
  *
63
63
  * Multi-range (`bytes=0-99,200-299`) returns null: it requires a multipart
64
64
  * response no PMTiles client asks for, and serving the whole object is the
@@ -69,8 +69,8 @@ export interface PmtilesHandlerOptions {
69
69
  /** Reads the archive, whole or by range. See {@link RangeReader}. */
70
70
  read: RangeReader;
71
71
  /**
72
- * `Cache-Control` for the response. An archive is immutable — a re-clip
73
- * overwrites the object wholesale — so the byte ranges cache hard at the
72
+ * `Cache-Control` for the response. An archive is immutable—a re-clip
73
+ * overwrites the object wholesale—so the byte ranges cache hard at the
74
74
  * edge. Default one day.
75
75
  */
76
76
  cacheControl?: string;
@@ -1,6 +1,6 @@
1
1
  // Copyright (c) 2026 BowenLabs. Astroid is MIT licensed.
2
2
  //
3
- // Byte-range serving from R2 — the plumbing under the self-hosted basemap.
3
+ // Byte-range serving from R2—the plumbing under the self-hosted basemap.
4
4
  //
5
5
  // A PMTiles archive is one immutable blob, often hundreds of megabytes, and the
6
6
  // client reads a few kilobytes at a time: a header, then directory pages, then
@@ -13,7 +13,7 @@
13
13
  //
14
14
  // The range parsing is deliberately complete. The implementation this
15
15
  // generalizes matched only `bytes=<start>-<end?>`, so a SUFFIX range
16
- // (`bytes=-20000`, "the last 20 KB" — how a client reads a footer without
16
+ // (`bytes=-20000`, "the last 20 KB"—how a client reads a footer without
17
17
  // knowing the length) fell through to serving the ENTIRE archive. That is a
18
18
  // correct-looking response and a catastrophic one.
19
19
  /**
@@ -22,7 +22,7 @@
22
22
  * Handles the three forms that matter:
23
23
  * `bytes=0-1023` a bounded window
24
24
  * `bytes=1024-` open-ended, to the end
25
- * `bytes=-20000` the LAST n bytes — the one the reference dropped
25
+ * `bytes=-20000` the LAST n bytes—the one the reference dropped
26
26
  *
27
27
  * Multi-range (`bytes=0-99,200-299`) returns null: it requires a multipart
28
28
  * response no PMTiles client asks for, and serving the whole object is the
@@ -57,7 +57,7 @@ export function parseRangeHeader(header, size) {
57
57
  const end = Number(bounded[2]);
58
58
  if (end < start)
59
59
  return { kind: "unsatisfiable" };
60
- // An end past the object is clamped, not an error — a client asking for more
60
+ // An end past the object is clamped, not an error—a client asking for more
61
61
  // than exists gets what exists.
62
62
  return { kind: "range", offset: start, length: Math.min(end, size - 1) - start + 1 };
63
63
  }
@@ -107,7 +107,7 @@ export async function servePmtiles(request, options) {
107
107
  : { offset: parsed.offset, ...(parsed.length ? { length: parsed.length } : {}) });
108
108
  if (!object)
109
109
  return new Response("Basemap not found", { status: 404 });
110
- // Trust what R2 says it returned rather than what was asked for — it clamps
110
+ // Trust what R2 says it returned rather than what was asked for—it clamps
111
111
  // ranges, and a Content-Range that disagrees with the body corrupts the
112
112
  // client's view of the archive.
113
113
  const got = object.range ?? {};
@@ -6,7 +6,7 @@ export declare const ASTROID_PMTILES_PATH = "/map/basemap.pmtiles";
6
6
  /** True when this project switched the map module on. */
7
7
  export declare const usesMap: (config: AstroidConfig) => boolean;
8
8
  /**
9
- * `src/pages/map/basemap.pmtiles.ts` — the range-serving tile route.
9
+ * `src/pages/map/basemap.pmtiles.ts`—the range-serving tile route.
10
10
  *
11
11
  * Thin: `servePmtiles` owns range parsing, the 206/416 contract, and trusting
12
12
  * R2's clamped range over the requested one. What's here is which bucket and
@@ -14,7 +14,7 @@ export declare const usesMap: (config: AstroidConfig) => boolean;
14
14
  */
15
15
  export declare function generateMapTileRoute(config: AstroidConfig): string | null;
16
16
  /**
17
- * `src/components/MapEmbed.astro` — the map itself.
17
+ * `src/components/MapEmbed.astro`—the map itself.
18
18
  *
19
19
  * The lazy load is not an optimisation detail, it's the reason this is usable:
20
20
  * MapLibre is ~1 MB, and a location map is almost always below the fold. The
@@ -5,8 +5,8 @@
5
5
  // GENERATED rather than shipped as a component, for a concrete reason. MapLibre
6
6
  // GL is ~1 MB and `pmtiles` is its companion; a `MapEmbed.astro` living in
7
7
  // astroid's own `src/components/` would make both a hard requirement of the
8
- // package — every project installing them, and the CI probe that type-checks
9
- // the component library needing them too — for a feature most sites never turn
8
+ // package—every project installing them, and the CI probe that type-checks
9
+ // the component library needing them too—for a feature most sites never turn
10
10
  // on. Generating the component into the projects that enable the module keeps
11
11
  // the dependency where the decision was made.
12
12
  //
@@ -19,7 +19,7 @@ export const ASTROID_PMTILES_PATH = "/map/basemap.pmtiles";
19
19
  /** True when this project switched the map module on. */
20
20
  export const usesMap = (config) => (config.modules ?? []).includes("map");
21
21
  /**
22
- * `src/pages/map/basemap.pmtiles.ts` — the range-serving tile route.
22
+ * `src/pages/map/basemap.pmtiles.ts`—the range-serving tile route.
23
23
  *
24
24
  * Thin: `servePmtiles` owns range parsing, the 206/416 contract, and trusting
25
25
  * R2's clamped range over the requested one. What's here is which bucket and
@@ -30,7 +30,7 @@ export function generateMapTileRoute(config) {
30
30
  return null;
31
31
  return [
32
32
  "// Serves the self-hosted Protomaps basemap (one PMTiles archive) from R2,",
33
- "// same-origin, with HTTP range support — the client reads only the byte",
33
+ "// same-origin, with HTTP range support—the client reads only the byte",
34
34
  "// ranges it needs per tile.",
35
35
  "//",
36
36
  "// Same-origin is the point: no external tile host and no API key means the",
@@ -38,7 +38,7 @@ export function generateMapTileRoute(config) {
38
38
  "// irrelevant.",
39
39
  "//",
40
40
  "// Upload an archive to this key before the map draws anything (until then",
41
- "// the canvas shows its background and pin — the module is dormant, not",
41
+ "// the canvas shows its background and pin—the module is dormant, not",
42
42
  "// broken):",
43
43
  `// wrangler r2 object put ${config.key}-media/${ASTROID_PMTILES_KEY} \\`,
44
44
  "// --file=extract.pmtiles --content-type=application/octet-stream --remote",
@@ -68,7 +68,7 @@ export function generateMapTileRoute(config) {
68
68
  ].join("\n");
69
69
  }
70
70
  /**
71
- * `src/components/MapEmbed.astro` — the map itself.
71
+ * `src/components/MapEmbed.astro`—the map itself.
72
72
  *
73
73
  * The lazy load is not an optimisation detail, it's the reason this is usable:
74
74
  * MapLibre is ~1 MB, and a location map is almost always below the fold. The
@@ -82,7 +82,7 @@ export function generateMapEmbedComponent(config) {
82
82
  return [
83
83
  "---",
84
84
  "// A MapLibre map over the self-hosted PMTiles basemap, centred on exact",
85
- "// coordinates. Scaffolded once; yours to edit — the pin, the gestures, and",
85
+ "// coordinates. Scaffolded once; yours to edit—the pin, the gestures, and",
86
86
  "// the placeholder are brand decisions.",
87
87
  "//",
88
88
  "// Coordinates, never a geocoded address string: geocoding at render time is",
@@ -195,7 +195,7 @@ export function generateMapEmbedComponent(config) {
195
195
  " if (!entry.isIntersecting) continue;",
196
196
  " obs.unobserve(entry.target);",
197
197
  " // `init` is async and nothing awaits it, so without this catch a",
198
- " // failed chunk fetch — a page load racing a deploy is enough — is a",
198
+ " // failed chunk fetch—a page load racing a deploy is enough—is a",
199
199
  " // silent unhandled rejection: the container just stays an empty",
200
200
  " // tinted box with nothing in the console to explain it.",
201
201
  " init(entry.target as HTMLElement).catch((err) => {",
@@ -27,18 +27,18 @@ export interface MapStyleOptions {
27
27
  pmtilesUrl: string;
28
28
  colors?: MapColors;
29
29
  /**
30
- * SDF glyph URL template (e.g. `"/map/fonts/{fontstack}/{range}.pbf"`).
31
- * Omit for an unlabelled map — which is the honest default, since labels
30
+ * SDF glyph URL template (for example, `"/map/fonts/{fontstack}/{range}.pbf"`).
31
+ * Omit for an unlabelled map—which is the honest default, since labels
32
32
  * without self-hosted glyphs mean an external font host and a looser CSP.
33
33
  */
34
34
  glyphs?: string;
35
35
  /** Font stack for labels. Only used when `glyphs` is set. */
36
36
  fontstack?: string;
37
37
  /** Attribution shown in the corner. Protomaps basemaps derive from OSM, and
38
- * the licence requires the credit — so it defaults to present, not absent. */
38
+ * the licence requires the credit—so it defaults to present, not absent. */
39
39
  attribution?: string;
40
40
  }
41
- /** A MapLibre style. Typed loosely on purpose — see the header. */
41
+ /** A MapLibre style. Typed loosely on purpose—see the header. */
42
42
  export interface MapStyle {
43
43
  version: 8;
44
44
  glyphs?: string;
package/dist/map/style.js CHANGED
@@ -6,7 +6,7 @@
6
6
  // rather than importing `maplibre-gl` (a megabyte) or `protomaps-themes-base`
7
7
  // for its types. astroidjs stays installable by a project that will never draw
8
8
  // a map, and a project that wants Protomaps' full maintained theme can swap
9
- // this out — the only contract is "an object MapLibre accepts".
9
+ // this out—the only contract is "an object MapLibre accepts".
10
10
  //
11
11
  // The layer set is the quiet-basemap subset: land, water, green space, a road
12
12
  // ramp with casings, buildings, and admin boundaries. Labels are opt-in and
@@ -3,8 +3,8 @@ import type { PortalGuardConfig, PortalRoute } from "./guard.js";
3
3
  /** Cookie prefix for the portal instance. Distinct from the studio's default
4
4
  * (`better-auth`) so the two sessions can coexist on one origin. */
5
5
  export declare const ASTROID_PORTAL_COOKIE_PREFIX = "portal";
6
- /** Table-name prefix for the portal's Better Auth tables — `portal_user`,
7
- * `portal_session`, … The studio owns the unprefixed names. */
6
+ /** Table-name prefix for the portal's Better Auth tables—`portal_user`,
7
+ * `portal_session`, … The studio's tables carry the `louise_` prefix. */
8
8
  export declare const ASTROID_PORTAL_TABLE_PREFIX = "portal_";
9
9
  /** Everything the generated portal wiring needs, defaults applied. */
10
10
  export interface ResolvedPortal {
@@ -13,7 +13,7 @@ export interface ResolvedPortal {
13
13
  cookiePrefix: string;
14
14
  tablePrefix: string;
15
15
  roles: string[];
16
- /** First role in `roles` — what a newly created account gets. */
16
+ /** First role in `roles`—what a newly created account gets. */
17
17
  defaultRole: string;
18
18
  routes: PortalRoute[];
19
19
  home: Record<string, string>;
@@ -1,20 +1,21 @@
1
1
  // Copyright (c) 2026 BowenLabs. Astroid is MIT licensed.
2
2
  //
3
- // Portal defaults derived from the project config — the single place that knows
3
+ // Portal defaults derived from the project config—the single place that knows
4
4
  // the portal's mount, cookie prefix, table prefix, and guard table.
5
5
  //
6
- // The isolation constants are fixed rather than configurable, and that's the
7
- // point: the studio instance MUST keep Better Auth's defaults (`/api/auth`, the
8
- // unprefixed tables) because the Louise editor client hardcodes them, so the
9
- // portal is the one that moves. Leaving that to a project invites the one
10
- // mistake that matters — two instances sharing a cookie prefix, where signing
11
- // into one silently signs you out of the other, intermittently, in production.
6
+ // The studio instance keeps `/api/auth` because the Louise editor client
7
+ // hardcodes it, so the portal is the one that moves: its own mount, cookie
8
+ // prefix, and table prefix. The defaults are safe distinct values, and
9
+ // `defineAstroid` rejects any override that collides with the editor. That guards
10
+ // the one mistake that matters—two instances sharing a cookie prefix, where
11
+ // signing into one silently signs you out of the other, intermittently, in
12
+ // production.
12
13
  import { ASTROID_PORTAL_BASE_PATH } from "../security/rate-rules.js";
13
14
  /** Cookie prefix for the portal instance. Distinct from the studio's default
14
15
  * (`better-auth`) so the two sessions can coexist on one origin. */
15
16
  export const ASTROID_PORTAL_COOKIE_PREFIX = "portal";
16
- /** Table-name prefix for the portal's Better Auth tables — `portal_user`,
17
- * `portal_session`, … The studio owns the unprefixed names. */
17
+ /** Table-name prefix for the portal's Better Auth tables—`portal_user`,
18
+ * `portal_session`, … The studio's tables carry the `louise_` prefix. */
18
19
  export const ASTROID_PORTAL_TABLE_PREFIX = "portal_";
19
20
  /** Default guard table: the account area, for any signed-in portal user. */
20
21
  const DEFAULT_ROUTES = [{ prefix: "/portal" }, { prefix: "/api/portal" }];
@@ -27,7 +28,7 @@ export function astroidPortal(config) {
27
28
  return {
28
29
  enabled: true,
29
30
  // Isolation is configurable so a site with an existing second instance
30
- // (coracle's shop account at /api/shop-auth, cookie `coracle_shop`, the
31
+ // (for example, a shop account at /api/shop-auth with its own cookie and the
31
32
  // unprefixed `user` tables) keeps its live mount + cookies unchanged. The
32
33
  // defaults stay the safe distinct-from-editor values; `defineAstroid`'s
33
34
  // `assertAuthIsolation` rejects a resolved value that collides with the
@@ -11,7 +11,7 @@ export interface PortalUser {
11
11
  }
12
12
  /** One rule: everything under `prefix` requires one of `roles`. */
13
13
  export interface PortalRoute {
14
- /** Path prefix, e.g. `/portal` — matches the prefix itself and everything
14
+ /** Path prefix, for example, `/portal`—matches the prefix itself and everything
15
15
  * beneath it, but NOT `/portalling`. */
16
16
  prefix: string;
17
17
  /** Roles allowed through. Empty means "any signed-in user". */
@@ -22,7 +22,7 @@ export interface PortalGuardConfig {
22
22
  routes: PortalRoute[];
23
23
  /** Where to send a signed-out visitor. Default `/login`. */
24
24
  loginPath?: string;
25
- /** Landing page for a signed-in user, by role — used to bounce someone who
25
+ /** Landing page for a signed-in user, by role—used to bounce someone who
26
26
  * reached an area they don't belong in. Default `/portal` for everyone. */
27
27
  home?: (role: string) => string;
28
28
  }
@@ -38,13 +38,13 @@ export type GuardDecision = null | {
38
38
  error: string;
39
39
  };
40
40
  };
41
- /** Prefix match on a path SEGMENT boundary — `/portal` covers `/portal` and
41
+ /** Prefix match on a path SEGMENT boundary—`/portal` covers `/portal` and
42
42
  * `/portal/orders`, but never `/portalling`. */
43
43
  export declare function matchesPrefix(path: string, prefix: string): boolean;
44
44
  /**
45
45
  * Decide whether a request may proceed.
46
46
  *
47
- * Pure — it returns a decision rather than a `Response`, so it's testable
47
+ * Pure—it returns a decision rather than a `Response`, so it's testable
48
48
  * without an Astro context and the middleware stays responsible for turning a
49
49
  * decision into a redirect or a body.
50
50
  */
@@ -5,7 +5,7 @@
5
5
  // coracle and ghostfire independently built the same thing: a declarative table
6
6
  // of `prefix → roles`, walked once per request. Declarative rather than a guard
7
7
  // call inside each page, because a guard you have to remember to write is a
8
- // guard someone eventually forgets — and the page that forgets it is the one
8
+ // guard someone eventually forgets—and the page that forgets it is the one
9
9
  // that leaks.
10
10
  //
11
11
  // Three answers, and which one you give matters:
@@ -14,9 +14,9 @@
14
14
  // not signed in, API → 401 JSON (a redirect to an HTML login page is
15
15
  // useless to fetch(); it looks like success)
16
16
  // signed in, wrong role → 403 for API, and for HTML a redirect to the area
17
- // this user DOES have — not back to login, which
17
+ // this user DOES have—not back to login, which
18
18
  // reads as "your password failed" when it didn't
19
- /** Prefix match on a path SEGMENT boundary — `/portal` covers `/portal` and
19
+ /** Prefix match on a path SEGMENT boundary—`/portal` covers `/portal` and
20
20
  * `/portal/orders`, but never `/portalling`. */
21
21
  export function matchesPrefix(path, prefix) {
22
22
  return path === prefix || path.startsWith(`${prefix}/`);
@@ -24,7 +24,7 @@ export function matchesPrefix(path, prefix) {
24
24
  /**
25
25
  * Decide whether a request may proceed.
26
26
  *
27
- * Pure — it returns a decision rather than a `Response`, so it's testable
27
+ * Pure—it returns a decision rather than a `Response`, so it's testable
28
28
  * without an Astro context and the middleware stays responsible for turning a
29
29
  * decision into a redirect or a body.
30
30
  */
@@ -3,7 +3,7 @@
3
3
  // The portal's navigation, as data.
4
4
  //
5
5
  // Two things fall out of declaring it rather than writing markup per page.
6
- // Items can be filtered by the viewer's role in one place — so an item a user
6
+ // Items can be filtered by the viewer's role in one place—so an item a user
7
7
  // can't reach is never rendered, instead of rendered-then-403'd, which reads as
8
8
  // a broken link. And "which item is active" is computed the same way the guard
9
9
  // matches prefixes, so the highlight can't disagree with the routing.
@@ -26,7 +26,7 @@ export function definePortalNav(items) {
26
26
  },
27
27
  activeFor(path) {
28
28
  // Longest href first, so `/portal/orders` wins over `/portal` on a page
29
- // both would match — otherwise the parent item is always the active one.
29
+ // both would match—otherwise the parent item is always the active one.
30
30
  return ([...items]
31
31
  .sort((a, b) => b.href.length - a.href.length)
32
32
  .find((item) => matchesPrefix(path, item.href)) ?? null);
@@ -1,20 +1,20 @@
1
1
  import type { AstroidConfig } from "../config.js";
2
2
  /**
3
- * `src/portal-auth.ts` — the portal Better Auth instance and its session
3
+ * `src/portal-auth.ts`—the portal Better Auth instance and its session
4
4
  * resolver.
5
5
  *
6
6
  * Returns null when the project has no portal.
7
7
  */
8
8
  export declare function generateAstroidPortalAuth(config: AstroidConfig): string | null;
9
9
  /**
10
- * `src/pages/api/portal-auth/[...all].ts` — the portal's Better Auth catch-all,
10
+ * `src/pages/api/portal-auth/[...all].ts`—the portal's Better Auth catch-all,
11
11
  * mounted at its own basePath so it never collides with the studio's
12
12
  * `/api/auth`.
13
13
  *
14
14
  * Lives here rather than as a literal in `create-astroid` for the same reason
15
15
  * the archetype sections moved (#277): the scaffolder is plain JS, so a drifted
16
16
  * import path there is invisible until a user's build fails. It is also the half
17
- * `generateAstroidPortalAuth` is useless without — `src/portal-auth.ts` exports
17
+ * `generateAstroidPortalAuth` is useless without—`src/portal-auth.ts` exports
18
18
  * `handlePortalAuth`, and nothing calls it unless this route exists.
19
19
  *
20
20
  * Returns null when the project has no portal.
@@ -22,7 +22,7 @@ export declare function generateAstroidPortalAuth(config: AstroidConfig): string
22
22
  export declare function generateAstroidPortalAuthRoute(config: AstroidConfig): string | null;
23
23
  /**
24
24
  * The `App.Locals` member the portal adds, as a block `create-astroid`
25
- * substitutes into `src/env.d.ts`. Empty without a portal — a project that
25
+ * substitutes into `src/env.d.ts`. Empty without a portal—a project that
26
26
  * types `portalUser` it never sets is inviting a null-check nobody needs.
27
27
  */
28
28
  export declare function generateAstroidPortalLocals(config: AstroidConfig): string;