docs/superpowers/plans/2026-07-29-semantic-css-reference-and-docs-generation.md
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.
https://docs.rxresu.me/guides/semantic-css-reference.pnpm docs:gen; remove the obsolete docs:semantic-css script.pnpm docs:gen regenerates docs/guides/semantic-css-reference.mdx, skills/resume-builder/references/schema.md, docs/guides/json-resume-schema.mdx, and docs/spec.json.https://rxresu.me/api/openapi and the root package version./schema.json, the skill schema reference, and the JSON schema guide use one canonical input-side JSON Schema created from resumeDataSchema.list-item row, list-item-content, and list-marker author semantics.git diff --cached before committing.packages/schema/src/resume/json-schema.ts: the only Zod-to-JSON-Schema conversion policy for ResumeData.packages/schema/src/resume/json-schema.test.ts: proves transforms are handled and the emitted schema describes input payloads.apps/server/src/static/schema.ts: return the canonical schema helper result.apps/server/src/openapi/generator.ts: own the shared OpenAPI router and generateOpenApiSpec.apps/server/src/openapi/generator.test.ts: verify caller-provided URL/version and stable API metadata.apps/server/src/openapi/generate-spec.ts: write the production documentation spec.apps/server/src/openapi/handler.ts: delegate spec generation while retaining request handling.apps/server/package.json: expose the server-side docs generation command.packages/resume/src/stylesheet/diagnostics.ts: add the typed compiler diagnostic catalog.packages/resume/src/stylesheet/values.ts: constrain the internal diagnostic helper to catalog codes.packages/resume/src/stylesheet/registry/semantic.ts: expose known attribute values, add contact-list as a valid template-part parent, and register the two Chikorita contact-row child contracts.packages/resume/src/stylesheet/registry/index.ts: export the new public metadata.packages/resume/src/stylesheet/index.ts: export compiler diagnostics and limits for generation.packages/resume/src/stylesheet/registry/semantic.test.ts: verify the new parent/value metadata.packages/pdf/src/semantic/preflight-reference.ts: pure failure metadata and author-facing preflight limits.packages/pdf/src/semantic/preflight-core.tsx: consume the catalog-backed failure-code type.packages/pdf/src/semantic/template-manifest.ts: expose the frozen manifest record through the existing getter.packages/pdf/src/semantic/template-manifest.test.ts: enforce primitive-part child coverage.packages/pdf/src/server.tsx: retain the existing preflight type exports.packages/pdf/package.json: add an explicit ./semantic-manifest export for the tooling package.apps/server/src/services/stylesheet-preflight.ts: consume and re-export the shared preflight limits.tooling/package.json: add schema/PDF workspace dependencies and a package-local docs:gen script.tooling/semantic-css/generate-reference.ts: render all Semantic CSS and Resume JSON Schema outputs.tooling/semantic-css/generate-reference.test.ts: verify markers, determinism, manifest coverage, examples, and committed output synchronization.package.json: replace docs:semantic-css with the root docs:gen orchestration.docs/guides/semantic-css-reference.mdx: the author-facing Semantic CSS reference with generated sections and recipes.docs/guides/json-resume-schema.mdx: replace the hand-copied schema with one generated block.skills/resume-builder/references/schema.md: compact field-path reference for AI authors.docs/spec.json: current version and production server URL.docs/docs.json: add the Semantic CSS reference after Using Custom Styles.apps/web/src/features/resume/stylesheet/editor.tsx: render the translated, accessible docs hint in shared editor chrome.apps/web/src/features/resume/stylesheet/editor.test.tsx: verify the exact link contract on desktop and in the mobile sheet.Files:
packages/schema/src/resume/json-schema.tspackages/schema/src/resume/json-schema.test.tsapps/server/src/static/schema.tsInterfaces:
Consumes: resumeDataSchema from @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" },
},
});
});
});
Run:
pnpm --filter @reactive-resume/schema test -- src/resume/json-schema.test.ts
Expected: FAIL because ./json-schema does not exist.
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.
/schema.json through the helperReplace 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",
},
});
}
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.
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"
Files:
apps/server/src/openapi/generator.tsapps/server/src/openapi/generator.test.tsapps/server/src/openapi/generate-spec.tsapps/server/src/openapi/handler.tsapps/server/package.jsonInterfaces:
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",
});
});
});
Run:
pnpm --filter server test -- src/openapi/generator.test.ts
Expected: FAIL because ./generator does not exist.
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: "[email protected]", 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"),
});
}
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 }));
}
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"
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.
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"
Files:
packages/resume/src/stylesheet/diagnostics.tspackages/resume/src/stylesheet/values.tspackages/resume/src/stylesheet/registry/semantic.tspackages/resume/src/stylesheet/registry/index.tspackages/resume/src/stylesheet/index.tspackages/resume/src/stylesheet/registry/semantic.test.tspackages/pdf/src/semantic/preflight-reference.tspackages/pdf/src/semantic/preflight-core.tsxpackages/pdf/src/semantic/template-manifest.test.tspackages/pdf/src/server.tsxpackages/pdf/package.jsonapps/server/src/services/stylesheet-preflight.tsInterfaces:
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 every PdfPreflightFailureCode, without importing the PDF renderer.
Produces: STYLESHEET_PREFLIGHT_LIMITS, shared by docs and the server runner.
Produces: @reactive-resume/pdf/semantic-manifest as 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);
}
}
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.
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.
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.
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.
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.
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.
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"
Files:
tooling/package.jsontooling/semantic-css/generate-reference.tstooling/semantic-css/generate-reference.test.tspackage.jsondocs/guides/json-resume-schema.mdxInterfaces:
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}\``);
}
}
});
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.
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.
Use stable sort order and these marker names:
SEMANTIC-CSS-SEMANTIC-ELEMENTSSEMANTIC-CSS-PROPERTIESSEMANTIC-CSS-SYSTEM-VARIABLESSEMANTIC-CSS-TEMPLATE-PARTSSEMANTIC-CSS-DIAGNOSTICSSEMANTIC-CSS-LIMITSRequired 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.
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/oneOf types are joined with or and each branch is traversed.
Required comes from the containing object's required array.
Constraints include enum, minimum, maximum, minLength, maxLength, and JSON-stringified default.
Escape Markdown pipes and newlines in descriptions.
Prevent duplicate rows with a Set keyed 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"
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.
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"
Files:
docs/guides/semantic-css-reference.mdxdocs/guides/json-resume-schema.mdxskills/resume-builder/references/schema.mddocs/spec.jsondocs/docs.jsontooling/semantic-css/generate-reference.test.tsInterfaces:
Consumes: the marker contracts and pnpm docs:gen from 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:
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-child take no arguments.:is(), :where(), and :not() require a selector list.:nth-child() and :nth-of-type() accept integer An+B, odd, and even.:nth-child() may use an of <selector-list> clause.:nth-of-type() does not accept an of clause.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:
!important wins first, then specificity, then later source order.:where() contributes zero specificity; :is() and :not() use their most specific nested selector.inherit reads the semantic parent's resolved value.initial removes the authored value.unset inherits inherited properties and removes non-inherited properties.revert restores the template/builder base value.var() supports nested fallbacks.--resume-* variables are read-only runtime values; author variables must use another prefix.State PDF and lifecycle behavior precisely:
display: none hides an existing semantic node; Semantic CSS never deletes source data.order changes stable sibling render order, with original semantic order as the tie-breaker.break-before, break-inside, -resume-min-presence-ahead, orphans, widows, and -resume-fixed map to bounded React PDF behavior.size applies only to page outside @media; one or two lengths create a custom page size.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.
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.
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.
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);
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"
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.
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.
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"
Files:
apps/web/src/features/resume/stylesheet/editor.tsxapps/web/src/features/resume/stylesheet/editor.test.tsxInterfaces:
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);
Run:
pnpm --filter web test -- src/features/resume/stylesheet/editor.test.tsx
Expected: FAIL because the reference link is absent.
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.
Run:
pnpm --filter web test -- src/features/resume/stylesheet/editor.test.tsx
pnpm --filter web typecheck
Expected: PASS.
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"
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.
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.
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.
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.
git status --short
git diff --stat
git log --oneline -8
Confirm: