Skip to content

Move the Fusion starter patch into agent-native and check it in CI - #6930

Closed
samijaber wants to merge 2 commits into
mainfrom
claude/trusting-fermi-3gywpw
Closed

samijaber wants to merge 2 commits into
mainfrom
claude/trusting-fermi-3gywpw

Conversation

@samijaber

@samijaber samijaber commented Oct 7, 2026 •

Copy link
Copy Markdown
Contributor

The Fusion starter (builder-agent-native-starter) builds its blank app from our Chat template. On each sync it applies about 30 exact find-and-replace edits to the mirrored template (.github/starter-patch/apply.mjs on its template branch). Nothing in this repo runs those edits, so a Chat or skill rewrite here breaks them silently. The break shows up hours later as a failed starter sync in another repo. #6872 broke 20 of them today. Before that, BuilderIO/builder-agent-native-starter#409 (9/28) and BuilderIO/builder-agent-native-starter#413 (9/30) were fixes for the same kind of drift.

This PR moves the patch here and checks it on the PRs that can break it, so a break fails in the PR that caused it and its fix lands in the same change.

Changes

  • starters/fusion/ holds the patch: the overlay, owned.txt, delete.txt, and apply.ts (converted from apply.mjs; Node 24 runs it directly through type stripping). The content matches BuilderIO/builder-agent-native-starter#415 byte for byte. That PR re-anchors the patch on Improve generated app scaffolds and skills #6872's rewrite and converts it to TypeScript on the starter side.
  • push-starter-template.yml copies starters/fusion/ to the starter's .github/starter-patch/ on every mirror, and also runs when starters/fusion/** changes. The starter's sync.yml keeps applying the patch at merge time, after skills update scaffold, so the patch still runs at the same point in the sync.
  • The Security guards job gains a step, "Fusion starter patch applies to the Chat template". It materializes templates/chat, applies the patch, then applies it again and requires a clean tree. It takes about 2 seconds, so it joins an existing job instead of adding one.
  • ci-change-scope.ts gains a starter_patch check that turns on for templates/chat/, packages/core/, or starters/fusion/ changes. A change only under starters/fusion/ selects lint, guards and starter_patch, the same treatment as a guard-script change. Without this, it would fall through to a full run.
  • .oxlintrc.json ignores starters/**, as it already does packages/core/src/templates/**. The overlay is app code that only type-resolves inside the starter, and the starter lints and builds it there.

Verification

  • The check catches the real break: the pre-Improve onboarding auth and shared design systems #415 patch from the starter's template branch fails on today's materialized Chat template with expected snippet not found in .../AGENTS.md, the same error the starter sync hit. The current patch applies cleanly and comes back clean on the second application.

  • The classifier on real main commits:

    Commit starter_patch
    61543b29 (Improve generated app scaffolds and skills #6872) on (full run)
    4a6dcd0c (docs only) off
    ed781044 (Content fix) off
  • Classifier tests: node --experimental-strip-types --test scripts/ci-change-scope.test.ts scripts/ci-build-workspaces.test.ts scripts/ci-test-lanes.test.ts passes 62/62. The new cases cover the check being on, off, on for a starters/fusion-only change, and on for full or tooling-only runs.

  • Repo checks: pnpm guards passes all 85 checks. The new content needed Agent-Native branding, UTM tags on the shipped README links, and coercion-ok reasons on two catches that intentionally treat failure as absent. oxfmt --check is clean.

  • Typecheck: apply.ts passes strict tsc against @types/node.

  • The patched starter builds: on the patched tree, pnpm install, typecheck, build, and agent-native doctor all pass. Install resolves @agent-native/otel@0.1.0.

Rollout

Merge starter#415 first, or at the same time. It switches the starter's sync.yml to apply.ts, and the first mirror after this PR then copies over identical content. After that, starter-patch edits belong in this repo. The starter README now says so, and a direct edit there would be overwritten by the next mirror.

🤖 Generated with Claude Code

https://claude.ai/code/session_01RUDitSt3FB37KqMKfQgkJj

The Fusion starter repo patches each mirrored Chat template with exact
search/replace edits. Agent Native changes broke those edits three times in
ten days (most recently #6872), and each break surfaced only hours later as a
failed starter sync in another repo.

- starters/fusion/ now holds the patch (apply.ts, overlay, owned and delete
  lists), converted from apply.mjs. push-starter-template.yml copies it to
  the starter's .github/starter-patch/ on every mirror.
- Security guards materializes templates/chat, applies the patch, and applies
  it again to prove idempotence on PRs that touch templates/chat,
  packages/core, or starters/fusion, so a break fails the PR that causes it
  and lands its fix in the same change.
- ci-change-scope gains a starter_patch check; starters/fusion changes stay
  targeted instead of forcing a full run.
@github-actions

github-actions Bot commented Oct 7, 2026

Copy link
Copy Markdown
Contributor

Visual recap — generation failed

Recap authentication failed — the PLAN_RECAP_TOKEN secret may be expired or revoked. Re-mint it with npx -y @agent-native/core@latest reconnect <app-url> (or npx @agent-native/core@latest connect <app-url> for first-time setup) and update the repo secret.

builder-io-integration[bot]

This comment was marked as outdated.

The copy-paste CRUD reference listed and mutated every row with no owner
check, so an app built from it let any signed-in user read or change another
user's notes. The example table is now ownable, reads go through
accessFilter, and writes through assertAccess. updatedAt is written as the
ISO string its text column expects.

uniqueReplace now fails when the original snippet survives next to its
replacement instead of treating the file as patched. Every deleted path is
in owned.txt so a later patch that stops deleting one restores it. The docs
now say the starter ships homePath "/" (from starter#388) and when to
replace it.

Copy link
Copy Markdown
Contributor Author

Closing this in favor of a different approach. Instead of moving the starter's find-and-replace patch here and checking it in CI, the Fusion starter becomes a bundled core scaffold template (packages/core/src/templates/fusion-starter) with its own skill overrides, and the patch is deleted entirely. A follow-up PR will do that.

8903704 on this branch addresses the review findings, and that content is the seed for the new template:

  • The CRUD example is owner-scoped (ownableColumns, accessFilter, assertAccess), and updatedAt is written as an ISO string.
  • Every deleted path is now in owned.txt.
  • uniqueReplace rejects a file that still holds the original snippet next to its replacement. This goes away with the patch.
  • The docs match the homePath: "/" that template app login / auth fixes builder-agent-native-starter#388 added on purpose, and DEVELOPING.md says when to replace it.

Generated by Claude Code

@samijaber samijaber closed this Oct 7, 2026

@builder-io-integration builder-io-integration Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Builder reviewed your changes and found 5 potential issues 🟡

Review Details

Incremental Code Review Summary

The updated commit, 8903704eb, addresses four prior findings: CRUD examples now apply ownership/share access controls and bounded pagination, the timestamp update uses the schema's string representation, replacement handling rejects mixed old/new snippets, and deleted paths are added to the restore manifest. I verified these changes in the current patch and resolved those four prior comment threads, plus the auth landing-route thread after the updated starter documentation now explicitly covers replacing / when the signed-in app moves elsewhere.

The architecture remains sound overall: the patch is still a targeted overlay with materialize/apply/reapply checks, and the expanded data example is safer. This is a standard-risk PR. The incremental review found new concerns in the restoration manifest and several previously identified-but-not-posted functional/CI gaps: one config file modified by the patch still is not restored, the advertised thread navigation still points to a deleted route, and the scope check does not cover Toolkit-only changes despite Toolkit imports in the overlay. The patch also pins pnpm below the version required for its package-age exclusion syntax. Additional medium findings cover the navigation-state reader, domain-operation guard, and migration URL selection.

🧪 Browser testing: Skipped — browser-test-planner is unavailable in this environment.

Comment thread starters/fusion/owned.txt
DEVELOPING.md
actions/navigate.ts
actions/view-screen.ts
agent-native.json

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Restore agent-native.config.ts before reapplying the patch

applyReplacements() changes agent-native.config.ts to add onboarding.firstRun, but this manifest entry is agent-native.json; agent-native.config.ts is absent from the restoration list. A later --source-root sync therefore does not restore the pristine config before reapplying, so the patch can retain stale output or fail to match after upstream changes. Add agent-native.config.ts to this append-only manifest.

Fix in Builder

Comment thread starters/fusion/apply.ts
Comment on lines +404 to +405
`minimumReleaseAgeExclude:
- "@agent-native/*"

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Use a pnpm version that supports wildcard release-age exclusions

This patch adds the @agent-native/* minimumReleaseAgeExclude pattern, while the generated package manager is pinned to pnpm 10.14.0. That release does not support the minimum-release-age exclusion/wildcard syntax being relied on here, so same-day framework packages may still be rejected by Fusion's install policy; pin to a supporting pnpm version or use syntax supported by the pinned version.

Fix in Builder

Comment on lines +58 to +60
const threadId =
typeof command?.threadId === "string" ? command.threadId.trim() : "";
return threadId ? `/chat/${encodeURIComponent(threadId)}` : "/";

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Do not navigate to the deleted Chat thread route

When a command includes threadId, this still navigates to /chat/<id>, but delete.txt removes app/routes/chat.$threadId.tsx and the starter intentionally ships without Chat. The supported navigate --threadId=... option therefore lands on a guaranteed 404; remove the thread option/branch or provide the route when Chat is enabled.

Fix in Builder

readmeChanged ||
starterPatchChanged,
qa_static: templateChanged,
starter_patch: chatChanged || coreChanged || starterPatchChanged,

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Run the patch compatibility check for Toolkit changes

The generated overlay imports Toolkit APIs such as AppProviders, ErrorBoundary, and HeaderActionsProvider, but this selector enables the compatibility lane only for Chat, core, or patch changes. A Toolkit-only API change can merge without checking those imports against the patched starter; include toolkitChanged in the selector.

Fix in Builder

Comment on lines +20 to +21
run: async () => {
const navigation = await readAppState("navigation");

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Read navigation state from the current tab

The generated root uses useAgentRouteState with browserTabId: TAB_ID, which writes navigation under a tab-scoped key, while this action reads the global navigation key. In normal sessions it therefore returns “No application state found” instead of the visible screen; read the current-tab-scoped key written by the UI.

Fix in Builder

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