╭ changed ────╮ │build.zig.zon│ ╰─────────────╯ ╭─────────────────────────────────────────────────────────────────────────────────────────────────────╮ │ 3 .version = "0.0.0", │ │ 4 .dependencies = .{ │ │ 5 .xit = .{ │ │ 6 - .url = "git+https://github.com/xit-vcs/xit#203ceedd05e2c021f3830f76da5af3217e1dfc2e",│ │ 7 - .hash = "xit-0.0.0-Ub3nDLnuUAFV-ZvaJ_ou1MsEbbssaqY_2iZEhptlWSI3", │ │ 6 + .url = "git+https://github.com/xit-vcs/xit#c6551bc1587169c23cbbe7b38b47d7746d612cae",│ │ 7 + .hash = "xit-0.0.0-Ub3nDLnuUAGeC8rudrtETGmtxePAzEHyoIwcSLkhA9Qe", │ │ 8 }, │ │ 9 }, │ │10 .paths = .{ │ ╰─────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭ changed ───────────╮ │src/embed/index.html│ ╰────────────────────╯ ╭───────────────────────────────────────────────────────────────────────────────╮ │76 overflow: hidden; │ │77 } │ │78 │ │79 - /* overlay elements share the grid's coordinate system since │ │80 - their parent (.page) is the positioning ancestor and the │ │81 - grid sits at its top-left. */ │ │79 + /* native controls are positioned over the matching TUI cells. */│ │80 .page input, │ │81 .page textarea, │ │82 .page button { │ ╰───────────────────────────────────────────────────────────────────────────────╯ ╭ changed ──────────╮ │src/embed/script.js│ ╰───────────────────╯ ╭───────────────────────────────────────────────────────────────────────────────────╮ │159 overlay.innerHTML = html; │ │160 }, │ │161 _focusInput: function (id) { │ │162 - const target = overlay.querySelector(`[data-focus-id="${id}"]`); │ │163 - if (target && document.activeElement !== target) target.focus(); │ │162 + const target = overlay.querySelector(`[data-focus-id="${id}"]`) ||│ │163 + grid.querySelector(`input[data-focus-id="${id}"]`); │ │164 + if (!target) return; │ │165 + if (document.activeElement !== target) target.focus(); │ │166 + if (target.readOnly) target.select(); │ │167 }, │ │168 _navigate: function (ptr, len) { │ │169 // a real browser navigation to another top-level page (the wasm │ ╰───────────────────────────────────────────────────────────────────────────────────╯ ╭──────────────────────────────────────────────────────────────────────────────────────────╮ │217 const ptr = wasmInstance.exports._alloc(bytes.length); │ │218 new Uint8Array(wasmInstance.exports.memory.buffer, ptr, bytes.length).set(bytes);│ │219 wasmInstance.exports._setTextInputValue(focusId, ptr, bytes.length); │ │217 - wasmInstance.exports._tick(minRows(), maxCols()); │ │218 -} │ │219 - │ │220 -function sendEnter(form) { │ │221 - const btn = form.querySelector("button[type=submit][data-focus-id]"); │ │222 - if (!btn) return; │ │223 - wasmInstance.exports._onMouseClick(Number(btn.dataset.focusId)); │ │224 - wasmInstance.exports._onKeyDown(13); │ │220 wasmInstance.exports._tick(minRows(), maxCols()); │ │221 } │ │222 │ ╰──────────────────────────────────────────────────────────────────────────────────────────╯ ╭─────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │335 const t = event.target; │ │336 if (!t || (t.tagName !== "INPUT" && t.tagName !== "TEXTAREA") || !t.dataset.focusId) return;│ │337 sendTextInputValue(Number(t.dataset.focusId), t.value); │ │343 - }); │ │344 - │ │345 - document.addEventListener("click", (event) => { │ │346 - const t = event.target; │ │347 - if (t && t.tagName === "INPUT" && t.readOnly) t.select(); │ │338 }); │ │339 │ │340 document.addEventListener("focusin", (event) => { │ ╰─────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭───────────────────────────────────────────────────────────────────────────────────────╮ │354 }); │ │355 │ │356 grid.addEventListener("click", (event) => { │ │357 + if (event.target.matches("input[readonly]")) { │ │358 + wasmInstance.exports._tick(minRows(), maxCols()); │ │359 + return; │ │360 + } │ │361 const span = event.target.closest(".clickable"); │ │362 if (!span) return; │ │363 // leave modified / non-left clicks (open-in-new-tab, etc.) to the browser│ ╰───────────────────────────────────────────────────────────────────────────────────────╯ ╭ changed ────────╮ │src/main_wasm.zig│ ╰─────────────────╯ ╭─────────────────────────────────────────────────────────────────────────╮ │73 _replaceState(url.ptr, @intCast(url.len)); │ │74 } │ │75 │ │76 - const html = try web.generateHtml(allocator, root_ptr); │ │76 + const html = try web.generateHtml(allocator, root_ptr, &session);│ │77 defer allocator.free(html); │ │78 _setHtml(html.ptr, @intCast(html.len)); │ │79 │ ╰─────────────────────────────────────────────────────────────────────────╯ ╭─────────────────────────────────────────────────────────────────────────────────╮ │80 - // emit the overlay (form + inputs + button) on every tick so the wasm │ │81 - // layout drives positions, not just the server's initial render. JS │ │80 + // emit the editable form overlay on every tick so the wasm layout drives│ │81 + // positions, not just the server's initial render. JS │ │82 // diffs the result against the previous overlay; an unchanged overlay │ │83 // leaves the live <form> alone — crucial since wiping it mid-click │ │84 // would detach the submit button before the browser can dispatch the │ ╰─────────────────────────────────────────────────────────────────────────────────╯ ╭───────────────────────────────────────────────────────────────────────────────────────╮ │90 const root_focus = root_ptr.getFocus(); │ │91 if (root_focus.grandchild_id) |gid| { │ │92 if (root_focus.children.get(gid)) |child| { │ │93 - // browser-focus the focused overlay control (text input, submit button│ │93 + // browser-focus the focused native control (text input, submit button │ │94 // or file picker) so the browser handles typing, Enter-to-submit and │ │95 // Enter-to-open-the-picker natively. without this the control never │ │96 // gets focus and Enter falls through to the wasm. │ ╰───────────────────────────────────────────────────────────────────────────────────────╯ ╭ changed ─╮ │src/ui.zig│ ╰──────────╯ ╭────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │1379 // focus id -> the live TextInput, refreshed each frame by the views that own │ │1380 // inputs. web/wasm form handling looks widgets up here by focus id. │ │1381 text_inputs: std.AutoHashMapUnmanaged(usize, *wgt.TextInput(Widget)) = .empty, │ │1382 - // focus id -> the page-constant text the web overlay renders into an │ │1383 - // input initially, refreshed each frame alongside `text_inputs`. never │ │1384 - // the input's live content: the overlay html must not change as the │ │1382 + // focus id -> the page-constant text the web renderer puts in an input │ │1383 + // initially, refreshed each frame alongside `text_inputs`. never an │ │1384 + // editable input's live content: the overlay html must not change as the │ │1385 // user types, or the diff in _setOverlay would rebuild the element. │ │1386 input_values: std.AutoHashMapUnmanaged(usize, []const u8) = .empty, │ │1387 nav_back: bool = false, // set by input (escape) to request the native TUI pop a page; see Nav│ ╰────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭──────╮ │next →│ ╰──────╯