astroidjs 0.12.1 → 0.13.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 (133) hide show
  1. package/README.md +42 -42
  2. package/bin/astroid.mjs +25 -25
  3. package/dist/analytics/index.d.ts +3 -3
  4. package/dist/analytics/index.js +9 -9
  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 +9 -9
  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 +9 -9
  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 +62 -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 +4 -4
  44. package/dist/map/style.d.ts +4 -4
  45. package/dist/map/style.js +1 -1
  46. package/dist/portal/config.d.ts +2 -2
  47. package/dist/portal/config.js +3 -3
  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 +6 -6
  53. package/dist/portal/session.d.ts +2 -2
  54. package/dist/portal/session.js +5 -5
  55. package/dist/portfolio/scaffold.d.ts +1 -1
  56. package/dist/portfolio/scaffold.js +4 -4
  57. package/dist/project/actions.d.ts +1 -1
  58. package/dist/project/actions.js +6 -6
  59. package/dist/project/generate.d.ts +4 -4
  60. package/dist/project/generate.js +15 -15
  61. package/dist/project/index.js +1 -1
  62. package/dist/project/scaffold.d.ts +2 -2
  63. package/dist/project/scaffold.js +11 -11
  64. package/dist/pwa/generate.d.ts +11 -11
  65. package/dist/pwa/generate.js +12 -12
  66. package/dist/queues/consumer.d.ts +3 -3
  67. package/dist/queues/consumer.js +2 -2
  68. package/dist/queues/messages.d.ts +4 -4
  69. package/dist/queues/messages.js +2 -2
  70. package/dist/queues/scaffold.d.ts +4 -4
  71. package/dist/queues/scaffold.js +8 -8
  72. package/dist/queues/webhook.d.ts +5 -5
  73. package/dist/queues/webhook.js +3 -3
  74. package/dist/realtime/scaffold.d.ts +4 -4
  75. package/dist/realtime/scaffold.js +8 -8
  76. package/dist/schema/collections.d.ts +6 -6
  77. package/dist/schema/collections.js +23 -23
  78. package/dist/schema/framework.d.ts +1 -1
  79. package/dist/schema/framework.js +2 -2
  80. package/dist/schema/generate.js +2 -2
  81. package/dist/schema/index.js +1 -1
  82. package/dist/secrets.d.ts +6 -6
  83. package/dist/secrets.js +6 -6
  84. package/dist/security/csp-origins.d.ts +1 -1
  85. package/dist/security/csp-origins.js +3 -3
  86. package/dist/security/rate-rules.d.ts +2 -2
  87. package/dist/security/rate-rules.js +8 -8
  88. package/dist/seo/resolve.d.ts +5 -5
  89. package/dist/seo/resolve.js +2 -2
  90. package/dist/seo/routes.d.ts +5 -5
  91. package/dist/seo/routes.js +3 -3
  92. package/dist/seo/structured-data.d.ts +6 -6
  93. package/dist/seo/structured-data.js +7 -7
  94. package/dist/status.d.ts +5 -5
  95. package/dist/status.js +7 -7
  96. package/dist/tenancy/index.d.ts +3 -3
  97. package/dist/tenancy/index.js +6 -6
  98. package/dist/worker/generate.d.ts +2 -2
  99. package/dist/worker/generate.js +19 -19
  100. package/dist/worker/index.js +1 -1
  101. package/dist/worker/routes.js +1 -1
  102. package/dist/workflow/advance.d.ts +3 -3
  103. package/dist/workflow/advance.js +6 -6
  104. package/dist/workflow/config.d.ts +4 -4
  105. package/dist/workflow/config.js +4 -4
  106. package/dist/workflow/generate.d.ts +2 -2
  107. package/dist/workflow/generate.js +4 -4
  108. package/package.json +3 -4
  109. package/src/components/Collection.tsx +5 -5
  110. package/src/components/Editable.astro +9 -9
  111. package/src/components/JustifiedGallery.astro +8 -8
  112. package/src/components/MediaSlot.astro +12 -12
  113. package/src/components/PortalShell.astro +4 -4
  114. package/src/components/RegisterSW.astro +3 -3
  115. package/src/components/Section.astro +8 -8
  116. package/src/components/Sections.astro +6 -6
  117. package/src/components/Seo.astro +3 -3
  118. package/src/components/StageBar.astro +3 -3
  119. package/src/components/StructuredData.astro +2 -2
  120. package/src/components/justify.ts +9 -9
  121. package/src/components/media-meta.ts +10 -10
  122. package/src/components/sections/AboutIntro.astro +1 -1
  123. package/src/components/sections/Contact.astro +1 -1
  124. package/src/components/sections/Cta.astro +1 -1
  125. package/src/components/sections/Faq.astro +1 -1
  126. package/src/components/sections/FeatureGrid.astro +2 -2
  127. package/src/components/sections/Hero.astro +1 -1
  128. package/src/components/sections/PricingTiers.astro +1 -1
  129. package/src/components/sections/ProductGrid.astro +1 -1
  130. package/src/components/sections/SplitImage.astro +1 -1
  131. package/src/components/sections/Steps.astro +1 -1
  132. package/src/components/sections/Testimonial.astro +1 -1
  133. package/src/components/sections.ts +17 -17
