fix(hermes): preserve paused editors during terminal reattachment
This commit is contained in:
parent
15c79c6da4
commit
cfcd932065
@ -1623,9 +1623,12 @@ RUN case "${HERMES_KANIKO_HEREDOC_COMPAT}" in 0) ;; 1) python /tmp/hermes-kaniko
|
||||
# alternate screen and current viewport after a bounded replay.
|
||||
COPY dockerfiles/patch-hermes-terminal-replay.py /tmp/patch-hermes-terminal-replay.py
|
||||
COPY dockerfiles/hermes-terminal-replay-regression.py /tmp/hermes-terminal-replay-regression.py
|
||||
COPY dockerfiles/hermes-terminal-resume-regression.js /tmp/hermes-terminal-resume-regression.js
|
||||
RUN /opt/hermes/.venv/bin/python /tmp/patch-hermes-terminal-replay.py \
|
||||
&& HERMES_SOURCE_ROOT=/opt/hermes /opt/hermes/.venv/bin/python \
|
||||
/tmp/hermes-terminal-replay-regression.py
|
||||
/tmp/hermes-terminal-replay-regression.py \
|
||||
&& node /tmp/hermes-terminal-resume-regression.js \
|
||||
&& node --check /opt/hermes/ui-tui/dist/entry.js
|
||||
|
||||
# The dashboard keeps keyboard and paste handling unchanged, but forwards only
|
||||
# bounded SGR wheel reports when the TUI has explicitly enabled mouse tracking.
|
||||
|
||||
@ -7,6 +7,7 @@
|
||||
!dockerfiles/hermes-session-migrate.py
|
||||
!dockerfiles/patch-hermes-terminal-replay.py
|
||||
!dockerfiles/hermes-terminal-replay-regression.py
|
||||
!dockerfiles/hermes-terminal-resume-regression.js
|
||||
!dockerfiles/patch-hermes-dashboard-wheel.js
|
||||
!dockerfiles/hermes-dashboard-terminal-input.ts
|
||||
!dockerfiles/hermes-dashboard-terminal-input.test.ts
|
||||
|
||||
33
dockerfiles/hermes-terminal-resume-regression.js
Normal file
33
dockerfiles/hermes-terminal-resume-regression.js
Normal file
@ -0,0 +1,33 @@
|
||||
/** Exercise the pinned source and shipped SIGCONT handlers without starting Hermes. */
|
||||
const assert = require("node:assert/strict");
|
||||
const fs = require("node:fs");
|
||||
const path = require("node:path");
|
||||
const root = process.env.HERMES_SOURCE_ROOT || "/opt/hermes";
|
||||
|
||||
const handlers = [
|
||||
["ui-tui/packages/hermes-ink/src/ink/ink.tsx", /^ private handleResume = \(\) => \{[\s\S]*?^ }/m],
|
||||
["ui-tui/dist/entry.js", /^ handleResume = \(\) => \{[\s\S]*?^ };/m],
|
||||
];
|
||||
|
||||
for (const [file, pattern] of handlers) {
|
||||
const source = fs.readFileSync(path.join(root, file), "utf8");
|
||||
const match = source.match(pattern);
|
||||
assert.ok(match, `missing pinned SIGCONT handler: ${file}`);
|
||||
// Evaluate only the reviewed handler, with renderer effects replaced by spies.
|
||||
const Renderer = new Function(`return class { ${match[0].replace("private ", "")} }`)();
|
||||
for (const [isTTY, isPaused, expected] of [
|
||||
[true, false, 1],
|
||||
[true, true, 0],
|
||||
[false, false, 0],
|
||||
]) {
|
||||
const renderer = new Renderer();
|
||||
let repaints = 0;
|
||||
renderer.options = { stdout: { isTTY } };
|
||||
renderer.isPaused = isPaused;
|
||||
renderer.altScreenActive = true;
|
||||
renderer.reenterAltScreen = () => { repaints++; };
|
||||
renderer.handleResume();
|
||||
assert.equal(repaints, expected, `${file}: TTY=${isTTY}, paused=${isPaused}`);
|
||||
}
|
||||
}
|
||||
console.log("dashboard SIGCONT source and shipped handlers passed");
|
||||
@ -62,6 +62,25 @@ def patch_sources(root: Path) -> None:
|
||||
" self._has_attached = True\n",
|
||||
),
|
||||
),
|
||||
# The pinned image ships readable esbuild output without the TUI build
|
||||
# dependencies. Keep its source and shipped handler in sync, and reject
|
||||
# either anchor changing rather than resolving a new dependency graph.
|
||||
"ui-tui/packages/hermes-ink/src/ink/ink.tsx": (
|
||||
(
|
||||
" private handleResume = () => {\n"
|
||||
" if (!this.options.stdout.isTTY) {\n",
|
||||
" private handleResume = () => {\n"
|
||||
" if (!this.options.stdout.isTTY || this.isPaused) {\n",
|
||||
),
|
||||
),
|
||||
"ui-tui/dist/entry.js": (
|
||||
(
|
||||
" handleResume = () => {\n"
|
||||
" if (!this.options.stdout.isTTY) {\n",
|
||||
" handleResume = () => {\n"
|
||||
" if (!this.options.stdout.isTTY || this.isPaused) {\n",
|
||||
),
|
||||
),
|
||||
}
|
||||
prepared = {}
|
||||
for relative, replacements in patches.items():
|
||||
@ -71,7 +90,8 @@ def patch_sources(root: Path) -> None:
|
||||
if source.count(before) != 1:
|
||||
raise ValueError(f"Hermes terminal replay patch context changed: {relative}")
|
||||
source = source.replace(before, after, 1)
|
||||
compile(source, str(path), "exec")
|
||||
if path.suffix == ".py":
|
||||
compile(source, str(path), "exec")
|
||||
prepared[path] = source
|
||||
for path, source in prepared.items():
|
||||
path.write_text(source)
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user