<feed xmlns='http://www.w3.org/2005/Atom'>
<title>ida-tui.git/tests, branch main</title>
<subtitle>tui for headless ida</subtitle>
<id>https://git.sl0p.foo/ida-tui.git/atom/tests?h=main</id>
<link rel='self' href='https://git.sl0p.foo/ida-tui.git/atom/tests?h=main'/>
<link rel='alternate' type='text/html' href='https://git.sl0p.foo/ida-tui.git/'/>
<updated>2026-08-21T10:20:02Z</updated>
<entry>
<title>scenarios: cover the event-&gt;UI auto-refresh path</title>
<updated>2026-08-21T10:20:02Z</updated>
<author>
<name>blasty</name>
<email>peter@haxx.in</email>
</author>
<published>2026-08-21T10:20:02Z</published>
<link rel='alternate' type='text/html' href='https://git.sl0p.foo/ida-tui.git/commit/?id=820c356204cb5552a2b4b4ad3591a134d1b6d1b0'/>
<id>urn:sha1:820c356204cb5552a2b4b4ad3591a134d1b6d1b0</id>
<content type='text'>
PR #1's live refresh had client-level coverage only (the listener thread
and debounce, in test_nexus_client); the app half -- _refresh_idb_events
invalidating, reindexing, and reloading the active surface -- was untested.

The scenario renames straight through the client so no UI cache hears
about it (what another client's edit looks like from this process), hands
the app a synthetic batch, and requires: the status says why the view is
about to move, the listing rebuilds around the same cursor and viewport
address, the function index carries the external name, pseudocode
decompiled afterwards shows it, and a second batch landing while decomp is
active reloads it in place. That second batch also reverts the rename, so
the scenario is idempotent.
</content>
</entry>
<entry>
<title>reformat: ruff format + import sort, mechanically (see ruff.toml)</title>
<updated>2026-08-21T10:15:15Z</updated>
<author>
<name>blasty</name>
<email>peter@haxx.in</email>
</author>
<published>2026-08-21T10:14:46Z</published>
<link rel='alternate' type='text/html' href='https://git.sl0p.foo/ida-tui.git/commit/?id=02d02417800184fb76cd0245cdaa94c437aa4081'/>
<id>urn:sha1:02d02417800184fb76cd0245cdaa94c437aa4081</id>
<content type='text'>
No behavior. Listed in .git-blame-ignore-revs (next commit).
</content>
</entry>
<entry>
<title>tests: repair the two seams the ida-nexus port left behind</title>
<updated>2026-08-21T10:12:53Z</updated>
<author>
<name>blasty</name>
<email>peter@haxx.in</email>
</author>
<published>2026-08-21T10:12:53Z</published>
<link rel='alternate' type='text/html' href='https://git.sl0p.foo/ida-tui.git/commit/?id=ea22067c52ec335ea4d3a616cb2107d5fdbdf7b6'/>
<id>urn:sha1:ea22067c52ec335ea4d3a616cb2107d5fdbdf7b6</id>
<content type='text'>
Two scenario monkeypatch sites still hooked client.invoke -- the rename to
call() updated the call sites but not the patches, so reprime_is_free and
split_view crashed instead of counting. call() takes the remote_ops
declaration itself now, so both count by its __name__.

remote_ops imported RemoteModule inside _bindings(), which made
test_nexus_client (NEEDS_IDA = False) unrunnable under a stdlib-only
python3 -- the house rule tests/run.py --fast depends on. The import moves
to the same eagerly-if-present, bound-to-None-so-patchable contract
nexus_client uses, and the test injects FakeRemoteModule/FakeRemoteError
exactly like its other fakes: real names when the library is installed,
strict fakes when it is not.
</content>
</entry>
<entry>
<title>Merge PR #1 from mrexodia: Windows support and auto-refresh on events</title>
<updated>2026-08-21T10:09:21Z</updated>
<author>
<name>blasty</name>
<email>peter@haxx.in</email>
</author>
<published>2026-08-21T10:09:12Z</published>
<link rel='alternate' type='text/html' href='https://git.sl0p.foo/ida-tui.git/commit/?id=9541043c9dee7973c140159b3af561c6d0405e9e'/>
<id>urn:sha1:9541043c9dee7973c140159b3af561c6d0405e9e</id>
<content type='text'>
https://github.com/blasty/ida-tui/pull/1 — from the ida-nexus (né
ida-codemode) maintainer: kitty-graphics fallback for platforms without
termios, Ctrl+R view refresh, typed remote operations through ida_nexus
RemoteModule, live auto-refresh from other clients' IDB events (upstream
item 7), discard-without-save (item 6), and the ida-nexus 0.7.0 rename.

tests/test_kittygfx.py is the union of both sides' files: our escape-
construction checks plus the PR's cross-platform fallback checks.
</content>
</entry>
<entry>
<title>splash: give the logo a placement id so re-anchoring replaces, not stacks</title>
<updated>2026-08-21T10:08:42Z</updated>
<author>
<name>blasty</name>
<email>peter@haxx.in</email>
</author>
<published>2026-08-21T10:08:42Z</published>
<link rel='alternate' type='text/html' href='https://git.sl0p.foo/ida-tui.git/commit/?id=7a3b198a904da186cc2860df059b063db24282ea'/>
<id>urn:sha1:7a3b198a904da186cc2860df059b063db24282ea</id>
<content type='text'>
A kitty placement is identified by (image id, placement id); an a=p with no
p= key is anonymous and every one stacks another copy. The splash re-anchors
on every progress note (~5/s), so a 60s load ended with ~300 placements of an
RGBA logo alpha-compositing over each other -- soft edges creeping to solid,
and the terminal re-rendering all of them per frame.

