@warpgogol/werkstatt-shared 0.2.0 → 0.3.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.
@@ -0,0 +1,22 @@
1
+ /*
2
+ <MODULE_CONTRACT>
3
+ <purpose>Public entrypoint for @warpgogol/werkstatt-shared/content — exports
4
+ markdown frontmatter utilities and system manifest loading (RFC-0868).</purpose>
5
+ <non-goals>
6
+ <item>Do not import from app-specific packages or stack plugins.</item>
7
+ </non-goals>
8
+ </MODULE_CONTRACT>
9
+ <CHANGE_SUMMARY>
10
+ <item>RFC-0868: extracted from werkstatt-site/src/content/index.ts.</item>
11
+ </CHANGE_SUMMARY>
12
+ */
13
+
14
+ export { parseMarkdownFrontmatter, stringifyMarkdownFrontmatter } from "./markdown-frontmatter.ts";
15
+ export type { ParsedFrontmatter } from "./markdown-frontmatter.ts";
16
+ export {
17
+ loadSystemManifest,
18
+ loadSystemManifestSync,
19
+ isUsingSystemMd,
20
+ isUsingSystemMdSync,
21
+ } from "./system-manifest.ts";
22
+ export type { SystemManifest, SystemManifestLoadResult } from "./system-manifest.ts";
@@ -0,0 +1,46 @@
1
+ /*
2
+ <MODULE_CONTRACT>
3
+ <purpose>Parse and stringify Markdown frontmatter using YAML. Extracts frontmatter
4
+ data and content from --- delimited blocks. Stack-agnostic utility used by both
5
+ engine and site plugin (RFC-0868).</purpose>
6
+ <non-goals>
7
+ <item>Do not handle raw Markdown parsing beyond frontmatter extraction.</item>
8
+ <item>Do not manage file I/O or transport mechanisms for Markdown files.</item>
9
+ </non-goals>
10
+ </MODULE_CONTRACT>
11
+ <CHANGE_SUMMARY>
12
+ <item>RFC-0868: extracted from werkstatt-site/src/content/markdown-frontmatter.ts.</item>
13
+ </CHANGE_SUMMARY>
14
+ */
15
+
16
+ import YAML from "yaml";
17
+
18
+ export type ParsedFrontmatter = {
19
+ content: string;
20
+ data: Record<string, unknown>;
21
+ };
22
+
23
+ export function parseMarkdownFrontmatter(source: string): ParsedFrontmatter {
24
+ const match = source.match(/^---\r?\n([\s\S]*?)\r?\n---\r?\n?([\s\S]*)$/);
25
+
26
+ if (!match) {
27
+ return {
28
+ content: source,
29
+ data: {},
30
+ };
31
+ }
32
+
33
+ return {
34
+ content: match[2] ?? "",
35
+ data: (YAML.parse(match[1]) ?? {}) as Record<string, unknown>,
36
+ };
37
+ }
38
+
39
+ export function stringifyMarkdownFrontmatter(
40
+ content: string,
41
+ data: Record<string, unknown>,
42
+ ): string {
43
+ const normalizedContent = content.replace(/^\s+/, "").replace(/\s+$/, "");
44
+ const frontmatter = YAML.stringify(data).trimEnd();
45
+ return `---\n${frontmatter}\n---\n\n${normalizedContent}\n`;
46
+ }
@@ -0,0 +1,182 @@
1
+ /*
2
+ <MODULE_CONTRACT>
3
+ <purpose>Facilitates loading and parsing of the canonical system.md manifest per RFC-0047.
4
+ Stack-agnostic utility used by both engine and site plugin (RFC-0868).</purpose>
5
+ <non-goals>
6
+ <item>Do not validate system manifest content (handled by validators).</item>
7
+ <item>Do not handle system manifest generation or modification.</item>
8
+ </non-goals>
9
+ </MODULE_CONTRACT>
10
+ <CHANGE_SUMMARY>
11
+ <item>RFC-0868: extracted from werkstatt-site/src/content/system-manifest.ts.</item>
12
+ </CHANGE_SUMMARY>
13
+ */
14
+
15
+ import { readFile, access } from "node:fs/promises";
16
+ import { readFileSync, accessSync } from "node:fs";
17
+ import { join } from "node:path";
18
+ import { parseMarkdownFrontmatter } from "./markdown-frontmatter.ts";
19
+
20
+ export interface SystemManifest {
21
+ app: string;
22
+ version: string;
23
+ identity: {
24
+ systemStar: string;
25
+ biome: string;
26
+ tagline: string;
27
+ domain?: string;
28
+ /** RFC-0087: Per-app default CTA pageId for shared header and final-cta. */
29
+ ctaTarget?: string;
30
+ /** RFC-0096: Operator details consumed by legal.scaffold to fill Impressum / Datenschutz stubs. */
31
+ legal?: {
32
+ responsibleName?: string;
33
+ address?: string;
34
+ email?: string;
35
+ };
36
+ };
37
+ i18n?: {
38
+ default: string;
39
+ supported: Record<string, unknown>;
40
+ };
41
+ constellations: string[];
42
+ clientEditable: string[];
43
+ sharedContext?: {
44
+ requiredPageIds: string[];
45
+ };
46
+ pages: Array<{
47
+ pageId: string;
48
+ routes?: Record<string, string>;
49
+ route?: string;
50
+ /** RFC-0097: explicit locale opt-in; the page exists only in these locales. */
51
+ locales?: string[];
52
+ cosmicStar: string;
53
+ planets: Array<{
54
+ cosmicPlanet: string;
55
+ pin: string;
56
+ }>;
57
+ }>;
58
+ growth: {
59
+ vendor: {
60
+ adapter: string;
61
+ options: Record<string, unknown>;
62
+ };
63
+ funnels: unknown[];
64
+ experiments: unknown[];
65
+ };
66
+ release: {
67
+ passport: {
68
+ enabled: boolean;
69
+ indexable: boolean;
70
+ keyVersion: string;
71
+ heartbeatUrl: string;
72
+ };
73
+ };
74
+ /**
75
+ * RFC-0211 Content Knowledge Lifecycle policy. Mirrors the `knowledge` block in
76
+ * the ontology systemManifestSchema; declared here so the CKL kernel commands
77
+ * (content.freshness.validate RFC-0213, content.plan.build RFC-0216) read it
78
+ * with real types instead of structural casts.
79
+ */
80
+ knowledge?: {
81
+ freshness?: {
82
+ soonWindowDays?: number;
83
+ critical?: Array<{ match: string; criticality: "advisory" | "important" | "blocking" }>;
84
+ };
85
+ derivation?: {
86
+ critical?: Array<{ match: string; criticality: "advisory" | "important" | "blocking" }>;
87
+ };
88
+ plan?: {
89
+ leadTimeDays?: number;
90
+ defaultOwner?: string;
91
+ criticalityMap?: Array<{ match: string; criticality: "advisory" | "important" | "blocking" }>;
92
+ };
93
+ };
94
+ /** RFC-0487: Business model declaration. Closed enum — currently only "b2b-only". */
95
+ businessModel?: "b2b-only";
96
+ /** RFC-0487/RFC-0509: Retired page routes — 410 Gone tombstones or 301 redirects. */
97
+ retiredRoutes?: Array<{ slug: string; status: 410 } | { slug: string; status: 301; to: string }>;
98
+ /** UI-level rendering toggles for split-list column order. */
99
+ ui?: {
100
+ responsibilityBlock?: {
101
+ swapOrder?: boolean;
102
+ };
103
+ };
104
+ }
105
+
106
+ export interface SystemManifestLoadResult {
107
+ manifest: SystemManifest;
108
+ source: "system.md";
109
+ filePath: string;
110
+ }
111
+
112
+ /**
113
+ * Loads and parses the canonical system.md manifest from src/content/system.md.
114
+ *
115
+ * @param contentDirectory The src/content directory path
116
+ * @returns Parsed system manifest with source information
117
+ */
118
+ export async function loadSystemManifest(
119
+ contentDirectory: string,
120
+ ): Promise<SystemManifestLoadResult> {
121
+ const systemMdPath = join(contentDirectory, "system.md");
122
+ await access(systemMdPath);
123
+ const content = await readFile(systemMdPath, "utf8");
124
+ const parsed = parseMarkdownFrontmatter(content);
125
+
126
+ return {
127
+ manifest: parsed.data as unknown as SystemManifest,
128
+ source: "system.md",
129
+ filePath: systemMdPath,
130
+ };
131
+ }
132
+
133
+ /**
134
+ * Synchronous version of loadSystemManifest for contexts where async is not available.
135
+ *
136
+ * @param contentDirectory The src/content directory path
137
+ * @returns Parsed system manifest with source information
138
+ */
139
+ export function loadSystemManifestSync(contentDirectory: string): SystemManifestLoadResult {
140
+ const systemMdPath = join(contentDirectory, "system.md");
141
+ accessSync(systemMdPath);
142
+ const content = readFileSync(systemMdPath, "utf8");
143
+ const parsed = parseMarkdownFrontmatter(content);
144
+
145
+ return {
146
+ manifest: parsed.data as unknown as SystemManifest,
147
+ source: "system.md",
148
+ filePath: systemMdPath,
149
+ };
150
+ }
151
+
152
+ /**
153
+ * Checks if the canonical system.md manifest exists.
154
+ *
155
+ * @param contentDirectory The src/content directory path
156
+ * @returns true if src/content/system.md exists
157
+ */
158
+ export async function isUsingSystemMd(contentDirectory: string): Promise<boolean> {
159
+ const systemMdPath = join(contentDirectory, "system.md");
160
+ try {
161
+ await access(systemMdPath);
162
+ return true;
163
+ } catch {
164
+ return false;
165
+ }
166
+ }
167
+
168
+ /**
169
+ * Synchronous version of isUsingSystemMd.
170
+ *
171
+ * @param contentDirectory The src/content directory path
172
+ * @returns true if src/content/system.md exists
173
+ */
174
+ export function isUsingSystemMdSync(contentDirectory: string): boolean {
175
+ const systemMdPath = join(contentDirectory, "system.md");
176
+ try {
177
+ accessSync(systemMdPath);
178
+ return true;
179
+ } catch {
180
+ return false;
181
+ }
182
+ }
@@ -0,0 +1,51 @@
1
+ /*
2
+ <MODULE_CONTRACT>
3
+ <purpose>Defines and validates the RFC-0070 onboarding brief contract from
4
+ onboarding/<system-id>/.input/00-brief.md (RFC-0532). Stack-agnostic utility
5
+ used by both engine and site plugin (RFC-0868).</purpose>
6
+ <non-goals>
7
+ <item>Do not scaffold apps or infer derived ecosystem choices like biome or constellation.</item>
8
+ </non-goals>
9
+ </MODULE_CONTRACT>
10
+ <CHANGE_SUMMARY>
11
+ <item>RFC-0868: extracted from werkstatt-site/src/onboarding/brief.ts.</item>
12
+ </CHANGE_SUMMARY>
13
+ */
14
+
15
+ import matter from "gray-matter";
16
+ import { z } from "zod";
17
+ import YAML from "yaml";
18
+
19
+ export const BriefFrontmatter = z
20
+ .object({
21
+ client: z.object({
22
+ id: z.string().regex(/^[a-z][a-z0-9-]{2,48}$/),
23
+ domain: z.string().regex(/^([a-z0-9-]+\.)+[a-z]{2,}$/),
24
+ }),
25
+ i18n: z
26
+ .object({
27
+ default: z.string().regex(/^[a-z]{2}$/),
28
+ supported: z.array(z.string().regex(/^[a-z]{2}$/)).min(1),
29
+ })
30
+ .refine((value) => value.supported.includes(value.default), {
31
+ message: "i18n.supported must contain i18n.default",
32
+ path: ["supported"],
33
+ }),
34
+ legalJurisdiction: z.string().regex(/^[A-Z]{2}$/),
35
+ })
36
+ .strict();
37
+
38
+ export type Brief = z.infer<typeof BriefFrontmatter>;
39
+
40
+ export function parseBriefFrontmatter(source: string): Brief {
41
+ const parsed = matter(source);
42
+ return BriefFrontmatter.parse(parsed.data);
43
+ }
44
+
45
+ export function parseSystemFrontmatter(source: string): Record<string, unknown> {
46
+ return matter(source).data as Record<string, unknown>;
47
+ }
48
+
49
+ export function parseMarkdownAsYaml(source: string): Record<string, unknown> {
50
+ return YAML.parse(source) as Record<string, unknown>;
51
+ }
@@ -0,0 +1,20 @@
1
+ /*
2
+ <MODULE_CONTRACT>
3
+ <purpose>Public entrypoint for @warpgogol/werkstatt-shared/onboarding — exports
4
+ brief parsing utilities (RFC-0868).</purpose>
5
+ <non-goals>
6
+ <item>Do not import from app-specific packages or stack plugins.</item>
7
+ </non-goals>
8
+ </MODULE_CONTRACT>
9
+ <CHANGE_SUMMARY>
10
+ <item>RFC-0868: extracted from werkstatt-site/src/onboarding/brief.ts.</item>
11
+ </CHANGE_SUMMARY>
12
+ */
13
+
14
+ export {
15
+ BriefFrontmatter,
16
+ parseBriefFrontmatter,
17
+ parseSystemFrontmatter,
18
+ parseMarkdownAsYaml,
19
+ } from "./brief.ts";
20
+ export type { Brief } from "./brief.ts";
@@ -0,0 +1 @@
1
+ export * from "./translation-policy.ts";
@@ -57,9 +57,13 @@ export async function runStandardLayoutOrchestration(
57
57
  }
