commit 3527c57c6cc2944fa42c4a67f46552eeb33ccd14
parent 1249b550ed17baa980be3378ca9025e1f5886deb
Author: Damian Myrda <damian@prime8.dev>
Date: Thu, 22 Oct 2026 00:37:33 +0000
web: keep the prompt focused across tab reveals, window focus and clicks
The line reveal hides lines with visibility:hidden, which blurs the
focused input, so focusing before it ran left a hollow cursor.
Diffstat:
2 files changed, 33 insertions(+), 9 deletions(-)
diff --git a/internal/web/scripts/modules/reveal.js b/internal/web/scripts/modules/reveal.js
@@ -37,6 +37,8 @@ function cleanup(root) {
el.classList.remove("line-in");
el.style.removeProperty("--line");
}
+ // hidden lines drop focus, so anything that wants it can take it back now
+ document.dispatchEvent(new CustomEvent("reveal:done"));
}
function onScreen(el) {
diff --git a/internal/web/scripts/modules/shell.js b/internal/web/scripts/modules/shell.js
@@ -18,7 +18,7 @@ const LOGIN_KEY = "prime8.lastlogin.v1";
const reducedMotion = () => window.matchMedia("(prefers-reduced-motion: reduce)").matches;
let fsPromise = null;
-const shellHandlers = { keydown: null, storage: null };
+const shellHandlers = { keydown: null, storage: null, click: null, winFocus: null, revealed: null };
function loadFS() {
if (!fsPromise) {
@@ -777,13 +777,35 @@ export function initShell() {
if (!load(LOGIN_KEY)) store(LOGIN_KEY, String(Date.now()));
render();
- // A terminal opens focused, on load and on every switch back to about. Skip touch screens, where focus pops the
- // on-screen keyboard, and never steal focus from something else.
- const active = document.activeElement;
- // A clicked tab keeps focus after the swap; coming back to about counts as idle.
- const idle = !active || active === document.body || active === document.documentElement ||
- !!active.closest?.("#tabs");
- if (!live.hidden && idle && window.matchMedia("(pointer: fine)").matches) {
- input.focus({ preventScroll: true });
+ // A terminal is focused whenever nothing else wants the keyboard: on load,
+ // on every switch back to about, when the window regains focus, and on a
+ // click anywhere that isn't a link, a control or a text selection. Touch
+ // screens are skipped, since focus pops the on-screen keyboard.
+ const finePointer = window.matchMedia("(pointer: fine)").matches;
+ const focusPrompt = () => {
+ if (!section.isConnected || live.hidden || !finePointer) return;
+ const active = document.activeElement;
+ // a clicked tab keeps focus after the swap; that still counts as idle
+ const idle = !active || active === document.body || active === document.documentElement ||
+ !!active.closest?.("#tabs");
+ if (idle && document.visibilityState === "visible") input.focus({ preventScroll: true });
+ };
+
+ for (const [ev, key, target] of [["click", "click", document], ["focus", "winFocus", window], ["reveal:done", "revealed", document]]) {
+ if (shellHandlers[key]) target.removeEventListener(ev, shellHandlers[key]);
}
+ shellHandlers.click = (e) => {
+ if (!section.isConnected || e.button !== 0) return;
+ if (e.target.closest?.("a, button, input, textarea, select, label, summary, [tabindex], #help-overlay")) return;
+ if (String(window.getSelection?.() || "")) return;
+ if (window.isLightboxOpen?.()) return;
+ focusPrompt();
+ };
+ shellHandlers.winFocus = () => setTimeout(focusPrompt, 0);
+ shellHandlers.revealed = focusPrompt;
+ document.addEventListener("click", shellHandlers.click);
+ window.addEventListener("focus", shellHandlers.winFocus);
+ document.addEventListener("reveal:done", shellHandlers.revealed);
+
+ focusPrompt();
}