<feed xmlns='http://www.w3.org/2005/Atom'>
<title>ida-tui.git/docs/TEXTUAL_NOTES.md, branch pre-codemode</title>
<subtitle>tui for headless ida</subtitle>
<id>https://git.sl0p.foo/ida-tui.git/atom/docs/TEXTUAL_NOTES.md?h=pre-codemode</id>
<link rel='self' href='https://git.sl0p.foo/ida-tui.git/atom/docs/TEXTUAL_NOTES.md?h=pre-codemode'/>
<link rel='alternate' type='text/html' href='https://git.sl0p.foo/ida-tui.git/'/>
<updated>2026-07-26T12:36:12Z</updated>
<entry>
<title>app: delete DisasmView, superseded by the unified listing</title>
<updated>2026-07-26T12:36:12Z</updated>
<author>
<name>blasty</name>
<email>blasty@local</email>
</author>
<published>2026-07-26T12:36:12Z</published>
<link rel='alternate' type='text/html' href='https://git.sl0p.foo/ida-tui.git/commit/?id=fd696d8cf080c37c1cbe7696f21fe8d5b8c468e4'/>
<id>urn:sha1:fd696d8cf080c37c1cbe7696f21fe8d5b8c468e4</id>
<content type='text'>
DisasmView was the function-scoped code view from before the unification. The
app hasn't instantiated it since — compose() yields only ListingView, and even
the test harness's Ctx.dis returns ListingView with a comment saying so. Its
CursorMoved messages had no handler, so every one it posted went nowhere.

304 lines of rendering, search, cursor and navigation logic that never ran. It
also cost real time this week: it made assembly highlighting look like a job
that needed doing twice, and its isinstance branches in the follow and xrefs
handlers were unreachable twins of the ListingView branch directly below them,
which is exactly the kind of thing you read carefully before realising it can't
execute.

Gone with it: the dead branches (folded into the ListingView ones, keeping the
fall-through-edge comment that was worth keeping), its CSS rule, and the
comments that pointed at it as though it were a live alternative.

DisasmModel STAYS — the domain still uses it to index a function's instructions
(_do_edit_item resolves a row within a function that way). Only the widget was
dead.

209/0 scenarios, 26/0 blob, 30/0 project UI, 36/0 index, 32/0 formats, 39/0
project, 27/0 pool. Smoke-tested a live pane afterwards: the listing renders,
highlighted, and `drive where` answers.
</content>
</entry>
<entry>
<title>load dialog: reachable address field, project mode, and a way back from a bad answer</title>
<updated>2026-07-25T22:25:35Z</updated>
<author>
<name>blasty</name>
<email>blasty@local</email>
</author>
<published>2026-07-25T22:25:35Z</published>
<link rel='alternate' type='text/html' href='https://git.sl0p.foo/ida-tui.git/commit/?id=1e246a1136c5b5d4d84f6a796391007fa7ad0abc'/>
<id>urn:sha1:1e246a1136c5b5d4d84f6a796391007fa7ad0abc</id>
<content type='text'>
Three bugs, reported together, with one shared root: you couldn't get to the
address field, so the address went into the processor filter, so IDA got a
nonsense processor name and refused to open — and the app dead-ended with a
misleading error.

**Tab never reached any modal.** Binding("tab,shift+tab", "toggle_view",
priority=True) is an APP binding, and priority bindings run before the focus
chain. Nothing in any dialog in this app could ever be tabbed to; the load
dialog is just where it finally mattered. action_toggle_view now hands the key
back when a modal is up, which fixes it everywhere.

**...and DOM order was the wrong tab order anyway.** focus_next() stopped at the
processor list, which is arrow-driven and has nothing to type. LoadOptionsScreen
overrides it to cycle the two fields you actually type into.

**...and the dialog outgrew the terminal.** With the palette's default
max-height the 21-row list pushed the address field and help line off the bottom
of the screen. Nothing errors — the field simply isn't there, which reads as
"Tab does nothing". Capped per-dialog.

**Project mode never asked.** _should_ask_load_options bailed on
`self._project is not None` with the comment "project mode carries per-binary
options already" — true only if someone had already filled them in. A raw blob
added to a project got the silent x86-at-0 treatment the dialog exists to
prevent. Now asked at boot AND on switching to an undescribed binary, and the
answer is written back to the project entry (Project.set_load), so it is asked
once per binary, not once per run.

