mindweave 3.1.0 → 3.2.1
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.
- package/README.md +1 -1
- package/dist/cli/App.js +121 -32
- package/dist/cli/App.js.map +1 -1
- package/dist/cli/blockSpacing.js +4 -0
- package/dist/cli/blockSpacing.js.map +1 -1
- package/dist/cli/components/BlockView.js +6 -6
- package/dist/cli/components/BlockView.js.map +1 -1
- package/dist/cli/components/CommandGroup.js +100 -0
- package/dist/cli/components/CommandGroup.js.map +1 -0
- package/dist/cli/components/TipLine.js +1 -0
- package/dist/cli/components/TipLine.js.map +1 -1
- package/dist/cli/components/ToolLine.js +69 -14
- package/dist/cli/components/ToolLine.js.map +1 -1
- package/dist/cli/components/WorkGroup.js +121 -0
- package/dist/cli/components/WorkGroup.js.map +1 -0
- package/dist/cli/diffWrap.js +24 -0
- package/dist/cli/diffWrap.js.map +1 -0
- package/dist/cli/expandHits.js +74 -0
- package/dist/cli/expandHits.js.map +1 -0
- package/dist/cli/help.js +13 -1
- package/dist/cli/help.js.map +1 -1
- package/dist/cli/mouse.js +16 -8
- package/dist/cli/mouse.js.map +1 -1
- package/dist/cli/scrollGlide.js +72 -0
- package/dist/cli/scrollGlide.js.map +1 -0
- package/dist/cli/scrollPill.js +21 -1
- package/dist/cli/scrollPill.js.map +1 -1
- package/dist/cli/selfUpdate.js +7 -0
- package/dist/cli/selfUpdate.js.map +1 -1
- package/dist/cli/shellNotes.js +21 -0
- package/dist/cli/shellNotes.js.map +1 -0
- package/dist/cli/terminalRestore.js +1 -1
- package/dist/cli/terminalRestore.js.map +1 -1
- package/dist/cli/toolDisplay.js +14 -10
- package/dist/cli/toolDisplay.js.map +1 -1
- package/dist/cli/transcript.js +100 -29
- package/dist/cli/transcript.js.map +1 -1
- package/dist/cli/uiLines.js +60 -0
- package/dist/cli/uiLines.js.map +1 -0
- package/dist/cli/wheelStep.js +29 -0
- package/dist/cli/wheelStep.js.map +1 -0
- package/dist/cli/workLines.js +84 -0
- package/dist/cli/workLines.js.map +1 -0
- package/dist/core/turnRunner.js +8 -6
- package/dist/core/turnRunner.js.map +1 -1
- package/dist/drivers/anthropic/manifest.js +23 -1
- package/dist/drivers/anthropic/manifest.js.map +1 -1
- package/dist/dynamo/engine.js +60 -32
- package/dist/dynamo/engine.js.map +1 -1
- package/dist/memory/compaction.js +24 -38
- package/dist/memory/compaction.js.map +1 -1
- package/dist/memory/earlier.js +13 -0
- package/dist/memory/earlier.js.map +1 -1
- package/dist/memory/session.js +7 -0
- package/dist/memory/session.js.map +1 -1
- package/dist/memory/sessionMemory.js +202 -72
- package/dist/memory/sessionMemory.js.map +1 -1
- package/dist/memory/store.js +11 -0
- package/dist/memory/store.js.map +1 -1
- package/dist/memory/types.js.map +1 -1
- package/dist/tools/detail.js +113 -1
- package/dist/tools/detail.js.map +1 -1
- package/dist/tools/readFile.js +9 -1
- package/dist/tools/readFile.js.map +1 -1
- package/dist/tools/rebuildDetail.js +155 -0
- package/dist/tools/rebuildDetail.js.map +1 -0
- package/dist/tools/runCommand.js +3 -3
- package/dist/tools/runCommand.js.map +1 -1
- package/dist/tools/sessionTools.js +24 -1
- package/dist/tools/sessionTools.js.map +1 -1
- package/dist/tools/ui.js +54 -1
- package/dist/tools/ui.js.map +1 -1
- package/package.json +3 -3
package/README.md
CHANGED
|
@@ -50,7 +50,7 @@ npm link
|
|
|
50
50
|
|
|
51
51
|
## What it does
|
|
52
52
|
|
|
53
|
-
- **16 providers,
|
|
53
|
+
- **16 providers, 63 models** — DeepSeek, Anthropic, OpenAI, Gemini, xAI, Mistral, Groq, Cerebras,
|
|
54
54
|
Qwen, Kimi, GLM, Meta, MiniMax, Tencent, OpenRouter, and local models through Ollama
|
|
55
55
|
with no key at all. [PROVIDERS.md](src/drivers/PROVIDERS.md)
|
|
56
56
|
- **Real tools** — file reads and edits, search, a shell with background jobs,
|
package/dist/cli/App.js
CHANGED
|
@@ -70,12 +70,17 @@ import { PromptInput } from "./components/PromptInput.js";
|
|
|
70
70
|
import { Picker } from "./components/Picker.js";
|
|
71
71
|
import { ApprovalBox } from "./components/ApprovalBox.js";
|
|
72
72
|
import { BlockView } from "./components/BlockView.js";
|
|
73
|
-
import { initialState, reduce, trimNarration } from "./transcript.js";
|
|
73
|
+
import { initialState, lastExpandable, reduce, trimNarration } from "./transcript.js";
|
|
74
74
|
import { isTight } from "./blockSpacing.js";
|
|
75
75
|
import { parseScreenArg, screenChoices, screenNotice, startupMode } from "./screenMode.js";
|
|
76
76
|
import { applyScreenMode } from "./screenShell.js";
|
|
77
77
|
import { saveScreenMode } from "./screenStore.js";
|
|
78
78
|
import { needsMeasure, pruneHeights } from "./blockHeights.js";
|
|
79
|
+
import { expandableAt, hitBlock, hitItem } from "./expandHits.js";
|
|
80
|
+
import { shellNote } from "./shellNotes.js";
|
|
81
|
+
import { readFileSync, statSync } from "node:fs";
|
|
82
|
+
import { compactUiDetail, rebuildFull, writtenFromDisk } from "../tools/rebuildDetail.js";
|
|
83
|
+
import { shownText } from "../memory/compaction.js";
|
|
79
84
|
import { BASE_COMMANDS } from "./commands.js";
|
|
80
85
|
import { attachmentBudget, dismissMarathon } from "../core/turnRunner.js";
|
|
81
86
|
import { describeMarathonEvent, resumeMarathon, startMarathon } from "../dynamo/marathon.js";
|
|
@@ -84,7 +89,7 @@ import { isFinished, isLive, marathonUiReduce } from "./marathonUi.js";
|
|
|
84
89
|
import { manualCommand, refusalReason } from "./selfUpdate.js";
|
|
85
90
|
import { currentInstall, requestRestart, runUpdate } from "./updateRunner.js";
|
|
86
91
|
import { enableMouse, readMouse, readWheel } from "./mouse.js";
|
|
87
|
-
import { wheelLines
|
|
92
|
+
import { wheelLines } from "./wheelStep.js";
|
|
88
93
|
import { applySelection, ctrlCShouldCopy, isEmpty, selectionText } from "./selection.js";
|
|
89
94
|
import { latestScreen, repaintOverlay, setFrameOverlay } from "./framebuffer/overlay.js";
|
|
90
95
|
import { requestFullRepaint } from "./framebuffer/writer.js";
|
|
@@ -322,6 +327,14 @@ export function App({ resumeSessionId, initialScreen }) {
|
|
|
322
327
|
/** The chip's cells in SCREEN coordinates, for the pointer handler. Null when there is
|
|
323
328
|
* nothing to click. Published by the render, read between frames. */
|
|
324
329
|
const pillHit = useRef(null);
|
|
330
|
+
// Where the layout's first row sits on the screen, published each render for the pointer.
|
|
331
|
+
const frameTopRef = useRef(0);
|
|
332
|
+
// The row the pointer is over, if it can be pressed. State rather than a ref: it is what
|
|
333
|
+
// the row draws from. Only set when it CHANGES, so a pointer crossing the screen is not
|
|
334
|
+
// a redraw per cell.
|
|
335
|
+
const [hoverId, setHoverId] = useState(null);
|
|
336
|
+
// Set when a row is opened or folded, read once by the height effect.
|
|
337
|
+
const rowToggled = useRef(false);
|
|
325
338
|
const [footerHeight, setFooterHeight] = useState(0);
|
|
326
339
|
// The chat viewport's REAL height. Yoga decides it now (flexGrow beside a
|
|
327
340
|
// flexShrink:0 footer); this is read back purely so the scroll maths knows how
|
|
@@ -616,6 +629,9 @@ export function App({ resumeSessionId, initialScreen }) {
|
|
|
616
629
|
const withEarlier = (session) => [...(session.earlier ?? []), ...session.transcript];
|
|
617
630
|
function showResumed(transcript) {
|
|
618
631
|
const readThisTurn = new Set(); // for narrationShown, as live
|
|
632
|
+
// Each call's name and arguments by id, so a result from a session saved before the uncut
|
|
633
|
+
// text was kept can have it put back together (see rebuildDetail.ts).
|
|
634
|
+
const callsById = new Map();
|
|
619
635
|
for (const e of transcript) {
|
|
620
636
|
if (e.role === "user") {
|
|
621
637
|
if (!e.synthetic)
|
|
@@ -638,11 +654,13 @@ export function App({ resumeSessionId, initialScreen }) {
|
|
|
638
654
|
const calls = (e.toolCalls ?? []).map((c) => ({ name: c.name, args: parseToolArgs(c.arguments) }));
|
|
639
655
|
const shown = calls.length === 0 || narrationShown(calls, readThisTurn);
|
|
640
656
|
noteReads(calls, readThisTurn);
|
|
641
|
-
|
|
657
|
+
const said = shownText(e);
|
|
658
|
+
if (shown && said && said.trim()) {
|
|
642
659
|
const intermediate = calls.length > 0;
|
|
643
|
-
dispatch({ type: "say", text: intermediate ? trimNarration(
|
|
660
|
+
dispatch({ type: "say", text: intermediate ? trimNarration(said) : said });
|
|
644
661
|
}
|
|
645
662
|
for (const call of e.toolCalls ?? []) {
|
|
663
|
+
callsById.set(call.id, { name: call.name, args: parseToolArgs(call.arguments) });
|
|
646
664
|
// The spawn itself is drawn by its sub-agent block live, never as a raw row.
|
|
647
665
|
// Replaying it as one puts a `● SpawnSubagent(...)` in a resumed transcript
|
|
648
666
|
// that was not in the live one.
|
|
@@ -669,6 +687,28 @@ export function App({ resumeSessionId, initialScreen }) {
|
|
|
669
687
|
ok: e.isError === undefined ? !e.content.startsWith("Error:") : !e.isError,
|
|
670
688
|
summary: e.summary,
|
|
671
689
|
detail: e.detail,
|
|
690
|
+
...(() => {
|
|
691
|
+
const call = callsById.get(e.toolCallId);
|
|
692
|
+
// A UI test saved before it became a short row comes back as the short row.
|
|
693
|
+
if (call?.name === "ui" && !e.detailFull) {
|
|
694
|
+
const compact = compactUiDetail(e.detail, e.isError);
|
|
695
|
+
if (compact)
|
|
696
|
+
return { detail: compact.detail, ...(compact.detailFull ? { detailFull: compact.detailFull } : {}) };
|
|
697
|
+
}
|
|
698
|
+
let full = e.detailFull ?? rebuildFull(call?.name, call?.args, e);
|
|
699
|
+
// A write whose text was cleared to save context has only the file left to open.
|
|
700
|
+
if (!full && call?.name === "write_file" && typeof call.args?.path === "string") {
|
|
701
|
+
try {
|
|
702
|
+
const abs = resolve(session.current?.cwd ?? process.cwd(), call.args.path);
|
|
703
|
+
if (statSync(abs).size < 2_000_000)
|
|
704
|
+
full = writtenFromDisk(e.detail, readFileSync(abs, "utf8"));
|
|
705
|
+
}
|
|
706
|
+
catch {
|
|
707
|
+
// gone or unreadable: the row stays as it was saved
|
|
708
|
+
}
|
|
709
|
+
}
|
|
710
|
+
return full ? { detailFull: full } : {};
|
|
711
|
+
})(),
|
|
672
712
|
...(e.detailKind ? { detailKind: e.detailKind } : {}),
|
|
673
713
|
...(e.quiet ? { quiet: true } : {}),
|
|
674
714
|
...(e.displayName ? { name: e.displayName } : {}),
|
|
@@ -701,24 +741,10 @@ export function App({ resumeSessionId, initialScreen }) {
|
|
|
701
741
|
setBgTick((t) => t + 1);
|
|
702
742
|
const mgr = session.current?.toolContext.backgroundShells;
|
|
703
743
|
for (const { info: sh, kind } of mgr?.takeUiEvents() ?? []) {
|
|
704
|
-
//
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
if (kind === "ready")
|
|
709
|
-
continue;
|
|
710
|
-
// The watchdog flagged a running shell as stuck — a prompt it is blocked on, or a
|
|
711
|
-
// long silence on a command that should be working. Worth a line: otherwise it sits
|
|
712
|
-
// invisible until it times out.
|
|
713
|
-
if (kind === "stalled") {
|
|
714
|
-
const why = sh.stallReason === "prompt" ? "waiting for input?" : "no output for a while — stuck?";
|
|
715
|
-
addTool(`shell #${sh.id} (${clipCmd(sh.command)}) ${why}`, { error: true });
|
|
716
|
-
continue;
|
|
717
|
-
}
|
|
718
|
-
if (sh.status === "killed" && sh.stoppedBy === "user")
|
|
719
|
-
continue;
|
|
720
|
-
const verb = sh.status === "killed" ? "killed" : `finished — exit ${sh.exitCode}`;
|
|
721
|
-
addTool(`shell #${sh.id} (${clipCmd(sh.command)}) ${verb}`, { error: sh.status !== "killed" && sh.exitCode !== 0 });
|
|
744
|
+
// Only what nothing else on screen says: see shellNotes.
|
|
745
|
+
const line = shellNote(sh, kind, clipCmd);
|
|
746
|
+
if (line)
|
|
747
|
+
addTool(line.text, { error: line.error });
|
|
722
748
|
}
|
|
723
749
|
}
|
|
724
750
|
// A server's state moved. Repaint, and surface anything the pool needs the user to
|
|
@@ -1086,8 +1112,6 @@ export function App({ resumeSessionId, initialScreen }) {
|
|
|
1086
1112
|
// counter kept climbing — and every one of those phantom lines then had to be
|
|
1087
1113
|
// scrolled back down before the view moved at all. A flick or two past the top bought
|
|
1088
1114
|
// a second of a wheel that did nothing, which reads as the app having frozen.
|
|
1089
|
-
/** The wheel gesture in progress, for scroll acceleration (wheelAccel.ts). */
|
|
1090
|
-
const wheelRef = useRef(wheelStart());
|
|
1091
1115
|
const scrollBy = useCallback((lines) => {
|
|
1092
1116
|
// Repaint the whole next frame. A scroll can move a row out from under the
|
|
1093
1117
|
// framebuffer's model (see requestFullRepaint), which is what left a transcript row
|
|
@@ -1150,6 +1174,17 @@ export function App({ resumeSessionId, initialScreen }) {
|
|
|
1150
1174
|
requestFullRepaint();
|
|
1151
1175
|
setScrollUp(0);
|
|
1152
1176
|
}
|
|
1177
|
+
// The keyboard way to the same thing a click does, for terminals that do not send
|
|
1178
|
+
// the mouse. It acts on the newest row that has more to show.
|
|
1179
|
+
else if (key.ctrl && _input === "o") {
|
|
1180
|
+
if (shell === "inline" && !reading) {
|
|
1181
|
+
note("Scroll up first (PageUp or the wheel), then press ctrl+O to open the newest long output.");
|
|
1182
|
+
return;
|
|
1183
|
+
}
|
|
1184
|
+
const target = lastExpandable(stateRef.current);
|
|
1185
|
+
if (target !== null)
|
|
1186
|
+
toggleRow(target);
|
|
1187
|
+
}
|
|
1153
1188
|
else if (key.home && key.ctrl) {
|
|
1154
1189
|
requestFullRepaint();
|
|
1155
1190
|
setScrollUp(maxScrollRef.current);
|
|
@@ -1256,7 +1291,18 @@ export function App({ resumeSessionId, initialScreen }) {
|
|
|
1256
1291
|
// Ordinary growth, not a resize — see growScroll for why this has to hold the
|
|
1257
1292
|
// reader's absolute position rather than leave `scrollUp` where it was.
|
|
1258
1293
|
const prev = prevContentHeight.current;
|
|
1259
|
-
|
|
1294
|
+
let next = growScroll(scrollUp, prev, height);
|
|
1295
|
+
// A row was just opened or folded. The row the reader pressed stays where it is and
|
|
1296
|
+
// the change happens below it: opening from the bottom would otherwise follow the
|
|
1297
|
+
// new end of the output and carry the row's own first line off the top, and folding
|
|
1298
|
+
// would slide the view up by what was taken away.
|
|
1299
|
+
if (rowToggled.current && height !== prev) {
|
|
1300
|
+
if (height > prev && scrollUp === 0)
|
|
1301
|
+
next = height - prev;
|
|
1302
|
+
else if (height < prev)
|
|
1303
|
+
next = Math.max(0, scrollUp - (prev - height));
|
|
1304
|
+
}
|
|
1305
|
+
rowToggled.current = false;
|
|
1260
1306
|
if (next !== scrollUp)
|
|
1261
1307
|
setScrollUp(next);
|
|
1262
1308
|
}
|
|
@@ -1412,12 +1458,11 @@ export function App({ resumeSessionId, initialScreen }) {
|
|
|
1412
1458
|
// Content moves out from under a selection when the view scrolls, so the
|
|
1413
1459
|
// highlight would be sitting on text that is no longer the text it copied.
|
|
1414
1460
|
clearSelection();
|
|
1415
|
-
// Signed notch count, then the distance for it
|
|
1416
|
-
//
|
|
1461
|
+
// Signed notch count, then the distance for it. The same distance for every notch,
|
|
1462
|
+
// applied at once (wheelStep.ts): how far the view goes follows how far the wheel
|
|
1463
|
+
// was turned, and nothing else.
|
|
1417
1464
|
const net = notches.reduce((n, dir) => n + (dir === "up" ? 1 : -1), 0);
|
|
1418
|
-
const
|
|
1419
|
-
wheelRef.current = step.state;
|
|
1420
|
-
const lines = step.lines;
|
|
1465
|
+
const lines = wheelLines(net);
|
|
1421
1466
|
// The wheel is how anyone actually scrolls, so in the inline shell it is what
|
|
1422
1467
|
// opens the reading view. Turning it UP is the gesture: the reader is going back
|
|
1423
1468
|
// through the conversation and wants the prompt to stay where they can type into
|
|
@@ -1437,7 +1482,13 @@ export function App({ resumeSessionId, initialScreen }) {
|
|
|
1437
1482
|
// in a chunk is on screen at the end of it, so the ones before it are painted for
|
|
1438
1483
|
// nobody. Cleared by press and release, which do their own painting.
|
|
1439
1484
|
let pendingDrag = false;
|
|
1485
|
+
let lastMove = null;
|
|
1440
1486
|
for (const event of events) {
|
|
1487
|
+
if (event.kind === "move") {
|
|
1488
|
+
// Only the last position in a chunk matters; a burst is one lookup.
|
|
1489
|
+
lastMove = event;
|
|
1490
|
+
continue;
|
|
1491
|
+
}
|
|
1441
1492
|
if (event.kind === "press") {
|
|
1442
1493
|
pendingDrag = false;
|
|
1443
1494
|
// The chip is a button. It is the only thing on screen that says what it does,
|
|
@@ -1478,6 +1529,14 @@ export function App({ resumeSessionId, initialScreen }) {
|
|
|
1478
1529
|
selection.current = null;
|
|
1479
1530
|
repaintOverlay();
|
|
1480
1531
|
setFrameOverlay(null);
|
|
1532
|
+
// A click on a row that has more to show opens it, and a second click folds
|
|
1533
|
+
// it. Tested BEFORE the caret, because a click on the transcript is never on
|
|
1534
|
+
// the input anyway, and a click that does nothing is the worse outcome.
|
|
1535
|
+
const hitId = expandableAt(event.y, frameTopRef.current, chatRef.current);
|
|
1536
|
+
if (hitId !== null) {
|
|
1537
|
+
toggleRow(hitId);
|
|
1538
|
+
continue;
|
|
1539
|
+
}
|
|
1481
1540
|
placeCaretAt(event.x, event.y);
|
|
1482
1541
|
continue;
|
|
1483
1542
|
}
|
|
@@ -1495,11 +1554,30 @@ export function App({ resumeSessionId, initialScreen }) {
|
|
|
1495
1554
|
}
|
|
1496
1555
|
if (pendingDrag)
|
|
1497
1556
|
repaintOverlay();
|
|
1557
|
+
// The pointer, resting or moving: light the row under it if it can be pressed, and put
|
|
1558
|
+
// the light out when it leaves. A press or a wheel turn clears it too, below, because
|
|
1559
|
+
// the content moves out from under a pointer that did not.
|
|
1560
|
+
if (lastMove) {
|
|
1561
|
+
const over = expandableAt(lastMove.y, frameTopRef.current, chatRef.current);
|
|
1562
|
+
setHoverId((h) => (h === over ? h : over));
|
|
1563
|
+
}
|
|
1564
|
+
else if (notches.length > 0 || events.some((e) => e.kind === "press")) {
|
|
1565
|
+
setHoverId((h) => (h === null ? h : null));
|
|
1566
|
+
}
|
|
1498
1567
|
// A real keystroke, so put any highlight away — the same as a terminal's own
|
|
1499
1568
|
// selection does the moment you type.
|
|
1500
1569
|
if (events.length === 0 && notches.length === 0)
|
|
1501
1570
|
clearSelection();
|
|
1502
1571
|
}, { isActive: true });
|
|
1572
|
+
/** Open or fold a row, keeping it where the reader pressed it. See the height effect. */
|
|
1573
|
+
function toggleRow(hit) {
|
|
1574
|
+
rowToggled.current = true;
|
|
1575
|
+
// A hit on one command inside a commands row opens that command; anything else is a row.
|
|
1576
|
+
if (hitItem(hit) >= 0)
|
|
1577
|
+
dispatch({ type: "toggleItem", id: hitBlock(hit), index: hitItem(hit) });
|
|
1578
|
+
else
|
|
1579
|
+
dispatch({ type: "toggleExpand", id: hit });
|
|
1580
|
+
}
|
|
1503
1581
|
function endTurn() {
|
|
1504
1582
|
releaseLiveGates();
|
|
1505
1583
|
if (turnStart.current != null)
|
|
@@ -1957,6 +2035,7 @@ export function App({ resumeSessionId, initialScreen }) {
|
|
|
1957
2035
|
summary: e.summary,
|
|
1958
2036
|
detail: e.detail,
|
|
1959
2037
|
detailKind: e.detailKind,
|
|
2038
|
+
detailFull: e.detailFull,
|
|
1960
2039
|
quiet: e.quiet,
|
|
1961
2040
|
action: e.displayKind,
|
|
1962
2041
|
name: e.displayName,
|
|
@@ -2536,6 +2615,14 @@ export function App({ resumeSessionId, initialScreen }) {
|
|
|
2536
2615
|
return;
|
|
2537
2616
|
}
|
|
2538
2617
|
const install = currentInstall();
|
|
2618
|
+
if (install.kind === "app") {
|
|
2619
|
+
const latest = await checkForUpdate({ readCacheImpl: () => null });
|
|
2620
|
+
const current = appVersion();
|
|
2621
|
+
note(latest
|
|
2622
|
+
? `${refusalReason(install)} v${latest} is out (this is v${current}); update the app from Settings, About.`
|
|
2623
|
+
: `Already on the latest${current ? ` (v${current})` : ""}. ${refusalReason(install)}`);
|
|
2624
|
+
return;
|
|
2625
|
+
}
|
|
2539
2626
|
if (install.kind !== "global") {
|
|
2540
2627
|
// Never guessed at. Overwriting a working tree or another project's dependency
|
|
2541
2628
|
// is the one thing this command must not do; see selfUpdate.ts.
|
|
@@ -3570,6 +3657,7 @@ export function App({ resumeSessionId, initialScreen }) {
|
|
|
3570
3657
|
newReplies: countNewReplies(allBlocks, scrollMark.current),
|
|
3571
3658
|
overlayOpen: overlay !== null,
|
|
3572
3659
|
width,
|
|
3660
|
+
maxScroll,
|
|
3573
3661
|
});
|
|
3574
3662
|
/**
|
|
3575
3663
|
* The chip's cells in SCREEN coordinates, so a click can be tested against them.
|
|
@@ -3586,6 +3674,7 @@ export function App({ resumeSessionId, initialScreen }) {
|
|
|
3586
3674
|
* measure nothing for itself.
|
|
3587
3675
|
*/
|
|
3588
3676
|
const frameTop = readingInline ? Math.max(0, rows - frameHeight) : 0;
|
|
3677
|
+
frameTopRef.current = frameTop;
|
|
3589
3678
|
pillHit.current = pill !== null && pillRow.current !== null ? pillBounds(pill, width, frameTop + pillRow.current) : null;
|
|
3590
3679
|
// The whole conversation stays reachable: the cap is only a safety valve (see
|
|
3591
3680
|
// SCROLLBACK_BLOCKS). Blocks outside the window cost one number each here and nothing
|
|
@@ -3692,13 +3781,13 @@ export function App({ resumeSessionId, initialScreen }) {
|
|
|
3692
3781
|
_jsx(Box, { ref: (node) => {
|
|
3693
3782
|
if (node && needsMeasure(blockHeights.current.get(b.id), b))
|
|
3694
3783
|
toMeasure.current.set(b, node);
|
|
3695
|
-
}, flexShrink: 0, flexDirection: "column", children: _jsx(BlockView, { block: b, columns: width, tightTop: isTight(allBlocks, offset + idx) }) }, b.id));
|
|
3784
|
+
}, flexShrink: 0, flexDirection: "column", children: _jsx(BlockView, { block: b, columns: width, tightTop: isTight(allBlocks, offset + idx), hovered: hoverId !== null && hitBlock(hoverId) === b.id && hitItem(hoverId) < 0, hoveredItem: hoverId !== null && hitBlock(hoverId) === b.id ? hitItem(hoverId) : -1 }) }, b.id));
|
|
3696
3785
|
}), padMiddle > 0 ? _jsx(Box, { flexShrink: 0, height: padMiddle }) : null, rendered.slice(known).map((b, i) => {
|
|
3697
3786
|
const idx = known + i;
|
|
3698
3787
|
return (_jsx(Box, { ref: (node) => {
|
|
3699
3788
|
if (node && needsMeasure(blockHeights.current.get(b.id), b))
|
|
3700
3789
|
toMeasure.current.set(b, node);
|
|
3701
|
-
}, flexShrink: 0, flexDirection: "column", children: _jsx(BlockView, { block: b, columns: width, tightTop: isTight(allBlocks, offset + idx) }) }, b.id));
|
|
3790
|
+
}, flexShrink: 0, flexDirection: "column", children: _jsx(BlockView, { block: b, columns: width, tightTop: isTight(allBlocks, offset + idx), hovered: hoverId !== null && hitBlock(hoverId) === b.id && hitItem(hoverId) < 0, hoveredItem: hoverId !== null && hitBlock(hoverId) === b.id ? hitItem(hoverId) : -1 }) }, b.id));
|
|
3702
3791
|
})] }) }), pill ? (_jsx(Box
|
|
3703
3792
|
// Measured so a CLICK can find it. The row is the only part of the chip's
|
|
3704
3793
|
// position the layout owns — the columns fall out of centring, which
|