@deepseek-harness-tui/dsh-tui 0.10.1 → 0.10.2
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 +5 -146
- package/lib/types/adapter/ports/channel-session.d.ts +3 -1
- package/lib/types/adapter/ports/channel-session.d.ts.map +1 -1
- package/lib/types/components/Chat/JobCard.d.ts.map +1 -1
- package/lib/types/components/Chat/JobCard.js +8 -1
- package/lib/types/components/Markdown.d.ts +2 -2
- package/lib/types/components/Markdown.d.ts.map +1 -1
- package/lib/types/components/Markdown.js +28 -3
- package/lib/types/components/MessageList.d.ts.map +1 -1
- package/lib/types/components/MessageList.js +10 -9
- package/lib/types/components/StreamingMarkdown.d.ts.map +1 -1
- package/lib/types/components/StreamingMarkdown.js +61 -9
- package/lib/types/dsh-adapter/channel/binding-events.d.ts +5 -0
- package/lib/types/dsh-adapter/channel/binding-events.d.ts.map +1 -1
- package/lib/types/dsh-adapter/channel/binding-events.js +12 -2
- package/lib/types/dsh-adapter/channel/input-delivery.d.ts +5 -2
- package/lib/types/dsh-adapter/channel/input-delivery.d.ts.map +1 -1
- package/lib/types/dsh-adapter/channel/input-delivery.js +71 -7
- package/lib/types/dsh-adapter/channel/permissions.d.ts.map +1 -1
- package/lib/types/dsh-adapter/channel/permissions.js +10 -7
- package/lib/types/dsh-adapter/channel/session-rewind.d.ts.map +1 -1
- package/lib/types/dsh-adapter/channel/session-rewind.js +9 -2
- package/lib/types/dsh-adapter/channel/session-tree-actions.d.ts.map +1 -1
- package/lib/types/dsh-adapter/channel/session-tree-actions.js +9 -3
- package/lib/types/dsh-adapter/channel/skill-catalog.d.ts +8 -2
- package/lib/types/dsh-adapter/channel/skill-catalog.d.ts.map +1 -1
- package/lib/types/dsh-adapter/channel/skill-catalog.js +49 -17
- package/lib/types/dsh-adapter/channel.d.ts.map +1 -1
- package/lib/types/dsh-adapter/channel.js +5 -2
- package/lib/types/dsh-adapter/compat/liveSession.d.ts +5 -3
- package/lib/types/dsh-adapter/compat/liveSession.d.ts.map +1 -1
- package/lib/types/dsh-adapter/compat/liveSession.js +5 -3
- package/lib/types/dsh-adapter/sessions/digest.d.ts.map +1 -1
- package/lib/types/dsh-adapter/sessions/digest.js +40 -19
- package/lib/types/dsh-adapter/sessions/frames.js +2 -0
- package/lib/types/dsh-adapter/sessions/list.js +3 -3
- package/lib/types/dsh-adapter/sessions/store.d.ts +2 -2
- package/lib/types/dsh-adapter/sessions/store.d.ts.map +1 -1
- package/lib/types/dsh-adapter/sessions/store.js +12 -11
- package/lib/types/dsh-adapter/sessions/types.d.ts +2 -0
- package/lib/types/dsh-adapter/sessions/types.d.ts.map +1 -1
- package/lib/types/ink/components/ScrollBox.d.ts.map +1 -1
- package/lib/types/ink/components/ScrollBox.js +30 -33
- package/lib/types/ink/dom.d.ts.map +1 -1
- package/lib/types/ink/dom.js +32 -64
- package/lib/types/ink/get-max-width.d.ts +7 -15
- package/lib/types/ink/get-max-width.d.ts.map +1 -1
- package/lib/types/ink/get-max-width.js +7 -20
- package/lib/types/ink/ink.d.ts +1 -0
- package/lib/types/ink/ink.d.ts.map +1 -1
- package/lib/types/ink/ink.js +22 -3
- package/lib/types/ink/layout/node.d.ts +2 -0
- package/lib/types/ink/layout/node.d.ts.map +1 -1
- package/lib/types/ink/layout/yoga.d.ts +1 -0
- package/lib/types/ink/layout/yoga.d.ts.map +1 -1
- package/lib/types/ink/layout/yoga.js +3 -0
- package/lib/types/ink/node-cache.d.ts +11 -0
- package/lib/types/ink/node-cache.d.ts.map +1 -1
- package/lib/types/ink/node-cache.js +3 -0
- package/lib/types/ink/output.d.ts +6 -1
- package/lib/types/ink/output.d.ts.map +1 -1
- package/lib/types/ink/output.js +55 -65
- package/lib/types/ink/render-node-to-output.d.ts.map +1 -1
- package/lib/types/ink/render-node-to-output.js +92 -67
- package/lib/types/ink/tabstops.d.ts +3 -2
- package/lib/types/ink/tabstops.d.ts.map +1 -1
- package/lib/types/ink/tabstops.js +5 -4
- package/lib/types/ink/wrap-text.js +2 -2
- package/lib/types/native-ts/yoga-layout/index.d.ts +8 -3
- package/lib/types/native-ts/yoga-layout/index.d.ts.map +1 -1
- package/lib/types/native-ts/yoga-layout/index.js +23 -8
- package/lib/types/screens/Chat.d.ts.map +1 -1
- package/lib/types/screens/Chat.js +20 -8
- package/package.json +3 -2
|
@@ -25,7 +25,7 @@ import { createHash } from 'node:crypto';
|
|
|
25
25
|
import { open } from 'node:fs/promises';
|
|
26
26
|
import { basename } from 'node:path';
|
|
27
27
|
import { scheduler } from 'node:timers/promises';
|
|
28
|
-
import { decodeFrame,
|
|
28
|
+
import { decodeFrame, decodeTail, readWindow, resyncFrames, walkFrames } from './frames.js';
|
|
29
29
|
/** Head window budget. Eight times the measured worst-case prompt offset. */
|
|
30
30
|
export const HEAD_WINDOW_BYTES = 64 * 1024;
|
|
31
31
|
/** Head frame ceiling — a cost bound independent of how the bytes compress. */
|
|
@@ -71,14 +71,14 @@ function isHumanSource(source) {
|
|
|
71
71
|
return false;
|
|
72
72
|
return source['kind'] === 'user';
|
|
73
73
|
}
|
|
74
|
-
/**
|
|
74
|
+
/** A human message is conversation evidence even when it has no title text. */
|
|
75
75
|
function humanPrompt(line) {
|
|
76
76
|
const data = line['data'];
|
|
77
77
|
if (data === null || typeof data !== 'object')
|
|
78
78
|
return undefined;
|
|
79
79
|
const record = data;
|
|
80
80
|
if (line['type'] === 'user/message') {
|
|
81
|
-
return isHumanSource(record['source']) ? textOfContent(record['content']) : undefined;
|
|
81
|
+
return isHumanSource(record['source']) ? { text: textOfContent(record['content']) } : undefined;
|
|
82
82
|
}
|
|
83
83
|
// The inbox splice precedes the durable user/message and reaches the log
|
|
84
84
|
// several frames earlier, which is what keeps the head window small.
|
|
@@ -86,16 +86,20 @@ function humanPrompt(line) {
|
|
|
86
86
|
const inserted = record['inserted'];
|
|
87
87
|
if (!Array.isArray(inserted))
|
|
88
88
|
return undefined;
|
|
89
|
+
let found = false;
|
|
89
90
|
for (const message of inserted) {
|
|
90
91
|
if (message === null || typeof message !== 'object')
|
|
91
92
|
continue;
|
|
92
93
|
const entry = message;
|
|
93
94
|
if (entry['role'] !== 'user' || !isHumanSource(entry['source']))
|
|
94
95
|
continue;
|
|
96
|
+
found = true;
|
|
95
97
|
const text = textOfContent(entry['content']);
|
|
96
98
|
if (text !== undefined)
|
|
97
|
-
return text;
|
|
99
|
+
return { text };
|
|
98
100
|
}
|
|
101
|
+
if (found)
|
|
102
|
+
return { text: undefined };
|
|
99
103
|
}
|
|
100
104
|
return undefined;
|
|
101
105
|
}
|
|
@@ -152,24 +156,36 @@ function timeOf(line) {
|
|
|
152
156
|
export function digestSession(path, cwd) {
|
|
153
157
|
const head = readWindow(path, HEAD_WINDOW_BYTES);
|
|
154
158
|
if (head === undefined) {
|
|
155
|
-
return { title: undefined, hasPrompt:
|
|
159
|
+
return { title: undefined, hasPrompt: true, model: undefined, label: undefined };
|
|
156
160
|
}
|
|
157
|
-
const
|
|
161
|
+
const headFrames = walkFrames(head.buffer, 0, HEAD_MAX_FRAMES);
|
|
162
|
+
const headLines = [];
|
|
163
|
+
let completeHead = head.whole && headFrames.at(-1)?.end === head.buffer.length;
|
|
164
|
+
for (const frame of headFrames) {
|
|
165
|
+
const lines = decodeFrame(head.buffer, frame);
|
|
166
|
+
if (lines === undefined)
|
|
167
|
+
completeHead = false;
|
|
168
|
+
else
|
|
169
|
+
headLines.push(...lines);
|
|
170
|
+
}
|
|
171
|
+
completeHead &&= headLines[0]?.['type'] === 'session';
|
|
158
172
|
let prompt;
|
|
173
|
+
let hasHumanMessage = false;
|
|
159
174
|
let headTitle;
|
|
160
175
|
let label;
|
|
161
176
|
for (const line of headLines) {
|
|
162
|
-
|
|
177
|
+
const human = humanPrompt(line);
|
|
178
|
+
hasHumanMessage ||= human !== undefined;
|
|
179
|
+
prompt ??= human?.text;
|
|
163
180
|
headTitle ??= titleOf(line);
|
|
164
181
|
label ??= labelOf(line);
|
|
165
182
|
}
|
|
166
|
-
//
|
|
167
|
-
//
|
|
168
|
-
//
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
const tail = head.whole ? undefined : readWindow(path, TAIL_WINDOW_BYTES, true);
|
|
183
|
+
// The byte window, frame limit, decoding and parsing must ALL cover the
|
|
184
|
+
// log before absence proves emptiness. Unknown is visible, never eligible
|
|
185
|
+
// for destructive cleanup; title text is not required for image-only input.
|
|
186
|
+
const hasPrompt = hasHumanMessage || !completeHead;
|
|
187
|
+
// Only a completely decoded head can stand in for the tail.
|
|
188
|
+
const tail = completeHead ? undefined : readWindow(path, TAIL_WINDOW_BYTES, true);
|
|
173
189
|
const tailLines = tail === undefined ? headLines : decodeTail(tail);
|
|
174
190
|
let tailTitle;
|
|
175
191
|
let model;
|
|
@@ -189,7 +205,7 @@ export function digestSession(path, cwd) {
|
|
|
189
205
|
hasPrompt,
|
|
190
206
|
model,
|
|
191
207
|
label,
|
|
192
|
-
...(!
|
|
208
|
+
...(!completeHead && tailTitle === undefined ? {} : { titleComplete: true }),
|
|
193
209
|
};
|
|
194
210
|
}
|
|
195
211
|
/** Read exactly one stable range from an already-open snapshot. */
|
|
@@ -341,6 +357,7 @@ async function recoverFirstPrompt(path, bytes, signal) {
|
|
|
341
357
|
}
|
|
342
358
|
try {
|
|
343
359
|
let position = 0;
|
|
360
|
+
let hasPrompt = false;
|
|
344
361
|
while (position < bytes) {
|
|
345
362
|
signal?.throwIfAborted();
|
|
346
363
|
const page = await forwardPage(handle, position, bytes, signal);
|
|
@@ -350,10 +367,14 @@ async function recoverFirstPrompt(path, bytes, signal) {
|
|
|
350
367
|
const lines = decodeFrame(page.buffer, frame);
|
|
351
368
|
if (lines === undefined)
|
|
352
369
|
return { prompt: undefined, complete: false };
|
|
370
|
+
if (position === 0 && frame.start === 0 && lines[0]?.['type'] !== 'session') {
|
|
371
|
+
return { prompt: undefined, complete: false };
|
|
372
|
+
}
|
|
353
373
|
for (const line of lines) {
|
|
354
374
|
const prompt = humanPrompt(line);
|
|
355
|
-
|
|
356
|
-
|
|
375
|
+
hasPrompt ||= prompt !== undefined;
|
|
376
|
+
if (prompt?.text !== undefined)
|
|
377
|
+
return { prompt: prompt.text, complete: true, hasPrompt: true };
|
|
357
378
|
}
|
|
358
379
|
}
|
|
359
380
|
const consumed = page.frames[page.frames.length - 1].end;
|
|
@@ -362,7 +383,7 @@ async function recoverFirstPrompt(path, bytes, signal) {
|
|
|
362
383
|
position += consumed;
|
|
363
384
|
await scheduler.yield();
|
|
364
385
|
}
|
|
365
|
-
return { prompt: undefined, complete:
|
|
386
|
+
return { prompt: undefined, complete: bytes > 0, ...(bytes > 0 ? { hasPrompt } : {}) };
|
|
366
387
|
}
|
|
367
388
|
finally {
|
|
368
389
|
await handle.close().catch(() => { });
|
|
@@ -381,7 +402,7 @@ export async function recoverSessionTitle(path, bytes, signal) {
|
|
|
381
402
|
return {
|
|
382
403
|
title: opening.prompt === undefined ? undefined : { text: opening.prompt, source: 'prompt' },
|
|
383
404
|
complete: opening.complete,
|
|
384
|
-
...(opening.
|
|
405
|
+
...(opening.hasPrompt === undefined ? {} : { hasPrompt: opening.hasPrompt }),
|
|
385
406
|
};
|
|
386
407
|
}
|
|
387
408
|
/** Hash the previous EOF neighborhood before carrying title evidence forward. */
|
|
@@ -157,13 +157,15 @@ export async function listSummaries(source, signal) {
|
|
|
157
157
|
else if (path !== undefined && token !== undefined) {
|
|
158
158
|
const digest = digestSession(path, header.cwd ?? '');
|
|
159
159
|
let title = digest.title;
|
|
160
|
-
|
|
160
|
+
const hasPrompt = digest.hasPrompt;
|
|
161
161
|
let titleComplete = digest.titleComplete === true;
|
|
162
162
|
const previous = cached?.derived;
|
|
163
163
|
// Across an append-only revision, validate the old EOF neighborhood then
|
|
164
164
|
// scan only the new frames. This preserves an older authoritative title
|
|
165
165
|
// without trusting file size alone, and still observes a newly appended
|
|
166
166
|
// rename even when later output pushed it outside the cheap tail window.
|
|
167
|
+
// Only title evidence carries forward: a formerly empty session can
|
|
168
|
+
// have acquired its first human message in the appended suffix.
|
|
167
169
|
if (!titleComplete &&
|
|
168
170
|
previous !== undefined &&
|
|
169
171
|
facts !== undefined &&
|
|
@@ -177,7 +179,6 @@ export async function listSummaries(source, signal) {
|
|
|
177
179
|
title = previous.title.length === 0
|
|
178
180
|
? undefined
|
|
179
181
|
: { text: previous.title, source: previous.titleSource };
|
|
180
|
-
hasPrompt = previous.hasPrompt;
|
|
181
182
|
titleComplete = true;
|
|
182
183
|
}
|
|
183
184
|
else if (facts.bytes > previous.bytes) {
|
|
@@ -190,7 +191,6 @@ export async function listSummaries(source, signal) {
|
|
|
190
191
|
? undefined
|
|
191
192
|
: { text: previous.title, source: previous.titleSource };
|
|
192
193
|
}
|
|
193
|
-
hasPrompt = previous.hasPrompt;
|
|
194
194
|
titleComplete = true;
|
|
195
195
|
}
|
|
196
196
|
}
|
|
@@ -26,8 +26,8 @@ export interface IndexEntry {
|
|
|
26
26
|
export type SessionIndex = Map<string, IndexEntry>;
|
|
27
27
|
/**
|
|
28
28
|
* Load the cache.
|
|
29
|
-
* @returns The parsed index;
|
|
30
|
-
*
|
|
29
|
+
* @returns The parsed index; old derived facts are discarded for a rebuild.
|
|
30
|
+
* Version 2 retains branch notes; unrecognized files yield an empty index.
|
|
31
31
|
*/
|
|
32
32
|
export declare function readIndex(): SessionIndex;
|
|
33
33
|
/**
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"store.d.ts","sourceRoot":"","sources":["../../../../src/dsh-adapter/sessions/store.ts"],"names":[],"mappings":"AAgCA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,YAAY,CAAA;AAW7C,gDAAgD;AAChD,MAAM,WAAW,YAAY;IAC3B,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAA;IACzB,wFAAwF;IACxF,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAA;IACtB,8EAA8E;IAC9E,QAAQ,CAAC,QAAQ,EAAE,MAAM,GAAG,SAAS,CAAA;IACrC,mFAAmF;IACnF,QAAQ,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,CAAA;IACnC,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAA;IACtB,QAAQ,CAAC,WAAW,EAAE,WAAW,CAAA;IACjC,8EAA8E;IAC9E,QAAQ,CAAC,aAAa,EAAE,OAAO,CAAA;IAC/B,QAAQ,CAAC,SAAS,EAAE,OAAO,CAAA;IAC3B,QAAQ,CAAC,KAAK,EAAE,MAAM,GAAG,SAAS,CAAA;IAClC,QAAQ,CAAC,KAAK,EAAE,MAAM,GAAG,SAAS,CAAA;CACnC;AAED,gFAAgF;AAChF,MAAM,WAAW,UAAU;IACzB,QAAQ,CAAC,OAAO,EAAE,YAAY,GAAG,SAAS,CAAA;IAC1C,oEAAoE;IACpE,QAAQ,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,CAAA;CACpC;AAED,2CAA2C;AAC3C,MAAM,MAAM,YAAY,GAAG,GAAG,CAAC,MAAM,EAAE,UAAU,CAAC,CAAA;
|
|
1
|
+
{"version":3,"file":"store.d.ts","sourceRoot":"","sources":["../../../../src/dsh-adapter/sessions/store.ts"],"names":[],"mappings":"AAgCA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,YAAY,CAAA;AAW7C,gDAAgD;AAChD,MAAM,WAAW,YAAY;IAC3B,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAA;IACzB,wFAAwF;IACxF,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAA;IACtB,8EAA8E;IAC9E,QAAQ,CAAC,QAAQ,EAAE,MAAM,GAAG,SAAS,CAAA;IACrC,mFAAmF;IACnF,QAAQ,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,CAAA;IACnC,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAA;IACtB,QAAQ,CAAC,WAAW,EAAE,WAAW,CAAA;IACjC,8EAA8E;IAC9E,QAAQ,CAAC,aAAa,EAAE,OAAO,CAAA;IAC/B,QAAQ,CAAC,SAAS,EAAE,OAAO,CAAA;IAC3B,QAAQ,CAAC,KAAK,EAAE,MAAM,GAAG,SAAS,CAAA;IAClC,QAAQ,CAAC,KAAK,EAAE,MAAM,GAAG,SAAS,CAAA;CACnC;AAED,gFAAgF;AAChF,MAAM,WAAW,UAAU;IACzB,QAAQ,CAAC,OAAO,EAAE,YAAY,GAAG,SAAS,CAAA;IAC1C,oEAAoE;IACpE,QAAQ,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,CAAA;CACpC;AAED,2CAA2C;AAC3C,MAAM,MAAM,YAAY,GAAG,GAAG,CAAC,MAAM,EAAE,UAAU,CAAC,CAAA;AAuDlD;;;;GAIG;AACH,wBAAgB,SAAS,IAAI,YAAY,CAkBxC;AAED;;;;;;;;;GASG;AACH,wBAAgB,UAAU,CAAC,KAAK,EAAE,YAAY,GAAG,IAAI,CAwBpD;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,UAAU,CAAC,SAAS,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,SAAS,GAAG,IAAI,CAO9E"}
|
|
@@ -31,11 +31,11 @@ import { mkdirSync, readFileSync, renameSync, rmSync, writeFileSync } from 'node
|
|
|
31
31
|
import { join } from 'node:path';
|
|
32
32
|
import { DATA_DIR } from '../../utils/paths.js';
|
|
33
33
|
/**
|
|
34
|
-
* Bumped when
|
|
35
|
-
*
|
|
36
|
-
*
|
|
34
|
+
* Bumped when derived facts change shape or meaning. Version 2 could cache
|
|
35
|
+
* incomplete reads as empty: discard those derivations, retaining only the
|
|
36
|
+
* branch notes that cannot be recovered from the session log.
|
|
37
37
|
*/
|
|
38
|
-
const SCHEMA_VERSION =
|
|
38
|
+
const SCHEMA_VERSION = 3;
|
|
39
39
|
const INDEX_FILE = join(DATA_DIR, 'session-index.json');
|
|
40
40
|
/** A title source as written to disk, or undefined for anything unexpected. */
|
|
41
41
|
function readTitleSource(value) {
|
|
@@ -44,12 +44,12 @@ function readTitleSource(value) {
|
|
|
44
44
|
: undefined;
|
|
45
45
|
}
|
|
46
46
|
/** Narrow one persisted entry; an unrecognizable record is simply absent. */
|
|
47
|
-
function readEntry(value) {
|
|
47
|
+
function readEntry(value, derivedValid) {
|
|
48
48
|
if (value === null || typeof value !== 'object')
|
|
49
49
|
return undefined;
|
|
50
50
|
const record = value;
|
|
51
51
|
const branch = typeof record['branch'] === 'string' ? record['branch'] : undefined;
|
|
52
|
-
const raw = record['derived'];
|
|
52
|
+
const raw = derivedValid ? record['derived'] : undefined;
|
|
53
53
|
if (raw === null || typeof raw !== 'object')
|
|
54
54
|
return { derived: undefined, branch };
|
|
55
55
|
const derived = raw;
|
|
@@ -68,7 +68,8 @@ function readEntry(value) {
|
|
|
68
68
|
(anchor !== undefined && typeof anchor !== 'string') ||
|
|
69
69
|
typeof title !== 'string' ||
|
|
70
70
|
titleSource === undefined ||
|
|
71
|
-
typeof titleComplete !== 'boolean'
|
|
71
|
+
typeof titleComplete !== 'boolean' ||
|
|
72
|
+
typeof derived['hasPrompt'] !== 'boolean') {
|
|
72
73
|
return { derived: undefined, branch };
|
|
73
74
|
}
|
|
74
75
|
return {
|
|
@@ -89,8 +90,8 @@ function readEntry(value) {
|
|
|
89
90
|
}
|
|
90
91
|
/**
|
|
91
92
|
* Load the cache.
|
|
92
|
-
* @returns The parsed index;
|
|
93
|
-
*
|
|
93
|
+
* @returns The parsed index; old derived facts are discarded for a rebuild.
|
|
94
|
+
* Version 2 retains branch notes; unrecognized files yield an empty index.
|
|
94
95
|
*/
|
|
95
96
|
export function readIndex() {
|
|
96
97
|
const index = new Map();
|
|
@@ -104,13 +105,13 @@ export function readIndex() {
|
|
|
104
105
|
if (parsed === null || typeof parsed !== 'object')
|
|
105
106
|
return index;
|
|
106
107
|
const file = parsed;
|
|
107
|
-
if (file['version'] !== SCHEMA_VERSION)
|
|
108
|
+
if (file['version'] !== SCHEMA_VERSION && file['version'] !== 2)
|
|
108
109
|
return index;
|
|
109
110
|
const entries = file['entries'];
|
|
110
111
|
if (entries === null || typeof entries !== 'object')
|
|
111
112
|
return index;
|
|
112
113
|
for (const [id, value] of Object.entries(entries)) {
|
|
113
|
-
const entry = readEntry(value);
|
|
114
|
+
const entry = readEntry(value, file['version'] === SCHEMA_VERSION);
|
|
114
115
|
if (entry !== undefined)
|
|
115
116
|
index.set(id, entry);
|
|
116
117
|
}
|
|
@@ -9,6 +9,8 @@ export type { SessionSummary, SessionKind, SessionTitle, TitleSource, PreviewEnt
|
|
|
9
9
|
*/
|
|
10
10
|
export interface SessionDigest {
|
|
11
11
|
readonly title: SessionTitle | undefined;
|
|
12
|
+
/** False only after a complete successful scan proves no human message;
|
|
13
|
+
* true also keeps inconclusive reads visible and out of empty cleanup. */
|
|
12
14
|
readonly hasPrompt: boolean;
|
|
13
15
|
readonly model: string | undefined;
|
|
14
16
|
readonly label: string | undefined;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../../src/dsh-adapter/sessions/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAA+B,YAAY,EAA6B,MAAM,wCAAwC,CAAA;AAClI,YAAY,EAAE,cAAc,EAAE,WAAW,EAAE,YAAY,EAAE,WAAW,EAAE,YAAY,EAAE,MAAM,wCAAwC,CAAA;AAGlI;;;;;;GAMG;AACH,MAAM,WAAW,aAAa;IAC5B,QAAQ,CAAC,KAAK,EAAE,YAAY,GAAG,SAAS,CAAA;IACxC,QAAQ,CAAC,SAAS,EAAE,OAAO,CAAA;IAC3B,QAAQ,CAAC,KAAK,EAAE,MAAM,GAAG,SAAS,CAAA;IAClC,QAAQ,CAAC,KAAK,EAAE,MAAM,GAAG,SAAS,CAAA;IAClC;mEAC+D;IAC/D,QAAQ,CAAC,aAAa,CAAC,EAAE,IAAI,CAAA;CAC9B"}
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../../src/dsh-adapter/sessions/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAA+B,YAAY,EAA6B,MAAM,wCAAwC,CAAA;AAClI,YAAY,EAAE,cAAc,EAAE,WAAW,EAAE,YAAY,EAAE,WAAW,EAAE,YAAY,EAAE,MAAM,wCAAwC,CAAA;AAGlI;;;;;;GAMG;AACH,MAAM,WAAW,aAAa;IAC5B,QAAQ,CAAC,KAAK,EAAE,YAAY,GAAG,SAAS,CAAA;IACxC;8EAC0E;IAC1E,QAAQ,CAAC,SAAS,EAAE,OAAO,CAAA;IAC3B,QAAQ,CAAC,KAAK,EAAE,MAAM,GAAG,SAAS,CAAA;IAClC,QAAQ,CAAC,KAAK,EAAE,MAAM,GAAG,SAAS,CAAA;IAClC;mEAC+D;IAC/D,QAAQ,CAAC,aAAa,CAAC,EAAE,IAAI,CAAA;CAC9B"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ScrollBox.d.ts","sourceRoot":"","sources":["../../../../src/ink/components/ScrollBox.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,EAAE,KAAK,iBAAiB,EAAE,KAAK,GAAG,EAAsD,MAAM,OAAO,CAAC;AACpH,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,WAAW,CAAC;AAGxC,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,WAAW,CAAC;AAM5C,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,cAAc,CAAC;AAE3C,MAAM,MAAM,eAAe,GAAG;IAC5B,QAAQ,EAAE,CAAC,CAAC,EAAE,MAAM,KAAK,IAAI,CAAC;IAC9B,QAAQ,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,IAAI,CAAC;IAC/B;;;;;;OAMG;IACH,eAAe,EAAE,CAAC,EAAE,EAAE,UAAU,EAAE,MAAM,CAAC,EAAE,MAAM,KAAK,IAAI,CAAC;IAC3D,cAAc,EAAE,MAAM,IAAI,CAAC;IAC3B,YAAY,EAAE,MAAM,MAAM,CAAC;IAC3B,eAAe,EAAE,MAAM,MAAM,CAAC;IAC9B,eAAe,EAAE,MAAM,MAAM,CAAC;IAC9B;;;;;OAKG;IACH,oBAAoB,EAAE,MAAM,MAAM,CAAC;IACnC,iBAAiB,EAAE,MAAM,MAAM,CAAC;IAChC;;;OAGG;IACH,cAAc,EAAE,MAAM,MAAM,CAAC;IAC7B;;;;;;OAMG;IACH,QAAQ,EAAE,MAAM,OAAO,CAAC;IACxB;;;;;OAKG;IACH,SAAS,EAAE,CAAC,QAAQ,EAAE,MAAM,IAAI,KAAK,MAAM,IAAI,CAAC;IAChD;;;;;;;OAOG;IACH,cAAc,EAAE,CAAC,GAAG,EAAE,MAAM,GAAG,SAAS,EAAE,GAAG,EAAE,MAAM,GAAG,SAAS,KAAK,IAAI,CAAC;CAC5E,CAAC;AACF,MAAM,MAAM,cAAc,GAAG,MAAM,CAAC,MAAM,EAAE,UAAU,GAAG,UAAU,GAAG,WAAW,GAAG,WAAW,CAAC,GAAG;IACjG,GAAG,CAAC,EAAE,GAAG,CAAC,eAAe,CAAC,CAAC;IAC3B;;;OAGG;IACH,YAAY,CAAC,EAAE,OAAO,CAAC;CACxB,CAAC;AAEF;;;;;;;;;GASG;AACH,iBAAS,SAAS,CAAC,EACjB,QAAQ,EACR,GAAG,EACH,YAAY,EACZ,GAAG,KAAK,EACT,EAAE,iBAAiB,CAAC,cAAc,CAAC,GAAG,KAAK,CAAC,SAAS,
|
|
1
|
+
{"version":3,"file":"ScrollBox.d.ts","sourceRoot":"","sources":["../../../../src/ink/components/ScrollBox.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,EAAE,KAAK,iBAAiB,EAAE,KAAK,GAAG,EAAsD,MAAM,OAAO,CAAC;AACpH,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,WAAW,CAAC;AAGxC,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,WAAW,CAAC;AAM5C,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,cAAc,CAAC;AAE3C,MAAM,MAAM,eAAe,GAAG;IAC5B,QAAQ,EAAE,CAAC,CAAC,EAAE,MAAM,KAAK,IAAI,CAAC;IAC9B,QAAQ,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,IAAI,CAAC;IAC/B;;;;;;OAMG;IACH,eAAe,EAAE,CAAC,EAAE,EAAE,UAAU,EAAE,MAAM,CAAC,EAAE,MAAM,KAAK,IAAI,CAAC;IAC3D,cAAc,EAAE,MAAM,IAAI,CAAC;IAC3B,YAAY,EAAE,MAAM,MAAM,CAAC;IAC3B,eAAe,EAAE,MAAM,MAAM,CAAC;IAC9B,eAAe,EAAE,MAAM,MAAM,CAAC;IAC9B;;;;;OAKG;IACH,oBAAoB,EAAE,MAAM,MAAM,CAAC;IACnC,iBAAiB,EAAE,MAAM,MAAM,CAAC;IAChC;;;OAGG;IACH,cAAc,EAAE,MAAM,MAAM,CAAC;IAC7B;;;;;;OAMG;IACH,QAAQ,EAAE,MAAM,OAAO,CAAC;IACxB;;;;;OAKG;IACH,SAAS,EAAE,CAAC,QAAQ,EAAE,MAAM,IAAI,KAAK,MAAM,IAAI,CAAC;IAChD;;;;;;;OAOG;IACH,cAAc,EAAE,CAAC,GAAG,EAAE,MAAM,GAAG,SAAS,EAAE,GAAG,EAAE,MAAM,GAAG,SAAS,KAAK,IAAI,CAAC;CAC5E,CAAC;AACF,MAAM,MAAM,cAAc,GAAG,MAAM,CAAC,MAAM,EAAE,UAAU,GAAG,UAAU,GAAG,WAAW,GAAG,WAAW,CAAC,GAAG;IACjG,GAAG,CAAC,EAAE,GAAG,CAAC,eAAe,CAAC,CAAC;IAC3B;;;OAGG;IACH,YAAY,CAAC,EAAE,OAAO,CAAC;CACxB,CAAC;AAEF;;;;;;;;;GASG;AACH,iBAAS,SAAS,CAAC,EACjB,QAAQ,EACR,GAAG,EACH,YAAY,EACZ,GAAG,KAAK,EACT,EAAE,iBAAiB,CAAC,cAAc,CAAC,GAAG,KAAK,CAAC,SAAS,CAkRrD;AACD,eAAe,SAAS,CAAC"}
|
|
@@ -89,6 +89,9 @@ function ScrollBox({ children, ref, stickyScroll, ...style }) {
|
|
|
89
89
|
markDirty(el);
|
|
90
90
|
markCommitStart();
|
|
91
91
|
notifyCoalesced();
|
|
92
|
+
queueRender(el);
|
|
93
|
+
}
|
|
94
|
+
function queueRender(el) {
|
|
92
95
|
if (renderQueuedRef.current)
|
|
93
96
|
return;
|
|
94
97
|
renderQueuedRef.current = true;
|
|
@@ -181,32 +184,13 @@ function ScrollBox({ children, ref, stickyScroll, ...style }) {
|
|
|
181
184
|
el.scrollAnchor = undefined;
|
|
182
185
|
const viewportH = el.scrollViewportHeight ?? 0;
|
|
183
186
|
const maxScroll = Math.max(0, (el.scrollHeight ?? 0) - viewportH);
|
|
184
|
-
const distance = maxScroll - (el.scrollTop ?? 0);
|
|
185
|
-
if (distance > viewportH && viewportH > 0) {
|
|
186
|
-
// FAR jump: drain instead of teleport. The viewport would land on
|
|
187
|
-
// rows that were never mounted — their spacer heights are
|
|
188
|
-
// DEFAULT_ROW_HEIGHT estimates, so the topPad swallows the view
|
|
189
|
-
// (blank transcript) AND it is a fixed point: unmounted rows never
|
|
190
|
-
// measure, nothing retried until the next wheel event. The drain
|
|
191
|
-
// walks the exact wheel path instead: proportional steps, the
|
|
192
|
-
// virtualization window follows per commit (it mounts the union of
|
|
193
|
-
// committed + pending), the visual clamp pins to the mounted edge
|
|
194
|
-
// while rows measure as they enter — no blank at any distance. The
|
|
195
|
-
// renderer's at-bottom re-pin restores sticky when it lands
|
|
196
|
-
// (stickyScroll=false + pending undefined + scrollTop >= maxScroll).
|
|
197
|
-
el.stickyScroll = false;
|
|
198
|
-
el.pendingScrollDelta = (el.pendingScrollDelta ?? 0) + distance;
|
|
199
|
-
scrollMutated(el);
|
|
200
|
-
return;
|
|
201
|
-
}
|
|
202
187
|
el.pendingScrollDelta = undefined;
|
|
203
188
|
el.stickyScroll = true;
|
|
204
|
-
//
|
|
205
|
-
//
|
|
206
|
-
//
|
|
207
|
-
//
|
|
208
|
-
//
|
|
209
|
-
// maxScroll next frame, so a stale cached height costs one frame.
|
|
189
|
+
// Jump intent is not wheel debt: mounting the union of the current
|
|
190
|
+
// position and a far pending delta defeats virtualization. MessageList's
|
|
191
|
+
// sticky tail walk mounts the destination directly, even with estimated
|
|
192
|
+
// heights. Publish sticky + target before notifying; the renderer then
|
|
193
|
+
// pins to the fresh maxScroll as those tail rows are measured.
|
|
210
194
|
el.scrollTop = maxScroll;
|
|
211
195
|
scrollMutated(el);
|
|
212
196
|
forceRender(n => n + 1);
|
|
@@ -247,8 +231,17 @@ function ScrollBox({ children, ref, stickyScroll, ...style }) {
|
|
|
247
231
|
const el = domRef.current;
|
|
248
232
|
if (!el)
|
|
249
233
|
return;
|
|
234
|
+
if (el.scrollClampMin === min && el.scrollClampMax === max)
|
|
235
|
+
return;
|
|
250
236
|
el.scrollClampMin = min;
|
|
251
237
|
el.scrollClampMax = max;
|
|
238
|
+
// Layout effects publish the new mount window AFTER resetAfterCommit
|
|
239
|
+
// may have painted with the previous bounds. Warm rows need no measure
|
|
240
|
+
// tick, so that paint can otherwise remain on blank spacer indefinitely.
|
|
241
|
+
// Invalidate the blit and schedule a paint, without notifying React
|
|
242
|
+
// subscribers (the window is already committed).
|
|
243
|
+
markDirty(el);
|
|
244
|
+
queueRender(el);
|
|
252
245
|
}
|
|
253
246
|
};
|
|
254
247
|
handleRef.current = handle;
|
|
@@ -267,6 +260,18 @@ function ScrollBox({ children, ref, stickyScroll, ...style }) {
|
|
|
267
260
|
if (e.deltaY !== 0)
|
|
268
261
|
handleRef.current?.scrollBy(e.deltaY);
|
|
269
262
|
}, []);
|
|
263
|
+
// Keep the host ref attached across updates. A new callback would detach
|
|
264
|
+
// it before children's layout effects publish their clamp bounds, making
|
|
265
|
+
// setClampBounds silently miss the commit (child effects run first).
|
|
266
|
+
const attachNode = useCallback((el) => {
|
|
267
|
+
domRef.current = el;
|
|
268
|
+
if (el) {
|
|
269
|
+
el.scrollTop ??= 0;
|
|
270
|
+
el.onStickyRestore = notify;
|
|
271
|
+
}
|
|
272
|
+
// notify only closes over refs, like the imperative handle above.
|
|
273
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
274
|
+
}, []);
|
|
270
275
|
// Structure: outer viewport (overflow:scroll, constrained height) >
|
|
271
276
|
// inner content (flexGrow:1, flexShrink:0 — fills at least the viewport
|
|
272
277
|
// but grows beyond it for tall content). flexGrow:1 lets children use
|
|
@@ -278,15 +283,7 @@ function ScrollBox({ children, ref, stickyScroll, ...style }) {
|
|
|
278
283
|
// stickyScroll is passed as a DOM attribute (via ink-box directly) so it's
|
|
279
284
|
// available on the first render — ref callbacks fire after the initial
|
|
280
285
|
// commit, which is too late for the first frame.
|
|
281
|
-
return _jsx("ink-box", { ref:
|
|
282
|
-
domRef.current = el;
|
|
283
|
-
if (el) {
|
|
284
|
-
el.scrollTop ??= 0;
|
|
285
|
-
// Renderer-side sticky restores (positional re-pin at the bottom)
|
|
286
|
-
// must reach React subscribers too — see dom.ts onStickyRestore.
|
|
287
|
-
el.onStickyRestore = notify;
|
|
288
|
-
}
|
|
289
|
-
}, onWheel: handleWheel, style: {
|
|
286
|
+
return _jsx("ink-box", { ref: attachNode, onWheel: handleWheel, style: {
|
|
290
287
|
flexWrap: 'nowrap',
|
|
291
288
|
flexDirection: style.flexDirection ?? 'row',
|
|
292
289
|
flexGrow: style.flexGrow ?? 0,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"dom.d.ts","sourceRoot":"","sources":["../../../src/ink/dom.ts"],"names":[],"mappings":"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
|
|
1
|
+
{"version":3,"file":"dom.d.ts","sourceRoot":"","sources":["../../../src/ink/dom.ts"],"names":[],"mappings":"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"}
|
package/lib/types/ink/dom.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { createLayoutNode } from './layout/engine.js';
|
|
2
2
|
import { LayoutDisplay, LayoutMeasureMode } from './layout/node.js';
|
|
3
3
|
import measureText from './measure-text.js';
|
|
4
|
-
import { addPendingClear, nodeCache } from './node-cache.js';
|
|
4
|
+
import { addPendingClear, nodeCache, textPaintCache } from './node-cache.js';
|
|
5
5
|
import squashTextNodes from './squash-text-nodes.js';
|
|
6
6
|
import { expandTabs } from './tabstops.js';
|
|
7
7
|
import wrapText from './wrap-text.js';
|
|
@@ -219,12 +219,31 @@ export const createTextNode = (text) => {
|
|
|
219
219
|
setTextNodeValue(node, text);
|
|
220
220
|
return node;
|
|
221
221
|
};
|
|
222
|
-
const
|
|
222
|
+
const textMeasureCache = new WeakMap();
|
|
223
|
+
// Yoga probes several widths/modes repeatedly in one layout. Retain only
|
|
224
|
+
// the current text's results so streaming cannot accumulate old snapshots.
|
|
225
|
+
const TEXT_MEASURE_CACHE_SIZE = 8;
|
|
223
226
|
const measureTextNode = function (node, width, widthMode) {
|
|
224
227
|
const rawText = node.nodeName === '#text' ? node.nodeValue : squashTextNodes(node);
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
+
const textWrap = node.style.textWrap ?? 'wrap';
|
|
229
|
+
let cache = textMeasureCache.get(node);
|
|
230
|
+
if (cache === undefined || cache.rawText !== rawText || cache.wrap !== textWrap) {
|
|
231
|
+
// Tabs use the same measurement expansion as the uncached path.
|
|
232
|
+
cache = { rawText, text: expandTabs(rawText), wrap: textWrap, entries: [] };
|
|
233
|
+
textMeasureCache.set(node, cache);
|
|
234
|
+
}
|
|
235
|
+
for (const entry of cache.entries) {
|
|
236
|
+
if (Object.is(entry.width, width) && entry.widthMode === widthMode)
|
|
237
|
+
return entry.result;
|
|
238
|
+
}
|
|
239
|
+
// Check above before measureText walks every line, even on a wrap-cache hit.
|
|
240
|
+
const result = measureTextDimensions(cache.text, width, widthMode, textWrap);
|
|
241
|
+
if (cache.entries.length === TEXT_MEASURE_CACHE_SIZE)
|
|
242
|
+
cache.entries.shift();
|
|
243
|
+
cache.entries.push({ width, widthMode, result });
|
|
244
|
+
return result;
|
|
245
|
+
};
|
|
246
|
+
function measureTextDimensions(text, width, widthMode, textWrap) {
|
|
228
247
|
const dimensions = measureText(text, width);
|
|
229
248
|
// Text fits into container, no need to wrap
|
|
230
249
|
if (dimensions.width <= width) {
|
|
@@ -247,66 +266,11 @@ const measureTextNode = function (node, width, widthMode) {
|
|
|
247
266
|
const effectiveWidth = Math.max(width, dimensions.width);
|
|
248
267
|
return measureText(text, effectiveWidth);
|
|
249
268
|
}
|
|
250
|
-
const textWrap = node.style.textWrap ?? 'wrap';
|
|
251
|
-
// Incremental path: same node, same width/wrap, text grew by append.
|
|
252
|
-
const cached = measureWrapCache.get(node);
|
|
253
|
-
if (cached !== undefined && cached.width === width && cached.wrap === textWrap) {
|
|
254
|
-
if (cached.text === text) {
|
|
255
|
-
// Same-frame repeat measure (yoga min/max probing) — free.
|
|
256
|
-
return cached.result;
|
|
257
|
-
}
|
|
258
|
-
if (text.startsWith(cached.text)) {
|
|
259
|
-
const tail = text.slice(cached.tailStart);
|
|
260
|
-
const tailWrapped = wrapText(tail, width, textWrap);
|
|
261
|
-
const tailDim = measureText(tailWrapped, width);
|
|
262
|
-
// Commit fully-terminated logical lines into the head so the tail
|
|
263
|
-
// stays bounded at one line of source text.
|
|
264
|
-
const lastNewline = tail.lastIndexOf('\n');
|
|
265
|
-
let { headHeight, tailStart } = cached;
|
|
266
|
-
let tailHeight = tailDim.height;
|
|
267
|
-
if (lastNewline > 0) {
|
|
268
|
-
const committed = wrapText(tail.slice(0, lastNewline + 1), width, textWrap);
|
|
269
|
-
headHeight += measureText(committed, width).height;
|
|
270
|
-
tailStart += lastNewline + 1;
|
|
271
|
-
tailHeight = measureText(wrapText(tail.slice(lastNewline + 1), width, textWrap), width).height;
|
|
272
|
-
}
|
|
273
|
-
const result = {
|
|
274
|
-
width: Math.max(cached.result.width, tailDim.width),
|
|
275
|
-
height: headHeight + tailHeight,
|
|
276
|
-
};
|
|
277
|
-
measureWrapCache.set(node, {
|
|
278
|
-
text,
|
|
279
|
-
width,
|
|
280
|
-
wrap: textWrap,
|
|
281
|
-
headHeight,
|
|
282
|
-
tailStart,
|
|
283
|
-
tailHeight,
|
|
284
|
-
result,
|
|
285
|
-
});
|
|
286
|
-
return result;
|
|
287
|
-
}
|
|
288
|
-
// Text replaced or shrunk (node reuse for a different message): fall
|
|
289
|
-
// through to the full path and overwrite the cache below.
|
|
290
|
-
}
|
|
291
269
|
const wrappedText = wrapText(text, width, textWrap);
|
|
292
|
-
|
|
293
|
-
//
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
const headHeight = tailStart === 0
|
|
297
|
-
? 0
|
|
298
|
-
: measureText(wrapText(text.slice(0, tailStart), width, textWrap), width).height;
|
|
299
|
-
measureWrapCache.set(node, {
|
|
300
|
-
text,
|
|
301
|
-
width,
|
|
302
|
-
wrap: textWrap,
|
|
303
|
-
headHeight,
|
|
304
|
-
tailStart,
|
|
305
|
-
tailHeight: fullResult.height - headHeight,
|
|
306
|
-
result: fullResult,
|
|
307
|
-
});
|
|
308
|
-
return fullResult;
|
|
309
|
-
};
|
|
270
|
+
// The wrapper has already chosen physical rows. Reapplying width-based
|
|
271
|
+
// row counting here double-counts trailing spaces at fractional widths.
|
|
272
|
+
return measureText(wrappedText, Infinity);
|
|
273
|
+
}
|
|
310
274
|
// ink-raw-ansi nodes hold pre-rendered ANSI strings with known dimensions.
|
|
311
275
|
// No stringWidth, no wrapping, no tab expansion — the producer (e.g. ColorDiff)
|
|
312
276
|
// already wrapped to the target width and each line is exactly one terminal row.
|
|
@@ -328,6 +292,9 @@ export const markDirty = (node) => {
|
|
|
328
292
|
if (current.nodeName !== '#text') {
|
|
329
293
|
;
|
|
330
294
|
(current).dirty = true;
|
|
295
|
+
// Culling can clear the paint dirty bit without preparing changed text.
|
|
296
|
+
// Invalidate at mutation time so re-entry cannot reuse stale content.
|
|
297
|
+
textPaintCache.delete(current);
|
|
331
298
|
// Only mark yoga dirty on leaf nodes that have measure functions
|
|
332
299
|
if (!markedYoga &&
|
|
333
300
|
(current.nodeName === 'ink-text' ||
|
|
@@ -362,6 +329,7 @@ export const markTreeDirty = (node) => {
|
|
|
362
329
|
continue;
|
|
363
330
|
const element = current;
|
|
364
331
|
element.dirty = true;
|
|
332
|
+
textPaintCache.delete(element);
|
|
365
333
|
// markDirty() is only legal on yoga nodes that carry a measure function;
|
|
366
334
|
// those are exactly the two text node kinds (see createNode).
|
|
367
335
|
if ((current.nodeName === 'ink-text' || current.nodeName === 'ink-raw-ansi') &&
|
|
@@ -1,19 +1,11 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import type { LayoutNode } from './layout/node.js';
|
|
2
2
|
/**
|
|
3
|
-
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
* node overflows (standard CSS behavior). Yoga measures leaf nodes in two
|
|
10
|
-
* passes: the AtMost pass determines width, the Exactly pass determines
|
|
11
|
-
* height. getComputedWidth() reflects the wider AtMost result while
|
|
12
|
-
* getComputedHeight() reflects the narrower Exactly result. Callers that
|
|
13
|
-
* use this for wrapping should clamp to actual available screen space so
|
|
14
|
-
* the rendered line count stays consistent with the layout height.
|
|
15
|
-
* @param yogaNode - the laid-out node to measure.
|
|
16
|
-
* @returns the node's content width: computed width minus padding and border.
|
|
3
|
+
* Return the text constraint that produced the laid-out height. Pixel-grid
|
|
4
|
+
* rounding can widen the painted box; wrapping at that rounded width would
|
|
5
|
+
* produce fewer rows than Yoga reserved and leave blank space at the bottom.
|
|
6
|
+
* The layout engine retains this constraint through its cache-hit paths.
|
|
7
|
+
* @param yogaNode - a laid-out text node with a measure function.
|
|
8
|
+
* @returns content width, already excluding padding and border.
|
|
17
9
|
*/
|
|
18
10
|
declare const getMaxWidth: (yogaNode: LayoutNode) => number;
|
|
19
11
|
export default getMaxWidth;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"get-max-width.d.ts","sourceRoot":"","sources":["../../../src/ink/get-max-width.ts"],"names":[],"mappings":"AAAA,OAAO,
|
|
1
|
+
{"version":3,"file":"get-max-width.d.ts","sourceRoot":"","sources":["../../../src/ink/get-max-width.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAA;AAElD;;;;;;;GAOG;AACH,QAAA,MAAM,WAAW,GAAI,UAAU,UAAU,KAAG,MAE3C,CAAA;AAED,eAAe,WAAW,CAAA"}
|
|
@@ -1,25 +1,12 @@
|
|
|
1
|
-
import { LayoutEdge } from './layout/node.js';
|
|
2
1
|
/**
|
|
3
|
-
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
* node overflows (standard CSS behavior). Yoga measures leaf nodes in two
|
|
10
|
-
* passes: the AtMost pass determines width, the Exactly pass determines
|
|
11
|
-
* height. getComputedWidth() reflects the wider AtMost result while
|
|
12
|
-
* getComputedHeight() reflects the narrower Exactly result. Callers that
|
|
13
|
-
* use this for wrapping should clamp to actual available screen space so
|
|
14
|
-
* the rendered line count stays consistent with the layout height.
|
|
15
|
-
* @param yogaNode - the laid-out node to measure.
|
|
16
|
-
* @returns the node's content width: computed width minus padding and border.
|
|
2
|
+
* Return the text constraint that produced the laid-out height. Pixel-grid
|
|
3
|
+
* rounding can widen the painted box; wrapping at that rounded width would
|
|
4
|
+
* produce fewer rows than Yoga reserved and leave blank space at the bottom.
|
|
5
|
+
* The layout engine retains this constraint through its cache-hit paths.
|
|
6
|
+
* @param yogaNode - a laid-out text node with a measure function.
|
|
7
|
+
* @returns content width, already excluding padding and border.
|
|
17
8
|
*/
|
|
18
9
|
const getMaxWidth = (yogaNode) => {
|
|
19
|
-
return
|
|
20
|
-
yogaNode.getComputedPadding(LayoutEdge.Left) -
|
|
21
|
-
yogaNode.getComputedPadding(LayoutEdge.Right) -
|
|
22
|
-
yogaNode.getComputedBorder(LayoutEdge.Left) -
|
|
23
|
-
yogaNode.getComputedBorder(LayoutEdge.Right));
|
|
10
|
+
return yogaNode.getComputedMeasureWidth();
|
|
24
11
|
};
|
|
25
12
|
export default getMaxWidth;
|
package/lib/types/ink/ink.d.ts
CHANGED