@stnd/core 0.22.1 → 0.22.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -2,21 +2,24 @@
2
2
  title: "@stnd/core"
3
3
  aliases: []
4
4
  created: 2026-06-20 13:56
5
- modified: 2026-07-15 10:20
5
+ modified: 2026-09-16T17:31:40.687Z
6
6
  last_audited: 2026-07-14
7
7
  audit_interval_days: 90
8
8
  next_audit: 2026-10-12
9
9
  audit_priority: 3
10
10
  maturity: tree
11
11
  mode: read
12
- publish: false
12
+ publish: true
13
13
  status: active
14
14
  tags:
15
15
  - package
16
16
  - stnd
17
17
  theme: kernel
18
18
  type: package
19
- visibility: private
19
+ visibility: public
20
+ garden-url: https://standard.garden/@francis/readme
21
+ garden-short: https://stnd.gd/DT0h6O
22
+ permalink: readme
20
23
  ---
21
24
 
22
25
  # @[stnd](../README)/core
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@stnd/core",
3
- "version": "0.22.1",
3
+ "version": "0.22.2",
4
4
  "description": "A fine-art typography framework implementing classical design principles, mathematical precision with the golden ratio, and micro-typography rules inspired by the masters of print design.",
5
5
  "author": "Francis Fontaine",
6
6
  "license": "MIT",
@@ -26,6 +26,7 @@
26
26
  "svelte": "./Hook.svelte",
27
27
  "default": "./Hook.astro"
28
28
  },
29
+ "./module-filter": "./module-filter.js",
29
30
  "./loader": "./src/loader.js",
30
31
  "./virtual": "./src/virtual.js",
31
32
  "./src/dev-toolbar/*": "./src/dev-toolbar/*",
@@ -53,24 +54,24 @@
53
54
  "dependencies": {
54
55
  "@astrojs/sitemap": "^3.7.3",
55
56
  "@astrojs/svelte": "9.0.1",
56
- "astro": "7.1.3",
57
+ "astro": "7.3.1",
57
58
  "glob": "^13.0.6",
58
- "svelte": "5.56.4",
59
+ "svelte": "5.57.0",
59
60
  "zod": "^4.4.3",
60
61
  "@stnd/client": "0.5.0",
61
62
  "@stnd/cloudflare": "0.5.0",
62
63
  "@stnd/fonts": "0.5.0",
63
64
  "@stnd/icon": "0.2.0",
64
- "@stnd/lab": "1.0.0",
65
- "@stnd/launcher": "1.0.0",
66
- "@stnd/layout": "0.5.0",
65
+ "@stnd/lab": "1.0.1",
66
+ "@stnd/launcher": "1.0.1",
67
+ "@stnd/layout": "0.5.1",
67
68
  "@stnd/log": "0.5.0",
68
- "@stnd/modules": "0.5.1",
69
+ "@stnd/modules": "0.5.2",
69
70
  "@stnd/press": "0.7.0",
70
71
  "@stnd/store": "0.5.0",
71
72
  "@stnd/styles": "0.5.2",
72
73
  "@stnd/themes": "0.5.1",
73
- "@stnd/ui": "0.5.0",
74
+ "@stnd/ui": "0.5.1",
74
75
  "@stnd/utils": "0.5.0"
75
76
  },
