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
@@ -1,11 +1,11 @@
1
1
  // Copyright (c) 2026 BowenLabs. Astroid is MIT licensed.
2
2
  //
3
- // `defineWorkflow` — staged, audited pipelines.
3
+ // `defineWorkflow`—staged, audited pipelines.
4
4
  //
5
5
  // The shape this generalizes is ghostfire.coffee's production floor, and the
6
6
  // framing correction in #256 is the important part: despite the name "order
7
7
  // tracker", it is NOT queue- or Durable-Object-driven. It is a synchronous SSR
8
- // + D1 state machine — an integer `stage` column advanced by sign-off rows,
8
+ // + D1 state machine—an integer `stage` column advanced by sign-off rows,
9
9
  // where "liveness" is an email plus a page reload. That maps to a workflow
10
10
  // module, distinct from the queues module and from #71's realtime DO. If live
11
11
  // push is wanted later it layers on top; it is not required for the pattern.
@@ -15,7 +15,7 @@
15
15
  // the same four things:
16
16
  //
17
17
  // 1. an ordered list of stages, and one integer saying which is in progress;
18
- // 2. exactly one audit row per completed stage — who, when, and what they
18
+ // 2. exactly one audit row per completed stage—who, when, and what they
19
19
  // recorded;
20
20
  // 3. an advance that is safe when two operators press the button at once;
21
21
  // 4. a per-stage side-effect hook (issue the invoice on packaging).
@@ -58,7 +58,7 @@ export function defineWorkflow(config) {
58
58
  throw new AstroidConfigError(`Workflow stage keys must be lowercase identifiers; got ${JSON.stringify(stage.key)}`);
59
59
  }
60
60
  // Duplicates would make `stationFields` ambiguous and an audit row's stage
61
- // key non-unique — both silent, both awful to debug later.
61
+ // key non-unique—both silent, both awful to debug later.
62
62
  if (seen.has(stage.key)) {
63
63
  throw new AstroidConfigError(`Workflow ${JSON.stringify(config.key)} has a duplicate stage ${JSON.stringify(stage.key)}`);
64
64
  }
@@ -3,7 +3,7 @@ import { type WorkflowConfig } from "./config.js";
3
3
  * The Drizzle table sources a workflow adds: the audit table, and the override
4
4
  * log when enabled.
5
5
  *
6
- * The `stage` column itself is NOT emitted here — it belongs to the project's
6
+ * The `stage` column itself is NOT emitted here—it belongs to the project's
7
7
  * own entity table (orders, applications, tickets), which Astroid doesn't own.
8
8
  * The returned `stageColumn` is the line to paste into it, which keeps the
9
9
  * ownership boundary honest rather than generating a table the project already has.
@@ -13,7 +13,7 @@ export declare function generateWorkflowSchema(config: WorkflowConfig): {
13
13
  stageColumn: string;
14
14
  };