58
58
  }
59
59
 
60
- // 2. LordIcon hydration (mandatory on demand)
60
+ // 2. LordIcon hydration (deferred to load event to avoid blocking LCP)
61
61
  if (has("lord-icon")) {
62
- initLordIconOnDemand();
62
+ if (document.readyState === "complete") {
63
+ initLordIconOnDemand();
64
+ } else {
65
+ window.addEventListener("load", () => initLordIconOnDemand(), { once: true });
66
+ }
63
67
  }
64
68
 
65
69
  // 3. Lenis smooth scroll (opt-in via smoothScroll: true)
@@ -331,6 +331,14 @@ export type SemanticPageModel = {
331
331
  * Image Provider Port run in the render layer), so it ships the raw token.
332
332
  */
333
333
  leadImageToken?: { src: string; alt: string };
334
+ /**
335
+ * Unresolved token for the page's actual LCP element, carried from the
336
+ * framework-free page handler to the asset-aware render layer. Set to the
337
+ * hero block's `backgroundImage` when present (full-viewport, fetchpriority
338
+ * high), falling back to `leadImage` when visible. Used for `<link
339
+ * rel="preload" as="image">` in `<head>`.
340
+ */
341
+ lcpImageToken?: { src: string; alt: string };
334
342
  /** RFC-0162: Open Graph type. Defaults to "website" when unset. */
335
343
  ogType?: OgType;
336
344
  /** RFC-0162: Open Graph locale in `xx_XX` form (from system.md i18n hreflang). */