Skip to content

P952: show actual editor mode in native footer and hint captions - #209

Merged
trissim merged 3 commits into
mainfrom
work/awareness-schema--P952--textual
Oct 29, 2026
Merged

trissim merged 3 commits into
mainfrom
work/awareness-schema--P952--textual

Conversation

@trissim

@trissim trissim commented Oct 29, 2026 •

Copy link
Copy Markdown

Summary

3 implementation commits; 6 native Textual files, +212/-34. Final source head: bc4f72dde5332eda49c9918a44b10136f1f9b4b4. Latest mount correction: 2 files, +100/-3; production-only delta +4/-2. FooterProjection.mode_label: main 0 declarations → candidate 1. No new mode state, type roster, timer, Toad wrapper or engine interpretation.

P952 makes existing editor-owned mode labels available to native Footer and left/right hint captions independently of Toad's Prompt InfoBar. Source delivery only: draft and unarmed for root review and copied-history/private-XDG frontend qualification. P917/P939 remap/application obligations and P928 Neovim remain separate; this change does not prove those workflows.

Ownership and class structure

Before:

  • DOMNode.key_mode supplies the ordinary/profile scope; TextArea.editing_mode is the actual reactive editor state.
  • Screen.active_binding_mode reads focused binding identity, but no display-label projection exists.
  • Footer projects groups/palette only. HintSession renders only HintClick.caption and takes focus away from the originating editor.
  • Current Toad main 4c3c4f3a03cd86b54453d83862d66df106c90fba declares Standard.label = "STANDARD"; its Prompt InfoBar is the only existing mode-text consumer in the requested UI.

After:

DOMNode.key_mode_label -> self.key_mode().label
  TextArea override -> self.editing_mode.label (no mode reconstruction)
  HintSession override -> original_focus.key_mode_label while attached to its active screen
    otherwise -> inherited current-scope projection
Screen.active_key_mode_label -> focused.key_mode_label / own key_mode_label
FooterProjection.mode_label <- Screen.active_key_mode_label
Footer's dedicated FooterLabel <- that immutable display projection
HintSession.caption <- actual mode label + existing click caption
  render + cell width + placement reservation <- same complete caption

The existing EditingMode/profile subclasses keep all mode meanings, transitions and bindings. A parameterized AwaitKey/pending state is never reconstructed to ask its label. Existing DOMNode, Screen and TextArea method bodies are AST-identical after excluding the new read-only properties.

Footer mechanism

Adds one native FooterLabel with -mode class, before the command-palette key. Empty native labels hide that child; nonempty labels contribute one column. The existing immutable FooterProjection equality includes the label.

Keeps the single existing bindings-updated subscription, mount refresh, recompose/batch lock, revision rescheduling, group handling and palette behavior. When only mode text changes, _apply_binding_projection updates the label in place and skips _reconcile_keys; unchanged binding children are neither removed nor mounted.

Hint lifecycle and geometry

The original editor remains the label owner while it is attached to the same active screen. _current_original_focus owns that validity decision for the label and focus restoration. Publication reconciliation retires a lost original reference once and invalidates the caption and bindings, including when the fallback label has the same cell width. Losing that resource uses the current native profile scope, not a retained mode string. Escape, click activation, paste, target availability and asynchronous retirement keep their existing paths; detached focus cannot remain a label source.

The caption is one Content value containing the mode plus the existing left/right description and Escape cancellation. Width and reserved cells use its cell length, not the shorter old click caption. Initial label placement now occurs on mount, after the session has its native screen; later width/bottom-offset changes reconcile placement through the existing publication lifecycle.

HintSession previously had no bindings subscription. It now owns one transient subscription/unsubscribe pair for caption invalidation. The order's no-second-subscription restriction is applied to Footer, whose count stays one; HintSession's first subscription is needed to invalidate an actual mode change without a writable mode cache or per-frame repaint loop. Publication reconciliation checks current geometry, but reruns target placement only when the caption width or bottom offset changes.

Deleted/replaced work

34 old lines removed cumulatively, mostly relocation of existing geometry/reconciliation logic, not 34 independently removed slop obligations. The three uses of the shorter click-only caption for initial width/reservation/render are replaced by the complete-caption owner. No old engine path, historical PR, compatibility alias or application preference was changed.

AST source evidence and checks

  • Retained source map uses NRA's ModuleSyntaxIndex, not another parser/consumer resolver.
  • Original map roots: Textual 79a8a6d69eed1d3300b54b12c490bc7d3753ee9c; Toad origin/main at 4c3c4f3a03cd86b54453d83862d66df106c90fba (read Git blobs, not its older checked-out working source).
  • 565 production modules; 2,571 original class declarations; initial implementation 559 → 584 selected declaration/read/write sites; zero parse omissions. Includes imports, bases, lexical owners and decisions in touched/consumer modules. Dynamic Python binding/MRO at runtime is not proven by this syntax inventory.
  • The earlier focus-retirement correction used targeted NRA reproof of _hints.py, not another whole scan. p952-final-source-proof.json retains the five production-file hashes and hint declarations/consumers at 197849b89. The current mount-correction proof below supersedes only its hints hash; the other four hashes remain identical. The initial 584 count describes the first checkpoint, not a new claimed final count.
  • compile(source, path, 'exec') passed for all five changed modules, with no generated pycache.
  • All ten added methods/properties have parameter and return annotations; the retained original-focus field is explicitly Widget | None.
  • AST comparison: existing DOMNode, Screen and TextArea bodies unchanged.
  • Order source count: FooterProjection.mode_label main 0, candidate 1.
  • Footer signal subscriptions 1 → 1. HintSession subscribe/unsubscribe 0/0 → 1/1.
  • git diff --check passed.
  • Evidence: /home/ts/.cache/agent-scratch/awareness-schema/p952-source-before.json, p952-source-after.json, p952-final-source-proof.json, p952-source-check.log, p952-focus-publish.log.
  • One-shot source-map collector was retired after completion/borrower checks; SHA256 31cd48838583c253d544465ff993d5f9d601b7805341c2527a3ce46c2dbc4916. Invocation: shared Core Python p952-source-map.py p952-source-before.json, then the same collector against the initial applied source into p952-source-after.json; final correction reproved only its changed module with ModuleSyntaxIndex.build.

