render: document createSurface as an explicit no-op in Apply - #48
Merged
Conversation
Apply silently dropped createSurface messages. The stance is now explicit and documented: a2tea treats the first updateComponents as implicit surface creation, host WithStyles owns theming (chrome stays monochrome so the host theme wins), and the component catalog is compiled in — so createSurface's theme (primaryColor/iconUrl/agentDisplayName) and catalogId hints carry nothing honorable and are deliberately ignored. - Apply gains an explicit no-op case for CreateSurface with a doc comment, and the Apply doc comment spells out the rationale. - docs/wire-format.md moves createSurface from the "Not yet" list to a documented decision in the implemented section. - Tests assert createSurface neither errors nor mutates state (view, data model, focus all survive; theme does not leak) and that a surface is only established by updateComponents, including the createSurface-then- updateComponents batch shape agents actually send. Fixes #47 Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
joestump
commented
Jul 18, 2026
joestump
left a comment
Collaborator
Author
There was a problem hiding this comment.
Adversarial review of the createSurface no-op change (issue #47). Verdict: no blockers; two nits.
What I verified
- Gate:
go build ./... && go vet ./... && go test ./...all green on the branch; both new tests pass with-count=1(not cache artifacts). - State preservation: the no-op case in
render/composite.go:38cannot mutate anything — confirmed by readingApply/applyComponents;TestApplyCreateSurfaceIsNoOpasserts real behavior (byte-identicalView().Content, theme text does not leak, focus survives and Enter still emitsButtonClicked{ID: "btn2"}), not mere execution. - Alive semantics:
TestApplyCreateSurfaceDoesNotCreateStatecorrectly pins thatcreateSurfacealone leavesrootID == ""(Apply returns false) and that the realisticcreateSurface→updateComponentsbatch establishes the surface. MatchesRender's first-updateComponents establishment ina2tea.go:130-141. - Ticket Done-when: explicit case + doc comment in code ✅,
docs/wire-format.mdmoved from "Not yet" to a documented decision ✅, tests ✅,website/untouched and the deferral is called out in the PR body per repo policy ✅. - Type claims: checked
tmc/a2uiv09 —Themereally isprimaryColor/iconUrl/agentDisplayName, and nothing onCreateSurfacemaps onto the monochrome-chrome contract. The rationale in the doc comment is accurate.
Nits (non-blocking)
sendDataModelis ignored but undocumented —render/composite.go:21-26anddocs/wire-format.mdenumeratethemeandcatalogIdas the ignored payload, buta2ui.CreateSurfacecarries a fourth field,SendDataModel bool(v09/message.go:24), which this no-op also drops. Since the whole point of #47 is "no silent drops," add one line notingsendDataModelis likewise ignored (a2tea has no client→agent data-model channel;ActionEvent.Contextalready round-trips input values). Fine to fold into the post-merge docs pass.- Switch-order shadowing on malformed messages — the new
case m.CreateSurface != nilatrender/composite.go:38precedesUpdateComponents, so a spec-invalid message carrying bothcreateSurfaceandupdateComponentsnow drops the components where it previously applied them. The wire format is a one-of, andScanvalidates upstream, so this is theoretical — but placing the no-op case last would make Apply strictly more forgiving at zero cost.
🤖 Posted on behalf of @joestump by Claude.
joestump
added a commit
that referenced
this pull request
Jul 18, 2026
Union merge of interactive tab switching (#45) with the five feature PRs that landed on main since the branch forked (#48 createSurface no-op, #49 ChildList templates, #51 Modal open/close, #52 editable inputs, #53 InputSubmitted/ChoiceSelected dispatch). Nothing from either side was dropped: - render/render.go: Surface keeps ALL state — activeTabs (branch) plus fieldValues/checkValues/choiceValues/sliderValues/choiceCursor/ openModals/scope (main). isInteractive now also covers non-empty Tabs bars. collectFocusables walks only the ACTIVE tab's subtree, so inputs and modals inside inactive tabs are excluded from the focus ring, and only an open modal's content joins it. Key routing: left/right step a focused slider and switch tabs on a focused tab bar; h/l switch tabs only on the tab bar and stay literal runes on a focused TextField/DateTimeInput; enter/space/esc keep main's button/checkbox/choice/modal/InputSubmitted behavior. - render/composite.go: deleteSurface clears every state map including activeTabs; applyComponents preserves active-tab, focus, modal, and edited-value state across merges, with out-of-range tabs clamping to the first at read time. - render/tabs_test.go: two new tests pin the union — slider-focused vs tab-bar-focused left/right disambiguation, and inputs/modals inside an inactive tab excluded from the focus ring. - README.md / docs/wire-format.md: merged feature descriptions; the 'not yet' lists are now empty (issues #42–#47 all landed). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
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.
What
Apply(render/composite.go) silently droppedcreateSurfacemessages. This PR makes the stance explicit and documented rather than implicit: a2tea deliberately ignorescreateSurface— a surface is established by its firstupdateComponents, hostWithStylesowns theming, and the component catalog is compiled in.How
Applygains an explicitcase m.CreateSurface != nilno-op with a doc comment, and theApplydoc comment spells out the rationale: the message'sthemehints (primaryColor,iconUrl,agentDisplayName) have no honorable mapping in a deliberately monochrome renderer where the host theme wins, andcatalogIdis ignored because the catalog is compiled in, by design. (Inspectedtmc/a2ui'sCreateSurface/Themeto confirm nothing is trivially honorable — applyingprimaryColorwould violate the monochrome-chrome contract.)docs/wire-format.mdmovescreateSurfacefrom the Not yet list to a documented decision in the implemented section.Tests
TestApplyCreateSurfaceIsNoOp: acreateSurface(with catalog URI + theme) neither errors nor mutates state — rendered view is byte-identical before/after, theme text does not leak, the surface stays alive, and focus survives (Enter still activates the focused button).TestApplyCreateSurfaceDoesNotCreateState:createSurfacealone does not make a surface renderable; thecreateSurface→updateComponentsbatch shape agents actually send establishes it via theupdateComponents.go build ./... && go vet ./... && go test ./...all green locally.Note: the ticket's Done-when also mentions the site's "Not yet"/implemented lists —
website/changes are deferred to the post-merge docs pass per repo policy.Fixes #47
🤖 Posted on behalf of
@joestumpby Claude.