diff --git a/kits/sheets/app/package-lock.json b/kits/sheets/app/package-lock.json
index 0f44869..c47836e 100644
--- a/kits/sheets/app/package-lock.json
+++ b/kits/sheets/app/package-lock.json
@@ -13,7 +13,7 @@
"react-dom": "^18.3.1",
"react-file-icon": "^1.6.0",
"react-markdown": "^9.0.1",
- "reifyui": "^0.8.3",
+ "reifyui": "^0.8.4",
"remark-gfm": "^4.0.0",
"xlsx": "^0.18.5"
},
@@ -2957,9 +2957,9 @@
}
},
"node_modules/reifyui": {
- "version": "0.8.3",
- "resolved": "https://registry.npmjs.org/reifyui/-/reifyui-0.8.3.tgz",
- "integrity": "sha512-oWsptZh6v4SMv9UnnsprsWPDcBYG2GDIaR694xRZchoK+qpXy9ZY0ZI7vw+RwdS7vlP0GIpHmMqmyn6fmPdH7A==",
+ "version": "0.8.4",
+ "resolved": "https://registry.npmjs.org/reifyui/-/reifyui-0.8.4.tgz",
+ "integrity": "sha512-Vxf3C3unmXo41sy2oPMBT3JZ0CNBUHaA61/BjdTDJ9gewwSwti8SHVivtXcwCBCzGuWsLqxfsPAUo3H4oZbphQ==",
"license": "MIT",
"engines": {
"node": ">=18"
diff --git a/kits/sheets/app/package.json b/kits/sheets/app/package.json
index e655a01..74c01ac 100644
--- a/kits/sheets/app/package.json
+++ b/kits/sheets/app/package.json
@@ -15,7 +15,7 @@
"react-dom": "^18.3.1",
"react-file-icon": "^1.6.0",
"react-markdown": "^9.0.1",
- "reifyui": "^0.8.3",
+ "reifyui": "^0.8.4",
"remark-gfm": "^4.0.0",
"xlsx": "^0.18.5"
},
diff --git a/kits/sheets/app/src/components/HarnessCell.jsx b/kits/sheets/app/src/components/HarnessCell.jsx
index 36c9a9e..be3451c 100644
--- a/kits/sheets/app/src/components/HarnessCell.jsx
+++ b/kits/sheets/app/src/components/HarnessCell.jsx
@@ -1,7 +1,8 @@
// What an agent cell looks like in the grid.
//
-// Mounted through SheetGrid's renderCell slot, inside the cell's own value box, so the row-height
-// line clamp and the cell chrome still apply.
+// Mounted through SheetGrid's renderCell slot, inside the cell's own value box, so the cell chrome
+// still applies and the row fits this cell the way it fits any other: the answer wraps, the row
+// grows to it up to the grid's cap, and past the cap the cell scrolls.
//
// Every state here is a state the run genuinely produces. There is no percentage and no estimate:
// the server exposes no queue — past its concurrency limit turns are accepted and block silently,
@@ -81,7 +82,7 @@ export function HarnessCell({ cell, live, readOnly, onRun, onPreviewFile }) {
return (
- {cell.error || 'This cell failed.'}
+ {cell.error || 'This cell failed.'}
{tools}
);
@@ -89,14 +90,14 @@ export function HarnessCell({ cell, live, readOnly, onRun, onPreviewFile }) {
if (status === 'skipped') {
return (
- {cell.error || 'Skipped.'}
+ {cell.error || 'Skipped.'}
{tools}
);
}
return (
-
+
{tools}
diff --git a/kits/sheets/app/src/pages/SheetPage.jsx b/kits/sheets/app/src/pages/SheetPage.jsx
index 714e91a..bbf2f1c 100644
--- a/kits/sheets/app/src/pages/SheetPage.jsx
+++ b/kits/sheets/app/src/pages/SheetPage.jsx
@@ -8,12 +8,12 @@
// The run happens in this tab. There is no workflow engine and no batch endpoint in this
// deployment, so the browser is the orchestrator; the UI says that before you press Run and says
// exactly what happened if you leave.
-import { useCallback, useEffect, useLayoutEffect, useMemo, useRef, useState } from 'react';
+import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
import ReactMarkdown from 'react-markdown';
import remarkGfm from 'remark-gfm';
import { Download, HelpCircle, Home } from 'lucide-react';
import { PaneResizer, useResizablePane, useDialog } from 'reifyui';
-import { SheetGrid, FilePreview, fitRowHeights } from 'reifyui';
+import { SheetGrid, FilePreview } from 'reifyui';
import { containerFileUrl, getResponse, lastAssistantText, sessionTurns } from 'reifyui/harness';
import {
defaultAgentId, getSheet, isPending, markViewed, renameSheet, runnableHarnesses, saveSheet,
@@ -36,10 +36,6 @@ const SAVE_DEBOUNCE_MS = 400;
const STATUS_POLL_MS = 2000; // while a turn is live: the grid fills in as it is written
const STATUS_IDLE_MS = 10000; // while nothing is: still notices a turn started elsewhere
const RELOAD_POLL_MS = 4000;
-// The line height and vertical padding the grid lays a cell out with (reifyui's sheet.css sets
-// line-height: 18px). They turn a measured height into a number of lines for --shg-clamp.
-const CLAMP_LINE = 18;
-const CLAMP_PAD = 10;
const now = () => Math.floor(Date.now() / 1000);
const escapeRe = (s) => String(s).replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
@@ -106,7 +102,6 @@ export function SheetPage({ id: routeId, seed }) {
idRef.current = id;
const runnerRef = useRef(null);
const exportRef = useRef(null);
- const gridRef = useRef(null);
const mirrored = useRef(''); // the session title we last asserted
const save = useRef({ timer: null, inFlight: false, backoff: 0, wanted: null });
@@ -385,14 +380,10 @@ export function SheetPage({ id: routeId, seed }) {
runnerRef.current = null;
setRunning(false);
- // The answers are on screen now, so their real height is measurable. Rows the run touched
- // grow to fit what is in them — the text and the file cards — rather than staying at the
- // height they had when they were empty.
- const touched = [...new Set(p.cells.map((c) => c.rowId))];
- const heights = fitRowHeights(gridRef.current, { rowIds: touched });
-
+ // No row heights are written here. The grid fits every row to its tallest cell on each
+ // render, so an answer has its room the moment it lands; a height stored after the run
+ // would pin the row and stop it refitting when its column is resized or its text edited.
const done = JSON.parse(JSON.stringify(sheetRef.current));
- for (const r of done.rows || []) if (heights[r.id]) r.height = heights[r.id];
if (done.run?.id === rid) {
done.run = {
...done.run,
@@ -404,53 +395,6 @@ export function SheetPage({ id: routeId, seed }) {
}
}, [env, concurrency, commit, dialog]);
- // ── how many lines of text a cell has room for ────────────────────────────
- // The grid derives --shg-clamp from a row's STORED height, falling back to 34px — one line —
- // for any row never explicitly sized. But a row is usually tall because of ONE column: an agent
- // cell keeping its files under its answer. Every other cell then showed a single ellipsised
- // line above a block of empty row, and the taller the neighbour, the more space went to waste.
- //
- // Three things have to be true at once, and each is a trap on its own:
- //
- // The basis cannot be the RENDERED height. More lines makes the row taller, which allows more
- // lines: measured live the rows climb 92 → 137 → 182 and never settle. So every clamp goes
- // back to one line before measuring, making the basis the height the row's OTHER content
- // needs — a quantity the clamp cannot influence.
- //
- // The basis cannot be the
. A table cell is stretched to its row and reports the row
- // height for every column, which makes them all look equally full. The measurement is taken
- // on the text's own box, which sizes to its content.
- //
- // The count cannot be shared by the row. A cell carrying files under its text has far less
- // room than one carrying nothing, and one number for both is the bug restated.
- //
- // Set on each | : a custom property resolves from the nearest ancestor, so this wins over the
- // row-level value the grid sets, for both this kit's answer text and the grid's own value cell.
- useLayoutEffect(() => {
- const trs = gridRef.current?.querySelectorAll('tbody tr[data-row-id]');
- if (!trs?.length) return;
- const rows = [...trs];
- for (const tr of rows) for (const td of tr.children) td.style.setProperty('--shg-clamp', '1');
- const plan = rows.map((tr) => ({
- base: tr.offsetHeight, // one reflow, every clamp at its floor
- cells: [...tr.children].map((td) => {
- const txt = td.querySelector('[data-shg-clamp-text]');
- const box = txt?.parentElement;
- return { td, extra: box ? Math.max(0, box.scrollHeight - txt.offsetHeight) : 0 };
- }),
- }));
- for (const { base, cells } of plan) {
- const floorLines = Math.max(1, Math.floor((base - CLAMP_PAD) / CLAMP_LINE));
- // The height the row needs for the cell carrying the most beside its text to still get the
- // row's baseline count. Every other cell then fills THAT, which is the whole point.
- const need = Math.max(...cells.map((c) => c.extra)) + floorLines * CLAMP_LINE + CLAMP_PAD;
- for (const { td, extra } of cells) {
- td.style.setProperty('--shg-clamp', String(Math.max(floorLines,
- Math.floor((need - extra - CLAMP_PAD) / CLAMP_LINE))));
- }
- }
- });
-
const stopRun = useCallback(() => { runnerRef.current?.stop(); }, []);
// Leaving mid-run stops the walk. Say so with the platform's own guard rather than inventing a
@@ -650,7 +594,7 @@ export function SheetPage({ id: routeId, seed }) {
)}
) : (
- |