Skip to content

chore(agents): teach parity + review loops idiomatic Ember patterns - #744

Merged
patricklx merged 5 commits into
mainfrom
chore/parity-agent-ember-idioms
Aug 6, 2026
Merged

chore(agents): teach parity + review loops idiomatic Ember patterns#744
patricklx merged 5 commits into
mainfrom
chore/parity-agent-ember-idioms

Conversation

@patricklx

Copy link
Copy Markdown
Collaborator

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 .tsx structure. 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:

  • A React → Ember translation table (React.Children.map/cloneElement, component-as-prop, controlled/uncontrolled pairs, useRef+useEffect, forwardRef, createPortal, context, useId, Floating UI, debounce, effect cleanup, lazy async values).
  • Worked examples for the six that need more than a table row.
  • A "What NOT to Reach For" list: @ember/render-modifiers, A()/pushObject, set() for tracked state, classic Component+.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 generated icons/*.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.md

  • New required Phase 1 step 4: map each React construct to its Ember idiom before writing code, with an abbreviated table inline.
  • Both implementation paths (A: fix existing, B: new component) now build on that mapping, with an explicit scope limit — modernise legacy patterns in code you're already touching, don't sweep the repo.
  • Three new success criteria (idiom translation, no new "What NOT to Reach For" usage, fully typed signature).
  • Fixes a wrong note: "Native Helpers: element is built-in, don't import it". It isn't — it comes from ember-element-helper, which AGENTS.md's Pitfall 1 has said all along.

scripts/fix-parity-issue.sh

  • The review prompt gets a concrete list of un-Ember-ish tells to check, and must name the replacement pattern plus an existing component using it, so findings are actionable. Scoped to what the branch touches; explicitly told not to invent nits to justify another round.
  • The fix prompt asks for the actual restructure, and to justify any finding it declines.
  • The PR-review and fallback prompts reference the same section.

Verification

  • bash -n clean; shellcheck shows only the 4 pre-existing informational findings.
  • Template renders correctly through the script's sed substitution chain — no placeholder collisions (the {{#in-element}} in the new table is untouched by every substitution rule).
  • Cross-checked each documented pattern against the source it cites, and corrected two drafting errors in the process: TrackedPromise lives in the generated icons/*.ts, not icon.gts, and <Tabs> yields a single positional Pane ({{yield (component TabPane tab=this)}}), not a namespace.

Docs/tooling only — no runtime code touched.

patricklx and others added 5 commits August 6, 2026 04:07
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>
@patricklx
patricklx merged commit a24f64e into main Aug 6, 2026
8 checks passed
@patricklx
patricklx deleted the chore/parity-agent-ember-idioms branch August 6, 2026 04:33
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.

1 participant