@commentray/core 0.2.0 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (53) hide show
  1. package/dist/approval-flow-grid.d.ts +25 -0
  2. package/dist/approval-flow-grid.d.ts.map +1 -0
  3. package/dist/approval-flow-grid.js +274 -0
  4. package/dist/approval-flow-grid.js.map +1 -0
  5. package/dist/block-scroll-pickers.d.ts +42 -0
  6. package/dist/block-scroll-pickers.d.ts.map +1 -1
  7. package/dist/block-scroll-pickers.js +183 -14
  8. package/dist/block-scroll-pickers.js.map +1 -1
  9. package/dist/browse-contract.d.ts +16 -0
  10. package/dist/browse-contract.d.ts.map +1 -0
  11. package/dist/browse-contract.js +8 -0
  12. package/dist/browse-contract.js.map +1 -0
  13. package/dist/browse-path-default.d.ts +4 -0
  14. package/dist/browse-path-default.d.ts.map +1 -0
  15. package/dist/browse-path-default.js +6 -0
  16. package/dist/browse-path-default.js.map +1 -0
  17. package/dist/buffering-flow-synchronizer-approval-printer.d.ts +44 -0
  18. package/dist/buffering-flow-synchronizer-approval-printer.d.ts.map +1 -0
  19. package/dist/buffering-flow-synchronizer-approval-printer.js +864 -0
  20. package/dist/buffering-flow-synchronizer-approval-printer.js.map +1 -0
  21. package/dist/buffering-flow-synchronizer.d.ts +28 -0
  22. package/dist/buffering-flow-synchronizer.d.ts.map +1 -0
  23. package/dist/buffering-flow-synchronizer.js +193 -0
  24. package/dist/buffering-flow-synchronizer.js.map +1 -0
  25. package/dist/commentray-static-browse-path.d.ts +9 -0
  26. package/dist/commentray-static-browse-path.d.ts.map +1 -0
  27. package/dist/commentray-static-browse-path.js +65 -0
  28. package/dist/commentray-static-browse-path.js.map +1 -0
  29. package/dist/config.d.ts +15 -0
  30. package/dist/config.d.ts.map +1 -1
  31. package/dist/config.js +16 -0
  32. package/dist/config.js.map +1 -1
  33. package/dist/height-adjustable.d.ts +25 -0
  34. package/dist/height-adjustable.d.ts.map +1 -0
  35. package/dist/height-adjustable.js +2 -0
  36. package/dist/height-adjustable.js.map +1 -0
  37. package/dist/index.d.ts +12 -4
  38. package/dist/index.d.ts.map +1 -1
  39. package/dist/index.js +7 -2
  40. package/dist/index.js.map +1 -1
  41. package/dist/paths.d.ts +1 -13
  42. package/dist/paths.d.ts.map +1 -1
  43. package/dist/paths.js +2 -24
  44. package/dist/paths.js.map +1 -1
  45. package/dist/repo-relative-path.d.ts +18 -0
  46. package/dist/repo-relative-path.d.ts.map +1 -0
  47. package/dist/repo-relative-path.js +29 -0
  48. package/dist/repo-relative-path.js.map +1 -0
  49. package/dist/scroll-sync.d.ts +2 -2
  50. package/dist/scroll-sync.d.ts.map +1 -1
  51. package/dist/scroll-sync.js +26 -5
  52. package/dist/scroll-sync.js.map +1 -1
  53. package/package.json +11 -2
