# Text deltas

> Render TextChunk values incrementally and fold them into a TextBlock.

- Path: `Guides > Inference > Streaming > Text deltas`
- Human: https://looprig.com/docs/guides/inference/streaming/text-deltas
- Machine index: https://looprig.com/llms.txt

`TextChunk` carries one provider-emitted text fragment. The fragment may be empty and still counts as a received chunk; the core accumulator preserves that distinction through `Empty`.

## API surface

```go
type TextChunk struct{ Text string }

var accumulated streamaccumulator.Text
accumulated.Add(&content.TextChunk{Text: "Hello, "})
accumulated.Add(&content.TextChunk{Text: "world!"})
block := accumulated.Block()
```

`block` is nil until at least one chunk is added. Afterwards it is a `*content.TextBlock` whose `Text` is the concatenation in arrival order.

```go
for {
	chunk, err := reader.Next()
	if errors.Is(err, io.EOF) {
		break
	}
	if err != nil {
		return err
	}
	if text, ok := chunk.(*content.TextChunk); ok {
		fmt.Print(text.Text) // live display
		accumulated.Add(text) // final block
	}
}
```

The accumulator does not send events, validate output, or decide whether the turn failed. Those policies stay in the caller or loop.

## Proof

- Source: [`core/content/chunk.go`](https://github.com/looprig/core/blob/main/content/chunk.go), [`core/content/streamaccumulator/streamaccumulator.go`](https://github.com/looprig/core/blob/main/content/streamaccumulator/streamaccumulator.go)
- Tests: [`core/content/streamaccumulator/streamaccumulator_test.go`](https://github.com/looprig/core/blob/main/content/streamaccumulator/streamaccumulator_test.go)
- Example: [`core/examples/streaming/example_test.go`](https://github.com/looprig/core/blob/main/examples/streaming/example_test.go)

Related: [TextBlock](/docs/guides/inference/content-blocks/text.md), [Accumulate a response](/docs/guides/inference/streaming/accumulation.md).
