npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@haneinfo/report-engine

v0.5.1

Published

Host-neutral banded report core and isolated Node PDF rendering, ported from report_engine_mvp.

Readme

@haneinfo/report-engine

Host-independent banded report core, Node PDF renderer, multi-document MCP tools and React 19 designer/viewer, ported from report_engine_mvp at 1fd22d629ea09f5a2184790b5a91a9b47e3c0744. E1–E5 and the maintainer-approved canGrow pagination correction are complete. The decision and task-breakdown status remain unchanged. The package is at 0.3.0; the report-center P1 layout follow-ups have a pending minor changeset for 0.4.0. Maintainers perform G1 versioning and publication. See the E5 validation report for final gates, historical assertion inventories, source differences, all 78 screenshots, visual findings, retained formula lint diagnostics and the complete pending release plan.

Package and host responsibilities

This is a pure library with no plugin manifest. The first host, haneinfo-reports, owns authorization, data queries, definition kinds, persistence, artifact storage and scheduling. The separate @haneinfo/instance-analytics package aggregates readable state-machine instances; it does not render PDFs. document-templates retains its existing flow-based output.

| Entry | Responsibility | | --- | --- | | ./core (also .) | Report/data types, v1-to-v2 parsing, parameters, formulas, validation and preflight | | ./pdf | Node Worker rendering and HTTP handlers; host fonts, images and limits | | ./mcp | SDK-free host tool definitions, 22-tool catalog and multi-document contracts | | ./registry | Definition-registry draft adapter, RBAC, optimistic revisions, receipts and user-content wrapping | | ./designer | Browser-safe ReportDesigner, ReportViewer, createReportStore and createPdfClient | | ./mcp/stdio | Explicit local-development SDK v2 transport/file adapters; ./mcp/stdio/cli is its executable entry |

Only ./designer exports createReportStore. reportSync and useReportSync remain and use host draft callbacks; the removed feature is the demo Vite current_report_layout.json synchronization.

Dependencies

| Dependency | E1 choice and reason | | --- | --- | | zod | Peer ^4.4.0 plus development dependency, following existing form/document-templates consumers; share the host's Zod 4 and avoid duplicate schema/error identities. Source declares ^4.6.7; the complete copied source compiles against the workspace's locked 4.4.3 without schema changes. | | decimal.js | Runtime ^10.6.0, matching source arithmetic. | | @js-temporal/polyfill | Runtime ^0.5.1, matching source dates/time zones. | | @react-pdf/renderer | Runtime ^4.9.0; ESM-only rendering dependency. | | jsbarcode | Runtime ^3.12.3, matching source barcode implementation. | | qrcode | Runtime ^1.5.4, matching source QR implementation. | | React | Peer ^19.2.7; development version pinned to existing workspace 19.2.7. No workspace React upgrade. | | TypeScript | Existing 5.9.3; NodeNext/NodeNext, ES2022, .js relative imports. Copied source build/typecheck pass without TypeScript upgrades or compiler workarounds. | | zustand | Runtime ^5.0.14, resolving to the existing single version 5.0.15; internal store authorized by the maintainer. | | lucide-react, class-variance-authority, tailwind-merge, clsx | Runtime ^1.22.0, ^0.7.1, ^3.6.0, ^2.1.1 added in E4 for ./designer, aligned to existing workspace versions. controls/types.ts restores ControlCategory and ReportControlPlugin. | | @react-pdf/types | Development-only 2.14.0, the renderer's existing transitive type package, explicitly declared for the source Style import. | | @modelcontextprotocol/sdk | Source v1 dependency is not installed or imported; maintainer selected v2 on 2026-10-29. | | @modelcontextprotocol/server | Optional peer exactly 2.0.0 plus devDependency 2.0.0; only the development transport entry imports it. | | @modelcontextprotocol/client | DevDependency exactly 2.0.0; actual-client regression and stdio smoke tests. | | @haneinfo/plugin-api, @haneinfo/definition-registry | Peers ^1.27.0 and ^0.2.6, respectively, plus workspace:* devDependencies; existing ports, RBAC, drafts, receipts and user-content wrapping. | | @napi-rs/canvas, pdfjs-dist | Development-only 1.0.13 and 6.4.299, pinned to the source lockfile for visual regression. | | jsdom, @testing-library/react | Development-only 30.0.1 and 16.3.2, pinned for Vitest component tests (test:components). | | vite, @tailwindcss/vite, tailwindcss | Development-only 8.2.2, 4.3.3 and 4.3.3, pinned for the unshipped minimal E2E test host (tests/host/serve.mjs). | | @playwright/test, @axe-core/playwright | Development-only 1.62.1 and 4.11.1, pinned for Chromium E2E flows, multi-viewport screenshots and axe accessibility verification (test:e2e). | | @types/node, @types/react, @types/jsbarcode, @types/qrcode, vitest | Development only. | | @fontsource/noto-sans-tc, buffer, oxlint | Not ported; full font, browser buffers and source linter are outside this package. |

The package has no manifest and does not change plugin-api or R1 changesets. ./mcp, ./registry, ./mcp/stdio and ./mcp/stdio/cli are implemented in E3. ./designer is implemented in E4 and exports ReportDesigner, ReportViewer, createReportStore, createPdfClient and related types; no other entry exports the store.

Source mapping

The original scaffolding commit is 9f1d910. Each of its four Git blob IDs was compared with the corresponding file at source commit 1fd22d6 and matched exactly. Checkout CRLF is normalized by both repositories; comparison uses Git objects, not text decoding. New agent-guide and empty entry placeholders are scaffolding, not replacement implementations. The following inventory records each tracked source file and its final disposition. The 60 files in 081d053 also match their pinned source Git blobs exactly.

