Nodes
Paragraph
Prosa, aus Ihren Daten geschrieben oder zur Anfragezeit aus Prompts aufgelöst.
The workhorse. A static paragraph holds the text as its children; a dynamic one carries prompts and a placeholder, and is filled at request time. Both land as the same node kind, differing only by `mode` — which is inferred from what you supply, never declared. Supplying both text and a prompt on one element is an error rather than a precedence rule.
- Helper
- Paragraph, useSetPrompts, useSetPlaceholders
- Node-Art
- paragraph
- Kategorie
- Text
- Wird aufgelöst
- Both
- Kinder
- None. Paragraphs are leaves.
| Option | Typ | Was sie bewirkt |
|---|---|---|
id | string | Stable address for the node. Engines target it and build artifacts diff on it, so treat a rename as a breaking change. Optional: a node without one takes an id from where it sits, which is what keeps branches and loops from forcing you to invent names. Two nodes claiming one id is an error rather than a race. |
children | string | Static only. The text, interpolated the way any JSX children are. A node given its own text is static, whatever prompts a hook set around it. |
text | string | Static only. The same thing as children, for when a computed string reads better as a prop than as a body. |
align | "left" | "center" | "right" | "justify" | How the lines sit in the text column. Say it here when the alignment is what the paragraph *is* — a date ranged right, a standfirst centred. Leave it out and let a variant carry it when the alignment is what the theme thinks that kind of block looks like. A node that states both wins over its block, the same way a cell wins over its column. |
placeholder | string | Dynamic only. What previews show in place of generated content. Also settable with useSetPlaceholders. Optional, but a document that reads badly without one cannot be reviewed. |
generalPrompt erforderlich | string | Dynamic only. What this node should say. |
infoPrompt | string | Dynamic only. Context the model should have but should not restate. |
negativePrompt | string | Dynamic only. What to avoid — claims, tones, or facts it must not invent. |
systemPrompt | string | Dynamic only. Role and voice, applied ahead of the other prompts. |
examplePrompt | string | Dynamic only. What a good answer looks like, written out as finished text. Shown last, because it is what the answer gets measured against. |
derivers | DeriverInvocation[] | Values the engine computes before the node resolves, written to derived.* and readable from a template token. Built with derive(). These survive publishing and run per document — use them for anything computed from request data. useDeriver runs one during the build instead. |
Einen schreiben
Der Text sind die Kinder des Elements, interpoliert wie alle JSX-Kinder. Es gibt keine Templatesprache — Formatierung, Verzweigung und Pluralbildung sind alle gewöhnliches TypeScript, erledigt im State-Initializer, wo eine Komponente ihr Denken unterbringt:
import { Paragraph, useState } from "docxcelerate/template";
import type { MemberData } from "../types.ts";
export const Greeting: Paragraph = () => {
const [state] = useState((data: MemberData) => ({ name: data.memberName }));
return <Paragraph id="greeting">Dear {state.name},</Paragraph>;
};
Eine Komponente darf async sein — aber eine, die etwas holt, ist eine, die
mitten im Build scheitern kann; legen Sie den Wert lieber vorher in Ihre Daten.
Wenn Sie doch awaiten, muss jeder Hook vor dem ersten await aufgerufen sein.
Das Token-Budget
useAvailableTokens() ist das Budget, das dieser Build zugeteilt hat — 2000,
sofern Sie availableTokens in den Build-Optionen nicht setzen. Statische Nodes
dürfen es ignorieren. Dynamische sollten es ausgeben:
const availableTokens = useAvailableTokens();
useSetPrompts({
generalPrompt: `Explain the change. At most ${Math.floor(availableTokens / 4)} words.`,
});
Verzweigen
Ein Dokument, das eines von drei Dingen sagt, ist eine Komponente mit drei Ausgängen. Geben Sie jedem Zweig eine eigene id:
if (state.settled) {
return <Paragraph id="balance-settled">Nothing outstanding.</Paragraph>;
}
return <Paragraph id="balance-arrears">A balance remains.</Paragraph>;
Eigene ids sind es, die das aufgelöste Dokument festhalten lassen, welchen Zweig dieser Empfänger bekommen hat, und sie sind Pflicht, sobald der Zweig veröffentlicht wird — eine Engine speichert beide Arme, und eine id adressiert genau einen Node.
Unterscheiden sich die Ausgänge nur im Wortlaut, hält es den String zu berechnen und einen Node zurückzugeben bei einer id — die ältere Gewohnheit, und weiterhin eine gute:
const [state] = useState((data: MemberData) => ({
line: data.settled ? "Nothing outstanding." : "A balance remains.",
}));
return <Paragraph id="balance">{state.line}</Paragraph>;
Dynamische Absätze
Ein dynamischer Absatz hat keinen eigenen Text; er trägt stattdessen Prompts und
einen Platzhalter, gesetzt mit useSetPrompts und useSetPlaceholders oder als
Props übergeben. Vorschau-Builds lösen ihn zum Platzhalter auf und kennzeichnen
ihn; Builds zur Anfragezeit senden die Prompts. Nur generalPrompt ist
erforderlich — die vierte Variante unten zeigt, wozu die anderen drei da sind.
Varianten
Static
src/nodes/paragraph/static.node.tsx Data in, a line of text out.
import { Paragraph, useState } from "docxcelerate/template";
import type { SampleData } from "../sample-data.ts";
/**
* The smallest useful node: data taken into state, and a line built from it.
* `useState` is where data enters a component, and the only place it does.
*/
export const Greeting: Paragraph = () => {
const [state] = useState((data: SampleData) => ({ name: data.memberName }));
return <Paragraph id="greeting">Dear {state.name},</Paragraph>;
}; Wozu es aufgelöst wird
Der Node, wie er im DocumentModel erscheint: das JSON, das ein Renderer bekommt. Kein Styling, kein Layout.
{
"id": "greeting",
"kind": "paragraph",
"mode": "static",
"text": "Dear Adaeze Nkemelu,"
} Static, with branching
src/nodes/paragraph/conditional.node.tsx One node, several outcomes — and the id stays put.
import { Paragraph, useState } from "docxcelerate/template";
import { money, type SampleData } from "../sample-data.ts";
/**
* Branching is an ordinary `if`. The component decides what it is before it
* says anything, and each arm returns the node that arm means — one id per
* outcome, so a reader of the resolved document can tell which one they got.
*/
export const PriceChange: Paragraph = () => {
const [state] = useState((data: SampleData) => {
const delta = data.newPrice - data.lastPrice;
return {
plan: data.plan,
renewsOn: data.renewsOn,
lastPrice: data.lastPrice,
newPrice: data.newPrice,
delta,
percent: Math.abs((delta / data.lastPrice) * 100).toFixed(1),
};
});
if (state.delta === 0) {
return (
<Paragraph id="price-held">
Your {state.plan} membership renews at {money(state.newPrice)} a year — the same
price you paid last year.
</Paragraph>
);
}
return (
<Paragraph id="price-change">
Your {state.plan} membership is {state.delta > 0 ? "rising" : "falling"} by{" "}
{state.percent}%, from {money(state.lastPrice)} to {money(state.newPrice)} a year.
That is {money(state.newPrice / 12)} a month from {state.renewsOn}.
</Paragraph>
);
}; Wozu es aufgelöst wird
Der Node, wie er im DocumentModel erscheint: das JSON, das ein Renderer bekommt. Kein Styling, kein Layout.
{
"id": "price-change",
"kind": "paragraph",
"mode": "static",
"text": "Your Peak Anytime membership is rising by 5.1%, from £468 to £492 a year. That is £41 a month from 1 October 2026."
} Dynamic
src/nodes/paragraph/dynamic.node.tsx A prompt and a placeholder. Previews show the placeholder, labelled.
import { Paragraph, useSetPlaceholders, useSetPrompts, useState } from "docxcelerate/template";
import type { SampleData } from "../sample-data.ts";
/**
* The minimum a dynamic paragraph needs: one prompt, and a placeholder so the
* preview still reads as a letter. Setting prompts is what makes the node
* dynamic — nothing declares a mode.
*/
export const NextSteps: Paragraph = () => {
const [state] = useState((data: SampleData) => ({
name: data.memberName,
renewsOn: data.renewsOn,
}));
useSetPrompts({
generalPrompt:
`In two sentences, tell ${state.name} that their membership renews ` +
`automatically on ${state.renewsOn} and how to change plan before then.`,
});
useSetPlaceholders(
`Your membership renews automatically on ${state.renewsOn}. ` +
`Nothing is needed from you unless you want to change plan.`,
);
return <Paragraph id="next-steps" />;
}; Wozu es aufgelöst wird
Der Node, wie er im DocumentModel erscheint: das JSON, das ein Renderer bekommt. Kein Styling, kein Layout.
{
"id": "next-steps",
"kind": "paragraph",
"mode": "dynamic",
"text": "Your membership renews automatically on 1 October 2026. Nothing is needed from you unless you want to change plan."
} Was der Endpoint gefragt wird
Gegen dieselben Beispieldaten aufgelöst. Ein Vorschau-Build hält beim Platzhalter an; ein Build zur Anfragezeit schickt diese mit.
- general
- In two sentences, tell Adaeze Nkemelu that their membership renews automatically on 1 October 2026 and how to change plan before then.
Dynamic, all four prompts
src/nodes/paragraph/prompted.node.tsx System, general, info and negative, each doing one job.
import {
Paragraph,
useAvailableTokens,
useSetPlaceholders,
useSetPrompts,
useState,
} from "docxcelerate/template";
import { money, type SampleData } from "../sample-data.ts";
/**
* All four slots: general says what to write, info supplies facts without
* asking for them back, negative fences off the failure modes, system fixes
* the voice. `useAvailableTokens` is the budget the build allotted this node.
*/
export const Apology: Paragraph = () => {
const availableTokens = useAvailableTokens();
const [state] = useState((data: SampleData) => ({
name: data.memberName,
centreName: data.centreName,
plan: data.plan,
newPrice: data.newPrice,
}));
useSetPrompts({
systemPrompt:
`You write for a public leisure centre. Plain British English, second ` +
`person, no marketing language.`,
generalPrompt: `Apologise to ${state.name} for the main pool closure and say what ` +
`is still open. At most ${Math.floor(availableTokens / 4)} words.`,
infoPrompt: `The main pool at ${state.centreName} is resurfacing until 12 October. ` +
`The teaching pool, gym and classes are unaffected. Members on ` +
`${state.plan} paying ${money(state.newPrice)} a year get two guest passes ` +
`as compensation.`,
negativePrompt:
`Do not promise a refund, do not give a reopening date beyond 12 October, ` +
`and do not restate the price.`,
});
useSetPlaceholders(
`The main pool is closed for resurfacing until 12 October. ` +
`The teaching pool and all land-based classes are running as normal.`,
);
return <Paragraph id="pool-closure" />;
}; Wozu es aufgelöst wird
Der Node, wie er im DocumentModel erscheint: das JSON, das ein Renderer bekommt. Kein Styling, kein Layout.
{
"id": "pool-closure",
"kind": "paragraph",
"mode": "dynamic",
"text": "The main pool is closed for resurfacing until 12 October. The teaching pool and all land-based classes are running as normal."
} Was der Endpoint gefragt wird
Gegen dieselben Beispieldaten aufgelöst. Ein Vorschau-Build hält beim Platzhalter an; ein Build zur Anfragezeit schickt diese mit.
- system
- You write for a public leisure centre. Plain British English, second person, no marketing language.
- general
- Apologise to Adaeze Nkemelu for the main pool closure and say what is still open. At most 500 words.
- info
- The main pool at Riverside Leisure Centre is resurfacing until 12 October. The teaching pool, gym and classes are unaffected. Members on Peak Anytime paying £492 a year get two guest passes as compensation.
- negative
- Do not promise a refund, do not give a reopening date beyond 12 October, and do not restate the price.
Anmerkungen
- Beide Modi lösen zu
kind: "paragraph"auf. Ein Renderer liestmode, falls es ihn überhaupt kümmert; er verzweigt nie danach, wie der Node geschrieben wurde. - Ein Node mit eigenem Text ist statisch, gleich welche Prompts ein geteilter Hook um ihn herum gesetzt hat. Beides an einem Element anzugeben ist ein Fehler.
- Ein leerer String ergibt einen leeren Absatz, nicht keinen Node. Um den Node
wegzulassen, geben Sie nichts zurück —
false,nullundundefinedwerden alle übersprungen. - Renderer escapen den Text. Ein Absatz kann kein Markup in die Seite schmuggeln und ist nicht der Ort für Formatierung.
Diese Seite auf GitHub bearbeiten ↗ Diese Seite als Markdown lesen