15
15
  /**
16
- * `src/pages/api/<key>/advance.ts` — the guarded advance route.
16
+ * `src/pages/api/<key>/advance.ts`—the guarded advance route.
17
17
  *
18
18
  * Thin by design: `advanceWorkflowStage` owns the concurrency guard and the
19
19
  * status contract, so what's left here is authentication and the per-stage side
@@ -3,15 +3,15 @@
3
3
  // Workflow → Drizzle schema + the advance route.
4
4
  //
5
5
  // The schema half is a pure function of the config (regenerate freely); the
6
- // route is SCAFFOLD-ONCE, because per-stage side effects — issue the invoice
7
- // when packaging is signed, email the customer on every move — are the whole
6
+ // route is SCAFFOLD-ONCE, because per-stage side effects—issue the invoice
7
+ // when packaging is signed, email the customer on every move—are the whole
8
8
  // reason a project reaches for this module, and regenerating would erase them.
9
9
  import { workflowAuditTable, workflowOverrideTable, workflowStages, } from "./config.js";
10
10
  /**
11
11
  * The Drizzle table sources a workflow adds: the audit table, and the override
12
12
  * log when enabled.
13
13
  *
14
- * The `stage` column itself is NOT emitted here — it belongs to the project's
14
+ * The `stage` column itself is NOT emitted here—it belongs to the project's
15
15
  * own entity table (orders, applications, tickets), which Astroid doesn't own.
16
16
  * The returned `stageColumn` is the line to paste into it, which keeps the
17
17
  * ownership boundary honest rather than generating a table the project already has.
@@ -68,7 +68,7 @@ export function generateWorkflowSchema(config) {
68
68
  return { source: lines.join("\n"), stageColumn: 'stage: integer("stage").notNull().default(0),' };
69
69
  }
70
70
  /**
71
- * `src/pages/api/<key>/advance.ts` — the guarded advance route.
71
+ * `src/pages/api/<key>/advance.ts`—the guarded advance route.
72
72
  *
73
73
  * Thin by design: `advanceWorkflowStage` owns the concurrency guard and the
74
74
  * status contract, so what's left here is authentication and the per-stage side
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "astroidjs",
3
- "version": "0.12.1",
3
+ "version": "0.13.0",
4
4
  "description": "Astroid — an opinionated meta-framework over Louise Toolkit and Astro for building editable, multi-editor sites on Cloudflare Workers.",
5
5
  "keywords": [
6
6
  "astro",
@@ -59,20 +59,19 @@
59
59
  "publishConfig": {
60
60
  "access": "public"
61
61
  },
62
- "dependencies": {
63
- "louise-toolkit": "^0.31.0"
64
- },
65
62
  "devDependencies": {
66
63
  "@types/node": "^24.13.3",
67
64
  "@typescript/native-preview": "7.0.0-dev.20260707.2",
68
65
  "@vitest/coverage-v8": "4.1.11",
69
66
  "astro": "^7.2.9",
67
+ "louise-toolkit": "^0.31.0",
70
68
  "solid-js": "^1.9.15",
71
69
  "typescript": "^5.9.3",
72
70
  "vitest": "^4.1.11"
73
71
  },
74
72
  "peerDependencies": {
75
73
  "astro": "^7.0.9",
74
+ "louise-toolkit": "^0.31.0",
76
75
  "solid-js": "^1.9.0"
77
76
  },
78
77
  "peerDependenciesMeta": {
@@ -1,6 +1,6 @@
1
1
  // Copyright (c) 2026 BowenLabs. Astroid is MIT licensed.
2
2
  //
3
- // `<Collection>` — the typed list primitive (ADR 0003, item 3). It renders a list
3
+ // `<Collection>`—the typed list primitive (ADR 0003, item 3). It renders a list
4
4
  // and hands each item to a render function that is FULLY TYPED, with no
5
5
  // hand-written interface: the item type is inferred from `items`, so `item` in the
6
6
  // slot carries whatever shape you passed.
@@ -10,7 +10,7 @@
10
10
  // </Collection>
11
11
  //
12
12
  // Why a Solid render-prop and not an `.astro` component: Astro slots can't type a
13
- // per-item `{item}`, but a Solid children-as-function can — this is the only shape
13
+ // per-item `{item}`, but a Solid children-as-function can—this is the only shape
14
14
  // that delivers the ADR's "`{item}` in its slot is fully typed". Server-render it
15
15
  // by using it inside a Solid island without a `client:*` directive: it emits
16
16
  // static HTML and ships no JS. Add `client:load`/`client:visible` only if the list
@@ -19,8 +19,8 @@
19
19
  // On the item type + ADR 0003 §6 ("infers from the collection's Zod schema"):
20
20
  // Louise's `CollectionConfig` is type-erased (`fields: Record<string,
21
21
  // FieldConfig>`), so the item shape can't be recovered from the collection VALUE.
22
- // Instead you type `items` from your data — per ADR 0001, a Zod-inferred query
23
- // result — and it flows through to the slot. Same guarantee (a typed `item`),
22
+ // Instead you type `items` from your data—per ADR 0001, a Zod-inferred query
23
+ // result—and it flows through to the slot. Same guarantee (a typed `item`),
24
24
  // sourced from the data rather than the erased config.
25
25
 
26
26
  import { For, type JSX } from "solid-js";
@@ -28,7 +28,7 @@ import { For, type JSX } from "solid-js";
28
28
  export interface CollectionProps<T> {
29
29
  /** The items to render, in order. The item type `T` is inferred from here. */
30
30
  items: readonly T[];
31
- /** Render one item — receives the fully-typed `item` and a reactive `index`. */
31
+ /** Render one item—receives the fully-typed `item` and a reactive `index`. */
32
32
  children: (item: T, index: () => number) => JSX.Element;
33
33
  /** Rendered when `items` is empty. */
34
34
  fallback?: JSX.Element;
@@ -1,5 +1,5 @@
1
1
  ---
