chore(agents): teach parity + review loops idiomatic Ember patterns - #744
Merged
Conversation
The parity-fix agent had almost no guidance beyond "simplify - don't overcomplicate React patterns", which pushed it toward transliterating the React source rather than translating it. The review+fix loop had no idiom dimension at all, so un-Ember-ish structure survived every round. AGENTS.md gains an "Idiomatic Ember Patterns" section: a React -> Ember translation table plus worked examples, all taken from components already in this repo (contextual components with WithBoundArgs in tabs/tree-view/ data-table, ComponentLike args in link/text-area, the controlled vs uncontrolled getter in tree-view's TreeNode, ember-modifier's functional modifier and yielded ModifierLike in -private/tooltip, in-element via Portal, restartable ember-concurrency tasks in search, registerDestructor cleanup in tabs, TrackedPromise in the generated icons). It also names what not to reach for -- @ember/render-modifiers, A()/pushObject, set() for tracked state, this.element -- which older components still use. agent-prompt.md gets a required Phase 1 step to map each React construct to its Ember idiom before writing code, wires that into both implementation paths, and adds three success criteria for it. Also corrects the "Native Helpers" note, which claimed `element` is built-in and shouldn't be imported -- it comes from ember-element-helper, as AGENTS.md's Pitfall 1 already said. fix-parity-issue.sh's review prompt now checks a concrete list of un-Ember-ish tells and requires each finding to name its replacement pattern and an existing component that uses it, so the fix round can act on it directly; the fix prompt asks for the restructure rather than a workaround. The PR-review and fallback prompts point at the same section. Signed-off-by: Patrick Pircher <patrick.pircher@ibm.com>
Review round 1 follow-up on the parity/review agent guidance: - Floating-UI row: point at the addon's own <Popover>/<PopoverContent> as the default for Carbon parity work, with ember-primitives' Popover reserved for new positioning primitives. popover.gts is no longer cited as an example of a pattern it doesn't use (it is hand-rolled maths + render modifiers). - Split the controlled/uncontrolled rule in two: a React value/defaultValue pair keeps BOTH args and defers to @value when defined (text-input.gts); only a single dual-purpose prop keys on the change handler's presence (TreeNode.expanded). The old single rule would have dropped @DefaultValue from new input APIs, contradicting the "every React prop" criterion. - Make the signature rule conditional: Args always, Element when the component spreads ...attributes, Blocks only for blocks it actually yields. The old hard rule would manufacture findings against the ~20 components with no Blocks and invite an empty Blocks entry that type-checks but renders nothing. - Type the §1 worked example (Owner / signature args, plain reassigned tracked array) and add a caveat that tabs.gts's A()/pushObject and any-typed constructor are legacy; lead with data-table.gts / tree-view.gts. - Trim the duplicated translation tables in agent-prompt.md and fix-parity-issue.sh to the highest-value rows plus a pointer to AGENTS.md as the single source of truth, so the copies stop drifting. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Signed-off-by: Patrick Pircher <patrick.pircher@ibm.com>
Round-2 review fixes for the parity/review-loop idiom docs: - §3's opener claimed a static @value must never freeze the component, contradicting its own Case A (text-input.gts returns `this.args.value ?? this.internalValue`, so a bare @value is genuinely controlled, matching React). Rewrite around "every component must offer *some* uncontrolled path" and split it per case. - Caveat the search.gts debounce citation in AGENTS.md and in the parity agent prompt: the file is a worked example of four things this document forbids (render-modifiers trigger, render-time tracked mutation, a document listener with no registerDestructor, `any` in its signature). - Reword the ember-primitives "What NOT to Reach For" bullet, which told agents to check ember-primitives first for popover/portal — the exact thing §5 forbids. Point at the addon's own <Portal>/<Popover> first. - Hoist the `constructor(owner: any, …)` caveat out of the tabs.gts note: it is repo-wide (13 components, including the text-input/number-input exemplars §3 says to follow), so scoping it to tabs implied the others were clean. Leave the A()/pushObject half where it was. - Give the PR-triage prompt in fix-parity-issue.sh the same idiom pointer the sibling review and fix paths already carry; it writes code too. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Signed-off-by: Patrick Pircher <patrick.pircher@ibm.com>
- §1 attributed child->parent registration to DataTable, but only tabs.gts registers children; DataTable/TreeView just yield bound namespaces. Scope the registration paragraph to Tabs and note the split in the caveat so an agent doesn't hunt for the pattern in files that lack it. - §6 cited search.gts:54-64; runSearch is actually 55-65. Cite it by name instead, matching the §6 prose and translation table, so it can't rot. - §5 portal snippet showed a top-level <template> using `this`, which isn't valid .gts. Wrap it in the Portal class it excerpts. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Signed-off-by: Patrick Pircher <patrick.pircher@ibm.com>
The signature guidance said "`Args` — always", but 16 components here
legitimately have no `Args` (argless wrappers like `form-item.gts`,
`toggletip/label.gts`, the ui-shell dividers). The review checklist in
fix-parity-issue.sh told the review agent to flag "a missing 'Args'" as a
defect, so those components would draw a bogus finding and get an empty
`Args: {}` bolted on. Make `Args` conditional like `Element`/`Blocks`, and
extend the existing "Do NOT flag..." clause to cover it.
Also correct the DataTable example: only Toolbar/Header/EachBodyRows bind
the table instance — SearchInput/Pagination/Table bind loading and paging
state, and Column/Menu are yielded as plain `typeof`. As written it taught
"bind the parent instance to every yielded component", the wrong
generalisation.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Signed-off-by: Patrick Pircher <patrick.pircher@ibm.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
Gives the automated parity-fix agent (and its review+fix loop) real guidance on writing idiomatic Ember, instead of transliterating the Carbon React source.
Why
The agent's instructions amounted to "Simplify: don't overcomplicate React patterns". That's not enough signal to pick between, say, yielding a contextual component and iterating over what was passed in — so generated components tended to mirror the
.tsxstructure. The review+fix loop had no idiom dimension at all, so un-Ember-ish structure survived all 5 rounds.Changes
AGENTS.md— new "Idiomatic Ember Patterns" section:React.Children.map/cloneElement, component-as-prop, controlled/uncontrolled pairs,useRef+useEffect,forwardRef,createPortal, context,useId, Floating UI, debounce, effect cleanup, lazy async values).@ember/render-modifiers,A()/pushObject,set()for tracked state, classicComponent+.hbs,this.element.Every entry points at a component already in this repo —
tabs.gts,tree-view.gts,data-table.gts,link.gts,text-area.gts,-private/tooltip.gts,portal.gts,search.gts,services/*, the generatedicons/*.ts. Nothing here is aspirational.The "Simplification Guidelines" section is reframed: simple means fewer moving parts for the consumer, not fewer lines in the component.
.github/workflows/templates/agent-prompt.mdelementis built-in, don't import it". It isn't — it comes fromember-element-helper, which AGENTS.md's Pitfall 1 has said all along.scripts/fix-parity-issue.shVerification
bash -nclean;shellcheckshows only the 4 pre-existing informational findings.sedsubstitution chain — no placeholder collisions (the{{#in-element}}in the new table is untouched by every substitution rule).TrackedPromiselives in the generatedicons/*.ts, noticon.gts, and<Tabs>yields a single positionalPane({{yield (component TabPane tab=this)}}), not a namespace.Docs/tooling only — no runtime code touched.