@cplieger/web-terminal-engine 3.10.2 → 3.10.4
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/jsr.json +1 -1
- package/package.json +1 -1
- package/src/render.ts +62 -0
- package/src/scroll.ts +10 -8
package/jsr.json
CHANGED
package/package.json
CHANGED
package/src/render.ts
CHANGED
|
@@ -494,6 +494,11 @@ function queueRowsViewportFirst(): void {
|
|
|
494
494
|
* history; the existing per-frame budget spreads a large backlog across frames
|
|
495
495
|
* so the switch never janks. Used by bind(); also safe to call directly to
|
|
496
496
|
* force a full repaint of the current store.
|
|
497
|
+
*
|
|
498
|
+
* The wipe covers content space in full: the rows AND the four overlays anchored
|
|
499
|
+
* into it (see collapseContentSpaceOverlays). Leaving the overlays behind left
|
|
500
|
+
* the container a measured 81081px of scroll range over zero rows of content,
|
|
501
|
+
* which is a black pane the bottom pin refuses to correct.
|
|
497
502
|
*/
|
|
498
503
|
export function rebuild(): void {
|
|
499
504
|
// The wipe is the largest content shrink this module performs, and it happens
|
|
@@ -515,6 +520,9 @@ export function rebuild(): void {
|
|
|
515
520
|
// Fresh surface: a stale give-up streak from a prior store must not deny the
|
|
516
521
|
// rebuilt surface its full transient-retry budget.
|
|
517
522
|
renderNoProgressStreak = 0;
|
|
523
|
+
// The rows are not the only thing in content space. Collapse the overlays
|
|
524
|
+
// with them, BEFORE the offset below is read.
|
|
525
|
+
collapseContentSpaceOverlays();
|
|
518
526
|
// Alt screen paints from the ephemeral grid in the flush, so it needs no
|
|
519
527
|
// absolute-index queueing here.
|
|
520
528
|
if (!store.isAlt()) {
|
|
@@ -524,6 +532,60 @@ export function rebuild(): void {
|
|
|
524
532
|
scheduleFlush();
|
|
525
533
|
}
|
|
526
534
|
|
|
535
|
+
/**
|
|
536
|
+
* Drop every CONTENT-SPACE overlay's hold on the scroll container, as part of a
|
|
537
|
+
* wipe. Four elements sit INSIDE the container carrying a `top` in content
|
|
538
|
+
* coordinates — the caret, the predicted cursor, the IME view and the consumer's
|
|
539
|
+
* hidden textarea — so each one holds the container's scrollable overflow at the
|
|
540
|
+
* offset it was last placed at. `output.replaceChildren()` removes the rows and
|
|
541
|
+
* leaves all four.
|
|
542
|
+
*
|
|
543
|
+
* Measured on a 4769-row tab in Chromium: after the wipe the scroller had ZERO
|
|
544
|
+
* rows of content and an 81081px scroll range, held by the caret and the
|
|
545
|
+
* textarea still sitting at 81064px, with the viewport parked at 80281px over
|
|
546
|
+
* nothing. Two consequences, and the second is why this is not cosmetic:
|
|
547
|
+
*
|
|
548
|
+
* - The reader sees that empty region, which paints the terminal's background:
|
|
549
|
+
* a black pane, and then a partial row clipped to the top edge once the
|
|
550
|
+
* drain's growing block reaches the parked offset.
|
|
551
|
+
* - `stickToBottom` is DISARMED. It measures `distanceFromBottom()` against
|
|
552
|
+
* the phantom height, reads 0 — already at the bottom — and pins nothing, so
|
|
553
|
+
* the invariant that would otherwise rescue the view refuses to act for
|
|
554
|
+
* exactly as long as the phantom height survives.
|
|
555
|
+
*
|
|
556
|
+
* The renderer heals all four at the END of the first flush
|
|
557
|
+
* (`positionCursorOverlay` plus `onCursorMove`), so the window is normally one
|
|
558
|
+
* frame and invisible. It stops being one frame whenever that tail does not
|
|
559
|
+
* run on time: a long frame (the wipe tears down thousands of rows), a throw
|
|
560
|
+
* mid-drain (the catch skips the tail and the bounded give-up can stop
|
|
561
|
+
* rescheduling), or a full reset arriving with the switch's reconnect. The
|
|
562
|
+
* reported symptom is a scroll tick clearing it, which fits: a scroll re-enters
|
|
563
|
+
* the built block, and the block sits at the TOP of the phantom range.
|
|
564
|
+
*
|
|
565
|
+
* Collapsing here also makes the wipe's clamp SYNCHRONOUS, which two callers
|
|
566
|
+
* need. `noteContentShrink` arms only when it observes the position actually
|
|
567
|
+
* move, so a deferred clamp left the wipe — the largest shrink this module
|
|
568
|
+
* performs — with nothing to announce. And `bind` records
|
|
569
|
+
* `pendingRestore.lastWrote` from the same offset, so a deferred clamp
|
|
570
|
+
* (measured: 80281 -> 4346) then read as a 76000px foreign write and threw away
|
|
571
|
+
* the reading position the switch was restoring.
|
|
572
|
+
*/
|
|
573
|
+
function collapseContentSpaceOverlays(): void {
|
|
574
|
+
// `cursorAbs` is -1 by now, which is the state positionCursorOverlay already
|
|
575
|
+
// hides for; go through it rather than restating the class name here.
|
|
576
|
+
positionCursorOverlay(undefined);
|
|
577
|
+
// The outgoing session's prediction cannot describe the incoming screen
|
|
578
|
+
// either. The consumer re-pushes it from the `onCursorMove` below, which is
|
|
579
|
+
// its call to make and cannot re-inflate anything: `rowEls` is empty, so
|
|
580
|
+
// setPredictedCursor's own fallback places it at the content origin.
|
|
581
|
+
predCursorEl?.classList.remove("visible");
|
|
582
|
+
// The IME view and the hidden textarea are the CONSUMER's, reachable only
|
|
583
|
+
// through the cursor seam — and after a wipe the cursor genuinely has no row,
|
|
584
|
+
// so the seam is accurate rather than borrowed. `getCursorPx` reports the
|
|
585
|
+
// content origin while `rowEls` is empty, so the handler moves them there.
|
|
586
|
+
onCursorMove?.();
|
|
587
|
+
}
|
|
588
|
+
|
|
527
589
|
/**
|
|
528
590
|
* Highest absolute line index the client holds, or -1 if empty. This is the
|
|
529
591
|
* resume `haveThrough` value (it replaces the old DOM-row count). Exposed so
|
package/src/scroll.ts
CHANGED
|
@@ -265,14 +265,16 @@ function writePreservingFollow(next: number): void {
|
|
|
265
265
|
* absolutely-positioned overlay) — and a lingering arm swallows the next real
|
|
266
266
|
* gesture.
|
|
267
267
|
*
|
|
268
|
-
* That last case
|
|
269
|
-
*
|
|
270
|
-
* hidden textarea all sit inside the scroll container carrying a
|
|
271
|
-
*
|
|
272
|
-
*
|
|
273
|
-
*
|
|
274
|
-
*
|
|
275
|
-
*
|
|
268
|
+
* That last case used to be live here rather than illustrative, and it is why
|
|
269
|
+
* the caller's ORDER matters: the caret, the predicted cursor, the IME view and
|
|
270
|
+
* the consumer's hidden textarea all sit inside the scroll container carrying a
|
|
271
|
+
* `top` in CONTENT coordinates, so any one of them left at the old offset holds
|
|
272
|
+
* scrollHeight above the built content and the clamp never happens. `rebuild`
|
|
273
|
+
* now collapses all four as part of its wipe, before it calls this
|
|
274
|
+
* (`render.ts` collapseContentSpaceOverlays), so the clamp it announces is real.
|
|
275
|
+
* A caller that removes content without collapsing whatever else is anchored in
|
|
276
|
+
* that space arms nothing and gets no announcement. See
|
|
277
|
+
* `docs/tab-switch-repaint.md` §6.2.
|
|
276
278
|
*
|
|
277
279
|
* A native-scroll-anchoring adjustment (Chrome/Firefox lowering scrollTop as
|
|
278
280
|
* rows are removed above the viewport) also moves the position and also arms
|