Repository navigation
Integrate keymap/native hints with accepted sidebar, tab and reader UI - #1305
Merged
Merged
Conversation
Thread rows already alternate between the sidebar tone and one step off it. Channel headers now alternate on their own pair, stepped from the sidebar tone toward the theme's primary hue, so the channel list and its threads read as two families without separator rows. One mechanism owns alternation: AlternatingTone (tone_paint.py) holds the row's parity and its declared tone pair. Thread rows keep the neutral pair; ChannelHeader declares the hue pair. The reactive flag and the tone branch on CommsRow are deleted. The hue tones are derived colours declared beside the other Textual-backend colours; native ANSI palettes resolve them to the background like every derived tone. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01RnH6dR85kyFKcc2tQAJLhK
Root cause of the desync: the underline was a 1fr child of the scrolling tab strip, so it was exactly one viewport wide while the cells it marks are as wide as all tabs. Once tabs overflowed, any selected tab past the first viewport width of cells had a span beyond the underline's last column and was drawn with no underline at all (probe: strip 100 wide, cells 206, selected span 154-167, underline 0-100). The cells and the underline now share one parent, TabCells, which is as wide as the cells or the strip, whichever is wider; the underline spans exactly the cells' columns by layout. The marked span is the tab's whole cell (label and close button), not its text. Motion: when the marked cells move (another tab chosen, a tab opened, closed, renamed or reordered) the underline slides from what it drew to the new cells with TAB_SLIDE on the app clock, one row repainted per frame. Tabs that open or move rise into place with the existing NEW_TEXT fade. The whole-track NEW_TEXT fade that flashed the full underline on every switch is deleted. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01RnH6dR85kyFKcc2tQAJLhK
… out Choosing a tab (or a sidebar thread or channel that opens one) puts its name in a framed toast centred over the workspace. Its frame, surface and name rise from the background, hold, and fall back on the app clock (Swell: 0.12 s rise, 0.5 s hold, 0.35 s fall, 30 fps), so the reader sees where they landed without hunting for the underline. It never takes focus or pointer input (can_focus off, hit_test_transparent), a new choice while it shows replaces the name and restarts the swell, and it leaves the layout when the swell ends. With animation off the clock reports the swell finished as soon as it starts, and the toast hides at once. EffectPhase now declares whether an effect is in motion, so a widget reads that from the phase family instead of comparing phase objects. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01RnH6dR85kyFKcc2tQAJLhK
…in view Before: an append at the followed end kept the window on the tail, so a message taller than the window appeared as one block already scrolled to its last row, and the reader scrolled up to find who sent it. Now the first message of an arriving burst is the reader's position for the layout that adds it (ArrivalPosition, a member of the ReaderPosition family beside FragmentPosition and BlockPosition). Its page is a geometry target, so that same layout places it: a message that fits follows the tail exactly as before; one taller than the window lands with its first row at the window's top and following stops there, the same rule message_head_hidden already applies to a clicked tall message. Reader movement during the change wins. Each burst also uncovers its rows from each message's first row down, one window height per sweep (ARRIVAL_REVEAL, NEW_TEXT's duration, 60 fps, on the app clock), alongside the existing rise-from-background fade. Arrivals inside a running burst join it rather than restarting it; once the sweep ends every row is uncovered, and with animation off nothing is ever covered. The effect phase family now starts the reveal where it already decided whether arrivals begin a new burst. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01RnH6dR85kyFKcc2tQAJLhK
The fact "this node's app is a ToadApp" was restated 16 times as
cast("ToadApp", self.app) (12 in widgets/side_bar.py) and 13 more times as a
per-class `app = getters.app(ToadApp)` descriptor, each with its own runtime
import of toad.app. ToadHosted (widget_view.py, beside WorkspaceHosted)
declares it once for readers and type checkers; the classes that read Toad's
app inherit it and every cast and per-class descriptor is deleted, along with
the imports they alone needed. Nothing changes at run time: `app` is the
node's own.
NRA flagged the casts as Toad's largest restated-identity finding
(nra-ranked-toad-formbridge.md).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RnH6dR85kyFKcc2tQAJLhK
…ith its reveal Review of 4c46986 found three defects, all fixed here: - A tall arrival snapped back to the tail. place_reader stores placed(), whose default calls moved(window, 0); inside the open change that is shifted(0), which returned TailPosition, so the next placement re-anchored the window. ArrivalPosition.placed now keeps what it placed: the message head as a FragmentPosition, or the tail. shifted() keeps the arrival; the reader leaving the tail replaces it through left_tail like any kept position. - An arrival hidden by the transcript filter resolved to the previous drawn message and could pull the reader up to it. hold_arrival takes the first arrival the page shows, and only for a shown window. - Only following hands the reader to an arrival: ReaderPosition.arrived keeps any kept position, TailPosition answers ArrivalPosition, and LatestPosition (End pressed, awaiting its source) keeps itself. - Covered rows never paint NEW_TEXT, which was what started it, so a burst could restart its reveal on every arrival while unfocused and keep rows blank. arrivals_began starts the fade with the reveal, so the burst is decided by a running phase. Cost: the reveal runs at NEW_TEXT's 30 fps, and a clock repaint with no new arrivals no longer scans every loaded fragment of every page. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01RnH6dR85kyFKcc2tQAJLhK
…creen Second review pass risks, both closed: - ArrivalPosition no longer turns into a FragmentPosition after its first placement. A FragmentPosition finds its page only once that page has been published, so a later layout in the same change could move the rows above the head with nothing placing it. The arrival keeps itself (shifted returns it) until its change ends, so every layout of the change places it from its own page; the inherited placed() then reads where the reader is. - A burst that begins while the history is off screen starts no fade or reveal: queued, they would start at the first paint and cover rows the reader already saw. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01RnH6dR85kyFKcc2tQAJLhK
The toast drew the name at body size, which is not the glance-readable name
the toast exists for. The name is now drawn three rows tall (LargeText) when
it fits the screen with the toast's margins, and at normal size otherwise.
Textual owns a 3x3 box-drawing font only for digits, A-F and a few signs
(textual/renderables/digits.py: DIGITS, DIGITS3X3); Rich and the installed
packages have no letter font. LargeText reads Textual's glyphs for those
characters and declares only the letters and signs Textual lacks, in the same
light style; lower case draws as upper case, anything else at normal size on
the middle row.
The right-edge offset in the earlier screenshots was the rasterizer, not the
toast: every row of the toast strip is exactly the box width with the border
in its first and last cells (probe: widths {69}, right column ╮│││││╯), and
the SVG places the right border at the corner's column. rsvg-convert ignores
the SVG's textLength and falls back to a font whose box glyphs advance
narrower; Chromium renders the same SVG aligned.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RnH6dR85kyFKcc2tQAJLhK
… ratchet debt The debt ratchet on a0fe52b failed on debt this branch introduced: - ToadHosted duplicated ToadNode (app_node.py), which already declares `app: ToadApp` and which DeclaredWidgetActions, SessionScope and others use. Every class now inherits ToadNode and ToadHosted is deleted; ToadNode sits last in each base list so the MRO stays consistent. This also clears the mypy errors the typing-only property caused. - AlternatingTone declared None in its tone pair. Rows now name both tones: ListedRow (a row its list orders) alternates on SidebarToneColour and AlternateRowToneColour; ChannelRow, the channel rows the welcome screen lists, alternates on the channel hue pair; the channel group header keeps ChannelHeader. A channel's title row inside its header is no longer an alternating row at all, so it never paints over the header's tone. SidebarGroup's rows are bound to ListedRow, the family that can be placed. - ThreadRow.open_selected spelled absence as None twice through a mode_name property; it now asks whether the open view's mode has a navigation entry whose session is tracked, which is what session_details answered. - selected_span and the toast's tab lookup no longer test for None. - The toast is a plain widget (CSS position/overlay, styles for its size), not a WidgetView, and reads the app through ToadNode. - effect_frame is generic per effect, so a history reads its Amount reveal and its Mix fade through one method. - New quoted annotations in history_anchor.py are unquoted. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01RnH6dR85kyFKcc2tQAJLhK
The deleted-interface guard (tests/guards/test_deleted_interface_mechanisms.py) seals the retired cursor family's `follow` name; the underline method that slides to the selected cells now says what it does. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01RnH6dR85kyFKcc2tQAJLhK
trissim
marked this pull request as ready for review
October 28, 2026 20:45
trissim
enabled auto-merge
October 28, 2026 20:45
There was a problem hiding this comment.
Copilot was unable to review this pull request because the user who requested the review has insufficient remaining quota.
An organization owner can choose the repository organization as the billing source for reviews requested by members with a paid Copilot license. Learn about Copilot code review billing.
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 in numbers
15 commits above fresh main, including one ordinary two-parent integration merge; 30 files, +621/-160 (
git diff --stat origin/main...HEAD). Two accepted source heads retained, one shared import conflict resolved, zero new tests or runtime operations by this executor. Same P888 / same substantive #1305 / same branch: no replacement PR or allocation.Frozen combined head:
6ec2e88fe58716779d9bee94d3ee12ebc6d891ae.Fresh registered main:
5fa29f3770a040af4686ede11f8584c6646d55c0(fetched before combination and again before commit; ancestor retained).87ac98cd6f09b9e5691d55cf720b8971504792a5d12963a9d35bd4a55c1d7d18148c05fa795ec9beBoth source SHAs were fetched independently through their real pull refs and compared to FETCH_HEAD. Forge readback before combination showed both OPEN/draft/unarmed. #1303's exact-head
agent-comms/reviewwas SUCCESS. #1305's exact-head review history records source SUCCESS at 17:24:37Z, then root's REVIEWING/PENDING at 20:37:00Z for this continuation. Those old approvals do not approve the combined head. At source delivery this PR remained draft/unarmed. Root has since authorized promotion of this exact combined head in message 35183; source #1303 remains draft/unarmed and is not separately queued.Integration: one conflict, no discarded member
The sole conflict was
src/toad/widgets/comms_sidebar.py's adjacent activation/tone imports. Resolution:Do not restore
ui_theme.scheme.AlternateRowToneColourthere: #1303 moved tone ownership intoAlternatingToneand declared row families. No whole-file ours/theirs selection, cherry-pick, squash, synthetic click, compatibility alias, second action/tone implementation or new target registry.The merged sidebar differs from accepted #1303 only by #1305's existing four context-activation hunks: the import, the capability parent,
context_activation(offset), and physical button-3 routing through that method. All other member files are automatically merged. Relative to #1303, the complete changed-file list is exactly #1305's original five files. #1302, #1304 and all other unreviewed source are excluded. Settings-file changes present here are #1303's existing ToadNode migration, not #1304's forms/preferences work.1. F1 catalog: acquired once, reused by the screen
Before:
KeymapRow.defaults/effectiverebuilt a one-mode catalog while F1 displayed an all-mode catalog. Selecting native Left in an unbound mode could lose the displayed invocation and crash strict lookup.After:
KeymapScreencaptures oneKeymapCatalogand uses it for declarations, filtering, table display,selected_rowsandpicked. Rows retain their identity and binding ID. Deleted: seven lines of competing row-owned catalog reconstruction. Strict indexing remains; empty cells display a dash, are noneditable and select no row. Remap, conflict, reset, save and cursor policies are retained. Model-owned one-mode conflict acquisition remains because its scope is different.Prior source census: 621 source/test modules, zero parse omissions. Root's existing caller migration
030ff1ada24add510cd34a8391319f10c28a91b2is preserved, assertions included; this executor did not edit tests.2. Left/context hints: native activation owns the effect
Before: CommsRow's physical right-click called its menu directly; its native capability exposed only primary opening.
After:
CommsRowcomposesPrimaryActivationand nativeContextActivation. Primary remainsopen_selected. Its onecontext_activation(offset: Offset)callsshow_menu(self.navigation_tree, offset). Physical button 3 stops once, invokes that same method and returns. Existing navigation/action scopes and menu lifecycle remain owners; no duplicate selected row or popup adapter.Native Textual #204 has already landed on its main at
965e920bd2202c5c5be97a78039e49d41455a9d4(root's recorded integration). This carrier neither vendors Textual nor pins a feature revision. Nativef/Ctrl+F intents share HintHost acquisition, the published paint walk and HintSession. OptionList owns actual Option identity, membership/enabled/cell revalidation and selection; style handlers own their single click dispatch. The primary remap identity remainsHintHost.action_show_hints; the contextual method has its own identity. Insert/editor consumption and terminal Ctrl+F priority are unchanged.Native companion metadata remains unchanged and open: P883 owns recording Textual #204 through the corrected Core operation. No live P888 link or claim was fabricated or altered here.
3. Sidebar tones: list placement, declared row capabilities
Before: CommsRow held an
alternatereactive, watcher and conditional tone. Channel headers had no separate channel hue pair.After: #1303's
AlternatingToneowns parity and repaint.ListedRow(CommsRow, AlternatingTone)declares the neutral pair;ChannelRowandChannelHeader(AlternatingTone, SidebarHeader)declare the channel hue pair.ChannelGroup.header_typeand header annotation remain;order_groupsplaces the headers.ThreadStatusRow(ListedRow, ToadNode)and ThreadRow's existing-tab opening behavior remain. Deleted: CommsRow's alternate reactive, watcher, tone branch and displaced cast/getter copies. Channel hue colours derive from the existing theme colour family, not copied RGB values.4. Tabs and switch toast: geometry and the existing clock
Before: the underline was viewport-wide while overflowing tab cells extended beyond it. A selected tab could lie outside its underline. Refreshing harder cannot repair the wrong geometry.
After: #1303's
TabCellscontains both titles and underline and spans all cells or the viewport, whichever is wider. The underline marks label plus close-button cells and slides on the existing animation clock. Opening/moving labels use the existing NEW_TEXT effect. Deleted: the whole-track NEW_TEXT flash on each switch.SwitchToast, mounted once by WorkspaceChrome, observes SessionSelected and briefly paints the chosen name without taking focus or pointer input. LargeText extends Textual's existing glyphs only where missing; the name uses ordinary text when it will not fit. Rapid switching restarts the existing effect. No second animation timer was added.5. Arriving messages: reader position owns the destination
Before: following a new message taller than the viewport landed at its tail.
After: #1303's
ArrivalPositionis a ReaderPosition member. Tail following hands over to the first shown arriving message; existing kept positions and explicit End retain their behavior. A short message follows its tail; a tall one places its head using the existing geometry owner and stops following. ARRIVAL_REVEAL and NEW_TEXT run on the same existing clock; bursts that nobody sees do not start a reveal. Clock ticks without new arrivals skip the scan of loaded fragments. Remaining known cost: visible arrival pages repaint at the existing effect cadence. This source behavior is not a measured 16ms result or a proven history-hang repair.6. ToadApp ownership and retained root caller migrations
#1303 replaces 16 casts and 13 per-class app getters with the existing
ToadNode, not another hosted mixin. All of those automatically merged changes remain.Root's
d12963a9d35bd4a55c1d7d18148c05fa795ec9beupdates two existing controls: channel-scrolling assertions crop the actual message area rather than sidebar ink; layout restoration waits for the transient toast to retire. Its source Debt ratchet was SUCCESS; its hosted broad suite was FAILURE. Root owns the selected-control qualification/results; this executor has not independently run or upgraded those results. Both test migrations are retained byte-for-byte from d129.Verification: source integration, not application acceptance
Performed for this combined carrier:
Combined qualification and promotion authorization
For exact head
6ec2e88fe58716779d9bee94d3ee12ebc6d891ae, root's message 35183 reports 7 combined controls passed in 30.12 seconds and root source OK, and explicitly authorizes marking this existing carrier ready and requesting normal auto-merge. Those controls were root-run, not repeated by this executor.Forge readback independently confirms Debt ratchet SUCCESS, workflow
40765099329, job123086999217. The broadsuitejob123086998913was IN_PROGRESS at promotion readback. Its previous failures are not waived or declared baseline. The same readback still showedagent-comms/reviewPENDING; root's reported OK is not substituted for the actual required GitHub status. That existing status remains the promotion gate; normal auto-merge must honor it, with no bypass.No combined tests, imports/compilation, UI, installed acceptance or performance run by this executor. No pytest, checker, ratchet, new tests/fixtures, package resolution/install, provider/native input, replay, service or additional agent operation. Root's seven-control result and the hosted Debt success do not establish installed-window behavior or performance.
Historical source results are not combined results:
87ac98cd6f09b9e5691d55cf720b8971504792a5: Debt ratchet SUCCESS on rerun, job123066819783, workflow40727245377; earlier broad suite FAILURE. Prior compilation/import success covered the original pair only, not this carrier.d12963a9d35bd4a55c1d7d18148c05fa795ec9be: Debt ratchet SUCCESS, broad suite FAILURE; root's two-control migrations/results belong to that source revision. The broad suite was not rerun here and its failures are not all proven baseline.Root owns one combined consumer qualification, whole-head verdict, normal merge-queue arming and subsequent paired installation. Real saved-session acceptance comes after installation, never as a circular pre-merge hold: native left/context operations on thread/channel rows and inside actual menus; disabled/clipped/removed targets; current-modal scope; Escape/focus; remaps and Insert typing; link open/copy; F1 empty/bound Left selection; sidebar tones, existing-tab return, underline overflow, reader arrival and toast behavior. No claim that P885's 16ms target, history hang or settings issue is solved.
Assumed: normal GitHub auto-merge honors the existing required
agent-comms/reviewstatus; root owns the remaining broad-suite disposition, preserves source #1303 until its accepted work lands, then performs one stack installation and real-window acceptance. Promotion is explicitly authorized by root's message 35183, not an inference from old member approvals. P883 still owns the missing native companion metadata. P888 remains open until its actual application acceptance.