Repository navigation
Conversation
…t cancelled every build GitHub's ubuntu-latest (26.04) has no Python 3.9, so setup failed on every PR and fail-fast cancelled every other build: no Textual PR has run its tests in CI (#251, #252 and #253 all show the cancelled matrix). The matrix starts at 3.10, and the four 3.9-only branches go with it. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XbFMSqJMarXn2ZE8iAL7Sd
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. Try requesting a review at a lower effort level.
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.
box_model.py and css/native.py declare type aliases with 'type X = ...' (PEP 695), so 3.10 and 3.11 cannot import textual: #255's 3.10 build failed collection with SyntaxError at box_model.py:30 and fail-fast cancelled the rest. The matrix tests the Pythons the code runs on. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XbFMSqJMarXn2ZE8iAL7Sd
… delivery order Issue Textualize#1814's requirement is that each subclass's Fired reaches its own namespace's handler. The test also pinned the order two independent pumps reach the app in, which the message loop does not promise; it failed 5/5 on main (right before left). It now checks each handler ran once. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XbFMSqJMarXn2ZE8iAL7Sd
… forwards the deadline generator Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XbFMSqJMarXn2ZE8iAL7Sd
76119a0 deleted the per-widget render steps after a layout's commit: composition is one atomic step after the budget pause, and it consumes pending repaints there. Under held frames the commit is now the layout's last step, so the readers test asserts every pause saw the previous scene instead of counting render steps that no longer exist. The repaint spy reads the damage where the frame takes the repaints (update_widgets), not before the pause. The paint digest is re-recorded: ad7782f's pilot returns before the screen's timer frame, so the first page-up's scrollbar column is written as its own update (22 -> 23). Every update after that split is byte-identical, under rich 14.2.0 (the lock) and 15.0.0 alike. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XbFMSqJMarXn2ZE8iAL7Sd
AwaitMount._finish (widget.py) skips the parent refresh when parent.is_leaving or _closed. The tests' Mock(spec=Widget) hand-set the private _closing/_pruning flags behind is_leaving, so is_leaving stayed a truthy Mock and refresh was never called. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XbFMSqJMarXn2ZE8iAL7Sd
0212c05 made TextArea.restore_editor_state a coroutine (it closes the temporary editing mode). The tests still called it bare, so the coroutine never ran and nothing was restored: a fresh Document and an empty selection. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XbFMSqJMarXn2ZE8iAL7Sd
b31f2ed made viewport layout choose its geometry type from visible_only, and a scroll-only reflow arranges through ScrolledGeometry, which captures no subtree. The test indexed the cache entry and raised KeyError; its stated invariant is that the overlay's resource is never an intrinsic clip, so it asserts exactly that. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XbFMSqJMarXn2ZE8iAL7Sd
debt-ratchet installs agent-comms at 88f4e6ee, which crashes at import on Python 3.14.10, and agent-comms main no longer ships agent-comms-ratchet at all (the NRA gate replaced it). black_format pins Python 3.11 and black 24.1.1, which cannot parse the tree's PEP 695 syntax (box_model.py:30) and would reformat about thirty files nobody formats with it. Both were red on every PR, so a red check here told nobody anything. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XbFMSqJMarXn2ZE8iAL7Sd
603a769 runs after-refresh callbacks once the frame after them publishes, which is Lazy's contract: the first frame is drawn without the lazy widget, then it mounts. run_test now returns after that second frame, so asserting #foo absent at entry tested pilot timing. The app records what each written frame contained and the test asserts the first had no #foo. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XbFMSqJMarXn2ZE8iAL7Sd
push_screen and switch_screen mount the screen (_get_screen) and only then read its CSS (_load_screen_css). bc570b4 made a CSS source change queue restyles on the screens in the stacks, and ScreenResume restyle only those queued nodes; the incoming screen is not on a stack yet, so its own CSS was never applied. A SeeThrough screen with background: transparent stayed opaque, and the screens behind it were not drawn. The loader holds the screen the rules are for and restyles it. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XbFMSqJMarXn2ZE8iAL7Sd
ad356fb reconciles footer groups and puts the common keys (the command palette) first, so clicking the first FooterKey opened the palette. Issue 4656 is about clicking the Quit binding. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XbFMSqJMarXn2ZE8iAL7Sd
…ount 065244c made ancestor hover part of the path key and restyles the hovered row when hover changes, so applying it afterwards hits that entry with no rule checks. The colour is the contract: an entry keyed without hover would answer red. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XbFMSqJMarXn2ZE8iAL7Sd
… retained 2094493 acquires only required geometry paths: buttons already placed in the published viewport are not re-acquired. The test demanded all forty; it now demands exactly the ones the viewport lacks, which also catches over-acquisition. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XbFMSqJMarXn2ZE8iAL7Sd
… one b31f2ed made a full arrangement capture IntrinsicSubtreeGeometry, but only PlacedSubtreeGeometry.matches let a complete (non-visible_only) capture answer a visible_only request. The next reflow_visible at the same scroll re-arranged the scroll container (arrangement_calls 1, expected 0). The rule lives once on SubtreeGeometry.matches; each member declares only which key inputs its placements depend on (_comparable: the key itself, or its placement-free intrinsic form). The test drops its pin on the capture's class and keeps the reuse and reference-equality assertions. Geometry and compositor tests: 251 passed. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XbFMSqJMarXn2ZE8iAL7Sd
…tainer ef8f854 moved layout: vertical out of the containers' CSS into DEFAULT_LAYOUT = VerticalLayout; d19dbd5 then deleted those nine declarations as redundant, because they equalled Widget's own default. Equal values, different facts: Widget's default is what a leaf has, a container's is a declaration. is_container and is_scrollable (one expression written twice) still read only the CSS child_layout, so an empty VerticalScroll stopped being scrollable: overflow-x: scroll drew no scrollbar (upstream issue 1616's test, red since ef8f854). - Layout.holds_children: a declared layout makes its widget a container. ImplicitVerticalLayout is the leaf default and holds none. - is_container reads the widget's layout; is_scrollable is is_container. - Container, ScrollableContainer, Vertical, VerticalGroup, Center, Right, Middle, CenterMiddle, Screen, Markdown, ToastRack and HelpPanel declare VerticalLayout again, as their CSS did. Textual suite without snapshots: 3849 passed, 0 failed (snap_compare absent locally). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XbFMSqJMarXn2ZE8iAL7Sd
Its digest was recorded under COLORTERM=truecolor; CI has no COLORTERM, so the console downgraded colours and the same 23 updates hashed differently. The test owns the terminal it records. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XbFMSqJMarXn2ZE8iAL7Sd
…n and identity Binding.for_mode spells an arrow under its modal key keeping every other field, including an author's key_display; with_key resets key_display, so comparing whole Bindings failed for the snapshot apps that set one (only collected in CI). The test protects that the spelling exists with the shared identity, not a display policy. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XbFMSqJMarXn2ZE8iAL7Sd
_render_container drew a keyline only when the CSS declared a layout (geometry.child_layout). Since ef8f854, Grid, Horizontal and the other containers declare their layout as DEFAULT_LAYOUT, so a keyline rule on them drew nothing: the keyline snapshots lost every line. It now asks is_container, the one container predicate. All three keyline snapshots match upstream pixel for pixel (theme colours normalised). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XbFMSqJMarXn2ZE8iAL7Sd (cherry picked from commit fb8a1c7)
InputEvent.can_dispatch_to (38e6003) refuses input handlers on a widget under a display:none ancestor, but Widget._dispatch_message then dropped the event outright, so it never bubbled. Auto-focus may focus a hidden widget (upstream does too), and every key after that vanished before Screen and App: richlog_width's App.key_p never ran and its RichLog stayed empty. The bubble step moves out of MessagePump._on_message into _bubble_onward, and a refused receiver passes the event on through it without running its own handlers. A detached widget still bubbles nothing (is_attached is False). Proof: a hidden focused RichLog plus App.key_p; before, the key stopped at RichLog; now RichLog -> Screen -> App.key_p, matching upstream's base. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XbFMSqJMarXn2ZE8iAL7Sd
The builder stores initial as None so it still overrides less specific rules, and StyleProperty.__get__ handed that None back. The default theme resolves $link-background to initial, so Widget's rule read link_background as None and Stylesheet.css raised AttributeError in Styles.css_lines. Any widget logging the stylesheet died at mount: scoped_css and unscoped_css lost their widgets. The crash is upstream's too; the fork's atomic mount made it drop the widgets. Proof: App().stylesheet.css raises before, succeeds after; test_scoped_css renders 0 px from upstream's reference. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XbFMSqJMarXn2ZE8iAL7Sd
Widget.render_line is (y, paint); two snapshot apps and the four checker guide examples still defined the deleted one-argument form, so they raised and rendered blank. The guide now says what paint carries. Proof: test_scrollbar_thumb_height and test_component_text_opacity render 0 px from upstream's reference (were fully blank). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XbFMSqJMarXn2ZE8iAL7Sd
test_text_area_paste wrote App._clipboard, a private string 4cbaf90 replaced with typed clipboard_content; nothing reads the old name, so ctrl+v pasted an empty clipboard and the snapshot was blank. copy_to_clipboard is the owner. Proof: test_text_area_paste renders 312 px from upstream's reference, the terminal cursor only (was 85,696, blank). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XbFMSqJMarXn2ZE8iAL7Sd
App._on_compose mounted [*screen._nodes, *app widgets]; mount_all leaves already-mounted nodes in place, and with the fork's atomic screen composition the screen's Tooltip (a Static) already exists there. query_one(Static) in on_mount then found the hidden tooltip: test_static_content_property set content on it and the app's own Static kept 'Hello, World'. Upstream only worked because its tooltip did not exist yet at that point. The app's widgets now mount before the screen's existing nodes; each system widget paints on its own layer, so order does not change the picture. Proof: test_static_content_property 0 px from upstream (was 1,223); every tooltip and notification snapshot stays 0 px. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XbFMSqJMarXn2ZE8iAL7Sd
CursorCommand.priority = True (5eaa473) made every list's arrows priority bindings, and priority resolves from the App inward, so a list nested in another lost its arrows to the outer one: in test_select_refocus the open Select overlay was focused while down moved the ListView behind it. Type-to- search consumes printable keys only, and printable cursor aliases already precede it through NavigationKey.preempts, so the priority bought nothing. The priority ClassVar had no other writer and goes too. Proof: the key trace matches upstream's base key for key; test_select_refocus 0 px (was 386,686); 757 binding, keymap, navigation, list and input tests pass. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XbFMSqJMarXn2ZE8iAL7Sd
Binding.for_mode gave every binding on an arrow the arrow's shared preference
identity, so Binding('i,up', id='app.increment') split into an app.increment
half and an UpKey half. set_keymap replaced only the first; the footer kept
showing 'up' with its stale key_display. preference_identity now answers on the
binding family: a named action keeps its explicit id and joins the arrow's
preference only without one; native ModeBinding commands always share it.
Proof: test_keymap_bindings_key_display 0 px (was 22,068);
test_navigation_spellings keeps id-less arrow actions on the shared identity.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XbFMSqJMarXn2ZE8iAL7Sd
Rich caches a Style's ANSI codes on the Style the first time any console renders it, and combined styles are shared through lru caches. The frame-damage tests run apps with COLORTERM unset, and test_paint_output's truecolor digest then read their 256-colour bytes for one reverse style: it failed in the full suite and passed alone. tests/conftest.py now declares the session's terminal once, before any app exists, and the digest test's own monkeypatch goes. Proof: test_partial_damage_uses_current_frame_for_spans_and_chops followed by the digest test: 4 passed (was 1 failed); settled snapshots stay 0 px. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XbFMSqJMarXn2ZE8iAL7Sd
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.
Textual CI had not run a single test on any PR:
ubuntu-latest(26.04) has no Python 3.9, setup failed, and fail-fast cancelled every other build. Under that cover 19 tests went red on main. This PR makes CI run the tests and fixes all 19 (local, without snapshots: 3849 passed, 0 failed), plus two checks that could never pass.In numbers: 15 commits. 19 failures → 0: 15 stale tests, 4 real source bugs. 2 dead workflows deleted. Every failure was bisected to the commit that caused it before deciding whether the test or the source was wrong.
CI runs the Pythons the code runs on
type X =at box_model.py:30,def of[Value]at css/native.py:99), so 3.10 and 3.11 cannot even import it.test_message_inheritance_namespaceasserted the delivery order between two pumps, which is scheduling. It now asserts the namespaces.debt-ratchet.yml: it installs agent-comms at 88f4e6ee, which crashes at import on 3.14.10, and agent-comms main no longer shipsagent-comms-ratchet(the NRA gate replaced it).black_format.yml: Python 3.11 plus black 24.1.1 cannot parse PEP 695, and about 30 files would reformat. Both were red on every PR, so red meant nothing.Real bugs fixed in source
Screen's ownCSSnever applied (abackground: transparentscreen stayed opaque, so the screens behind it were not drawn)push_screen/switch_screenread the screen's CSS before stacking it_load_screen_cssrestyles the screen it loaded rules forVerticalScrollis not scrollable:overflow-x: scrolldraws no bar (upstream Textualize#1616)layout: verticaltoDEFAULT_LAYOUT; d19dbd5 deleted those as "redundant" because they equalledWidget's default;is_container/is_scrollableread only the CSS layoutLayout.holds_children;ImplicitVerticalLayoutis the leaf default;is_containerreads the layout andis_scrollableisis_container; the 12 containers declareVerticalLayoutagainIntrinsicSubtreeGeometry, and onlyPlacedSubtreeGeometry.matcheslet a complete capture servevisible_onlySubtreeGeometry.matches; members declare only_comparable(the key, or its intrinsic form)pilot.pause()returns before the screen's timer frameStale tests repaired against the current contract
render_line(y)→render_line(y, paint)(89c3a65).AwaitMountreadsis_leaving; the Mock set private flags instead.restore_editor_statebecame a coroutine (0212c05); the tests never awaited it.test_lazy: asserts the first drawn frame has no lazy widget (Lazy's contract), not whenrun_testreturns.Still owed: pyproject declares
python ^3.9; raising it changes poetry.lock's hash, so it lands with a relock.🤖 Generated with Claude Code
https://claude.ai/code/session_01XbFMSqJMarXn2ZE8iAL7Sd