**A rejected answer dead-ended.** Getting a processor wrong is an ordinary
mistake; it left an empty app with "connect failed: worker exited (code 1)" and
a message blaming a locked .i64. The worker now names the real suspect when load
switches were in play, and the app re-opens the dialog instead of giving up.

Verified with real keys in a tmux pane, which is the only way any of this shows
up: Tab -&gt; address field -&gt; 0x8000000 -&gt; Enter -&gt; 35 functions at 0x80039AC; a
bogus processor -&gt; "those load options were rejected — try again" with the
dialog back; project mode -&gt; asks, loads at the right base, and the answer is in
the project file.

tests: +3 scenarios (Tab moves focus under a modal, lands on the address field,
cycles back). 202/0 scenarios, 39/0 project, 32/0 formats, 30/0 project UI.
docs/TEXTUAL_NOTES.md gets the priority-binding and clipped-modal traps.
</content>
</entry>
<entry>
<title>Revert "nav: bind back to backspace as well — a bare Esc isn't reliable in a terminal"</title>
<updated>2026-07-25T18:32:19Z</updated>
<author>
<name>user</name>
<email>user@clank</email>
</author>
<published>2026-07-25T18:32:19Z</published>
<link rel='alternate' type='text/html' href='https://git.sl0p.foo/ida-tui.git/commit/?id=d3788a559bbd74793155d100bc4bff8f350d3a03'/>
<id>urn:sha1:d3788a559bbd74793155d100bc4bff8f350d3a03</id>
<content type='text'>
This reverts commit baf599e143dbf23f33886ba03a60c5d9f3c19078.
</content>
</entry>
<entry>
<title>nav: bind back to backspace as well — a bare Esc isn't reliable in a terminal</title>
<updated>2026-07-25T18:19:34Z</updated>
<author>
<name>blasty</name>
<email>blasty@local</email>
</author>
<published>2026-07-25T18:19:34Z</published>
<link rel='alternate' type='text/html' href='https://git.sl0p.foo/ida-tui.git/commit/?id=baf599e143dbf23f33886ba03a60c5d9f3c19078'/>
<id>urn:sha1:baf599e143dbf23f33886ba03a60c5d9f3c19078</id>
<content type='text'>
Esc-to-go-back needed two presses. It looked like the decomp pane was swallowing
the first one, but the RPC state dump before/after a single Esc settles it:

    nav_depth   3  -&gt;  3      action_back never ran
    cursor.col  19 -&gt; 20      +1 column, i.e. `right`
    modal       null          nothing was up to eat the key

