Repository navigation
Settings menu in two declared pages, with the input box layout as a setting - #1304
Merged
Merged
Conversation
…etting The settings screen shows one tab per FormPage the top-level groups declare (Interface; Agents and shell), generated from the declarations through formbridge. Field semantics move to formbridge.forms.declared_fields; toad/forms keeps only the Textual widgets. The compact_input flag, whose consumers each branched on it, becomes the InputBox choice family (Normal, Compact, Flush); each prompt part reads its room from the chosen member. The dead prompt_message setting becomes the live input placeholder, and the three prompt symbols become settings. Choices that declared a title without TitledChoice now show it, the states_default and patch_preview member flags become overrides, and numeric help states its declared bounds. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01RnH6dR85kyFKcc2tQAJLhK
trissim
enabled auto-merge
October 28, 2026 15:37
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.
…ed files Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01RnH6dR85kyFKcc2tQAJLhK
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01RnH6dR85kyFKcc2tQAJLhK
setting_effects imported Prompt inside two new effect functions because a module-level import would cycle (preferences -> setting_effects, prompt -> preferences). The effects now live on PromptSettingEffects in the leaf setting_widget.py as static Effect metadata; Prompt (and MessagePrompt) compose it, each effect enumerates open widgets of that capability, and InputSettings names them directly. setting_effects.prompt_layout and prompt_text are deleted. Both expands_preview status parameters are annotated with ToolCallStatus. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01RnH6dR85kyFKcc2tQAJLhK
trissim
disabled auto-merge
October 28, 2026 19:04
trissim
marked this pull request as draft
October 28, 2026 19:04
form_view.py keeps main's field refusal label and the branch's search text on DeclaredField; settings.py keeps the declared pages and main's ToadNode. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XbFMSoWgC3ZwKzjT4SwMDu
…as hidden test_forms.py:131 still read app notifications after 961e7ca moved refusals onto the field's own FieldRefusal; test_settings_screen.py:113 counted ui.keymap.bindings as editable though Keymap is marked hidden as a class. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XbFMSoWgC3ZwKzjT4SwMDu
trissim
marked this pull request as ready for review
October 30, 2026 09:51
trissim
enabled auto-merge
October 30, 2026 09:51
…ts where no cycle ToolCallStatus and DeclaredField import at run time; only ToadApp stays behind TYPE_CHECKING in setting_widget.py, since toad.app imports it back. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XbFMSoWgC3ZwKzjT4SwMDu
EachOpenWidget (setting_declarations.py) owns running a method on every open widget of the class it is declared on, so setting_widget.py no longer names the app type and its TYPE_CHECKING import of toad.app is gone. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XbFMSoWgC3ZwKzjT4SwMDu
trissim
added a commit
to OpenHCSDev/agent-comms
that referenced
this pull request
Oct 30, 2026
…#1304, 35770a0c2) batrachian-toad 35770a0c, formbridge b67c97e5; the carry's toad.preferences InputSettings and toad.setting_choices FlushInputBox now resolve, and every cutover module imports against this lock. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XbFMSoWgC3ZwKzjT4SwMDu
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.
Toad's settings menu now has two tabs, Interface and Agents and shell. Every tab, group, dropdown, toggle and number box is generated from the settings declarations. A new field or Choice family appears in the menu with no UI code. The input box now has a layout setting, Normal (classic, bordered and padded), Compact (the current look, still the default) or Flush, and the change applies live.
Depends on OpenHCSDev/formbridge#16, now merged.
uv.lockis re-locked to formbridge mainb67c97e5. The carry PR on agent-comms (work/toad-input-settings-carry) lands after this one (#3568, a draft until this merges). #3567 reverts the copy I pushed to agent-comms main by mistake.The generator (one owner per fact)
>= 1-style bounds from annotated_types, search text, validation, edit) live informbridge/forms/declared_fields.py:DeclaredField, which a PyQt backend can also read.toad/forms/declared_views.py,field_path.pyandkinds.pyare deleted.FormPagemetadata on each top-level group:ui: Annotated[UiSettings, ..., INTERFACE](src/toad/preferences.py).formbridge.form_pagesderives the tabs and their order.FormView.pages(state)(src/toad/forms/form_view.py) builds one form per page, andSettingsScreen(src/toad/screens/settings.py) draws aTabsrow over aContentSwitcher. Search filters every page.DeclaredViewsfamilies. Choice families get dropdowns throughFamilyChoices, bools get checkboxes, int and float get validated inputs that show their bounds, and nested dataclasses become sub-groups. The closed screen costs nothing because it is built only when opened.app.settings.ui.input.box.<attr>. ObjectState returns its cached object, measured at 0.14 µs perapp.settings.ui.footerread (timeit, 2000 reads). There is no lookup chain.Scoped (lazy) inheritance: none
I checked every setting for a real scope (app -> agent kind -> thread or tab). No consumer reads any setting per agent kind or per tab. Every read is
app.settings.<group>.<field>, the one app-wide value. Adding ObjectState scope layers would add states nothing reads, so no setting gets scoped inheritance in this PR. The candidates if a scope appears later areagent.thoughts,tools.expandandshell.*(per agent kind). The page split is Interface vs Agents and shell, not scoped vs plain.Numbers
ui.input.box,agent_symbol,multiline_symbolandshell_symbol. Movedprompt_message->ui.input.placeholder. Deletedcompact_input.if compact_inputbranches (prompt.pyAgentInfo.padding, InfoBar.margin, PromptLabel.padding);PromptLabel.AI/SHELL/MULTILINE);states_defaultmember constants and their branch (NRA91487a46b2);patch_previewmember constants and their branch (NRAa425f587fa);FormViewparameters (paths=andfocus=, used nowhere).prompt_messagewas declared and shown in the menu, but nothing read it. It is now the live placeholder.title = "Success only"/"Fail only"/"When there are multiple sessions"/"When app is not focused"were dead: those members lackedTitledChoice, so the dropdowns said "Success", "Fail", "Multiple" and "Blur". They now show their titles (checked in the interpreter).Inventory (AST scan of src/toad: 16 geometry, 15 glyph and 69 numeric constants, plus property branches)
Promoted:
widgets/prompt.py:105AgentInfo paddingif compact_inputInputBox.agent_info_paddingwidgets/prompt.py:153InfoBar marginif compact_inputInputBox.info_bar_margin(footer)widgets/prompt.py:161-167PromptLabel glyphs❯ $ ☰and padding branchui.input.agent_symbol / shell_symbol / multiline_symbol,InputBox.label_paddingpreferences.py:87prompt_message(dead)ui.input.placeholder, read inPrompt.update_promptpreferences.py:90compact_inputui.input.box(InputBoxfamily insetting_choices.py)InputBox.frame_border/frame_padding/frame_margin(Normal draws a round border)Not promoted, with reasons:
Protocol/safety limits:
acp/permission_controller.py:12PERMISSION_TIMEOUT_SECONDSmcp_decision.py:28-29,mcp_inventory.py:20-21timeouts and byte capscomms_root.py:89ROUTE_CHECK_SECONDSacp/agent_session.py:135reconnect_timeout_secondsfile_kind.py:16,clipboard_image.pybyte capsThese guard correctness, not taste.
Scroll and preparation budgets:
widgets/presentation_window.py:60-69max_items, minimum_widgets, lookahead_seconds, scroll_idle_secondswidgets/conversation.py:809MAX_LIVE_BLOCKSacp/projection_attachment.py:67MAX_PREBINDwidgets/line_markdown.py:32RESTYLE_INTERVALwidgets/acp_log.py:35FOLLOW_INTERVALfuzzy_index.py:96MAX_CANDIDATESwork_preparation.py:44,50,transcript_preparation.py:226sizesThese are measured values on the p99.9 frame path, and CLAUDE.md requires review of scroll-path changes.
history_buffer_viewportsis already a setting.Glyph sets:
acp/status.py:118,128⌛ ✔line_blocks.py:101−conversation_actions.py:139␣prompt_actions.py:41⏎sidebar_layout.py:71,76andwidgets/side_bar.py:301,309<── ──>terminal_attention.py:112🐸textual_colours.py:126GLYPHSThese belong in one future
GlyphSetchoice (Unicode vs ASCII for fonts that lack them), not as per-glyph strings. Next step.Candidate preferences left for a follow-up:
widgets/conversation.py:120AUTO_SESSION_TITLE_MAX_LENGTHwidgets/session_details.py:42DETAIL_ROWSscreens/welcome.py:85LIMITwire_history.py:297-298tail/page limitswidgets/terminal.py:29ESCAPE_TAP_DURATION (keyboard timing; I kept clear of the keymap work, P887)Design, not preference:
forms/*andscreens/settings.pyform geometrywidget_view.py:18-52base defaultswidgets/session_tabs.py:43,179ansi/_ansi.py:1302terminal defaultsColours and motion: left to the UX agent's
work/toad-ux-motion. They plug in as Choice fields underuiand need no UI code here.NRA
I used claude-nra's scan of Toad origin/main 5fa29f3 (NRA eca10589, 105 detectors,
complete: true, 0 omitted, 340 findings):91487a46b2(states_default) anda425f587fa(patch_preview): fixed by overrides on the family.1f92187de2(hand_rolled_registration on DeclaredViews -> AutoRegisterMeta): declined. AutoRegisterMeta is an ABCMeta and Textual widgets use_MessagePumpMeta.class X(Widget, metaclass=AutoRegisterMeta)raisesTypeError: metaclass conflict(checked in the interpreter). DeclaredViews also registers many keys per class, not 1:1.ruff --select UP037on the touched files: nothing to fix.Proof (headless Textual pilot, isolated XDG and comms root; SVGs and PNGs in
/home/ts/.cache/agent-scratch/claude-code/toad-settings/)before-main.png,before-settings.png: one long form, no tabs.crops.png: the input box, from top to bottom: before, Compact (identical apart from the now-live placeholder), Normal (round bordered padded box), Flush (no spacing).after-tabs-settings.png,after-page2-settings-keys.png: the two tabs. The second shows "Fail only" where it used to say "Fail".after-search-settings-keys.png: searching "input" shows the Input box group with the Layout dropdown and bounds or default notes.crop-live.png: the Layout set to Normal through the editor's ownsendwhile the app runs. The prompt re-lays out at once, andtoad.jsonon dismiss holds{"ui":{"input":{"box":"normal"}}}.Touches outside the core surface
src/toad/keymap.py: one import line (declared_fields moved). The keymap menu is otherwise untouched (P887 owns it).app.screen.valuesreplacesapp.screen.form.values; the test extension field names a page. I did not run them (no suites here); CI runs them.Durable state
ui.compact-inputandui.prompt_messageare retired keys, and settings decoding refuses unknown keys. The agent-comms carrytools/cutover/carry_toad_input_settings.py(branchwork/toad-input-settings-carry) rewrites them once.compact-input: truebecomesbox: flush, and the message becomes the placeholder. I ran it on a sample file: dry run changed=1, apply changed=1, rerun changed=0, and the result decodes.Correction after hosted debt (run 40718265703)
That run flagged two FunctionLocalImport hits in
setting_effects.py(the new localPromptimports inprompt_layoutandprompt_text) and two UnannotatedParameter hits insetting_choices.py(thestatusparameter of eachexpands_preview).Promptintosetting_effectswould create an import cycle (preferencesimportssetting_effects, andwidgets.promptimportspreferences). So the two effects now live onPromptSettingEffectsin the leaf modulesrc/toad/setting_widget.py, next toSettingVisibility.apply_input_layoutandapply_input_textare staticEffectobjects. Class access returns theEffectitself, soSavedSettingsstill applies a change through the same single path:field.metadata(Effect).apply(app, field).Effect.open_widgets(app, PromptSettingEffects).Promptadds the capability to its class bases, andMessagePromptinherits it.setting_effects.prompt_layoutandprompt_textare deleted, with no aliases.expands_previewstatusparameters are now annotatedToolCallStatus, imported underTYPE_CHECKINGonly.setting_effects.py:18/41/58) are unchanged and are not counted as fixed.2(only the baselinePromptandPromptTextAreaimports remain).preferencesfirst,promptfirst,setting_effectsfirst.ui.inputmetadata entries areEffectobjects:boxisapply_input_layout, the other four areapply_input_text, and the two callbacks are distinct.PromptSettingEffectsappears once in the MRO of bothPromptandMessagePrompt.src,testsandtoolsfinds zero references toprompt_layoutorprompt_text.py_compilepasses, and formbridge stays locked atb67c97e5.tests/textual/test_settings_screen.py,tests/textual/test_forms.py,tests/model/test_saved_settings.py,tests/textual/test_quit_with_unsaved_settings.py), and no hosted debt re-read. These probes do not prove paint, focus or save behaviour.🤖 Generated with Claude Code
https://claude.ai/code/session_01RnH6dR85kyFKcc2tQAJLhK