Skip to content

refactor: Migrate all templates to createFileSync() pattern#62

Merged
gopinav merged 5 commits intomainfrom
feat/migrate-templates-file-sync
Mar 20, 2026
Merged

refactor: Migrate all templates to createFileSync() pattern#62
gopinav merged 5 commits intomainfrom
feat/migrate-templates-file-sync

Conversation

@gopinav
Copy link
Contributor

@gopinav gopinav commented Mar 20, 2026

Summary

  1. All 8 templates (analytics, brand, calendar, content, imagegen, mail, slides, videos) now have createFileSync() wiring matching the default template pattern from PR feat: Improve File Sync DX — hardening, factory, Convex adapter, docs #57
  2. Each template gets: async createAppServer(), .then() node-build, diagnostic endpoint (/api/file-sync/status), SSE extraEmitters, graceful shutdown, and conflict notification
  3. Templates without SSE (analytics, calendar, slides, videos) now have createFileWatcher + createSSEHandler
  4. Dead Firestore/initFileSync() docs in calendar and slides AGENTS.md replaced with correct createFileSync() documentation
  5. New sync-config.json files created for brand, mail, and videos templates
  6. Content template correctly uses contentRoot: "./content" (not ./data)
  7. Mail template syncs ./data only — ./application-state is ephemeral and excluded
  8. Slides keeps existing /api/decks/events alongside new /api/events for file sync

Testing

  • pnpm build passes — all 13 workspace packages build successfully
  • All 182 existing tests pass
  • Sync remains opt-in via FILE_SYNC_ENABLED=true — no behavioral change without it

@netlify
Copy link

netlify bot commented Mar 20, 2026

Deploy Preview for agent-native-fw ready!

Name Link
🔨 Latest commit 638e3c3
🔍 Latest deploy log https://app.netlify.com/projects/agent-native-fw/deploys/69bdc963bd29070008f00f29
😎 Deploy Preview https://deploy-preview-62--agent-native-fw.netlify.app
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.

To edit notification comments on pull requests, go to your Netlify project configuration.

Copy link

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

Choose a reason for hiding this comment

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

Builder has reviewed your changes and found 3 potential issues.

Review Details

Code Review Summary — PR #62

This PR migrates all 8 templates (analytics, brand, calendar, content, imagegen, mail, slides, videos) to the createFileSync() pattern established in PR #57. Each template gets an async createAppServer(), a diagnostic endpoint, SSE extra-emitters, graceful shutdown, and conflict notification. The approach is architecturally sound and the documentation updates are thorough and accurate.

Risk: Standard (🟡) — Core server infrastructure across 8 templates; no auth/payment changes.

Key Findings

🔴 HIGH:

  • Route collision in calendar — The new /api/events SSE route shadows the existing GET /api/events events-list endpoint (present but not visible in the diff). In H3, the later registration wins, so listEvents is replaced by the SSE stream, breaking the calendar's event list UI even when sync is disabled.

🟡 MEDIUM:

  • SIGTERM handler in factoryprocess.on("SIGTERM", ...) is registered inside createAppServer(). This fires before createProductionServer()'s own shutdown handler (server.close(...)), causing abrupt exit instead of graceful connection draining. Additionally, repeated calls (tests, dev hot-reload) accumulate listeners up to Node's MaxListenersExceededWarning limit.
  • Missing .catch() on node-build.ts — All 8 templates now use .then() without .catch(). If createFileSync() rejects (e.g. an unhandled exception), Node emits an unhandled rejection and the server silently never starts.
  • Conflict notification overwrites silentlyfs.writeFileSync("application-state/sync-conflict.json", ...) unconditionally replaces any existing conflict. Two rapid conflicts lose the first; a conflict-resolved event for conflict A can delete the file while it describes conflict B.

Positives

  • ✅ Opt-in design (FILE_SYNC_ENABLED=true) ensures zero behavioral change without explicit opt-in
  • ✅ Content template correctly uses contentRoot: "./content" instead of ./data
  • ✅ Mail template correctly excludes google-auth.json from sync patterns
  • ✅ AGENTS.md docs are comprehensive and consistent across all templates

Found by running 4 parallel code-review agents with randomized file ordering.


View in Builder.io

Code review by Builder.io

);