The keypress arrived as a right-arrow. \x1b is both Escape and the lead byte of
every arrow/function-key sequence (ESC [ C is right), so a terminal or tmux that
merges a lone Esc with what follows delivers something else entirely. Nothing in
the nav path touches cursor_x and there is no widget-level escape binding on the
code views, so this was never application logic — I spent two commits looking in
the wrong place before asking for the dump.

Bind back to "escape,backspace". Backspace is unambiguous, so going back never
depends on a byte the terminal can reinterpret; Esc still works wherever it's
delivered intact. tmux users generally want `set -sg escape-time 10` regardless.

docs/TEXTUAL_NOTES.md records the symptom and, more usefully, the triage: check
nav_depth in the state dump first — if the action never ran, it's the input
layer, not the logic.

Verified: backspace in the pseudocode pane takes nav 3 -&gt; 2 and reloads the
previous function. Suite 192/0.
</content>
</entry>
<entry>
<title>docs: sweep for the worker-only reality (drop mcp supervisor/spawn.sh/--db)</title>
<updated>2026-07-24T13:11:54Z</updated>
<author>
<name>blasty</name>
<email>blasty@local</email>
</author>
<published>2026-07-24T13:11:54Z</published>
<link rel='alternate' type='text/html' href='https://git.sl0p.foo/ida-tui.git/commit/?id=b7717df55c8d9a230bd3672b82fb37a7552bbb76'/>
<id>urn:sha1:b7717df55c8d9a230bd3672b82fb37a7552bbb76</id>
<content type='text'>
* RPC.md: the TUI is launched via `./ida-tui &lt;bin&gt; --rpc &lt;sock&gt;` (was
  `idatui.tui --db`); point the "regression lock" note at rpcclient/drive +
  the pilot instead of the deleted rpc_smoke.py.
* PAGING_FINDINGS.md: reframe the intro as the ida-pro-mcp tool functions the
  worker now calls in-process (shapes/caps unchanged); replace the
  supervisor-era "idle self-exit" + "max_workers cap" sections with the
  single-owned-worker lifecycle (no cap, no idle-exit, crash -&gt; reconnect).
* TEXTUAL_NOTES.md / TUI_DRIVING_BLUEPRINT.md: drop the ida-pro-mcp framing and
  the deleted rpc_smoke.py references (-&gt; test_scenarios.py / rpcclient).

Also updated the idatui + idatui-rpc skills (in ~/.pi, outside the repo) to the
worker model: no supervisor/spawn.sh/--db, worker python + $IDATUI_WORKER_PYTHON,
worker.py/worker_client.py/errors.py architecture, and the load-starvation gotcha.
</content>
</entry>
<entry>
<title>names: command-palette fuzzy finder (Ctrl+N), overlay-first</title>
<updated>2026-07-09T21:00:32Z</updated>
<author>
<name>blasty</name>
<email>blasty@local</email>
</author>
<published>2026-07-09T21:00:32Z</published>
<link rel='alternate' type='text/html' href='https://git.sl0p.foo/ida-tui.git/commit/?id=7c58501232d493bc8f132d2d745f62a96ea27d82'/>
<id>urn:sha1:7c58501232d493bc8f132d2d745f62a96ea27d82</id>
<content type='text'>
Replace the docked names pane as the primary way to jump to symbols with a
command-palette overlay: Ctrl+N opens SymbolPalette, type to fuzzy-find (scored
subsequence match with matched-char highlighting), up/down to select, Enter to
open, Esc to close. Mouse-clickable too.

The docked FunctionsPanel now starts hidden and is still reachable/toggleable
with Ctrl+B (classic table view, sort, filter, goto all unchanged); a code view
takes focus on startup. Status hints 'Ctrl+N: find symbol'.

Pilot checks for open/fuzzy/subsequence/select/close. Tests reveal the docked
pane (Ctrl+B) for the existing table-driven checks. full suite 74/74.
</content>
</entry>
<entry>
<title>decompiler: fix stale scroll frame on same-function jump</title>
<updated>2026-07-09T19:27:24Z</updated>
<author>
<name>blasty</name>
<email>blasty@local</email>
</author>
<published>2026-07-09T19:27:24Z</published>
<link rel='alternate' type='text/html' href='https://git.sl0p.foo/ida-tui.git/commit/?id=584af7e8edf6224c0f3affb2bb98230078a9e836'/>
<id>urn:sha1:584af7e8edf6224c0f3affb2bb98230078a9e836</id>
<content type='text'>
DecompView.goto derived its scroll with _scroll_cursor_into_view() -&gt; a plain
scroll_to, which updates scroll_offset but doesn't repaint at the new offset
(Textual only repaints on a rounded scroll change, and there's no layout pass
since the pane isn't reloaded). So a same-function xref jump landed the cursor
correctly but painted the old scroll until the next cursor move nudged a repaint.

Route goto's scroll (derived or explicit) through _apply_scroll, which re-applies
after the next refresh with layout=True -- the same pattern show() uses. The
cross-function path already worked because reloading + clearing the loading cover
forces a fresh paint. Document the gotcha in TEXTUAL_NOTES.

full suite 64/64.
</content>
</entry>
<entry>
<title>docs: TEXTUAL_NOTES.md — Textual pitfalls, app patterns, testing gotchas</title>
<updated>2026-07-09T17:38:46Z</updated>
<author>
<name>blasty</name>
<email>blasty@local</email>
</author>
<published>2026-07-09T17:38:46Z</published>
<link rel='alternate' type='text/html' href='https://git.sl0p.foo/ida-tui.git/commit/?id=6f1a2a2e73974e41ec893cf19ea9c30ebea1d865'/>
<id>urn:sha1:6f1a2a2e73974e41ec893cf19ea9c30ebea1d865</id>
<content type='text'>
Companion to PAGING_FINDINGS.md capturing the hard-won Textual behaviour
(bindings/mixins, ScrollView repaint &amp; scroll-clamp, loading cover blurs focus,
mouse offset mapping, Input/Footer overlap, no cpp grammar) and the app patterns
(name-gen cache invalidation, cursor+scroll history, name+address follow).
</content>
</entry>
</feed>