| Source path | Target path / disposition | Status | | --- | --- | | .assets/agent_live_preview.svg | — | Not ported: source application, development tooling or source documentation outside the library port | | .assets/agent_preview.png | — | Not ported: source application, development tooling or source documentation outside the library port | | .assets/designer_preview.png | — | Not ported: source application, development tooling or source documentation outside the library port | | .assets/pdf_preview.png | — | Not ported: source application, development tooling or source documentation outside the library port | | .assets/report_engine_agent.mkv | — | Not ported: source application, development tooling or source documentation outside the library port | | .assets/schema_preview.png | — | Not ported: source application, development tooling or source documentation outside the library port | | .github/workflows/check.yml | — | Not ported: source application, development tooling or source documentation outside the library port | | .gitignore | .gitignore | E1 copied verbatim in b60c5ea; generated PDF/PNG/tmp files ignored | | .oxlintrc.json | — | Not ported: source application, development tooling or source documentation outside the library port | | CODE_REVIEW.md | — | Not ported: source application, development tooling or source documentation outside the library port | | README.md | README.md | Copied verbatim, then adapted to the workspace library | | docs/conditional-printing.md | — | Not ported: source application, development tooling or source documentation outside the library port | | docs/embedding.md | — | Not ported: source application, development tooling or source documentation outside the library port | | docs/formula-engine-usage.md | — | Not ported: source application, development tooling or source documentation outside the library port | | docs/formula-engine-v1.md | — | Not ported: source application, development tooling or source documentation outside the library port | | docs/mcp-changes.md | — | Not ported: source application, development tooling or source documentation outside the library port | | docs/pdf-pagination.md | — | Not ported: source application, development tooling or source documentation outside the library port | | docs/review-guide.md | — | Not ported: source application, development tooling or source documentation outside the library port | | docs/testing.md | — | Not ported: source application, development tooling or source documentation outside the library port | | examples/conditional-invoice.json | tests/fixtures/v1/conditional-invoice.json | E2 copied verbatim in 8fd6f06; Git blob still matches pinned source | | examples/custom-invoice.json | tests/fixtures/v1/custom-invoice.json | E2 copied verbatim in 8fd6f06; Git blob still matches pinned source | | examples/embedded.html | — | Not ported: source application, development tooling or source documentation outside the library port | | examples/embedded.tsx | — | Not ported: source application, development tooling or source documentation outside the library port | | implementation_plan.md | — | Not ported: source application, development tooling or source documentation outside the library port | | index.html | — | Not ported: source application, development tooling or source documentation outside the library port | | mcp-config.example.json | — | Not ported: source application, development tooling or source documentation outside the library port | | package-lock.json | — | Not ported: source application, development tooling or source documentation outside the library port | | package.json | package.json | Copied verbatim, then adapted to the workspace library | | packages/report-engine/README.md | — | Not ported: source application, development tooling or source documentation outside the library port | | packages/report-engine/package.json | — | Not ported: source application, development tooling or source documentation outside the library port | | playwright.config.ts | — | Not ported: source application, development tooling or source documentation outside the library port | | public/favicon.svg | — | Not ported: source application, development tooling or source documentation outside the library port | | public/fonts/NotoSansTC.ttf | src/__tests__/fixtures/ReportEngineFixture.ttf | E1 derived test-only subset; full font not copied | | public/fonts/OFL.txt | src/__tests__/fixtures/OFL.txt | E1 copied verbatim | | public/icons.svg | — | Not ported: source application, development tooling or source documentation outside the library port | | scripts/check_bundle.ts | — | Not ported: source application, development tooling or source documentation outside the library port | | scripts/finish_package.ts | — | Not ported: source application, development tooling or source documentation outside the library port | | scripts/pdf_regression.ts | scripts/pdf_regression.ts | E1 copied verbatim in b60c5ea, then adapted; source assertions preserved; full 54-page regression passes | | scripts/serve_e2e.ts | — | Not ported: source application, development tooling or source documentation outside the library port | | scripts/test_mcp_e2e.ts | scripts/test_mcp_e2e.ts + tests/mcpE2e.test.ts | E3 copied verbatim in 6bd7135; all 41 source asserts retained in vitest using registry and real v2 client | | scripts/test_package.ts | — | Not ported: source application, development tooling or source documentation outside the library port | | src/App.tsx | src/designer/DesignerView.tsx | E4 copied verbatim in fd205a0, then adapted as internal DesignerView shell | | src/DemoApp.tsx | — | Not ported: standalone demo application shell with local state and copilot (E4-01) | | src/assets/hero.png | — | Not ported: standalone demo application asset (E4-01) | | src/client/reportPdf.ts | src/client/reportPdf.ts | E1 imported; E4 extended PdfRenderContext with v2 document/parameter/signal context | | src/compiler/bandLayout.tsx | src/compiler/bandLayout.tsx | E1 imported; host-safe font/layout integration and maintainer-approved canGrow page-break guard (20c479f) | | src/compiler/bandPipeline.ts | src/compiler/bandPipeline.ts | E1 imported; mechanical NodeNext adaptation | | src/compiler/evaluator.ts | src/compiler/evaluator.ts | E1 imported; mechanical NodeNext adaptation | | src/compiler/formatters.ts | src/compiler/formatters.ts | E1 imported; mechanical NodeNext adaptation | | src/compiler/grouping.ts | src/compiler/grouping.ts | E1 imported; mechanical NodeNext adaptation | | src/compiler/pdfCompiler.tsx | src/compiler/pdfCompiler.tsx | E1 imported; mechanical NodeNext adaptation | | src/components/agent/AgentCopilot.tsx | — | Not ported: standalone demo copilot panel (E4-01) | | src/components/data/DataSourceEditor.tsx | src/components/data/DataSourceEditor.tsx | E4 copied verbatim in fd205a0, then adapted for v2 templates and host tokens | | src/components/designer/BandCanvas.tsx | src/components/designer/BandCanvas.tsx | E4 copied verbatim in fd205a0, then adapted for host tokens, locale context and ARIA grid compliance | | src/components/designer/Navbar.tsx | src/components/designer/Navbar.tsx | E4 copied verbatim in fd205a0, then adapted for host tokens, parameter tab and callback sync | | src/components/designer/PropertyPanel.tsx | src/components/designer/PropertyPanel.tsx | E4 copied verbatim in fd205a0, then adapted for host tokens and locale context | | src/components/designer/Toolbox.tsx | src/components/designer/Toolbox.tsx | E4 copied verbatim in fd205a0, then adapted for host tokens and locale context | | src/components/designer/properties/BandPropertiesSection.tsx | src/components/designer/properties/BandPropertiesSection.tsx | E4 copied verbatim in fd205a0, then adapted for host tokens and locale context | | src/components/designer/properties/ColorPickerField.tsx | src/components/designer/properties/ColorPickerField.tsx | E4 copied verbatim in fd205a0, then adapted for host tokens and locale context | | src/components/designer/properties/ConditionalPrintSection.tsx | src/components/designer/properties/ConditionalPrintSection.tsx | E4 copied verbatim in fd205a0, then adapted for host tokens and locale context | | src/components/designer/properties/ControlSpecificInspector.tsx | src/components/designer/properties/ControlSpecificInspector.tsx | E4 copied verbatim in fd205a0, then adapted for host tokens and locale context | | src/components/designer/properties/FieldSelectInput.tsx | src/components/designer/properties/FieldSelectInput.tsx | E4 copied verbatim in fd205a0, then adapted for host tokens and locale context | | src/components/designer/properties/GeometrySection.tsx | src/components/designer/properties/GeometrySection.tsx | E4 copied verbatim in fd205a0, then adapted for host tokens and locale context | | src/components/designer/properties/GlobalReportSettings.tsx | src/components/designer/properties/GlobalReportSettings.tsx | E4 copied verbatim in fd205a0, then adapted for host tokens and locale context | | src/components/designer/properties/TypographySection.tsx | src/components/designer/properties/TypographySection.tsx | E4 copied verbatim in fd205a0, then adapted for host tokens and locale context | | src/components/diagnostics/ReportPreflightPanel.tsx | src/components/diagnostics/ReportPreflightPanel.tsx | E4 copied verbatim in fd205a0, then adapted for v2 preflight, MULTI_VALUE_PARAMETER_REFERENCE and host tokens | | src/components/formula/CalculatedFieldsPanel.tsx | src/components/formula/CalculatedFieldsPanel.tsx | E4 copied verbatim in fd205a0, then adapted for host tokens and locale context | | src/components/formula/ControlFormulaEditor.tsx | src/components/formula/ControlFormulaEditor.tsx | E4 copied verbatim in fd205a0, then adapted for host tokens and locale context | | src/components/formula/FormulaEditor.tsx | src/components/formula/FormulaEditor.tsx | E4 copied verbatim in fd205a0, then adapted for host tokens and locale context | | src/components/formula/useFormulaData.ts | src/components/formula/useFormulaData.ts | E4 copied verbatim in fd205a0, then adapted for preview data and single-value parameters | | src/components/schema/SchemaViewer.tsx | src/components/schema/SchemaViewer.tsx | E4 copied verbatim in fd205a0, then adapted for host tokens and locale context | | src/components/ui/LocaleSwitcher.tsx | — | Not ported: relied on global i18n store; host controls locale via props (E4-02) | | src/components/ui/badge.tsx | src/components/ui/badge.tsx | E4 copied verbatim in fd205a0, then adapted for NodeNext imports | | src/components/ui/badgeVariants.ts | src/components/ui/badgeVariants.ts | E4 copied verbatim in fd205a0, then adapted for host tokens | | src/components/ui/button.tsx | src/components/ui/button.tsx | E4 copied verbatim in fd205a0, then adapted for NodeNext imports | | src/components/ui/buttonVariants.ts | src/components/ui/buttonVariants.ts | E4 copied verbatim in fd205a0, then adapted for host tokens | | src/components/ui/input.tsx | src/components/ui/input.tsx | E4 copied verbatim in fd205a0, then adapted for host tokens | | src/components/ui/label.tsx | src/components/ui/label.tsx | E4 copied verbatim in fd205a0, then adapted for host tokens | | src/components/viewer/ReportViewer.tsx | src/components/viewer/ReportViewer.tsx | E4 copied verbatim in fd205a0, then adapted for runtime ParameterPanel, host preview data and PDF callbacks | | src/constants/labels.ts | src/constants/labels.ts | E4 copied verbatim in fd205a0, then adapted for NodeNext imports | | src/controls/barcodeUtils.ts | src/controls/barcodeUtils.ts | E1 imported; mechanical NodeNext adaptation | | src/controls/pdfRegistry.tsx | src/controls/pdfRegistry.tsx | E1 imported; mechanical NodeNext adaptation | | src/controls/plugins/BarcodeControl.tsx | src/controls/plugins/BarcodeControl.tsx | E4 copied verbatim in fd205a0, then adapted for browser-only style helpers and host tokens | | src/controls/plugins/CheckBoxControl.tsx | src/controls/plugins/CheckBoxControl.tsx | E4 copied verbatim in fd205a0, then adapted for browser-only style helpers and host tokens | | src/controls/plugins/ExpressionControl.tsx | src/controls/plugins/ExpressionControl.tsx | E4 copied verbatim in fd205a0, then adapted for browser-only style helpers and host tokens | | src/controls/plugins/FieldControl.tsx | src/controls/plugins/FieldControl.tsx | E4 copied verbatim in fd205a0, then adapted for browser-only style helpers and host tokens | | src/controls/plugins/ImageControl.tsx | src/controls/plugins/ImageControl.tsx | E4 copied verbatim in fd205a0, then adapted for browser-only style helpers and host tokens | | src/controls/plugins/LabelControl.tsx | src/controls/plugins/LabelControl.tsx | E4 copied verbatim in fd205a0, then adapted for browser-only style helpers and host tokens | | src/controls/plugins/LineControl.tsx | src/controls/plugins/LineControl.tsx | E4 copied verbatim in fd205a0, then adapted for browser-only style helpers and host tokens | | src/controls/plugins/PanelControl.tsx | src/controls/plugins/PanelControl.tsx | E4 copied verbatim in fd205a0, then adapted for browser-only style helpers and host tokens | | src/controls/plugins/QRCodeControl.tsx | src/controls/plugins/QRCodeControl.tsx | E4 copied verbatim in fd205a0, then adapted for browser-only style helpers and host tokens | | src/controls/plugins/SubReportControl.tsx | src/controls/plugins/SubReportControl.tsx | E4 copied verbatim in fd205a0, then adapted for browser-only style helpers and host tokens | | src/controls/registry.ts | src/controls/registry.ts | E4 copied verbatim in fd205a0, then adapted for NodeNext imports | | src/controls/styleUtils.ts | src/controls/styleUtils.ts | E1 imported; E4 extracted browser-safe designerStyleUtils.ts so ./designer never imports Node font helpers | | src/controls/types.ts | src/controls/types.ts | E1 imported; E4 restored ControlCategory and ReportControlPlugin from fd205a0 | | src/data/schema.ts | src/data/schema.ts | E1 imported; mechanical NodeNext adaptation | | src/data/schemaField.ts | src/data/schemaField.ts | E1 imported; mechanical NodeNext adaptation | | src/designer/EditorToolbar.tsx | src/designer/EditorToolbar.tsx | E4 copied verbatim in fd205a0, then adapted for host tokens and locale context | | src/designer/ReportDesigner.tsx | src/designer/ReportDesigner.tsx | E4 copied verbatim in fd205a0, then adapted for locale prop, v2 callbacks, accessDenied and WCAG 2.2 AA | | src/designer/services.ts | src/designer/services.ts | E4 copied verbatim in fd205a0, then extended with preview data, parameter options, draft and conflict callbacks | | src/formula/engine.ts | src/formula/engine.ts | E1 imported; mechanical NodeNext adaptation | | src/formula/parser.ts | src/formula/parser.ts | E1 imported; mechanical NodeNext adaptation | | src/formula/presentation.ts | src/formula/presentation.ts | E1 imported; mechanical NodeNext adaptation | | src/formula/registry.ts | src/formula/registry.ts | E1 imported; mechanical NodeNext adaptation | | src/formula/report.ts | src/formula/report.ts | E1 imported; mechanical NodeNext adaptation | | src/formula/types.ts | src/formula/types.ts | E1 imported; mechanical NodeNext adaptation | | src/formula/values.ts | src/formula/values.ts | E1 imported; mechanical NodeNext adaptation | | src/i18n/config.ts | src/i18n/config.ts | E4 copied verbatim in fd205a0, then adapted for NodeNext imports | | src/i18n/index.ts | src/i18n/index.ts | E4 copied verbatim in fd205a0, then adapted to use per-mount React LocaleContext instead of global store | | src/i18n/messages/en.ts | src/i18n/messages/en.ts | E4 copied verbatim in fd205a0, then completed with missing keys and verb/action copy | | src/i18n/messages/index.ts | src/i18n/messages/index.ts | E4 copied verbatim in fd205a0, then adapted for NodeNext imports | | src/i18n/messages/zh-TW.ts | src/i18n/messages/zh-TW.ts | E4 copied verbatim in fd205a0, then completed with missing keys and Taiwan Mandarin copy | | src/i18n/store.ts | — | Not ported: global Zustand locale store replaced by per-mount LocaleContext (E4-02) | | src/index.css | — | Not ported: package no longer ships global styles or :root tokens; host supplies tokens and @source (E4-01) | | src/inspector/layoutInspector.ts | src/inspector/layoutInspector.ts | E1 imported; mechanical NodeNext adaptation | | src/inspector/measureBand.ts | src/inspector/measureBand.ts | E1 imported; mechanical NodeNext adaptation | | src/lib/utils.ts | src/lib/utils.ts | E4 copied verbatim in fd205a0 | | src/main.tsx | — | Not ported: standalone Vite application entry (E4-01) | | src/mcp/changeTools.ts | src/mcp/changeTools.ts | E3 copied verbatim in 6bd7135, then adapted; source tool logic retained | | src/mcp/formulaTools.ts | src/mcp/formulaTools.ts | E3 copied verbatim in 6bd7135, then adapted; source tool logic retained | | src/mcp/index.ts | src/mcp/index.ts | E3 copied verbatim in 6bd7135, then adapted; source tool logic retained | | src/mcp/results.ts | src/mcp/results.ts | E3 copied verbatim in 6bd7135, then adapted; source tool logic retained | | src/mcp/server.ts | src/mcp/server.ts | E3 copied verbatim in 6bd7135, then adapted; source tool logic retained | | src/mcp/tools.ts | src/mcp/tools.ts | E3 copied verbatim in 6bd7135, then adapted; source tool logic retained | | src/mock/mockDataSource.ts | src/mock/mockDataSource.ts | E1 imported for core/PDF tests; never imported by ./designer (E4-01) | | src/mock/sampleReport.ts | src/mock/sampleReport.ts | E1 imported for core/PDF tests; never imported by ./designer (E4-01) | | src/public/core.ts | src/public/core.ts | E1 imported; E2 template/parameter integration on the ported implementation | | src/public/designer.ts | src/public/designer.ts | E4 copied verbatim in 00eff0d, then adapted to remove global CSS import and export ReportViewer | | src/public/mcp.ts | src/public/mcp.ts | E3 copied verbatim in 6bd7135, then separated host and development exports | | src/public/pdf.ts | src/public/pdf.ts | E1 imported; E2 template/parameter integration on the ported implementation | | src/report/dataPath.ts | src/report/dataPath.ts | E1 imported; mechanical NodeNext adaptation | | src/report/dataSchema.ts | src/report/dataSchema.ts | E1 imported; mechanical NodeNext adaptation | | src/report/defaultSubReport.ts | src/report/defaultSubReport.ts | E1 imported; mechanical NodeNext adaptation | | src/report/document.ts | src/report/document.ts | E1 imported; E2 template/parameter integration on the ported implementation | | src/report/geometry.ts | src/report/geometry.ts | E1 imported; mechanical NodeNext adaptation | | src/report/layoutDiagnostics.ts | src/report/layoutDiagnostics.ts | E1 imported; mechanical NodeNext adaptation | | src/report/operations.ts | src/report/operations.ts | E1 imported; mechanical NodeNext adaptation | | src/report/pagination.ts | src/report/pagination.ts | E1 imported; mechanical NodeNext adaptation | | src/report/preflight.ts | src/report/preflight.ts | E1 imported; E2 template/parameter integration on the ported implementation | | src/report/presentationCapabilities.ts | src/report/presentationCapabilities.ts | E1 imported; mechanical NodeNext adaptation | | src/report/schema.ts | src/report/schema.ts | E1 imported; mechanical NodeNext adaptation | | src/report/subReport.ts | src/report/subReport.ts | E1 imported; mechanical NodeNext adaptation | | src/report/validateDocument.ts | src/report/validateDocument.ts | E1 imported; E2 template/parameter integration on the ported implementation | | src/server/atomicFile.ts | src/server/atomicFile.ts | E1 imported; mechanical NodeNext adaptation | | src/server/documentRepository.ts | src/server/documentRepository.ts | E1 imported; E2 template/parameter integration on the ported implementation | | src/server/layoutPlugin.ts | — | Removed by decision point 3 and maintainer 2026-10-29 decision; Vite demo only | | src/server/pdfService.ts | src/server/pdfService.ts | E1 imported; E2 template/parameter integration on the ported implementation | | src/server/reportApi.ts | src/server/reportApi.ts | E1 imported; E2 template/parameter integration on the ported implementation | | src/server/reportRepository.ts | src/server/reportRepository.ts | E1 imported; E2 template/parameter integration on the ported implementation | | src/server/requestSchema.ts | src/server/requestSchema.ts | E1 imported; E2 template/parameter integration on the ported implementation | | src/server/standalone.ts | — | Not ported: standalone demo server entry | | src/store/useReportStore.ts | src/store/useReportStore.ts | E1 imported; E4 copied designer store in fd205a0 and adapted for v2 documents, parameters and per-mount context | | src/sync/reportSync.ts | src/sync/reportSync.ts | E1 imported; E3/E4 adapted to support host loadDraft/saveDraft callbacks and v2 templates | | src/sync/useReportSync.ts | src/sync/useReportSync.ts | E4 copied verbatim in fd205a0, then adapted to drive createReportSync via host loadDraft/saveDraft/onVersionConflict callbacks | | src/types/datasource.ts | src/types/datasource.ts | E1 imported; mechanical NodeNext adaptation | | src/types/diagnostics.ts | src/types/diagnostics.ts | E1 imported; mechanical NodeNext adaptation | | src/types/document.ts | src/types/document.ts | E1 imported; E2 template/parameter integration on the ported implementation | | src/types/render.ts | src/types/render.ts | E1 imported; E2 template/parameter integration on the ported implementation | | src/types/report.ts | src/types/report.ts | E1 imported; mechanical NodeNext adaptation | | tests/api.test.ts | — | 12 asserts removed by decision point 3 and maintainer 2026-10-29 decision (Vite demo feature removed); not weakened or replaced | | tests/designer.test.ts | tests/designer.test.ts | E4 copied verbatim in fd205a0, migrated to Vitest; all 26 source assertions retained | | tests/document.test.ts | tests/document.test.ts | E1 imported; vitest assertions preserved | | tests/e2e/designer.spec.ts | tests/e2e/designer.spec.ts | E4 copied verbatim in fd205a0, adapted for minimal test host and real MCP v2 client/registry; all 47 source expect sites retained | | tests/fixtures/pdf-baseline.json | tests/fixtures/pdf-baseline.json | E1 copied verbatim in b60c5ea; only long-text page 2 regenerated after the approved 75-line fixture change; all other bytes unchanged | | tests/formula.test.ts | tests/formula.test.ts | E1 imported; vitest assertions preserved | | tests/formulaApi.test.ts | tests/formulaApi.test.ts | E1 imported; vitest assertions preserved | | tests/helpers.ts | tests/helpers.ts | E1 imported; vitest assertions preserved | | tests/mcpChanges.test.ts | tests/mcpChanges.test.ts | E3 copied verbatim in 6bd7135, vitest/v2 transport; all 32 source asserts retained | | tests/pdf.test.ts | tests/pdf.test.ts | E1 imported; vitest assertions preserved | | tests/pdfFixtures.ts | tests/pdfFixtures.ts | E1 imported; maintainer-authorized long-text change: 95 lines (6,269 UTF-16 units) to 75 (4,949), to satisfy decision item 4's 5,000-unit cap; line format/layout unchanged | | tests/presentation.test.ts | tests/presentation.test.ts | E1 imported; vitest assertions preserved | | tests/report.test.ts | tests/report.test.ts | E1 imported; vitest assertions preserved | | tests/sync.test.ts | tests/sync.test.ts | E3 copied verbatim in 6bd7135; all 32 source asserts retained against v2 responses, plus v2 template round-trip | | tests/vitePreview.test.ts | — | 6 asserts removed by decision point 3 and maintainer 2026-10-29 decision (Vite demo feature removed); not weakened or replaced | | tsconfig.app.json | tsconfig.json | Copied verbatim, then adapted to the workspace library | | tsconfig.json | — | Not ported: source application, development tooling or source documentation outside the library port | | tsconfig.node.json | — | Not ported: source application, development tooling or source documentation outside the library port | | tsconfig.package.json | tsconfig.build.json | Copied verbatim, then adapted to the workspace library | | tsconfig.test.json | — | Not ported: source application, development tooling or source documentation outside the library port | | vite.config.ts | — | Not ported: source application, development tooling or source documentation outside the library port | | vite.package.config.ts | — | Not ported: source application, development tooling or source documentation outside the library port |