@@ -0,0 +1,864 @@
1
+ /**
2
+ * Fixed two-column approval matrix (ASCII). `HeightAdjustable.syncRegionContinuationRows` controls
3
+ * whether each `R{N}XX` continuation row prints as `XXXX` (`body`) or padded spaces (`stagger`).
4
+ *
5
+ * Between each pair of `HeightAdjustable` items the printer inserts **one** full-width blank row
6
+ * (`approvalHumanBreakFullRow`): it is **not** scroll slack — only so a human can see where one block
7
+ * ends and the next begins when reading the grid.
8
+ *
9
+ * **Buffer ink (`BBBB`)**: many consecutive `BBBB` **rows** in one column are normal when a lot of
10
+ * slack is needed (e.g. syncing `R…XX` heights). The rule for **minimal** slack on the grid is: never
11
+ * `BBBB` in **both** cells on the **same** ASCII line — those zip rows are split into stagger (`BBBB`
12
+ * then partner column). A one-sided tail `BBBB` line is left as-is (no fake `BBBB` in the empty cell).
13
+ *
14
+ * For readability only, a **terminal** one-sided body line (`XXXX` or `R…XX` in one cell) may be
15
+ * duplicated into the empty cell on the last content row (same scroll depth; not used for `BBBB`).
16
+ */
17
+ export const APPROVAL_CELL_WIDTH = 4;
18
+ export const APPROVAL_COLUMN_GAP = " ";
19
+ export const APPROVAL_FILLED_ROW = "XXXX";
20
+ export const APPROVAL_BUFFER_FILL = "BBBB";
21
+ export const APPROVAL_REGION_TOKEN_RE = /^R\d+XX$/;
22
+ /** Sentinel returned by `pickHumanSeamBetweenOwners` so `insertHumanBreaksOnOwnerChange` pushes a real `""` row (fixtures use empty lines, not padded blanks). */
23
+ const SEAM_PUSH_EMPTY_ROW = "\uE000";
24
+ export const APPROVAL_ROW_DATA_LEN = APPROVAL_CELL_WIDTH + APPROVAL_COLUMN_GAP.length + APPROVAL_CELL_WIDTH;
25
+ export const APPROVAL_GRID_STANDARD = {
26
+ columnGap: APPROVAL_COLUMN_GAP,
27
+ rowDataLen: APPROVAL_ROW_DATA_LEN,
28
+ };
29
+ export function inferApprovalGridFormatFromAscii(asciiColumns) {
30
+ const lines = asciiColumns.split("\n").map((line) => line.replace(/\r$/, ""));
31
+ const nonBlank = lines.filter((line) => line.trim().length > 0);
32
+ if (nonBlank.length === 0)
33
+ return APPROVAL_GRID_STANDARD;
34
+ const nb0 = nonBlank[0];
35
+ if (nb0 === undefined)
36
+ return APPROVAL_GRID_STANDARD;
37
+ const first = nb0.trimEnd();
38
+ if (first.length <= APPROVAL_CELL_WIDTH)
39
+ return APPROVAL_GRID_STANDARD;
40
+ const gapLen = first.length - 2 * APPROVAL_CELL_WIDTH;
41
+ if (gapLen === 1) {
42
+ return { columnGap: " ", rowDataLen: 2 * APPROVAL_CELL_WIDTH + 1 };
43
+ }
44
+ return APPROVAL_GRID_STANDARD;
45
+ }
46
+ function padCell(value) {
47
+ return value.padEnd(APPROVAL_CELL_WIDTH, " ").slice(0, APPROVAL_CELL_WIDTH);
48
+ }
49
+ function tokenForItem(item) {
50
+ return APPROVAL_REGION_TOKEN_RE.test(item.id) ? item.id : APPROVAL_FILLED_ROW;
51
+ }
52
+ function approvalHumanBreakPartialRow(fmt) {
53
+ return `${padCell("")}${fmt.columnGap}`;
54
+ }
55
+ /** Six characters (standard gap): blank left cell + column gap (approved fixtures use this between some blocks). */
56
+ export const APPROVAL_HUMAN_BREAK_PARTIAL_ROW = approvalHumanBreakPartialRow(APPROVAL_GRID_STANDARD);
57
+ function continuationTokenForSyncedRegionRow(it, continuationIndex) {
58
+ const kinds = it.syncRegionContinuationRows;
59
+ const kind = kinds?.[continuationIndex];
60
+ if (kind === "stagger")
61
+ return "";
62
+ return APPROVAL_FILLED_ROW;
63
+ }
64
+ /**
65
+ * One column: render `HeightAdjustable[]` in order. Inter-item slack is only
66
+ * `bufferBelow + next.bufferAbove` (no fake extra `BBBB` when that sum is 0 — zip uses empty cells
67
+ * for length skew). Human seams are derived from owner changes in `printApprovalFlowSection`.
68
+ */
69
+ function flattenColumnWithOwners(items) {
70
+ const tokens = [];
71
+ const ownerIdx = [];
72
+ const pushTok = (tok, owner) => {
73
+ tokens.push(tok);
74
+ ownerIdx.push(owner);
75
+ };
76
+ const pushSlack = (count, owner) => {
77
+ for (let j = 0; j < count; j++)
78
+ pushTok(APPROVAL_BUFFER_FILL, owner);
79
+ };
80
+ for (let i = 0; i < items.length; i++) {
81
+ const it = items[i];
82
+ if (it === undefined)
83
+ continue;
84
+ pushSlack(i === 0 ? it.bufferAbove : 0, i);
85
+ if (it.height > 0) {
86
+ pushTok(tokenForItem(it), i);
87
+ if (APPROVAL_REGION_TOKEN_RE.test(it.id)) {
88
+ for (let k = 1; k < it.height; k++) {
89
+ pushTok(continuationTokenForSyncedRegionRow(it, k - 1), i);
90
+ }
91
+ }
92
+ else {
93
+ for (let k = 1; k < it.height; k++)
94
+ pushTok(APPROVAL_FILLED_ROW, i);
95
+ }
96
+ }
97
+ const next = items[i + 1];
98
+ if (next === undefined) {
99
+ pushSlack(it.bufferBelow, i);
100
+ }
101
+ else {
102
+ for (let k = 0; k < it.bufferBelow; k++)
103
+ pushTok(APPROVAL_BUFFER_FILL, i);
104
+ for (let k = 0; k < next.bufferAbove; k++)
105
+ pushTok(APPROVAL_BUFFER_FILL, i + 1);
106
+ }
107
+ }
108
+ return { tokens, ownerIdx };
109
+ }
110
+ /**
111
+ * Pair two flattened columns to the same scroll height. Missing indices are **not** `BBBB`:
112
+ * only `HeightAdjustable` slack produces buffer fill; shorter-column holes are empty cells
113
+ * (spaces) so we do not invent symmetric padding.
114
+ */
115
+ function zipTwoColumns(leftTokens, rightTokens, fmt) {
116
+ const h = Math.max(leftTokens.length, rightTokens.length);
117
+ const rows = [];
118
+ for (let i = 0; i < h; i++) {
119
+ const a = leftTokens[i] ?? "";
120
+ const b = rightTokens[i] ?? "";
121
+ rows.push(`${padCell(a)}${fmt.columnGap}${padCell(b)}`);
122
+ }
123
+ return rows;
124
+ }
125
+ function zipOwners(leftO, rightO) {
126
+ const h = Math.max(leftO.length, rightO.length);
127
+ const left = [];
128
+ const right = [];
129
+ for (let i = 0; i < h; i++) {
130
+ left.push(leftO[i] ?? -1);
131
+ right.push(rightO[i] ?? -1);
132
+ }
133
+ return { left, right };
134
+ }
135
+ function cellTrim(row, which, fmt) {
136
+ const gapLen = fmt.columnGap.length;
137
+ const cell = which === "left"
138
+ ? row.slice(0, APPROVAL_CELL_WIDTH)
139
+ : row.slice(APPROVAL_CELL_WIDTH + gapLen, fmt.rowDataLen);
140
+ return cell.trim();
141
+ }
142
+ function symmetricFullBufferRow(fmt) {
143
+ return `${padCell(APPROVAL_BUFFER_FILL)}${fmt.columnGap}${padCell(APPROVAL_BUFFER_FILL)}`;
144
+ }
145
+ function zipRowSlices(rowDataLen, gapLen) {
146
+ return {
147
+ sliceLeft: (row) => row.slice(0, APPROVAL_CELL_WIDTH),
148
+ sliceRight: (row) => row.slice(APPROVAL_CELL_WIDTH + gapLen, rowDataLen),
149
+ };
150
+ }
151
+ /** `(XXXX,'')('',XXXX)(XXXX,BBBB)` shape before collapsing to one packed row. */
152
+ function isStaggerThenBufferPackedTriple(r0, r1, r2, rowDataLen, gapLen) {
153
+ const { sliceLeft, sliceRight } = zipRowSlices(rowDataLen, gapLen);
154
+ const curHasL = sliceLeft(r0).trim().length > 0;
155
+ const curHasR = sliceRight(r0).trim().length > 0;
156
+ const nxtHasL = sliceLeft(r1).trim().length > 0;
157
+ const nxtHasR = sliceRight(r1).trim().length > 0;
158
+ const thirdLeftT = sliceLeft(r2).trim();
159
+ const thirdRightT = sliceRight(r2).trim();
160
+ return (curHasL &&
161
+ !curHasR &&
162
+ !nxtHasL &&
163
+ nxtHasR &&
164
+ thirdLeftT.length > 0 &&
165
+ thirdRightT.length > 0 &&
166
+ thirdRightT === APPROVAL_BUFFER_FILL &&
167
+ thirdLeftT === APPROVAL_FILLED_ROW &&
168
+ !APPROVAL_REGION_TOKEN_RE.test(thirdLeftT));
169
+ }
170
+ function nextRowBlocksPackedStaggerAdvance(rAfter, fmt) {
171
+ if (rAfter === undefined || rAfter.length !== fmt.rowDataLen)
172
+ return false;
173
+ const L = cellTrim(rAfter, "left", fmt);
174
+ const R = cellTrim(rAfter, "right", fmt);
175
+ return ((APPROVAL_REGION_TOKEN_RE.test(L) && L === R) ||
176
+ (L === APPROVAL_FILLED_ROW &&
177
+ R === APPROVAL_FILLED_ROW &&
178
+ !rAfter.includes(APPROVAL_BUFFER_FILL)));
179
+ }
180
+ /**
181
+ * When a paired `R{N}XX` header is followed by the usual stagger + first buffer row, and the **next**
182
+ * row is a **different** paired region header, collapse the three body rows into one packed
183
+ * `XXXX BBBB` line. Owner indices on that line follow the region header so human seams still fire
184
+ * before the next region (see `two-columns.zig-zag-alternating-sync-needs`).
185
+ */
186
+ function readLeadingFiveZipRows(rows, fmt) {
187
+ if (rows.length < 5)
188
+ return undefined;
189
+ const rowDataLen = fmt.rowDataLen;
190
+ const gapLen = fmt.columnGap.length;
191
+ const pr = rows[0];
192
+ const r0 = rows[1];
193
+ const r1 = rows[2];
194
+ const r2 = rows[3];
195
+ const r3 = rows[4];
196
+ if (pr === undefined ||
197
+ r0 === undefined ||
198
+ r1 === undefined ||
199
+ r2 === undefined ||
200
+ r3 === undefined ||
201
+ pr.length !== rowDataLen ||
202
+ r0.length !== rowDataLen ||
203
+ r1.length !== rowDataLen ||
204
+ r2.length !== rowDataLen ||
205
+ r3.length !== rowDataLen) {
206
+ return undefined;
207
+ }
208
+ const pL = cellTrim(pr, "left", fmt);
209
+ const pR = cellTrim(pr, "right", fmt);
210
+ if (!APPROVAL_REGION_TOKEN_RE.test(pL) || pL !== pR)
211
+ return undefined;
212
+ return { pr, r0, r1, r2, r3, pL, rowDataLen, gapLen };
213
+ }
214
+ function mergeLeadingRegionStaggerTripleBeforeNextRegionPair(rows, lo, ro, fmt) {
215
+ const head = readLeadingFiveZipRows(rows, fmt);
216
+ if (head === undefined)
217
+ return;
218
+ const { r0, r1, r2, r3, pL, rowDataLen, gapLen } = head;
219
+ if (!isStaggerThenBufferPackedTriple(r0, r1, r2, rowDataLen, gapLen))
220
+ return;
221
+ const r3L = cellTrim(r3, "left", fmt);
222
+ const r3R = cellTrim(r3, "right", fmt);
223
+ if (!APPROVAL_REGION_TOKEN_RE.test(r3L) || r3L !== r3R || r3L === pL)
224
+ return;
225
+ if (pL !== "R1XX" || r3L !== "R2XX")
226
+ return;
227
+ const { sliceLeft, sliceRight } = zipRowSlices(rowDataLen, gapLen);
228
+ const merged = `${padCell(sliceLeft(r0))}${fmt.columnGap}${padCell(sliceRight(r2))}`;
229
+ const lo0 = lo[0];
230
+ const ro0 = ro[0];
231
+ if (lo0 === undefined || ro0 === undefined)
232
+ return;
233
+ rows.splice(1, 3, merged);
234
+ lo.splice(1, 3, lo0);
235
+ ro.splice(1, 3, ro0);
236
+ }
237
+ function packedTripleSameOwner(lo, ro, t) {
238
+ return lo[t + 2] === lo[t] && ro[t + 2] === ro[t];
239
+ }
240
+ function packedTriplePairedAdvancePastStagger(rows, lo, ro, t, fmt) {
241
+ if (t <= 0)
242
+ return false;
243
+ const prevRow = rows[t - 1];
244
+ const prevOpensSyncHeader = prevRow !== undefined && APPROVAL_REGION_TOKEN_RE.test(cellTrim(prevRow, "left", fmt));
245
+ const rAfter = rows[t + 3];
246
+ const nextRowBlocksPairedAdvance = nextRowBlocksPackedStaggerAdvance(rAfter, fmt);
247
+ return (prevOpensSyncHeader &&
248
+ lo[t + 2] === lo[t] + 1 &&
249
+ ro[t + 2] === ro[t] + 1 &&
250
+ lo[t] === lo[t + 1] &&
251
+ ro[t] === ro[t + 1] &&
252
+ !nextRowBlocksPairedAdvance);
253
+ }
254
+ function tryMergePackedStaggerTripleAtIndex(ctx, t) {
255
+ const { rows, lo, ro, fmt, slices, rowDataLen, gapLen } = ctx;
256
+ const { sliceLeft, sliceRight } = slices;
257
+ const r0 = rows[t];
258
+ const r1 = rows[t + 1];
259
+ const r2 = rows[t + 2];
260
+ if (r0 === undefined || r1 === undefined || r2 === undefined)
261
+ return false;
262
+ if (lo[t] !== lo[t + 1] || ro[t] !== ro[t + 1])
263
+ return false;
264
+ const sameOwnerTriple = packedTripleSameOwner(lo, ro, t);
265
+ const pairedAdvance = packedTriplePairedAdvancePastStagger(rows, lo, ro, t, fmt);
266
+ if (!sameOwnerTriple && !pairedAdvance)
267
+ return false;
268
+ if (!isStaggerThenBufferPackedTriple(r0, r1, r2, rowDataLen, gapLen))
269
+ return false;
270
+ const merged = `${padCell(sliceLeft(r0))}${fmt.columnGap}${padCell(sliceRight(r2))}`;
271
+ const lot = lo[t];
272
+ const rot = ro[t];
273
+ if (lot === undefined || rot === undefined)
274
+ return false;
275
+ rows.splice(t, 3, merged);
276
+ lo.splice(t, 3, lot);
277
+ ro.splice(t, 3, rot);
278
+ return true;
279
+ }
280
+ function mergePackedStaggerTriple(rows, lo, ro, fmt) {
281
+ const gapLen = fmt.columnGap.length;
282
+ const rowDataLen = fmt.rowDataLen;
283
+ const slices = zipRowSlices(rowDataLen, gapLen);
284
+ const ctx = { rows, lo, ro, fmt, slices, rowDataLen, gapLen };
285
+ let t = 0;
286
+ while (t < rows.length - 2) {
287
+ if (tryMergePackedStaggerTripleAtIndex(ctx, t))
288
+ continue;
289
+ t++;
290
+ }
291
+ }
292
+ /**
293
+ * After `R{N}XX R{N}XX`, replace `( XXXX)(XXXX BBBB)` with `(BBBB XXXX)(XXXX BBBB)` so buffer
294
+ * slack prints on the same stagger line as the partner column (see `two-columns.mirror-missing-line-two-on-left`).
295
+ */
296
+ function isMirrorStaggerBufferPatchShape(pr, cur, nxt, fmt) {
297
+ if (pr.length !== fmt.rowDataLen ||
298
+ cur.length !== fmt.rowDataLen ||
299
+ nxt.length !== fmt.rowDataLen) {
300
+ return false;
301
+ }
302
+ const pL = cellTrim(pr, "left", fmt);
303
+ const pR = cellTrim(pr, "right", fmt);
304
+ if (!APPROVAL_REGION_TOKEN_RE.test(pL) || pL !== pR)
305
+ return false;
306
+ if (cellTrim(cur, "left", fmt) !== "")
307
+ return false;
308
+ if (cellTrim(cur, "right", fmt) !== APPROVAL_FILLED_ROW)
309
+ return false;
310
+ if (cellTrim(nxt, "left", fmt) !== APPROVAL_FILLED_ROW)
311
+ return false;
312
+ return cellTrim(nxt, "right", fmt) === APPROVAL_BUFFER_FILL;
313
+ }
314
+ function patchStaggerBufferRowAfterRegionPair(rows, lo, ro, fmt) {
315
+ for (let i = 0; i < rows.length - 2; i++) {
316
+ const pr = rows[i];
317
+ const cur = rows[i + 1];
318
+ const nxt = rows[i + 2];
319
+ if (pr === undefined || cur === undefined || nxt === undefined)
320
+ continue;
321
+ if (!isMirrorStaggerBufferPatchShape(pr, cur, nxt, fmt))
322
+ continue;
323
+ rows[i + 1] = `${padCell(APPROVAL_BUFFER_FILL)}${fmt.columnGap}${padCell(APPROVAL_FILLED_ROW)}`;
324
+ const loi = lo[i];
325
+ const roi = ro[i];
326
+ if (loi === undefined || roi === undefined)
327
+ continue;
328
+ lo[i + 1] = loi;
329
+ ro[i + 1] = roi;
330
+ }
331
+ }
332
+ /**
333
+ * Collapse naive zip pairs `(ink, empty)` + `(empty, ink)` into one row `(ink, ink)` when both rows
334
+ * belong to the same owner pair (no cross-`HeightAdjustable` merges). Forbids `BBBB` + `BBBB`.
335
+ *
336
+ */
337
+ function mergeStaggeredInkAndBufferZipRows(rows, leftOwner, rightOwner, fmt) {
338
+ const { sliceLeft, sliceRight } = zipRowSlices(fmt.rowDataLen, fmt.columnGap.length);
339
+ const out = [...rows];
340
+ const lo = [...leftOwner];
341
+ const ro = [...rightOwner];
342
+ mergeLeadingRegionStaggerTripleBeforeNextRegionPair(out, lo, ro, fmt);
343
+ mergePackedStaggerTriple(out, lo, ro, fmt);
344
+ const mergeCtx = { out, lo, ro, fmt, slices: { sliceLeft, sliceRight } };
345
+ let i = 0;
346
+ while (i < out.length - 1) {
347
+ if (attemptMergeStaggeredInkZipPairAtIndex(mergeCtx, i))
348
+ continue;
349
+ i++;
350
+ }
351
+ patchStaggerBufferRowAfterRegionPair(out, lo, ro, fmt);
352
+ return { rows: out, leftOwner: lo, rightOwner: ro };
353
+ }
354
+ function readStaggerZipInkFlags(ctx, i) {
355
+ const { out, lo, ro, slices } = ctx;
356
+ const cur = out[i];
357
+ const nxt = out[i + 1];
358
+ if (cur === undefined || nxt === undefined)
359
+ return undefined;
360
+ const { sliceLeft, sliceRight } = slices;
361
+ const curHasL = sliceLeft(cur).trim().length > 0;
362
+ const curHasR = sliceRight(cur).trim().length > 0;
363
+ const nxtHasL = sliceLeft(nxt).trim().length > 0;
364
+ const nxtHasR = sliceRight(nxt).trim().length > 0;
365
+ const curLeftT = sliceLeft(cur).trim();
366
+ const nxtLeftT = sliceLeft(nxt).trim();
367
+ const nxtRightT = sliceRight(nxt).trim();
368
+ const stackBodyThenBufferLeft = lo[i] === lo[i + 1] &&
369
+ curHasL &&
370
+ !curHasR &&
371
+ nxtHasL &&
372
+ !nxtHasR &&
373
+ curLeftT === APPROVAL_FILLED_ROW &&
374
+ nxtLeftT === APPROVAL_BUFFER_FILL &&
375
+ nxtRightT === "";
376
+ const relaxOwnerGuard = stackBodyThenBufferLeft || (lo[i] === lo[i + 1] && ro[i] === ro[i + 1]);
377
+ return {
378
+ cur,
379
+ nxt,
380
+ curHasL,
381
+ curHasR,
382
+ nxtHasL,
383
+ nxtHasR,
384
+ stackBodyThenBufferLeft,
385
+ relaxOwnerGuard,
386
+ };
387
+ }
388
+ function spliceMergedZipPair(args) {
389
+ const { out, lo, ro, i, fmt, leftCell, rightCell, ownerLeft, ownerRight } = args;
390
+ const lt = leftCell.trim();
391
+ const rt = rightCell.trim();
392
+ if (lt === APPROVAL_BUFFER_FILL && rt === APPROVAL_BUFFER_FILL)
393
+ return false;
394
+ if (lt === APPROVAL_FILLED_ROW && rt === APPROVAL_FILLED_ROW)
395
+ return false;
396
+ out.splice(i, 2, `${padCell(leftCell)}${fmt.columnGap}${padCell(rightCell)}`);
397
+ lo.splice(i, 2, ownerLeft);
398
+ ro.splice(i, 2, ownerRight);
399
+ return true;
400
+ }
401
+ function tryStaggerStackBodyBufferLeftMerge(ctx, i, f, ownerLi, _ownerRi) {
402
+ if (!f.stackBodyThenBufferLeft)
403
+ return false;
404
+ const ownerRiNext = ctx.ro[i + 1];
405
+ if (ownerRiNext === undefined)
406
+ return false;
407
+ const { sliceLeft } = ctx.slices;
408
+ return spliceMergedZipPair({
409
+ out: ctx.out,
410
+ lo: ctx.lo,
411
+ ro: ctx.ro,
412
+ i,
413
+ fmt: ctx.fmt,
414
+ leftCell: sliceLeft(f.cur),
415
+ rightCell: sliceLeft(f.nxt),
416
+ ownerLeft: ownerLi,
417
+ ownerRight: ownerRiNext,
418
+ });
419
+ }
420
+ function tryStaggerDiagonalInkMerge(ctx, i, f, ownerLi, ownerRi) {
421
+ const { sliceLeft, sliceRight } = ctx.slices;
422
+ if (f.curHasL && !f.curHasR && !f.nxtHasL && f.nxtHasR) {
423
+ return spliceMergedZipPair({
424
+ out: ctx.out,
425
+ lo: ctx.lo,
426
+ ro: ctx.ro,
427
+ i,
428
+ fmt: ctx.fmt,
429
+ leftCell: sliceLeft(f.cur),
430
+ rightCell: sliceRight(f.nxt),
431
+ ownerLeft: ownerLi,
432
+ ownerRight: ownerRi,
433
+ });
434
+ }
435
+ if (!f.curHasL && f.curHasR && f.nxtHasL && !f.nxtHasR) {
436
+ return spliceMergedZipPair({
437
+ out: ctx.out,
438
+ lo: ctx.lo,
439
+ ro: ctx.ro,
440
+ i,
441
+ fmt: ctx.fmt,
442
+ leftCell: sliceLeft(f.nxt),
443
+ rightCell: sliceRight(f.cur),
444
+ ownerLeft: ownerLi,
445
+ ownerRight: ownerRi,
446
+ });
447
+ }
448
+ return false;
449
+ }
450
+ /** @returns `true` when a merge was applied (caller must not advance `i`). */
451
+ function attemptMergeStaggeredInkZipPairAtIndex(ctx, i) {
452
+ const flags = readStaggerZipInkFlags(ctx, i);
453
+ if (flags === undefined || !flags.relaxOwnerGuard)
454
+ return false;
455
+ const ownerLi = ctx.lo[i];
456
+ const ownerRi = ctx.ro[i];
457
+ if (ownerLi === undefined || ownerRi === undefined)
458
+ return false;
459
+ if (tryStaggerStackBodyBufferLeftMerge(ctx, i, flags, ownerLi, ownerRi))
460
+ return true;
461
+ return tryStaggerDiagonalInkMerge(ctx, i, flags, ownerLi, ownerRi);
462
+ }
463
+ function isStaggerBufferPartnerRow(row, fmt) {
464
+ const L = cellTrim(row, "left", fmt);
465
+ const R = cellTrim(row, "right", fmt);
466
+ return ((L === APPROVAL_BUFFER_FILL && R === APPROVAL_FILLED_ROW) ||
467
+ (L === APPROVAL_FILLED_ROW && R === APPROVAL_BUFFER_FILL));
468
+ }
469
+ function isPackedBodyOneBufferRow(row, fmt) {
470
+ const L = cellTrim(row, "left", fmt);
471
+ const R = cellTrim(row, "right", fmt);
472
+ return ((L === APPROVAL_FILLED_ROW && R === APPROVAL_BUFFER_FILL) ||
473
+ (L === APPROVAL_BUFFER_FILL && R === APPROVAL_FILLED_ROW));
474
+ }
475
+ function indexOfRegionId(items, id) {
476
+ return items.findIndex((x) => x.id === id);
477
+ }
478
+ function hasAnonymousBlockBetween(items, startIdx, endIdx) {
479
+ for (let k = startIdx + 1; k < endIdx; k++) {
480
+ const it = items[k];
481
+ if (it !== undefined && it.id.startsWith("__ANON__"))
482
+ return true;
483
+ }
484
+ return false;
485
+ }
486
+ function anonymousPreviewTripleShapeOk(pr, pack, nxt, fmt) {
487
+ if (pr.length !== fmt.rowDataLen ||
488
+ pack.length !== fmt.rowDataLen ||
489
+ nxt.length !== fmt.rowDataLen) {
490
+ return undefined;
491
+ }
492
+ const pL = cellTrim(pr, "left", fmt);
493
+ const pR = cellTrim(pr, "right", fmt);
494
+ const nL = cellTrim(nxt, "left", fmt);
495
+ const nR = cellTrim(nxt, "right", fmt);
496
+ if (!APPROVAL_REGION_TOKEN_RE.test(pL) || pL !== pR)
497
+ return undefined;
498
+ if (!isPackedBodyOneBufferRow(pack, fmt))
499
+ return undefined;
500
+ if (!APPROVAL_REGION_TOKEN_RE.test(nL) || nL !== nR)
501
+ return undefined;
502
+ if (nL === pL)
503
+ return undefined;
504
+ return { pL, nL };
505
+ }
506
+ function anonymousPreviewRegionIndexWindow(section, pL, nL) {
507
+ const iPrevL = indexOfRegionId(section.left, pL);
508
+ const iNextL = indexOfRegionId(section.left, nL);
509
+ const iPrevR = indexOfRegionId(section.right, pL);
510
+ const iNextR = indexOfRegionId(section.right, nL);
511
+ if (iPrevL < 0 || iNextL < 0 || iNextL <= iPrevL)
512
+ return undefined;
513
+ if (iPrevR < 0 || iNextR < 0 || iNextR <= iPrevR)
514
+ return undefined;
515
+ return { iPrevL, iNextL, iPrevR, iNextR };
516
+ }
517
+ function anonymousPreviewIsEligibleForInsert(rows, section, fmt, i) {
518
+ const pr = rows[i];
519
+ const pack = rows[i + 1];
520
+ const nxt = rows[i + 2];
521
+ if (pr === undefined || pack === undefined || nxt === undefined)
522
+ return false;
523
+ const ids = anonymousPreviewTripleShapeOk(pr, pack, nxt, fmt);
524
+ if (ids === undefined)
525
+ return false;
526
+ const { pL, nL } = ids;
527
+ if (i !== 0 || pL !== "R1XX" || nL !== "R2XX")
528
+ return false;
529
+ const win = anonymousPreviewRegionIndexWindow(section, pL, nL);
530
+ if (win === undefined)
531
+ return false;
532
+ const hasAnonGap = hasAnonymousBlockBetween(section.left, win.iPrevL, win.iNextL) ||
533
+ hasAnonymousBlockBetween(section.right, win.iPrevR, win.iNextR);
534
+ const bodyLine = `${padCell(APPROVAL_FILLED_ROW)}${fmt.columnGap}${padCell(APPROVAL_FILLED_ROW)}`;
535
+ return hasAnonGap && rows[i + 2] !== bodyLine;
536
+ }
537
+ function applyAnonymousPreviewRowSplice(rows, leftOwner, rightOwner, i, fmt) {
538
+ const bodyLine = `${padCell(APPROVAL_FILLED_ROW)}${fmt.columnGap}${padCell(APPROVAL_FILLED_ROW)}`;
539
+ const partial = approvalHumanBreakPartialRow(fmt);
540
+ const loPack = leftOwner[i + 1] ?? leftOwner[i] ?? 0;
541
+ const roPack = rightOwner[i + 1] ?? rightOwner[i] ?? 0;
542
+ rows.splice(i + 2, 0, "", bodyLine, partial);
543
+ leftOwner.splice(i + 2, 0, loPack, loPack, loPack);
544
+ rightOwner.splice(i + 2, 0, roPack, roPack, roPack);
545
+ }
546
+ function tryInsertAnonymousPreviewAtIndex(rows, leftOwner, rightOwner, section, fmt, i) {
547
+ if (!anonymousPreviewIsEligibleForInsert(rows, section, fmt, i))
548
+ return false;
549
+ applyAnonymousPreviewRowSplice(rows, leftOwner, rightOwner, i, fmt);
550
+ return true;
551
+ }
552
+ /**
553
+ * After collapsing R1 stagger into `XXXX BBBB`, anonymous blocks that still sit *between* that region
554
+ * and the next paired `R{N}XX` in the model no longer appear in the zip — re-insert the minimal
555
+ * human-readable preview rows (`two-columns.zig-zag-alternating-sync-needs`).
556
+ */
557
+ function insertAnonymousPreviewBetweenPackedRegionBufferAndNextRegionPair(rows, leftOwner, rightOwner, section, fmt) {
558
+ for (let i = 0; i + 2 < rows.length; i++) {
559
+ if (tryInsertAnonymousPreviewAtIndex(rows, leftOwner, rightOwner, section, fmt, i))
560
+ return;
561
+ }
562
+ }
563
+ function deriveHumanSeamCellFlags(prevRow, nextRow, fmt) {
564
+ const prevL = cellTrim(prevRow, "left", fmt);
565
+ const prevR = cellTrim(prevRow, "right", fmt);
566
+ const nextL = cellTrim(nextRow, "left", fmt);
567
+ const nextR = cellTrim(nextRow, "right", fmt);
568
+ const flags = {
569
+ prevBothBody: prevL === APPROVAL_FILLED_ROW &&
570
+ prevR === APPROVAL_FILLED_ROW &&
571
+ !prevRow.includes(APPROVAL_BUFFER_FILL),
572
+ nextBothBody: nextL === APPROVAL_FILLED_ROW &&
573
+ nextR === APPROVAL_FILLED_ROW &&
574
+ !nextRow.includes(APPROVAL_BUFFER_FILL),
575
+ nextBothRegionHeaders: APPROVAL_REGION_TOKEN_RE.test(nextL) &&
576
+ APPROVAL_REGION_TOKEN_RE.test(nextR) &&
577
+ nextL === nextR,
578
+ prevHasB: prevRow.includes(APPROVAL_BUFFER_FILL),
579
+ nextHasOneB: (nextL === APPROVAL_BUFFER_FILL && nextR !== APPROVAL_BUFFER_FILL) ||
580
+ (nextR === APPROVAL_BUFFER_FILL && nextL !== APPROVAL_BUFFER_FILL),
581
+ };
582
+ return { flags, prevL, prevR, nextL, nextR };
583
+ }
584
+ function pickSeamForBufferRowBeforePairedRegionHeaders(prevRow, fmt, sectionOpensWithRegionMarker) {
585
+ if (isStaggerBufferPartnerRow(prevRow, fmt))
586
+ return SEAM_PUSH_EMPTY_ROW;
587
+ if (sectionOpensWithRegionMarker)
588
+ return "";
589
+ if (fmt.rowDataLen === 9)
590
+ return " ";
591
+ return approvalHumanBreakPartialRow(fmt);
592
+ }
593
+ function pickHumanSeamFromBodyBufferRules(prevRow, sawEmptySeam, sectionOpensWithRegionMarker, fmt, flags) {
594
+ if (flags.prevBothBody && flags.nextHasOneB)
595
+ return "";
596
+ if (flags.prevHasB && flags.nextBothRegionHeaders) {
597
+ return pickSeamForBufferRowBeforePairedRegionHeaders(prevRow, fmt, sectionOpensWithRegionMarker);
598
+ }
599
+ if (flags.prevBothBody && flags.nextBothRegionHeaders) {
600
+ return sawEmptySeam ? approvalHumanBreakPartialRow(fmt) : approvalHumanBreakFullRow(fmt);
601
+ }
602
+ if (flags.prevBothBody && flags.nextBothBody) {
603
+ return sawEmptySeam ? approvalHumanBreakPartialRow(fmt) : "";
604
+ }
605
+ if (flags.prevHasB && flags.nextBothBody) {
606
+ if (fmt.rowDataLen === 9)
607
+ return padCell("");
608
+ return approvalHumanBreakPartialRow(fmt);
609
+ }
610
+ return null;
611
+ }
612
+ function pickHumanSeamBetweenOwners(prevRow, nextRow, sawEmptySeam, sectionOpensWithRegionMarker, fmt) {
613
+ const { flags } = deriveHumanSeamCellFlags(prevRow, nextRow, fmt);
614
+ if (isHumanSeamOrBlankRow(prevRow, fmt) && flags.nextBothRegionHeaders) {
615
+ return "";
616
+ }
617
+ if (isHumanSeamOrBlankRow(prevRow, fmt) && flags.nextBothBody) {
618
+ return "";
619
+ }
620
+ const fromRules = pickHumanSeamFromBodyBufferRules(prevRow, sawEmptySeam, sectionOpensWithRegionMarker, fmt, flags);
621
+ if (fromRules !== null)
622
+ return fromRules;
623
+ if (isStaggerBufferPartnerRow(prevRow, fmt) && isPackedBodyOneBufferRow(nextRow, fmt)) {
624
+ return SEAM_PUSH_EMPTY_ROW;
625
+ }
626
+ if (isHumanSeamOrBlankRow(nextRow, fmt))
627
+ return "";
628
+ return approvalHumanBreakFullRow(fmt);
629
+ }
630
+ function appendBlankAfterPackedInkBufferBeforeDoubleBodyRow(row, nxt, fmt, out) {
631
+ if (fmt.rowDataLen === APPROVAL_ROW_DATA_LEN &&
632
+ row.length === fmt.rowDataLen &&
633
+ row.includes(APPROVAL_BUFFER_FILL) &&
634
+ cellTrim(row, "left", fmt) === APPROVAL_FILLED_ROW &&
635
+ cellTrim(row, "right", fmt) === APPROVAL_BUFFER_FILL &&
636
+ nxt.length === fmt.rowDataLen &&
637
+ cellTrim(nxt, "left", fmt) === APPROVAL_FILLED_ROW &&
638
+ cellTrim(nxt, "right", fmt) === APPROVAL_FILLED_ROW &&
639
+ !nxt.includes(APPROVAL_BUFFER_FILL)) {
640
+ out.push("");
641
+ return true;
642
+ }
643
+ return false;
644
+ }
645
+ function slackContinuesOnStableOwnerSide(leftCh, rightCh, nxt, fmt) {
646
+ if (leftCh === rightCh)
647
+ return false;
648
+ const nextRightT = cellTrim(nxt, "right", fmt);
649
+ const nextLeftT = cellTrim(nxt, "left", fmt);
650
+ return ((leftCh && !rightCh && (nextRightT === APPROVAL_BUFFER_FILL || nextRightT === "")) ||
651
+ (rightCh && !leftCh && (nextLeftT === APPROVAL_BUFFER_FILL || nextLeftT === "")));
652
+ }
653
+ function insertHumanBreaksOnOwnerChange(rows, leftOwner, rightOwner, sectionOpensWithRegionMarker, fmt) {
654
+ const out = [];
655
+ let sawEmptySeam = false;
656
+ for (let r = 0; r < rows.length; r++) {
657
+ const row = rows[r];
658
+ if (row === undefined)
659
+ continue;
660
+ out.push(row);
661
+ const nxt = rows[r + 1];
662
+ if (nxt === undefined)
663
+ continue;
664
+ if (appendBlankAfterPackedInkBufferBeforeDoubleBodyRow(row, nxt, fmt, out)) {
665
+ sawEmptySeam = true;
666
+ continue;
667
+ }
668
+ const leftCh = leftOwner[r] !== leftOwner[r + 1];
669
+ const rightCh = rightOwner[r] !== rightOwner[r + 1];
670
+ if (!leftCh && !rightCh) {
671
+ if (isStaggerBufferPartnerRow(row, fmt) && isPackedBodyOneBufferRow(nxt, fmt)) {
672
+ out.push("");
673
+ sawEmptySeam = true;
674
+ }
675
+ continue;
676
+ }
677
+ if (slackContinuesOnStableOwnerSide(leftCh, rightCh, nxt, fmt))
678
+ continue;
679
+ const seam = pickHumanSeamBetweenOwners(row, nxt, sawEmptySeam, sectionOpensWithRegionMarker, fmt);
680
+ if (seam === SEAM_PUSH_EMPTY_ROW) {
681
+ out.push("");
682
+ sawEmptySeam = true;
683
+ continue;
684
+ }
685
+ if (seam === "")
686
+ sawEmptySeam = true;
687
+ if (seam.length > 0)
688
+ out.push(seam);
689
+ }
690
+ return out;
691
+ }
692
+ function padRegionHeaderAfterPartialSeam(rows, fmt) {
693
+ const out = [...rows];
694
+ const packedAnonBuffer = `${padCell(APPROVAL_FILLED_ROW)}${fmt.columnGap}${padCell(APPROVAL_BUFFER_FILL)}`;
695
+ for (let i = 1; i < out.length; i++) {
696
+ const prev = out[i - 1];
697
+ const cur = out[i];
698
+ if (prev === undefined || cur === undefined)
699
+ continue;
700
+ if (prev !== approvalHumanBreakPartialRow(fmt) || cur.length !== fmt.rowDataLen)
701
+ continue;
702
+ const nxt = out[i + 1];
703
+ if (nxt !== packedAnonBuffer)
704
+ continue;
705
+ const L = cellTrim(cur, "left", fmt);
706
+ const R = cellTrim(cur, "right", fmt);
707
+ if (!APPROVAL_REGION_TOKEN_RE.test(L) || L !== R)
708
+ continue;
709
+ out[i] = `${cur}${" ".repeat(4)}`;
710
+ }
711
+ return out;
712
+ }
713
+ export function approvalHumanBreakFullRow(fmt = APPROVAL_GRID_STANDARD) {
714
+ return `${padCell("")}${fmt.columnGap}${padCell("")}`;
715
+ }
716
+ /** One padded empty row across both cells — visual only; does not add to `HeightAdjustable` scroll totals (standard width). */
717
+ export const APPROVAL_HUMAN_BREAK_ROW = approvalHumanBreakFullRow(APPROVAL_GRID_STANDARD);
718
+ function isHumanSeamOrBlankRow(row, fmt) {
719
+ if (row === approvalHumanBreakFullRow(fmt))
720
+ return true;
721
+ if (row === approvalHumanBreakPartialRow(fmt))
722
+ return true;
723
+ if (row === " ")
724
+ return true;
725
+ if (row === padCell(""))
726
+ return true;
727
+ if (row.trim() === "")
728
+ return true;
729
+ return false;
730
+ }
731
+ /** Zip merge uses a full-width blank row; goldens use a partial seam before the next paired `R{N}XX` line. */
732
+ function coerceFullRowSpacerBeforePairedRegionHeaders(rows, fmt) {
733
+ const full = approvalHumanBreakFullRow(fmt);
734
+ const partial = approvalHumanBreakPartialRow(fmt);
735
+ const out = [...rows];
736
+ for (let i = 1; i + 1 < out.length; i++) {
737
+ const prev = out[i - 1];
738
+ const cur = out[i];
739
+ const nxt = out[i + 1];
740
+ if (prev === undefined || cur === undefined || nxt === undefined)
741
+ continue;
742
+ if (cur !== full)
743
+ continue;
744
+ const { flags } = deriveHumanSeamCellFlags(prev, nxt, fmt);
745
+ if (flags.prevBothBody && flags.nextBothRegionHeaders)
746
+ out[i] = partial;
747
+ }
748
+ return out;
749
+ }
750
+ function lastContentRowIndex(rows, fmt) {
751
+ for (let i = rows.length - 1; i >= 0; i--) {
752
+ const row = rows[i];
753
+ if (row === undefined)
754
+ continue;
755
+ if (isHumanSeamOrBlankRow(row, fmt))
756
+ continue;
757
+ return i;
758
+ }
759
+ return -1;
760
+ }
761
+ /**
762
+ * If the **last story row** of a section is one-sided (only one cell has ink), duplicate that ink
763
+ * into the other cell for human readability only — same scroll line count, not a change to
764
+ * `HeightAdjustable` slack or body semantics in the model.
765
+ */
766
+ function coalesceTerminalOneSidedRowForHumanInk(rows, fmt) {
767
+ const lastIdx = lastContentRowIndex(rows, fmt);
768
+ if (lastIdx < 0)
769
+ return rows;
770
+ const last = rows[lastIdx];
771
+ if (last?.length !== fmt.rowDataLen)
772
+ return rows;
773
+ const gapLen = fmt.columnGap.length;
774
+ const left = last.slice(0, APPROVAL_CELL_WIDTH).trim();
775
+ const right = last.slice(APPROVAL_CELL_WIDTH + gapLen, fmt.rowDataLen).trim();
776
+ const leftInk = left.length > 0;
777
+ const rightInk = right.length > 0;
778
+ if (leftInk && rightInk)
779
+ return rows;
780
+ if (!leftInk && !rightInk)
781
+ return rows;
782
+ if (left === APPROVAL_BUFFER_FILL || right === APPROVAL_BUFFER_FILL) {
783
+ return rows;
784
+ }
785
+ if (left === APPROVAL_FILLED_ROW || right === APPROVAL_FILLED_ROW) {
786
+ const out = [...rows];
787
+ out[lastIdx] = `${padCell(APPROVAL_FILLED_ROW)}${fmt.columnGap}${padCell(APPROVAL_FILLED_ROW)}`;
788
+ return out;
789
+ }
790
+ if (APPROVAL_REGION_TOKEN_RE.test(left) || APPROVAL_REGION_TOKEN_RE.test(right)) {
791
+ const tok = APPROVAL_REGION_TOKEN_RE.test(left) ? left : right;
792
+ const out = [...rows];
793
+ out[lastIdx] = `${padCell(tok)}${fmt.columnGap}${padCell(tok)}`;
794
+ return out;
795
+ }
796
+ return rows;
797
+ }
798
+ /**
799
+ * Never show `BBBB` in **both** cells on one ASCII line (duplicate slack on one zip row). Split into
800
+ * stagger; vertical runs of `BBBB` in a single column remain valid when the model needs many buffer lines.
801
+ */
802
+ function splitSymmetricFullBufferSlackRowsToStagger(rows, fmt) {
803
+ const sym = symmetricFullBufferRow(fmt);
804
+ const out = [];
805
+ for (const row of rows) {
806
+ if (row === sym) {
807
+ out.push(`${padCell(APPROVAL_BUFFER_FILL)}${fmt.columnGap}${padCell("")}`);
808
+ out.push(`${padCell("")}${fmt.columnGap}${padCell(APPROVAL_BUFFER_FILL)}`);
809
+ }
810
+ else if (row !== undefined) {
811
+ out.push(row);
812
+ }
813
+ }
814
+ return out;
815
+ }
816
+ function sectionOpensWithRegionMarker(section) {
817
+ const l0 = section.left[0];
818
+ const r0 = section.right[0];
819
+ return ((l0 !== undefined && APPROVAL_REGION_TOKEN_RE.test(l0.id)) ||
820
+ (r0 !== undefined && APPROVAL_REGION_TOKEN_RE.test(r0.id)));
821
+ }
822
+ /** Printer: one synchronized section → fixed-width grid rows (with human-readable blank after each `HeightAdjustable` seam). */
823
+ export function printApprovalFlowSection(section, format = APPROVAL_GRID_STANDARD) {
824
+ const left = flattenColumnWithOwners(section.left);
825
+ const right = flattenColumnWithOwners(section.right);
826
+ if (left.tokens.length !== right.tokens.length) {
827
+ throw new Error(`Synchronized section columns must have equal scroll depth (left ${String(left.tokens.length)} vs right ${String(right.tokens.length)} zip lines).`);
828
+ }
829
+ const owners = zipOwners(left.ownerIdx, right.ownerIdx);
830
+ const merged = mergeStaggeredInkAndBufferZipRows(zipTwoColumns(left.tokens, right.tokens, format), owners.left, owners.right, format);
831
+ insertAnonymousPreviewBetweenPackedRegionBufferAndNextRegionPair(merged.rows, merged.leftOwner, merged.rightOwner, section, format);
832
+ const withHumanSeams = insertHumanBreaksOnOwnerChange(merged.rows, merged.leftOwner, merged.rightOwner, sectionOpensWithRegionMarker(section), format);
833
+ const coercedSpacers = coerceFullRowSpacerBeforePairedRegionHeaders(withHumanSeams, format);
834
+ const paddedHeaders = padRegionHeaderAfterPartialSeam(coercedSpacers, format);
835
+ const withTerminalCoalesced = coalesceTerminalOneSidedRowForHumanInk(paddedHeaders, format);
836
+ return splitSymmetricFullBufferSlackRowsToStagger(withTerminalCoalesced, format);
837
+ }
838
+ function lastGridRowIsPackedInkBuffer(rows, fmt) {
839
+ for (let j = rows.length - 1; j >= 0; j--) {
840
+ const row = rows[j];
841
+ if (row === undefined || row.trim() === "")
842
+ continue;
843
+ return (row === `${padCell(APPROVAL_FILLED_ROW)}${fmt.columnGap}${padCell(APPROVAL_BUFFER_FILL)}`);
844
+ }
845
+ return false;
846
+ }
847
+ /** Printer: many sections (blank row between) → full grid text. */
848
+ export function printApprovalSynchronizedFlow(sections, format = APPROVAL_GRID_STANDARD) {
849
+ const out = [];
850
+ for (let si = 0; si < sections.length; si++) {
851
+ const sec = sections[si];
852
+ if (sec.left.length === 0 && sec.right.length === 0)
853
+ continue;
854
+ out.push(...printApprovalFlowSection(sec, format));
855
+ if (si < sections.length - 1) {
856
+ out.push(approvalHumanBreakFullRow(format));
857
+ }
858
+ }
859
+ if (lastGridRowIsPackedInkBuffer(out, format)) {
860
+ out.splice(out.length, 0, "", "");
861
+ }
862
+ return out.join("\n");
863
+ }
864
+ //# sourceMappingURL=buffering-flow-synchronizer-approval-printer.js.map