{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "pdf-templates",
  "title": "Pdf Templates",
  "description": "Three renderer-native A4 document templates powered by Takumi, the PDF engine behind PDFCN: a commercial invoice, monthly operations report, and evidence-led compliance certificate. Exports serialisable data contracts, document components, deterministic samples, and renderPdfTemplate() for server-side PDF generation.",
  "dependencies": [
    "takumi-pdf@^0.6.4"
  ],
  "files": [
    {
      "path": "components/blocks/domain/pdf-templates/documents.tsx",
      "content": "import * as React from \"react\"\n\nimport type {\n  ComplianceCertificateTemplateData,\n  InvoiceTemplateData,\n  OperationsReportTemplateData,\n  PdfCurrency,\n  PdfParty,\n  PdfTemplateData,\n  PdfTemplateName,\n} from \"./types\"\n\nconst INK = \"#18181b\"\nconst MUTED_INK = \"#52525b\"\nconst FAINT_INK = \"#52525b\"\nconst RULE = \"#d4d4d8\"\nconst WASH = \"#f4f4f5\"\nconst PAPER = \"#ffffff\"\n\nconst pageStyle: React.CSSProperties = {\n  backgroundColor: PAPER,\n  color: INK,\n  display: \"flex\",\n  flexDirection: \"column\",\n  fontFamily: \"Helvetica, Arial, sans-serif\",\n  fontSize: 11,\n  lineHeight: 1.5,\n  width: \"100%\",\n}\n\nconst labelStyle: React.CSSProperties = {\n  color: MUTED_INK,\n  fontSize: 8,\n  fontWeight: 700,\n  letterSpacing: 1,\n  margin: 0,\n  textTransform: \"uppercase\",\n}\n\nconst sectionHeadingStyle: React.CSSProperties = {\n  borderBottom: `1px solid ${RULE}`,\n  fontSize: 13,\n  fontWeight: 700,\n  margin: \"28px 0 12px\",\n  paddingBottom: 6,\n}\n\nfunction formatMoneyMinor(amountMinor: number, currency: PdfCurrency): string {\n  return new Intl.NumberFormat(\"en-GB\", {\n    currency,\n    currencyDisplay: \"symbol\",\n    maximumFractionDigits: 2,\n    minimumFractionDigits: 2,\n    style: \"currency\",\n  }).format(amountMinor / 100)\n}\n\nfunction assertSafeInteger(value: number, name: string): number {\n  if (!Number.isSafeInteger(value)) {\n    throw new Error(`${name} must be a safe integer.`)\n  }\n  return value\n}\n\nfunction assertNonNegativeSafeInteger(value: number, name: string): number {\n  const integer = assertSafeInteger(value, name)\n  if (integer < 0) {\n    throw new Error(`${name} must not be negative.`)\n  }\n  return integer\n}\n\nfunction quantityToMillionths(quantity: number): number {\n  if (!Number.isFinite(quantity) || quantity < 0) {\n    throw new Error(\"Invoice item quantity must be a finite, non-negative number.\")\n  }\n\n  const quantityMillionths = Math.round(quantity * 1_000_000)\n  if (!Number.isSafeInteger(quantityMillionths) || Math.abs(quantity - quantityMillionths / 1_000_000) > 1e-10) {\n    throw new Error(\"Invoice item quantity supports at most six decimal places.\")\n  }\n  return quantityMillionths\n}\n\nfunction multiplySafe(left: number, right: number, name: string): number {\n  const product = assertNonNegativeSafeInteger(left, name) * assertNonNegativeSafeInteger(right, name)\n  if (!Number.isSafeInteger(product)) {\n    throw new Error(`${name} exceeds JavaScript's safe integer range.`)\n  }\n  return product\n}\n\nfunction addSafe(left: number, right: number, name: string): number {\n  const sum = assertSafeInteger(left, name) + assertSafeInteger(right, name)\n  if (!Number.isSafeInteger(sum)) {\n    throw new Error(`${name} exceeds JavaScript's safe integer range.`)\n  }\n  return sum\n}\n\nfunction divideAndRound(numerator: number, denominator: number, name: string): number {\n  const result = Math.round(numerator / denominator)\n  if (!Number.isSafeInteger(result)) {\n    throw new Error(`${name} exceeds JavaScript's safe integer range.`)\n  }\n  return result\n}\n\nexport function calculateInvoiceTotals(data: InvoiceTemplateData): {\n  lineTotalsMinor: number[]\n  subtotalMinor: number\n  taxMinor: number\n  totalMinor: number\n} {\n  const lineTotalsMinor = data.items.map((item) =>\n    divideAndRound(\n      multiplySafe(quantityToMillionths(item.quantity), item.unitPriceMinor, \"Invoice line total\"),\n      1_000_000,\n      \"Invoice line total\"\n    )\n  )\n  const subtotalMinor = lineTotalsMinor.reduce(\n    (total, itemTotal) => addSafe(total, itemTotal, \"Invoice subtotal\"),\n    0\n  )\n  const taxMinor = divideAndRound(\n    multiplySafe(subtotalMinor, data.taxRateBasisPoints, \"Invoice tax\"),\n    10_000,\n    \"Invoice tax\"\n  )\n\n  return {\n    lineTotalsMinor,\n    subtotalMinor,\n    taxMinor,\n    totalMinor: addSafe(subtotalMinor, taxMinor, \"Invoice total\"),\n  }\n}\n\nexport function PdfPageFooter({ documentId }: { documentId: string }) {\n  return (\n    <footer\n      style={{\n        borderTop: `1px solid ${RULE}`,\n        color: FAINT_INK,\n        display: \"flex\",\n        fontSize: 8,\n        justifyContent: \"space-between\",\n        paddingTop: 8,\n      }}\n    >\n      <span>{documentId}</span>\n      <span>\n        Page <span className=\"pageNumber\" /> of <span className=\"totalPages\" />\n      </span>\n    </footer>\n  )\n}\n\nfunction Party({ party }: { party: PdfParty }) {\n  return (\n    <div style={{ display: \"flex\", flexDirection: \"column\", gap: 1 }}>\n      <strong>{party.name}</strong>\n      {party.address.map((line) => (\n        <span key={line} style={{ color: MUTED_INK }}>\n          {line}\n        </span>\n      ))}\n      {party.registrationNumber ? (\n        <span style={{ color: MUTED_INK }}>{party.registrationNumber}</span>\n      ) : null}\n      {party.email ? <span style={{ color: MUTED_INK }}>{party.email}</span> : null}\n    </div>\n  )\n}\n\nexport function InvoicePdfDocument({ data }: { data: InvoiceTemplateData }) {\n  const { lineTotalsMinor, subtotalMinor, taxMinor, totalMinor } = calculateInvoiceTotals(data)\n  const taxRate = data.taxRateBasisPoints / 100\n\n  return (\n    <main style={pageStyle}>\n      <header style={{ display: \"flex\", justifyContent: \"space-between\" }}>\n        <div>\n          <p style={labelStyle}>Invoice</p>\n          <h1 style={{ fontSize: 30, letterSpacing: -0.8, margin: \"2px 0 0\" }}>\n            {data.invoiceNumber}\n          </h1>\n        </div>\n        <div style={{ color: MUTED_INK, textAlign: \"right\" }}>\n          <p style={{ margin: 0 }}>Issued {data.issueDate}</p>\n          <p style={{ margin: \"2px 0 0\" }}>Due {data.dueDate}</p>\n        </div>\n      </header>\n\n      <section style={{ display: \"flex\", gap: 32, marginTop: 32 }}>\n        <div style={{ flex: 1 }}>\n          <p style={labelStyle}>From</p>\n          <Party party={data.supplier} />\n        </div>\n        <div style={{ flex: 1 }}>\n          <p style={labelStyle}>Bill to</p>\n          <Party party={data.customer} />\n        </div>\n      </section>\n\n      <section aria-label=\"Invoice line items\">\n        <h2 style={sectionHeadingStyle}>Items</h2>\n        <div style={{ borderBottom: `1px solid ${INK}`, display: \"flex\", fontSize: 9, fontWeight: 700, paddingBottom: 6, textTransform: \"uppercase\" }}>\n          <span style={{ flex: 1 }}>Description</span>\n          <span style={{ textAlign: \"right\", width: 56 }}>Qty</span>\n          <span style={{ textAlign: \"right\", width: 104 }}>Rate</span>\n          <span style={{ textAlign: \"right\", width: 104 }}>Amount</span>\n        </div>\n        {data.items.map((item, index) => (\n          <div key={item.description} style={{ borderBottom: `1px solid ${RULE}`, display: \"flex\", padding: \"9px 0\" }}>\n            <span style={{ flex: 1 }}>{item.description}</span>\n            <span style={{ textAlign: \"right\", width: 56 }}>{item.quantity}</span>\n            <span style={{ textAlign: \"right\", width: 104 }}>\n              {formatMoneyMinor(item.unitPriceMinor, data.currency)}\n            </span>\n            <span style={{ fontWeight: 600, textAlign: \"right\", width: 104 }}>\n              {formatMoneyMinor(lineTotalsMinor[index], data.currency)}\n            </span>\n          </div>\n        ))}\n      </section>\n\n      <section style={{ alignSelf: \"flex-end\", marginTop: 20, width: 260 }}>\n        {[\n          [\"Subtotal\", formatMoneyMinor(subtotalMinor, data.currency)],\n          [`VAT (${taxRate}%)`, formatMoneyMinor(taxMinor, data.currency)],\n        ].map(([label, value]) => (\n          <div key={label} style={{ display: \"flex\", justifyContent: \"space-between\", padding: \"4px 0\" }}>\n            <span style={{ color: MUTED_INK }}>{label}</span>\n            <span>{value}</span>\n          </div>\n        ))}\n        <div style={{ borderTop: `2px solid ${INK}`, display: \"flex\", fontSize: 15, fontWeight: 700, justifyContent: \"space-between\", marginTop: 6, paddingTop: 8 }}>\n          <span>Total due</span>\n          <span>{formatMoneyMinor(totalMinor, data.currency)}</span>\n        </div>\n      </section>\n\n      {data.notes ? (\n        <section>\n          <h2 style={sectionHeadingStyle}>Notes</h2>\n          <p style={{ color: MUTED_INK, margin: 0 }}>{data.notes}</p>\n        </section>\n      ) : null}\n    </main>\n  )\n}\n\nfunction StatusMark({ status }: { status: string }) {\n  return <span>{status}</span>\n}\n\nexport function OperationsReportPdfDocument({\n  data,\n}: {\n  data: OperationsReportTemplateData\n}) {\n  return (\n    <main style={pageStyle}>\n      <header style={{ borderBottom: `2px solid ${INK}`, paddingBottom: 18 }}>\n        <p style={labelStyle}>Operations report · {data.period}</p>\n        <h1 style={{ fontSize: 28, letterSpacing: -0.8, margin: \"2px 0 4px\" }}>{data.title}</h1>\n        <p style={{ color: MUTED_INK, margin: 0 }}>\n          Prepared by {data.preparedBy} for {data.preparedFor}\n        </p>\n      </header>\n\n      <section>\n        <h2 style={sectionHeadingStyle}>Executive summary</h2>\n        <p style={{ fontSize: 13, lineHeight: 1.55, margin: 0 }}>{data.summary}</p>\n      </section>\n\n      <section>\n        <h2 style={sectionHeadingStyle}>Key metrics</h2>\n        <div style={{ display: \"flex\", flexWrap: \"wrap\", gap: 10 }}>\n          {data.metrics.map((metric) => (\n            <div key={metric.label} style={{ backgroundColor: WASH, minWidth: \"47%\", padding: 12 }}>\n              <p style={labelStyle}>{metric.label}</p>\n              <p style={{ fontSize: 21, fontWeight: 700, letterSpacing: -0.4, margin: \"3px 0\" }}>{metric.value}</p>\n              {metric.context ? <p style={{ color: MUTED_INK, fontSize: 9, margin: 0 }}>{metric.context}</p> : null}\n            </div>\n          ))}\n        </div>\n      </section>\n\n      <section>\n        <h2 style={sectionHeadingStyle}>Workstreams</h2>\n        <div style={{ borderBottom: `1px solid ${INK}`, display: \"flex\", fontSize: 9, fontWeight: 700, paddingBottom: 6, textTransform: \"uppercase\" }}>\n          <span style={{ flex: 1 }}>Workstream</span>\n          <span style={{ width: 100 }}>Owner</span>\n          <span style={{ textAlign: \"right\", width: 76 }}>Progress</span>\n          <span style={{ textAlign: \"right\", width: 128 }}>Status</span>\n        </div>\n        {data.workstreams.map((workstream) => (\n          <div key={workstream.name} style={{ borderBottom: `1px solid ${RULE}`, display: \"flex\", padding: \"9px 0\" }}>\n            <span style={{ flex: 1, fontWeight: 600 }}>{workstream.name}</span>\n            <span style={{ color: MUTED_INK, width: 100 }}>{workstream.owner}</span>\n            <span style={{ textAlign: \"right\", width: 76 }}>{workstream.progress}%</span>\n            <span style={{ textAlign: \"right\", width: 128 }}><StatusMark status={workstream.status} /></span>\n          </div>\n        ))}\n      </section>\n\n      <section style={{ display: \"flex\", gap: 22 }}>\n        <div style={{ flex: 1 }}>\n          <h2 style={sectionHeadingStyle}>Decisions needed</h2>\n          <ol style={{ margin: 0, paddingLeft: 16 }}>\n            {data.decisions.map((decision) => <li key={decision} style={{ marginBottom: 7 }}>{decision}</li>)}\n          </ol>\n        </div>\n        <div style={{ flex: 1 }}>\n          <h2 style={sectionHeadingStyle}>Next steps</h2>\n          <ol style={{ margin: 0, paddingLeft: 16 }}>\n            {data.nextSteps.map((step) => <li key={step} style={{ marginBottom: 7 }}>{step}</li>)}\n          </ol>\n        </div>\n      </section>\n    </main>\n  )\n}\n\nexport function ComplianceCertificatePdfDocument({\n  data,\n}: {\n  data: ComplianceCertificateTemplateData\n}) {\n  return (\n    <main style={pageStyle}>\n      <header style={{ alignItems: \"flex-start\", borderBottom: `3px solid ${INK}`, display: \"flex\", justifyContent: \"space-between\", paddingBottom: 18 }}>\n        <div>\n          <p style={labelStyle}>Evidence certificate</p>\n          <h1 style={{ fontSize: 28, letterSpacing: -0.8, margin: \"2px 0 0\" }}>Compliance record</h1>\n        </div>\n        <div style={{ textAlign: \"right\" }}>\n          <p style={labelStyle}>Certificate</p>\n          <strong>{data.certificateNumber}</strong>\n        </div>\n      </header>\n\n      <section style={{ backgroundColor: WASH, marginTop: 24, padding: 16 }}>\n        <p style={labelStyle}>Subject</p>\n        <Party party={data.subject} />\n      </section>\n\n      <section style={{ display: \"flex\", gap: 28 }}>\n        <div style={{ flex: 1 }}>\n          <h2 style={sectionHeadingStyle}>Material and project</h2>\n          <p style={{ margin: \"0 0 7px\" }}><strong>Material:</strong> {data.material}</p>\n          <p style={{ margin: \"0 0 7px\" }}><strong>Project:</strong> {data.project}</p>\n          <p style={{ margin: 0 }}><strong>Location:</strong> {data.location}</p>\n        </div>\n        <div style={{ flex: 1 }}>\n          <h2 style={sectionHeadingStyle}>Validity</h2>\n          <p style={{ margin: \"0 0 7px\" }}><strong>Issued:</strong> {data.issuedDate}</p>\n          <p style={{ margin: 0 }}><strong>Valid until:</strong> {data.validUntil}</p>\n        </div>\n      </section>\n\n      <section>\n        <h2 style={sectionHeadingStyle}>Statement</h2>\n        <p style={{ margin: 0 }}>{data.statement}</p>\n      </section>\n\n      <section>\n        <h2 style={sectionHeadingStyle}>Evidence checks</h2>\n        <div style={{ borderBottom: `1px solid ${INK}`, display: \"flex\", fontSize: 9, fontWeight: 700, paddingBottom: 6, textTransform: \"uppercase\" }}>\n          <span style={{ flex: 1 }}>Requirement</span>\n          <span style={{ flex: 1 }}>Evidence</span>\n          <span style={{ textAlign: \"right\", width: 104 }}>Result</span>\n        </div>\n        {data.checks.map((check) => (\n          <div key={check.requirement} style={{ borderBottom: `1px solid ${RULE}`, display: \"flex\", padding: \"9px 0\" }}>\n            <span style={{ flex: 1, fontWeight: 600 }}>{check.requirement}</span>\n            <span style={{ color: MUTED_INK, flex: 1 }}>{check.evidence}</span>\n            <span style={{ textAlign: \"right\", width: 104 }}>{check.result}</span>\n          </div>\n        ))}\n      </section>\n\n      <section style={{ marginTop: 32 }}>\n        <p style={labelStyle}>Issued by</p>\n        <p style={{ fontSize: 13, fontWeight: 700, margin: \"3px 0 0\" }}>{data.issuedBy}</p>\n      </section>\n    </main>\n  )\n}\n\nexport function PdfTemplateDocument<Name extends PdfTemplateName>({\n  name,\n  data,\n}: {\n  name: Name\n  data: PdfTemplateData<Name>\n}) {\n  if (name === \"invoice\") {\n    return <InvoicePdfDocument data={data as InvoiceTemplateData} />\n  }\n  if (name === \"operations-report\") {\n    return <OperationsReportPdfDocument data={data as OperationsReportTemplateData} />\n  }\n  return <ComplianceCertificatePdfDocument data={data as ComplianceCertificateTemplateData} />\n}\n",
      "type": "registry:component",
      "target": "components/blocks/domain/pdf-templates/documents.tsx"
    },
    {
      "path": "components/blocks/domain/pdf-templates/index.ts",
      "content": "export {\n  ComplianceCertificatePdfDocument,\n  calculateInvoiceTotals,\n  InvoicePdfDocument,\n  OperationsReportPdfDocument,\n  PdfTemplateDocument,\n} from \"./documents\"\nexport { renderPdfTemplate } from \"./render\"\nexport { PDF_TEMPLATE_SAMPLES } from \"./sample-data\"\nexport {\n  PDF_TEMPLATE_NAMES,\n  type ComplianceCertificateTemplateData,\n  type ComplianceCheck,\n  type InvoiceLineItem,\n  type InvoiceTemplateData,\n  type OperationsMetric,\n  type OperationsReportTemplateData,\n  type OperationsWorkstream,\n  type PdfCurrency,\n  type PdfParty,\n  type PdfRenderOptions,\n  type PdfTemplateData,\n  type PdfTemplateDataMap,\n  type PdfTemplateName,\n} from \"./types\"\n",
      "type": "registry:component",
      "target": "components/blocks/domain/pdf-templates/index.ts"
    },
    {
      "path": "components/blocks/domain/pdf-templates/render.tsx",
      "content": "import { measure, render } from \"takumi-pdf\"\n\nimport { PdfPageFooter, PdfTemplateDocument } from \"./documents\"\nimport { PDF_TEMPLATE_SAMPLES } from \"./sample-data\"\nimport type {\n  ComplianceCertificateTemplateData,\n  InvoiceTemplateData,\n  OperationsReportTemplateData,\n  PdfRenderOptions,\n  PdfTemplateData,\n  PdfTemplateName,\n} from \"./types\"\n\nfunction documentIdForTemplate<Name extends PdfTemplateName>(\n  name: Name,\n  data: PdfTemplateData<Name>\n): string {\n  if (name === \"invoice\") {\n    return (data as InvoiceTemplateData).invoiceNumber\n  }\n  if (name === \"operations-report\") {\n    const report = data as OperationsReportTemplateData\n    return `${report.title} · ${report.period}`\n  }\n  return (data as ComplianceCertificateTemplateData).certificateNumber\n}\n\n/**\n * Renders one of the bundled PDF template documents with Takumi — the renderer\n * underpinning pdfcn's Takumi registry. Call this from a server action, route\n * handler, worker, or any other trusted server-side integration.\n */\nexport async function renderPdfTemplate<Name extends PdfTemplateName>(\n  name: Name,\n  data: PdfTemplateData<Name> = PDF_TEMPLATE_SAMPLES[name] as PdfTemplateData<Name>,\n  options: PdfRenderOptions = {}\n): Promise<Uint8Array> {\n  const footer = <PdfPageFooter documentId={documentIdForTemplate(name, data)} />\n  const { height: footerHeight } = await measure(footer, {\n    fontFamilies: options.fontFamilies,\n    fonts: options.fonts,\n    images: options.images,\n    lang: \"en-GB\",\n    size: \"a4\",\n    stylesheets: options.stylesheets,\n  })\n\n  return render(<PdfTemplateDocument name={name} data={data} />, {\n    ...options,\n    footer,\n    lang: \"en-GB\",\n    margin: { bottom: Math.ceil(footerHeight) + 16, left: 48, right: 48, top: 48 },\n    outline: true,\n    size: \"a4\",\n  })\n}\n",
      "type": "registry:component",
      "target": "components/blocks/domain/pdf-templates/render.tsx"
    },
    {
      "path": "components/blocks/domain/pdf-templates/sample-data.ts",
      "content": "import type { PdfTemplateDataMap } from \"./types\"\n\n/**\n * Deterministic reference data used by the showcase endpoint and render check.\n * Replace it with application data before rendering in a consumer application.\n */\nexport const PDF_TEMPLATE_SAMPLES: PdfTemplateDataMap = {\n  invoice: {\n    invoiceNumber: \"NX-INV-2026-041\",\n    issueDate: \"13 August 2026\",\n    dueDate: \"12 September 2026\",\n    supplier: {\n      name: \"Nexus ReGen Ltd\",\n      address: [\"15 Greville Street\", \"London\", \"EC1N 8SQ\"],\n      email: \"finance@nexusregen.com\",\n      registrationNumber: \"Company No. 15432108\",\n    },\n    customer: {\n      name: \"Acme Civil Engineering Ltd\",\n      address: [\"Station Yard\", \"Hungerford\", \"RG17 0EY\"],\n      email: \"accounts@acmecivil.example\",\n      registrationNumber: \"Company No. 09182044\",\n    },\n    items: [\n      { description: \"Materials assurance workspace — August\", quantity: 1, unitPriceMinor: 240_000 },\n      { description: \"Verified movement records\", quantity: 84, unitPriceMinor: 1_200 },\n      { description: \"Implementation support\", quantity: 6, unitPriceMinor: 17_500 },\n    ],\n    currency: \"GBP\",\n    taxRateBasisPoints: 2_000,\n    notes: \"Please quote the invoice number with your payment. Payment terms: 30 days.\",\n  },\n  \"operations-report\": {\n    title: \"Monthly delivery report\",\n    period: \"August 2026\",\n    preparedFor: \"Nexus ReGen leadership team\",\n    preparedBy: \"Delivery Office\",\n    summary:\n      \"Delivery remained on plan overall. The two decisions below would remove the only material risk to the September release window.\",\n    metrics: [\n      { label: \"Movements verified\", value: \"1,284\", context: \"+18% month on month\" },\n      { label: \"Evidence complete\", value: \"94.6%\", context: \"+3.2 percentage points\" },\n      { label: \"Avoided disposal\", value: \"486 t\", context: \"Estimate across live projects\" },\n      { label: \"Open decisions\", value: \"2\", context: \"Both owner-assigned\" },\n    ],\n    workstreams: [\n      { name: \"Assurance workflows\", owner: \"M. Patel\", progress: 88, status: \"On track\" },\n      { name: \"Evidence ingestion\", owner: \"A. Roberts\", progress: 71, status: \"Requires decision\" },\n      { name: \"Partner onboarding\", owner: \"C. Green\", progress: 64, status: \"At risk\" },\n      { name: \"Reporting release\", owner: \"D. Khan\", progress: 93, status: \"On track\" },\n    ],\n    decisions: [\n      \"Approve an additional evidence-review capacity allocation for September.\",\n      \"Confirm the partner onboarding pilot cohort before 21 August.\",\n    ],\n    nextSteps: [\n      \"Close the remaining ingestion edge cases and run the acceptance pack.\",\n      \"Publish the reporting release note and customer enablement guide.\",\n      \"Reforecast the September delivery plan after the pilot cohort decision.\",\n    ],\n  },\n  \"compliance-certificate\": {\n    certificateNumber: \"NXR-CERT-2026-0187\",\n    issuedDate: \"13 August 2026\",\n    validUntil: \"13 February 2027\",\n    subject: {\n      name: \"Acme Civil Engineering Ltd\",\n      address: [\"Station Yard\", \"Hungerford\", \"RG17 0EY\"],\n      registrationNumber: \"Company No. 09182044\",\n    },\n    material: \"6F2 recycled aggregate\",\n    project: \"Riverside logistics hub\",\n    location: \"Bristol, United Kingdom\",\n    statement:\n      \"This certificate records the evidence reviewed for the stated material and project. It is a traceable record of the checks below, not a substitute for statutory approvals or site-specific professional advice.\",\n    checks: [\n      { requirement: \"Source and movement record\", evidence: \"Verified chain of custody\", result: \"Pass\" },\n      { requirement: \"Material classification\", evidence: \"EWC 17 01 07 recorded\", result: \"Pass\" },\n      { requirement: \"Quality evidence\", evidence: \"Test certificate linked\", result: \"Pass\" },\n      { requirement: \"Receiving-site acceptance\", evidence: \"Acceptance evidence due at next movement\", result: \"Review\" },\n    ],\n    issuedBy: \"Nexus ReGen Assurance\",\n  },\n}\n",
      "type": "registry:component",
      "target": "components/blocks/domain/pdf-templates/sample-data.ts"
    },
    {
      "path": "components/blocks/domain/pdf-templates/types.ts",
      "content": "import type { RenderOptions } from \"takumi-pdf\"\n\n/** The supported document templates. */\nexport const PDF_TEMPLATE_NAMES = [\n  \"invoice\",\n  \"operations-report\",\n  \"compliance-certificate\",\n] as const\n\nexport type PdfTemplateName = (typeof PDF_TEMPLATE_NAMES)[number]\n\nexport type PdfCurrency = \"EUR\" | \"GBP\" | \"USD\"\n\nexport interface PdfParty {\n  name: string\n  address: string[]\n  email?: string\n  registrationNumber?: string\n}\n\nexport interface InvoiceLineItem {\n  description: string\n  /** A non-negative quantity, up to six decimal places. */\n  quantity: number\n  /** Unit price in the currency's minor unit, e.g. 2,400.00 GBP is 240000. */\n  unitPriceMinor: number\n}\n\nexport interface InvoiceTemplateData {\n  invoiceNumber: string\n  issueDate: string\n  dueDate: string\n  supplier: PdfParty\n  customer: PdfParty\n  items: InvoiceLineItem[]\n  currency: PdfCurrency\n  /** Tax rate in basis points, e.g. 20% is 2000. */\n  taxRateBasisPoints: number\n  notes?: string\n}\n\nexport interface OperationsMetric {\n  label: string\n  value: string\n  context?: string\n}\n\nexport interface OperationsWorkstream {\n  name: string\n  owner: string\n  progress: number\n  status: \"At risk\" | \"On track\" | \"Requires decision\"\n}\n\nexport interface OperationsReportTemplateData {\n  title: string\n  period: string\n  preparedFor: string\n  preparedBy: string\n  summary: string\n  metrics: OperationsMetric[]\n  workstreams: OperationsWorkstream[]\n  decisions: string[]\n  nextSteps: string[]\n}\n\nexport interface ComplianceCheck {\n  requirement: string\n  evidence: string\n  result: \"Pass\" | \"Review\" | \"Not assessed\"\n}\n\nexport interface ComplianceCertificateTemplateData {\n  certificateNumber: string\n  issuedDate: string\n  validUntil: string\n  subject: PdfParty\n  material: string\n  project: string\n  location: string\n  statement: string\n  checks: ComplianceCheck[]\n  issuedBy: string\n}\n\nexport interface PdfTemplateDataMap {\n  invoice: InvoiceTemplateData\n  \"operations-report\": OperationsReportTemplateData\n  \"compliance-certificate\": ComplianceCertificateTemplateData\n}\n\nexport type PdfTemplateData<Name extends PdfTemplateName = PdfTemplateName> =\n  PdfTemplateDataMap[Name]\n\n/**\n * Safe renderer options consumers may customise without changing the A4 page\n * contract used by these templates. The template owns page geometry and locale.\n */\nexport type PdfRenderOptions = Pick<\n  RenderOptions,\n  \"fontFamilies\" | \"fonts\" | \"images\" | \"metadata\" | \"stylesheets\" | \"tagged\"\n>\n",
      "type": "registry:component",
      "target": "components/blocks/domain/pdf-templates/types.ts"
    }
  ],
  "type": "registry:block"
}