docs(architecture): callback props + scoped cells for parent-child (ADR 0017, #518)#617
Conversation
…hild (#518) Record ADR 0017, deciding the parent-child axis of the interactivity epic (#520). Today three mechanisms (emit + g:on, exports + g:on:exports, g:bind) all lower to the same data-gowdk-parent-on-* bubbling transport, so the divergence is in the authoring surface, not the wire. ADR 0017 collapses them along the action vs state axis: - Actions (discrete) -> callback props (onEvent={expr}), lowered to the existing parent-on transport (authoring surface unified, wire unchanged - the same way g:bind desugars today). Replaces emit + exports-as-events. - State (continuous) -> writable scoped cells; bind: is sugar over value-prop + callback-prop. Replaces store-sharing, exports-observation, and g:bind. Depends on #517 (scoped cells). Includes the deprecation/migration plan (precise nudges, no silent aliases) and stays consistent with #384. Direction is decided; implementation is phased (action half is #517-independent; state half gated on #517).
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: a016d1cce6
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| passes behavior: | ||
|
|
||
| ```gwdk | ||
| <TodoItem onDone={Count++} /> |
There was a problem hiding this comment.
Normalize callback names before reusing parent-on attrs
With the existing parent-on wire, callback prop names that follow this onDone shape need an explicit normalization rule. The current JS runtime derives the event to listen for by slicing attr.name from data-gowdk-parent-on-* (internal/clientrt/assets/island.js:1561-1563), and HTML attribute names are lowercased in the DOM while CustomEvent names are case-sensitive. If Phase 1 maps onDone to a Done callback event, the listener becomes done and the callback never fires; specify lower-case event names or another stable encoding before locking this surface.
Useful? React with 👍 / 👎.
| callback prop that writes it back: | ||
|
|
||
| ```gwdk | ||
| <SearchBox bind:value={@page Query} /> |
There was a problem hiding this comment.
Reserve bind: before colliding with prop renames
The proposed bind:value syntax currently collides with component prop renaming: non-g: attributes containing : are parsed as target:source prop aliases (docs/language/components.md:281-284, implemented in componentPropTarget), so <SearchBox bind:value={...}> would be treated as a prop named bind sourced from value, not as a binding directive. Please make the ADR reserve bind:*/change the rename grammar or choose a non-conflicting syntax before implementers add this surface.
Useful? React with 👍 / 👎.
Fold in Codex review points: - Reserve the callback event-name encoding: onCamelCase authoring maps to the lower-cased data-gowdk-parent-on-<event> wire and a lower-cased CustomEvent, so the DOM's attribute lower-casing cannot desync parent listener and child dispatch. - Reserve bind:* as a directive prefix matched before the target:source prop- rename grammar, so bind:value is a binding directive rather than a prop named "bind".
Closes #518.
#518 is an
[Architecture]/ design-direction issue. This PR delivers the deciding ADR 0017.The decision
Today parent↔child has three overlapping mechanisms —
emit+g:on,exports+g:on:exports, andg:bind— and they all already lower to the same wire (data-gowdk-parent-on-<event>, a bubblingCustomEvent; seeinternal/viewrender/component.go+internal/clientrt/assets/island.js). The divergence is purely in the authoring surface.ADR 0017 collapses them along the action vs state axis:
<TodoItem onDone={Count++} />. Replacesemit+ exports-as-events. Lowers to the existingdata-gowdk-parent-on-*transport — we unify the authoring surface, not the wire (the same wayg:binddesugars today; the issue's "implementation honesty" note).<SearchBox bind:value={@page Query} />, wherebind:is sugar over a value prop + a write-back callback prop. Replaces store-sharing,exports-observation, andg:bind. Depends on [Architecture] Unify reactive state under one primitive with a scope axis (state @scope) #517 (scoped cells).Net: events are callbacks, state is scoped cells — two principled mechanisms instead of three, no new transport, no event bus (#514 stays closed). Consistent with #384 (one bounded-client IR/evaluator).
Why a doc, phased implementation
[Architecture]issue; the state half is blocked on #517 (scoped cells) and both halves are gated on #384. ADR 0017 makes the binding decision and the migration plan now (deprecateemit/exports/g:bindwith precise nudges, breaking acceptable in 0.x), and sequences implementation:bind:→ value-prop + callback-prop over a scoped cell.Checklist (from #518)
exports-style continuous observation via shared scoped cells ([Architecture] Unify reactive state under one primitive with a scope axis (state @scope) #517). (decided; Phase 2, gated on [Architecture] Unify reactive state under one primitive with a scope axis (state @scope) #517)bind:desugars to value-prop + callback-prop. (decided)emit/exports/g:bind. (migration plan decided)Files
docs/engineering/decisions/0017-callback-props-and-scoped-cells.mddocs/engineering/decisions/README.md(index; also backfills 0014/0015)CHANGELOG.mdPart of the interactivity-unification epic #520.