// Graceful shutdown
process.on("SIGTERM", async () => {

Choose a reason for hiding this comment

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

🟡 SIGTERM handler inside createAppServer() bypasses graceful shutdown and accumulates on repeated calls

The handler calls process.exit(0) before createProductionServer()'s own SIGTERM listener can call server.close(), dropping in-flight requests. Additionally, since process.on() appends a new listener on every createAppServer() call (dev hot-reload, tests), listeners accumulate causing MaxListenersExceededWarning. Move signal handling to node-build.ts or return a { app, shutdown } tuple so the caller owns lifecycle management. Affects all 8 templates.


How did I do? React with 👍 or 👎 to help me improve.

import { createProductionServer } from "@agent-native/core/server";
import { createAppServer } from "./index.js";
createProductionServer(createAppServer());
createAppServer().then((app) => createProductionServer(app));

Choose a reason for hiding this comment

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

🟡 Missing .catch() on async startup — unhandled rejection if createFileSync() throws

createAppServer().then((app) => createProductionServer(app)) has no .catch(). If createAppServer() rejects (e.g. an exception inside createFileSync()), Node.js terminates with an unhandled rejection and no useful diagnostic, and the already-created file watcher is never closed. Add .catch((err) => { console.error('[startup] Fatal:', err); process.exit(1); }). Affects all 8 node-build.ts files.


How did I do? React with 👍 or 👎 to help me improve.

Copy link

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

Choose a reason for hiding this comment

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

Builder has reviewed your changes and found 2 potential issues.

Review Details

Code Review Summary — PR #62 (Update 2)

This review covers the full PR diff including the new packages/core/src/server/sse.ts change (widening extraEmitters from EventEmitter to an EventLike interface). The sse.ts change is correct — createSSEHandler uses .off() for cleanup and the sync emitter implements both on and off. No issues there.

Risk: Standard (🟡) — Core infrastructure across 8 templates + a core package change.

Previously reported issues (calendar route collision, SIGTERM handler, missing .catch()) remain open and unfixed.

New Findings

🔴 HIGH:

  • createFileSync ignores contentRoot for config pathloadSyncConfig() has a hardcoded fallback to content/sync-config.json. Since createFileSync never derives syncConfigPath from contentRoot, every template using contentRoot: "./data" silently gets empty sync patterns. Sync is effectively disabled for 7 of 8 templates (brand, mail, videos, calendar, analytics, slides, imagegen) even when FILE_SYNC_ENABLED=true. Only the content template accidentally works because ./content matches the hardcoded default. Found independently by 2 of 4 agents.

🟡 MEDIUM:

  • Conflict notification file is last-write-wins and prematurely deleted — Multiple simultaneous conflicts overwrite each other in sync-conflict.json. Worse: any conflict-resolved event unconditionally deletes the file, even if it resolved a different conflict and others are still outstanding. Found by 3 of 4 agents.

Found by running 4 parallel code-review agents with randomized file ordering.


View in Builder.io

Code review by Builder.io

try {
if (event.type === "conflict-needs-llm") {
fs.mkdirSync("application-state", { recursive: true });
fs.writeFileSync(

Choose a reason for hiding this comment

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

🟡 Conflict notification overwrites concurrent conflicts and prematurely deletes on unrelated resolves

The single sync-conflict.json file is always overwritten on conflict-needs-llm, so a second concurrent conflict silently drops the first. Worse, any conflict-resolved event unconditionally deletes the file even if it resolved a different file — leaving other active conflicts invisible to the agent. Fix: key conflict files per path (e.g. sync-conflict-${safeId}.json) or check syncResult.fileSync.conflictCount === 0 before deleting. Same pattern copied across all 8 templates.


How did I do? React with 👍 or 👎 to help me improve.

Copy link

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

Choose a reason for hiding this comment

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

Builder has reviewed your changes and found 1 potential issue.

Review Details

Code Review Summary — PR #62 (Update 3)

This update fixes the previously reported calendar route collision by renaming the SSE endpoint from /api/events to /api/sse. However, the client-side SSE connection was not updated to match.

Risk: Standard (🟡)

Previously reported issues (SIGTERM handler, missing .catch(), createFileSync syncConfigPath bug, conflict notification overwrite) remain open and unfixed.

New Finding

🔴 HIGH:

  • Calendar client still connects to /api/events for SSE — The server was renamed to /api/sse but useFileWatcher in calendar/client/App.tsx has no eventsUrl override and defaults to /api/events. The EventSource opens against the calendar CRUD endpoint (which returns JSON), so real-time file-sync updates are completely broken for the calendar template. Fix: add eventsUrl: "/api/sse" to the useFileWatcher call in calendar/client/App.tsx.

Found by running 1 of 4 code-review agents (others aborted due to concurrent PR update).


View in Builder.io

Code review by Builder.io

// SSE uses /api/sse to avoid collision with /api/events (calendar CRUD)
router.get(
"/api/sse",
createSSEHandler(watcher, { extraEmitters, contentRoot: "./data" }),

Choose a reason for hiding this comment

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

🔴 SSE renamed to /api/sse but calendar client still connects to /api/events — real-time updates broken

The server registers SSE at /api/sse to avoid collision with the calendar CRUD /api/events route, but calendar/client/App.tsx calls useFileWatcher with no eventsUrl override. The hook defaults to /api/events, so the EventSource connects to the JSON CRUD endpoint instead of the SSE stream — real-time updates are silently broken. Fix: add eventsUrl: "/api/sse" to the useFileWatcher call in App.tsx.


How did I do? React with 👍 or 👎 to help me improve.

@gopinav
Copy link
Contributor Author

gopinav commented Mar 20, 2026

Resolved comments:

  • Calendar /api/events route collision — Fixed in 44ed1f3. Renamed SSE route to /api/sse to avoid collision with the existing calendar CRUD endpoint.

  • createFileSync ignores contentRoot for sync-config.json — Fixed in 4c83f76. createFileSync now derives syncConfigPath from contentRoot so data/sync-config.json is found for all templates.

gopinav added 5 commits March 20, 2026 23:25
Wire createFileSync() into analytics, brand, calendar, content,
imagegen, mail, slides, and videos templates — matching the pattern
established in the default template (PR #57).

Per template:
- Make createAppServer() async
- Update node-build.ts to .then() pattern
- Add createFileSync() wiring with diagnostic endpoint, SSE
  extraEmitters, graceful shutdown, and conflict notification
- Add/update AGENTS.md with standard File Sync section
- Create sync-config.json where missing (brand, mail, videos)

Templates that had no SSE (analytics, calendar, slides, videos) now
get createFileWatcher + createSSEHandler. Content template correctly
uses contentRoot: "./content". Mail template syncs ./data only (not
./application-state). Slides keeps existing /api/decks/events alongside
new /api/events. Dead Firestore/initFileSync docs replaced in calendar
and slides AGENTS.md.

Sync remains opt-in — apps work identically without FILE_SYNC_ENABLED.
… compatibility

The SSEHandlerOptions type required concrete EventEmitter but
createFileSync returns TypedEventEmitter which wraps EventEmitter
without extending it. Define an EventLike interface with just on/off
signatures (the only methods the SSE handler uses) so both types work.
@gopinav gopinav force-pushed the feat/migrate-templates-file-sync branch from 4c83f76 to 638e3c3 Compare March 20, 2026 22:25
Copy link

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

Choose a reason for hiding this comment

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

Builder has reviewed your changes and found 1 potential issue.

Review Details

Code Review Summary — PR #62 (Update 4)

This update fixes the createFileSync syncConfigPath bug (now correctly derives the path from contentRoot). Good fix. Previously reported issues (SIGTERM handler, missing .catch(), conflict overwrite, calendar client /api/sse mismatch) remain open.

Risk: Standard (🟡)

New Finding

🔴 HIGH:

  • Slides client never connects to /api/events — The slides template adds a new /api/events SSE endpoint with the syncResult.sseEmitter wired through it, but the slides client (DeckContext.tsx) hardcodes new EventSource("/api/decks/events") and never subscribes to /api/events. Sync events and conflict notifications are written to disk but never propagate to the browser. The new createFileWatcher("./data") and syncResult.sseEmitter on /api/events have zero subscribers. Fix: wire syncResult.sseEmitter into the existing /api/decks/events handler, or add a second EventSource("/api/events") subscription in DeckContext.tsx.

Found by 1 of 4 code-review agents (others aborted due to concurrent PR update).


View in Builder.io

Code review by Builder.io

@@ -133,5 +162,36 @@ export function createAppServer() {
}),
);

Choose a reason for hiding this comment

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

🔴 Slides client uses /api/decks/events but sync SSE is on /api/events — sync events never reach the browser

The slides client (DeckContext.tsx) hardcodes new EventSource("/api/decks/events") and never subscribes to the new /api/events endpoint. All sync events and conflict notifications written through syncResult.sseEmitter are silently dropped. Fix: wire syncResult.sseEmitter into the existing /api/decks/events handler, or add a useFileWatcher call with eventsUrl: "/api/events" on the client.


How did I do? React with 👍 or 👎 to help me improve.

@gopinav gopinav merged commit ebfc804 into main Mar 20, 2026
9 checks passed
@gopinav gopinav deleted the feat/migrate-templates-file-sync branch March 20, 2026 22:28
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