devLast updated on
Last updated on
Preview React compositions live beside the PDF they render to
Serves every React composition in the project, paginated on screen with its sample data, beside the PDF the same tree renders to.
Usage
paradoc dev [dir] [options]Arguments
| Argument | Description |
|---|---|
dir | Project directory. Defaults to the working directory |
Options
| Option | Description |
|---|---|
--port <number> | Port to listen on (default: 5180) |
--host <host> | Address to bind (default: 127.0.0.1) |
--open | Open the preview in a browser |
--list | Print the compositions and what each is bound to, then exit |
--json | With --list, print JSON |
The conventions
Three things are found by convention. paradoc dev --list prints what it found, so
a project can check its own layout without starting anything.
Compositions
A composition is a .tsx or .jsx file under a compositions/ directory,
anywhere in the project. Modules named *.sample.*, *.test.*, *.spec.* and
*.stories.* sit beside compositions without being one.
The module's default export is the component, which is what a React layer's
path names. It receives { artifact, data }.
purchase-order.yaml
compositions/
purchase-order.tsx the composition
purchase-order.sample.ts its sample dataThe artifact
An artifact declares its composition as a file layer whose MIME type is
text/tsx or text/jsx, and the layer's path is relative to the artifact file
that declares it:
layers:
composition:
kind: file
mimeType: text/tsx
path: compositions/purchase-order.tsxparadoc dev reads every artifact in the project and pairs a composition with the
artifact whose layer resolves to it. If no layer points at a composition, it
falls back to an artifact file of the same name beside it —
purchase-order.tsx next to purchase-order.yaml — so a composition can be
written before its layer entry exists. Anything else is reported in the preview
rather than guessed.
Sample data
Either a sibling <name>.sample.ts (or .tsx, .js, .mjs, .jsx) whose
default export is the data, or a sample export from the composition module
itself. The sibling wins when both exist. The shape is the artifact's document
data:
export default {
fields: { orderNumber: "PO-2026-0148" },
parties: { buyer: { name: "Northwind Manufacturing" } },
};All three rules live in @paradoc/react/discovery and are read by
paradoc check as well, so a composition that previews is a
composition that checks.
The preview
The document is on the left, paginated in the browser at the page geometry
@paradoc/react fixes, and the PDF is on the right, rendered in Node from the
same tree with the preview's page breaks passed as hints. Editing a composition
or its sample updates both; editing an artifact reloads the page.
Before it renders, the PDF route runs the same check paradoc check runs
(checkElement from @paradoc/react-pdf/check): every Tailwind class the engine
cannot express, and every path the artifact does not declare. A composition with
a finding shows it where the PDF would have been, and a document that throws
while it draws shows the reason where the document would have been. A
composition with no artifact says so there too, rather than sitting on
"rendering".
Images are not a finding here. paradoc dev reads the image sources the check
reports and loads their bytes from disk before rendering, so only a source that
is not a file on disk reaches you, named by the renderer.
When the preview's page plan names a keep the tree no longer carries, the render drops that hint and the proof pane says which one, because the PDF then breaks where the preview did not.
What it serves
paradoc dev writes nothing to disk. The page is four modules generated in memory
and addressed as files of the project root:
| Module | What it is |
|---|---|
paradoc-dev.client.tsx | The preview application |
paradoc-dev.manifest.ts | What discovery found, with a loader per composition |
paradoc-dev.styles.css | The document's Tailwind, plus the preview's own frame |
paradoc-dev.element.ts | The one createElement call the PDF route makes in your React |
POST /@paradoc/dev/pdf is the proof view's own route and can be driven
headlessly. It takes { "id": "<composition id>", "plan": { "breaks": [], "repeats": [] } } — the id is what --list --json reports — and answers with
application/pdf, or 422 and { "findings": [{ "kind", "message" }] } when
the check fails. X-Paradoc-Unknown-Breaks and X-Paradoc-Unknown-Repeats name
any hint the render could not honour.
What the project must supply
The preview compiles the project's own compositions against the project's own
React, so it uses the project's toolchain. paradoc does not carry a bundler:
npm install @paradoc/react @paradoc/react-pdf react react-dom
npm install --save-dev vite @vitejs/plugin-react @tailwindcss/vite tailwindcss@paradoc/react, @paradoc/react-pdf, react and react-dom are resolved at
your project and nowhere else, because the page loads them and a second copy
would be a different React. The PDF route renders through your
@paradoc/react-pdf. A project missing any of the eight is told which, in the
install commands of whichever package manager left a lockfile, before anything
starts.
The project's own vite.config is not read: paradoc dev serves one page it
generates itself, so a composition should import packages and its own neighbours
and nothing else. TypeScript and JavaScript path aliases from tsconfig.json or
jsconfig.json are supported.
The preview also uses the installed pages component. Add it before starting:
paradoc add pagesExamples
Preview the project in the working directory:
paradoc devCheck what the conventions found, without starting a server:
paradoc dev --list
paradoc dev --list --json--list --json reports one object per composition and exits 0 even when there
are none:
[
{
"id": "compositions/purchase-order",
"composition": "compositions/purchase-order.tsx",
"artifact": {
"file": "purchase-order.yaml",
"name": "purchase-order",
"layer": "composition",
"matched_by": "layer"
},
"sample": { "from": "sibling", "file": "compositions/purchase-order.sample.ts" },
"problems": []
}
]Serve on another port and open a browser:
paradoc dev ./packages/documents --port 5300 --openHeadless PDF requests
A client posting a page plan must send Content-Type: application/json and Origin: <preview URL>. The plan accepts fonts alongside id and plan. The server returns 415 for another content type and 403 for another origin, including cross-origin font requests.