48 KiB
Semantic CSS Author Reference and Unified Documentation Generation Implementation Plan
For agentic workers: REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (
- [ ]) syntax for tracking.
Goal: Publish a canonical author-facing Semantic CSS language reference, link it from Custom Styles, and make pnpm docs:gen regenerate every Semantic CSS, Resume JSON Schema, and OpenAPI documentation artifact.
Architecture: Keep runtime registries and PDF template manifests authoritative, adding only the missing structured metadata needed for documentation. A tooling-owned generator updates Semantic CSS and Resume JSON Schema Markdown from those sources, while a server-owned generator reuses the runtime OpenAPI builder for docs/spec.json; the root command runs both. Generated Markdown is marker-delimited, deterministic, and tested against the committed artifacts.
Tech Stack: TypeScript, pnpm workspaces, Zod 4 JSON Schema conversion, oRPC OpenAPI, Vitest, React 19, Lingui, Mintlify MDX.
Global Constraints
- The public reference route is exactly
https://docs.rxresu.me/guides/semantic-css-reference. - The audience is resume authors, not compiler contributors.
- The root command is exactly
pnpm docs:gen; remove the obsoletedocs:semantic-cssscript. pnpm docs:genregeneratesdocs/guides/semantic-css-reference.mdx,skills/resume-builder/references/schema.md,docs/guides/json-resume-schema.mdx, anddocs/spec.json.- The checked-in OpenAPI document uses
https://rxresu.me/api/openapiand the root package version. - Runtime
/schema.json, the skill schema reference, and the JSON schema guide use one canonical input-side JSON Schema created fromresumeDataSchema. - Complete Semantic CSS examples marked as valid must compile; selected invalid examples must emit their documented code.
- Preserve distinct
list-itemrow,list-item-content, andlist-markerauthor semantics. - Do not add a shared docs-link component, URL registry, documentation playground, or contributor/compiler architecture page.
- Do not add a general file-transaction framework; compute each generator's complete output set before its first write.
- Preserve all pre-existing worktree changes. When a target file is already modified, stage only this plan's hunks and inspect
git diff --cachedbefore committing. - Verification is CLI-based. Do not use Chrome.
File Map
Canonical schema and API artifacts
- Create
packages/schema/src/resume/json-schema.ts: the only Zod-to-JSON-Schema conversion policy for ResumeData. - Create
packages/schema/src/resume/json-schema.test.ts: proves transforms are handled and the emitted schema describes input payloads. - Modify
apps/server/src/static/schema.ts: return the canonical schema helper result. - Create
apps/server/src/openapi/generator.ts: own the shared OpenAPI router andgenerateOpenApiSpec. - Create
apps/server/src/openapi/generator.test.ts: verify caller-provided URL/version and stable API metadata. - Create
apps/server/src/openapi/generate-spec.ts: write the production documentation spec. - Modify
apps/server/src/openapi/handler.ts: delegate spec generation while retaining request handling. - Modify
apps/server/package.json: expose the server-side docs generation command.
Semantic CSS reference metadata
- Modify
packages/resume/src/stylesheet/diagnostics.ts: add the typed compiler diagnostic catalog. - Modify
packages/resume/src/stylesheet/values.ts: constrain the internal diagnostic helper to catalog codes. - Modify
packages/resume/src/stylesheet/registry/semantic.ts: expose known attribute values, addcontact-listas a valid template-part parent, and register the two Chikorita contact-row child contracts. - Modify
packages/resume/src/stylesheet/registry/index.ts: export the new public metadata. - Modify
packages/resume/src/stylesheet/index.ts: export compiler diagnostics and limits for generation. - Modify
packages/resume/src/stylesheet/registry/semantic.test.ts: verify the new parent/value metadata. - Create
packages/pdf/src/semantic/preflight-reference.ts: pure failure metadata and author-facing preflight limits. - Modify
packages/pdf/src/semantic/preflight-core.tsx: consume the catalog-backed failure-code type. - Modify
packages/pdf/src/semantic/template-manifest.ts: expose the frozen manifest record through the existing getter. - Modify
packages/pdf/src/semantic/template-manifest.test.ts: enforce primitive-part child coverage. - Modify
packages/pdf/src/server.tsx: retain the existing preflight type exports. - Modify
packages/pdf/package.json: add an explicit./semantic-manifestexport for the tooling package. - Modify
apps/server/src/services/stylesheet-preflight.ts: consume and re-export the shared preflight limits.
Documentation generation and content
- Modify
tooling/package.json: add schema/PDF workspace dependencies and a package-localdocs:genscript. - Modify
tooling/semantic-css/generate-reference.ts: render all Semantic CSS and Resume JSON Schema outputs. - Modify
tooling/semantic-css/generate-reference.test.ts: verify markers, determinism, manifest coverage, examples, and committed output synchronization. - Modify
package.json: replacedocs:semantic-csswith the rootdocs:genorchestration. - Rewrite
docs/guides/semantic-css-reference.mdx: the author-facing Semantic CSS reference with generated sections and recipes. - Modify
docs/guides/json-resume-schema.mdx: replace the hand-copied schema with one generated block. - Regenerate
skills/resume-builder/references/schema.md: compact field-path reference for AI authors. - Regenerate
docs/spec.json: current version and production server URL. - Modify
docs/docs.json: add the Semantic CSS reference after Using Custom Styles.
Custom Styles hint
- Modify
apps/web/src/features/resume/stylesheet/editor.tsx: render the translated, accessible docs hint in shared editor chrome. - Modify
apps/web/src/features/resume/stylesheet/editor.test.tsx: verify the exact link contract on desktop and in the mobile sheet.
Task 1: Canonical Resume JSON Schema
Files:
- Create:
packages/schema/src/resume/json-schema.ts - Create:
packages/schema/src/resume/json-schema.test.ts - Modify:
apps/server/src/static/schema.ts
Interfaces:
-
Consumes:
resumeDataSchemafrom@reactive-resume/schema/resume/data. -
Produces:
createResumeDataJsonSchema(): ReturnType<typeof z.toJSONSchema>. -
Produces: an input-side draft 2020-12 schema that tolerates Zod transforms with
unrepresentable: "any". -
Step 1: Write the failing canonical-schema test
import { describe, expect, it } from "vitest";
import { createResumeDataJsonSchema } from "./json-schema";
describe("createResumeDataJsonSchema", () => {
it("describes accepted ResumeData input even when the Zod schema contains transforms", () => {
const schema = createResumeDataJsonSchema();
expect(schema).toMatchObject({
$schema: "https://json-schema.org/draft/2020-12/schema",
type: "object",
required: ["picture", "basics", "summary", "sections", "customSections", "metadata"],
properties: {
picture: { type: "object" },
basics: { type: "object" },
sections: { type: "object" },
metadata: { type: "object" },
},
});
});
});
- Step 2: Run the schema test and verify the missing module failure
Run:
pnpm --filter @reactive-resume/schema test -- src/resume/json-schema.test.ts
Expected: FAIL because ./json-schema does not exist.
- Step 3: Add the canonical conversion helper
import z from "zod";
import { resumeDataSchema } from "./data";
export function createResumeDataJsonSchema() {
return z.toJSONSchema(resumeDataSchema, {
io: "input",
unrepresentable: "any",
});
}
This policy is deliberate: documentation describes accepted resume payloads, and transform output cannot be represented exactly in JSON Schema.
- Step 4: Route
/schema.jsonthrough the helper
Replace the direct Zod conversion in apps/server/src/static/schema.ts with:
import { createResumeDataJsonSchema } from "@reactive-resume/schema/resume/json-schema";
export function handleSchemaJson() {
return Response.json(createResumeDataJsonSchema(), {
status: 200,
headers: {
"Content-Type": "application/schema+json; charset=utf-8",
"Cache-Control": "public, max-age=86400, immutable",
"Surrogate-Control": "max-age=86400",
"X-Content-Type-Options": "nosniff",
"X-Robots-Tag": "index, follow",
ETag: appVersion,
Vary: "Accept",
},
});
}
- Step 5: Run focused tests and typechecks
Run:
pnpm --filter @reactive-resume/schema test -- src/resume/json-schema.test.ts
pnpm --filter @reactive-resume/schema typecheck
pnpm --filter server typecheck
Expected: PASS.
- Step 6: Commit only the canonical-schema changes
git add packages/schema/src/resume/json-schema.ts packages/schema/src/resume/json-schema.test.ts apps/server/src/static/schema.ts
git diff --cached --check
git commit -m "refactor: centralize resume JSON schema generation"
Task 2: Shared Runtime and Documentation OpenAPI Generator
Files:
- Create:
apps/server/src/openapi/generator.ts - Create:
apps/server/src/openapi/generator.test.ts - Create:
apps/server/src/openapi/generate-spec.ts - Modify:
apps/server/src/openapi/handler.ts - Modify:
apps/server/package.json
Interfaces:
-
Consumes: the current oRPC router,
downloadResumePdfProcedure, and explicit{ appUrl, version }. -
Produces:
generateOpenApiSpec(options: { appUrl: string; version: string }): Promise<object>. -
Produces: server script
pnpm --filter server docs:gen. -
Step 1: Write the failing OpenAPI generator test
import { describe, expect, it } from "vitest";
import { generateOpenApiSpec } from "./generator";
describe("generateOpenApiSpec", () => {
it("uses caller-provided application URL and version", async () => {
const spec = await generateOpenApiSpec({
appUrl: "https://rxresu.me",
version: "9.8.7",
});
expect(spec.info).toMatchObject({
title: "Reactive Resume",
version: "9.8.7",
});
expect(spec.servers).toEqual([{ url: "https://rxresu.me/api/openapi" }]);
expect(spec.externalDocs).toEqual({
url: "https://docs.rxresu.me",
description: "Reactive Resume Documentation",
});
});
});
- Step 2: Run the test and verify the missing module failure
Run:
pnpm --filter server test -- src/openapi/generator.test.ts
Expected: FAIL because ./generator does not exist.
- Step 3: Extract the router and OpenAPI builder
Move openAPIRouter, openAPIGenerator, and the current metadata object from handler.ts into generator.ts. Keep the existing download-PDF override and internal-route filter. Export:
export const openAPIRouter = {
...router,
resume: {
...router.resume,
downloadPdf: downloadResumePdfProcedure,
},
};
export async function generateOpenApiSpec({ appUrl, version }: { appUrl: string; version: string }) {
return openAPIGenerator.generate(openAPIRouter, {
info: {
title: "Reactive Resume",
version,
description: "Reactive Resume API",
license: { name: "MIT", url: "https://github.com/amruthpillai/reactive-resume/blob/main/LICENSE" },
contact: { name: "Amruth Pillai", email: "hello@amruthpillai.com", url: "https://amruthpillai.com" },
},
servers: [{ url: `${appUrl}/api/openapi` }],
externalDocs: { url: "https://docs.rxresu.me", description: "Reactive Resume Documentation" },
commonSchemas: {
ResumeData: { schema: resumeDataSchema },
},
components: {
securitySchemes: {
apiKey: {
type: "apiKey",
name: "x-api-key",
in: "header",
description: "The API key to authenticate requests.",
},
},
},
security: [{ apiKey: [] }],
filter: ({ contract }) => !contract["~orpc"].route.tags?.includes("Internal"),
});
}
- Step 4: Make the runtime handler delegate
Keep OpenAPIHandler, plugins, interceptors, locale, and response-header behavior in handler.ts. Replace only its spec-building branch:
if (request.method === "GET" && (request.url.endsWith("/spec.json") || request.url.endsWith("/spec"))) {
return Response.json(await generateOpenApiSpec({ appUrl: env.APP_URL, version: appVersion }));
}
- Step 5: Add the deterministic documentation writer
Create generate-spec.ts with a default target of docs/spec.json, read the root package.json version, and serialize with tabs plus one trailing newline:
import { readFile, writeFile } from "node:fs/promises";
import { fileURLToPath } from "node:url";
import { generateOpenApiSpec } from "./generator";
export async function generateOpenApiDocumentation(
target = fileURLToPath(new URL("../../../../docs/spec.json", import.meta.url)),
) {
const packageJson = JSON.parse(
await readFile(new URL("../../../../package.json", import.meta.url), "utf8"),
) as { version: string };
const spec = await generateOpenApiSpec({ appUrl: "https://rxresu.me", version: packageJson.version });
await writeFile(target, `${JSON.stringify(spec, null, "\t")}\n`);
}
if (process.argv[1] && fileURLToPath(import.meta.url) === process.argv[1]) {
await generateOpenApiDocumentation(process.argv[2]);
}
Add to apps/server/package.json:
"docs:gen": "tsx src/openapi/generate-spec.ts"
- Step 6: Run focused OpenAPI tests and typecheck
Run:
pnpm --filter server test -- src/openapi/generator.test.ts src/http/app.test.ts
pnpm --filter server typecheck
Expected: PASS, and existing request routing remains unchanged.
- Step 7: Commit the shared OpenAPI builder
git add apps/server/src/openapi/generator.ts apps/server/src/openapi/generator.test.ts apps/server/src/openapi/generate-spec.ts apps/server/src/openapi/handler.ts apps/server/package.json
git diff --cached --check
git commit -m "refactor: share OpenAPI documentation generator"
Task 3: Structured Semantic CSS Reference Metadata
Files:
- Modify:
packages/resume/src/stylesheet/diagnostics.ts - Modify:
packages/resume/src/stylesheet/values.ts - Modify:
packages/resume/src/stylesheet/registry/semantic.ts - Modify:
packages/resume/src/stylesheet/registry/index.ts - Modify:
packages/resume/src/stylesheet/index.ts - Modify:
packages/resume/src/stylesheet/registry/semantic.test.ts - Create:
packages/pdf/src/semantic/preflight-reference.ts - Modify:
packages/pdf/src/semantic/preflight-core.tsx - Modify:
packages/pdf/src/semantic/template-manifest.test.ts - Modify:
packages/pdf/src/server.tsx - Modify:
packages/pdf/package.json - Modify:
apps/server/src/services/stylesheet-preflight.ts
Interfaces:
-
Produces:
SEMANTIC_CSS_DIAGNOSTIC_CATALOG_V1, keyed by every compiler diagnostic code. -
Produces:
attributeValues?: Readonly<Record<string, readonly string[]>>on semantic definitions. -
Produces:
PDF_PREFLIGHT_DIAGNOSTIC_CATALOG, keyed by everyPdfPreflightFailureCode, without importing the PDF renderer. -
Produces:
STYLESHEET_PREFLIGHT_LIMITS, shared by docs and the server runner. -
Produces:
@reactive-resume/pdf/semantic-manifestas a tooling-safe explicit export. -
Step 1: Write failing registry and manifest consistency assertions
Add to semantic.test.ts:
expect(SEMANTIC_REGISTRY_V1.resume.attributeValues?.template).toEqual(templateSchema.options);
expect(SEMANTIC_REGISTRY_V1.section.attributeValues).toMatchObject({
type: sectionTypeSchema.options,
placement: ["main", "sidebar"],
origin: ["main", "sidebar"],
});
expect(SEMANTIC_REGISTRY_V1["template-part"].parents).toContain("contact-list");
expect(TEMPLATE_PART_CHILD_KINDS_V1["contact-row-primary"]).toEqual(["contact-item"]);
expect(TEMPLATE_PART_CHILD_KINDS_V1["contact-row-secondary"]).toEqual(["contact-item"]);
Add to template-manifest.test.ts:
for (const manifest of Object.values(getTemplateSemanticRegistryFingerprintInput())) {
for (const part of manifest.parts) {
if (part.binding.type === "alias") continue;
expect(TEMPLATE_PART_CHILD_KINDS_V1, `${manifest.template}:${part.name}`).toHaveProperty(part.name);
}
}
- Step 2: Run the focused tests and verify the missing metadata failures
Run:
pnpm --filter @reactive-resume/resume test -- src/stylesheet/registry/semantic.test.ts
pnpm --filter @reactive-resume/pdf test -- src/semantic/template-manifest.test.ts
Expected: FAIL for missing attribute domains, contact-list parent support, and Chikorita contact-row child contracts.
- Step 3: Add semantic attribute domains and parent/child consistency
Extend SemanticNodeDefinition:
export type SemanticNodeDefinition = {
parents: readonly SemanticNodeKind[];
attributes: readonly string[];
roles: readonly string[];
attributeValues?: Readonly<Record<string, readonly string[]>>;
};
Import sectionTypeSchema and templateSchema, then add finite domains:
resume: {
parents: [],
attributes: ["template"],
roles: [],
attributeValues: { template: templateSchema.options },
},
region: {
parents: ["page"],
attributes: ["placement", "region", "part"],
roles: [],
attributeValues: {
placement: ["main", "sidebar"],
region: ["header", "main", "sidebar", "featured"],
},
},
section: {
parents: ["region"],
attributes: ["type", "placement", "origin", "part"],
roles: ["featured-summary"],
attributeValues: {
type: sectionTypeSchema.options,
placement: ["main", "sidebar"],
origin: ["main", "sidebar"],
},
},
"rich-heading": {
parents: ["rich-text"],
attributes: ["level"],
roles: [],
attributeValues: { level: ["1", "2", "3", "4", "5", "6"] },
},
"list-item-content": {
parents: ["list-item"],
attributes: ["direction"],
roles: [],
attributeValues: { direction: ["ltr", "rtl"] },
},
Add "contact-list" to the template-part parent list and:
"contact-row-primary": ["contact-item"],
"contact-row-secondary": ["contact-item"],
Do not invent domains for resume-specific IDs, names, page numbers, part names, or roles.
- Step 4: Add exhaustive compiler diagnostic metadata
Define:
type DiagnosticReference = {
severity: SemanticCssDiagnostic["severity"];
meaning: string;
action: string;
};
export const SEMANTIC_CSS_DIAGNOSTIC_CATALOG_V1 = {
MISSING_VERSION_DIRECTIVE: {
severity: "warning",
meaning: "The stylesheet omitted @version.",
action: "Add @version 1; as the first statement.",
},
DUPLICATE_VERSION_DIRECTIVE: {
severity: "error",
meaning: "More than one @version directive was found.",
action: "Keep exactly one version directive.",
},
INVALID_VERSION: {
severity: "error",
meaning: "The version directive is not one positive integer without a block.",
action: "Use @version 1;.",
},
VERSION_MISMATCH: {
severity: "error",
meaning: "The directive and stored language version disagree.",
action: "Set both to version 1.",
},
UNSUPPORTED_VERSION: {
severity: "error",
meaning: "The requested Semantic CSS version is not implemented.",
action: "Use @version 1;.",
},
CSS_PARSE_ERROR: {
severity: "error",
meaning: "The stylesheet is not valid parseable CSS syntax.",
action: "Fix the syntax at the reported source range.",
},
CSS_RAW_SYNTAX: {
severity: "error",
meaning: "The parser encountered unsupported raw CSS syntax.",
action: "Rewrite the declaration or selector using documented Semantic CSS syntax.",
},
FORBIDDEN_AT_RULE: {
severity: "error",
meaning: "The at-rule can load resources or execute unsupported CSS behavior.",
action: "Remove the at-rule.",
},
UNSUPPORTED_AT_RULE: {
severity: "error",
meaning: "The at-rule is not part of Semantic CSS version 1.",
action: "Use only @version and documented @media queries.",
},
INVALID_MEDIA_QUERY: {
severity: "error",
meaning: "The PDF dimension query is malformed or unsupported.",
action: "Use width, min-width, max-width, height, min-height, or max-height with a Semantic CSS length.",
},
MEDIA_PAGE_SIZE: {
severity: "error",
meaning: "A media rule attempts to change the page size it is evaluated against.",
action: "Move the page size declaration outside @media.",
},
INVALID_SELECTOR: {
severity: "error",
meaning: "The selector uses unsupported syntax or exceeds selector limits.",
action: "Rewrite it using documented Semantic CSS selectors and combinators.",
},
UNSUPPORTED_PROPERTY: {
severity: "error",
meaning: "The property is not in the Semantic CSS property registry.",
action: "Choose a property from the property reference.",
},
SYSTEM_VARIABLE_READONLY: {
severity: "error",
meaning: "An author attempted to assign a reserved --resume-* variable.",
action: "Read the system variable or rename the author variable.",
},
FORBIDDEN_CSS_VALUE: {
severity: "error",
meaning: "The value attempts to use an external resource or forbidden CSS capability.",
action: "Use a PDF-safe local value.",
},
INVALID_VALUE: {
severity: "error",
meaning: "The value does not match the supported grammar for the property.",
action: "Use the documented property value form.",
},
VARIABLE_CYCLE: {
severity: "error",
meaning: "Custom properties form a var() reference cycle.",
action: "Break the cycle or provide a non-cyclic fallback.",
},
UNRESOLVED_VARIABLE: {
severity: "error",
meaning: "A var() reference has neither a value nor a usable fallback.",
action: "Define the variable or add a fallback.",
},
EXTREME_VALUE: {
severity: "warning",
meaning: "A value is valid but likely to produce unusable output.",
action: "Reduce the value unless the effect is intentional.",
},
SELECTOR_NO_MATCH: {
severity: "warning",
meaning: "The selector matches no node in the current resume and template.",
action: "Check the ID, attribute value, placement, or template guard.",
},
PROPERTY_NOT_APPLICABLE: {
severity: "warning",
meaning: "The property cannot affect any matched semantic node kind.",
action: "Target a node listed in the property's Applies to column.",
},
RESOURCE_LIMIT: {
severity: "error",
meaning: "Compilation, matching, values, variables, or semantic nodes exceeded a bounded Semantic CSS limit.",
action: "Reduce stylesheet or resume complexity.",
},
} as const satisfies Readonly<Record<string, DiagnosticReference>>;
export type SemanticCssCompilerDiagnosticCode = keyof typeof SEMANTIC_CSS_DIAGNOSTIC_CATALOG_V1;
Change createDiagnostic and the private values.ts helper to accept SemanticCssCompilerDiagnosticCode. Export the catalog through @reactive-resume/resume/stylesheet.
- Step 5: Add exhaustive PDF preflight metadata and shared limits
Create preflight-reference.ts so documentation generation does not load React PDF merely to read constants:
export const PDF_PREFLIGHT_DIAGNOSTIC_CATALOG = {
STYLESHEET_PREFLIGHT_INVALID: {
meaning: "The stylesheet has compiler or semantic errors.",
action: "Fix the accompanying Semantic CSS diagnostics.",
},
STYLESHEET_PREFLIGHT_PAGE_SIZE_LIMIT: {
meaning: "An authored page exceeds the PDF dimension or area budget.",
action: "Use a smaller page size.",
},
STYLESHEET_PREFLIGHT_BYTE_LIMIT: {
meaning: "The rendered PDF exceeds the byte budget.",
action: "Reduce pages, images, or styled content.",
},
STYLESHEET_PREFLIGHT_PAGE_LIMIT: {
meaning: "The rendered PDF exceeds the page-count budget.",
action: "Reduce content or pagination.",
},
STYLESHEET_PREFLIGHT_TIMEOUT: {
meaning: "PDF preflight exceeded its deadline.",
action: "Reduce stylesheet or document complexity and retry.",
},
STYLESHEET_PREFLIGHT_MEMORY_LIMIT: {
meaning: "PDF preflight exceeded its memory budget.",
action: "Reduce document, image, or layout complexity.",
},
STYLESHEET_PREFLIGHT_RENDER_FAILED: {
meaning: "The PDF renderer could not render the candidate stylesheet.",
action: "Simplify the candidate and inspect accompanying diagnostics.",
},
STYLESHEET_PREFLIGHT_PARSE_FAILED: {
meaning: "The rendered PDF could not be inspected.",
action: "Retry after simplifying the candidate.",
},
STYLESHEET_PREFLIGHT_WORKER_FAILED: {
meaning: "The isolated PDF preflight worker failed or its queue was full.",
action: "Retry; simplify the candidate if the failure repeats.",
},
} as const;
export type PdfPreflightFailureCode = keyof typeof PDF_PREFLIGHT_DIAGNOSTIC_CATALOG;
export const STYLESHEET_PREFLIGHT_LIMITS = Object.freeze({
timeoutMs: 5_000,
maxPages: 20,
maxBytes: 10_000_000,
maxPageWidthPt: 2_000,
maxPageHeightPt: 20_000,
maxPageAreaPt2: 20_000_000,
maxOldGenerationMb: 256,
maxConcurrentWorkers: 1,
maxQueuedRequests: 32,
});
Import PdfPreflightFailureCode into preflight-core.tsx and re-export the type there so current browser/server entrypoints retain their interface. Import and re-export STYLESHEET_PREFLIGHT_LIMITS from apps/server/src/services/stylesheet-preflight.ts so current callers and tests also retain their interface. Add an explicit @reactive-resume/pdf/preflight-reference package export for the tooling generator.
- Step 6: Expose actual template manifests to tooling
Add the package export:
"./semantic-manifest": "./src/semantic/template-manifest.ts"
The tooling generator will call the existing:
getTemplateSemanticRegistryFingerprintInput(): Readonly<Record<Template, TemplateSemanticManifest>>
Do not create a duplicate reference-specific manifest.
- Step 7: Run focused metadata tests and boundaries
Run:
pnpm --filter @reactive-resume/resume test -- src/stylesheet/registry/semantic.test.ts src/stylesheet/values.test.ts
pnpm --filter @reactive-resume/pdf test -- src/semantic/template-manifest.test.ts src/semantic/preflight-core.test.tsx
pnpm --filter server test -- src/services/stylesheet-preflight.test.ts
pnpm --filter @reactive-resume/resume typecheck
pnpm --filter @reactive-resume/pdf typecheck
pnpm --filter server typecheck
pnpm exec turbo boundaries
Expected: PASS.
- Step 8: Commit only the structured reference metadata
Stage only the task's hunks, especially in already-modified PDF files:
git add -p packages/resume/src/stylesheet packages/pdf/src apps/server/src/services/stylesheet-preflight.ts packages/pdf/package.json
git diff --cached --check
git diff --cached
git commit -m "feat: expose Semantic CSS reference metadata"
Task 4: Unified Semantic CSS and Resume Schema Markdown Generator
Files:
- Modify:
tooling/package.json - Modify:
tooling/semantic-css/generate-reference.ts - Modify:
tooling/semantic-css/generate-reference.test.ts - Modify:
package.json - Modify:
docs/guides/json-resume-schema.mdx
Interfaces:
-
Consumes: canonical Resume JSON Schema, Semantic CSS registries/catalogs/limits, PDF preflight catalogs/limits, and actual template manifests.
-
Produces:
updateGeneratedDocumentation(paths?: Partial<DocumentationPaths>): Promise<void>. -
Produces: root
pnpm docs:gen. -
Step 1: Replace the synchronization test with failing multi-output tests
Define targets:
export type DocumentationPaths = {
semanticCssReference: string;
jsonSchemaGuide: string;
skillSchemaReference: string;
};
Tests must cover:
it("rejects missing and duplicate generated markers", () => {
expect(() => replaceGeneratedBlock("plain text", "SEMANTIC-CSS-ELEMENTS", "body", "reference.mdx")).toThrow(
/Missing generated markers/,
);
expect(() =>
replaceGeneratedBlock(
"<!-- SEMANTIC-CSS-ELEMENTS:START --><!-- SEMANTIC-CSS-ELEMENTS:END --><!-- SEMANTIC-CSS-ELEMENTS:START --><!-- SEMANTIC-CSS-ELEMENTS:END -->",
"SEMANTIC-CSS-ELEMENTS",
"body",
"reference.mdx",
),
).toThrow(/Duplicate generated markers/);
});
it("builds identical output twice", async () => {
expect(await buildGeneratedDocumentation(paths)).toEqual(await buildGeneratedDocumentation(paths));
});
it("documents every manifest part with its real selector form", () => {
const table = renderTemplateParts(getTemplateSemanticRegistryFingerprintInput());
for (const [template, manifest] of Object.entries(getTemplateSemanticRegistryFingerprintInput())) {
for (const part of manifest.parts) {
expect(table).toContain(`| \`${template}\``);
expect(table).toContain(`\`${part.name}\``);
}
}
});
- Step 2: Run the tooling test and verify it fails
Run:
pnpm --filter @reactive-resume/tooling test -- semantic-css/generate-reference.test.ts
Expected: FAIL because the multi-output functions and markers do not exist.
- Step 3: Add the minimum marker and output pipeline
Implement:
export function replaceGeneratedBlock(source: string, name: string, body: string, path: string): string {
const start = `<!-- ${name}:START -->`;
const end = `<!-- ${name}:END -->`;
const startMatches = source.split(start).length - 1;
const endMatches = source.split(end).length - 1;
if (startMatches === 0 || endMatches === 0) throw new Error(`Missing generated markers ${name} in ${path}.`);
if (startMatches !== 1 || endMatches !== 1) throw new Error(`Duplicate generated markers ${name} in ${path}.`);
const startIndex = source.indexOf(start);
const endIndex = source.indexOf(end);
if (endIndex < startIndex) throw new Error(`Generated markers ${name} are out of order in ${path}.`);
return `${source.slice(0, startIndex)}${start}\n${body}\n${end}${source.slice(endIndex + end.length)}`;
}
buildGeneratedDocumentation reads every source, creates the canonical schema once, applies all blocks in memory, and returns the three complete output strings. updateGeneratedDocumentation calls it once and only then writes all three files with Promise.all. The determinism test uses temporary source files containing one of every required marker; the committed-output synchronization assertion is added after the real reference page is written in Task 5.
- Step 4: Render the Semantic CSS factual blocks
Use stable sort order and these marker names:
SEMANTIC-CSS-SEMANTIC-ELEMENTSSEMANTIC-CSS-PROPERTIESSEMANTIC-CSS-SYSTEM-VARIABLESSEMANTIC-CSS-TEMPLATE-PARTSSEMANTIC-CSS-DIAGNOSTICSSEMANTIC-CSS-LIMITS
Required table columns:
Semantic elements: Element | Parents | Attributes | Known values | Roles
Properties: Property | Category | Applies to | Inherits | Units | Known keywords
System variables: Variable | Runtime value
Template parts: Template | Name | Selector | Owner/condition | Allowed children
Diagnostics: Code | Severity | Meaning | What to do
Limits: Stage | Limit | Value
For template parts:
const selector =
part.binding.type === "primitive"
? `template-part[name="${part.name}"]`
: `${part.binding.canonicalKind}[role~="${part.binding.token}"]`;
Primitive parts read allowed children from TEMPLATE_PART_CHILD_KINDS_V1; missing coverage throws with <template>:<part>. Alias parts display “canonical node” instead of pretending that a template-part wrapper exists. Include owner placement, section types, columns, and last-item conditions when present.
Label registry values “Known keywords” and add this sentence below the table:
Known keywords are completion hints, not a complete value grammar; use the property examples and value rules above for accepted numbers, colors, functions, and shorthands.
- Step 5: Render both schema outputs from one schema object
Serialize the full guide block as:
const fullSchemaBlock = [
"```json /schema.json lines expandable",
JSON.stringify(schema, null, "\t"),
"```",
].join("\n");
Replace RESUME-JSON-SCHEMA markers in docs/guides/json-resume-schema.mdx.
For the skill reference, walk object properties and array item schemas depth-first. Emit:
# Reactive Resume Schema Reference
Generated by `pnpm docs:gen` from `resumeDataSchema`. Do not edit this file directly.
Canonical schema: https://rxresu.me/schema.json
## Required top-level fields
`picture`, `basics`, `summary`, `sections`, `customSections`, and `metadata`
## Field catalog
| Path | Type | Required | Constraints and default | Description |
| --- | --- | --- | --- | --- |
Rules for the walker:
-
Object properties append
.name. -
Array items append
[]. -
anyOf/oneOftypes are joined withorand each branch is traversed. -
Required comes from the containing object's
requiredarray. -
Constraints include
enum,minimum,maximum,minLength,maxLength, and JSON-stringifieddefault. -
Escape Markdown pipes and newlines in descriptions.
-
Prevent duplicate rows with a
Setkeyed by path plus rendered type. -
Sort object property names in their schema-defined order and union branches in emitted order.
-
Step 6: Add package dependencies and the root command
In tooling/package.json, add:
"docs:gen": "tsx semantic-css/generate-reference.ts"
and workspace dependencies for @reactive-resume/pdf and @reactive-resume/schema.
In the root package.json, replace docs:semantic-css with:
"docs:gen": "pnpm --filter @reactive-resume/tooling docs:gen && pnpm --filter server docs:gen"
- Step 7: Run focused generator tests and typechecks
Run:
pnpm install --lockfile-only
pnpm --filter @reactive-resume/tooling test -- semantic-css/generate-reference.test.ts
pnpm --filter @reactive-resume/tooling typecheck
pnpm --filter server test -- src/openapi/generator.test.ts
pnpm --filter server typecheck
pnpm exec turbo boundaries
Expected: PASS against temporary marker fixtures.
- Step 8: Commit the generator implementation
Stage only this task's changes:
git add package.json tooling/package.json tooling/semantic-css/generate-reference.ts tooling/semantic-css/generate-reference.test.ts docs/guides/json-resume-schema.mdx
git add -p pnpm-lock.yaml
git diff --cached --check
git diff --cached
git commit -m "feat: generate documentation artifacts"
Task 5: Write and Generate the Author-Facing References
Files:
- Rewrite:
docs/guides/semantic-css-reference.mdx - Modify:
docs/guides/json-resume-schema.mdx - Regenerate:
skills/resume-builder/references/schema.md - Regenerate:
docs/spec.json - Modify:
docs/docs.json - Modify:
tooling/semantic-css/generate-reference.test.ts
Interfaces:
-
Consumes: the marker contracts and
pnpm docs:genfrom Task 4. -
Produces: the canonical public Semantic CSS reference route and all synchronized generated artifacts.
-
Step 1: Write the Semantic CSS page around the generated markers
Use the approved ten-section structure:
- Semantic CSS in one minute
- Selector grammar
- Semantic element catalog
- Cascade and values
- Property reference
- PDF behavior
- Template-specific selectors
- Diagnostics and limits
- Copy-paste recipes
- Unsupported capabilities and portability checklist
Include this complete portable example and mark it for compiler verification:
<!-- SEMANTIC-CSS-EXAMPLE:valid -->
```css
@version 1;
:root {
--accent: #2563eb;
}
section-heading {
color: var(--accent);
border-bottom: 1pt solid var(--accent);
padding-bottom: 3pt;
}
section[type="experience"] > section-items > item + item {
margin-top: 8pt;
}
region[placement="sidebar"] section-heading {
font-size: 10pt;
text-transform: uppercase;
}
```
Include the complete selector forms:
* { opacity: 1; }
section { margin-bottom: 8pt; }
#experience { break-before: page; }
[role~="primary-text"] { font-weight: 700; }
section[type="experience"] > section-items > item:first-child { margin-top: 0; }
field[name="company"], field[name="position"] { color: #111827; }
section:is([type="experience"], [type="education"]) { break-inside: avoid; }
item:not(:last-child) { margin-bottom: 6pt; }
Document all six supported attribute matchers: =, ~=, |=, ^=, $=, and *=. Attribute selector flags, custom classes, and pseudo-elements are rejected. Document :root, :is(), :where(), :not(), :first-child, :last-child, :only-child, :nth-child(), and :nth-of-type() with these exact restrictions:
:root,:first-child,:last-child, and:only-childtake no arguments.:is(),:where(), and:not()require a selector list.:nth-child()and:nth-of-type()accept integerAn+B,odd, andeven.:nth-child()may use anof <selector-list>clause.:nth-of-type()does not accept anofclause.- A compound selector may contain only one semantic type selector.
- Selector functions, selector lists, selector length, and combinators are bounded by the generated limits.
Document media queries as comma-separated OR branches and and-combined features. Supported features are width, min-width, max-width, height, min-height, max-height, and orientation: portrait|landscape. Dimension values accept finite numbers with pt, px, in, mm, cm, vw, vh, em, or rem; omitted units resolve as points. Media rules evaluate against authored PDF dimensions, may nest up to the generated limit, and cannot contain size.
State the cascade and value behavior precisely:
!importantwins first, then specificity, then later source order.- For a selector list, the highest-specificity branch that actually matches the node is used.
:where()contributes zero specificity;:is()and:not()use their most specific nested selector.inheritreads the semantic parent's resolved value.initialremoves the authored value.unsetinherits inherited properties and removes non-inherited properties.revertrestores the template/builder base value.- Author custom properties inherit through the semantic tree;
var()supports nested fallbacks. - Missing variables without fallbacks and variable cycles are errors.
--resume-*variables are read-only runtime values; author variables must use another prefix.- Units resolve against PDF points, authored page dimensions, the root font size, or the current/parent font size as appropriate.
State PDF and lifecycle behavior precisely:
display: nonehides an existing semantic node; Semantic CSS never deletes source data.orderchanges stable sibling render order, with original semantic order as the tie-breaker.break-before,break-inside,-resume-min-presence-ahead,orphans,widows, and-resume-fixedmap to bounded React PDF behavior.sizeapplies only topageoutside@media; one or two lengths create a custom page size.- Semantic CSS cannot create, re-parent, or duplicate arbitrary nodes; fixed content repeats only an existing semantic node.
- Invalid editable source remains saved, while preview and export keep the last source that compiled and passed PDF preflight.
- Template-part selectors are optional and must be guarded with
resume[template="..."]when portability matters.
End with an explicit unsupported checklist: class selectors, pseudo-elements, CSS Grid, @import, @font-face, @supports, arbitrary at-rules, url(), external/local assets, font loading, scripts, interaction states, animations, transitions, filters, gradients, general box-shadow/text-shadow, browser layout APIs, and node creation are unsupported. Note that picture shadows are available only through -resume-shadow-color and -resume-shadow-width.
Mark one invalid complete example:
<!-- SEMANTIC-CSS-EXAMPLE:invalid INVALID_SELECTOR -->
```css
@version 1;
section::before {
content: "Unsupported";
}
```
Explain that pseudo-elements cannot create PDF nodes.
- Step 2: Add generated sections at their lookup locations
Place each exact marker pair once:
<!-- SEMANTIC-CSS-SEMANTIC-ELEMENTS:START -->
<!-- SEMANTIC-CSS-SEMANTIC-ELEMENTS:END -->
Repeat for properties, system variables, template parts, diagnostics, and limits. Keep manual explanations immediately before or after their table, not inside generated blocks.
- Step 3: Add exact author recipes
Each recipe starts with @version 1; and is independently copyable:
/* One section type */
@version 1;
section[type="experience"] section-heading {
color: #0f766e;
}
/* One resume-specific node */
@version 1;
#experience-item-uuid {
break-inside: avoid;
}
/* Sidebar content */
@version 1;
region[placement="sidebar"] {
background-color: #f8fafc;
padding: 18pt;
}
/* Rich-text list row versus content */
@version 1;
list-item {
gap: 4pt;
}
list-marker {
color: var(--resume-primary-color);
}
list-item-content {
line-height: 1.35;
}
/* Authored page dimensions */
@version 1;
page {
size: 210mm 297mm;
}
/* Pagination */
@version 1;
section {
-resume-min-presence-ahead: 72pt;
}
item {
break-inside: avoid;
}
/* Template decoration */
@version 1;
resume[template="azurill"] template-part[name="timeline-line"] {
background-color: #94a3b8;
}
/* PDF dimension query */
@version 1;
@media (max-width: 600pt) {
region[placement="sidebar"] {
padding: 12pt;
}
}
Prefix each complete recipe fence with <!-- SEMANTIC-CSS-EXAMPLE:valid -->; leave only deliberately incomplete syntax fragments unmarked.
- Step 4: Test examples directly from the page
First add the committed-output synchronization test:
it("keeps every committed generated document synchronized", async () => {
const before = await readTargets(defaultDocumentationPaths);
await updateGeneratedDocumentation(defaultDocumentationPaths);
expect(await readTargets(defaultDocumentationPaths)).toEqual(before);
});
Then extract fenced CSS immediately following:
<!-- SEMANTIC-CSS-EXAMPLE:valid -->
<!-- SEMANTIC-CSS-EXAMPLE:invalid CODE -->
For valid examples:
const result = compileStylesheet({ languageVersion: 1, text: example.source });
expect(result.program, example.label).not.toBeNull();
expect(result.diagnostics.filter(({ severity }) => severity === "error"), example.label).toEqual([]);
For invalid examples:
expect(result.diagnostics.map(({ code }) => code)).toContain(example.expectedCode);
- Step 5: Add the schema guide marker and docs navigation
Wrap only the full JSON code fence in:
<!-- RESUME-JSON-SCHEMA:START -->
<!-- RESUME-JSON-SCHEMA:END -->
In docs/docs.json, insert:
"guides/using-custom-styles",
"guides/semantic-css-reference",
"guides/using-ai-in-the-builder"
- Step 6: Regenerate every artifact
Run:
pnpm docs:gen
pnpm docs:gen
git diff --check -- docs/guides/semantic-css-reference.mdx docs/guides/json-resume-schema.mdx skills/resume-builder/references/schema.md docs/spec.json
Expected: the first run updates all four artifact groups; the second run is byte-for-byte idempotent as proved by the determinism and synchronization tests. docs/spec.json contains version 5.2.4 and server URL https://rxresu.me/api/openapi.
- Step 7: Run focused documentation verification
Run:
pnpm --filter @reactive-resume/tooling test -- semantic-css/generate-reference.test.ts
pnpm --filter @reactive-resume/tooling typecheck
pnpm exec markdownlint-cli2 docs/guides/semantic-css-reference.mdx docs/guides/json-resume-schema.mdx skills/resume-builder/references/schema.md
Expected: PASS.
- Step 8: Commit the author references and generated artifacts
git add docs/guides/semantic-css-reference.mdx docs/guides/json-resume-schema.mdx skills/resume-builder/references/schema.md docs/spec.json docs/docs.json tooling/semantic-css/generate-reference.test.ts
git diff --cached --check
git diff --cached
git commit -m "docs: publish the Semantic CSS author reference"
Task 6: Link the Reference from Custom Styles
Files:
- Modify:
apps/web/src/features/resume/stylesheet/editor.tsx - Modify:
apps/web/src/features/resume/stylesheet/editor.test.tsx
Interfaces:
-
Consumes: the fixed public URL
https://docs.rxresu.me/guides/semantic-css-reference. -
Produces: one translated, accessible link in shared editor chrome on desktop and mobile.
-
Step 1: Write the failing desktop and mobile link assertions
Add a helper:
const referenceName = /browse the semantic css language reference.*opens in new tab/i;
const expectReferenceLink = (root: HTMLElement) => {
const link = within(root).getByRole("link", { name: referenceName });
expect(link).toHaveAttribute("href", "https://docs.rxresu.me/guides/semantic-css-reference");
expect(link).toHaveAttribute("target", "_blank");
expect(link).toHaveAttribute("rel", "noopener noreferrer");
};
In a desktop test, render StylesheetEditorShell and call expectReferenceLink(container).
In the existing mobile-sheet test, after opening the sheet:
expectReferenceLink(sheet);
- Step 2: Run the focused editor test and verify failure
Run:
pnpm --filter web test -- src/features/resume/stylesheet/editor.test.tsx
Expected: FAIL because the reference link is absent.
- Step 3: Add the local help hint
Import BookOpenIcon from the existing icon package and render this immediately after StylesheetToolbar and before the editor:
<p className="flex items-center gap-1.5 text-muted-foreground text-xs">
<BookOpenIcon aria-hidden="true" className="shrink-0" />
<span>
<Trans>Not sure what to write?</Trans>{" "}
<a
className="text-primary underline underline-offset-4"
href="https://docs.rxresu.me/guides/semantic-css-reference"
target="_blank"
rel="noopener noreferrer"
>
<Trans>Browse the Semantic CSS language reference.</Trans>
<span className="sr-only">
{" "}
(<Trans>opens in new tab</Trans>)
</span>
</a>
</span>
</p>
Do not add props, a URL constant, or a shared component. The existing editorChrome reuse makes the hint available in both desktop and mobile focus mode.
- Step 4: Run focused web tests and typecheck
Run:
pnpm --filter web test -- src/features/resume/stylesheet/editor.test.tsx
pnpm --filter web typecheck
Expected: PASS.
- Step 5: Commit only the help-hint hunks
Both files already have worktree changes, so use interactive staging and inspect the cached patch:
git add -p apps/web/src/features/resume/stylesheet/editor.tsx apps/web/src/features/resume/stylesheet/editor.test.tsx
git diff --cached --check
git diff --cached
git commit -m "feat: link Custom Styles to Semantic CSS reference"
Task 7: Final Non-Chrome Verification
Files:
- Verify only; fix failures in the owning task's files.
Interfaces:
-
Consumes: all previous tasks.
-
Produces: one clean, repeatable documentation generation and focused repository gates.
-
Step 1: Verify generation is deterministic and committed
pnpm docs:gen
git diff --exit-code -- docs/guides/semantic-css-reference.mdx docs/guides/json-resume-schema.mdx skills/resume-builder/references/schema.md docs/spec.json
Expected: PASS with no generated diff.
- Step 2: Run focused tests
pnpm --filter @reactive-resume/schema test -- src/resume/json-schema.test.ts
pnpm --filter @reactive-resume/resume test -- src/stylesheet/registry/semantic.test.ts src/stylesheet/values.test.ts
pnpm --filter @reactive-resume/pdf test -- src/semantic/template-manifest.test.ts src/semantic/preflight-core.test.tsx
pnpm --filter @reactive-resume/tooling test -- semantic-css/generate-reference.test.ts
pnpm --filter server test -- src/openapi/generator.test.ts src/http/app.test.ts src/services/stylesheet-preflight.test.ts
pnpm --filter web test -- src/features/resume/stylesheet/editor.test.tsx
Expected: PASS.
- Step 3: Run typechecks and package boundaries
pnpm --filter @reactive-resume/schema typecheck
pnpm --filter @reactive-resume/resume typecheck
pnpm --filter @reactive-resume/pdf typecheck
pnpm --filter @reactive-resume/tooling typecheck
pnpm --filter server typecheck
pnpm --filter web typecheck
pnpm exec turbo boundaries
Expected: PASS.
- Step 4: Run non-mutating formatting and documentation checks
pnpm exec biome check packages/schema/src/resume/json-schema.ts packages/schema/src/resume/json-schema.test.ts apps/server/src/static/schema.ts apps/server/src/openapi apps/server/src/services/stylesheet-preflight.ts packages/resume/src/stylesheet packages/pdf/src/semantic packages/pdf/src/server.tsx tooling/semantic-css apps/web/src/features/resume/stylesheet/editor.tsx apps/web/src/features/resume/stylesheet/editor.test.tsx
pnpm exec markdownlint-cli2 docs/guides/semantic-css-reference.mdx docs/guides/json-resume-schema.mdx skills/resume-builder/references/schema.md docs/superpowers/plans/2026-07-29-semantic-css-reference-and-docs-generation.md
git diff --check
Expected: PASS. Do not run pnpm check, because it mutates the entire worktree.
- Step 5: Inspect scope without using Chrome
git status --short
git diff --stat
git log --oneline -8
Confirm:
- No Chrome or Playwright browser session was used.
- Unrelated pre-existing changes remain present and were not reverted.
- Generated artifacts are synchronized.
- The public link, nav entry, production OpenAPI URL, and root version are exact.