Same pair every time (LOGO_PLACEMENT) = the terminal REPLACES the placement,
so re-anchoring is free and atomic. That is also why on_resize no longer
clear()s first (that showed a hole for a frame), and why a shrunk-to-nothing
region now drops the placement instead of leaving a stale one anchored.

tests/test_kittygfx.py pins the escapes (pure, stdlib);
experiments/splash_place_count.py counts what a real splash sends.
</content>
</entry>
<entry>
<title>Switch to ida-nexus</title>
<updated>2026-08-20T21:55:52Z</updated>
<author>
<name>Duncan Ogilvie</name>
<email>mr.exodia.tpodt@gmail.com</email>
</author>
<published>2026-08-20T21:55:52Z</published>
<link rel='alternate' type='text/html' href='https://git.sl0p.foo/ida-tui.git/commit/?id=f4c1d9b5497fd0d38137b6b345e8171b307c1117'/>
<id>urn:sha1:f4c1d9b5497fd0d38137b6b345e8171b307c1117</id>
<content type='text'>
</content>
</entry>
<entry>
<title>Adopt idb_events and remote module features from ida-codemode</title>
<updated>2026-08-20T21:42:42Z</updated>
<author>
<name>Duncan Ogilvie</name>
<email>mr.exodia.tpodt@gmail.com</email>
</author>
<published>2026-08-20T21:42:42Z</published>
<link rel='alternate' type='text/html' href='https://git.sl0p.foo/ida-tui.git/commit/?id=f3715d8de0d255c8b14710acfa120ccb9ea953fd'/>
<id>urn:sha1:f3715d8de0d255c8b14710acfa120ccb9ea953fd</id>
<content type='text'>
</content>
</entry>
<entry>
<title>Add Ctrl+R to refresh all views</title>
<updated>2026-08-17T13:22:09Z</updated>
<author>
<name>Duncan Ogilvie</name>
<email>mr.exodia.tpodt@gmail.com</email>
</author>
<published>2026-08-17T13:22:09Z</published>
<link rel='alternate' type='text/html' href='https://git.sl0p.foo/ida-tui.git/commit/?id=10142605e0137fc27b542646c884d2587c6dcf12'/>
<id>urn:sha1:10142605e0137fc27b542646c884d2587c6dcf12</id>
<content type='text'>
</content>
</entry>
<entry>
<title>Add kitty graphics fallback for Windows support</title>
<updated>2026-08-17T13:21:59Z</updated>
<author>
<name>Duncan Ogilvie</name>
<email>mr.exodia.tpodt@gmail.com</email>
</author>
<published>2026-08-17T13:21:59Z</published>
<link rel='alternate' type='text/html' href='https://git.sl0p.foo/ida-tui.git/commit/?id=61b93538fe6e03f175b907c19c2d196ecd860782'/>
<id>urn:sha1:61b93538fe6e03f175b907c19c2d196ecd860782</id>
<content type='text'>
</content>
</entry>
<entry>
<title>Port to the ida-codemode 0.5.3+ public API</title>
<updated>2026-08-13T22:26:19Z</updated>
<author>
<name>blasty</name>
<email>peter@haxx.in</email>
</author>
<published>2026-08-13T22:26:19Z</published>
<link rel='alternate' type='text/html' href='https://git.sl0p.foo/ida-tui.git/commit/?id=db3576d7b4174e78222203a7d5246c5ee080049b'/>
<id>urn:sha1:db3576d7b4174e78222203a7d5246c5ee080049b</id>
<content type='text'>
0.5.x turned ida_codemode.__all__ into a real public API and hid the rest:
client.py -&gt; handle.py, registry.py -&gt; _registry.py + instances.py,
resolver.py -&gt; _resolver.py. Every import we had was from a module that no
longer exists, so the TUI could not attach at all.

  - handle.entry            -&gt; handle.instance (RegistryEntry -&gt; DatabaseInstance)
  - 30 keyword-only options -&gt; DatabaseOpenOptions(...) passed as options=
  - IdbBusy                 -&gt; DatabaseBusyError
  - InstanceDisconnected/ClientError -&gt; DatabaseDisconnected/CodeModeConnectionError
  - our scan_instances()+idb_key() ownership walks -&gt; find_database_owner()
  - our FileLock poking (_wait_for_entry_release)  -&gt; wait_database_released()
  - registry.discover_instances() -&gt; discover_databases() + InstanceState

_database_exists() is deleted with it: upstream now drops the loader switches
itself when reopening an existing IDB (_resolver._build_worker_command), which
is the same fix we had client-side. See docs/CODEMODE_UPSTREAM.md section 4 for
the one invocation that still slips through.

The offline contract suite has to keep running under a stdlib-only python3,
where every Code Mode name is bound to None -- so it now injects a strict fake
DatabaseOpenOptions and a real DatabaseBusyError exception alongside the fake
handle. Without the latter, `except DatabaseBusyError` is `except None`, and
the TypeError it raises masks whatever actually failed inside the try. The
loader-option names moved inside the options dataclass, so the guard that
caught `loading_address` vs `image_base` moved with them
(_option_fields_are_real).

uv.lock pins 0.6.1; ~/ida-venv and .venv are on 0.6.1 with the ida-domain
0.5.1 / zeromcp 1.8.0 floors it requires. Full gate: 1065 passed.
</content>
</entry>
</feed>
