agentgrep(feat[tui]): Chat layout and deductive search#89
Open
tony wants to merge 6 commits into
Open
Conversation
Owner
Author
Code reviewFound 1 issue:
agentgrep/src/agentgrep/ui/workflows/deductive.py Lines 24 to 29 in b28e4c0 🤖 Generated with Claude Code - If this code review was useful, please react with 👍. Otherwise, react with 👎. |
why: Workflow.BINDINGS has been declared on the Workflow protocol since ADR 0013 but was never installed on the hosting screen, so a workflow's own keys could never fire. The upcoming deductive workflow needs widen/clear keys, so the seam must actually wire workflow-owned bindings and route them back to the strategy object. what: - Add Workflow.on_action(host, action_id) to the protocol; no-op (returns False) on SearchWorkflow and BrowseWorkflow. - Install the active workflow's BINDINGS onto LayoutScreen on every attach (mount, swap, resume) and remove them by identity on swap, replacing the per-key bucket first since BindingsMap.copy() shares the class-level lists. - Add LayoutScreen.action_workflow to delegate parameterized key actions into the active workflow (one bounded call, pump-safe). - Cover on_action no-ops and prove end-to-end key routing + removal with Pilot tests (a dead binding is invisible to the static guard).
why: The explorer needed a Claude-Code/pi-style lens that reads a search as a conversation — each query a turn, matches streaming in beneath it — both as a layout in its own right and as the substrate the deductive narrowing workflow will render into. what: - Add widgets/turns.py: frozen+slots Turn value objects (QueryTurn/ResultTurn/SystemTurn), a MessageTurn bubble carrying its value object, and a non-slotted TurnRenderer dispatching by type via singledispatchmethod (bounded renders — first line + path, never a full-body highlight). - Add widgets/transcript.py: ConversationLog, an append-only VerticalScroll(layout: stream) that mounts turns and never recomposes, with a turn budget. - Add layouts/chat.py: ChatLayout (full WorkflowHost over the shared greplog non-blocking transport; per-block result cap + count note) and a self-contained DetailScreen that builds heavy bodies off the pump. Register the chat layout and export the new widgets. - Cover the renderer (pure), the layout streaming/filter/cap/detail (Pilot), and update the registry/cycle tests for the third layout.
why: The headline feature — a "deductive" search where the first prompt fixes a haystack and each later prompt narrows within it, with a key to widen back out. It composes with the chat layout (the conversation reads as 1240 → 88 → 12) and any other layout. what: - Add workflows/deductive.py: DeductiveWorkflow holds an immutable refinement stack; first submit runs one engine search (the haystack), later submits narrow in-memory via a composed-AND filter_loaded, ctrl+up widens (pops + re-seeds the prompt), ctrl+l clears. Narrowing routes through filter_loaded only, so a future disk re-grep is a drop-in (compose all frames, run_search). - Add widgets/breadcrumb.py: RefinementBreadcrumb renders the active path (all ▸ a ▸ b) and hides when empty. - Extend the WorkflowHost protocol with set_input_text and update_breadcrumb; the chat layout renders both, while HUD and grep-log re-seed the input and no-op the breadcrumb (PL-6). - Register the deductive workflow; cover the narrowing matrix against a recording host and prove ctrl+up routes end-to-end on the chat layout (binding install + on_action + breadcrumb shrink).
why: The workflow seam grew structural members across this branch — Workflow.on_action and the WorkflowHost set_input_text / update_breadcrumb hooks — so the pre-existing test fakes and a few turn/renderer assertions no longer satisfied the protocols under the full ty check (the per-step focused checks did not span the test tree). what: - Add on_action to the recording workflow fake and set_input_text / update_breadcrumb to the recording host fake. - Narrow Turn subclass and Rich Text accesses in the chat/turn tests with explicit casts / isinstance guards. - Drop two now-redundant casts in the binding tests.
…014) why: The chat layout and deductive workflow make genuine contract changes — the WorkflowHost surface grew, the dead Workflow.BINDINGS were wired, and on_action was added — so they warrant their own ADR rather than landing as new registry instances. what: - Add ADR 0014 (DN-1..DN-5): in-memory fixed-haystack narrowing (not a re-query) with the re-grep seam left open, the immutable refinement stack, the append-only frozen transcript + singledispatch renderer, the installed-bindings / on_action / host-hook seam, and cross-layout orthogonality; extends ADR 0012/0013, honors 0011. - List the chat layout and deductive workflow in the TUI guide.
why: A recall-mode review of the chat/deductive work caught a feature-breaker plus several non-blocking and lifecycle gaps the green gate missed — the deductive tests asserted host-call shape against a fake host but never ran the matcher, so a composed filter that matched nothing slipped through. what: - Compose a single refinement verbatim instead of wrapping it in parens; '(python)' parsed as a literal term and matched nothing, so the first narrow returned zero results. - Supersede the in-flight search generation when narrowing, so a still-streaming haystack's late batches are dropped instead of mounting unfiltered into the narrowed block (NB-10). - Bound the result-turn render (slice before splitlines) so a large body never freezes the pump. - Route a worker-thread error through the gated emitter so a superseded search's exception can't freeze the live status. - Guard DetailScreen._present against the modal being dismissed before its off-pump body build returns. - Drop the misleading composed-text bubble on a programmatic widen. - Highlight detail with the active refinement terms (via the filter matcher), and clear the breadcrumb on run_search/reset_view. - Add an end-to-end test that narrows over real records through the engine seam (the gap that let the paren bug through).
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
chatTUI layout — a Claude-Code/pi-style conversation transcript where each query is ayou ▸ …turn and matches stream in beneath it as result bubbles, capped per block with a count note so the narrowing reads straight down the screen (1240 → 88 → 12).deductiveworkflow — the first prompt fixes a haystack with one engine search; each later prompt narrows within it in-memory;ctrl+upwidens back out andctrl+lclears. It composes with any layout, not justchat.Workflow.BINDINGSseam — bindings declared on a workflow were never installed on the hosting screen, so workflow-owned keys could never fire. Bindings now install on attach (removed by identity on swap), and anon_actionhook routes parameterized key actions back into the Textual-free strategy object.WorkflowHostsurface withset_input_text(re-seed the prompt after a widen) andupdate_breadcrumb(repaint the refinement path); the chat layout renders both, while HUD and grep-log re-seed the input and no-op the breadcrumb so deductive narrows their views too.What it looks like
$ agentgrep ui --layout chat --workflow deductiveChanges by area
Layouts
src/agentgrep/ui/layouts/chat.py:ChatLayout(fullWorkflowHostover the shared grep-log non-blocking transport — gated emitter,@offloadworker,@pump_onlyapply, chunked mounts) plus a self-containedDetailScreenmodal that builds heavy bodies off the pump and bails if dismissed mid-build.src/agentgrep/ui/layouts/_base.py: install/remove the active workflow'sBINDINGSon every attach, andaction_workflowto delegate key actions into the workflow.src/agentgrep/ui/layouts/{hud,greplog}.py:set_input_text+ no-opupdate_breadcrumbso deductive composes there without crashing.Workflows
src/agentgrep/ui/workflows/deductive.py:DeductiveWorkflowwith an immutable refinement-frame stack; narrows viafilter_loaded, widens by popping.src/agentgrep/ui/workflows/_protocol.py:Workflow.on_actionand the two newWorkflowHosthooks;search.py/browse.pyget no-opon_action.Widgets
src/agentgrep/ui/widgets/turns.py: frozen-slotsTurnvalue objects, aMessageTurnbubble, and a non-slottedTurnRendererdispatching by type viasingledispatchmethod(bounded — first line + compact path).src/agentgrep/ui/widgets/transcript.py:ConversationLog, an append-onlyVerticalScroll(layout: stream)that mounts turns and never recomposes.src/agentgrep/ui/widgets/breadcrumb.py:RefinementBreadcrumbfor the narrowing path.Registry & docs
src/agentgrep/ui/registry.py: register thechatlayout anddeductiveworkflow.Design decisions
SearchInvoker.run) only accepts aSearchQuery, not a set of record IDs, so narrowing filters the already-loaded haystack with a composedAND(a single refinement passes through verbatim; two or more are paren-grouped clauses). This is the correct "fixed haystack" semantics and the cheaper non-blocking choice. It routes throughfilter_loadedonly, so a future disk re-grep escape hatch is a drop-in — compose every frame including the base and callrun_search— with no data-model change. Accepted gap: in-memory narrowing can't see records beyond the haystack's initial limit (the re-grep hatch is the planned mitigation).frozen=True, slots=Trueturn has no__dict__, so derived state lives on the non-slottedTurnRenderer. Renders are bounded (sliced before line-splitting); the detail modal builds heavy bodies off the pump.WorkflowHostprotocol surface and adds theon_actionrouting seam (a contract future workflows depend on), so it's recorded as ADR 0014 extending the reusable-widget and pluggable-layout ADRs and leaving the non-blocking invariants unchanged.Verification
The new layout and workflow are registered:
$ rg -n "_load_chat|_load_deductive" src/agentgrep/ui/registry.pyThe transcript is append-only — it must never recompose:
$ rg -n "recompose" src/agentgrep/ui/widgets/transcript.pyJSON parsing stays confined to the bounded detail-body builder:
$ rg -n "json\.(loads|dumps)" src/agentgrep/ui/layouts/chat.pyWorkflow bindings are installed and routed:
$ rg -n "make_bindings|action_workflow|on_action" src/agentgrep/ui/layouts/_base.pyTest plan
uv run ruff check .anduv run ruff format .— cleanuv run ty check— clean (full tree, including tests)uv run pytest— full suite greentest_ui_deductive.py— narrowing matrix against a recording host, the composed-filter form (single verbatim vs AND-grouped), and an end-to-end narrow over real records through the engine seam, plusctrl+uprouting on the chat layouttest_ui_chat.py— streaming mounts result turns, filter appends a narrowed block, the per-block cap bounds mounted turns, detail opens on a focused resulttest_ui_pluggable.py— binding install/route/remove end-to-end and theF2/F3cycle through the third layout/workflowtest_ui_widgets.py—TurnRendererdispatch + bounded result rendertest_tui_non_blocking.py— the static guard scans the new chat/turns/transcript pump entrypointsjust build-docs— ADR 0014 renders and cross-references resolve--layout chat --workflow deductiveonce underAGENTGREP_TUI_WATCHDOG=1against a large real store