@@ -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
@@ -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
@@ -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,7 +3,7 @@ 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`,
6
+ /** Table-name prefix for the portal's Better Auth tables—`portal_user`,
7
7
  * `portal_session`, … The studio owns the unprefixed names. */
8
8
  export declare const ASTROID_PORTAL_TABLE_PREFIX = "portal_";
9
9
  /** Everything the generated portal wiring needs, defaults applied. */
@@ -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,19 +1,19 @@
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
6
  // The isolation constants are fixed rather than configurable, and that's the
7
7
  // point: the studio instance MUST keep Better Auth's defaults (`/api/auth`, the
8
8
  // unprefixed tables) because the Louise editor client hardcodes them, so the
9
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
10
+ // mistake that matters—two instances sharing a cookie prefix, where signing
11
11
  // into one silently signs you out of the other, intermittently, in production.
12
12
  import { ASTROID_PORTAL_BASE_PATH } from "../security/rate-rules.js";
13
13
  /** Cookie prefix for the portal instance. Distinct from the studio's default
14
14
  * (`better-auth`) so the two sessions can coexist on one origin. */
15
15
  export const ASTROID_PORTAL_COOKIE_PREFIX = "portal";
16
- /** Table-name prefix for the portal's Better Auth tables — `portal_user`,
16
+ /** Table-name prefix for the portal's Better Auth tables—`portal_user`,
17
17
  * `portal_session`, … The studio owns the unprefixed names. */
18
18
  export const ASTROID_PORTAL_TABLE_PREFIX = "portal_";
19
19
  /** Default guard table: the account area, for any signed-in portal user. */
@@ -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;
@@ -3,15 +3,15 @@
3
3
  // The portal's SCAFFOLD-ONCE pieces: the second Better Auth instance, and the
4
4
  // `App.Locals` / `CloudflareEnv` additions that come with it.
5
5
  //
6
- // The auth instance is scaffolded rather than generated because a site edits it
7
- // — the reset email, the role a new account gets, extra user columns. What
6
+ // The auth instance is scaffolded rather than generated because a site edits it—the
7
+ // reset email, the role a new account gets, extra user columns. What
8
8
  // Astroid fixes are the three things that must not drift: the mount, the cookie
9
9
  // prefix, and the table prefix. Get any of those wrong and the two instances
10
10
  // fight over one origin's cookies, which fails intermittently and looks like a
11
11
  // session bug rather than a configuration one.
12
12
  import { astroidPortal } from "./config.js";
13
13
  /**
14
- * `src/portal-auth.ts` — the portal Better Auth instance and its session
14
+ * `src/portal-auth.ts`—the portal Better Auth instance and its session
15
15
  * resolver.
16
16
  *
17
17
  * Returns null when the project has no portal.
@@ -97,14 +97,14 @@ export function generateAstroidPortalAuth(config) {
97
97
  ].join("\n");
98
98
  }
99
99
  /**
100
- * `src/pages/api/portal-auth/[...all].ts` — the portal's Better Auth catch-all,
100
+ * `src/pages/api/portal-auth/[...all].ts`—the portal's Better Auth catch-all,
101
101
  * mounted at its own basePath so it never collides with the studio's
102
102
  * `/api/auth`.
103
103
  *
104
104
  * Lives here rather than as a literal in `create-astroid` for the same reason
105
105
  * the archetype sections moved (#277): the scaffolder is plain JS, so a drifted
106
106
  * import path there is invisible until a user's build fails. It is also the half
107
- * `generateAstroidPortalAuth` is useless without — `src/portal-auth.ts` exports
107
+ * `generateAstroidPortalAuth` is useless without—`src/portal-auth.ts` exports
108
108
  * `handlePortalAuth`, and nothing calls it unless this route exists.
109
109
  *
110
110
  * Returns null when the project has no portal.
@@ -126,7 +126,7 @@ export function generateAstroidPortalAuthRoute(config) {
126
126
  }
127
127
  /**
128
128
  * The `App.Locals` member the portal adds, as a block `create-astroid`
129
- * substitutes into `src/env.d.ts`. Empty without a portal — a project that
129
+ * substitutes into `src/env.d.ts`. Empty without a portal—a project that
130
130
  * types `portalUser` it never sets is inviting a null-check nobody needs.
131
131
  */
132
132
  export function generateAstroidPortalLocals(config) {
@@ -8,7 +8,7 @@ export type PortalSessionResolver = (request: Request) => Promise<PortalUser | n
8
8
  * request both await one lookup rather than starting a second.
9
9
  */
10
10
  export declare function resolvePortalSession(request: Request, resolve: PortalSessionResolver): Promise<PortalUser | null>;
11
- /** JSON response helper — the shape every portal API route returns. */
11
+ /** JSON response helper—the shape every portal API route returns. */
12
12
  export declare function json(body: unknown, status?: number, headers?: Record<string, string>): Response;
13
13
  /** True when the request came from this same origin. */
14
14
  export declare function isSameOrigin(request: Request): boolean;
@@ -20,7 +20,7 @@ export type CustomerGuardResult = {
20
20
  response: Response;
21
21
  };
22
22
  /**
23
- * Guard a portal API handler: a signed-in user, and — on mutations — a
23
+ * Guard a portal API handler: a signed-in user, and—on mutations—a
24
24
  * same-origin request.
25
25
  *
26
26
  * ```ts