76
77
  "devDependencies": {
package/src/loader.js CHANGED
@@ -60,6 +60,19 @@ const ModuleManifestSchema = z
60
60
  dependencies: z.array(z.string()).optional(),
61
61
  config: z.record(z.any()).optional(),
62
62
  routes: z.array(RouteSchema).optional(),
63
+ // Where this module is allowed to exist. `domain` lists the canonical
64
+ // domain(s) it belongs to; the module's routes and hooks disappear
65
+ // everywhere else. The framework never resolves a hostname itself — the
66
+ // app tells the guard which canonical domain the current request is on,
67
+ // so "standard.garden" still matches while developing on localhost.
68
+ // Absent = the module exists everywhere (the default for every module
69
+ // that doesn't opt in).
70
+ filter: z
71
+ .object({
72
+ domain: z.union([z.string(), z.array(z.string())]).optional(),
73
+ })
74
+ .passthrough()
75
+ .optional(),
63
76
  hooks: z.record(z.union([HookSchema, z.array(HookSchema)])).optional(),
64
77
  styles: z.array(z.string()).optional(),
65
78
  scripts: z.array(ScriptSchema).optional(),
@@ -183,7 +196,7 @@ export async function loadModules({
183
196
  const task = tasks[index];
184
197
  if (result.status === "rejected") {
185
198
  log.fatal(
186
- `Failed to load module: ${task.resolvedPath}\nSee: https://stnd.build/manual/5-reference/module-load-failure`,
199
+ `Failed to load module: ${task.resolvedPath}\nSee: https://stnd.build/manual/diagnostic/module-load-failure`,
187
200
  result.reason,
188
201
  );
189
202
  }
@@ -196,7 +209,7 @@ export async function loadModules({
196
209
  if (task.sourceModule) {
197
210
  errorMsg += `\n Required by: "${task.sourceModule}" module`;
198
211
  }
199
- errorMsg += `\nSee: https://stnd.build/manual/5-reference/core-is-not-a-module`;
212
+ errorMsg += `\nSee: https://stnd.build/manual/diagnostic/core-is-not-a-module`;
200
213
  log.fatal(errorMsg);
201
214
  }
202
215
 
@@ -210,7 +223,7 @@ export async function loadModules({
210
223
  if (task.sourceModule) {
211
224
  errorMsg += ` Required by: "${task.sourceModule}" module\n`;
212
225
  }
213
- errorMsg += `See: https://stnd.build/manual/5-reference/content-validation`;
226
+ errorMsg += `See: https://stnd.build/manual/diagnostic/content-validation`;
214
227
  log.fatal(errorMsg);
215
228
  }
216
229
 
@@ -225,7 +238,7 @@ export async function loadModules({
225
238
  `\n Path 1: ${existingPath}` +
226
239
  `\n Path 2: ${resolvedPath}` +
227
240
  `\n Every module must have a unique ID.` +
228
- `\n See: https://stnd.build/manual/5-reference/module-duplicate-id`,
241
+ `\n See: https://stnd.build/manual/diagnostic/module-duplicate-id`,
229
242
  );
230
243
  }
231
244
  }
@@ -276,7 +289,7 @@ export async function loadModules({
276
289
 
277
290
  if (!idToPath.has(depId)) {
278
291
  log.fatal(
279
- `Module "${moduleId}" has unresolved dependency: "${rawDep}"\nSee: https://stnd.build/manual/5-reference/module-dependency-unresolved`,
292
+ `Module "${moduleId}" has unresolved dependency: "${rawDep}"\nSee: https://stnd.build/manual/diagnostic/module-dependency-unresolved`,
280
293
  );
281
294
  }
282
295
 
@@ -302,13 +315,17 @@ export async function loadModules({
302
315
 
303
316
  if (sortedIds.length !== allIds.length) {
304
317
  log.fatal(
305
- "Circular dependency detected in module graph.\nSee: https://stnd.build/manual/5-reference/module-dependency-circular",
318
+ "Circular dependency detected in module graph.\nSee: https://stnd.build/manual/diagnostic/module-dependency-circular",
306
319
  );
307
320
  }
308
321
 
309
322
  // 3. Process
310
323
  const modules = {
311
324
  runtime: [],
325
+ // Server-only: {id, filter, routes} for modules declaring `filter.domain`.
326
+ // See the comment at the injectRoute() call site for why this exists
327
+ // separately from `runtime` (which is the client-bundled payload).
328
+ domainFilters: [],
312
329
  config: [],
313
330
  aliases: {},
314
331
  ui: {},
@@ -405,6 +422,22 @@ export async function loadModules({
405
422
  });
406
423
  }
407
424
 
425
+ // A module declaring `filter: { domain }` needs its route PATTERNS at
426
+ // request time, server-side, to turn "hidden in the UI" into "the URL
427
+ // itself answers 404" (routes are injected once here, at build time, so
428
+ // hiding a button never stops the URL from working). This is collected
429
+ // separately from `modules.runtime` because that array is the CLIENT
430
+ // payload and deliberately strips `routes` (see the destructuring a few
431
+ // hundred lines down) — bundling every route pattern to the browser for
432
+ // a check only the server needs would be pure waste.
433
+ if (def.filter) {
434
+ modules.domainFilters.push({
435
+ id: def.id,
436
+ filter: def.filter,
437
+ routes: def.routes || [],
438
+ });
439
+ }
440
+
408
441
  if (def.styles) {
409
442
  def.styles.forEach((style) => {
410
443
  // Preserve external URLs (https:// or protocol-relative //) as-is so they don't get path-resolved into local files.
@@ -520,7 +553,7 @@ export async function loadModules({
520
553
  `\n Module "${def.id}" registered a ${kind} entry (${entryPath}), but "${hookName}" was already classified as ${previousKind} by an earlier module.` +
521
554
  `\n A hook name must consistently resolve to one kind: logic hooks are ".js/.ts" entries not prefixed "launcher:" or suffixed ":action"; everything else is a UI/action zone.` +
522
555
  `\n Rename this hook or make its entry type consistent with the other registrations.` +
523
- `\n See: https://stnd.build/manual/5-reference/hook-kind-mismatch`,
556
+ `\n See: https://stnd.build/manual/diagnostic/hook-kind-mismatch`,
524
557
  );
525
558
  }
526
559
  hookKindByName.set(hookName, kind);
@@ -545,7 +578,7 @@ export async function loadModules({
545
578
  `Hook "${hookName}" (module "${def.id}") sets "server: true", but this hook classifies as a UI/action zone contribution, which has no server/client split.` +
546
579
  `\n "server: true" only has an effect on logic hooks (".js/.ts" entries not prefixed "launcher:" or suffixed ":action").` +
547
580
  `\n Remove "server: true", or rename this hook off the "launcher:"/":action" pattern if it must stay out of the client bundle.` +
548
- `\n See: https://stnd.build/manual/5-reference/hook-server-flag-ignored`,
581
+ `\n See: https://stnd.build/manual/diagnostic/hook-server-flag-ignored`,
549
582
  );
550
583
  }
551
584
  if (!modules.ui[hookName]) modules.ui[hookName] = [];
package/src/virtual.js CHANGED
@@ -96,6 +96,21 @@ export function generateModulesModule(runtimeModules) {
96
96
  );
97
97
  }
98
98
 
99
+ /**
100
+ * Server-only virtual module carrying route patterns for domain-filtered
101
+ * modules (`filter: { domain }` in a manifest). Deliberately separate from
102
+ * `generateModulesModule`/`generateModulesMetadataModule`, whose payload is
103
+ * bundled to the client — route patterns have no reason to ship there.
104
+ * Consumed by the app's own request middleware to 404 a build-time-injected
105
+ * route that shouldn't answer on the current domain.
106
+ *
107
+ * @param {{id: string, filter: object, routes: Array<{path: string}>}[]} domainFilterModules
108
+ * @returns {string} module source
109
+ */
110
+ export function generateModuleDomainsModule(domainFilterModules = []) {
111
+ return `export const modules = ${JSON.stringify(domainFilterModules)};`;
112
+ }
113
+
99
114
  export function generateModulesMetadataModule(runtimeModules) {
100
115
  // Build a sanitized client payload with functions removed (safe for JSON serialization)
101
116
  const sanitizedModules = runtimeModules.map((m) => {
package/standard.js CHANGED
@@ -16,6 +16,7 @@ import {
16
16
  generateConfigModule,
17
17
  generateModulesModule,
18
18
  generateModulesMetadataModule,
19
+ generateModuleDomainsModule,
19
20
  generateStylesModule,
20
21
  generateScriptsModule,
21
22
  generateComponentsModule,
@@ -225,7 +226,7 @@ export default function standard(options = {}) {
225
226
  log.warn(
226
227
  `Modules define actions but 'src/actions/index.ts' is missing.\n` +
227
228
  ` Actions will trigger 404 errors without this bridge file.\n` +
228
- ` See fix: https://stnd.build/manual/5-reference/actions-404`,
229
+ ` See fix: https://stnd.build/manual/diagnostic/actions-404`,
229
230
  );
230
231
  }
231
232
  }
@@ -344,6 +345,8 @@ export default function standard(options = {}) {
344
345
  return generateModulesModule(modules.runtime);
345
346
  if (id === "\0virtual-stnd/modules-metadata")
346
347
  return generateModulesMetadataModule(modules.runtime);
348
+ if (id === "\0virtual-stnd/module-domains")
349
+ return generateModuleDomainsModule(modules.domainFilters);
347
350
  if (id === "\0virtual-stnd/components")
348
351
  return generateComponentsModule(modules.ui);
349
352
  if (id === "\0virtual-stnd/middleware")
@@ -377,37 +380,6 @@ export default function standard(options = {}) {
377
380
  .filter(Boolean),
378
381
  ];
379
382
 
380
- const astro7Workarounds = {
381
- name: "stnd-astro-7-workarounds",
382
- enforce: "post",
383
- configEnvironment(name, config) {
384
- if (name === "prerender") {
385
- config.build ||= {};
386
- config.build.rollupOptions ||= {};
387
- config.build.rollupOptions.output = {
388
- ...config.build.rollupOptions.output,
389
- entryFileNames: "prerender-entry.[hash].mjs",
390
- format: "esm"
391
- };
392
- }
393
- },
394
- options(rollupOptions) {
395
- if (this.environment?.name === "client") {
396
- const envConfig = this.environment.config;
397
- const rolldownInput = envConfig?.build?.rolldownOptions?.input;
398
- const isDefaultInput = !rollupOptions.input ||
399
- rollupOptions.input === "index.html" ||
400
- (typeof rollupOptions.input === "string" && rollupOptions.input.endsWith("index.html")) ||
401
- (Array.isArray(rollupOptions.input) && rollupOptions.input.length === 0);
402
-
403
- if (rolldownInput && isDefaultInput) {
404
- rollupOptions.input = rolldownInput;
405
- }
406
- }
407
- return rollupOptions;
408
- }
409
- };
410
-
411
383
  updateConfig({
412
384
  // Speed is a respect (Standard value #2): every Standard app prefetches
413
385
  // links in the viewport and speculatively prerenders them. These are
@@ -425,7 +397,7 @@ export default function standard(options = {}) {
425
397
  },
426
398
  integrations: moduleIntegrations,
427
399
  vite: {
428
- plugins: [virtualModuleStnd, astro7Workarounds],
400
+ plugins: [virtualModuleStnd],
429
401
  resolve: {
430
402
  alias: aliases,
431
403
  dedupe: ["svelte"],