Test assertion inventory

Static call-expression counts compared with the pinned source; assertions map one-to-one. Counts are assertion sites, not dynamically executed assertion totals.

| Source test | Source assert calls | Ported expect/assert calls | Status | | --- | ---: | ---: | --- | | tests/formula.test.ts | 65 | 65 | E1 vitest migration; all source assertions retained | | tests/formulaApi.test.ts | 36 | 36 | E1 vitest migration; all source assertions retained | | tests/document.test.ts | 22 | 22 | E1 vitest migration; all source assertions retained | | tests/report.test.ts | 54 | 54 | E1 vitest migration; all source assertions retained | | tests/presentation.test.ts | 24 | 24 | E1 vitest migration; all source assertions retained | | tests/pdf.test.ts | 12 | 40 | E1 vitest migration (12 source asserts retained) + 2026-10-29 canGrow multi-page pagination overlap regression tests (28 asserts across N = 26, 27, 30, 60, 1000 on legacy and v2 routes, single-row multi-page text, ReportHeader + PageHeader, GroupHeader + GroupFooter, and canGrow: false parity) | | tests/safety.test.ts | — | 19 | New host safety assertions; includes four assertions for fixture lengths, the historical 95-line text-cap rejection (code and message), and successful 75-line PDF rendering with the same host configuration | | tests/worker.test.ts | — | 13 | E1 actual Worker lifecycle/resource assertions retained | | tests/documentV2.test.ts | — | 13 | E2: 3 v1/v2/host-data tests; looped fixture vectors execute 21 assertions | | tests/parameters.test.ts | — | 30 | E2: 12 tests for five types, cardinalities, options and defaults | | tests/parameterDocument.test.ts | — | 53 | E2: 45 tests for document/HTTP/Worker integration and JSON Pointer diagnostics | | tests/documentDiagnostics.test.ts | — | 10 | Review: 7 structural-location/cap tests | | tests/pdfBaseline.test.ts | — | 2 | Review: 4 cross-route consistency guard tests | | tests/mcpChanges.test.ts | 32 | 32 | E3: vitest with original strict assert calls, v2 real client and file-byte races | | tests/sync.test.ts | 32 | 37 | E3: all original assertions plus 5 v2-template sync assertions | | scripts/test_mcp_e2e.ts | 41 | 43 | E3: source 41 strict asserts plus all-22-tools coverage and artifact non-publication guard; imported by mcpE2e.test.ts | | tests/registry.test.ts | — | 36 | E3: kind/RBAC/scopes, receipts, wrapping, versions, previews and E2 parameter preservation | | tests/mcpSafety.test.ts | — | 17 | E3: actual stdio client, explicit file config, output/image/text/Worker guards | | tests/designer.test.ts | 26 | 26 | E4: store transactions, multi-drag single-step undo, selection/clipboard/arrange and PDF client abort/cache | | tests/designerBoundary.test.ts | — | 3 | E4: ./designer exports, no store leakage from other entries, and static import graph boundary (no Node/@react-pdf/*/Worker imports) | | tests/designerComponents.test.tsx | — | 81 | E4: 9 tests covering dual-mount locale/state/history/clipboard isolation, parameter editor validation & undo, runtime parameter panel (touch-friendly multi-select checklist, search filter, keyboard toggle, required check & error retry without contradictory message), MULTI_VALUE_PARAMETER_REFERENCE preflight navigation, callback sync conflict handling, localized lazy panel transitions without English loading text, localized formula editor copy, < 768px narrow-viewport designer notice with read-only preview entry and preserved undo history across resize, and inserting all 10 control types in en and zh-TW without CJK leaks or bilingual side-by-side strings | | tests/i18n.test.ts | — | 2 | E4: bilingual key parity (zh-TW vs en) and non-empty strings | | tests/e2e/designer.spec.ts | 47 | 47 | E4: 5 Chromium browser flows on the minimal test host (conditional PDF bytes, preflight control navigation & 422, multi-drag single-step undo & shortcut scoping, real MCP v2 sync & version conflict, dual built-package designer isolation) | | tests/viewerHost.test.tsx | — | 112 | 0.3.0: 18 tests (23 executions) for server-rendered runs and aborts, rowCount empty notice, host error copy and actions, end-user default copy without preflight, designer preview copy retained, viewer actions context, print and open, inlinePdf modes with pdfViewerEnabled, initial values and autoApply, viewer labelling, and the full draft conflict message and sync label in both locales | | tests/e2e/viewer-host.spec.ts | — | 48 | 0.3.0: 8 server-mode viewer screens × 2 themes × 3 viewports (48 screenshots + axe) with 360px overflow and 44px target checks, one run per apply, real PDF bytes, open/print downloads, host error action and CSV action; draft conflict message at 1280/768 px × 2 locales × 2 themes (8 screenshots + axe) | | tests/e2e/quality.spec.ts | — | 63 | E4: 13 screens × 2 themes (light/dark, verifying documentElement colorScheme) × 3 viewports (1440×900, 768×1024, 375×812) with screen-specific content waits, no-loading-indicator guard, rendered PDF page verification (pdfjs-dist), geometric non-overlap assertions between PageHeader and detail rows in viewer and large screens, dark-theme print-white paper check, < 768px designer notice check, 768×1024 and 1440×900 zoom percentage vs rendered paper scale + zoom-in + drag coordinate accuracy assertions, 360px overflow & >=44px touch target assertions, 78 screenshots + 78 axe scans (0 violations, including iframe) |

helpers.ts and pdfFixtures.ts are imported E1 support files. api (12) and vitePreview (6) are removed by decision point 3 and the maintainer's 2026-10-29 decision: their Vite demo feature is removed. All other source assertions are retained: E1 (213), E3 (105: 32 + 32 + 41), E4 Vitest (tests/designer.test.ts: 26) and E4 Playwright (tests/e2e/designer.spec.ts: 47). Total Vitest assertion sites: 658 (518 from E1–E3 + 26 newly ported designer.test.ts + 86 E4 sites: 3 + 81 + 2 + 28 new canGrow pagination regression sites in pdf.test.ts). Total Playwright E2E assertion sites: 110 (47 source + 63 quality). All 213 original assertions in the six E1 files are retained; pdf.test.ts adds 28 sites. E5 leaves src/formula unchanged. The PDF CLI's 18 assert sites and pdfBaseline.ts cross-route consistency guard remain separate and unchanged. Counts are static assertion sites; parameterized/looped cases execute more.

Verification

Run the workspace gates in order: pnpm install, pnpm build, pnpm build:graph, pnpm typecheck, pnpm -r --no-bail test, pnpm lint, pnpm specs:lint, pnpm exec changeset status. Run report-engine's test:components, test:pdf, test:mcp and test:e2e scripts as well. The PDF script checks legacy and v2 independently against the same frozen 10-case/54-page baseline and requires exact agreement between the two routes.

Final E5 results: 22 Vitest files / 172 tests / 658 assertion sites; components 4 files / 16 tests; MCP 4 files / 16 tests / 22 tools; Playwright 2 files / 6 tests / 110 assertion sites; 78 axe scans with zero violations. Workspace tests: 3,794 passed / 3 existing Windows skips in 48 packages. Lint has zero errors: the preserved formula engine retains 11 noNonNullAssertion warnings, 1 useOptionalChain warning and 8 useTemplate infos; 7 pre-existing validation infos are unchanged. All other report-engine lint diagnostics are fixed mechanically using the existing required helper and template literals. No suppression, rule weakening, dependency addition or baseline update. See the validation report for individual diagnostic locations and the initial axe.json newline failure.

Host PDF safety (E1-03)

Call renderReportPdf(input, hostOptions); font paths/Buffers are host configuration, never request JSON. A readable font containing the CJK glyphs 中、文、測、試 is validated before rendering. Font registration no longer uses window, a Vite constant or a bundled full font. Images accept only registered ASCII keys (letters/digits, hyphen/underscore); the host resolver returns bytes. URLs, data URLs and filesystem paths are rejected before rendering. Non-printing C0/C1 characters are removed; tabs/newlines remain layout whitespace. Text input and formatted output are limited to 5,000 UTF-16 units and markup remains plain text. Input data is copied, not mutated.

Font fixture: source public/fonts/NotoSansTC.ttf at 1fd22d6, SHA-256 28dba526a37f0e20795d200e5e1b36c878c908b3732f9c9cfe33a1fd95014add. CJK subset: SHA-256 df20ade3b51e8779dbd22b3f56cad064a3653c29b3f4d12c673d5de2df26692e, 129,236 bytes, 544 mapped Unicode characters. Latin-only rejection fixture: 22,328 bytes, 95 characters, SHA-256 6f3df0c5659aa280caf35fc4194adae42bbc6746a9637132dd6f9e1693413be1. Both fixtures include per-file metadata/codepoints and the source OFL license. The subset script was adapted from the existing document-templates precedent; in 9797d9e that script is copied verbatim from this workspace, while OFL.txt is copied from the pinned source. It is not a source report_engine_mvp script.

Only fonttools 4.60.1 was installed from PyPI into a disposable system-temp venv. Pip summary: downloaded the 2.3 MB cp312/win_amd64 wheel, successfully installed fonttools-4.60.1. The venv and failed sandbox directory have been deleted. Fonttools is not a package dependency.

Worker generation contract (E1-04)

renderReportPdf(input, hostOptions) always compiles in a fresh, terminable Worker Thread. The HTTP handler accepts hostOptions as its third argument; client JSON cannot configure it. All limits are required positive safe integers supplied by the host (no hidden defaults):

const host = {
  font: '/host-configured/font.ttf', // or Buffer
  limits: { maxPages: 200, maxTableRows: 10000, maxOutputBytes: 20971520,
    generationTimeoutMs: 30000, maxConcurrentGenerations: 2 },
  resolveImage: async (key: string) => authorizedImageBytes(key),
};
const bytes = await renderReportPdf(input, host);

The timer covers host image resolution and worker generation; after timeout no new worker is started. A currently running worker is terminated and awaited before rejection/capacity release. Host callbacks themselves run in the caller and must provide their own I/O cancellation. Admission counts are process-local; an App with multiple Node processes must budget each process. Nested input rows count toward maxTableRows. Actual laid-out pages are checked through the renderer 4.x onRender layout data (fail closed if unavailable); output bytes are bounded while consuming its stream. PreflightDocument runs before PDF/font rendering. Its new optional useDataSchema flag defaults true; the internal v1 bare-data adapter uses false to preserve the original renderer's schema-less formula behavior. In E2, v2 always uses its template schema, even when useDataSchema is false; the single host data source lives in options.dataSource. Only sanitized user text reaches drawing nodes; the source layout/controls/formula implementation is retained. Resolved image bytes are passed into the worker, never callback functions or paths. The Worker always exits before a completed or failed call returns.

| Code | Meaning | HTTP status | | --- | --- | --- | | REPORT_ENGINE_INVALID | Invalid input, preflight diagnostics, host font/image configuration | 422 | | REPORT_ENGINE_TOO_LARGE | Actual pages, nested row count or output bytes exceed host limits | 413 | | REPORT_ENGINE_TIMEOUT | Generation deadline exceeded and worker terminated | 504 | | REPORT_ENGINE_BUSY | Host concurrency budget exhausted | 503 |

Formula/preflight diagnostics retain their original fields and HTTP 422 behavior. Worker lifecycle diagnostics are internal test support, not public exports. All limits and awaited actual timeout exit are covered by tests.

PDF visual baseline (E1-05 complete)

The source regression script and 54-page baseline were copied verbatim in b60c5ea. The script retains every original assertion and uses a tsc-only runner, the injected font subset and the real Worker-backed renderer. Generated PDF/PNG/HTML and actual-baseline output stay ignored. The unchanged 5,000 UTF-16-unit boundary is applied to every case.

The original long-text value was 95 identically formatted joined lines (6,269 UTF-16 units). The maintainer authorized changing only the count to 75 (4,949 units), the largest number of complete lines within the cap: each line is 65 units, plus the intervening newlines. Expected markers are LINE-0001 through LINE-0075, AFTER-TEXT and REPORT-END; every marker must still occur exactly once. Font, layout and other fixture data remain unchanged. The historical 95-line value is constructed by the same original expression in safety.test.ts: four expect sites verify 6,269/4,949 lengths, REPORT_ENGINE_INVALID rejection with the 5000 UTF-16 message, and successful 75-line PDF rendering with the same font and limits. The test count remains 58. No cap, refusal behavior or original assertion was relaxed.

Both old and new long-text outputs have 2 pages. Extracted markers in the new PDF:

| Page | Markers | Continuation evidence | | --- | --- | --- | | 1 | LINE-0001–LINE-0061 | 61 original-format lines | | 2 | LINE-0062–LINE-0075 | 14 continuing lines; AFTER-TEXT and REPORT-END follow |

Two additional CLI assertions require at least two pages and LINE markers on page 2; per-page ranges are printed by test:pdf. The first run with the approved fixture correctly failed against the old page-2 geometry digest, before the explicitly scoped baseline update:

AssertionError [ERR_ASSERTION]: long-text page 2: text geometry changed
actual:   28ea0547a3abec63ee37e771609abf0521d37f09a3c082efc511ee53b93d2c11
expected: d214d7a09ad54df85b03d51ced38fdd157961e95ee9396f85968fedad3ee35fe

Only the long-text key was regenerated: page 1 is identical, and only page 2's geometry and perceptual hash values changed. Geometry is the script's SHA-256 of text strings, transforms and widths, so removing LINE-0076–LINE-0095 also changes this digest. The shorter content and following AFTER-TEXT/REPORT-END placement on page 2 account for the approved fixture-specific change. No font or layout setting was changed. Detailed page differences:

| Page | Page count, old → new | Geometry digest, old → new | Hash changed bits | | --- | --- | --- | ---: | | 1 | 2 → 2 | f5ceb184c47157712d3ce6cd1103bf378b15b169430daf5f63425e27bf478e76 → identical | 0 | | 2 | 2 → 2 | d214d7a09ad54df85b03d51ced38fdd157961e95ee9396f85968fedad3ee35fe → 28ea0547a3abec63ee37e771609abf0521d37f09a3c082efc511ee53b93d2c11 | 66 |

Full perceptual hashes (1,024 bits each):

page 1 old = new:
0000000000000000c6840000d6d6000090d600000000000086040000d6d40000c68e0000d6f6000090d6000000000000c6040000d6dc0000c68e0000d6d700005012000000000000c6040000d6d40000c68e0000d6d500001000000000000000c6040000d2d40000c68e0000d6d5000010000000000000008000000000000000
page 2 old:
0000000000000000c6840000d6d6000090d600000000000086040000d6d40000c68e0000d6f6000090d6000000000000c6040000d6dc0000c68e0000d6d700005012000000000000980000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c000000000000000
page 2 new:
0000000000000000c6840000d6d6000090d600000000000086040000d6d4000000000000100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c000000000000000

| Other case | Pages | Comparison with original baseline | | --- | ---: | --- | | conditional | 1 | Geometry/hash identical on every page | | keep-together | 2 | Geometry/hash identical on every page | | split-fixed-band | 2 | Geometry/hash identical on every page | | empty | 1 | Geometry/hash identical on every page | | table | 4 | Geometry/hash identical on every page | | long-table | 3 | Geometry/hash identical on every page | | nested | 4 | Geometry/hash identical on every page | | groups | 2 | Geometry/hash identical on every page | | large | 33 | Geometry/hash identical on every page |

All nine other keys (52 pages) retain their original bytes, including formatting. A raw before/after comparison confirms that the only changed bytes are the two page-2 long-text values. No font-subset hash rebaseline was needed elsewhere. The full test:pdf rerun passes all content, margins, page counts, geometry and hash checks (10 cases / 54 pages). The general --update page-count/geometry guards remain unchanged.

Template/data separation (E2-01)

ReportDocument now has formatVersion: 2, report, dataSource: { id, name, schema }, and optional sampleData: { parameters?, items }. The strict v2 schema rejects dataSource.data. parseReportDocument and reportDocumentSchema accept v1 and return v2: metadata, schema and report are retained; all v1 dataSource.data becomes sampleData. Zod creates a copy and neither caller input nor stored source bytes are rewritten. Hosts should remove confidential samples before persisting templates. No automatic v2 save occurs on read.

renderReportPdf({ document, dataSource, executionTime, timeZone }, hostOptions) renders host ReportDataSource data. preflightDocument(document, { dataSource, executionTime, timeZone }) uses the same single dataSource option; without it, sampleData (or empty rows) is used. The document schema remains authoritative for host row validation. The source report/data render input remains compatible. The E1 optional useDataSchema flag is retained only for its legacy v1 schema-less bridge; v2 always enforces its schema; no second host-data argument was introduced. GetReportPreviewData(document, parameters, signal?) => Promise defines the E4 callback contract: hosts own query authorization and cancellation, and runtime data is never saved automatically. The designer implements this callback through its per-instance preview-data provider.

Store and draft callbacks round-trip v2 templates, sample data and parameter definitions.

E2 tests: documentV2.test.ts adds 3 tests / 13 expect sites (8 looped example compatibility sites, 5 template/host precedence sites; 21 dynamically executed assertions). E1's 213 source, 19 safety and 13 worker assertion sites remain. The PDF regression renders all unchanged fixtures through both legacy report/data and v2 + host data, comparing each route's 54 pages with the unchanged E1 baseline. The two JSON fixtures were copied verbatim from pinned source in 8fd6f06; embedded.html/tsx remain demo-only; sync.test.ts was retained in E3.

Report parameters (E2-02; maintainer decision 2026-10-29)

ReportDocument.parameters contains ReportParameterDefinition[]: name, label, type (string/number/boolean/date/datetime), multiple?, required?, options? and defaultValue?. Options are { kind: 'static', values: [{ value, label }] } or { kind: 'host', key }. Defaults are { kind: 'fixed', value } or { kind: 'formula', expression }. Core exports reportParameterDefinitionSchema, reportParameterDefinitionsSchema, reportParameterOptionSchema, and resolveReportParameters(definitions, input?, options?). Dates are strict ISO dates; datetime requires an explicit offset, using the existing formula engine's Temporal semantics. Values are never coerced. Unknown parameter names, duplicate definitions, header-name collisions, invalid types and option values are rejected. Host options are supplied as options.parameterOptions[key]; the engine performs no I/O.

resolveReportParameters returns values (including arrays for authorized host query/filter binding) and formulaParameters (single values only). Hosts bind values into their queries, e.g. region = ANY(:regions). Arrays must contain values of the declared type, required arrays cannot be empty, and every static/host-option selection must be listed. Multi-value defaults must be fixed arrays. Single defaults can be fixed or existing engine formulas, e.g. =DATEADD("month", -1, DATE(YEAR(TODAY()), MONTH(TODAY()), 1)). Formula defaults require host executionTime and timeZone. References to other parameters, fields, row indexes and aggregates are rejected: dependent parameters remain a report-center v2 feature. No syntax or function was added; the catalog remains 38 functions.

Layout formulas use params.name for single report parameters and header fields. Their names must not collide. Multi-values must never enter FormulaContext.reportParameters and layout references to them (explicit or bare fallback) are rejected, including nested subreports. Hosts wishing to display selections supply a separate scalar header field (e.g. SQL array_to_string(:regions, '、')). The engine does not serialize lists for formulas. These multi-value rules were decided by the maintainer on 2026-10-29.

parameters.test.ts adds 12 tests covering five types in both cardinalities, required values, static/host options, fixed defaults, all single formula default types, timezone boundaries, forbidden dependencies, header collisions, unknown names and copy/isolation behavior.

Parameter-aware validation and preflight (E2-03)

validateReportDocument uses the existing validateReportFormulas analyzer with header fields and single report parameters in the existing params namespace. Multi-value definitions are excluded from that schema. The document layer refines their UNKNOWN_FIELD diagnostics to MULTI_VALUE_PARAMETER_REFERENCE, using AST spans and the declared row schema. Declared row fields keep precedence over same-named query parameters. Source traversal covers Band/control visibleWhen, styleRules.when, calculatedFields, Expression and fieldName, recursively including subreports. No separate formula validator or syntax/function was introduced.

preflightDocument(document, { dataSource?, parameters?, parameterOptions?, executionTime?, timeZone?, execute? }) validates host rows against the template schema, applies defaults, validates selections and passes only headers plus single values into the existing formula run. Host user inputs override declared single defaults, and cannot override header fields. A copied host source is used; samples, template input and caller parameters are not mutated. execute:false performs static checks without requiring runtime values; supplied host/sample data is still structurally checked. The host can first call resolveReportParameters to bind query values and then supply the returned dataset to preflight/render. Engine operations perform no query I/O.

Diagnostic paths use JSON Pointer at document boundaries: /report/bands/0/controls/0/expression, /parameters/0/defaultValue/expression, /parameters/limit, /dataSource/data/items/0/amount. Names containing slash/tilde are escaped. FormulaDiagnostic fields such as code/span/controlId are preserved. Invalid default syntax/functions, incompatible return types, and forbidden references are blocked statically; errors requiring execution (e.g. division by zero or invalid calendar values) are also blocked by preflight with the default formula location.

parameterDocument.test.ts proves all maintainer rules: root/child × explicit/bare reference matrix covers six formula locations (24 tests); row-precedence and nested-inheritance tests check that valid row access stays valid and arrays never enter reportParameters. Five types × two cardinalities (10 tests) cover runtime value/option/default validation. Other tests cover required/empty arrays, strict elements, fixed-only multi defaults, forbidden dependent defaults, header collisions, scalar selection-display headers, host clock/timezone, malformed host data, JSON Pointer escaping, HTTP and actual Worker rendering/rejection. parameters.test.ts independently covers fixed/default/option definition vectors and asserts the unchanged 38-function catalog.

E2 including review fixes adds 71 tests and 108 expect sites (13 + 30 + 53 + 10 + 2); E1's 58 tests and all 245 expect sites (213 source + 19 safety + 13 Worker) remain. Total: 129 tests / 353 expect sites. The PDF CLI retains all 18 assertion sites, applies them to legacy and v2 inputs, and adds one exact cross-route guard (19 sites across the CLI and pdfBaseline.ts). Each input checks 54 pages. Both v1 fixture Git blobs still match pinned source exactly. No sync/UI/MCP work was imported. MCP uses SDK v2 and host draft callbacks as described below.

E3 multi-document MCP (2026-10-29)

All ten E3 source files were copied unchanged in 6bd7135 before adaptation. The source modules and function names remain: tools.ts, formulaTools.ts, changeTools.ts, results.ts, server.ts and index.ts. The server module now builds McpToolDefinitionPort objects and registerInto(target) accepts the McpToolRegistry / McpToolsPort registerTool contract. It does not instantiate an SDK server or read/write artifact files. There is no publish tool. Every tool requires code; every mutation requires a fresh UUID requestId (reused only for an identical retry). Older mutation expectedVersion remains optional; every actual repository write carries the snapshot version. apply_report_changes requires baseVersion; preview can check it. Repository calls are read(code), write(code, document, expectedVersion).

The host provides initialDocument(code, context) to retain the source reset semantics for each selected document. No global demo document is used. A missing code or reset seed must fail. Host publishArtifact({ code, mediaType, bytes, name }, context) handles all PDF/SVG outputs; its reference is returned to the agent. Tool input outputPath is rejected. The development publisher writes uniquely named files only under an explicit absolute directory. PDF always uses renderReportPdf with host font, registered image keys and all five Worker limits. No assetsDirectory image resolver or second rendering implementation exists. Inspection retains the source SVG and geometry diagnostics; it compiles the guarded PDF before publishing either preview artifact. A host publisher is responsible for its own storage authorization and atomicity if publishing a two-artifact inspection partially fails.

ReportDocument remains v2, with schema and optional sampleData. Source data-source tools edit sample data and return a sample-data view; they never fetch runtime query data. Source formula handlers reuse the E2 parameter resolver/document validator, retain single parameters and header values, and never send multi-value query parameters to FormulaContext. Static validation does not require runtime values; MULTI_VALUE_PARAMETER_REFERENCE remains a document diagnostic. Parameter definitions survive layout edits, data-source replacement and atomic changes.

Use createReportMcpServer(options).registerInto(hostRegistry). The host supplies repository, kind, per-code reset seeds, pdf options, publishArtifact and the agent output protection callback. The registry helper supplies the existing definition-registry user-content boundary and notice. Read-only tools omit mutates (the port represents false by absence); writes set mutates:true. For the following table, R = definitionPermission(kind, 'read'), W = definitionPermission(kind, 'write'). For Report these are report:definition:read and report:definition:write. All handlers check actingUserId and the complete scope list.

| Tool | requiredScopes | mutates | Behavior regression | | --- | --- | --- | --- | | get_formula_catalog | R | false | source MCP regression: 38 functions, signatures, examples | | validate_report_formulas | R | false | source MCP regression + mcpChanges: valid, cycles/unknown functions | | preview_formula | R | false | source MCP regression: decimal total, formatted result, child Unicode | | set_calculated_fields | R, W | true | source MCP regression: chained calculations, cycle rollback | | preflight_report | R | false | mcpChanges: saved/candidate, structure/runtime; all-tools regression | | preview_report_changes | R | false | mcpChanges: no writes, geometry, stale version; registry receipt test | | apply_report_changes | R, W | true | mcpChanges: atomic batch, runtime failure, conflict/race, no-op | | get_report_data_source | R | false | source MCP regression: persisted schema and sample rows | | set_report_data_source | R, W | true | source MCP regression: atomic report/schema replacement, invalid data rollback | | get_report_layout | R | false | source MCP regression + mcpChanges: geometry, snapshot-version consistency | | get_available_data_fields | R | false | source MCP regression: calculations, schema discovery, sample values | | add_control | R, W | true | source MCP regression: exact control placement | | update_control | R, W | true | source MCP regression: child styles, runtime-invalid export rejection | | batch_update_controls | R, W | true | source MCP regression: style merge, missing-target rollback | | delete_control | R, W | true | source MCP regression: removes the inserted control | | set_band_height | R, W | true | source MCP regression: negative height rejected without mutation | | set_band_controls | R, W | true | all-tools extension: atomically replace selected band's controls | | update_page_settings | R, W | true | source MCP regression: landscape printable width, stdio edit | | reset_report_layout | R, W | true | source MCP regression: restore configured seed after corruption | | set_full_report | R, W | true | source MCP regression: invalid structure rejected, valid full replacement | | export_pdf | R | false | source MCP regression: empty/two/custom rows, PDF text, failed output preservation | | inspect_and_preview_layout | R | false | all-tools extension: guarded PDF + SVG host callback; protected inspection text |

All tools in this table are called through the actual @modelcontextprotocol/client 2.0.0 in-memory transport using createTestDefinitionRegistry and the ./registry adapter. The source's mcpChanges.test.ts additionally retains its complete file-byte/race assertions.

Development transport and dependencies

  • ./mcp exports the host tools, contracts, tool catalog, change types and conflict error. Its source entry and host tool modules import neither @modelcontextprotocol/* nor a file repository nor node:fs. The existing PDF service owns font reads and Worker rendering.
  • ./registry exports createRegistryReportRepository, createRegistryReportMcpOptions, protectReportToolReply, RegistryReportRepositoryOptions and USER_CONTENT_NOTICE.
  • ./mcp/stdio exports the SDK v2 adapter, explicit code-to-file repository allowlist, explicit directory artifact publisher, source file repository and StdioServerTransport. Only this development adapter imports @modelcontextprotocol/server and its ./stdio entry.
  • ./mcp/stdio/cli is the executable development entry (dist/mcp/index.js). Set absolute REPORT_ENGINE_LAYOUT_FILE (or positional file argument), REPORT_ENGINE_INITIAL_FILE, REPORT_ENGINE_FONT and REPORT_ENGINE_OUTPUT_DIRECTORY. REPORT_ENGINE_CODE defaults to report; REPORT_ENGINE_KIND defaults to Report. Missing configuration fails before any file is created. The initial document is an explicitly provided JSON file, not a bundled demo reset seed.
  • @modelcontextprotocol/server: optional peer 2.0.0 and devDependency 2.0.0. @modelcontextprotocol/client: devDependency 2.0.0 only. No v1 SDK dependency/import. @haneinfo/plugin-api: peer ^1.27.0 + dev workspace:; @haneinfo/definition-registry: peer ^0.2.6 + dev workspace:. No shared dependency upgrade and no other new large dependency.
  • Only ./designer exports the per-instance store. reportSync and useReportSync preserve their source assertions and run through host callbacks. Vite layoutPlugin and the demo api/vitePreview tests are excluded by decision point 3 and the maintainer decision.

Registry adapter permissions and receipts (E3-02)

The host registers its own definition kind and stores ReportDocument in envelope.spec. createRegistryReportMcpOptions({ registry, receipts, kind, rbac, initialDocument, pdf, publishArtifact }) supplies the per-invocation repository and agent output boundary. registry is the host DefinitionRegistryService draft interface (get/patchDraft), and receipts is the read-only findReceipt view of the same DEFINITION_STORE. No alternate receipt store, cache, definition schema, publisher or kind registration is created by this package. createRegistryReportRepository is the lower-level actor/scopes/callerKey/call-bound adapter; its raw document snapshots are engine inputs and must not be sent directly to an agent.

Both registry factories accept documentPath (default /spec), a JSON Pointer into the definition envelope. For an App envelope whose spec contains { document, category, main, header, children }, use /spec/document. Reads parse only that value; writes issue one replace at that exact path, preserving every sibling and the envelope metadata. The path must already exist for replacement. RFC 6901 ~0 and ~1 escapes are supported.

permissionScopes: { read, write } overrides the exact scope names in both the tool catalog and repository authorization. It is accepted by both registry factories and createReportMcpServer. Omission retains definitionPermission(kind, 'read' | 'write'); mutations still require both scopes at the tool boundary. Kind RBAC continues to use hasDefinitionPermission and is never renamed by a custom scope. Hosts still own their semantic-validation and locking boundary.

Set includeRegistryValidation: true on either registry factory to return the persisted registry result. The lower-level write snapshot and receipt replay gain validation: { valid, diagnostics }; MCP replies gain saved: true, valid, the saved version, and registry diagnostics. An invalid intermediate draft is still saved: success: true, isError: false, code: 'DRAFT_SAVED_INVALID', saved: true, valid: false. Here success means the save completed, not that the draft may be published. Do not undo it or retry it with a new requestId merely because valid is false. Fix the named JSON Pointer paths with a new edit. Valid drafts can also carry registry warnings. Without the option, successful write/replay replies retain the 0.1.0 shape and behavior.

Registry diagnostic codes, severity (ERROR / WARNING) and envelope JSON Pointer paths are preserved; messages and suggestions use the existing $userContent protection in both reply channels. DefinitionRegistryError diagnostics also survive failed tool calls, including when validation-result reporting is not enabled. These remain failed writes (`