@@ -5,12 +5,12 @@
5
5
  // The middleware resolves it (to gate routes) and so does whatever handler runs
6
6
  // next (to know who's asking). Both hitting the session store is a wasted D1
7
7
  // round-trip on every authenticated request, so the in-flight promise is shared
8
- // per request via a `WeakMap` — keyed on the `Request`, which means entries
8
+ // per request via a `WeakMap`—keyed on the `Request`, which means entries
9
9
  // disappear with the request rather than needing eviction.
10
10
  //
11
11
  // `requireCustomer` then adds the check a session alone doesn't give you:
12
12
  // same-origin on mutations. A cookie is attached by the browser to any request
13
- // to this origin, including one a third-party page triggered — so a session
13
+ // to this origin, including one a third-party page triggered—so a session
14
14
  // proves identity, and the origin check proves intent.
15
15
  const inFlight = new WeakMap();
16
16
  /**
@@ -29,7 +29,7 @@ export function resolvePortalSession(request, resolve) {
29
29
  inFlight.set(request, promise);
30
30
  return promise;
31
31
  }
32
- /** JSON response helper — the shape every portal API route returns. */
32
+ /** JSON response helper—the shape every portal API route returns. */
33
33
  export function json(body, status = 200, headers = {}) {
34
34
  return new Response(JSON.stringify(body), {
35
35
  status,
@@ -46,7 +46,7 @@ export function isSameOrigin(request) {
46
46
  return origin === target;
47
47
  // No Origin header: browsers always send one on cross-origin mutations, so
48
48
  // its absence means a same-origin or non-browser caller. Fall back to Referer
49
- // when present, and allow otherwise — being stricter would break legitimate
49
+ // when present, and allow otherwise—being stricter would break legitimate
50
50
  // server-to-server callers without stopping a real CSRF, which always carries
51
51
  // an Origin.
52
52
  const referer = request.headers.get("referer");
@@ -61,7 +61,7 @@ export function isSameOrigin(request) {
61
61
  return true;
62
62
  }
63
63
  /**
64
- * Guard a portal API handler: a signed-in user, and — on mutations — a
64
+ * Guard a portal API handler: a signed-in user, and—on mutations—a
65
65
  * same-origin request.
66
66
  *
67
67
  * ```ts
@@ -1,6 +1,6 @@
1
1
  import type { AstroidConfig } from "../config.js";
2
2
  /**
3
- * `src/pages/work.astro` — the portfolio gallery. Null for any other archetype.
3
+ * `src/pages/work.astro`—the portfolio gallery. Null for any other archetype.
4
4
  *
5
5
  * Intrinsic `width`/`height` are carried through deliberately: they feed the
6
6
  * pre-decode layout, so a library with dimensions recorded lays out correctly on
@@ -3,17 +3,17 @@
3
3
  // The `portfolio` archetype's scaffold-once page: a justified gallery over the
4
4
  // media library.
5
5
  //
6
- // Scaffold-once, not regenerated, for the usual reason — this is the first file
6
+ // Scaffold-once, not regenerated, for the usual reason—this is the first file
7
7
  // a portfolio site edits (which assets appear, in what order, whether tiles link
8
8
  // to a detail page), so `astroid generate` must never rewrite it.
9
9
  //
10
10
  // It exists because the primitives alone don't finish the job. `<MediaSlot>` and
11
11
  // `<JustifiedGallery>` are archetype-agnostic, but the wiring between them and
12
- // the media registry — the public URL shape, filtering to images, carrying
12
+ // the media registry—the public URL shape, filtering to images, carrying
13
13
  // alt/caption and intrinsic dimensions through so the first paint isn't a guess
14
- // — is identical every time, and is exactly what the consuming sites hand-wrote.
14
+ //—is identical every time, and is exactly what the consuming sites hand-wrote.
15
15
  /**
16
- * `src/pages/work.astro` — the portfolio gallery. Null for any other archetype.
16
+ * `src/pages/work.astro`—the portfolio gallery. Null for any other archetype.
17
17
  *
18
18
  * Intrinsic `width`/`height` are carried through deliberately: they feed the
19
19
  * pre-decode layout, so a library with dimensions recorded lays out correctly on
@@ -1,3 +1,3 @@
1
1
  import type { AstroidConfig } from "../config.js";
2
- /** `src/actions/index.ts` — the typed mutation surface, scaffolded once. */
2
+ /** `src/actions/index.ts`—the typed mutation surface, scaffolded once. */
3
3
  export declare function generateAstroidActions(config: AstroidConfig): string;
@@ -1,21 +1,21 @@
1
1
  // Copyright (c) 2026 BowenLabs. Astroid is MIT licensed.
2
2
  //
3
- // `src/actions/index.ts` — the Astro-native, typed mutation surface (ADR 0001
3
+ // `src/actions/index.ts`—the Astro-native, typed mutation surface (ADR 0001
4
4
  // layer 2), beside the framework-agnostic `/api/louise/*` routes.
5
5
  //
6
6
  // Astroid generated only the route half. That is not a missing convenience: the
7
7
  // two entrypoints write the SAME rows, and the whole reason `@louise-toolkit/astro`
8
- // exposes these factories is that each one shares the raw route's store path —
9
- // `applyFieldSave`, `applySettingsPatch`, `applySaveDraft`. A project that wired
8
+ // exposes these factories is that each one shares the raw route's store
9
+ // path—`applyFieldSave`, `applySettingsPatch`, `applySaveDraft`. A project that wired
10
10
  // its own Actions by hand would get a second write path, and a second write path
11
11
  // is where validation, sanitization, and draft-merge semantics drift apart
12
12
  // silently (#138).
13
13
  //
14
- // So this file is SCAFFOLD-ONCE and is meant to be added to — the reference site
15
- // keeps its own bespoke actions right beside these — but the three below come
14
+ // So this file is SCAFFOLD-ONCE and is meant to be added to—the reference site
15
+ // keeps its own bespoke actions right beside these—but the three below come
16
16
  // pre-wired against the same tables and the same collection config the generated
17
17
  // worker uses.
18
- /** `src/actions/index.ts` — the typed mutation surface, scaffolded once. */
18
+ /** `src/actions/index.ts`—the typed mutation surface, scaffolded once. */
19
19
  export function generateAstroidActions(config) {
20
20
  const customKeys = config.settings?.customKeys ?? [];
21
21
  const extraImageKeys = config.settings?.imageKeys ?? [];