Nodes
Paragraph
Tekst, geschreven uit je data of op het aanvraagmoment opgelost uit prompts.
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.
- Helpers
- Paragraph, useSetPrompts, useSetPlaceholders
- Nodesoort
- paragraph
- Categorie
- Tekst
- Wordt opgelost
- Both
- Children
- None. Paragraphs are leaves.
| Optie | Type | Wat het doet |
|---|---|---|
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 verplicht | 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. |
Er een schrijven
De tekst zijn de children van het element, geïnterpoleerd zoals alle JSX-children. Er is geen templatetaal, dus formattering, vertakking en meervoudsvormen zijn allemaal gewoon TypeScript, gedaan in de state-initializer, waar een component zijn denkwerk doet:
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>;
};
Een component mag async zijn — maar een die iets ophaalt is een die halverwege
de build kan falen, dus zet de waarde liever eerst in je data. Doe je toch een
await, dan moet elke hook vóór de eerste zijn aangeroepen.
Het tokenbudget
useAvailableTokens() is het budget dat deze build heeft toegewezen, 2000
tenzij je availableTokens in de buildopties zet. Statische nodes mogen het
negeren. Dynamische zouden het moeten uitgeven:
const availableTokens = useAvailableTokens();
useSetPrompts({
generalPrompt: `Explain the change. At most ${Math.floor(availableTokens / 4)} words.`,
});
Vertakken
Een document dat een van drie dingen zegt is één component met drie uitkomsten. Geef elke tak zijn eigen id:
if (state.settled) {
return <Paragraph id="balance-settled">Nothing outstanding.</Paragraph>;
}
return <Paragraph id="balance-arrears">A balance remains.</Paragraph>;
Aparte ids zijn wat het opgeloste document laat vastleggen welke tak deze ontvanger kreeg, en ze zijn verplicht zodra de vertakking gepubliceerd wordt — een engine slaat beide takken op, en een id adresseert precies één node.
Verschillen de uitkomsten alleen in bewoording, dan houdt de string berekenen en één node teruggeven het bij één id, wat de oudere gewoonte is en nog steeds een goede:
const [state] = useState((data: MemberData) => ({
line: data.settled ? "Nothing outstanding." : "A balance remains.",
}));
return <Paragraph id="balance">{state.line}</Paragraph>;
Dynamische alinea’s
Een dynamische alinea heeft geen eigen tekst; hij draagt in plaats daarvan
prompts en een placeholder, gezet met useSetPrompts en useSetPlaceholders of
meegegeven als props. Previewbuilds lossen hem op naar de placeholder en labelen
hem; builds op het aanvraagmoment sturen de prompts. Alleen generalPrompt is
verplicht — de vierde variant hieronder laat zien waar de andere drie voor zijn.
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>;
}; Waartoe het wordt opgelost
De node zoals hij in het DocumentModel verschijnt: de JSON die een renderer aangereikt krijgt. Geen opmaak, geen lay-out.
{
"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>
);
}; Waartoe het wordt opgelost
De node zoals hij in het DocumentModel verschijnt: de JSON die een renderer aangereikt krijgt. Geen opmaak, geen lay-out.
{
"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" />;
}; Waartoe het wordt opgelost
De node zoals hij in het DocumentModel verschijnt: de JSON die een renderer aangereikt krijgt. Geen opmaak, geen lay-out.
{
"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."
} Wat er aan het endpoint wordt gevraagd
Opgelost tegen dezelfde voorbeelddata. Een previewbuild stopt bij de placeholder; een build op het moment van de aanvraag stuurt deze mee.
- 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" />;
}; Waartoe het wordt opgelost
De node zoals hij in het DocumentModel verschijnt: de JSON die een renderer aangereikt krijgt. Geen opmaak, geen lay-out.
{
"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."
} Wat er aan het endpoint wordt gevraagd
Opgelost tegen dezelfde voorbeelddata. Een previewbuild stopt bij de placeholder; een build op het moment van de aanvraag stuurt deze mee.
- 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.
Aantekeningen
- Beide modi lossen op naar
kind: "paragraph". Een renderer leestmodeals het hem al iets kan schelen; hij vertakt nooit op hoe de node geschreven is. - Een node met eigen tekst is statisch, wat voor prompts een gedeelde hook er ook omheen heeft gezet. Beide op één element aanleveren is een fout.
- Een lege string levert een lege alinea op, niet géén node. Wil je de node
weglaten, geef dan niets terug —
false,nullenundefinedworden allemaal overgeslagen. - Renderers escapen de tekst. Een alinea kan geen markup de pagina in smokkelen, en is niet de plek voor opmaak.
Deze pagina bewerken op GitHub ↗ Deze pagina als Markdown lezen