No pytest, mypy, ratchet, GUI/controller attachment, provider input, environment/package changes, live preference mutation, installation or service operations were run by the author, as ordered.

Open-PR overlap

Read the live native PR file list before editing. Only Textual #208cd4d860 overlapped dom.py/_text_area.py; Footer, hints and Screen had no open overlaps. Root message 39086 confirms the two exact display-only hooks were relayed to vim-neovim. P952 owns those hooks and Footer/HintSession/Screen; vim-neovim retains all engine semantics. No Toad PR or wrapper is part of this delivery.

Initial-width mount correction (root40691)

The actual private Toad run failed in HintSession.on_mount -> _bindings_changed -> _reconcile_caption_geometry: styles.width was still None, so the old styles.width.value comparison raised AttributeError. Twelve published targets did not imply a retained hint session. This was my source defect, not evidence of focus loss. Retained trace: codex-greentext/p952-private-hints-20261029-144110/state/toad/logs/AttributeError_2026-10-29T14_41_18_733171.txt.

The existing geometry owner now converts the desired cell width once with Scalar.from_number, compares the complete Scalar with styles.width, and assigns that same value. This handles initial unset width by normal value equality and distinguishes equal numeric values with different units. No fallback branch, cached width, blur workaround or new lifecycle owner. Offset, reserved-cell placement, focus rules and subscriptions are unchanged. The other four production-file hashes, including Footer, are identical to 197849b89.

Authored two controls, each parameterized over declared left/right click intents; UNRUN in tests/test_hints.py:

  • test_hint_caption_initial_unset_width_mount: actual published targets, explicit unset-width precondition, real App mount, retained session/focus/caption width and Escape restoration.
  • test_hint_caption_same_width_mode_change: NORMAL -> INSERT through the original editor's mode owner, cached rendered-line content changes while caption width, position, session identity and target-label positions remain unchanged. A shared label-only EditingMode fixture avoids depending on the separately evolving engine classes.

Bounded source evidence: NRA ModuleSyntaxIndex reproof of hints, Scalar, ScalarProperty and the test module; all four parse and compile, zero omissions. Hash-checked all 256 native production modules from the retained 565-module paired map: only _hints.py changed from the final 197849b89 hashes. This is not a new complete scan or behavioral proof. git diff --check passes. Artifacts: awareness-schema/p952-mount-{before,after}-proof.json and corresponding logs. One-shot source collector retired after both completed runs; SHA256 2ce723f9dbd5d185fd4dbcd4f1c92e5b900590d9579287fbc5cd0ca3d641afc2.

Fresh live native open-PR readback found only #209; no additional hints/test overlap. Queue-toad-fix subsequently received the exact unmerged Footer head and blob in40742 and owns its separate in-place group reconciliation; this correction does not touch Footer.

The hint session died before it could hint. None.value was an ambitious opening line.

Required root run / Assumed

Root39504 already reports actual hidden/scrolled STANDARD, NORMAL/INSERT/VISUAL/AwaitKey Footer and Input/noneditor mode controls passed at 197849b89. Those are retained root results, not author executions. The mounting crash prevented hint qualification. Root40691 retains those successful controls and reruns the failed left/right actual-app cases on this correction. No whole-head approval, merge, installation or hint acceptance is claimed.

Root owns exact-head review, recording/linking, installation and the real copied-history/private-XDG frontend run. All these runtime cases remain unrun by the author:

  • STANDARD appears in native footer and active left/right captions with Prompt InfoBar hidden.
  • Actual Normal/Insert/Visual editor transitions update labels; parameterized pending states do not invoke their constructors.
  • Native Input/TextArea and noneditor profile scopes use their actual owners.
  • Long capped/scrolled prompt remains usable; labels do not mutate remaps or text entry.
  • Hint captions preserve the originating live mode, reserve their full geometry, restore focus on Escape/click and never retain a detached original's mode.
  • Footer label-only transition retains unchanged binding-child identity.
  • Transient subscriptions retire with the hint session; no stale focus or duplicate children.

Native ordinary EditingMode.label is intentionally empty; Toad Standard supplies STANDARD through its existing declaration. Future Neovim noneditor profile fallback must not manufacture NORMAL before acknowledgement. This change does not interpret Neovim codes; its actual acknowledged editing_mode.label remains the separate engine owner's contract and needs paired qualification after that work lands.

The mode already knew its name. The footer just wasn't asking it.

@trissim
trissim marked this pull request as ready for review October 29, 2026 21:09
Copilot AI balanced review requested due to automatic review settings October 29, 2026 21:09

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

@trissim
trissim merged commit 4ecfb67 into main Oct 29, 2026
1 check passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants