···5656 return o;
5757}
58585959-function packString(view: DataView, bytes: Uint8Array, o: number): number {
5959+function packString(
6060+ view: DataView,
6161+ bytes: Uint8Array,
6262+ o: number,
6363+ end: number,
6464+ context: string,
6565+): number {
6666+ let paddedLength = Math.ceil(bytes.length / 4) * 4;
6767+ let next = o + 4 + paddedLength;
6868+ if (next > end) {
6969+ throw new RangeError(
7070+ `clayterm transfer buffer capacity exceeded while packing ${context} ` +
7171+ `(${next} byte offset, ${end} byte limit). ` +
7272+ `Render a smaller visible slice or reduce frame content.`,
7373+ );
7474+ }
7575+6076 view.setUint32(o, bytes.length, true);
6177 o += 4;
6278 new Uint8Array(view.buffer).set(bytes, o);
6363- o += Math.ceil(bytes.length / 4) * 4;
7979+ o += paddedLength;
6480 return o;
6581}
6682···86102 o += 4;
8710388104 let bytes = encoder.encode(op.id);
8989- o = packString(view, bytes, o);
105105+ o = packString(view, bytes, o, end, "element id");
9010691107 let mask = 0;
92108 if (op.layout) mask |= PROP_LAYOUT;
···224240 o += 4;
225241226242 let str = encoder.encode(op.content);
227227- o = packString(view, str, o);
243243+ o = packString(view, str, o, end, "text content");
228244 break;
229245 }
230246 }
+7
specs/renderer-spec.md
···469469the renderer and is not an operation the caller performs or observes. The
470470transfer mechanism is an implementation detail described in Section 12.1.
471471472472+If a frame exceeds transfer-buffer capacity while packing string content, the
473473+renderer MUST throw a descriptive `RangeError` that identifies the condition as
474474+a transfer-buffer, frame-capacity, or packing overflow. The renderer MUST NOT
475475+expose only the raw host-level TypedArray message `"offset is out of bounds"`
476476+for this condition. The error message SHOULD direct callers to render a smaller
477477+visible slice or reduce frame content.
478478+472479### 9.3 Directive identity
473480474481Each element directive carries an `id` provided by the caller via `open()`.
+52
test/pack.test.ts
···11+import { describe, expect, it } from "./suite.ts";
22+import { close, open, pack, text } from "../ops.ts";
33+44+describe("pack", () => {
55+ it("throws a descriptive RangeError when text exceeds the transfer buffer", () => {
66+ let memory = new ArrayBuffer(64);
77+ let error: unknown;
88+99+ try {
1010+ pack(
1111+ [
1212+ open("root"),
1313+ text("x".repeat(128)),
1414+ close(),
1515+ ],
1616+ memory,
1717+ 0,
1818+ memory.byteLength,
1919+ );
2020+ } catch (caught) {
2121+ error = caught;
2222+ }
2323+2424+ expect(error).toBeInstanceOf(RangeError);
2525+ expect((error as Error).message).toMatch(
2626+ /transfer buffer|capacity|packing/,
2727+ );
2828+ expect((error as Error).message).toContain("text content");
2929+ expect((error as Error).message).not.toBe("offset is out of bounds");
3030+ expect((error as Error).message).toMatch(
3131+ /smaller visible slice|reduce frame content/,
3232+ );
3333+ });
3434+3535+ it("throws a descriptive RangeError when an element id exceeds the transfer buffer", () => {
3636+ let memory = new ArrayBuffer(16);
3737+ let error: unknown;
3838+3939+ try {
4040+ pack([open("x".repeat(64)), close()], memory, 0, memory.byteLength);
4141+ } catch (caught) {
4242+ error = caught;
4343+ }
4444+4545+ expect(error).toBeInstanceOf(RangeError);
4646+ expect((error as Error).message).toMatch(
4747+ /transfer buffer|capacity|packing/,
4848+ );
4949+ expect((error as Error).message).toContain("element id");
5050+ expect((error as Error).message).not.toBe("offset is out of bounds");
5151+ });
5252+});