# Paragraph

> Tekst, geschreven uit je data of op het aanvraagmoment opgelost uit prompts.

Source: https://docxcelerate.com/nl/docs/nodes/paragraph/

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:

```tsx
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:

```tsx
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:

```tsx
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:

```tsx
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

Data in, a line of text out.

Source: `src/nodes/paragraph/static.node.tsx`

```tsx
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**

```json
{
  "id": "greeting",
  "kind": "paragraph",
  "mode": "static",
  "text": "Dear Adaeze Nkemelu,"
}
```

### Static, with branching

One node, several outcomes — and the id stays put.

Source: `src/nodes/paragraph/conditional.node.tsx`

```tsx
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**

```json
{
  "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

A prompt and a placeholder. Previews show the placeholder, labelled.

Source: `src/nodes/paragraph/dynamic.node.tsx`

```tsx
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**

```json
{
  "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**

- `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

System, general, info and negative, each doing one job.

Source: `src/nodes/paragraph/prompted.node.tsx`

```tsx
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**

```json
{
  "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**

- `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 leest `mode` als
  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`, `null` en `undefined` worden allemaal
  overgeslagen.
- Renderers escapen de tekst. Een alinea kan geen markup de pagina in smokkelen,
  en is niet de plek voor opmaak.