2
- // `<Editable>` — the Astroid inline-edit primitive (ADR 0003, checklist item 1).
2
+ // `<Editable>`—the Astroid inline-edit primitive (ADR 0003, checklist item 1).
3
3
  //
4
4
  // It owns the `data-louise-*` marker contract so sites stop stamping it by hand:
5
5
  // in edit mode it renders your element with the markers the Louise client
@@ -8,25 +8,25 @@
8
8
  // the public HTML.
9
9
  //
10
10
  // Two flavors, per which surface the field lives on:
11
- // • PAGE field — pass `collection` + `key` + `field`; emits
11
+ // • PAGE field: pass `collection` + `key` + `field`; emits
12
12
  // `data-louise-field="<collection>:<key>:<field>"` (saved as a
13
13
  // versioned draft when the page is mounted with `versionedPageId`).
14
- // • SECTION field — pass `base` (this item's path, e.g. `"2"` or `"2.blocks.0"`)
14
+ // • SECTION field: pass `base` (this item's path, for example, `"2"` or `"2.blocks.0"`)
15
15
  // plus `field`; emits `data-louise-node="<base>.<field>"` for
16
16
  // the on-canvas section editor. `sfield` stays as the escape
17
- // hatch for a path you build yourself — an array entry:
17
+ // hatch for a path you build yourself—an array entry:
18
18
  // sfield={`${base}.items.${i}.title`}.
19
19
  //
20
20
  // Since ADR 0010 A2 this is the SAME attribute the section
21
21
  // boundary carries, one path deeper. The catalog says whether a
22
22
  // field is edited in place and with which editor, so `type` and
23
- // `multiline` no longer affect a section field's markup — they
23
+ // `multiline` no longer affect a section field's markup—they
24
24
  // are accepted for compatibility and ignored.
25
25
  //
26
26
  // The `base` form is what ADR 0005 §2 asks for: "a site author writes `<Editable
27
27
  // field="heading">` and never hand-stamps the deeper path". `<Section>` supplies
28
28
  // `base`, so the SAME component renders correctly whether it sits at the top level
29
- // (`"2.heading"`) or inside a block (`"2.blocks.0.heading"`) — the depth never
29
+ // (`"2.heading"`) or inside a block (`"2.blocks.0.heading"`)—the depth never
30
30
  // appears in the component's source, which is what makes a section component
31
31
  // reusable as a block component.
32
32
  //
