Skip to content

Own declared form fields, pages and bounds for every UI backend - #16

Merged
trissim merged 1 commit into
mainfrom
work/settings-semantics
Oct 28, 2026
Merged

trissim merged 1 commit into
mainfrom
work/settings-semantics

Conversation

@trissim

@trissim trissim commented Oct 28, 2026

Copy link
Copy Markdown
Contributor

This PR moves field semantics out of Toad's Textual backend and into formbridge. A second backend (pyqt-reactive) can then read them without restating them. The Toad settings-menu PR (OpenHCSDev/toad, branch `work/toad-settings-menu`) depends on this one and imports `formbridge.forms.declared_fields`.

What moved (from toad/forms, deleted there)

`src/formbridge/forms/declared_fields.py`:

  • `DeclaredField` and `OptionalMemberField`: label, help (cleandoc of the docstring), default, metadata lookup, validation before edit, edit through ObjectState. Formerly `toad/forms/declared_views.py:76-163`.
  • `declarations(annotation)` and `DeclaredViews`. `DeclaredViews` is the registry a backend's widget family uses to choose a widget for each declared kind (`shows = (int,)`). Formerly `declared_views.py:26-74`.
  • `FieldPath`, `declared_annotation`, `declared_type`. Formerly `toad/forms/field_path.py`.
  • `Placeholder` and `Multiline` kinds. Formerly `toad/forms/kinds.py`.
  • `DeclaredField.search_text`, the field's label plus the labels of the groups that contain it. Formerly computed in Toad's `ValueView.init`.

New facts read from the declaration

  • Bounds: `DeclaredField.interval` merges every annotated_types `Ge/Gt/Le/Lt/Interval` into one `Interval`, reading each bound by its `Interval` field name, so no class list is kept. `bounds_text` renders it, e.g. `>= 1, <= 10`. Toad shows it under the help text. pyqt-reactive's `RangeConfigurable` can now be fed real bounds instead of its hard-coded ±999999 (`widget_strategies.py:473-477`).
  • Pages: `FormPage` is `Annotated` metadata on a top-level group: `Annotated[UiSettings, INTERFACE]`. `form_pages(state, shown)` derives the tabs and their order from the fields that name them. A paged form that leaves a top-level field without a page raises `TypeError`.

Backend interface

A backend implements two `DeclaredViews` families:

  • field views: one per formbridge `ParameterInfo` kind (value or nested group);
  • value editors: one per declared kind (bool, int, float, str, `Multiline`, `AnnotationChoices`, `Enum`, `Optional`).

It also draws one tab per `FormPage`. The editors call `DeclaredField.edit`, and ObjectState owns values and dirty marks. Toad's Textual implementation lives in `toad/forms/{form_view,editors,presentation}.py`.

Checks

  • New tests: `tests/test_declared_fields.py` covers bounds merging, search text, page order and refusal of an unpaged field. I executed the four functions directly in the agent-comms runtime interpreter (pytest is not installed there), and all four passed. `ruff check` passes on both new files.
  • NRA (shared checkout, 97 detectors, `complete: true`, 0 omitted) on this branch: 0 findings name `declared_fields`.
  • Not run: the full pytest suite (CI runs it).

Next, not in this PR

  • pyqt-reactive is still on formbridge `>=0.1.3,<0.2` with `objectstate<4`. It cannot read these until it moves to 0.2/ObjectState 5.
  • Both backends still hand-code dirty-mark glyphs and ancestor propagation, value-kind widget selection (pyqt's if-chain at `widget_strategies.py:842-906`), and the "Pipeline default:" placeholder text.
  • `ui_hidden` is still a string-keyed metadata flag (`parameter_form_service.py:284`). It should become a declared kind.
  • Not applied: the 4 formbridge codemods in claude-nra's ranked list (`1ea58902e5`, `51ad6c5e21`, `daa4e87a6d`, `fcae361a8e`). They concern bridge DTOs, not settings, and need NRA #124.

🤖 Generated with Claude Code

https://claude.ai/code/session_01RnH6dR85kyFKcc2tQAJLhK

Toad's Textual forms carried the field semantics (label, help, default,
validation, edit, search text, typed field paths, Multiline/Placeholder
kinds, the widget-family registry) in toad/forms, where a second backend
such as pyqt-reactive would have to restate them. They now live in
formbridge.forms.declared_fields, plus two new facts read from the
declaration: the declared bounds (annotated_types Ge/Gt/Le/Lt/Interval,
merged by Interval field name) and FormPage, the tab a top-level group
is shown on.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RnH6dR85kyFKcc2tQAJLhK
Copilot AI balanced review requested due to automatic review settings October 28, 2026 15:35
@trissim
trissim merged commit b67c97e into main Oct 28, 2026
1 of 11 checks passed

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.

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