@json-to-office/jto 6.4.0 → 6.8.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.
- package/dist/cli.js +1 -1
- package/dist/client/assets/{HomePage-DgyrBIGY.js → HomePage-DdKO7Rz8.js} +4 -4
- package/dist/client/assets/{HomePage-DgyrBIGY.js.map → HomePage-DdKO7Rz8.js.map} +1 -1
- package/dist/client/assets/{JsonEditorPage-BGctWRXE.js → JsonEditorPage-CIB7kfUW.js} +3 -3
- package/dist/client/assets/{JsonEditorPage-BGctWRXE.js.map → JsonEditorPage-CIB7kfUW.js.map} +1 -1
- package/dist/client/assets/{MonacoPluginProvider-DbBCCotq.js → MonacoPluginProvider-Cql9aylS.js} +5 -5
- package/dist/client/assets/{MonacoPluginProvider-DbBCCotq.js.map → MonacoPluginProvider-Cql9aylS.js.map} +1 -1
- package/dist/client/assets/{_virtual_jto-plugin-type-libs-BpQu9aDr.js → _virtual_jto-plugin-type-libs-BJgH7I8Z.js} +32 -9
- package/dist/client/assets/{_virtual_jto-plugin-type-libs-BpQu9aDr.js.map → _virtual_jto-plugin-type-libs-BJgH7I8Z.js.map} +1 -1
- package/dist/client/assets/_virtual_jto-sandbox-runtime-BFXK15tx.js +2 -0
- package/dist/client/assets/_virtual_jto-sandbox-runtime-BFXK15tx.js.map +1 -0
- package/dist/client/assets/{editor-DnPnx_0M.js → editor-Dl0mPZWB.js} +2 -2
- package/dist/client/assets/{editor-DnPnx_0M.js.map → editor-Dl0mPZWB.js.map} +1 -1
- package/dist/client/assets/{editor-monaco-json-B_U6hHbA.js → editor-monaco-json-BWLlWM6o.js} +2 -2
- package/dist/client/assets/{editor-monaco-json-B_U6hHbA.js.map → editor-monaco-json-BWLlWM6o.js.map} +1 -1
- package/dist/client/assets/{index-2S58SOrb.js → index-1Vg-Jg2R.js} +4 -4
- package/dist/client/assets/index-1Vg-Jg2R.js.map +1 -0
- package/dist/client/assets/{index-CWzQeme_.js → index-Db2WFiF0.js} +2 -2
- package/dist/client/assets/{index-CWzQeme_.js.map → index-Db2WFiF0.js.map} +1 -1
- package/dist/client/assets/{preview-Bdmil0FT.js → preview-BXysnPth.js} +2 -2
- package/dist/client/assets/{preview-Bdmil0FT.js.map → preview-BXysnPth.js.map} +1 -1
- package/dist/client/assets/{useQualityAnalysis-B0tasjoU.js → useQualityAnalysis-Dr3UHxyN.js} +2 -2
- package/dist/client/assets/{useQualityAnalysis-B0tasjoU.js.map → useQualityAnalysis-Dr3UHxyN.js.map} +1 -1
- package/dist/client/index.html +1 -1
- package/package.json +10 -10
- package/dist/client/assets/_virtual_jto-sandbox-runtime-CySzPJ5n.js +0 -2
- package/dist/client/assets/_virtual_jto-sandbox-runtime-CySzPJ5n.js.map +0 -1
- package/dist/client/assets/index-2S58SOrb.js.map +0 -1
package/dist/client/assets/{useQualityAnalysis-B0tasjoU.js.map → useQualityAnalysis-Dr3UHxyN.js.map}
RENAMED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useQualityAnalysis-B0tasjoU.js","sources":["../../../src/client/lib/theme-editor/model.ts","../../../src/client/components/theme-editor/theme-editor-shared.tsx","../../../src/client/lib/quality-findings.ts","../../../src/client/lib/quality-policy.ts","../../../src/client/lib/quality-profiles.ts","../../../src/client/lib/quality-sequence.ts","../../../src/client/hooks/useQualityAnalysis.ts"],"sourcesContent":["import { ThemeConfigSchema as DocxThemeConfigSchema } from '@json-to-office/shared-docx';\nimport { ThemeConfigSchema as PptxThemeConfigSchema } from '@json-to-office/shared-pptx';\nimport { mutateDocumentAtPath } from '../doc-mutations';\nimport type { FormatName } from '../env';\n\n/**\n * The visual theme editor's model: what a theme of each format is made of,\n * read from the shared schemas so the form cannot drift from them, plus the\n * pure edits the form applies.\n *\n * The form never owns the theme. Every change is applied to the parsed JSON\n * and written back as text; keys the form has no field for (component\n * defaults, font registries, `$schema`) survive untouched.\n */\n\nexport type ThemeJson = Record<string, unknown>;\n\nexport type ParsedTheme =\n | { ok: true; theme: ThemeJson }\n | { ok: false; error: string };\n\nexport function parseTheme(text: string): ParsedTheme {\n try {\n const value = JSON.parse(text);\n if (!value || typeof value !== 'object' || Array.isArray(value)) {\n return { ok: false, error: 'A theme is a JSON object.' };\n }\n return { ok: true, theme: value as ThemeJson };\n } catch (error) {\n return {\n ok: false,\n error: error instanceof Error ? error.message : 'Invalid JSON',\n };\n }\n}\n\nexport function serializeTheme(theme: ThemeJson): string {\n return JSON.stringify(theme, null, 2);\n}\n\nexport type Path = (string | number)[];\n\n/** Set a leaf, creating the objects on the way. Never mutates the input. */\nexport function setAt(theme: ThemeJson, path: Path, value: unknown): ThemeJson {\n return mutateDocumentAtPath(theme, path, value) as ThemeJson;\n}\n\n/** Remove a leaf. Never mutates the input; a missing path is a no-op. */\nexport function deleteAt(theme: ThemeJson, path: Path): ThemeJson {\n if (path.length === 0) return theme;\n const parentPath = path.slice(0, -1);\n const key = path[path.length - 1];\n const parent = getAt(theme, parentPath);\n if (!parent || typeof parent !== 'object') return theme;\n if (Array.isArray(parent)) {\n // `splice` coerces its index: -1 would drop the last entry, NaN the\n // first, 1.5 the second. Only a real index deletes anything.\n if (typeof key !== 'number' || !Number.isInteger(key)) return theme;\n if (key < 0 || key >= parent.length) return theme;\n const next = parent.slice();\n next.splice(key, 1);\n return parentPath.length === 0\n ? (next as unknown as ThemeJson)\n : setAt(theme, parentPath, next);\n }\n if (!Object.prototype.hasOwnProperty.call(parent, key)) return theme;\n const next = { ...(parent as Record<string, unknown>) };\n delete next[key as string];\n return parentPath.length === 0 ? next : setAt(theme, parentPath, next);\n}\n\nexport function getAt(theme: unknown, path: Path): unknown {\n let current: unknown = theme;\n for (const key of path) {\n if (current === null || typeof current !== 'object') return undefined;\n current = (current as Record<string | number, unknown>)[key];\n }\n return current;\n}\n\n// ---------------------------------------------------------------------------\n// Colours\n// ---------------------------------------------------------------------------\n\nexport interface ColorTokenDescriptor {\n key: string;\n label: string;\n group: string;\n required: boolean;\n hint: string;\n}\n\nconst COLOR_HINTS: Record<string, string> = {\n primary: 'Headings, the strongest brand colour',\n secondary: 'Supporting brand colour',\n accent: 'Highlights and emphasis',\n text: 'Default body text',\n textPrimary: 'Primary text',\n textSecondary: 'Secondary text',\n textMuted: 'Captions and de-emphasised text',\n background: 'Page or slide background',\n backgroundPrimary: 'Primary surface',\n backgroundSecondary: 'Alternate surface, table stripes',\n background2: 'Alternate surface, table rules',\n text2: 'Secondary text, captions',\n border: 'Default rules and borders',\n borderPrimary: 'Primary rules',\n borderSecondary: 'Subtle rules',\n accent4: 'Chart series 4',\n accent5: 'Chart series 5',\n accent6: 'Chart series 6',\n};\n\nconst COLOR_GROUPS: Array<[string, string[]]> = [\n ['Core', ['primary', 'secondary', 'accent']],\n ['Text', ['text', 'textPrimary', 'textSecondary', 'textMuted', 'text2']],\n [\n 'Background',\n ['background', 'backgroundPrimary', 'backgroundSecondary', 'background2'],\n ],\n ['Border', ['border', 'borderPrimary', 'borderSecondary']],\n ['Chart', ['accent4', 'accent5', 'accent6']],\n];\n\nfunction humanize(key: string): string {\n return key\n .replace(/([a-z])([A-Z0-9])/g, '$1 $2')\n .replace(/^./, (c) => c.toUpperCase());\n}\n\nfunction schemaColors(schema: unknown): {\n keys: string[];\n required: Set<string>;\n} {\n const colors = (schema as { properties?: { colors?: unknown } })?.properties\n ?.colors as { properties?: Record<string, unknown>; required?: string[] };\n return {\n keys: Object.keys(colors?.properties ?? {}),\n required: new Set(colors?.required ?? []),\n };\n}\n\n/** The colour tokens a format's theme schema declares, grouped for the form. */\nexport function colorTokens(format: FormatName): ColorTokenDescriptor[] {\n const { keys, required } = schemaColors(\n format === 'docx' ? DocxThemeConfigSchema : PptxThemeConfigSchema\n );\n const ordered: ColorTokenDescriptor[] = [];\n const seen = new Set<string>();\n for (const [group, groupKeys] of COLOR_GROUPS) {\n for (const key of groupKeys) {\n if (!keys.includes(key)) continue;\n seen.add(key);\n ordered.push({\n key,\n label: humanize(key),\n group,\n required: required.has(key),\n hint: COLOR_HINTS[key] ?? '',\n });\n }\n }\n for (const key of keys) {\n if (seen.has(key)) continue;\n ordered.push({\n key,\n label: humanize(key),\n group: 'Other',\n required: required.has(key),\n hint: COLOR_HINTS[key] ?? '',\n });\n }\n return ordered;\n}\n\nexport const HEX_COLOR = /^#[0-9A-Fa-f]{6}$/;\n\nexport function isHexColor(value: unknown): value is string {\n return typeof value === 'string' && HEX_COLOR.test(value);\n}\n\n/** `#abc` → `#AABBCC`; `abcdef` → `#ABCDEF`; anything else unchanged. */\nexport function normalizeHex(value: string): string {\n const trimmed = value.trim();\n const short = trimmed.match(/^#?([0-9A-Fa-f]{3})$/);\n if (short) {\n return `#${short[1]\n .split('')\n .map((c) => c + c)\n .join('')\n .toUpperCase()}`;\n }\n const full = trimmed.match(/^#?([0-9A-Fa-f]{6})$/);\n if (full) return `#${full[1].toUpperCase()}`;\n return trimmed;\n}\n\n/** Colour keys a theme defines, for token pickers. */\nexport function definedColorKeys(theme: ThemeJson): string[] {\n const colors = theme.colors;\n if (!colors || typeof colors !== 'object') return [];\n return Object.entries(colors as Record<string, unknown>)\n .filter(([, value]) => typeof value === 'string')\n .map(([key]) => key);\n}\n\n/** Follow token references (`accent4: \"primary\"`) to a hex, or null. */\nexport function resolveColor(\n theme: ThemeJson,\n value: unknown,\n depth = 0\n): string | null {\n if (typeof value !== 'string' || depth > 8) return null;\n if (HEX_COLOR.test(value)) return value;\n const bare = value.match(/^#?([0-9A-Fa-f]{6})$/);\n if (bare) return `#${bare[1]}`;\n const colors = theme.colors as Record<string, unknown> | undefined;\n return colors ? resolveColor(theme, colors[value], depth + 1) : null;\n}\n\n/** WCAG relative-luminance contrast ratio between two hex colours. */\nexport function contrastRatio(a: string, b: string): number | null {\n const la = luminance(a);\n const lb = luminance(b);\n if (la === null || lb === null) return null;\n const [light, dark] = la > lb ? [la, lb] : [lb, la];\n return (light + 0.05) / (dark + 0.05);\n}\n\nfunction luminance(hex: string): number | null {\n const match = hex.match(/^#?([0-9A-Fa-f]{6})$/);\n if (!match) return null;\n const channels = [0, 2, 4].map((i) => {\n const c = parseInt(match[1].slice(i, i + 2), 16) / 255;\n return c <= 0.03928 ? c / 12.92 : ((c + 0.055) / 1.055) ** 2.4;\n });\n return 0.2126 * channels[0] + 0.7152 * channels[1] + 0.0722 * channels[2];\n}\n\n// ---------------------------------------------------------------------------\n// Fonts\n// ---------------------------------------------------------------------------\n\nexport const DOCX_FONT_ROLES = ['heading', 'body', 'mono', 'light'] as const;\nexport type DocxFontRole = (typeof DOCX_FONT_ROLES)[number];\nexport const PPTX_FONT_ROLES = ['heading', 'body'] as const;\n\nexport const FONT_ROLE_HINTS: Record<string, string> = {\n heading: 'Headings and titles',\n body: 'Body text',\n mono: 'Code and tabular figures',\n light: 'Large, light display text',\n};\n\n// ---------------------------------------------------------------------------\n// Page (DOCX)\n// ---------------------------------------------------------------------------\n\nexport const PAGE_SIZES = ['A4', 'A3', 'LETTER', 'LEGAL'] as const;\nexport const MARGIN_KEYS = [\n 'top',\n 'bottom',\n 'left',\n 'right',\n 'header',\n 'footer',\n 'gutter',\n] as const;\n\nexport type LengthUnit = 'in' | 'cm' | 'pt' | 'twips';\nconst TWIPS_PER_UNIT: Record<LengthUnit, number> = {\n in: 1440,\n cm: 1440 / 2.54,\n pt: 20,\n twips: 1,\n};\n\nexport function twipsToUnit(twips: number, unit: LengthUnit): number {\n const value = twips / TWIPS_PER_UNIT[unit];\n return unit === 'twips' ? Math.round(value) : Math.round(value * 100) / 100;\n}\n\nexport function unitToTwips(value: number, unit: LengthUnit): number {\n return Math.round(value * TWIPS_PER_UNIT[unit]);\n}\n\n// ---------------------------------------------------------------------------\n// Styles\n// ---------------------------------------------------------------------------\n\nexport type FieldKind =\n | 'text'\n | 'number'\n | 'boolean'\n | 'select'\n | 'color'\n | 'fontRole'\n | 'fontFamily';\n\nexport interface StyleFieldDescriptor {\n path: Path;\n label: string;\n kind: FieldKind;\n options?: readonly string[];\n unit?: string;\n min?: number;\n max?: number;\n step?: number;\n /** Shown only under \"More\". */\n advanced?: boolean;\n hint?: string;\n}\n\nconst ALIGNMENTS = ['left', 'center', 'right', 'justify'] as const;\n\nexport const DOCX_STYLE_FIELDS: readonly StyleFieldDescriptor[] = [\n { path: ['font'], label: 'Font role', kind: 'fontRole' },\n {\n path: ['size'],\n label: 'Size',\n kind: 'number',\n unit: 'pt',\n min: 8,\n max: 120,\n step: 0.5,\n },\n { path: ['color'], label: 'Colour', kind: 'color' },\n { path: ['bold'], label: 'Bold', kind: 'boolean' },\n { path: ['italic'], label: 'Italic', kind: 'boolean' },\n { path: ['underline'], label: 'Underline', kind: 'boolean' },\n {\n path: ['alignment'],\n label: 'Alignment',\n kind: 'select',\n options: ALIGNMENTS,\n },\n {\n path: ['spacing', 'before'],\n label: 'Space before',\n kind: 'number',\n unit: 'pt',\n min: 0,\n step: 1,\n },\n {\n path: ['spacing', 'after'],\n label: 'Space after',\n kind: 'number',\n unit: 'pt',\n min: 0,\n step: 1,\n },\n {\n path: ['lineSpacing', 'type'],\n label: 'Line spacing',\n kind: 'select',\n options: ['single', 'multiple', 'double', 'atLeast', 'exactly'],\n },\n {\n path: ['lineSpacing', 'value'],\n label: 'Line spacing value',\n kind: 'number',\n min: 0,\n step: 0.05,\n hint: 'A factor for multiple; points for atLeast and exactly',\n },\n {\n path: ['fontWeight'],\n label: 'Weight',\n kind: 'number',\n min: 100,\n max: 900,\n step: 100,\n advanced: true,\n },\n {\n path: ['characterSpacing', 'type'],\n label: 'Tracking',\n kind: 'select',\n options: ['condensed', 'expanded'],\n advanced: true,\n },\n {\n path: ['characterSpacing', 'value'],\n label: 'Tracking amount',\n kind: 'number',\n min: 0,\n step: 1,\n advanced: true,\n hint: 'Twentieths of a point',\n },\n {\n path: ['scale'],\n label: 'Width scale',\n kind: 'number',\n unit: '%',\n min: 1,\n max: 600,\n step: 1,\n advanced: true,\n },\n {\n path: ['keepNext'],\n label: 'Keep with next',\n kind: 'boolean',\n advanced: true,\n },\n {\n path: ['keepLinesTogether'],\n label: 'Keep lines together',\n kind: 'boolean',\n advanced: true,\n },\n {\n path: ['widowControl'],\n label: 'Widow control',\n kind: 'boolean',\n advanced: true,\n },\n {\n path: ['outlineLevel'],\n label: 'Outline level',\n kind: 'number',\n min: 0,\n max: 9,\n step: 1,\n advanced: true,\n },\n { path: ['baseStyle'], label: 'Based on', kind: 'text', advanced: true },\n {\n path: ['followingStyle'],\n label: 'Next style',\n kind: 'text',\n advanced: true,\n },\n];\n\nexport const PPTX_STYLE_FIELDS: readonly StyleFieldDescriptor[] = [\n {\n path: ['fontSize'],\n label: 'Size',\n kind: 'number',\n unit: 'pt',\n min: 1,\n max: 200,\n step: 1,\n },\n { path: ['fontFace'], label: 'Font', kind: 'fontFamily' },\n { path: ['fontColor'], label: 'Colour', kind: 'color' },\n { path: ['bold'], label: 'Bold', kind: 'boolean' },\n { path: ['italic'], label: 'Italic', kind: 'boolean' },\n { path: ['align'], label: 'Alignment', kind: 'select', options: ALIGNMENTS },\n {\n path: ['lineSpacing'],\n label: 'Line spacing',\n kind: 'number',\n unit: 'pt',\n min: 0,\n step: 1,\n advanced: true,\n },\n {\n path: ['charSpacing'],\n label: 'Tracking',\n kind: 'number',\n unit: 'pt',\n step: 0.5,\n advanced: true,\n },\n {\n path: ['paraSpaceAfter'],\n label: 'Space after',\n kind: 'number',\n unit: 'pt',\n min: 0,\n step: 1,\n advanced: true,\n },\n {\n path: ['fontWeight'],\n label: 'Weight',\n kind: 'number',\n min: 100,\n max: 900,\n step: 100,\n advanced: true,\n },\n];\n\n/** DOCX's fixed style slots, in the order they read best. */\nexport const DOCX_STYLE_NAMES = [\n 'normal',\n 'title',\n 'subtitle',\n 'heading1',\n 'heading2',\n 'heading3',\n 'heading4',\n 'heading5',\n 'heading6',\n 'TOC1',\n 'TOC2',\n 'TOC3',\n 'TOC4',\n 'TOC5',\n 'TOC6',\n] as const;\n\nfunction schemaStyleNames(schema: unknown): string[] {\n const styles = (schema as { properties?: { styles?: unknown } })?.properties\n ?.styles as\n | { properties?: Record<string, unknown>; anyOf?: unknown[] }\n | undefined;\n if (!styles) return [];\n if (styles.properties) return Object.keys(styles.properties);\n // Type.Optional(Type.Partial(...)) still exposes `properties` in TypeBox;\n // a union wrapper would not, so read the first object branch.\n const branch = (styles.anyOf ?? []).find(\n (b) => (b as { properties?: unknown }).properties\n ) as { properties: Record<string, unknown> } | undefined;\n return branch ? Object.keys(branch.properties) : [];\n}\n\n/** The named style slots a format's schema declares, in the form's order. */\nexport function styleNames(format: FormatName): string[] {\n if (format === 'docx') {\n const declared = schemaStyleNames(DocxThemeConfigSchema);\n const ordered = DOCX_STYLE_NAMES.filter((n) => declared.includes(n));\n return [\n ...ordered,\n ...declared.filter((n) => !ordered.includes(n as never)),\n ];\n }\n return schemaStyleNames(PptxThemeConfigSchema);\n}\n\nexport function styleFields(\n format: FormatName\n): readonly StyleFieldDescriptor[] {\n return format === 'docx' ? DOCX_STYLE_FIELDS : PPTX_STYLE_FIELDS;\n}\n\n/** Styles the theme defines beyond the schema's named slots (DOCX allows any). */\nexport function customStyleNames(\n theme: ThemeJson,\n format: FormatName\n): string[] {\n const known = new Set(styleNames(format));\n const styles = theme.styles;\n if (!styles || typeof styles !== 'object') return [];\n return Object.keys(styles as Record<string, unknown>).filter(\n (name) => !known.has(name)\n );\n}\n\nexport const STYLE_LABELS: Record<string, string> = {\n normal: 'Normal (body)',\n title: 'Title',\n subtitle: 'Subtitle',\n body: 'Body',\n caption: 'Caption',\n heading1: 'Heading 1',\n heading2: 'Heading 2',\n heading3: 'Heading 3',\n heading4: 'Heading 4',\n heading5: 'Heading 5',\n heading6: 'Heading 6',\n TOC1: 'TOC level 1',\n TOC2: 'TOC level 2',\n TOC3: 'TOC level 3',\n TOC4: 'TOC level 4',\n TOC5: 'TOC level 5',\n TOC6: 'TOC level 6',\n};\n\nexport function styleLabel(name: string): string {\n return STYLE_LABELS[name] ?? name;\n}\n\n// ---------------------------------------------------------------------------\n// Everything else\n// ---------------------------------------------------------------------------\n\n/** Top-level keys the form edits directly; the rest is \"Advanced\". */\nconst FORM_KEYS: Record<FormatName, readonly string[]> = {\n docx: [\n 'name',\n 'displayName',\n 'description',\n 'version',\n 'colors',\n 'fonts',\n 'page',\n 'styles',\n ],\n pptx: [\n 'name',\n 'displayName',\n 'description',\n 'version',\n 'colors',\n 'fonts',\n 'defaults',\n 'styles',\n ],\n};\n\n/** The top-level keys the hand-built sections edit. */\nexport function formKeys(format: FormatName): readonly string[] {\n return FORM_KEYS[format];\n}\n\n/**\n * Keys the form leaves alone, with a one-line description each: neither a\n * hand-built section's nor, once the schema is known, one of the sections\n * derived from it.\n */\nexport function advancedKeys(\n theme: ThemeJson,\n format: FormatName,\n alsoHandled: readonly string[] = []\n): Array<{ key: string; summary: string }> {\n const handled = new Set([...FORM_KEYS[format], ...alsoHandled]);\n return Object.keys(theme)\n .filter((key) => !handled.has(key))\n .map((key) => ({ key, summary: summarize(theme[key]) }));\n}\n\nfunction summarize(value: unknown): string {\n if (Array.isArray(value))\n return `${value.length} item${value.length === 1 ? '' : 's'}`;\n if (value && typeof value === 'object') {\n const keys = Object.keys(value as object);\n return keys.length === 0\n ? 'empty'\n : keys.slice(0, 4).join(', ') + (keys.length > 4 ? ', …' : '');\n }\n if (typeof value === 'string')\n return value.length > 40 ? `${value.slice(0, 40)}…` : value;\n return String(value);\n}\n\n/** Scaffold the containers a fresh theme may be missing, so fields have a home. */\nexport function ensureContainers(\n theme: ThemeJson,\n format: FormatName\n): ThemeJson {\n let next = theme;\n const want: string[] =\n format === 'docx'\n ? ['colors', 'fonts', 'page', 'styles']\n : ['colors', 'fonts', 'defaults', 'styles'];\n for (const key of want) {\n if (!next[key] || typeof next[key] !== 'object')\n next = setAt(next, [key], {});\n }\n return next;\n}\n","import React, {\n createContext,\n useCallback,\n useContext,\n useEffect,\n useRef,\n useState,\n} from 'react';\nimport { ChevronDown, ChevronRight, ChevronUp, X } from 'lucide-react';\nimport { Input } from '../ui/input';\nimport { API_ENDPOINTS } from '../../config/api';\nimport type { Path } from '../../lib/theme-editor/model';\nimport { cn } from '../../lib/utils';\n\n/**\n * Primitives the theme form is built from.\n *\n * Two rules shape everything here. The form never holds the theme: an edit\n * is a path and a value handed to the editor, which writes the JSON back to\n * the tab. And typing never fights the round trip: text and number inputs\n * keep a draft while focused and commit on blur or Enter, so the re-render\n * that follows a commit lands on an input that is no longer being typed in.\n */\n\n// ---------------------------------------------------------------------------\n// Editor context\n// ---------------------------------------------------------------------------\n\nexport interface ThemeEditorActions {\n /** Write a leaf. Containers on the way are created. */\n set: (path: Path, value: unknown) => void;\n /**\n * Remove a leaf. With `prune`, empty objects left behind are removed too,\n * up to (not including) `keepDepth` path segments — a cleared\n * `lineSpacing.value` must not leave `lineSpacing: {}` behind, but the\n * style itself has to stay.\n */\n remove: (path: Path, prune?: { keepDepth: number }) => void;\n}\n\nconst ThemeEditorContext = createContext<ThemeEditorActions | null>(null);\n\nexport const ThemeEditorProvider = ThemeEditorContext.Provider;\n\nexport function useThemeEditor(): ThemeEditorActions {\n const ctx = useContext(ThemeEditorContext);\n if (!ctx) {\n throw new Error('useThemeEditor must be used inside ThemeVisualEditor');\n }\n return ctx;\n}\n\n// ---------------------------------------------------------------------------\n// Font catalog (fetched once per session)\n// ---------------------------------------------------------------------------\n\nexport interface FontCatalog {\n safe: string[];\n google: {\n family: string;\n category: string;\n weights: number[];\n hasItalic: boolean;\n }[];\n}\n\nconst EMPTY_CATALOG: FontCatalog = { safe: [], google: [] };\nlet catalogCache: FontCatalog | null = null;\nlet catalogRequest: Promise<FontCatalog> | null = null;\n\nfunction loadCatalog(): Promise<FontCatalog> {\n if (catalogCache) return Promise.resolve(catalogCache);\n if (!catalogRequest) {\n catalogRequest = fetch(API_ENDPOINTS.fonts.catalog)\n .then((r) => (r.ok ? r.json() : EMPTY_CATALOG))\n .then((data: unknown) => {\n const parsed = data as Partial<FontCatalog> | null;\n catalogCache = {\n safe: Array.isArray(parsed?.safe) ? parsed.safe : [],\n google: Array.isArray(parsed?.google) ? parsed.google : [],\n };\n return catalogCache;\n })\n .catch(() => {\n // The datalist is a convenience; a missing catalog leaves the inputs\n // free-text, which is what they are anyway.\n catalogRequest = null;\n return EMPTY_CATALOG;\n });\n }\n return catalogRequest;\n}\n\nexport function useFontCatalog(): FontCatalog {\n const [catalog, setCatalog] = useState<FontCatalog>(\n catalogCache ?? EMPTY_CATALOG\n );\n useEffect(() => {\n let live = true;\n if (!catalogCache) {\n loadCatalog().then((c) => {\n if (live) setCatalog(c);\n });\n }\n return () => {\n live = false;\n };\n }, []);\n return catalog;\n}\n\n// ---------------------------------------------------------------------------\n// Shared classes\n// ---------------------------------------------------------------------------\n\nexport const INPUT_CLASS = 'h-7 px-2 text-xs shadow-none';\nexport const SELECT_TRIGGER_CLASS =\n 'h-7 min-h-0 w-full gap-1.5 px-2 py-0 text-xs shadow-none [&>span]:truncate [&>span]:whitespace-nowrap';\nexport const HINT_CLASS = 'text-[11px] leading-snug text-muted-foreground';\nexport const FIELD_LABEL_CLASS =\n 'text-[11px] font-medium leading-none text-muted-foreground';\n\n// ---------------------------------------------------------------------------\n// Section\n// ---------------------------------------------------------------------------\n\n/**\n * A form section wearing the rail's eyebrow: disclosure, 11px uppercase\n * tracked name, then a one-line hint under it. Hairline above, no elevation.\n */\nexport function EditorSection({\n title,\n hint,\n actions,\n defaultOpen = true,\n forceOpen,\n children,\n}: {\n title: string;\n hint?: React.ReactNode;\n actions?: React.ReactNode;\n defaultOpen?: boolean;\n /** Held open regardless of the disclosure — while a search is filtering. */\n forceOpen?: boolean;\n children: React.ReactNode;\n}) {\n const [toggled, setOpen] = useState(defaultOpen);\n const open = forceOpen ?? toggled;\n return (\n // The rule divides one section from the one above it; the first has the\n // pane's own header there instead, and two hairlines is one too many.\n <section\n className=\"border-t border-border/60 pt-2 first:border-t-0\"\n data-crumb={title}\n >\n <div className=\"flex h-7 items-center gap-1.5\">\n <button\n type=\"button\"\n onClick={() => setOpen((o) => !o)}\n aria-expanded={open}\n className={cn(\n 'flex h-7 min-w-0 flex-1 cursor-pointer items-center gap-1.5 rounded-sm px-1',\n 'text-foreground/75 transition-colors hover:bg-accent hover:text-foreground',\n 'focus-visible:ring-1 focus-visible:ring-ring focus-visible:outline-none'\n )}\n >\n <ChevronRight\n aria-hidden\n className={cn(\n 'size-3 shrink-0 transition-transform duration-150 ease-out',\n open && 'rotate-90'\n )}\n />\n <span className=\"text-[11px] font-medium tracking-[0.08em] uppercase\">\n {title}\n </span>\n </button>\n {actions && (\n <div className=\"flex shrink-0 items-center gap-1 pr-1\">{actions}</div>\n )}\n </div>\n {hint && <p className={cn(HINT_CLASS, 'px-1 pb-2')}>{hint}</p>}\n {open && <div className=\"flex flex-col gap-3 px-1 pb-4\">{children}</div>}\n </section>\n );\n}\n\n/** 10px tracked divider inside a section, as the rail uses between groups. */\nexport function GroupLabel({ children }: { children: React.ReactNode }) {\n return (\n <div className=\"pt-1 text-[10px] tracking-[0.08em] text-muted-foreground uppercase\">\n {children}\n </div>\n );\n}\n\n// ---------------------------------------------------------------------------\n// Small controls\n// ---------------------------------------------------------------------------\n\n/** 24px clear target; hidden (not disabled) when there is nothing to clear. */\nexport function ClearButton({\n label,\n onClick,\n className,\n}: {\n label: string;\n onClick: () => void;\n className?: string;\n}) {\n return (\n <button\n type=\"button\"\n onClick={onClick}\n aria-label={label}\n title={label}\n className={cn(\n 'flex size-6 shrink-0 cursor-pointer items-center justify-center rounded-sm',\n 'text-muted-foreground/70 transition-colors hover:bg-accent hover:text-foreground',\n 'focus-visible:ring-1 focus-visible:ring-ring focus-visible:outline-none',\n className\n )}\n >\n <X className=\"size-3.5\" aria-hidden />\n </button>\n );\n}\n\n/** Inline text-styled button for secondary actions (\"Define\", \"More\"). */\nexport function QuietButton({\n children,\n onClick,\n className,\n ...rest\n}: React.ButtonHTMLAttributes<HTMLButtonElement>) {\n return (\n <button\n type=\"button\"\n onClick={onClick}\n className={cn(\n 'flex h-6 cursor-pointer items-center gap-1 rounded-sm px-1.5',\n 'text-[11px] font-medium text-muted-foreground transition-colors',\n 'hover:bg-accent hover:text-foreground',\n 'focus-visible:ring-1 focus-visible:ring-ring focus-visible:outline-none',\n 'disabled:pointer-events-none disabled:opacity-50',\n className\n )}\n {...rest}\n >\n {children}\n </button>\n );\n}\n\n/** Segmented radio group; the options are the whole control. */\nexport function Segmented<T extends string>({\n label,\n value,\n options,\n onChange,\n className,\n}: {\n label: string;\n value: T;\n options: readonly { value: T; label: string }[];\n onChange: (next: T) => void;\n className?: string;\n}) {\n return (\n <div\n role=\"radiogroup\"\n aria-label={label}\n className={cn(\n 'flex h-6 shrink-0 items-center rounded-sm border border-border/70 bg-muted/60 p-0.5',\n className\n )}\n >\n {options.map((option) => {\n const active = option.value === value;\n return (\n <button\n key={option.value}\n type=\"button\"\n role=\"radio\"\n aria-checked={active}\n onClick={() => onChange(option.value)}\n className={cn(\n 'flex h-full flex-1 cursor-pointer items-center justify-center rounded-[3px] px-1.5 text-[11px] transition-colors',\n 'focus-visible:ring-1 focus-visible:ring-ring focus-visible:outline-none',\n active\n ? 'bg-background text-foreground shadow-xs'\n : 'text-muted-foreground hover:text-foreground'\n )}\n >\n {option.label}\n </button>\n );\n })}\n </div>\n );\n}\n\n// ---------------------------------------------------------------------------\n// Draft inputs\n// ---------------------------------------------------------------------------\n\n/**\n * Local draft while focused, committed on blur or Enter, dropped on Escape.\n * The draft lives in a ref as well as state because the blur handler runs in\n * the same tick as an Escape's `setDraft(null)` and must see the cleared\n * value, not the closure's stale one.\n */\nexport function useDraft(value: string, commit: (draft: string) => void) {\n const [draft, setDraft] = useState<string | null>(null);\n const draftRef = useRef<string | null>(null);\n const write = useCallback((next: string | null) => {\n draftRef.current = next;\n setDraft(next);\n }, []);\n const onFocus = useCallback(() => write(value), [value, write]);\n const onChange = useCallback(\n (e: React.ChangeEvent<HTMLInputElement>) => write(e.target.value),\n [write]\n );\n const onBlur = useCallback(() => {\n const pending = draftRef.current;\n write(null);\n if (pending !== null && pending !== value) commit(pending);\n }, [commit, value, write]);\n const onKeyDown = useCallback(\n (e: React.KeyboardEvent<HTMLInputElement>) => {\n if (e.key === 'Enter') {\n e.preventDefault();\n e.currentTarget.blur();\n } else if (e.key === 'Escape') {\n write(null);\n e.currentTarget.blur();\n }\n },\n [write]\n );\n // For a control that commits without going through the input — a stepper\n // that keeps focus in the field. Without this the draft stays on the value\n // from before the step, hides it on screen, and commits it back on blur.\n // A no-op while nothing is focused, so it never opens a draft of its own.\n const syncDraft = useCallback(\n (next: string) => {\n if (draftRef.current !== null) write(next);\n },\n [write]\n );\n return {\n syncDraft,\n props: { value: draft ?? value, onFocus, onChange, onBlur, onKeyDown },\n };\n}\n\ntype DraftInputProps = Omit<\n React.ComponentProps<typeof Input>,\n 'value' | 'onChange' | 'onBlur' | 'onFocus' | 'onKeyDown'\n>;\n\n/** Text input; an empty commit clears the key. */\nexport function DraftTextInput({\n value,\n onCommit,\n className,\n ...rest\n}: DraftInputProps & {\n value: string | undefined;\n onCommit: (next: string | null) => void;\n}) {\n const commit = useCallback(\n (draft: string) => onCommit(draft.trim() === '' ? null : draft),\n [onCommit]\n );\n const { props: draft } = useDraft(value ?? '', commit);\n return (\n <Input\n type=\"text\"\n autoComplete=\"off\"\n spellCheck={false}\n className={cn(INPUT_CLASS, className)}\n {...draft}\n {...rest}\n />\n );\n}\n\n/**\n * Number field: a typed value, a unit suffix inside the box, and a pair of\n * steppers on the trailing edge.\n *\n * The native spinner is not enough here — it is hidden by the base input\n * reset, it appears only on hover in some engines, and it cannot step by the\n * half points a font size wants. These buttons step by the field's own\n * `step`, hold to repeat, and start from a sensible value when the key is\n * unset, so a blank size can be raised without typing a number first.\n */\n\n/** Steps a number field, repeating while held. */\nfunction useRepeat(step: (direction: 1 | -1) => void) {\n const timer = useRef<ReturnType<typeof setTimeout> | null>(null);\n const stop = useCallback(() => {\n if (timer.current) clearTimeout(timer.current);\n timer.current = null;\n }, []);\n const start = useCallback(\n (direction: 1 | -1) => {\n step(direction);\n const tick = (delay: number) => {\n timer.current = setTimeout(() => {\n step(direction);\n tick(60);\n }, delay);\n };\n tick(400);\n },\n [step]\n );\n useEffect(() => stop, [stop]);\n return { start, stop };\n}\n\nexport function DraftNumberInput({\n value,\n onCommit,\n unit,\n className,\n min,\n max,\n step = 1,\n ...rest\n}: DraftInputProps & {\n value: number | undefined;\n onCommit: (next: number | null) => void;\n unit?: string;\n}) {\n const commit = useCallback(\n (draft: string) => {\n const trimmed = draft.trim();\n if (trimmed === '') {\n onCommit(null);\n return;\n }\n const parsed = Number(trimmed);\n if (Number.isFinite(parsed)) onCommit(parsed);\n },\n [onCommit]\n );\n const { props: draft, syncDraft } = useDraft(\n value === undefined ? '' : String(value),\n commit\n );\n\n const numericStep = typeof step === 'number' ? step : Number(step) || 1;\n const lower = typeof min === 'number' ? min : Number(min);\n const upper = typeof max === 'number' ? max : Number(max);\n const bump = useCallback(\n (direction: 1 | -1) => {\n // An unset field steps from its floor, or from zero, rather than\n // refusing until a number is typed.\n const base = value ?? (Number.isFinite(lower) ? lower : 0);\n let next = base + direction * numericStep;\n if (Number.isFinite(lower)) next = Math.max(lower, next);\n if (Number.isFinite(upper)) next = Math.min(upper, next);\n // Float arithmetic on a 0.5 step leaves 12.299999999999999 behind.\n const stepped = Number(next.toFixed(4));\n // The steppers keep focus in the field, so a draft may be open on it.\n onCommit(stepped);\n syncDraft(String(stepped));\n },\n [lower, numericStep, onCommit, syncDraft, upper, value]\n );\n const repeat = useRepeat(bump);\n\n return (\n <div className=\"relative flex min-w-0 items-center\">\n <Input\n type=\"number\"\n inputMode=\"decimal\"\n min={min}\n max={max}\n step={step}\n className={cn(\n INPUT_CLASS,\n 'tabular-nums',\n // Room for the steppers, plus the unit when there is one. Kept as\n // tight as the chrome allows: the narrowest field this is used in\n // is 6.5rem, and every pixel here is a pixel the number loses.\n // The native spinner is suppressed — these buttons replace it.\n unit ? 'pr-11' : 'pr-5',\n '[appearance:textfield] [&::-webkit-inner-spin-button]:appearance-none [&::-webkit-outer-spin-button]:appearance-none',\n className\n )}\n {...draft}\n {...rest}\n />\n {unit && (\n <span\n aria-hidden\n className=\"pointer-events-none absolute right-5 text-[10px] text-muted-foreground\"\n >\n {unit}\n </span>\n )}\n <div className=\"absolute right-0 flex h-7 w-4 flex-col border-l border-input/70\">\n <StepButton\n label=\"Increase\"\n icon={ChevronUp}\n onStart={() => repeat.start(1)}\n onStop={repeat.stop}\n disabled={\n Number.isFinite(upper) && value !== undefined && value >= upper\n }\n />\n <StepButton\n label=\"Decrease\"\n icon={ChevronDown}\n onStart={() => repeat.start(-1)}\n onStop={repeat.stop}\n disabled={\n Number.isFinite(lower) && value !== undefined && value <= lower\n }\n />\n </div>\n </div>\n );\n}\n\nfunction StepButton({\n label,\n icon: Icon,\n onStart,\n onStop,\n disabled,\n}: {\n label: string;\n icon: React.ComponentType<{ className?: string }>;\n onStart: () => void;\n onStop: () => void;\n disabled?: boolean;\n}) {\n return (\n <button\n type=\"button\"\n aria-label={label}\n tabIndex={-1}\n disabled={disabled}\n onPointerDown={(event) => {\n // Keep focus (and any draft) in the field the buttons belong to.\n event.preventDefault();\n onStart();\n }}\n onPointerUp={onStop}\n onPointerLeave={onStop}\n onPointerCancel={onStop}\n className={cn(\n 'flex h-1/2 cursor-pointer items-center justify-center',\n 'text-muted-foreground transition-colors hover:bg-accent hover:text-foreground',\n 'disabled:pointer-events-none disabled:opacity-30'\n )}\n >\n <Icon className=\"size-2.5\" />\n </button>\n );\n}\n\n// ---------------------------------------------------------------------------\n// Contrast\n// ---------------------------------------------------------------------------\n\n/** WCAG thresholds as the three state colours the system already rations. */\nexport function contrastTone(ratio: number): string {\n if (ratio >= 4.5) return 'text-success';\n if (ratio >= 3) return 'text-warning';\n return 'text-destructive';\n}\n\nexport function ContrastBadge({\n label,\n ratio,\n}: {\n label: string;\n ratio: number | null;\n}) {\n return (\n <span className=\"inline-flex items-center gap-1 text-[11px] text-muted-foreground\">\n {label}\n <span\n className={cn(\n 'font-medium tabular-nums',\n ratio === null ? 'text-muted-foreground' : contrastTone(ratio)\n )}\n title={\n ratio === null\n ? 'Both colours must resolve to a hex'\n : ratio >= 4.5\n ? 'Meets WCAG AA for body text'\n : ratio >= 3\n ? 'Large text only'\n : 'Below WCAG minimums'\n }\n >\n {ratio === null ? '—' : `${ratio.toFixed(1)}:1`}\n </span>\n </span>\n );\n}\n","/**\n * The playground's own model of a quality finding.\n *\n * The server hands the same finding over in two shapes: `/validate` returns\n * rich `QualityDiagnostic` objects, while `/generate` flattens each one into a\n * `GenerationWarning` whose real fields live in `context` and whose severity is\n * squashed to `warning | info`. Normalising both here keeps that asymmetry out\n * of the panel, the store and the Monaco markers.\n */\n\nexport type QualitySeverity = 'error' | 'warning' | 'info';\n\nexport type QualityCertainty =\n | 'deterministic'\n | 'measured'\n | 'estimated'\n | 'rendered'\n | 'evaluative';\n\nexport interface QualityFixOp {\n op: 'add' | 'replace' | 'remove' | 'move' | 'copy' | 'test';\n path: string;\n value?: unknown;\n from?: string;\n}\n\nexport interface QualityEvidence {\n actual?: unknown;\n expected?: unknown;\n unit?: string;\n [key: string]: unknown;\n}\n\nexport interface QualityFinding {\n /** Stable within one analysis; used as a React key and for fix dedup. */\n id: string;\n code: string;\n message: string;\n /** RFC 6901 pointer into the authored document. */\n path: string;\n severity: QualitySeverity;\n ruleId?: string;\n category?: string;\n certainty?: QualityCertainty;\n blocking?: boolean;\n suggestion?: string;\n relatedPaths?: readonly string[];\n evidence?: QualityEvidence;\n fixes?: readonly QualityFixOp[];\n}\n\nexport interface GenerationWarningLike {\n component: string;\n message: string;\n severity?: 'warning' | 'info';\n context?: Record<string, unknown>;\n}\n\nconst SEVERITIES: readonly QualitySeverity[] = ['error', 'warning', 'info'];\n\nconst SEVERITY_RANK: Record<string, number> = {\n error: 0,\n warning: 1,\n info: 2,\n};\n\n/**\n * Anything that is not one of the three legal severities sorts last rather\n * than throwing, so a future server-side severity cannot crash the panel.\n */\nconst UNRANKED = 3;\n\nfunction rankOf(severity: unknown): number {\n return typeof severity === 'string' && severity in SEVERITY_RANK\n ? (SEVERITY_RANK[severity] as number)\n : UNRANKED;\n}\n\nconst CERTAINTIES: readonly QualityCertainty[] = [\n 'deterministic',\n 'measured',\n 'estimated',\n 'rendered',\n 'evaluative',\n];\n\nconst FIX_OPS: readonly QualityFixOp['op'][] = [\n 'add',\n 'replace',\n 'remove',\n 'move',\n 'copy',\n 'test',\n];\n\n/** Findings with no category are grouped under this bucket. */\nconst UNCATEGORIZED = 'other';\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return typeof value === 'object' && value !== null && !Array.isArray(value);\n}\n\n/**\n * The same test as `isRecord` but without the type predicate, for values that\n * are already typed and only need the runtime guard — narrowing them to\n * `Record<string, unknown>` would throw away the declared shape.\n */\nfunction looksLikeObject(value: unknown): boolean {\n return isRecord(value);\n}\n\nfunction asString(value: unknown): string | undefined {\n return typeof value === 'string' ? value : undefined;\n}\n\nfunction asSeverity(value: unknown): QualitySeverity | undefined {\n return typeof value === 'string' &&\n (SEVERITIES as readonly string[]).includes(value)\n ? (value as QualitySeverity)\n : undefined;\n}\n\nfunction asCertainty(value: unknown): QualityCertainty | undefined {\n return typeof value === 'string' &&\n (CERTAINTIES as readonly string[]).includes(value)\n ? (value as QualityCertainty)\n : undefined;\n}\n\nfunction asStringArray(value: unknown): readonly string[] | undefined {\n if (!Array.isArray(value)) return undefined;\n const strings = value.filter((entry): entry is string => {\n return typeof entry === 'string';\n });\n return strings.length > 0 ? strings : undefined;\n}\n\nfunction asEvidence(value: unknown): QualityEvidence | undefined {\n return isRecord(value) ? (value as QualityEvidence) : undefined;\n}\n\n/**\n * A malformed patch operation is worse than none: the fix button would apply\n * garbage to the author's document, so anything without a legal `op` and a\n * string `path` is discarded rather than passed along.\n */\nfunction asFixes(value: unknown): readonly QualityFixOp[] | undefined {\n if (!Array.isArray(value)) return undefined;\n const fixes: QualityFixOp[] = [];\n for (const entry of value) {\n if (!isRecord(entry)) continue;\n const op = entry['op'];\n const path = entry['path'];\n if (typeof op !== 'string' || typeof path !== 'string') continue;\n if (!(FIX_OPS as readonly string[]).includes(op)) continue;\n const fix: QualityFixOp = { op: op as QualityFixOp['op'], path };\n if ('value' in entry) fix.value = entry['value'];\n const from = asString(entry['from']);\n if (from !== undefined) fix.from = from;\n fixes.push(fix);\n }\n return fixes.length > 0 ? fixes : undefined;\n}\n\n/**\n * The generate path double-encodes the code: the message arrives as\n * \"[W_QUALITY_FONT_SIZE_MIN] Body text is 5pt\" while the code is also in\n * `context.code`. Stripping the prefix keeps the panel from showing the code\n * twice, but only when the two agree — an unrelated bracketed prefix is part\n * of the rule's own wording and must survive.\n */\nfunction stripCodePrefix(message: string, code: string | undefined): string {\n if (!code) return message;\n const prefix = `[${code}] `;\n return message.startsWith(prefix) ? message.slice(prefix.length) : message;\n}\n\n/**\n * Ids must survive a re-render of the same analysis, so they are derived from\n * the finding rather than from a counter or a random value. The index breaks\n * ties between findings that share a code and a path.\n */\nfunction makeId(code: string, path: string, index: number): string {\n return `${code || 'quality'}:${path}:${index}`;\n}\n\nfunction buildFinding(\n fields: {\n code: unknown;\n message: unknown;\n path: unknown;\n severity: QualitySeverity;\n ruleId: unknown;\n category: unknown;\n certainty: unknown;\n blocking: unknown;\n suggestion: unknown;\n relatedPaths: unknown;\n evidence: unknown;\n fixes: unknown;\n },\n index: number\n): QualityFinding | undefined {\n const code = asString(fields.code) ?? '';\n const rawMessage = asString(fields.message) ?? '';\n // Nothing to render and nothing to look up: the entry is not a finding.\n if (!code && !rawMessage) return undefined;\n\n const path = asString(fields.path) ?? '';\n const finding: QualityFinding = {\n id: makeId(code, path, index),\n code,\n // A finding row with no text at all is unreadable, so a code-only entry\n // shows its code rather than rendering blank.\n message: stripCodePrefix(rawMessage, code) || code,\n path,\n severity: fields.severity,\n };\n\n const ruleId = asString(fields.ruleId);\n if (ruleId !== undefined) finding.ruleId = ruleId;\n\n const category = asString(fields.category);\n if (category !== undefined) finding.category = category;\n\n const certainty = asCertainty(fields.certainty);\n if (certainty !== undefined) finding.certainty = certainty;\n\n if (typeof fields.blocking === 'boolean') finding.blocking = fields.blocking;\n\n const suggestion = asString(fields.suggestion);\n if (suggestion !== undefined) finding.suggestion = suggestion;\n\n const relatedPaths = asStringArray(fields.relatedPaths);\n if (relatedPaths !== undefined) finding.relatedPaths = relatedPaths;\n\n const evidence = asEvidence(fields.evidence);\n if (evidence !== undefined) finding.evidence = evidence;\n\n const fixes = asFixes(fields.fixes);\n if (fixes !== undefined) finding.fixes = fixes;\n\n return finding;\n}\n\n/** Split a generate response's warnings into quality findings and everything else. */\nexport function splitQualityWarnings(\n warnings: readonly GenerationWarningLike[] | null | undefined\n): { findings: QualityFinding[]; others: GenerationWarningLike[] } {\n const findings: QualityFinding[] = [];\n const others: GenerationWarningLike[] = [];\n if (!Array.isArray(warnings)) return { findings, others };\n\n for (const warning of warnings) {\n // The array is typed, but it comes off the wire — a null slot is possible.\n if (!looksLikeObject(warning)) continue;\n if (warning.component !== 'quality') {\n others.push(warning);\n continue;\n }\n\n const context = isRecord(warning.context) ? warning.context : {};\n // `context.originalSeverity` is the only place an 'error' survives; the\n // top-level severity was already coerced to warning | info by the server.\n const severity =\n asSeverity(context['originalSeverity']) ??\n asSeverity(warning.severity) ??\n 'warning';\n\n const finding = buildFinding(\n {\n code: context['code'],\n message: warning.message,\n path: context['path'],\n severity,\n ruleId: context['ruleId'],\n category: context['category'],\n certainty: context['certainty'],\n blocking: context['blocking'],\n suggestion: context['suggestion'],\n relatedPaths: context['relatedPaths'],\n evidence: context['evidence'],\n fixes: context['fixes'],\n },\n findings.length\n );\n if (finding) findings.push(finding);\n }\n\n return { findings, others };\n}\n\n/** Findings from a /validate response's qualityAnalysis object. */\nexport function findingsFromAnalysis(analysis: unknown): QualityFinding[] {\n if (!isRecord(analysis)) return [];\n const diagnostics = analysis['diagnostics'];\n if (!Array.isArray(diagnostics)) return [];\n\n const findings: QualityFinding[] = [];\n for (const diagnostic of diagnostics) {\n if (!isRecord(diagnostic)) continue;\n const finding = buildFinding(\n {\n code: diagnostic['code'],\n message: diagnostic['message'],\n path: diagnostic['path'],\n severity: asSeverity(diagnostic['severity']) ?? 'warning',\n ruleId: diagnostic['ruleId'],\n category: diagnostic['category'],\n certainty: diagnostic['certainty'],\n blocking: diagnostic['blocking'],\n suggestion: diagnostic['suggestion'],\n relatedPaths: diagnostic['relatedPaths'],\n evidence: diagnostic['evidence'],\n fixes: diagnostic['fixes'],\n },\n findings.length\n );\n if (finding) findings.push(finding);\n }\n return findings;\n}\n\nexport function countBySeverity(findings: readonly QualityFinding[]): {\n error: number;\n warning: number;\n info: number;\n} {\n const counts = { error: 0, warning: 0, info: 0 };\n if (!Array.isArray(findings)) return counts;\n for (const finding of findings) {\n const severity = asSeverity(finding?.severity);\n if (severity) counts[severity] += 1;\n }\n return counts;\n}\n\n/** error < warning < info, then by path, then by code. For Array.sort. */\nexport function compareFindings(a: QualityFinding, b: QualityFinding): number {\n const rankA = rankOf(a?.severity);\n const rankB = rankOf(b?.severity);\n if (rankA !== rankB) return rankA - rankB;\n const pathA = a?.path ?? '';\n const pathB = b?.path ?? '';\n if (pathA !== pathB) return pathA < pathB ? -1 : 1;\n const codeA = a?.code ?? '';\n const codeB = b?.code ?? '';\n if (codeA === codeB) return 0;\n return codeA < codeB ? -1 : 1;\n}\n\n/** Groups sorted by worst severity within the group, then category name. */\nexport function groupByCategory(\n findings: readonly QualityFinding[]\n): Array<{ category: string; findings: QualityFinding[] }> {\n const groups = new Map<string, QualityFinding[]>();\n if (!Array.isArray(findings)) return [];\n\n for (const finding of findings) {\n if (!finding) continue;\n const category = finding.category || UNCATEGORIZED;\n const bucket = groups.get(category);\n if (bucket) bucket.push(finding);\n else groups.set(category, [finding]);\n }\n\n const worstRank = (group: readonly QualityFinding[]): number => {\n return group.reduce((worst, finding) => {\n const rank = rankOf(finding.severity);\n return rank < worst ? rank : worst;\n }, UNRANKED);\n };\n\n return Array.from(groups.entries())\n .map(([category, bucket]) => ({\n category,\n findings: [...bucket].sort(compareFindings),\n }))\n .sort((a, b) => {\n const rankDelta = worstRank(a.findings) - worstRank(b.findings);\n if (rankDelta !== 0) return rankDelta;\n if (a.category === b.category) return 0;\n return a.category < b.category ? -1 : 1;\n });\n}\n\nexport function filterByMinSeverity(\n findings: readonly QualityFinding[],\n min: QualitySeverity\n): QualityFinding[] {\n if (!Array.isArray(findings)) return [];\n // An unknown threshold must not silently hide findings, so it lets all pass.\n const limit = asSeverity(min) ? rankOf(min) : UNRANKED;\n return findings.filter((finding) => rankOf(finding?.severity) <= limit);\n}\n\n/** Human label for a certainty, e.g. 'measured' -> 'Measured'. */\nexport function certaintyLabel(\n c: QualityCertainty | undefined\n): string | undefined {\n const certainty = asCertainty(c);\n if (!certainty) return undefined;\n return certainty.charAt(0).toUpperCase() + certainty.slice(1);\n}\n","/**\n * The run policy an author writes by hand.\n *\n * The profile and gate controls cover the two settings most runs need; this\n * covers the rest of the contract — per-rule severity, enable/disable and\n * parameters, suppressions, and the diagnostic budget — which until now was\n * reachable only through the CLI's `--quality-policy` file.\n *\n * `gate` is deliberately NOT accepted here. It has a control of its own, and\n * two writable sources for one field means the dropdown either lies or silently\n * loses, so naming it in the JSON is reported as a mistake instead.\n */\n\nimport { rulesForFormat } from './quality-rules';\n\nexport interface QualityRuleConfiguration {\n enabled?: boolean;\n severity?: 'error' | 'warning' | 'info';\n parameters?: Record<string, unknown>;\n}\n\nexport interface QualitySuppression {\n ruleId?: string;\n code?: string;\n path?: string;\n pathMatch?: 'exact' | 'subtree';\n reason: string;\n}\n\nexport interface EditableQualityPolicy {\n rules?: Record<string, QualityRuleConfiguration>;\n suppressions?: QualitySuppression[];\n maxDiagnostics?: number;\n onRuleError?: 'continue' | 'throw';\n}\n\nexport type ParsedQualityPolicy =\n | { ok: true; policy: EditableQualityPolicy | undefined }\n | { ok: false; error: string };\n\n/** What a fresh editor opens on: valid, inert, and a shape to copy. */\nexport const EMPTY_POLICY_TEXT = `{\n \"rules\": {},\n \"suppressions\": []\n}\n`;\n\nconst SEVERITIES = new Set(['error', 'warning', 'info']);\n\nfunction isPlainObject(value: unknown): value is Record<string, unknown> {\n return typeof value === 'object' && value !== null && !Array.isArray(value);\n}\n\n/**\n * Parse and check a hand-written policy.\n *\n * Errors are values and name the offending key, because this text is typed by\n * hand and the server's own rejection would arrive as a 400 the panel reports\n * far from the field that caused it.\n */\nexport function parseQualityPolicy(\n text: string | undefined\n): ParsedQualityPolicy {\n const trimmed = (text ?? '').trim();\n if (!trimmed) return { ok: true, policy: undefined };\n\n let parsed: unknown;\n try {\n parsed = JSON.parse(trimmed);\n } catch (error) {\n return { ok: false, error: `Not valid JSON: ${(error as Error).message}` };\n }\n if (!isPlainObject(parsed)) {\n return { ok: false, error: 'A policy must be a JSON object.' };\n }\n\n if ('gate' in parsed) {\n return {\n ok: false,\n error: 'Remove \"gate\" — it is set by the Gate control above.',\n };\n }\n if ('profile' in parsed) {\n return {\n ok: false,\n error: 'Remove \"profile\" — it is set by the Profile control above.',\n };\n }\n\n // Every key below is read by name, so anything else would be dropped in\n // silence — `{\"maxDiagnostic\": 0}` parsed as a valid empty policy and the\n // budget the author asked for never applied. Say so instead.\n const EDITABLE_KEYS = new Set([\n 'rules',\n 'suppressions',\n 'maxDiagnostics',\n 'onRuleError',\n ]);\n const unknownKey = Object.keys(parsed).find((key) => !EDITABLE_KEYS.has(key));\n if (unknownKey !== undefined) {\n return {\n ok: false,\n error: `Unknown key \"${unknownKey}\". A policy takes: ${[...EDITABLE_KEYS].join(', ')}.`,\n };\n }\n\n const known = new Set(rulesForFormat().map((rule) => rule.id));\n const policy: EditableQualityPolicy = {};\n\n if (parsed.rules !== undefined) {\n if (!isPlainObject(parsed.rules)) {\n return {\n ok: false,\n error: '\"rules\" must be an object keyed by rule id.',\n };\n }\n for (const [ruleId, configuration] of Object.entries(parsed.rules)) {\n if (!known.has(ruleId)) {\n return {\n ok: false,\n error: `Unknown rule \"${ruleId}\". This format ships: ${[...known].join(', ')}.`,\n };\n }\n if (!isPlainObject(configuration)) {\n return {\n ok: false,\n error: `Rule \"${ruleId}\" must be an object, e.g. { \"severity\": \"warning\" }.`,\n };\n }\n if (\n configuration.severity !== undefined &&\n !SEVERITIES.has(configuration.severity as string)\n ) {\n return {\n ok: false,\n error: `Rule \"${ruleId}\" has an invalid severity. Use \"error\", \"warning\" or \"info\".`,\n };\n }\n if (\n configuration.enabled !== undefined &&\n typeof configuration.enabled !== 'boolean'\n ) {\n return {\n ok: false,\n error: `Rule \"${ruleId}\": \"enabled\" must be true or false.`,\n };\n }\n if (\n configuration.parameters !== undefined &&\n !isPlainObject(configuration.parameters)\n ) {\n return {\n ok: false,\n error: `Rule \"${ruleId}\": \"parameters\" must be an object.`,\n };\n }\n }\n policy.rules = parsed.rules as Record<string, QualityRuleConfiguration>;\n }\n\n if (parsed.suppressions !== undefined) {\n if (!Array.isArray(parsed.suppressions)) {\n return { ok: false, error: '\"suppressions\" must be an array.' };\n }\n for (const [index, entry] of parsed.suppressions.entries()) {\n if (!isPlainObject(entry)) {\n return { ok: false, error: `Suppression ${index} must be an object.` };\n }\n // The contract makes `reason` mandatory on purpose: a muted finding that\n // nobody has to justify is how a rule quietly stops being enforced.\n if (typeof entry.reason !== 'string' || !entry.reason.trim()) {\n return {\n ok: false,\n error: `Suppression ${index} needs a \"reason\" saying why the finding is acceptable.`,\n };\n }\n if (\n entry.pathMatch !== undefined &&\n entry.pathMatch !== 'exact' &&\n entry.pathMatch !== 'subtree'\n ) {\n return {\n ok: false,\n error: `Suppression ${index}: \"pathMatch\" must be \"exact\" or \"subtree\".`,\n };\n }\n }\n policy.suppressions = parsed.suppressions as QualitySuppression[];\n }\n\n if (parsed.maxDiagnostics !== undefined) {\n if (\n !Number.isInteger(parsed.maxDiagnostics) ||\n (parsed.maxDiagnostics as number) < 0\n ) {\n return {\n ok: false,\n error: '\"maxDiagnostics\" must be a whole number, zero or more.',\n };\n }\n policy.maxDiagnostics = parsed.maxDiagnostics as number;\n }\n\n if (parsed.onRuleError !== undefined) {\n if (parsed.onRuleError !== 'continue' && parsed.onRuleError !== 'throw') {\n return {\n ok: false,\n error: '\"onRuleError\" must be \"continue\" or \"throw\".',\n };\n }\n policy.onRuleError = parsed.onRuleError;\n }\n\n const empty =\n (policy.rules === undefined || Object.keys(policy.rules).length === 0) &&\n (policy.suppressions === undefined || policy.suppressions.length === 0) &&\n policy.maxDiagnostics === undefined &&\n policy.onRuleError === undefined;\n\n return { ok: true, policy: empty ? undefined : policy };\n}\n","/**\n * The shipped quality profiles, and the one place a stored profile id is\n * turned into a request.\n *\n * Settings persist to a single localStorage key shared by both playgrounds\n * (`window.__JTO_FORMAT__` picks the format; the origin is the same), so a\n * profile chosen on the DOCX playground is still in storage when the PPTX one\n * opens. Sending it would be worse than useless: the client stamps\n * `formats: [FORMAT]` itself, so the server's compatibility check passes, the\n * id matches no registered profile, and the analysis silently runs on defaults\n * while the panel and the response both report the profile as active.\n * Resolution therefore happens here, at the point of use, rather than in a\n * component that only guards while it is mounted.\n */\n\nimport { FORMAT, type FormatName } from './env';\nimport { parseQualityPolicy } from './quality-policy';\n\nexport interface QualityProfileChoice {\n id: string;\n label: string;\n description: string;\n}\n\nexport const QUALITY_PROFILES: Record<\n FormatName,\n readonly QualityProfileChoice[]\n> = {\n docx: [\n {\n id: 'client-report',\n label: 'Client report',\n description:\n 'Running head with page numbers after the cover; takeaway and source wherever a block declares them; every size on the theme scale, at most eight in play; no page rendered empty or half blank under the running head; at least one chart or table',\n },\n {\n id: 'executive-report',\n label: 'Executive report',\n description: 'Short decision document with strict outline continuity',\n },\n {\n id: 'technical-report',\n label: 'Technical report',\n description:\n 'Running head with page numbers after the cover; a source wherever a block declares one; no heading skipped; every size on the theme scale, at most nine in play; no page rendered empty or half blank; at least one chart or table',\n },\n {\n id: 'legal-appendix',\n label: 'Legal appendix',\n description: 'Dense appendix: preserve integrity without editorial taste',\n },\n {\n id: 'general',\n label: 'General',\n description:\n 'The format default: integrity and information-design rules, nothing required by structure',\n },\n ],\n pptx: [\n {\n id: 'executive-presentation',\n label: 'Executive presentation',\n description: 'Decision deck optimized for scan speed and projection',\n },\n {\n id: 'technical-presentation',\n label: 'Technical presentation',\n description: 'Portable professional presentation defaults',\n },\n ],\n};\n\n/** The profiles offerable for the format this playground is serving. */\nexport function profilesForFormat(): readonly QualityProfileChoice[] {\n return QUALITY_PROFILES[FORMAT];\n}\n\n/** The profile id stored for this format, if it still names a shipped one. */\nexport function storedProfileId(\n ids: Partial<Record<FormatName, string>> | undefined\n): string | undefined {\n const id = ids?.[FORMAT];\n return isProfileForFormat(id) ? id : undefined;\n}\n\n/** True when `id` names a profile the current format actually ships. */\nexport function isProfileForFormat(id: string | undefined): id is string {\n return id !== undefined && QUALITY_PROFILES[FORMAT].some((p) => p.id === id);\n}\n\nexport interface QualityRequestOptions {\n profile?: { id: string; formats: string[] };\n policy?: Record<string, unknown>;\n}\n\n/**\n * Build the `options.quality` payload for a generate or validate request.\n *\n * Returns undefined when neither a profile nor a gate was asked for, so the\n * request omits the key entirely and the server keeps its own defaults. A\n * profile id belonging to the other format is dropped rather than sent.\n */\nexport function buildQualityOptions(\n profileId: string | undefined,\n gate: string | undefined,\n policyText?: string\n): QualityRequestOptions | undefined {\n const options: QualityRequestOptions = {};\n if (isProfileForFormat(profileId)) {\n options.profile = { id: profileId, formats: [FORMAT] };\n }\n // A policy still being typed is not sent at all: the run keeps working under\n // the last good one rather than failing on every unbalanced brace.\n const parsed = parseQualityPolicy(policyText);\n const authored = parsed.ok ? parsed.policy : undefined;\n const hasGate = gate === 'error' || gate === 'warning' || gate === 'info';\n if (authored || hasGate) {\n options.policy = {\n ...(authored ?? {}),\n // The Gate control owns this field; `parseQualityPolicy` rejects a policy\n // that also names it, so there is nothing here to overwrite.\n ...(hasGate && { gate }),\n };\n }\n return options.profile || options.policy ? options : undefined;\n}\n","/**\n * Ordering tickets for quality analyses.\n *\n * Three independent producers write the same store slice: the editor's\n * debounced validate pass, the preview's immediate re-analysis after a fix,\n * and the build's own findings. Each aborts only its own in-flight request, so\n * per-request cancellation cannot order them against each other — a ~10s build\n * started before an edit still resolves after the validate pass that overtook\n * it, and would otherwise reinstate findings for text the editor no longer\n * holds.\n *\n * Every producer takes a ticket before it starts and drops its result if a\n * higher one has already landed. Tickets are process-wide because the writers\n * are separate hook instances in one bundle; a per-instance counter would\n * compare numbers that mean nothing to each other.\n */\n\nimport type { QualityState } from '../store/output-store';\n\nlet counter = 0;\n\n/** Monotonic, allocated at the moment work starts, not when it finishes. */\nexport function nextQualityTicket(): number {\n counter += 1;\n return counter;\n}\n\n/** True when a newer analysis has already been committed. */\nexport function isStaleQualityTicket(\n current: QualityState | null | undefined,\n ticket: number\n): boolean {\n return current != null && current.seq > ticket;\n}\n","import { useCallback, useContext, useEffect, useRef } from 'react';\nimport { API_ENDPOINTS } from '../config/api';\nimport { FORMAT } from '../lib/env';\nimport { countBySeverity, findingsFromAnalysis } from '../lib/quality-findings';\nimport { buildQualityOptions, storedProfileId } from '../lib/quality-profiles';\nimport {\n isStaleQualityTicket,\n nextQualityTicket,\n} from '../lib/quality-sequence';\nimport type { QualityState } from '../store/output-store';\nimport {\n OutputStoreContext,\n useOutputStore,\n} from '../store/output-store-provider';\nimport { useSettingsStore } from '../store/settings-store-provider';\nimport { ThemesStoreContext } from '../store/themes-store-provider';\nimport { validThemesByName } from '../store/themes-store';\nimport {\n expandForServer,\n type ExpandForServerResult,\n} from '../lib/plugins/expand-for-server';\nimport type { QualityFinding } from '../lib/quality-findings';\nimport { isJsonMediaType } from './usePresentationGenerator';\n\n/**\n * Point findings computed against the expanded document back at the text the\n * author sees. A finding inside a plugin's output has no node to fix in the\n * editor — its patch would land on the wrong thing — so it keeps only its\n * path to the plugin node and says which component it came from.\n */\nfunction remapFindings(\n findings: QualityFinding[],\n remap: ExpandForServerResult['remap']\n): QualityFinding[] {\n return findings.map((finding) => {\n const target = remap(finding.path);\n const relatedPaths = finding.relatedPaths?.map((p) => remap(p).path);\n if (!target.insidePlugin) {\n if (target.path === finding.path && !relatedPaths) return finding;\n return {\n ...finding,\n path: target.path,\n ...(relatedPaths ? { relatedPaths } : {}),\n };\n }\n return {\n ...finding,\n path: target.path,\n message: `${finding.message} (inside the output of a browser plugin — fix it in the plugin)`,\n ...(relatedPaths ? { relatedPaths } : {}),\n fixes: undefined,\n };\n });\n}\n\n/**\n * Long enough that a burst of typing costs one request rather than one per\n * keystroke, short enough that a pause reads as \"the panel just updated\".\n *\n * The upper bound is not taste: `/api/*` rate-limits on `METHOD:path`, and in\n * production that budget is 100 requests per 15 minutes (server config\n * `RATE_LIMIT_MAX`). One analysis per typing pause has to stay well under\n * ~7 requests/minute sustained or the panel starts answering 429 to the author\n * who is typing the most.\n */\nconst ANALYSIS_DEBOUNCE_MS = 1500;\n\ninterface ValidateResponseBody {\n success?: boolean;\n data?: { valid?: boolean; qualityAnalysis?: unknown };\n error?: string;\n /** `QUALITY_GATE_FAILED` distinguishes a real rejection from a 429 or a 500. */\n code?: string;\n /** Present on a gate failure: the analysis that tripped it. */\n quality?: unknown;\n}\n\n/** The JSON body, or undefined when the response is not JSON at all. */\nasync function readJsonBody(\n response: Response\n): Promise<ValidateResponseBody | undefined> {\n // A proxy that never reached the API answers with its own HTML error page,\n // and parsing that unguarded is what turns a dead backend into\n // `Unexpected token '<'` instead of the status it actually was.\n if (!isJsonMediaType(response.headers.get('content-type'))) return undefined;\n try {\n return (await response.json()) as ValidateResponseBody;\n } catch {\n return undefined;\n }\n}\n\n/**\n * The analysis-level fields, read one at a time.\n *\n * Spreading the server's object into the store would let an unexpected type\n * through to the panel, where a `blocked` that is a string reads as truthy and\n * a `profileId` that is an object renders as `[object Object]`.\n */\nfunction readAnalysisFlags(\n analysis: unknown\n): Pick<QualityState, 'profileId' | 'blocked' | 'truncated'> {\n if (!analysis || typeof analysis !== 'object') return {};\n const source = analysis as Record<string, unknown>;\n return {\n ...(typeof source.profileId === 'string'\n ? { profileId: source.profileId }\n : {}),\n ...(typeof source.blocked === 'boolean' ? { blocked: source.blocked } : {}),\n ...(typeof source.truncated === 'boolean'\n ? { truncated: source.truncated }\n : {}),\n };\n}\n\n/** The API's own message when there is one; the status is all that is left. */\nfunction describeFailure(\n response: Response,\n body: ValidateResponseBody | undefined\n): string {\n if (body?.error) return body.error;\n return `Quality analysis failed (${response.status}${\n response.statusText ? ` ${response.statusText}` : ''\n }).`;\n}\n\n/**\n * Re-run the quality analysis as the document changes, without rendering it.\n *\n * `/validate` returns the rich diagnostics `/generate` flattens into warnings,\n * so this is what keeps the panel current between builds — the author sees the\n * findings move while editing rather than only after asking for bytes.\n */\nexport function useQualityAnalysis(): {\n analyze: (\n documentName: string,\n documentText: string,\n options?: { immediate?: boolean }\n ) => void;\n cancel: () => void;\n} {\n const setOutput = useOutputStore((state) => state.setOutput);\n const outputStore = useContext(OutputStoreContext)!;\n // Optional: the JSON editor page has no themes store, and a plugin then\n // renders against the built-in theme its document names.\n const themesStore = useContext(ThemesStoreContext);\n // Read here rather than inside the request so a settings change re-creates\n // `analyze`, and a caller holding the old one cannot keep analysing under a\n // profile the author has already switched away from.\n const profileIds = useSettingsStore((state) => state.qualityProfileIds);\n const profileId = storedProfileId(profileIds);\n const gate = useSettingsStore((state) => state.qualityGate);\n const policyText = useSettingsStore(\n (state) => state.qualityPolicies?.[FORMAT]\n );\n\n const timerRef = useRef<ReturnType<typeof setTimeout> | null>(null);\n const abortRef = useRef<AbortController | null>(null);\n\n const cancel = useCallback(() => {\n if (timerRef.current !== null) {\n clearTimeout(timerRef.current);\n timerRef.current = null;\n }\n abortRef.current?.abort();\n abortRef.current = null;\n }, []);\n\n // An analysis that outlives its component would resolve into a store nobody\n // is reading, and worse, overwrite findings a later mount had already put\n // there.\n useEffect(() => cancel, [cancel]);\n\n /**\n * Commit a result unless a newer analysis has already landed.\n *\n * Aborting only reaches this hook instance's own request, and there are\n * several instances plus the build writing the same slice, so the ticket is\n * what actually orders them.\n */\n const commit = useCallback(\n (ticket: number, next: QualityState) => {\n if (isStaleQualityTicket(outputStore.getState().quality, ticket)) return;\n setOutput({ quality: next });\n },\n [outputStore, setOutput]\n );\n\n const run = useCallback(\n async (documentName: string, documentText: string) => {\n let jsonDefinition: unknown;\n try {\n jsonDefinition = JSON.parse(documentText);\n } catch {\n // Half-typed JSON is the normal state of an editor, not something to\n // report. Keep the previous findings on screen: blanking the panel on\n // every unbalanced brace is worse than showing findings one keystroke\n // out of date.\n return;\n }\n\n abortRef.current?.abort();\n const controller = new AbortController();\n abortRef.current = controller;\n const ticket = nextQualityTicket();\n\n const quality = buildQualityOptions(profileId, gate, policyText);\n\n // Browser plugins are expanded first, as before a build: the validator\n // knows only standard components. A plugin that fails to expand is an\n // analysis error, reported the way a dead backend is — nothing about\n // the document itself is known yet. Findings come back against the\n // expanded tree and are pointed back at the authored one below.\n let remap: ExpandForServerResult['remap'] = (pointer) => ({\n path: pointer,\n insidePlugin: false,\n });\n try {\n const expansion = await expandForServer(documentText, {\n customThemes: themesStore\n ? validThemesByName(themesStore.getState().customThemes)\n : {},\n signal: controller.signal,\n });\n if (controller.signal.aborted) return;\n if (expansion.expanded) {\n jsonDefinition = JSON.parse(expansion.text);\n remap = expansion.remap;\n }\n } catch (error) {\n if (controller.signal.aborted) return;\n const stored = outputStore.getState().quality;\n const previous =\n stored && stored.documentName === documentName ? stored : undefined;\n commit(ticket, {\n ...(previous ?? {\n findings: [],\n counts: { error: 0, warning: 0, info: 0 },\n source: 'validate' as const,\n }),\n documentName,\n seq: ticket,\n analyzedAt: Date.now(),\n blocked: previous?.blocked ?? false,\n gateError: undefined,\n analysisError: error instanceof Error ? error.message : String(error),\n });\n return;\n }\n\n try {\n const response = await fetch(API_ENDPOINTS.validate, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({\n jsonDefinition,\n options: quality ? { quality } : {},\n }),\n signal: controller.signal,\n });\n const body = await readJsonBody(response);\n\n // A superseded request has already been replaced by a newer one, whose\n // answer is the current one; writing this stale result would undo it.\n if (controller.signal.aborted) return;\n\n if (!response.ok) {\n const message = describeFailure(response, body);\n if (body?.code === 'QUALITY_GATE_FAILED') {\n // A real rejection, and it carries the diagnostics that caused it.\n const findings = remapFindings(\n findingsFromAnalysis(body.quality),\n remap\n );\n commit(ticket, {\n findings,\n counts: countBySeverity(findings),\n documentName,\n seq: ticket,\n blocked: true,\n source: 'validate',\n analyzedAt: Date.now(),\n gateError: message,\n });\n return;\n }\n // Everything else — 429 from the rate limiter, 413, a 500, a dead\n // proxy — says nothing about the document. Reporting it as a gate\n // failure would tell an author with no gate configured that their\n // document was blocked, and discarding the findings alongside it\n // would take away the only thing on screen that was still true.\n // Keeping the findings is only right while they still describe this\n // document. Across a tab switch they describe the previous one, and\n // every finding is a JSON Pointer into a file that is no longer on\n // screen — \"Apply fix\" would patch the wrong document. A stale\n // `blocked` is worse still: a gate rejection from before the switch\n // would keep the new document blocked on a transport error.\n const stored = outputStore.getState().quality;\n const previous =\n stored && stored.documentName === documentName ? stored : undefined;\n commit(ticket, {\n ...(previous ?? {\n findings: [],\n counts: { error: 0, warning: 0, info: 0 },\n source: 'validate' as const,\n }),\n documentName,\n seq: ticket,\n analyzedAt: Date.now(),\n blocked: previous?.blocked ?? false,\n gateError: undefined,\n analysisError: message,\n });\n return;\n }\n\n // `success` is false for a schema-invalid document and for a blocked\n // one, neither of which is a failed request — the analysis, when there\n // is one, is still in `data`.\n const analysis = body?.data?.qualityAnalysis;\n const findings = remapFindings(findingsFromAnalysis(analysis), remap);\n commit(ticket, {\n findings,\n counts: countBySeverity(findings),\n ...readAnalysisFlags(analysis),\n documentName,\n seq: ticket,\n source: 'validate',\n analyzedAt: Date.now(),\n });\n } catch {\n // An abort lands here, and so does a dropped connection. Neither says\n // anything about the document, so the last analysis stands.\n } finally {\n if (abortRef.current === controller) abortRef.current = null;\n }\n },\n [commit, gate, outputStore, policyText, profileId, themesStore]\n );\n\n const analyze = useCallback(\n (\n documentName: string,\n documentText: string,\n options?: { immediate?: boolean }\n ) => {\n if (timerRef.current !== null) {\n clearTimeout(timerRef.current);\n timerRef.current = null;\n }\n if (options?.immediate) {\n void run(documentName, documentText);\n return;\n }\n timerRef.current = setTimeout(() => {\n timerRef.current = null;\n void run(documentName, documentText);\n }, ANALYSIS_DEBOUNCE_MS);\n },\n [run]\n );\n\n return { analyze, cancel };\n}\n"],"names":["parseTheme","text","value","error","serializeTheme","theme","setAt","path","mutateDocumentAtPath","deleteAt","parentPath","key","parent","getAt","next","current","COLOR_HINTS","COLOR_GROUPS","humanize","c","schemaColors","schema","colors","_a","colorTokens","format","keys","required","DocxThemeConfigSchema","PptxThemeConfigSchema","ordered","seen","group","groupKeys","HEX_COLOR","isHexColor","normalizeHex","trimmed","short","full","definedColorKeys","resolveColor","depth","bare","contrastRatio","a","b","la","luminance","lb","light","dark","hex","match","channels","i","DOCX_FONT_ROLES","PPTX_FONT_ROLES","FONT_ROLE_HINTS","PAGE_SIZES","MARGIN_KEYS","TWIPS_PER_UNIT","twipsToUnit","twips","unit","unitToTwips","ALIGNMENTS","DOCX_STYLE_FIELDS","PPTX_STYLE_FIELDS","DOCX_STYLE_NAMES","schemaStyleNames","styles","branch","styleNames","declared","n","styleFields","customStyleNames","known","name","STYLE_LABELS","styleLabel","FORM_KEYS","formKeys","advancedKeys","alsoHandled","handled","summarize","ensureContainers","want","ThemeEditorContext","createContext","ThemeEditorProvider","useThemeEditor","ctx","useContext","EMPTY_CATALOG","catalogCache","catalogRequest","loadCatalog","API_ENDPOINTS","r","data","parsed","useFontCatalog","catalog","setCatalog","useState","useEffect","live","INPUT_CLASS","SELECT_TRIGGER_CLASS","HINT_CLASS","FIELD_LABEL_CLASS","EditorSection","title","hint","actions","defaultOpen","forceOpen","children","toggled","setOpen","open","jsxs","o","cn","jsx","ChevronRight","GroupLabel","ClearButton","label","onClick","className","X","QuietButton","rest","Segmented","options","onChange","option","active","useDraft","commit","draft","setDraft","draftRef","useRef","write","useCallback","onFocus","e","onBlur","pending","onKeyDown","DraftTextInput","onCommit","Input","useRepeat","step","timer","stop","start","direction","tick","delay","DraftNumberInput","min","max","syncDraft","numericStep","lower","upper","bump","stepped","repeat","StepButton","ChevronUp","ChevronDown","Icon","onStart","onStop","disabled","event","contrastTone","ratio","ContrastBadge","SEVERITIES","SEVERITY_RANK","UNRANKED","rankOf","severity","CERTAINTIES","FIX_OPS","UNCATEGORIZED","isRecord","looksLikeObject","asString","asSeverity","asCertainty","asStringArray","strings","entry","asEvidence","asFixes","fixes","op","fix","from","stripCodePrefix","message","code","prefix","makeId","index","buildFinding","fields","rawMessage","finding","ruleId","category","certainty","suggestion","relatedPaths","evidence","splitQualityWarnings","warnings","findings","others","warning","context","findingsFromAnalysis","analysis","diagnostics","diagnostic","countBySeverity","counts","compareFindings","rankA","rankB","pathA","pathB","codeA","codeB","groupByCategory","groups","bucket","worstRank","worst","rank","rankDelta","filterByMinSeverity","limit","certaintyLabel","EMPTY_POLICY_TEXT","isPlainObject","parseQualityPolicy","EDITABLE_KEYS","unknownKey","rulesForFormat","rule","policy","configuration","QUALITY_PROFILES","storedProfileId","ids","id","FORMAT","isProfileForFormat","p","buildQualityOptions","profileId","gate","policyText","authored","hasGate","counter","nextQualityTicket","isStaleQualityTicket","ticket","remapFindings","remap","target","ANALYSIS_DEBOUNCE_MS","readJsonBody","response","isJsonMediaType","readAnalysisFlags","source","describeFailure","body","useQualityAnalysis","setOutput","useOutputStore","state","outputStore","OutputStoreContext","themesStore","ThemesStoreContext","profileIds","useSettingsStore","timerRef","abortRef","cancel","run","documentName","documentText","jsonDefinition","controller","quality","pointer","expansion","expandForServer","validThemesByName","stored","previous","_b"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"useQualityAnalysis-Dr3UHxyN.js","sources":["../../../src/client/lib/theme-editor/model.ts","../../../src/client/components/theme-editor/theme-editor-shared.tsx","../../../src/client/lib/quality-findings.ts","../../../src/client/lib/quality-policy.ts","../../../src/client/lib/quality-profiles.ts","../../../src/client/lib/quality-sequence.ts","../../../src/client/hooks/useQualityAnalysis.ts"],"sourcesContent":["import { ThemeConfigSchema as DocxThemeConfigSchema } from '@json-to-office/shared-docx';\nimport { ThemeConfigSchema as PptxThemeConfigSchema } from '@json-to-office/shared-pptx';\nimport { mutateDocumentAtPath } from '../doc-mutations';\nimport type { FormatName } from '../env';\n\n/**\n * The visual theme editor's model: what a theme of each format is made of,\n * read from the shared schemas so the form cannot drift from them, plus the\n * pure edits the form applies.\n *\n * The form never owns the theme. Every change is applied to the parsed JSON\n * and written back as text; keys the form has no field for (component\n * defaults, font registries, `$schema`) survive untouched.\n */\n\nexport type ThemeJson = Record<string, unknown>;\n\nexport type ParsedTheme =\n | { ok: true; theme: ThemeJson }\n | { ok: false; error: string };\n\nexport function parseTheme(text: string): ParsedTheme {\n try {\n const value = JSON.parse(text);\n if (!value || typeof value !== 'object' || Array.isArray(value)) {\n return { ok: false, error: 'A theme is a JSON object.' };\n }\n return { ok: true, theme: value as ThemeJson };\n } catch (error) {\n return {\n ok: false,\n error: error instanceof Error ? error.message : 'Invalid JSON',\n };\n }\n}\n\nexport function serializeTheme(theme: ThemeJson): string {\n return JSON.stringify(theme, null, 2);\n}\n\nexport type Path = (string | number)[];\n\n/** Set a leaf, creating the objects on the way. Never mutates the input. */\nexport function setAt(theme: ThemeJson, path: Path, value: unknown): ThemeJson {\n return mutateDocumentAtPath(theme, path, value) as ThemeJson;\n}\n\n/** Remove a leaf. Never mutates the input; a missing path is a no-op. */\nexport function deleteAt(theme: ThemeJson, path: Path): ThemeJson {\n if (path.length === 0) return theme;\n const parentPath = path.slice(0, -1);\n const key = path[path.length - 1];\n const parent = getAt(theme, parentPath);\n if (!parent || typeof parent !== 'object') return theme;\n if (Array.isArray(parent)) {\n // `splice` coerces its index: -1 would drop the last entry, NaN the\n // first, 1.5 the second. Only a real index deletes anything.\n if (typeof key !== 'number' || !Number.isInteger(key)) return theme;\n if (key < 0 || key >= parent.length) return theme;\n const next = parent.slice();\n next.splice(key, 1);\n return parentPath.length === 0\n ? (next as unknown as ThemeJson)\n : setAt(theme, parentPath, next);\n }\n if (!Object.prototype.hasOwnProperty.call(parent, key)) return theme;\n const next = { ...(parent as Record<string, unknown>) };\n delete next[key as string];\n return parentPath.length === 0 ? next : setAt(theme, parentPath, next);\n}\n\nexport function getAt(theme: unknown, path: Path): unknown {\n let current: unknown = theme;\n for (const key of path) {\n if (current === null || typeof current !== 'object') return undefined;\n current = (current as Record<string | number, unknown>)[key];\n }\n return current;\n}\n\n// ---------------------------------------------------------------------------\n// Colours\n// ---------------------------------------------------------------------------\n\nexport interface ColorTokenDescriptor {\n key: string;\n label: string;\n group: string;\n required: boolean;\n hint: string;\n}\n\nconst COLOR_HINTS: Record<string, string> = {\n primary: 'Headings, the strongest brand colour',\n secondary: 'Supporting brand colour',\n accent: 'Highlights and emphasis',\n text: 'Default body text',\n textPrimary: 'Primary text',\n textSecondary: 'Secondary text',\n textMuted: 'Captions and de-emphasised text',\n background: 'Page or slide background',\n backgroundPrimary: 'Primary surface',\n backgroundSecondary: 'Alternate surface, table stripes',\n background2: 'Alternate surface, table rules',\n text2: 'Secondary text, captions',\n border: 'Default rules and borders',\n borderPrimary: 'Primary rules',\n borderSecondary: 'Subtle rules',\n accent4: 'Chart series 4',\n accent5: 'Chart series 5',\n accent6: 'Chart series 6',\n};\n\nconst COLOR_GROUPS: Array<[string, string[]]> = [\n ['Core', ['primary', 'secondary', 'accent']],\n ['Text', ['text', 'textPrimary', 'textSecondary', 'textMuted', 'text2']],\n [\n 'Background',\n ['background', 'backgroundPrimary', 'backgroundSecondary', 'background2'],\n ],\n ['Border', ['border', 'borderPrimary', 'borderSecondary']],\n ['Chart', ['accent4', 'accent5', 'accent6']],\n];\n\nfunction humanize(key: string): string {\n return key\n .replace(/([a-z])([A-Z0-9])/g, '$1 $2')\n .replace(/^./, (c) => c.toUpperCase());\n}\n\nfunction schemaColors(schema: unknown): {\n keys: string[];\n required: Set<string>;\n} {\n const colors = (schema as { properties?: { colors?: unknown } })?.properties\n ?.colors as { properties?: Record<string, unknown>; required?: string[] };\n return {\n keys: Object.keys(colors?.properties ?? {}),\n required: new Set(colors?.required ?? []),\n };\n}\n\n/** The colour tokens a format's theme schema declares, grouped for the form. */\nexport function colorTokens(format: FormatName): ColorTokenDescriptor[] {\n const { keys, required } = schemaColors(\n format === 'docx' ? DocxThemeConfigSchema : PptxThemeConfigSchema\n );\n const ordered: ColorTokenDescriptor[] = [];\n const seen = new Set<string>();\n for (const [group, groupKeys] of COLOR_GROUPS) {\n for (const key of groupKeys) {\n if (!keys.includes(key)) continue;\n seen.add(key);\n ordered.push({\n key,\n label: humanize(key),\n group,\n required: required.has(key),\n hint: COLOR_HINTS[key] ?? '',\n });\n }\n }\n for (const key of keys) {\n if (seen.has(key)) continue;\n ordered.push({\n key,\n label: humanize(key),\n group: 'Other',\n required: required.has(key),\n hint: COLOR_HINTS[key] ?? '',\n });\n }\n return ordered;\n}\n\nexport const HEX_COLOR = /^#[0-9A-Fa-f]{6}$/;\n\nexport function isHexColor(value: unknown): value is string {\n return typeof value === 'string' && HEX_COLOR.test(value);\n}\n\n/** `#abc` → `#AABBCC`; `abcdef` → `#ABCDEF`; anything else unchanged. */\nexport function normalizeHex(value: string): string {\n const trimmed = value.trim();\n const short = trimmed.match(/^#?([0-9A-Fa-f]{3})$/);\n if (short) {\n return `#${short[1]\n .split('')\n .map((c) => c + c)\n .join('')\n .toUpperCase()}`;\n }\n const full = trimmed.match(/^#?([0-9A-Fa-f]{6})$/);\n if (full) return `#${full[1].toUpperCase()}`;\n return trimmed;\n}\n\n/** Colour keys a theme defines, for token pickers. */\nexport function definedColorKeys(theme: ThemeJson): string[] {\n const colors = theme.colors;\n if (!colors || typeof colors !== 'object') return [];\n return Object.entries(colors as Record<string, unknown>)\n .filter(([, value]) => typeof value === 'string')\n .map(([key]) => key);\n}\n\n/** Follow token references (`accent4: \"primary\"`) to a hex, or null. */\nexport function resolveColor(\n theme: ThemeJson,\n value: unknown,\n depth = 0\n): string | null {\n if (typeof value !== 'string' || depth > 8) return null;\n if (HEX_COLOR.test(value)) return value;\n const bare = value.match(/^#?([0-9A-Fa-f]{6})$/);\n if (bare) return `#${bare[1]}`;\n const colors = theme.colors as Record<string, unknown> | undefined;\n return colors ? resolveColor(theme, colors[value], depth + 1) : null;\n}\n\n/** WCAG relative-luminance contrast ratio between two hex colours. */\nexport function contrastRatio(a: string, b: string): number | null {\n const la = luminance(a);\n const lb = luminance(b);\n if (la === null || lb === null) return null;\n const [light, dark] = la > lb ? [la, lb] : [lb, la];\n return (light + 0.05) / (dark + 0.05);\n}\n\nfunction luminance(hex: string): number | null {\n const match = hex.match(/^#?([0-9A-Fa-f]{6})$/);\n if (!match) return null;\n const channels = [0, 2, 4].map((i) => {\n const c = parseInt(match[1].slice(i, i + 2), 16) / 255;\n return c <= 0.03928 ? c / 12.92 : ((c + 0.055) / 1.055) ** 2.4;\n });\n return 0.2126 * channels[0] + 0.7152 * channels[1] + 0.0722 * channels[2];\n}\n\n// ---------------------------------------------------------------------------\n// Fonts\n// ---------------------------------------------------------------------------\n\nexport const DOCX_FONT_ROLES = ['heading', 'body', 'mono', 'light'] as const;\nexport type DocxFontRole = (typeof DOCX_FONT_ROLES)[number];\nexport const PPTX_FONT_ROLES = ['heading', 'body'] as const;\n\nexport const FONT_ROLE_HINTS: Record<string, string> = {\n heading: 'Headings and titles',\n body: 'Body text',\n mono: 'Code and tabular figures',\n light: 'Large, light display text',\n};\n\n// ---------------------------------------------------------------------------\n// Page (DOCX)\n// ---------------------------------------------------------------------------\n\nexport const PAGE_SIZES = ['A4', 'A3', 'LETTER', 'LEGAL'] as const;\nexport const MARGIN_KEYS = [\n 'top',\n 'bottom',\n 'left',\n 'right',\n 'header',\n 'footer',\n 'gutter',\n] as const;\n\nexport type LengthUnit = 'in' | 'cm' | 'pt' | 'twips';\nconst TWIPS_PER_UNIT: Record<LengthUnit, number> = {\n in: 1440,\n cm: 1440 / 2.54,\n pt: 20,\n twips: 1,\n};\n\nexport function twipsToUnit(twips: number, unit: LengthUnit): number {\n const value = twips / TWIPS_PER_UNIT[unit];\n return unit === 'twips' ? Math.round(value) : Math.round(value * 100) / 100;\n}\n\nexport function unitToTwips(value: number, unit: LengthUnit): number {\n return Math.round(value * TWIPS_PER_UNIT[unit]);\n}\n\n// ---------------------------------------------------------------------------\n// Styles\n// ---------------------------------------------------------------------------\n\nexport type FieldKind =\n | 'text'\n | 'number'\n | 'boolean'\n | 'select'\n | 'color'\n | 'fontRole'\n | 'fontFamily';\n\nexport interface StyleFieldDescriptor {\n path: Path;\n label: string;\n kind: FieldKind;\n options?: readonly string[];\n unit?: string;\n min?: number;\n max?: number;\n step?: number;\n /** Shown only under \"More\". */\n advanced?: boolean;\n hint?: string;\n}\n\nconst ALIGNMENTS = ['left', 'center', 'right', 'justify'] as const;\n\nexport const DOCX_STYLE_FIELDS: readonly StyleFieldDescriptor[] = [\n { path: ['font'], label: 'Font role', kind: 'fontRole' },\n {\n path: ['size'],\n label: 'Size',\n kind: 'number',\n unit: 'pt',\n min: 8,\n max: 120,\n step: 0.5,\n },\n { path: ['color'], label: 'Colour', kind: 'color' },\n { path: ['bold'], label: 'Bold', kind: 'boolean' },\n { path: ['italic'], label: 'Italic', kind: 'boolean' },\n { path: ['underline'], label: 'Underline', kind: 'boolean' },\n {\n path: ['alignment'],\n label: 'Alignment',\n kind: 'select',\n options: ALIGNMENTS,\n },\n {\n path: ['spacing', 'before'],\n label: 'Space before',\n kind: 'number',\n unit: 'pt',\n min: 0,\n step: 1,\n },\n {\n path: ['spacing', 'after'],\n label: 'Space after',\n kind: 'number',\n unit: 'pt',\n min: 0,\n step: 1,\n },\n {\n path: ['lineSpacing', 'type'],\n label: 'Line spacing',\n kind: 'select',\n options: ['single', 'multiple', 'double', 'atLeast', 'exactly'],\n },\n {\n path: ['lineSpacing', 'value'],\n label: 'Line spacing value',\n kind: 'number',\n min: 0,\n step: 0.05,\n hint: 'A factor for multiple; points for atLeast and exactly',\n },\n {\n path: ['fontWeight'],\n label: 'Weight',\n kind: 'number',\n min: 100,\n max: 900,\n step: 100,\n advanced: true,\n },\n {\n path: ['characterSpacing', 'type'],\n label: 'Tracking',\n kind: 'select',\n options: ['condensed', 'expanded'],\n advanced: true,\n },\n {\n path: ['characterSpacing', 'value'],\n label: 'Tracking amount',\n kind: 'number',\n min: 0,\n step: 1,\n advanced: true,\n hint: 'Twentieths of a point',\n },\n {\n path: ['scale'],\n label: 'Width scale',\n kind: 'number',\n unit: '%',\n min: 1,\n max: 600,\n step: 1,\n advanced: true,\n },\n {\n path: ['keepNext'],\n label: 'Keep with next',\n kind: 'boolean',\n advanced: true,\n },\n {\n path: ['keepLinesTogether'],\n label: 'Keep lines together',\n kind: 'boolean',\n advanced: true,\n },\n {\n path: ['widowControl'],\n label: 'Widow control',\n kind: 'boolean',\n advanced: true,\n },\n {\n path: ['outlineLevel'],\n label: 'Outline level',\n kind: 'number',\n min: 0,\n max: 9,\n step: 1,\n advanced: true,\n },\n { path: ['baseStyle'], label: 'Based on', kind: 'text', advanced: true },\n {\n path: ['followingStyle'],\n label: 'Next style',\n kind: 'text',\n advanced: true,\n },\n];\n\nexport const PPTX_STYLE_FIELDS: readonly StyleFieldDescriptor[] = [\n {\n path: ['fontSize'],\n label: 'Size',\n kind: 'number',\n unit: 'pt',\n min: 1,\n max: 200,\n step: 1,\n },\n { path: ['fontFace'], label: 'Font', kind: 'fontFamily' },\n { path: ['fontColor'], label: 'Colour', kind: 'color' },\n { path: ['bold'], label: 'Bold', kind: 'boolean' },\n { path: ['italic'], label: 'Italic', kind: 'boolean' },\n { path: ['align'], label: 'Alignment', kind: 'select', options: ALIGNMENTS },\n {\n path: ['lineSpacing'],\n label: 'Line spacing',\n kind: 'number',\n unit: 'pt',\n min: 0,\n step: 1,\n advanced: true,\n },\n {\n path: ['charSpacing'],\n label: 'Tracking',\n kind: 'number',\n unit: 'pt',\n step: 0.5,\n advanced: true,\n },\n {\n path: ['paraSpaceAfter'],\n label: 'Space after',\n kind: 'number',\n unit: 'pt',\n min: 0,\n step: 1,\n advanced: true,\n },\n {\n path: ['fontWeight'],\n label: 'Weight',\n kind: 'number',\n min: 100,\n max: 900,\n step: 100,\n advanced: true,\n },\n];\n\n/** DOCX's fixed style slots, in the order they read best. */\nexport const DOCX_STYLE_NAMES = [\n 'normal',\n 'title',\n 'subtitle',\n 'heading1',\n 'heading2',\n 'heading3',\n 'heading4',\n 'heading5',\n 'heading6',\n 'TOC1',\n 'TOC2',\n 'TOC3',\n 'TOC4',\n 'TOC5',\n 'TOC6',\n] as const;\n\nfunction schemaStyleNames(schema: unknown): string[] {\n const styles = (schema as { properties?: { styles?: unknown } })?.properties\n ?.styles as\n | { properties?: Record<string, unknown>; anyOf?: unknown[] }\n | undefined;\n if (!styles) return [];\n if (styles.properties) return Object.keys(styles.properties);\n // Type.Optional(Type.Partial(...)) still exposes `properties` in TypeBox;\n // a union wrapper would not, so read the first object branch.\n const branch = (styles.anyOf ?? []).find(\n (b) => (b as { properties?: unknown }).properties\n ) as { properties: Record<string, unknown> } | undefined;\n return branch ? Object.keys(branch.properties) : [];\n}\n\n/** The named style slots a format's schema declares, in the form's order. */\nexport function styleNames(format: FormatName): string[] {\n if (format === 'docx') {\n const declared = schemaStyleNames(DocxThemeConfigSchema);\n const ordered = DOCX_STYLE_NAMES.filter((n) => declared.includes(n));\n return [\n ...ordered,\n ...declared.filter((n) => !ordered.includes(n as never)),\n ];\n }\n return schemaStyleNames(PptxThemeConfigSchema);\n}\n\nexport function styleFields(\n format: FormatName\n): readonly StyleFieldDescriptor[] {\n return format === 'docx' ? DOCX_STYLE_FIELDS : PPTX_STYLE_FIELDS;\n}\n\n/** Styles the theme defines beyond the schema's named slots (DOCX allows any). */\nexport function customStyleNames(\n theme: ThemeJson,\n format: FormatName\n): string[] {\n const known = new Set(styleNames(format));\n const styles = theme.styles;\n if (!styles || typeof styles !== 'object') return [];\n return Object.keys(styles as Record<string, unknown>).filter(\n (name) => !known.has(name)\n );\n}\n\nexport const STYLE_LABELS: Record<string, string> = {\n normal: 'Normal (body)',\n title: 'Title',\n subtitle: 'Subtitle',\n body: 'Body',\n caption: 'Caption',\n heading1: 'Heading 1',\n heading2: 'Heading 2',\n heading3: 'Heading 3',\n heading4: 'Heading 4',\n heading5: 'Heading 5',\n heading6: 'Heading 6',\n TOC1: 'TOC level 1',\n TOC2: 'TOC level 2',\n TOC3: 'TOC level 3',\n TOC4: 'TOC level 4',\n TOC5: 'TOC level 5',\n TOC6: 'TOC level 6',\n};\n\nexport function styleLabel(name: string): string {\n return STYLE_LABELS[name] ?? name;\n}\n\n// ---------------------------------------------------------------------------\n// Everything else\n// ---------------------------------------------------------------------------\n\n/** Top-level keys the form edits directly; the rest is \"Advanced\". */\nconst FORM_KEYS: Record<FormatName, readonly string[]> = {\n docx: [\n 'name',\n 'displayName',\n 'description',\n 'version',\n 'colors',\n 'fonts',\n 'page',\n 'styles',\n ],\n pptx: [\n 'name',\n 'displayName',\n 'description',\n 'version',\n 'colors',\n 'fonts',\n 'defaults',\n 'styles',\n ],\n};\n\n/** The top-level keys the hand-built sections edit. */\nexport function formKeys(format: FormatName): readonly string[] {\n return FORM_KEYS[format];\n}\n\n/**\n * Keys the form leaves alone, with a one-line description each: neither a\n * hand-built section's nor, once the schema is known, one of the sections\n * derived from it.\n */\nexport function advancedKeys(\n theme: ThemeJson,\n format: FormatName,\n alsoHandled: readonly string[] = []\n): Array<{ key: string; summary: string }> {\n const handled = new Set([...FORM_KEYS[format], ...alsoHandled]);\n return Object.keys(theme)\n .filter((key) => !handled.has(key))\n .map((key) => ({ key, summary: summarize(theme[key]) }));\n}\n\nfunction summarize(value: unknown): string {\n if (Array.isArray(value))\n return `${value.length} item${value.length === 1 ? '' : 's'}`;\n if (value && typeof value === 'object') {\n const keys = Object.keys(value as object);\n return keys.length === 0\n ? 'empty'\n : keys.slice(0, 4).join(', ') + (keys.length > 4 ? ', …' : '');\n }\n if (typeof value === 'string')\n return value.length > 40 ? `${value.slice(0, 40)}…` : value;\n return String(value);\n}\n\n/** Scaffold the containers a fresh theme may be missing, so fields have a home. */\nexport function ensureContainers(\n theme: ThemeJson,\n format: FormatName\n): ThemeJson {\n let next = theme;\n const want: string[] =\n format === 'docx'\n ? ['colors', 'fonts', 'page', 'styles']\n : ['colors', 'fonts', 'defaults', 'styles'];\n for (const key of want) {\n if (!next[key] || typeof next[key] !== 'object')\n next = setAt(next, [key], {});\n }\n return next;\n}\n","import React, {\n createContext,\n useCallback,\n useContext,\n useEffect,\n useRef,\n useState,\n} from 'react';\nimport { ChevronDown, ChevronRight, ChevronUp, X } from 'lucide-react';\nimport { Input } from '../ui/input';\nimport { API_ENDPOINTS } from '../../config/api';\nimport type { Path } from '../../lib/theme-editor/model';\nimport { cn } from '../../lib/utils';\n\n/**\n * Primitives the theme form is built from.\n *\n * Two rules shape everything here. The form never holds the theme: an edit\n * is a path and a value handed to the editor, which writes the JSON back to\n * the tab. And typing never fights the round trip: text and number inputs\n * keep a draft while focused and commit on blur or Enter, so the re-render\n * that follows a commit lands on an input that is no longer being typed in.\n */\n\n// ---------------------------------------------------------------------------\n// Editor context\n// ---------------------------------------------------------------------------\n\nexport interface ThemeEditorActions {\n /** Write a leaf. Containers on the way are created. */\n set: (path: Path, value: unknown) => void;\n /**\n * Remove a leaf. With `prune`, empty objects left behind are removed too,\n * up to (not including) `keepDepth` path segments — a cleared\n * `lineSpacing.value` must not leave `lineSpacing: {}` behind, but the\n * style itself has to stay.\n */\n remove: (path: Path, prune?: { keepDepth: number }) => void;\n}\n\nconst ThemeEditorContext = createContext<ThemeEditorActions | null>(null);\n\nexport const ThemeEditorProvider = ThemeEditorContext.Provider;\n\nexport function useThemeEditor(): ThemeEditorActions {\n const ctx = useContext(ThemeEditorContext);\n if (!ctx) {\n throw new Error('useThemeEditor must be used inside ThemeVisualEditor');\n }\n return ctx;\n}\n\n// ---------------------------------------------------------------------------\n// Font catalog (fetched once per session)\n// ---------------------------------------------------------------------------\n\nexport interface FontCatalog {\n safe: string[];\n google: {\n family: string;\n category: string;\n weights: number[];\n hasItalic: boolean;\n }[];\n}\n\nconst EMPTY_CATALOG: FontCatalog = { safe: [], google: [] };\nlet catalogCache: FontCatalog | null = null;\nlet catalogRequest: Promise<FontCatalog> | null = null;\n\nfunction loadCatalog(): Promise<FontCatalog> {\n if (catalogCache) return Promise.resolve(catalogCache);\n if (!catalogRequest) {\n catalogRequest = fetch(API_ENDPOINTS.fonts.catalog)\n .then((r) => (r.ok ? r.json() : EMPTY_CATALOG))\n .then((data: unknown) => {\n const parsed = data as Partial<FontCatalog> | null;\n catalogCache = {\n safe: Array.isArray(parsed?.safe) ? parsed.safe : [],\n google: Array.isArray(parsed?.google) ? parsed.google : [],\n };\n return catalogCache;\n })\n .catch(() => {\n // The datalist is a convenience; a missing catalog leaves the inputs\n // free-text, which is what they are anyway.\n catalogRequest = null;\n return EMPTY_CATALOG;\n });\n }\n return catalogRequest;\n}\n\nexport function useFontCatalog(): FontCatalog {\n const [catalog, setCatalog] = useState<FontCatalog>(\n catalogCache ?? EMPTY_CATALOG\n );\n useEffect(() => {\n let live = true;\n if (!catalogCache) {\n loadCatalog().then((c) => {\n if (live) setCatalog(c);\n });\n }\n return () => {\n live = false;\n };\n }, []);\n return catalog;\n}\n\n// ---------------------------------------------------------------------------\n// Shared classes\n// ---------------------------------------------------------------------------\n\nexport const INPUT_CLASS = 'h-7 px-2 text-xs shadow-none';\nexport const SELECT_TRIGGER_CLASS =\n 'h-7 min-h-0 w-full gap-1.5 px-2 py-0 text-xs shadow-none [&>span]:truncate [&>span]:whitespace-nowrap';\nexport const HINT_CLASS = 'text-[11px] leading-snug text-muted-foreground';\nexport const FIELD_LABEL_CLASS =\n 'text-[11px] font-medium leading-none text-muted-foreground';\n\n// ---------------------------------------------------------------------------\n// Section\n// ---------------------------------------------------------------------------\n\n/**\n * A form section wearing the rail's eyebrow: disclosure, 11px uppercase\n * tracked name, then a one-line hint under it. Hairline above, no elevation.\n */\nexport function EditorSection({\n title,\n hint,\n actions,\n defaultOpen = true,\n forceOpen,\n children,\n}: {\n title: string;\n hint?: React.ReactNode;\n actions?: React.ReactNode;\n defaultOpen?: boolean;\n /** Held open regardless of the disclosure — while a search is filtering. */\n forceOpen?: boolean;\n children: React.ReactNode;\n}) {\n const [toggled, setOpen] = useState(defaultOpen);\n const open = forceOpen ?? toggled;\n return (\n // The rule divides one section from the one above it; the first has the\n // pane's own header there instead, and two hairlines is one too many.\n <section\n className=\"border-t border-border/60 pt-2 first:border-t-0\"\n data-crumb={title}\n >\n <div className=\"flex h-7 items-center gap-1.5\">\n <button\n type=\"button\"\n onClick={() => setOpen((o) => !o)}\n aria-expanded={open}\n className={cn(\n 'flex h-7 min-w-0 flex-1 cursor-pointer items-center gap-1.5 rounded-sm px-1',\n 'text-foreground/75 transition-colors hover:bg-accent hover:text-foreground',\n 'focus-visible:ring-1 focus-visible:ring-ring focus-visible:outline-none'\n )}\n >\n <ChevronRight\n aria-hidden\n className={cn(\n 'size-3 shrink-0 transition-transform duration-150 ease-out',\n open && 'rotate-90'\n )}\n />\n <span className=\"text-[11px] font-medium tracking-[0.08em] uppercase\">\n {title}\n </span>\n </button>\n {actions && (\n <div className=\"flex shrink-0 items-center gap-1 pr-1\">{actions}</div>\n )}\n </div>\n {hint && <p className={cn(HINT_CLASS, 'px-1 pb-2')}>{hint}</p>}\n {open && <div className=\"flex flex-col gap-3 px-1 pb-4\">{children}</div>}\n </section>\n );\n}\n\n/** 10px tracked divider inside a section, as the rail uses between groups. */\nexport function GroupLabel({ children }: { children: React.ReactNode }) {\n return (\n <div className=\"pt-1 text-[10px] tracking-[0.08em] text-muted-foreground uppercase\">\n {children}\n </div>\n );\n}\n\n// ---------------------------------------------------------------------------\n// Small controls\n// ---------------------------------------------------------------------------\n\n/** 24px clear target; hidden (not disabled) when there is nothing to clear. */\nexport function ClearButton({\n label,\n onClick,\n className,\n}: {\n label: string;\n onClick: () => void;\n className?: string;\n}) {\n return (\n <button\n type=\"button\"\n onClick={onClick}\n aria-label={label}\n title={label}\n className={cn(\n 'flex size-6 shrink-0 cursor-pointer items-center justify-center rounded-sm',\n 'text-muted-foreground/70 transition-colors hover:bg-accent hover:text-foreground',\n 'focus-visible:ring-1 focus-visible:ring-ring focus-visible:outline-none',\n className\n )}\n >\n <X className=\"size-3.5\" aria-hidden />\n </button>\n );\n}\n\n/** Inline text-styled button for secondary actions (\"Define\", \"More\"). */\nexport function QuietButton({\n children,\n onClick,\n className,\n ...rest\n}: React.ButtonHTMLAttributes<HTMLButtonElement>) {\n return (\n <button\n type=\"button\"\n onClick={onClick}\n className={cn(\n 'flex h-6 cursor-pointer items-center gap-1 rounded-sm px-1.5',\n 'text-[11px] font-medium text-muted-foreground transition-colors',\n 'hover:bg-accent hover:text-foreground',\n 'focus-visible:ring-1 focus-visible:ring-ring focus-visible:outline-none',\n 'disabled:pointer-events-none disabled:opacity-50',\n className\n )}\n {...rest}\n >\n {children}\n </button>\n );\n}\n\n/** Segmented radio group; the options are the whole control. */\nexport function Segmented<T extends string>({\n label,\n value,\n options,\n onChange,\n className,\n}: {\n label: string;\n value: T;\n options: readonly { value: T; label: string }[];\n onChange: (next: T) => void;\n className?: string;\n}) {\n return (\n <div\n role=\"radiogroup\"\n aria-label={label}\n className={cn(\n 'flex h-6 shrink-0 items-center rounded-sm border border-border/70 bg-muted/60 p-0.5',\n className\n )}\n >\n {options.map((option) => {\n const active = option.value === value;\n return (\n <button\n key={option.value}\n type=\"button\"\n role=\"radio\"\n aria-checked={active}\n onClick={() => onChange(option.value)}\n className={cn(\n 'flex h-full flex-1 cursor-pointer items-center justify-center rounded-[3px] px-1.5 text-[11px] transition-colors',\n 'focus-visible:ring-1 focus-visible:ring-ring focus-visible:outline-none',\n active\n ? 'bg-background text-foreground shadow-xs'\n : 'text-muted-foreground hover:text-foreground'\n )}\n >\n {option.label}\n </button>\n );\n })}\n </div>\n );\n}\n\n// ---------------------------------------------------------------------------\n// Draft inputs\n// ---------------------------------------------------------------------------\n\n/**\n * Local draft while focused, committed on blur or Enter, dropped on Escape.\n * The draft lives in a ref as well as state because the blur handler runs in\n * the same tick as an Escape's `setDraft(null)` and must see the cleared\n * value, not the closure's stale one.\n */\nexport function useDraft(value: string, commit: (draft: string) => void) {\n const [draft, setDraft] = useState<string | null>(null);\n const draftRef = useRef<string | null>(null);\n const write = useCallback((next: string | null) => {\n draftRef.current = next;\n setDraft(next);\n }, []);\n const onFocus = useCallback(() => write(value), [value, write]);\n const onChange = useCallback(\n (e: React.ChangeEvent<HTMLInputElement>) => write(e.target.value),\n [write]\n );\n const onBlur = useCallback(() => {\n const pending = draftRef.current;\n write(null);\n if (pending !== null && pending !== value) commit(pending);\n }, [commit, value, write]);\n const onKeyDown = useCallback(\n (e: React.KeyboardEvent<HTMLInputElement>) => {\n if (e.key === 'Enter') {\n e.preventDefault();\n e.currentTarget.blur();\n } else if (e.key === 'Escape') {\n write(null);\n e.currentTarget.blur();\n }\n },\n [write]\n );\n // For a control that commits without going through the input — a stepper\n // that keeps focus in the field. Without this the draft stays on the value\n // from before the step, hides it on screen, and commits it back on blur.\n // A no-op while nothing is focused, so it never opens a draft of its own.\n const syncDraft = useCallback(\n (next: string) => {\n if (draftRef.current !== null) write(next);\n },\n [write]\n );\n return {\n syncDraft,\n props: { value: draft ?? value, onFocus, onChange, onBlur, onKeyDown },\n };\n}\n\ntype DraftInputProps = Omit<\n React.ComponentProps<typeof Input>,\n 'value' | 'onChange' | 'onBlur' | 'onFocus' | 'onKeyDown'\n>;\n\n/** Text input; an empty commit clears the key. */\nexport function DraftTextInput({\n value,\n onCommit,\n className,\n ...rest\n}: DraftInputProps & {\n value: string | undefined;\n onCommit: (next: string | null) => void;\n}) {\n const commit = useCallback(\n (draft: string) => onCommit(draft.trim() === '' ? null : draft),\n [onCommit]\n );\n const { props: draft } = useDraft(value ?? '', commit);\n return (\n <Input\n type=\"text\"\n autoComplete=\"off\"\n spellCheck={false}\n className={cn(INPUT_CLASS, className)}\n {...draft}\n {...rest}\n />\n );\n}\n\n/**\n * Number field: a typed value, a unit suffix inside the box, and a pair of\n * steppers on the trailing edge.\n *\n * The native spinner is not enough here — it is hidden by the base input\n * reset, it appears only on hover in some engines, and it cannot step by the\n * half points a font size wants. These buttons step by the field's own\n * `step`, hold to repeat, and start from a sensible value when the key is\n * unset, so a blank size can be raised without typing a number first.\n */\n\n/** Steps a number field, repeating while held. */\nfunction useRepeat(step: (direction: 1 | -1) => void) {\n const timer = useRef<ReturnType<typeof setTimeout> | null>(null);\n const stop = useCallback(() => {\n if (timer.current) clearTimeout(timer.current);\n timer.current = null;\n }, []);\n const start = useCallback(\n (direction: 1 | -1) => {\n step(direction);\n const tick = (delay: number) => {\n timer.current = setTimeout(() => {\n step(direction);\n tick(60);\n }, delay);\n };\n tick(400);\n },\n [step]\n );\n useEffect(() => stop, [stop]);\n return { start, stop };\n}\n\nexport function DraftNumberInput({\n value,\n onCommit,\n unit,\n className,\n min,\n max,\n step = 1,\n ...rest\n}: DraftInputProps & {\n value: number | undefined;\n onCommit: (next: number | null) => void;\n unit?: string;\n}) {\n const commit = useCallback(\n (draft: string) => {\n const trimmed = draft.trim();\n if (trimmed === '') {\n onCommit(null);\n return;\n }\n const parsed = Number(trimmed);\n if (Number.isFinite(parsed)) onCommit(parsed);\n },\n [onCommit]\n );\n const { props: draft, syncDraft } = useDraft(\n value === undefined ? '' : String(value),\n commit\n );\n\n const numericStep = typeof step === 'number' ? step : Number(step) || 1;\n const lower = typeof min === 'number' ? min : Number(min);\n const upper = typeof max === 'number' ? max : Number(max);\n const bump = useCallback(\n (direction: 1 | -1) => {\n // An unset field steps from its floor, or from zero, rather than\n // refusing until a number is typed.\n const base = value ?? (Number.isFinite(lower) ? lower : 0);\n let next = base + direction * numericStep;\n if (Number.isFinite(lower)) next = Math.max(lower, next);\n if (Number.isFinite(upper)) next = Math.min(upper, next);\n // Float arithmetic on a 0.5 step leaves 12.299999999999999 behind.\n const stepped = Number(next.toFixed(4));\n // The steppers keep focus in the field, so a draft may be open on it.\n onCommit(stepped);\n syncDraft(String(stepped));\n },\n [lower, numericStep, onCommit, syncDraft, upper, value]\n );\n const repeat = useRepeat(bump);\n\n return (\n <div className=\"relative flex min-w-0 items-center\">\n <Input\n type=\"number\"\n inputMode=\"decimal\"\n min={min}\n max={max}\n step={step}\n className={cn(\n INPUT_CLASS,\n 'tabular-nums',\n // Room for the steppers, plus the unit when there is one. Kept as\n // tight as the chrome allows: the narrowest field this is used in\n // is 6.5rem, and every pixel here is a pixel the number loses.\n // The native spinner is suppressed — these buttons replace it.\n unit ? 'pr-11' : 'pr-5',\n '[appearance:textfield] [&::-webkit-inner-spin-button]:appearance-none [&::-webkit-outer-spin-button]:appearance-none',\n className\n )}\n {...draft}\n {...rest}\n />\n {unit && (\n <span\n aria-hidden\n className=\"pointer-events-none absolute right-5 text-[10px] text-muted-foreground\"\n >\n {unit}\n </span>\n )}\n <div className=\"absolute right-0 flex h-7 w-4 flex-col border-l border-input/70\">\n <StepButton\n label=\"Increase\"\n icon={ChevronUp}\n onStart={() => repeat.start(1)}\n onStop={repeat.stop}\n disabled={\n Number.isFinite(upper) && value !== undefined && value >= upper\n }\n />\n <StepButton\n label=\"Decrease\"\n icon={ChevronDown}\n onStart={() => repeat.start(-1)}\n onStop={repeat.stop}\n disabled={\n Number.isFinite(lower) && value !== undefined && value <= lower\n }\n />\n </div>\n </div>\n );\n}\n\nfunction StepButton({\n label,\n icon: Icon,\n onStart,\n onStop,\n disabled,\n}: {\n label: string;\n icon: React.ComponentType<{ className?: string }>;\n onStart: () => void;\n onStop: () => void;\n disabled?: boolean;\n}) {\n return (\n <button\n type=\"button\"\n aria-label={label}\n tabIndex={-1}\n disabled={disabled}\n onPointerDown={(event) => {\n // Keep focus (and any draft) in the field the buttons belong to.\n event.preventDefault();\n onStart();\n }}\n onPointerUp={onStop}\n onPointerLeave={onStop}\n onPointerCancel={onStop}\n className={cn(\n 'flex h-1/2 cursor-pointer items-center justify-center',\n 'text-muted-foreground transition-colors hover:bg-accent hover:text-foreground',\n 'disabled:pointer-events-none disabled:opacity-30'\n )}\n >\n <Icon className=\"size-2.5\" />\n </button>\n );\n}\n\n// ---------------------------------------------------------------------------\n// Contrast\n// ---------------------------------------------------------------------------\n\n/** WCAG thresholds as the three state colours the system already rations. */\nexport function contrastTone(ratio: number): string {\n if (ratio >= 4.5) return 'text-success';\n if (ratio >= 3) return 'text-warning';\n return 'text-destructive';\n}\n\nexport function ContrastBadge({\n label,\n ratio,\n}: {\n label: string;\n ratio: number | null;\n}) {\n return (\n <span className=\"inline-flex items-center gap-1 text-[11px] text-muted-foreground\">\n {label}\n <span\n className={cn(\n 'font-medium tabular-nums',\n ratio === null ? 'text-muted-foreground' : contrastTone(ratio)\n )}\n title={\n ratio === null\n ? 'Both colours must resolve to a hex'\n : ratio >= 4.5\n ? 'Meets WCAG AA for body text'\n : ratio >= 3\n ? 'Large text only'\n : 'Below WCAG minimums'\n }\n >\n {ratio === null ? '—' : `${ratio.toFixed(1)}:1`}\n </span>\n </span>\n );\n}\n","/**\n * The playground's own model of a quality finding.\n *\n * The server hands the same finding over in two shapes: `/validate` returns\n * rich `QualityDiagnostic` objects, while `/generate` flattens each one into a\n * `GenerationWarning` whose real fields live in `context` and whose severity is\n * squashed to `warning | info`. Normalising both here keeps that asymmetry out\n * of the panel, the store and the Monaco markers.\n */\n\nexport type QualitySeverity = 'error' | 'warning' | 'info';\n\nexport type QualityCertainty =\n | 'deterministic'\n | 'measured'\n | 'estimated'\n | 'rendered'\n | 'evaluative';\n\nexport interface QualityFixOp {\n op: 'add' | 'replace' | 'remove' | 'move' | 'copy' | 'test';\n path: string;\n value?: unknown;\n from?: string;\n}\n\nexport interface QualityEvidence {\n actual?: unknown;\n expected?: unknown;\n unit?: string;\n [key: string]: unknown;\n}\n\nexport interface QualityFinding {\n /** Stable within one analysis; used as a React key and for fix dedup. */\n id: string;\n code: string;\n message: string;\n /** RFC 6901 pointer into the authored document. */\n path: string;\n severity: QualitySeverity;\n ruleId?: string;\n category?: string;\n certainty?: QualityCertainty;\n blocking?: boolean;\n suggestion?: string;\n relatedPaths?: readonly string[];\n evidence?: QualityEvidence;\n fixes?: readonly QualityFixOp[];\n}\n\nexport interface GenerationWarningLike {\n component: string;\n message: string;\n severity?: 'warning' | 'info';\n context?: Record<string, unknown>;\n}\n\nconst SEVERITIES: readonly QualitySeverity[] = ['error', 'warning', 'info'];\n\nconst SEVERITY_RANK: Record<string, number> = {\n error: 0,\n warning: 1,\n info: 2,\n};\n\n/**\n * Anything that is not one of the three legal severities sorts last rather\n * than throwing, so a future server-side severity cannot crash the panel.\n */\nconst UNRANKED = 3;\n\nfunction rankOf(severity: unknown): number {\n return typeof severity === 'string' && severity in SEVERITY_RANK\n ? (SEVERITY_RANK[severity] as number)\n : UNRANKED;\n}\n\nconst CERTAINTIES: readonly QualityCertainty[] = [\n 'deterministic',\n 'measured',\n 'estimated',\n 'rendered',\n 'evaluative',\n];\n\nconst FIX_OPS: readonly QualityFixOp['op'][] = [\n 'add',\n 'replace',\n 'remove',\n 'move',\n 'copy',\n 'test',\n];\n\n/** Findings with no category are grouped under this bucket. */\nconst UNCATEGORIZED = 'other';\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return typeof value === 'object' && value !== null && !Array.isArray(value);\n}\n\n/**\n * The same test as `isRecord` but without the type predicate, for values that\n * are already typed and only need the runtime guard — narrowing them to\n * `Record<string, unknown>` would throw away the declared shape.\n */\nfunction looksLikeObject(value: unknown): boolean {\n return isRecord(value);\n}\n\nfunction asString(value: unknown): string | undefined {\n return typeof value === 'string' ? value : undefined;\n}\n\nfunction asSeverity(value: unknown): QualitySeverity | undefined {\n return typeof value === 'string' &&\n (SEVERITIES as readonly string[]).includes(value)\n ? (value as QualitySeverity)\n : undefined;\n}\n\nfunction asCertainty(value: unknown): QualityCertainty | undefined {\n return typeof value === 'string' &&\n (CERTAINTIES as readonly string[]).includes(value)\n ? (value as QualityCertainty)\n : undefined;\n}\n\nfunction asStringArray(value: unknown): readonly string[] | undefined {\n if (!Array.isArray(value)) return undefined;\n const strings = value.filter((entry): entry is string => {\n return typeof entry === 'string';\n });\n return strings.length > 0 ? strings : undefined;\n}\n\nfunction asEvidence(value: unknown): QualityEvidence | undefined {\n return isRecord(value) ? (value as QualityEvidence) : undefined;\n}\n\n/**\n * A malformed patch operation is worse than none: the fix button would apply\n * garbage to the author's document, so anything without a legal `op` and a\n * string `path` is discarded rather than passed along.\n */\nfunction asFixes(value: unknown): readonly QualityFixOp[] | undefined {\n if (!Array.isArray(value)) return undefined;\n const fixes: QualityFixOp[] = [];\n for (const entry of value) {\n if (!isRecord(entry)) continue;\n const op = entry['op'];\n const path = entry['path'];\n if (typeof op !== 'string' || typeof path !== 'string') continue;\n if (!(FIX_OPS as readonly string[]).includes(op)) continue;\n const fix: QualityFixOp = { op: op as QualityFixOp['op'], path };\n if ('value' in entry) fix.value = entry['value'];\n const from = asString(entry['from']);\n if (from !== undefined) fix.from = from;\n fixes.push(fix);\n }\n return fixes.length > 0 ? fixes : undefined;\n}\n\n/**\n * The generate path double-encodes the code: the message arrives as\n * \"[W_QUALITY_FONT_SIZE_MIN] Body text is 5pt\" while the code is also in\n * `context.code`. Stripping the prefix keeps the panel from showing the code\n * twice, but only when the two agree — an unrelated bracketed prefix is part\n * of the rule's own wording and must survive.\n */\nfunction stripCodePrefix(message: string, code: string | undefined): string {\n if (!code) return message;\n const prefix = `[${code}] `;\n return message.startsWith(prefix) ? message.slice(prefix.length) : message;\n}\n\n/**\n * Ids must survive a re-render of the same analysis, so they are derived from\n * the finding rather than from a counter or a random value. The index breaks\n * ties between findings that share a code and a path.\n */\nfunction makeId(code: string, path: string, index: number): string {\n return `${code || 'quality'}:${path}:${index}`;\n}\n\nfunction buildFinding(\n fields: {\n code: unknown;\n message: unknown;\n path: unknown;\n severity: QualitySeverity;\n ruleId: unknown;\n category: unknown;\n certainty: unknown;\n blocking: unknown;\n suggestion: unknown;\n relatedPaths: unknown;\n evidence: unknown;\n fixes: unknown;\n },\n index: number\n): QualityFinding | undefined {\n const code = asString(fields.code) ?? '';\n const rawMessage = asString(fields.message) ?? '';\n // Nothing to render and nothing to look up: the entry is not a finding.\n if (!code && !rawMessage) return undefined;\n\n const path = asString(fields.path) ?? '';\n const finding: QualityFinding = {\n id: makeId(code, path, index),\n code,\n // A finding row with no text at all is unreadable, so a code-only entry\n // shows its code rather than rendering blank.\n message: stripCodePrefix(rawMessage, code) || code,\n path,\n severity: fields.severity,\n };\n\n const ruleId = asString(fields.ruleId);\n if (ruleId !== undefined) finding.ruleId = ruleId;\n\n const category = asString(fields.category);\n if (category !== undefined) finding.category = category;\n\n const certainty = asCertainty(fields.certainty);\n if (certainty !== undefined) finding.certainty = certainty;\n\n if (typeof fields.blocking === 'boolean') finding.blocking = fields.blocking;\n\n const suggestion = asString(fields.suggestion);\n if (suggestion !== undefined) finding.suggestion = suggestion;\n\n const relatedPaths = asStringArray(fields.relatedPaths);\n if (relatedPaths !== undefined) finding.relatedPaths = relatedPaths;\n\n const evidence = asEvidence(fields.evidence);\n if (evidence !== undefined) finding.evidence = evidence;\n\n const fixes = asFixes(fields.fixes);\n if (fixes !== undefined) finding.fixes = fixes;\n\n return finding;\n}\n\n/** Split a generate response's warnings into quality findings and everything else. */\nexport function splitQualityWarnings(\n warnings: readonly GenerationWarningLike[] | null | undefined\n): { findings: QualityFinding[]; others: GenerationWarningLike[] } {\n const findings: QualityFinding[] = [];\n const others: GenerationWarningLike[] = [];\n if (!Array.isArray(warnings)) return { findings, others };\n\n for (const warning of warnings) {\n // The array is typed, but it comes off the wire — a null slot is possible.\n if (!looksLikeObject(warning)) continue;\n if (warning.component !== 'quality') {\n others.push(warning);\n continue;\n }\n\n const context = isRecord(warning.context) ? warning.context : {};\n // `context.originalSeverity` is the only place an 'error' survives; the\n // top-level severity was already coerced to warning | info by the server.\n const severity =\n asSeverity(context['originalSeverity']) ??\n asSeverity(warning.severity) ??\n 'warning';\n\n const finding = buildFinding(\n {\n code: context['code'],\n message: warning.message,\n path: context['path'],\n severity,\n ruleId: context['ruleId'],\n category: context['category'],\n certainty: context['certainty'],\n blocking: context['blocking'],\n suggestion: context['suggestion'],\n relatedPaths: context['relatedPaths'],\n evidence: context['evidence'],\n fixes: context['fixes'],\n },\n findings.length\n );\n if (finding) findings.push(finding);\n }\n\n return { findings, others };\n}\n\n/** Findings from a /validate response's qualityAnalysis object. */\nexport function findingsFromAnalysis(analysis: unknown): QualityFinding[] {\n if (!isRecord(analysis)) return [];\n const diagnostics = analysis['diagnostics'];\n if (!Array.isArray(diagnostics)) return [];\n\n const findings: QualityFinding[] = [];\n for (const diagnostic of diagnostics) {\n if (!isRecord(diagnostic)) continue;\n const finding = buildFinding(\n {\n code: diagnostic['code'],\n message: diagnostic['message'],\n path: diagnostic['path'],\n severity: asSeverity(diagnostic['severity']) ?? 'warning',\n ruleId: diagnostic['ruleId'],\n category: diagnostic['category'],\n certainty: diagnostic['certainty'],\n blocking: diagnostic['blocking'],\n suggestion: diagnostic['suggestion'],\n relatedPaths: diagnostic['relatedPaths'],\n evidence: diagnostic['evidence'],\n fixes: diagnostic['fixes'],\n },\n findings.length\n );\n if (finding) findings.push(finding);\n }\n return findings;\n}\n\nexport function countBySeverity(findings: readonly QualityFinding[]): {\n error: number;\n warning: number;\n info: number;\n} {\n const counts = { error: 0, warning: 0, info: 0 };\n if (!Array.isArray(findings)) return counts;\n for (const finding of findings) {\n const severity = asSeverity(finding?.severity);\n if (severity) counts[severity] += 1;\n }\n return counts;\n}\n\n/** error < warning < info, then by path, then by code. For Array.sort. */\nexport function compareFindings(a: QualityFinding, b: QualityFinding): number {\n const rankA = rankOf(a?.severity);\n const rankB = rankOf(b?.severity);\n if (rankA !== rankB) return rankA - rankB;\n const pathA = a?.path ?? '';\n const pathB = b?.path ?? '';\n if (pathA !== pathB) return pathA < pathB ? -1 : 1;\n const codeA = a?.code ?? '';\n const codeB = b?.code ?? '';\n if (codeA === codeB) return 0;\n return codeA < codeB ? -1 : 1;\n}\n\n/** Groups sorted by worst severity within the group, then category name. */\nexport function groupByCategory(\n findings: readonly QualityFinding[]\n): Array<{ category: string; findings: QualityFinding[] }> {\n const groups = new Map<string, QualityFinding[]>();\n if (!Array.isArray(findings)) return [];\n\n for (const finding of findings) {\n if (!finding) continue;\n const category = finding.category || UNCATEGORIZED;\n const bucket = groups.get(category);\n if (bucket) bucket.push(finding);\n else groups.set(category, [finding]);\n }\n\n const worstRank = (group: readonly QualityFinding[]): number => {\n return group.reduce((worst, finding) => {\n const rank = rankOf(finding.severity);\n return rank < worst ? rank : worst;\n }, UNRANKED);\n };\n\n return Array.from(groups.entries())\n .map(([category, bucket]) => ({\n category,\n findings: [...bucket].sort(compareFindings),\n }))\n .sort((a, b) => {\n const rankDelta = worstRank(a.findings) - worstRank(b.findings);\n if (rankDelta !== 0) return rankDelta;\n if (a.category === b.category) return 0;\n return a.category < b.category ? -1 : 1;\n });\n}\n\nexport function filterByMinSeverity(\n findings: readonly QualityFinding[],\n min: QualitySeverity\n): QualityFinding[] {\n if (!Array.isArray(findings)) return [];\n // An unknown threshold must not silently hide findings, so it lets all pass.\n const limit = asSeverity(min) ? rankOf(min) : UNRANKED;\n return findings.filter((finding) => rankOf(finding?.severity) <= limit);\n}\n\n/** Human label for a certainty, e.g. 'measured' -> 'Measured'. */\nexport function certaintyLabel(\n c: QualityCertainty | undefined\n): string | undefined {\n const certainty = asCertainty(c);\n if (!certainty) return undefined;\n return certainty.charAt(0).toUpperCase() + certainty.slice(1);\n}\n","/**\n * The run policy an author writes by hand.\n *\n * The profile and gate controls cover the two settings most runs need; this\n * covers the rest of the contract — per-rule severity, enable/disable and\n * parameters, suppressions, and the diagnostic budget — which until now was\n * reachable only through the CLI's `--quality-policy` file.\n *\n * `gate` is deliberately NOT accepted here. It has a control of its own, and\n * two writable sources for one field means the dropdown either lies or silently\n * loses, so naming it in the JSON is reported as a mistake instead.\n */\n\nimport { rulesForFormat } from './quality-rules';\n\nexport interface QualityRuleConfiguration {\n enabled?: boolean;\n severity?: 'error' | 'warning' | 'info';\n parameters?: Record<string, unknown>;\n}\n\nexport interface QualitySuppression {\n ruleId?: string;\n code?: string;\n path?: string;\n pathMatch?: 'exact' | 'subtree';\n reason: string;\n}\n\nexport interface EditableQualityPolicy {\n rules?: Record<string, QualityRuleConfiguration>;\n suppressions?: QualitySuppression[];\n maxDiagnostics?: number;\n onRuleError?: 'continue' | 'throw';\n}\n\nexport type ParsedQualityPolicy =\n | { ok: true; policy: EditableQualityPolicy | undefined }\n | { ok: false; error: string };\n\n/** What a fresh editor opens on: valid, inert, and a shape to copy. */\nexport const EMPTY_POLICY_TEXT = `{\n \"rules\": {},\n \"suppressions\": []\n}\n`;\n\nconst SEVERITIES = new Set(['error', 'warning', 'info']);\n\nfunction isPlainObject(value: unknown): value is Record<string, unknown> {\n return typeof value === 'object' && value !== null && !Array.isArray(value);\n}\n\n/**\n * Parse and check a hand-written policy.\n *\n * Errors are values and name the offending key, because this text is typed by\n * hand and the server's own rejection would arrive as a 400 the panel reports\n * far from the field that caused it.\n */\nexport function parseQualityPolicy(\n text: string | undefined\n): ParsedQualityPolicy {\n const trimmed = (text ?? '').trim();\n if (!trimmed) return { ok: true, policy: undefined };\n\n let parsed: unknown;\n try {\n parsed = JSON.parse(trimmed);\n } catch (error) {\n return { ok: false, error: `Not valid JSON: ${(error as Error).message}` };\n }\n if (!isPlainObject(parsed)) {\n return { ok: false, error: 'A policy must be a JSON object.' };\n }\n\n if ('gate' in parsed) {\n return {\n ok: false,\n error: 'Remove \"gate\" — it is set by the Gate control above.',\n };\n }\n if ('profile' in parsed) {\n return {\n ok: false,\n error: 'Remove \"profile\" — it is set by the Profile control above.',\n };\n }\n\n // Every key below is read by name, so anything else would be dropped in\n // silence — `{\"maxDiagnostic\": 0}` parsed as a valid empty policy and the\n // budget the author asked for never applied. Say so instead.\n const EDITABLE_KEYS = new Set([\n 'rules',\n 'suppressions',\n 'maxDiagnostics',\n 'onRuleError',\n ]);\n const unknownKey = Object.keys(parsed).find((key) => !EDITABLE_KEYS.has(key));\n if (unknownKey !== undefined) {\n return {\n ok: false,\n error: `Unknown key \"${unknownKey}\". A policy takes: ${[...EDITABLE_KEYS].join(', ')}.`,\n };\n }\n\n const known = new Set(rulesForFormat().map((rule) => rule.id));\n const policy: EditableQualityPolicy = {};\n\n if (parsed.rules !== undefined) {\n if (!isPlainObject(parsed.rules)) {\n return {\n ok: false,\n error: '\"rules\" must be an object keyed by rule id.',\n };\n }\n for (const [ruleId, configuration] of Object.entries(parsed.rules)) {\n if (!known.has(ruleId)) {\n return {\n ok: false,\n error: `Unknown rule \"${ruleId}\". This format ships: ${[...known].join(', ')}.`,\n };\n }\n if (!isPlainObject(configuration)) {\n return {\n ok: false,\n error: `Rule \"${ruleId}\" must be an object, e.g. { \"severity\": \"warning\" }.`,\n };\n }\n if (\n configuration.severity !== undefined &&\n !SEVERITIES.has(configuration.severity as string)\n ) {\n return {\n ok: false,\n error: `Rule \"${ruleId}\" has an invalid severity. Use \"error\", \"warning\" or \"info\".`,\n };\n }\n if (\n configuration.enabled !== undefined &&\n typeof configuration.enabled !== 'boolean'\n ) {\n return {\n ok: false,\n error: `Rule \"${ruleId}\": \"enabled\" must be true or false.`,\n };\n }\n if (\n configuration.parameters !== undefined &&\n !isPlainObject(configuration.parameters)\n ) {\n return {\n ok: false,\n error: `Rule \"${ruleId}\": \"parameters\" must be an object.`,\n };\n }\n }\n policy.rules = parsed.rules as Record<string, QualityRuleConfiguration>;\n }\n\n if (parsed.suppressions !== undefined) {\n if (!Array.isArray(parsed.suppressions)) {\n return { ok: false, error: '\"suppressions\" must be an array.' };\n }\n for (const [index, entry] of parsed.suppressions.entries()) {\n if (!isPlainObject(entry)) {\n return { ok: false, error: `Suppression ${index} must be an object.` };\n }\n // The contract makes `reason` mandatory on purpose: a muted finding that\n // nobody has to justify is how a rule quietly stops being enforced.\n if (typeof entry.reason !== 'string' || !entry.reason.trim()) {\n return {\n ok: false,\n error: `Suppression ${index} needs a \"reason\" saying why the finding is acceptable.`,\n };\n }\n if (\n entry.pathMatch !== undefined &&\n entry.pathMatch !== 'exact' &&\n entry.pathMatch !== 'subtree'\n ) {\n return {\n ok: false,\n error: `Suppression ${index}: \"pathMatch\" must be \"exact\" or \"subtree\".`,\n };\n }\n }\n policy.suppressions = parsed.suppressions as QualitySuppression[];\n }\n\n if (parsed.maxDiagnostics !== undefined) {\n if (\n !Number.isInteger(parsed.maxDiagnostics) ||\n (parsed.maxDiagnostics as number) < 0\n ) {\n return {\n ok: false,\n error: '\"maxDiagnostics\" must be a whole number, zero or more.',\n };\n }\n policy.maxDiagnostics = parsed.maxDiagnostics as number;\n }\n\n if (parsed.onRuleError !== undefined) {\n if (parsed.onRuleError !== 'continue' && parsed.onRuleError !== 'throw') {\n return {\n ok: false,\n error: '\"onRuleError\" must be \"continue\" or \"throw\".',\n };\n }\n policy.onRuleError = parsed.onRuleError;\n }\n\n const empty =\n (policy.rules === undefined || Object.keys(policy.rules).length === 0) &&\n (policy.suppressions === undefined || policy.suppressions.length === 0) &&\n policy.maxDiagnostics === undefined &&\n policy.onRuleError === undefined;\n\n return { ok: true, policy: empty ? undefined : policy };\n}\n","/**\n * The shipped quality profiles, and the one place a stored profile id is\n * turned into a request.\n *\n * Settings persist to a single localStorage key shared by both playgrounds\n * (`window.__JTO_FORMAT__` picks the format; the origin is the same), so a\n * profile chosen on the DOCX playground is still in storage when the PPTX one\n * opens. Sending it would be worse than useless: the client stamps\n * `formats: [FORMAT]` itself, so the server's compatibility check passes, the\n * id matches no registered profile, and the analysis silently runs on defaults\n * while the panel and the response both report the profile as active.\n * Resolution therefore happens here, at the point of use, rather than in a\n * component that only guards while it is mounted.\n */\n\nimport { FORMAT, type FormatName } from './env';\nimport { parseQualityPolicy } from './quality-policy';\n\nexport interface QualityProfileChoice {\n id: string;\n label: string;\n description: string;\n}\n\nexport const QUALITY_PROFILES: Record<\n FormatName,\n readonly QualityProfileChoice[]\n> = {\n docx: [\n {\n id: 'client-report',\n label: 'Client report',\n description:\n 'Running head with page numbers after the cover; takeaway and source wherever a block declares them; every size on the theme scale, at most eight in play; no page rendered empty or half blank under the running head; at least one chart or table',\n },\n {\n id: 'executive-report',\n label: 'Executive report',\n description: 'Short decision document with strict outline continuity',\n },\n {\n id: 'technical-report',\n label: 'Technical report',\n description:\n 'Running head with page numbers after the cover; a source wherever a block declares one; no heading skipped; every size on the theme scale, at most nine in play; no page rendered empty or half blank; at least one chart or table',\n },\n {\n id: 'legal-appendix',\n label: 'Legal appendix',\n description: 'Dense appendix: preserve integrity without editorial taste',\n },\n {\n id: 'general',\n label: 'General',\n description:\n 'The format default: integrity and information-design rules, nothing required by structure',\n },\n ],\n pptx: [\n {\n id: 'executive-presentation',\n label: 'Executive presentation',\n description: 'Decision deck optimized for scan speed and projection',\n },\n {\n id: 'technical-presentation',\n label: 'Technical presentation',\n description: 'Portable professional presentation defaults',\n },\n ],\n};\n\n/** The profiles offerable for the format this playground is serving. */\nexport function profilesForFormat(): readonly QualityProfileChoice[] {\n return QUALITY_PROFILES[FORMAT];\n}\n\n/** The profile id stored for this format, if it still names a shipped one. */\nexport function storedProfileId(\n ids: Partial<Record<FormatName, string>> | undefined\n): string | undefined {\n const id = ids?.[FORMAT];\n return isProfileForFormat(id) ? id : undefined;\n}\n\n/** True when `id` names a profile the current format actually ships. */\nexport function isProfileForFormat(id: string | undefined): id is string {\n return id !== undefined && QUALITY_PROFILES[FORMAT].some((p) => p.id === id);\n}\n\nexport interface QualityRequestOptions {\n profile?: { id: string; formats: string[] };\n policy?: Record<string, unknown>;\n}\n\n/**\n * Build the `options.quality` payload for a generate or validate request.\n *\n * Returns undefined when neither a profile nor a gate was asked for, so the\n * request omits the key entirely and the server keeps its own defaults. A\n * profile id belonging to the other format is dropped rather than sent.\n */\nexport function buildQualityOptions(\n profileId: string | undefined,\n gate: string | undefined,\n policyText?: string\n): QualityRequestOptions | undefined {\n const options: QualityRequestOptions = {};\n if (isProfileForFormat(profileId)) {\n options.profile = { id: profileId, formats: [FORMAT] };\n }\n // A policy still being typed is not sent at all: the run keeps working under\n // the last good one rather than failing on every unbalanced brace.\n const parsed = parseQualityPolicy(policyText);\n const authored = parsed.ok ? parsed.policy : undefined;\n const hasGate = gate === 'error' || gate === 'warning' || gate === 'info';\n if (authored || hasGate) {\n options.policy = {\n ...(authored ?? {}),\n // The Gate control owns this field; `parseQualityPolicy` rejects a policy\n // that also names it, so there is nothing here to overwrite.\n ...(hasGate && { gate }),\n };\n }\n return options.profile || options.policy ? options : undefined;\n}\n","/**\n * Ordering tickets for quality analyses.\n *\n * Three independent producers write the same store slice: the editor's\n * debounced validate pass, the preview's immediate re-analysis after a fix,\n * and the build's own findings. Each aborts only its own in-flight request, so\n * per-request cancellation cannot order them against each other — a ~10s build\n * started before an edit still resolves after the validate pass that overtook\n * it, and would otherwise reinstate findings for text the editor no longer\n * holds.\n *\n * Every producer takes a ticket before it starts and drops its result if a\n * higher one has already landed. Tickets are process-wide because the writers\n * are separate hook instances in one bundle; a per-instance counter would\n * compare numbers that mean nothing to each other.\n */\n\nimport type { QualityState } from '../store/output-store';\n\nlet counter = 0;\n\n/** Monotonic, allocated at the moment work starts, not when it finishes. */\nexport function nextQualityTicket(): number {\n counter += 1;\n return counter;\n}\n\n/** True when a newer analysis has already been committed. */\nexport function isStaleQualityTicket(\n current: QualityState | null | undefined,\n ticket: number\n): boolean {\n return current != null && current.seq > ticket;\n}\n","import { useCallback, useContext, useEffect, useRef } from 'react';\nimport { API_ENDPOINTS } from '../config/api';\nimport { FORMAT } from '../lib/env';\nimport { countBySeverity, findingsFromAnalysis } from '../lib/quality-findings';\nimport { buildQualityOptions, storedProfileId } from '../lib/quality-profiles';\nimport {\n isStaleQualityTicket,\n nextQualityTicket,\n} from '../lib/quality-sequence';\nimport type { QualityState } from '../store/output-store';\nimport {\n OutputStoreContext,\n useOutputStore,\n} from '../store/output-store-provider';\nimport { useSettingsStore } from '../store/settings-store-provider';\nimport { ThemesStoreContext } from '../store/themes-store-provider';\nimport { validThemesByName } from '../store/themes-store';\nimport {\n expandForServer,\n type ExpandForServerResult,\n} from '../lib/plugins/expand-for-server';\nimport type { QualityFinding } from '../lib/quality-findings';\nimport { isJsonMediaType } from './usePresentationGenerator';\n\n/**\n * Point findings computed against the expanded document back at the text the\n * author sees. A finding inside a plugin's output has no node to fix in the\n * editor — its patch would land on the wrong thing — so it keeps only its\n * path to the plugin node and says which component it came from.\n */\nfunction remapFindings(\n findings: QualityFinding[],\n remap: ExpandForServerResult['remap']\n): QualityFinding[] {\n return findings.map((finding) => {\n const target = remap(finding.path);\n const relatedPaths = finding.relatedPaths?.map((p) => remap(p).path);\n if (!target.insidePlugin) {\n if (target.path === finding.path && !relatedPaths) return finding;\n return {\n ...finding,\n path: target.path,\n ...(relatedPaths ? { relatedPaths } : {}),\n };\n }\n return {\n ...finding,\n path: target.path,\n message: `${finding.message} (inside the output of a browser plugin — fix it in the plugin)`,\n ...(relatedPaths ? { relatedPaths } : {}),\n fixes: undefined,\n };\n });\n}\n\n/**\n * Long enough that a burst of typing costs one request rather than one per\n * keystroke, short enough that a pause reads as \"the panel just updated\".\n *\n * The upper bound is not taste: `/api/*` rate-limits on `METHOD:path`, and in\n * production that budget is 100 requests per 15 minutes (server config\n * `RATE_LIMIT_MAX`). One analysis per typing pause has to stay well under\n * ~7 requests/minute sustained or the panel starts answering 429 to the author\n * who is typing the most.\n */\nconst ANALYSIS_DEBOUNCE_MS = 1500;\n\ninterface ValidateResponseBody {\n success?: boolean;\n data?: { valid?: boolean; qualityAnalysis?: unknown };\n error?: string;\n /** `QUALITY_GATE_FAILED` distinguishes a real rejection from a 429 or a 500. */\n code?: string;\n /** Present on a gate failure: the analysis that tripped it. */\n quality?: unknown;\n}\n\n/** The JSON body, or undefined when the response is not JSON at all. */\nasync function readJsonBody(\n response: Response\n): Promise<ValidateResponseBody | undefined> {\n // A proxy that never reached the API answers with its own HTML error page,\n // and parsing that unguarded is what turns a dead backend into\n // `Unexpected token '<'` instead of the status it actually was.\n if (!isJsonMediaType(response.headers.get('content-type'))) return undefined;\n try {\n return (await response.json()) as ValidateResponseBody;\n } catch {\n return undefined;\n }\n}\n\n/**\n * The analysis-level fields, read one at a time.\n *\n * Spreading the server's object into the store would let an unexpected type\n * through to the panel, where a `blocked` that is a string reads as truthy and\n * a `profileId` that is an object renders as `[object Object]`.\n */\nfunction readAnalysisFlags(\n analysis: unknown\n): Pick<QualityState, 'profileId' | 'blocked' | 'truncated'> {\n if (!analysis || typeof analysis !== 'object') return {};\n const source = analysis as Record<string, unknown>;\n return {\n ...(typeof source.profileId === 'string'\n ? { profileId: source.profileId }\n : {}),\n ...(typeof source.blocked === 'boolean' ? { blocked: source.blocked } : {}),\n ...(typeof source.truncated === 'boolean'\n ? { truncated: source.truncated }\n : {}),\n };\n}\n\n/** The API's own message when there is one; the status is all that is left. */\nfunction describeFailure(\n response: Response,\n body: ValidateResponseBody | undefined\n): string {\n if (body?.error) return body.error;\n return `Quality analysis failed (${response.status}${\n response.statusText ? ` ${response.statusText}` : ''\n }).`;\n}\n\n/**\n * Re-run the quality analysis as the document changes, without rendering it.\n *\n * `/validate` returns the rich diagnostics `/generate` flattens into warnings,\n * so this is what keeps the panel current between builds — the author sees the\n * findings move while editing rather than only after asking for bytes.\n */\nexport function useQualityAnalysis(): {\n analyze: (\n documentName: string,\n documentText: string,\n options?: { immediate?: boolean }\n ) => void;\n cancel: () => void;\n} {\n const setOutput = useOutputStore((state) => state.setOutput);\n const outputStore = useContext(OutputStoreContext)!;\n // Optional: the JSON editor page has no themes store, and a plugin then\n // renders against the built-in theme its document names.\n const themesStore = useContext(ThemesStoreContext);\n // Read here rather than inside the request so a settings change re-creates\n // `analyze`, and a caller holding the old one cannot keep analysing under a\n // profile the author has already switched away from.\n const profileIds = useSettingsStore((state) => state.qualityProfileIds);\n const profileId = storedProfileId(profileIds);\n const gate = useSettingsStore((state) => state.qualityGate);\n const policyText = useSettingsStore(\n (state) => state.qualityPolicies?.[FORMAT]\n );\n\n const timerRef = useRef<ReturnType<typeof setTimeout> | null>(null);\n const abortRef = useRef<AbortController | null>(null);\n\n const cancel = useCallback(() => {\n if (timerRef.current !== null) {\n clearTimeout(timerRef.current);\n timerRef.current = null;\n }\n abortRef.current?.abort();\n abortRef.current = null;\n }, []);\n\n // An analysis that outlives its component would resolve into a store nobody\n // is reading, and worse, overwrite findings a later mount had already put\n // there.\n useEffect(() => cancel, [cancel]);\n\n /**\n * Commit a result unless a newer analysis has already landed.\n *\n * Aborting only reaches this hook instance's own request, and there are\n * several instances plus the build writing the same slice, so the ticket is\n * what actually orders them.\n */\n const commit = useCallback(\n (ticket: number, next: QualityState) => {\n if (isStaleQualityTicket(outputStore.getState().quality, ticket)) return;\n setOutput({ quality: next });\n },\n [outputStore, setOutput]\n );\n\n const run = useCallback(\n async (documentName: string, documentText: string) => {\n let jsonDefinition: unknown;\n try {\n jsonDefinition = JSON.parse(documentText);\n } catch {\n // Half-typed JSON is the normal state of an editor, not something to\n // report. Keep the previous findings on screen: blanking the panel on\n // every unbalanced brace is worse than showing findings one keystroke\n // out of date.\n return;\n }\n\n abortRef.current?.abort();\n const controller = new AbortController();\n abortRef.current = controller;\n const ticket = nextQualityTicket();\n\n const quality = buildQualityOptions(profileId, gate, policyText);\n\n // Browser plugins are expanded first, as before a build: the validator\n // knows only standard components. A plugin that fails to expand is an\n // analysis error, reported the way a dead backend is — nothing about\n // the document itself is known yet. Findings come back against the\n // expanded tree and are pointed back at the authored one below.\n let remap: ExpandForServerResult['remap'] = (pointer) => ({\n path: pointer,\n insidePlugin: false,\n });\n try {\n const expansion = await expandForServer(documentText, {\n customThemes: themesStore\n ? validThemesByName(themesStore.getState().customThemes)\n : {},\n signal: controller.signal,\n });\n if (controller.signal.aborted) return;\n if (expansion.expanded) {\n jsonDefinition = JSON.parse(expansion.text);\n remap = expansion.remap;\n }\n } catch (error) {\n if (controller.signal.aborted) return;\n const stored = outputStore.getState().quality;\n const previous =\n stored && stored.documentName === documentName ? stored : undefined;\n commit(ticket, {\n ...(previous ?? {\n findings: [],\n counts: { error: 0, warning: 0, info: 0 },\n source: 'validate' as const,\n }),\n documentName,\n seq: ticket,\n analyzedAt: Date.now(),\n blocked: previous?.blocked ?? false,\n gateError: undefined,\n analysisError: error instanceof Error ? error.message : String(error),\n });\n return;\n }\n\n try {\n const response = await fetch(API_ENDPOINTS.validate, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({\n jsonDefinition,\n options: quality ? { quality } : {},\n }),\n signal: controller.signal,\n });\n const body = await readJsonBody(response);\n\n // A superseded request has already been replaced by a newer one, whose\n // answer is the current one; writing this stale result would undo it.\n if (controller.signal.aborted) return;\n\n if (!response.ok) {\n const message = describeFailure(response, body);\n if (body?.code === 'QUALITY_GATE_FAILED') {\n // A real rejection, and it carries the diagnostics that caused it.\n const findings = remapFindings(\n findingsFromAnalysis(body.quality),\n remap\n );\n commit(ticket, {\n findings,\n counts: countBySeverity(findings),\n documentName,\n seq: ticket,\n blocked: true,\n source: 'validate',\n analyzedAt: Date.now(),\n gateError: message,\n });\n return;\n }\n // Everything else — 429 from the rate limiter, 413, a 500, a dead\n // proxy — says nothing about the document. Reporting it as a gate\n // failure would tell an author with no gate configured that their\n // document was blocked, and discarding the findings alongside it\n // would take away the only thing on screen that was still true.\n // Keeping the findings is only right while they still describe this\n // document. Across a tab switch they describe the previous one, and\n // every finding is a JSON Pointer into a file that is no longer on\n // screen — \"Apply fix\" would patch the wrong document. A stale\n // `blocked` is worse still: a gate rejection from before the switch\n // would keep the new document blocked on a transport error.\n const stored = outputStore.getState().quality;\n const previous =\n stored && stored.documentName === documentName ? stored : undefined;\n commit(ticket, {\n ...(previous ?? {\n findings: [],\n counts: { error: 0, warning: 0, info: 0 },\n source: 'validate' as const,\n }),\n documentName,\n seq: ticket,\n analyzedAt: Date.now(),\n blocked: previous?.blocked ?? false,\n gateError: undefined,\n analysisError: message,\n });\n return;\n }\n\n // `success` is false for a schema-invalid document and for a blocked\n // one, neither of which is a failed request — the analysis, when there\n // is one, is still in `data`.\n const analysis = body?.data?.qualityAnalysis;\n const findings = remapFindings(findingsFromAnalysis(analysis), remap);\n commit(ticket, {\n findings,\n counts: countBySeverity(findings),\n ...readAnalysisFlags(analysis),\n documentName,\n seq: ticket,\n source: 'validate',\n analyzedAt: Date.now(),\n });\n } catch {\n // An abort lands here, and so does a dropped connection. Neither says\n // anything about the document, so the last analysis stands.\n } finally {\n if (abortRef.current === controller) abortRef.current = null;\n }\n },\n [commit, gate, outputStore, policyText, profileId, themesStore]\n );\n\n const analyze = useCallback(\n (\n documentName: string,\n documentText: string,\n options?: { immediate?: boolean }\n ) => {\n if (timerRef.current !== null) {\n clearTimeout(timerRef.current);\n timerRef.current = null;\n }\n if (options?.immediate) {\n void run(documentName, documentText);\n return;\n }\n timerRef.current = setTimeout(() => {\n timerRef.current = null;\n void run(documentName, documentText);\n }, ANALYSIS_DEBOUNCE_MS);\n },\n [run]\n );\n\n return { analyze, cancel };\n}\n"],"names":["parseTheme","text","value","error","serializeTheme","theme","setAt","path","mutateDocumentAtPath","deleteAt","parentPath","key","parent","getAt","next","current","COLOR_HINTS","COLOR_GROUPS","humanize","c","schemaColors","schema","colors","_a","colorTokens","format","keys","required","DocxThemeConfigSchema","PptxThemeConfigSchema","ordered","seen","group","groupKeys","HEX_COLOR","isHexColor","normalizeHex","trimmed","short","full","definedColorKeys","resolveColor","depth","bare","contrastRatio","a","b","la","luminance","lb","light","dark","hex","match","channels","i","DOCX_FONT_ROLES","PPTX_FONT_ROLES","FONT_ROLE_HINTS","PAGE_SIZES","MARGIN_KEYS","TWIPS_PER_UNIT","twipsToUnit","twips","unit","unitToTwips","ALIGNMENTS","DOCX_STYLE_FIELDS","PPTX_STYLE_FIELDS","DOCX_STYLE_NAMES","schemaStyleNames","styles","branch","styleNames","declared","n","styleFields","customStyleNames","known","name","STYLE_LABELS","styleLabel","FORM_KEYS","formKeys","advancedKeys","alsoHandled","handled","summarize","ensureContainers","want","ThemeEditorContext","createContext","ThemeEditorProvider","useThemeEditor","ctx","useContext","EMPTY_CATALOG","catalogCache","catalogRequest","loadCatalog","API_ENDPOINTS","r","data","parsed","useFontCatalog","catalog","setCatalog","useState","useEffect","live","INPUT_CLASS","SELECT_TRIGGER_CLASS","HINT_CLASS","FIELD_LABEL_CLASS","EditorSection","title","hint","actions","defaultOpen","forceOpen","children","toggled","setOpen","open","jsxs","o","cn","jsx","ChevronRight","GroupLabel","ClearButton","label","onClick","className","X","QuietButton","rest","Segmented","options","onChange","option","active","useDraft","commit","draft","setDraft","draftRef","useRef","write","useCallback","onFocus","e","onBlur","pending","onKeyDown","DraftTextInput","onCommit","Input","useRepeat","step","timer","stop","start","direction","tick","delay","DraftNumberInput","min","max","syncDraft","numericStep","lower","upper","bump","stepped","repeat","StepButton","ChevronUp","ChevronDown","Icon","onStart","onStop","disabled","event","contrastTone","ratio","ContrastBadge","SEVERITIES","SEVERITY_RANK","UNRANKED","rankOf","severity","CERTAINTIES","FIX_OPS","UNCATEGORIZED","isRecord","looksLikeObject","asString","asSeverity","asCertainty","asStringArray","strings","entry","asEvidence","asFixes","fixes","op","fix","from","stripCodePrefix","message","code","prefix","makeId","index","buildFinding","fields","rawMessage","finding","ruleId","category","certainty","suggestion","relatedPaths","evidence","splitQualityWarnings","warnings","findings","others","warning","context","findingsFromAnalysis","analysis","diagnostics","diagnostic","countBySeverity","counts","compareFindings","rankA","rankB","pathA","pathB","codeA","codeB","groupByCategory","groups","bucket","worstRank","worst","rank","rankDelta","filterByMinSeverity","limit","certaintyLabel","EMPTY_POLICY_TEXT","isPlainObject","parseQualityPolicy","EDITABLE_KEYS","unknownKey","rulesForFormat","rule","policy","configuration","QUALITY_PROFILES","storedProfileId","ids","id","FORMAT","isProfileForFormat","p","buildQualityOptions","profileId","gate","policyText","authored","hasGate","counter","nextQualityTicket","isStaleQualityTicket","ticket","remapFindings","remap","target","ANALYSIS_DEBOUNCE_MS","readJsonBody","response","isJsonMediaType","readAnalysisFlags","source","describeFailure","body","useQualityAnalysis","setOutput","useOutputStore","state","outputStore","OutputStoreContext","themesStore","ThemesStoreContext","profileIds","useSettingsStore","timerRef","abortRef","cancel","run","documentName","documentText","jsonDefinition","controller","quality","pointer","expansion","expandForServer","validThemesByName","stored","previous","_b"],"mappings":"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"}
|
package/dist/client/index.html
CHANGED
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
<link rel="preconnect" href="https://fonts.googleapis.com" />
|
|
10
10
|
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
|
|
11
11
|
<title>JSON to Office</title>
|
|
12
|
-
<script type="module" crossorigin src="/assets/index-
|
|
12
|
+
<script type="module" crossorigin src="/assets/index-1Vg-Jg2R.js"></script>
|
|
13
13
|
<link rel="modulepreload" crossorigin href="/assets/react-vendor-BmQ2Ko_D.js">
|
|
14
14
|
<link rel="modulepreload" crossorigin href="/assets/query-vendor--rhYD8mC.js">
|
|
15
15
|
<link rel="modulepreload" crossorigin href="/assets/radix-ui-BzbaiLpZ.js">
|