@@ -38,11 +38,11 @@ import type { HTMLAttributes, HTMLTag } from "astro/types";
38
38
  interface Props extends HTMLAttributes<"div"> {
39
39
  /** The element to render. Default `"div"`. */
40
40
  as?: HTMLTag;
41
- /** PAGE-field mode: the Louise collection slug (e.g. `"pages"`). */
41
+ /** PAGE-field mode: the Louise collection slug (for example, `"pages"`). */
42
42
  collection?: string;
43
43
  /** PAGE-field mode: the row key/id the field is on. */
44
44
  key?: string | number;
45
- /** PAGE-field mode: the field name (e.g. `"title"`, `"body"`). Also the field
45
+ /** PAGE-field mode: the field name (for example, `"title"`, `"body"`). Also the field
46
46
  * name in SECTION mode, where it's joined onto `base`. */
47
47
  field?: string;
48
48
  /** SECTION-field mode: this item's path prefix (`"2"`, `"2.blocks.0"`),
@@ -87,7 +87,7 @@ let markers: Record<string, string> = {};
87
87
  if (editing) {
88
88
  if (sectionPath) {
89
89
  // ONE marker (ADR 0010 A2). This used to emit `data-louise-sfield` plus
90
- // `data-louise-type="richtext"` plus `data-louise-multiline` — three
90
+ // `data-louise-type="richtext"` plus `data-louise-multiline`—three
91
91
  // attributes describing a field the catalog already fully describes. The
92
92
  // editor now reads the field's type to know whether it's rich text and
93
93
  // whether it holds more than one line, so `type` and `multiline` are inert
@@ -1,18 +1,18 @@
1
1
  ---
2
- // `<JustifiedGallery>` — Flickr-style row-balanced photo grid (#257).
2
+ // `<JustifiedGallery>`—Flickr-style row-balanced photo grid (#257).
3
3
  //
4
4
  // Images keep their aspect ratios, rows fill the container exactly, and row
5
5
  // heights land near a target. CSS alone can't do it: `grid` wants uniform
6
6
  // tracks, and `columns` gives a masonry *column* flow where reading order runs
7
- // down the page instead of across it — wrong for a portfolio, and wrong for
7
+ // down the page instead of across it—wrong for a portfolio, and wrong for
8
8
  // keyboard/screen-reader order.
9
9
  //
10
10
  // Two layers, so it never depends on JavaScript to be usable:
11
11
  //
12
12
  // 1. SSR emits a flex-wrap floor. Each tile carries its declared aspect ratio
13
13
  // (from the media library's recorded dimensions) as `flex-grow`/`flex-basis`,
14
- // which already produces a justified, gap-correct grid with no layout shift
15
- // — that's what someone with JS blocked, or on the first paint before the
14
+ // which already produces a justified, gap-correct grid with no layout shift—that's
15
+ // what someone with JS blocked, or on the first paint before the
16
16
  // bundle lands, sees.
17
17
  // 2. On the client, once the images decode and their TRUE dimensions are known,
18
18
  // `justifyRows` recomputes exact rows and pins each tile's box. This is the
@@ -23,7 +23,7 @@
23
23
  // right, so the correction isn't visible as a jump on a well-populated library.
24
24
  //
25
25
  // CSP: the layout script is a normal (bundled) `<script>`, so Astro hashes it
26
- // into `script-src` — no `is:inline`, no `unsafe-inline`. Per-tile sizing is
26
+ // into `script-src`—no `is:inline`, no `unsafe-inline`. Per-tile sizing is
27
27
  // written as inline `style`, which is what the `style-src` rewrite in #253
28
28
  // exists for.
29
29
  import { DEFAULT_ASPECT, type GalleryItem } from "./justify.js";
@@ -129,7 +129,7 @@ const aspectOf = (item: GalleryItem) =>
129
129
  }
130
130
 
131
131
  /* Reveal-on-scroll. The visible state is the DEFAULT so that a browser with
132
- no IntersectionObserver — or no JS at all — shows every tile; the script
132
+ no IntersectionObserver—or no JS at all—shows every tile; the script
133
133
  opts tiles into the hidden state only once it can guarantee it will undo it. */
134
134
  .astroid-gallery--reveal .astroid-gallery__tile[data-reveal="pending"] {
135
135
  opacity: 0;
@@ -164,7 +164,7 @@ const aspectOf = (item: GalleryItem) =>
164
164
 
165
165
  const targetHeight = Number(root.dataset.targetHeight) || 260;
166
166
  const gap = Number(root.dataset.gap) || 8;
167
- // clientWidth excludes the scrollbar, which offsetWidth does not — using the
167
+ // clientWidth excludes the scrollbar, which offsetWidth does not—using the
168
168
  // wrong one overflows the row by the scrollbar width on Windows.
169
169
  const containerWidth = root.clientWidth;
170
170
 
@@ -242,7 +242,7 @@ const aspectOf = (item: GalleryItem) =>
242
242
 
243
243
  if (typeof ResizeObserver !== "undefined") {
244
244
  let frame = 0;
245
- // Coalesce to one layout per animation frame — a drag-resize fires this
245
+ // Coalesce to one layout per animation frame—a drag-resize fires this
246
246
  // continuously, and each run touches every tile's style.
247
247
  const observer = new ResizeObserver(() => {
248
248
  cancelAnimationFrame(frame);
@@ -1,5 +1,5 @@
1
1
  ---
2
- // `<MediaSlot>` — the responsive-image primitive (#257).
2
+ // `<MediaSlot>`—the responsive-image primitive (#257).
3
3
  //
4
4
  // One place that knows how to put a media-library image on a page correctly:
5
5
  // a width-descriptor `srcset` over Cloudflare Image Resizing, a `sizes` hint so
@@ -7,7 +7,7 @@
7
7
  // box so the image can't shift the page as it loads, and lazy/async decoding by
8
8
  // default.
9
9
  //
10
- // The math is not new — `louise-toolkit/media` already ships `cfImageSrcset`
10
+ // The math is not new—`louise-toolkit/media` already ships `cfImageSrcset`
11
11
  // (width descriptors, ratio-derived heights, cover-crop gravity, `format=auto`)
12
12
  // and `circleImage`. What every consuming site rebuilt by hand was the *component*
13
13
  // around it: the ratio box, the `sizes` string, the focal-point crop, and
@@ -15,13 +15,13 @@
15
15
  // Contentful Paint regression. That's what this owns.
16
16
  //
17
17
  // CSP: the focal/zoom knobs emit a data-driven inline `style` attribute. That is
18
- // exactly the case astroid's middleware rewrites `style-src` for (#253) — scripts
18
+ // exactly the case astroid's middleware rewrites `style-src` for (#253)—scripts
19
19
  // stay hash-pinned with no `unsafe-inline`.
20
20
  import { cfImageSrcset, circleImage } from "louise-toolkit/media";
21
21
  import type { HTMLAttributes } from "astro/types";
22
22
 
23
23
  interface Props extends Omit<HTMLAttributes<"img">, "src" | "srcset" | "sizes" | "width" | "height"> {
24
- /** Image URL — a media-library asset, or any URL on a resizing-enabled zone. */
24
+ /** Image URL—a media-library asset, or any URL on a resizing-enabled zone. */
25
25
  src: string;
26
26
  /**
27
27
  * Alternative text. REQUIRED, and `""` is a legitimate value: an image that
@@ -33,18 +33,18 @@ interface Props extends Omit<HTMLAttributes<"img">, "src" | "srcset" | "sizes" |
33
33
  alt: string;
34
34
  /**
35
35
  * The largest width, in CSS px, this image is displayed at on a 1× screen.
36
- * Drives the srcset ladder — not a hard render width.
36
+ * Drives the srcset ladder—not a hard render width.
37
37
  */
38
38
  width?: number;
39
39
  /**
40
- * The `sizes` attribute: how wide the image renders at each breakpoint, e.g.
40
+ * The `sizes` attribute: how wide the image renders at each breakpoint, for example,
41
41
  * `"(max-width: 640px) 100vw, 33vw"`. Without it the browser assumes `100vw`
42
42
  * and over-fetches on every multi-column layout, which is the single most
43
43
  * common way a "responsive" image ends up slower than a fixed one.
44
44
  */
45
45
  sizes?: string;
46
- /** Aspect ratio as `"w/h"` (e.g. `"16/9"`). Reserves the box and derives each
47
- * derivative's height so the crop matches what's shown — no wasted pixels. */
46
+ /** Aspect ratio as `"w/h"` (for example, `"16/9"`). Reserves the box and derives each
47
+ * derivative's height so the crop matches what's shown—no wasted pixels. */
48
48
  ratio?: string;
49
49
  /** `rect` (default) or `circle`. A circle uses a square focal crop + 2× retina. */
50
50
  shape?: "rect" | "circle";
@@ -57,7 +57,7 @@ interface Props extends Omit<HTMLAttributes<"img">, "src" | "srcset" | "sizes" |
57
57
  /** Encode quality, 1–100. Default 82. */
58
58
  quality?: number;
59
59
  /**
60
- * Focal point as percentages, for when `gravity: auto` picks wrong — a portrait
60
+ * Focal point as percentages, for when `gravity: auto` picks wrong—a portrait
61
61
  * where the subject sits high, say. Applied as `object-position`, so it's a
62
62
  * render-time framing choice and the same derivative is reused.
63
63
  */
@@ -65,7 +65,7 @@ interface Props extends Omit<HTMLAttributes<"img">, "src" | "srcset" | "sizes" |
65
65
  /** Scale factor inside the box, for a punch-in crop. Default 1. */
66
66
  zoom?: number;
67
67
  /**
68
- * `lazy` (default) or `eager`. Set `eager` — and `priority` — for anything
68
+ * `lazy` (default) or `eager`. Set `eager`—and `priority`—for anything
69
69
  * above the fold: lazy-loading the LCP image delays it by a full network
70
70
  * round-trip after layout.
71
71
  */
@@ -111,7 +111,7 @@ const sizesAttr = sizes ?? (circle ? `${size}px` : `${width}px`);
111
111
 
112
112
  // Framing lives on the <img>: `object-position` moves the visible window of a
113
113
  // cover-crop, `scale` punches in. Both are per-placement, so they must NOT go
114
- // through the CDN — that would mint a second derivative of the same source.
114
+ // through the CDN—that would mint a second derivative of the same source.
115
115
  const frame = [
116
116
  focal ? `object-position:${focal.x}% ${focal.y}%` : "",
117
117
  zoom !== 1 ? `transform:scale(${zoom})` : "",
@@ -132,7 +132,7 @@ const img = {
132
132
  src: derived.src,
133
133
  srcset: derived.srcset,
134
134
  // A circle's srcset uses DPR descriptors (`1x`/`2x`), which `sizes` does not
135
- // apply to — supplying it there would be ignored at best and confusing at worst.
135
+ // apply to—supplying it there would be ignored at best and confusing at worst.
136
136
  ...(circle ? {} : { sizes: sizesAttr }),
137
137
  alt,
138
138
  loading,
@@ -1,5 +1,5 @@
1
1
  ---
2
- // `<PortalShell>` — the account-area chrome: a sidebar of nav items and a main
2
+ // `<PortalShell>`—the account-area chrome: a sidebar of nav items and a main
3
3
  // column, collapsing to a burger on small screens.
4
4
  //
5
5
  // Theme-tokened, never brand-styled. Every colour here is a daisyUI token
@@ -14,11 +14,11 @@ import type { PortalNav } from "astroidjs";
14
14
 
15
15
  interface Props {
16
16
  nav: PortalNav;
17
- /** The signed-in user's role — decides which nav items are visible. */
17
+ /** The signed-in user's role—decides which nav items are visible. */
18
18
  role?: string | null;
19
19
  /** Current path, for the active highlight. Defaults to the rendered URL. */
20
20
  path?: string;
21
- /** Heading above the nav, e.g. the account holder's name. */
21
+ /** Heading above the nav, for example, the account holder's name. */
22
22
  title?: string;
23
23
  }
24
24
 
@@ -26,7 +26,7 @@ const { nav, role, path, title } = Astro.props;
26
26
  const current = path ?? Astro.url.pathname;
27
27
 
28
28
  // Filtered by role BEFORE render: an item the viewer can't reach is never
29
- // drawn, rather than drawn and then 403'd — which reads as a broken link.
29
+ // drawn, rather than drawn and then 403'd—which reads as a broken link.
30
30
  const items = nav.forRole(role);
31
31
  const active = nav.activeFor(current);
32
32
  ---
@@ -1,10 +1,10 @@
1
1
  ---
2
- // `<RegisterSW>` — registers the PWA service worker.
2
+ // `<RegisterSW>`—registers the PWA service worker.
3
3
  //
4
4
  // A bundled `<script>`, never an inline one: Astro hashes it into `script-src`,
5
5
  // so this works under the strict CSP without `unsafe-inline`. That's also why
6
6
  // the scope rides on a `data-` attribute rather than being interpolated into
7
- // the script — `define:vars` forces `is:inline`, whose content varies per
7
+ // the script—`define:vars` forces `is:inline`, whose content varies per
8
8
  // render and therefore can't be hashed.
9
9
  //
10
10
  // Registration is best-effort by design. A service worker is an enhancement:
@@ -36,7 +36,7 @@ const { scope = "/", src = "/sw.js" } = Astro.props;
36
36
  navigator.serviceWorker
37
37
  .register(el.dataset.src ?? "/sw.js", { scope: el.dataset.scope ?? "/" })
38
38
  .catch((error) => {
39
- // Not fatal — the app works without it. Logged rather than swallowed so
39
+ // Not fatal—the app works without it. Logged rather than swallowed so
40
40
  // a scope/MIME misconfiguration is findable instead of just "the PWA
41
41
  // doesn't install and nobody knows why".
42
42
  console.warn("[astroid] service worker registration failed", error);
@@ -1,10 +1,10 @@
1
1
  ---
2
- // `<Section>` — the section-library dispatcher (ADR 0003 item 2, on the ADR 0005
2
+ // `<Section>`—the section-library dispatcher (ADR 0003 item 2, on the ADR 0005
3
3
  // model).
4
4
  //
5
5
  // It renders ONE stored `SectionItem`: dispatch on `_type`, hand the component
6
6
  // its `base` path, and let the component own every pixel. What changed from the
7
- // old `kind`-prop version is where the truth lives — the item is data loaded from
7
+ // old `kind`-prop version is where the truth lives—the item is data loaded from
8
8
  // the page's `sections` column, not props a template hand-wrote, so what renders
9
9
  // is exactly what the on-canvas editor edits in place.
10
10
  //
@@ -14,7 +14,7 @@
14
14
  // • the FIELDS are stamped by `<Editable base={base} field="…">` inside each
15
15
  // component, because only the component knows which of its text nodes are
16
16
  // editable. Since A2 those carry the SAME `data-louise-node` attribute, one
17
- // path deeper — the catalog says whether a field is edited in place, so the
17
+ // path deeper—the catalog says whether a field is edited in place, so the
18
18
  // marker no longer has to.
19
19
  //
20
20
  // Blocks recurse through this same component with a deeper `base`. That's the
@@ -51,8 +51,8 @@ interface Props {
51
51
  /**
52
52
  * `_type` → render component. A map rather than a chain of `{type === "x" && …}`
53
53
  * for one reason that matters: it can be checked. A catalog entry whose type is
54
- * missing here renders NOTHING — a silent hole in the page rather than an error
55
- * — so the test suite asserts these keys and the catalog's are the same set.
54
+ * missing here renders NOTHING—a silent hole in the page rather than an error—so
55
+ * the test suite asserts these keys and the catalog's are the same set.
56
56
  */
57
57
  const COMPONENTS: Record<string, ((props: SectionRenderProps) => unknown) | undefined> = {
58
58
  hero: Hero,
@@ -82,12 +82,12 @@ const type = String(item._type);
82
82
  const blocks = Array.isArray(item.blocks) ? item.blocks : [];
83
83
 
84
84
  // The boundary marker the on-canvas chrome draws its ring on. It is the item's
85
- // path, verbatim — pre-0010 this line string-sniffed `base` for ".blocks." to pick
85
+ // path, verbatim—pre-0010 this line string-sniffed `base` for ".blocks." to pick
86
86
  // between two attribute names, which was the three-attribute editing model leaking
87
87
  // into an otherwise uniform recursive render (ADR 0010, Context). The editor
88
88
  // resolves what a path CAN do from the catalog; the render just says where it is.
89
89
  //
90
- // `class="contents"` keeps this wrapper out of layout entirely — the ring is drawn
90
+ // `class="contents"` keeps this wrapper out of layout entirely—the ring is drawn
91
91
  // as a box-shadow on the element itself, so an extra block box here would offset
92
92
  // every section's spacing for the sake of an attribute.
93
93
  const boundary = editing ? { "data-louise-node": base } : {};
@@ -95,7 +95,7 @@ const boundary = editing ? { "data-louise-node": base } : {};
95
95
  const shared = { item, base, edit: editing, mediaMeta };
96
96
 
97
97
  // Resolved in the frontmatter so the template is one dynamic tag rather than a
98
- // ladder of comparisons. The map itself is the dispatch check — an unknown
98
+ // ladder of comparisons. The map itself is the dispatch check—an unknown
99
99
  // `_type` (legitimate mid-migration, and already rejected on write) simply
100
100
  // finds nothing and renders nothing, which is why the lookup is typed as
101
101
  // possibly-undefined rather than asserted.
@@ -1,10 +1,10 @@
1
1
  ---
2
- // `<Sections>` — render a page's whole `sections` array.
2
+ // `<Sections>`—render a page's whole `sections` array.
3
3
  //
4
4
  // This exists for one reason `<Section>` can't serve: the media lookup has to
5
5
  // span the page. A section stores an image as a URL, but the `alt`/`caption` an
6
6
  // editor typed live on the media ASSET, so rendering images correctly means
7
- // joining back to the registry — and doing that per image is thirty D1
7
+ // joining back to the registry—and doing that per image is thirty D1
8
8
  // round-trips for one gallery. Here it's ONE bounded `IN (...)` per page,
9
9
  // threaded down as `mediaMeta`.
10
10
  //
@@ -16,7 +16,7 @@ import { astroidSectionCatalog } from "./sections.js";
16
16
  import Section from "./Section.astro";
17
17
 
18
18
  interface Props {
19
- /** The page's stored `sections` array. Untrusted JSON — a non-array, or an
19
+ /** The page's stored `sections` array. Untrusted JSON—a non-array, or an
20
20
  * item with an unknown `_type`, renders nothing rather than throwing. */
21
21
  items: unknown;
22
22
  /** D1, for the media alt/caption lookup. Omit to skip it entirely. */
@@ -24,7 +24,7 @@ interface Props {
24
24
  /** Public media base, for turning a stored URL back into a registry key.
25
25
  * Matches the site's `MEDIA_URL`. */
26
26
  mediaBase?: string;
27
- /** Override the catalog — a site that extends Astroid's set passes its own. */
27
+ /** Override the catalog—a site that extends Astroid's set passes its own. */
28
28
  catalog?: SectionCatalog;
29
29
  /** Force edit markers on/off. Defaults to `Astro.locals.editMode`. */
30
30
  edit?: boolean;
@@ -42,7 +42,7 @@ const {
42
42
  } = Astro.props;
43
43
 
44
44
  // A page with no sections column yet, or a malformed one, is a normal state
45
- // (nothing seeded) — not an error worth failing the render over.
45
+ // (nothing seeded)—not an error worth failing the render over.
46
46
  const list: SectionItem[] = Array.isArray(items) ? (items as SectionItem[]) : [];
47
47
 
48
48
  // One lookup for the page. Skipped when the caller already did it (a layout that
@@ -54,7 +54,7 @@ const mediaMeta =
54
54
  {/*
55
55
  The host element. `mountSections(el, opts)` takes the element to scan for
56
56
  `[data-louise-node]` markers and to hang on-canvas chrome off, so rendering
57
- it here — rather than asking every page to remember a wrapper — is what makes
57
+ it here—rather than asking every page to remember a wrapper—is what makes
58
58
  `<Sections>` self-sufficient: drop it on a page and the editor can find it.
59
59
  A plain block wrapper, not `display: contents`, because the chrome positions
60
60
  toolbars against a real box.
@@ -1,11 +1,11 @@
1
1
  ---
2
- // `<Seo>` — the settings-driven `<head>` block: title (templated), description,
2
+ // `<Seo>`—the settings-driven `<head>` block: title (templated), description,
3
3
  // canonical, robots, Open Graph, and the Twitter/X card.
4
4
  //
5
5
  // Tags are emitted directly rather than through a wrapper library. There are
6
6
  // about fifteen of them, the shapes are frozen by the OG and Twitter specs, and
7
7
  // owning them means the resolution rules (see `resolvePageSeo`) are the ONLY
8
- // place a value can come from — which is what makes the layer predictable.
8
+ // place a value can come from—which is what makes the layer predictable.
9
9
  //
10
10
  // JSON-LD is not here: it's a separate concern with different escaping rules and
11
11
  // its own component (`<StructuredData>`).
@@ -40,7 +40,7 @@ const seo = resolvePageSeo(settings, page, {
40
40
  {seo.noindex && <meta name="robots" content="noindex, nofollow" />}
41
41
 
42
42
  <meta property="og:type" content={seo.ogType} />
43
- {/* The bare title, not the templated one — og:site_name already carries the brand. */}
43
+ {/* The bare title, not the templated one—og:site_name already carries the brand. */}
44
44
  <meta property="og:title" content={seo.bareTitle} />
45
45
  <meta property="og:url" content={seo.canonical} />
46
46
  {seo.description && <meta property="og:description" content={seo.description} />}
@@ -1,5 +1,5 @@
1
1
  ---
2
- // `<StageBar>` — a pipeline's progress as a segmented bar.
2
+ // `<StageBar>`—a pipeline's progress as a segmented bar.
3
3
  //
4
4
  // Generalized from ghostfire's six-stage floor tracker, which hard-coded the
5
5
  // segment count, the corner radii, the brand hex codes, and a mascot image.
@@ -17,7 +17,7 @@ interface Props {
17
17
  stage: number;
18
18
  /**
19
19
  * An optional marker (an emoji, a mascot glyph) that rides above the active
20
- * segment. Off by default — the reference's bobbing ghost is brand, not
20
+ * segment. Off by default—the reference's bobbing ghost is brand, not
21
21
  * mechanism.
22
22
  */
23
23
  marker?: string;
@@ -81,7 +81,7 @@ const markerLeft = complete ? null : `${(((current + 0.5) / stages.length) * 100
81
81
  transition: background-color 300ms ease;
82
82
  }
83
83
  /* Round only the outer ends, so the bar reads as one track rather than a row
84
- of separate pills — at any segment count. */
84
+ of separate pills—at any segment count. */
85
85
  .astroid-stagebar__step:first-child .astroid-stagebar__seg {
86
86
  border-start-start-radius: 999px;
87
87
  border-end-start-radius: 999px;
@@ -1,5 +1,5 @@
1
1
  ---
2
- // `<StructuredData>` — the schema.org JSON-LD `@graph` for rich results and AI
2
+ // `<StructuredData>`—the schema.org JSON-LD `@graph` for rich results and AI
3
3
  // answer surfaces: the business (its `@type` from the archetype), the WebSite,
4
4
  // and optionally the entity this page is about.
5
5
  //
@@ -16,7 +16,7 @@ interface Props {
16
16
  /** Absolute origin serving this page. Defaults to the current origin, which
17
17
  * keeps a preview deploy describing itself rather than the live domain. */
18
18
  siteUrl?: string;
19
- /** The thing this page is about — a Product, VisualArtwork, Article. */
19
+ /** The thing this page is about—a Product, VisualArtwork, Article. */
20
20
  entity?: JsonLdNode;
21
21
  }
22
22