@agent-native/core 0.39.1 → 0.39.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (61) hide show
  1. package/dist/cli/index.js +1 -1
  2. package/dist/cli/index.js.map +1 -1
  3. package/dist/cli/skills.d.ts +5 -6
  4. package/dist/cli/skills.d.ts.map +1 -1
  5. package/dist/cli/skills.js +430 -723
  6. package/dist/cli/skills.js.map +1 -1
  7. package/dist/client/MultiTabAssistantChat.d.ts.map +1 -1
  8. package/dist/client/MultiTabAssistantChat.js +2 -5
  9. package/dist/client/MultiTabAssistantChat.js.map +1 -1
  10. package/dist/client/NewWorkspaceAppFlow.js +1 -1
  11. package/dist/client/NewWorkspaceAppFlow.js.map +1 -1
  12. package/dist/client/settings/SettingsPanel.d.ts.map +1 -1
  13. package/dist/client/settings/SettingsPanel.js +11 -19
  14. package/dist/client/settings/SettingsPanel.js.map +1 -1
  15. package/dist/client/use-chat-models.d.ts.map +1 -1
  16. package/dist/client/use-chat-models.js +2 -5
  17. package/dist/client/use-chat-models.js.map +1 -1
  18. package/dist/deploy/build.d.ts.map +1 -1
  19. package/dist/deploy/build.js +2 -1
  20. package/dist/deploy/build.js.map +1 -1
  21. package/dist/deploy/route-discovery.d.ts +29 -0
  22. package/dist/deploy/route-discovery.d.ts.map +1 -1
  23. package/dist/deploy/route-discovery.js +158 -11
  24. package/dist/deploy/route-discovery.js.map +1 -1
  25. package/dist/server/auth.d.ts +2 -0
  26. package/dist/server/auth.d.ts.map +1 -1
  27. package/dist/server/auth.js +9 -0
  28. package/dist/server/auth.js.map +1 -1
  29. package/dist/templates/default/.agents/skills/actions/SKILL.md +96 -11
  30. package/dist/templates/default/.agents/skills/adding-a-feature/SKILL.md +126 -26
  31. package/dist/templates/default/.agents/skills/capture-learnings/SKILL.md +56 -30
  32. package/dist/templates/default/.agents/skills/create-skill/SKILL.md +28 -0
  33. package/dist/templates/default/.agents/skills/delegate-to-agent/SKILL.md +75 -5
  34. package/dist/templates/default/.agents/skills/frontend-design/SKILL.md +17 -0
  35. package/dist/templates/default/.agents/skills/real-time-collab/SKILL.md +99 -124
  36. package/dist/templates/default/.agents/skills/real-time-sync/SKILL.md +43 -10
  37. package/dist/templates/default/.agents/skills/security/SKILL.md +162 -144
  38. package/dist/templates/default/.agents/skills/self-modifying-code/SKILL.md +5 -3
  39. package/dist/templates/default/.agents/skills/shadcn-ui/SKILL.md +15 -0
  40. package/dist/templates/default/.agents/skills/storing-data/SKILL.md +116 -83
  41. package/dist/templates/default/DEVELOPING.md +10 -13
  42. package/dist/templates/workspace-core/.agents/skills/client-methods/references/legacy-client-fetch-audit-2026-06-03.md +9 -0
  43. package/dist/templates/workspace-core/.agents/skills/writing-agent-instructions/SKILL.md +27 -0
  44. package/docs/content/template-plan.md +5 -3
  45. package/docs/content/visual-plans.md +5 -2
  46. package/package.json +1 -1
  47. package/src/templates/default/.agents/skills/actions/SKILL.md +96 -11
  48. package/src/templates/default/.agents/skills/adding-a-feature/SKILL.md +126 -26
  49. package/src/templates/default/.agents/skills/capture-learnings/SKILL.md +56 -30
  50. package/src/templates/default/.agents/skills/create-skill/SKILL.md +28 -0
  51. package/src/templates/default/.agents/skills/delegate-to-agent/SKILL.md +75 -5
  52. package/src/templates/default/.agents/skills/frontend-design/SKILL.md +17 -0
  53. package/src/templates/default/.agents/skills/real-time-collab/SKILL.md +99 -124
  54. package/src/templates/default/.agents/skills/real-time-sync/SKILL.md +43 -10
  55. package/src/templates/default/.agents/skills/security/SKILL.md +162 -144
  56. package/src/templates/default/.agents/skills/self-modifying-code/SKILL.md +5 -3
  57. package/src/templates/default/.agents/skills/shadcn-ui/SKILL.md +15 -0
  58. package/src/templates/default/.agents/skills/storing-data/SKILL.md +116 -83
  59. package/src/templates/default/DEVELOPING.md +10 -13
  60. package/src/templates/workspace-core/.agents/skills/client-methods/references/legacy-client-fetch-audit-2026-06-03.md +9 -0
  61. package/src/templates/workspace-core/.agents/skills/writing-agent-instructions/SKILL.md +27 -0
@@ -2,8 +2,11 @@
2
2
  name: delegate-to-agent
3
3
  description: >-
4
4
  How to delegate all AI work to the agent chat. Use when delegating AI work
5
- from UI or scripts to the agent, when tempted to add inline LLM calls, or
6
- when sending messages to the agent from application code.
5
+ from UI or scripts to the agent, when a user asks for agent behavior or
6
+ LLM-powered features, when tempted to add inline LLM calls, or when sending
7
+ messages to the agent from application code.
8
+ metadata:
9
+ internal: true
7
10
  ---
8
11
 
9
12
  # Delegate All AI to the Agent
@@ -14,7 +17,7 @@ The UI and server never call an LLM directly. All AI work is delegated to the ag
14
17
 
15
18
  ## Why
16
19
 
17
- The agent is the single AI interface. It has context about the full project, can read/write the database, and can run scripts. Inline LLM calls bypass this — they create a shadow AI that doesn't know what the agent knows and can't coordinate with it.
20
+ The agent is the single AI interface. It has context about the full project, can read/write any file, and can run scripts. Inline LLM calls bypass this — they create a shadow AI that doesn't know what the agent knows and can't coordinate with it.
18
21
 
19
22
  ## How
20
23
 
@@ -123,6 +126,55 @@ Buttons that produce new content ("New Design", "Create Dashboard", "Make Deck",
123
126
 
124
127
  If you find yourself writing `submit: true` with a hardcoded creative verb (`"design a..."`, `"write a..."`, `"build a..."`), stop and add a Popover.
125
128
 
129
+ ## Delegating to a Sub-Agent (Agent Teams)
130
+
131
+ `sendToAgentChat()` delegates from app code _to_ the agent. The other axis of
132
+ delegation is the agent handing work _to a sub-agent_ through the Agent Teams
133
+ run-manager. The main chat stays the orchestrator: it spawns sub-agents, then
134
+ reads and integrates their results.
135
+
136
+ ### When to spawn a sub-agent vs do it yourself
137
+
138
+ - **Do it yourself** when the work is small, on the critical path, or tightly
139
+ coupled to what you're already doing. Sub-agent overhead and coordination risk
140
+ outweigh the benefit.
141
+ - **Spawn a sub-agent** for a self-contained unit of work that can run
142
+ independently — a disjoint investigation, an isolated implementation slice, a
143
+ long-running search — especially when it frees the main thread to keep
144
+ orchestrating.
145
+
146
+ ### Briefing contract
147
+
148
+ Every sub-agent brief must specify four things, or the sub-agent will guess:
149
+
150
+ - **Objective** — the one concrete outcome it owns, in a sentence.
151
+ - **Context** — the facts it needs (paths, prior findings, constraints) so it
152
+ doesn't re-derive them.
153
+ - **Output** — the exact shape you want back (a summary, a file edited, a list
154
+ of paths, a yes/no with rationale).
155
+ - **Boundaries** — what it must NOT touch (files, branches, side effects) and
156
+ when to stop and report rather than push forward.
157
+
158
+ ### Fan-out discipline
159
+
160
+ - **Default to a single sub-agent.** Most delegation is one focused task.
161
+ - **Spawn multiple only for genuinely independent units** that don't share state
162
+ or files. Never parallelize coupled work — if B needs A's output, run them in
163
+ sequence.
164
+ - **Cap parallel fan-out at ~3.** More sub-agents means more synthesis cost and
165
+ more chance of conflicting edits to the same area.
166
+
167
+ ### Synthesis discipline
168
+
169
+ - **Read every result** before concluding — don't act on the first one back.
170
+ - **Reconcile conflicts** between sub-agent findings explicitly; decide which is
171
+ right rather than averaging or ignoring.
172
+ - **Integrate into one answer.** The main thread produces the single coherent
173
+ result; it never just forwards raw sub-agent transcripts to the user.
174
+
175
+ Background sub-agents must use the core run-manager / Agent Teams infrastructure
176
+ rather than ad-hoc LLM calls.
177
+
126
178
  ## Don't
127
179
 
128
180
  - Don't `import Anthropic from "@anthropic-ai/sdk"` in client or server code
@@ -136,9 +188,27 @@ If you find yourself writing `submit: true` with a hardcoded creative verb (`"de
136
188
 
137
189
  Scripts may call external APIs (image generation, search, etc.) — but the AI reasoning and orchestration still goes through the agent. A script is a tool the agent uses, not a replacement for the agent.
138
190
 
191
+ ## When to Use A2A Instead
192
+
193
+ `sendToAgentChat()` delegates work to the **local** agent — the one running alongside your app. When the work should go to a **different** agent entirely (e.g., asking an analytics agent for data, or a calendar agent for availability), use the A2A (agent-to-agent) protocol instead.
194
+
195
+ ```ts
196
+ import { callAgent } from "@agent-native/core/a2a";
197
+
198
+ // Call a different agent — not the local agent chat
199
+ const stats = await callAgent(
200
+ "https://analytics.example.com",
201
+ "What were last week's signups?",
202
+ { apiKey: process.env.ANALYTICS_A2A_KEY },
203
+ );
204
+ ```
205
+
206
+ See the **a2a-protocol** skill for the full pattern.
207
+
139
208
  ## Related Skills
140
209
 
141
- - **scripts** — The agent invokes scripts via `pnpm action <name>` to perform complex operations
210
+ - **a2a-protocol** — When the work goes to a different agent, not the local one
211
+ - **actions** — The agent invokes actions via `pnpm action <name>` to perform complex operations
142
212
  - **self-modifying-code** — The agent operates through the chat bridge to make code changes
143
213
  - **storing-data** — The agent writes results to the database after processing requests
144
- - **real-time-sync** — The UI updates automatically when the agent writes to the database
214
+ - **real-time-sync** — The UI updates automatically when the agent writes data
@@ -8,6 +8,8 @@ description: >-
8
8
  creative, polished UI that avoids generic AI aesthetics.
9
9
  license: Complete terms in LICENSE.txt
10
10
  source: https://github.com/anthropics/skills/blob/main/skills/frontend-design/SKILL.md
11
+ metadata:
12
+ internal: true
11
13
  ---
12
14
 
13
15
  # Frontend Design
@@ -28,6 +30,19 @@ Before coding, decide:
28
30
 
29
31
  Then implement working code that is cohesive, accessible, responsive, and polished in small details: typography, spacing, copy, motion, empty states, loading states, focus states, and error states.
30
32
 
33
+ ## Minimalism And Progressive Disclosure
34
+
35
+ Default to Apple/Linear-level restraint: make the primary workflow obvious, then remove everything that does not help that workflow right now. A polished UI often has fewer visible controls, fewer borders, fewer labels, and fewer explanatory surfaces than the first reasonable implementation.
36
+
37
+ - **Start by subtracting**: Before adding a visible control, banner, toolbar row, card, or explanatory block, ask what can be removed, merged, renamed, or moved into an existing affordance.
38
+ - **One primary action**: Each surface should have one dominant next action. Secondary actions belong in menus, popovers, command palettes, disclosure rows, or contextual hover/focus states unless they are used constantly.
39
+ - **Progressively disclose rare work**: Advanced options, diagnostics, metadata, settings, import/export, destructive actions, and inspection tools should stay tucked away until requested. Prefer small icon triggers with tooltips, popovers, drawers, or detail panels over permanent chrome.
40
+ - **Keep chrome quiet**: Avoid new always-visible bars, badges, callouts, helper text, and counters unless they prevent mistakes or are central to repeated use. Status can often be a dot, ring, muted count, or tooltip.
41
+ - **Favor content over containers**: Do not wrap every section in a card. Use whitespace, alignment, typography, dividers, and full-width bands before adding boxes.
42
+ - **Design for repeated use**: Production app UI should feel calm after the hundredth use. If a control shouts, animates, explains itself, or occupies a full row for an occasional action, hide or compress it.
43
+ - **Make absence intentional**: Empty states should be sparse and action-oriented. Do not fill blank space with marketing copy, decorative art, or lists of features just because the screen feels empty.
44
+ - **Use familiar primitives**: Icon buttons need clear tooltips. Menus, popovers, tabs, switches, and segmented controls should carry complexity instead of exposing every option at once.
45
+
31
46
  ## Aesthetic Guidelines
32
47
 
33
48
  - **Typography**: Use the product's existing type system first. For net-new public pages, choose characterful but readable type and keep sizing appropriate to the surface.
@@ -72,6 +87,8 @@ Avoid:
72
87
  - Custom reimplementations of shadcn primitives.
73
88
  - Raw color overrides on shared components when semantic tokens or variants would work.
74
89
  - New always-visible controls for rare actions. Prefer menus, popovers, sheets, tabs, collapsibles, or advanced sections.
90
+ - Full-width banners, persistent helper rows, decorative cards, or explanatory chrome for status that could be a compact affordance.
91
+ - Treating progressive disclosure as optional. If a control is not part of the main daily workflow, hide it until context, hover, focus, or explicit user intent makes it relevant.
75
92
  - UI cards nested inside other cards.
76
93
  - Text or icons that resize or shift fixed-format UI on hover/loading.
77
94
 
@@ -1,183 +1,158 @@
1
1
  ---
2
2
  name: real-time-collab
3
3
  description: >-
4
- How to enable multi-user collaborative editing with Yjs CRDT, TipTap
5
- Collaboration extension, live cursors, and agent-driven edits.
4
+ Multi-user collaborative editing with Yjs CRDT and live cursors. Use when
5
+ adding real-time collaborative editing to a template, debugging sync issues,
6
+ or understanding how the agent and humans edit documents simultaneously.
7
+ metadata:
8
+ internal: true
6
9
  ---
7
10
 
8
11
  # Real-Time Collaboration
9
12
 
10
- The framework provides a Yjs-based collaborative editing system in `@agent-native/core/collab`. Multiple users can edit the same document simultaneously with live cursor positions, and the AI agent can make surgical edits that appear in real-time.
13
+ ## Rule
11
14
 
12
- ## Architecture
15
+ Collaborative editing uses Yjs CRDT via TipTap. The agent and human users are equal participants — both edit the same Y.Doc and changes merge cleanly without conflicts.
13
16
 
14
- ```
15
- User A (TipTap + Collaboration ext) ←→ Y.XmlFragment ←→ Server (_collab_docs table)
16
- User B (TipTap + Collaboration ext) ←→ Y.XmlFragment ←→ ↑
17
- Agent (edit-document action) ←→ search-replace endpoint ─┘
18
- ```
17
+ ## How It Works
19
18
 
20
- - **Yjs Y.Doc** stores the document as a `Y.XmlFragment` (ProseMirror node tree)
19
+ - **`Y.Doc`** stores the document as a `Y.XmlFragment` (ProseMirror node tree)
21
20
  - **TipTap's Collaboration extension** binds the editor to the Y.XmlFragment via `ySyncPlugin`
22
21
  - **CollaborationCaret extension** renders remote users' cursors with names and colors
23
22
  - **Polling** (every 2s) syncs Y.Doc updates and awareness state between clients and server
24
- - **SQL `_collab_docs` table** persists Yjs state (base64-encoded binary)
23
+ - **SQL `_collab_docs` table** persists Yjs state as base64-encoded binary (works across SQLite/Postgres)
25
24
 
26
- ## Enabling Collaboration in a Template
25
+ ## Agent + Human Editing
27
26
 
28
- ### 1. Install dependencies
27
+ 1. **Human edits** → TipTap → ySyncPlugin → Y.XmlFragment → `POST /_agent-native/collab/:docId/update`
28
+ 2. **Agent edits** → action edits canonical SQL content + bumps `updatedAt` → change-sync refetch → the open editor reconciles the new content into the live Y.Doc (see below) → poll update → all clients
29
29
 
30
- ```bash
31
- pnpm add @tiptap/extension-collaboration @tiptap/extension-collaboration-caret @tiptap/y-tiptap --filter your-template
30
+ Both produce Yjs operations that merge cleanly. Agent edits appear without destroying cursor position, selection, or undo history.
31
+
32
+ This is how content (documents) and slides now work. The agent does **not** push edits into Yjs in-process, and it does **not** call any `findCollabOrigin()` / localhost probe — that approach silently no-op'd on serverless (the action runs in a different process), so agent edits didn't show up live until the user navigated away and back. Nor does it search-and-replace inside existing Y.XmlText nodes, which could never create new block structure (lists, headings, tables). The peer-editor model below replaces both.
33
+
34
+ ## Agent Edits As A Real-Time Peer Editor
35
+
36
+ The agent edits documents the same way a human collaborator does: its change lands in the shared Y.Doc, propagates to every connected client, and persists. It gets there without any in-process Yjs push from the action.
37
+
38
+ **SQL is the durable source of truth for document body content.** The agent action edits the canonical content (e.g. `documents.content`) and bumps `updatedAt`. That's the whole server side — no localhost calls, no Yjs mutation from the action.
39
+
40
+ **The open editor reconciles authoritative external content into the live Y.Doc.** The action's `updatedAt` bump flows through the change-sync system (see `real-time-sync`), which refetches the record. The editor applies the new content through its real markdown/HTML pipeline via `setContent`, so new block structure (lists, headings, tables) renders correctly and merges with concurrent human edits through the Yjs CRDT diff. The result: the agent's edit propagates to every connected client and persists, exactly like a human collaborator's edit.
41
+
42
+ ### The `updatedAt` gate
43
+
44
+ The editor only adopts content that is genuinely **newer** than what it already reflects. An older-or-equal `updatedAt` is a lagging poll or a stale snapshot and is **ignored**.
45
+
46
+ ```ts
47
+ // Pseudocode in the editor's reconcile effect
48
+ if (loaded.updatedAt > lastAppliedUpdatedAt.current) {
49
+ applyAuthoritativeContent(loaded.content); // adopt
50
+ lastAppliedUpdatedAt.current = loaded.updatedAt;
51
+ }
52
+ // else: lagging poll / stale snapshot → ignore
32
53
  ```
33
54
 
34
- ### 2. Add Vite optimizeDeps
55
+ **Why:** without the gate, a slightly-behind poll response re-applies old content right after the agent's edit, so the edit "reverts on the next poll" / "doesn't show until refresh" — the whack-a-mole we kept hitting. A **fresh mount or doc-switch has no baseline**, so it always adopts whatever content it loaded — which is why a manual refresh is always correct.
56
+
57
+ ### Lead-client election
58
+
59
+ Exactly ONE connected client applies an authoritative snapshot into the shared Y.Doc; the rest receive it through normal Yjs sync. The lead is the present client with the lowest Yjs `clientID`, decided by the core helper:
35
60
 
36
- In `vite.config.ts`:
37
61
  ```ts
38
- export default defineConfig({
39
- plugins: [reactRouter()],
40
- optimizeDeps: {
41
- include: [
42
- "yjs",
43
- "y-protocols/awareness",
44
- "@tiptap/extension-collaboration",
45
- "@tiptap/extension-collaboration-caret",
46
- "@tiptap/y-tiptap",
47
- ],
48
- },
49
- });
62
+ import { isReconcileLeadClient } from "@agent-native/core/client";
63
+
64
+ if (
65
+ loaded.updatedAt > lastAppliedUpdatedAt.current &&
66
+ isReconcileLeadClient(provider.awareness, ydoc.clientID)
67
+ ) {
68
+ applyAuthoritativeContent(loaded.content);
69
+ }
50
70
  ```
51
71
 
52
- This prevents Vite from re-bundling TipTap in incompatible ways during dev.
72
+ **Why:** if every open editor independently diffed the same snapshot into the CRDT, each would insert the changed region at the same position, duplicating it N times (concurrent inserts → duplicated text). Electing one lead avoids that. The agent's awareness id (`AGENT_CLIENT_ID`, max int) can never win, and a client editing alone is always the lead. The election is deterministic across clients with no coordination round-trip.
53
73
 
54
- ### 3. Add the collab server plugin
74
+ ### v1 limitation
75
+
76
+ A full-content reconcile is **last-writer-wins for the rare case** where a human has unsaved edits in the exact region the agent simultaneously rewrites — the agent's snapshot can clobber that in-flight human edit. Inline and structural edits in **different** regions merge fine through the CRDT; only same-region simultaneous rewrites are at risk.
77
+
78
+ ## Enabling Collaboration
79
+
80
+ ### 1. Install packages
81
+
82
+ ```bash
83
+ pnpm add @tiptap/extension-collaboration @tiptap/extension-collaboration-caret @tiptap/y-tiptap
84
+ ```
85
+
86
+ ### 2. Add collab server plugin
55
87
 
56
- Create `server/plugins/collab.ts`:
57
88
  ```ts
58
- import { createCollabPlugin } from "@agent-native/core/server";
89
+ // server/plugins/collab.ts
90
+ import { createCollabPlugin } from "@agent-native/core/collab";
91
+
59
92
  export default createCollabPlugin({
60
- table: "your_table",
93
+ table: "documents",
61
94
  contentColumn: "content",
62
95
  idColumn: "id",
63
- autoSeed: false, // Client-side seeding on first load
64
96
  });
65
97
  ```
66
98
 
67
- This mounts routes under `/_agent-native/collab/`:
68
- - `GET /:docId/state` — fetch Y.Doc state
69
- - `POST /:docId/update` — apply client update
70
- - `POST /:docId/text` — apply full text (diff-based)
71
- - `POST /:docId/search-replace` — surgical text find/replace in Y.XmlFragment
72
- - `POST /:docId/awareness` — sync cursor/presence state
73
-
74
- ### 4. Use the `useCollaborativeDoc` hook
99
+ ### 3. Use the client hook
75
100
 
76
101
  ```ts
77
- import { useCollaborativeDoc, generateTabId } from "@agent-native/core/client";
102
+ import { useCollaborativeDoc } from "@agent-native/core/client";
78
103
 
79
- const TAB_ID = generateTabId();
80
-
81
- const { ydoc, awareness, isLoading, activeUsers } = useCollaborativeDoc({
82
- docId: documentId,
83
- requestSource: TAB_ID,
84
- user: { name: "Steve", email: "steve@example.com", color: "#60a5fa" },
85
- });
104
+ const { ydoc, provider } = useCollaborativeDoc(documentId);
86
105
  ```
87
106
 
88
- The hook:
89
- - Creates a stable `Y.Doc` per docId (never changes identity)
90
- - Fetches server state and applies it
91
- - Sends local updates to server
92
- - Polls for remote updates (every 2s)
93
- - Tracks active users via awareness
94
-
95
- ### 5. Add Collaboration extension to TipTap
107
+ ### 4. Add TipTap extensions
96
108
 
97
109
  ```ts
98
- import Collaboration from "@tiptap/extension-collaboration";
99
- import CollaborationCaret from "@tiptap/extension-collaboration-caret";
100
- import { Awareness } from "y-protocols/awareness";
101
-
102
- // Create awareness locally (must use same y-protocols as the caret extension)
103
- const awareness = new Awareness(ydoc);
104
- awareness.setLocalStateField("user", { name, color });
110
+ import { Collaboration } from "@tiptap/extension-collaboration";
111
+ import { CollaborationCaret } from "@tiptap/extension-collaboration-caret";
105
112
 
106
113
  const editor = useEditor({
107
114
  extensions: [
108
- StarterKit.configure({ history: false }), // Disable history — Yjs handles undo
109
115
  Collaboration.configure({ document: ydoc }),
110
116
  CollaborationCaret.configure({
111
- provider: { awareness },
112
- user: { name, color },
117
+ provider,
118
+ user: { name: session.email, color: "#6366f1" },
113
119
  }),
114
- // ... other extensions
115
120
  ],
116
- content: initialContent, // Seeds Y.XmlFragment on first load
117
121
  });
118
122
  ```
119
123
 
120
- **Important:** Disable `history` in StarterKit when using Collaboration — Yjs handles undo/redo.
121
-
122
- ### 6. Seed the Y.XmlFragment
123
-
124
- The Collaboration extension does NOT auto-seed from the `content` prop. You must seed manually:
124
+ ### 5. Add to vite.config.ts optimizeDeps
125
125
 
126
126
  ```ts
127
- useEffect(() => {
128
- if (!editor || !ydoc || !content) return;
129
- const fragment = ydoc.getXmlFragment("default");
130
- if (fragment.length === 0) {
131
- editor.commands.setContent(parseContent(content));
132
- }
133
- }, [editor, ydoc, content]);
134
- ```
135
-
136
- **Critical:** Guard against saving empty content back to SQL when the editor is in collab mode but hasn't been seeded yet:
137
-
138
- ```ts
139
- onUpdate: ({ editor }) => {
140
- const md = editor.storage.markdown.getMarkdown();
141
- if (!md.trim() && ydoc) return; // Don't save empty during seeding
142
- onChange(md);
127
+ optimizeDeps: {
128
+ include: [
129
+ "@tiptap/extension-collaboration",
130
+ "@tiptap/extension-collaboration-caret",
131
+ "@tiptap/y-tiptap",
132
+ ],
143
133
  }
144
134
  ```
145
135
 
146
- ## Agent Edits via `edit-document`
147
-
148
- The `edit-document` action uses search-and-replace:
149
- ```bash
150
- pnpm action edit-document --id <docId> --find "old text" --replace "new text"
151
- ```
152
-
153
- When collab state exists, the action calls the server's `search-replace` endpoint which:
154
- 1. Walks the Y.XmlFragment tree
155
- 2. Finds the text in Y.XmlText nodes
156
- 3. Applies minimal delete/insert operations
157
- 4. Emits a Yjs update via the poll system
158
- 5. Client receives the update → ySyncPlugin applies a targeted ProseMirror transaction → cursor preserved
136
+ ## Collab Routes (auto-mounted)
159
137
 
160
- **Important:** Actions run in a separate process, so they must use the HTTP endpoint (not the collab module directly) to emit updates to the server's poll system.
161
-
162
- ## Key Modules
163
-
164
- | Module | Path | Purpose |
165
- |--------|------|---------|
166
- | `@agent-native/core/collab` | `packages/core/src/collab/` | Server-side Yjs management |
167
- | `useCollaborativeDoc` | `packages/core/src/collab/client.ts` | Client hook |
168
- | `createCollabPlugin` | `packages/core/src/server/collab-plugin.ts` | Route mounting |
169
- | `searchAndReplace` | `packages/core/src/collab/ydoc-manager.ts` | Y.XmlFragment text mutation |
138
+ | Route | Purpose |
139
+ | ----- | ------- |
140
+ | `GET /_agent-native/collab/:docId/state` | Fetch full Y.Doc state |
141
+ | `POST /_agent-native/collab/:docId/update` | Apply client Yjs update |
142
+ | `POST /_agent-native/collab/:docId/text` | Apply full text (diff-based) |
143
+ | `POST /_agent-native/collab/:docId/search-replace` | Surgical find/replace in Y.XmlFragment |
144
+ | `POST /_agent-native/collab/:docId/awareness` | Sync cursor/presence state |
145
+ | `GET /_agent-native/collab/:docId/users` | List active users |
170
146
 
171
147
  ## Common Pitfalls
172
148
 
173
- 1. **TipTap version mismatch** All `@tiptap/*` packages must be the same version. The Collaboration extension requires `editor.utils` which was added in v3.22.2.
174
-
175
- 2. **Empty editor on first load** The Collaboration extension uses Y.XmlFragment as the source of truth. If the fragment is empty, the editor shows empty. Seed manually (see above).
176
-
177
- 3. **Data loss from empty saves** — The `onUpdate` handler fires when the editor initializes with an empty Y.XmlFragment. If this empty content is saved to SQL, it overwrites the real content. Always guard against saving empty content in collab mode.
178
-
179
- 4. **Stale content on document switch** — Use `key={documentId}` on the editor component to force a full remount when switching documents. This ensures the Y.Doc, seeding, and editor state are all fresh.
149
+ - **Don't pass `content` as a TipTap prop** when Collaboration is enabled Yjs owns the content. Set initial content via the Y.Doc instead.
150
+ - **Don't call `editor.setContent()` ad hoc for agent edits.** The only sanctioned `setContent` is the editor's reconcile path described above — gated by `updatedAt` and guarded by `isReconcileLeadClient`. Calling it from elsewhere (e.g. on every poll, or from every client) re-applies stale content or duplicates the changed region across the CRDT.
151
+ - **Add packages to `optimizeDeps`**Vite won't pre-bundle Yjs packages correctly otherwise, causing runtime errors in dev.
152
+ - **One `Y.Doc` per document** — Don't create multiple Y.Doc instances for the same document ID. Use the `useCollaborativeDoc` hook which caches by ID.
180
153
 
181
- 5. **Separate process for actions** — Actions run via `pnpm action` in a new Node.js process. The in-memory EventEmitter in the action process doesn't reach the dev server's poll system. Use HTTP endpoints for collab operations from actions.
154
+ ## Related Skills
182
155
 
183
- 6. **Vite dep optimization** Adding Yjs-related packages to a template changes Vite's dependency bundling, which can break TipTap's React integration. Always add them to `optimizeDeps.include`.
156
+ - `real-time-sync`The change-sync system that delivers the `updatedAt` bump driving editor reconciliation; also `useReconciledState` for non-collaborative "copy a server value into local edit state" surfaces
157
+ - `storing-data` — The `_collab_docs` table where Yjs state is persisted; SQL holds the canonical document body that the editor reconciles from
158
+ - `self-modifying-code` — Agent edits to collaborative documents edit canonical SQL content, not raw Yjs
@@ -4,6 +4,8 @@ description: >-
4
4
  How to keep the UI in sync with agent changes via SSE plus polling fallback.
5
5
  Use when wiring query invalidation for new data models, debugging UI not
6
6
  updating, or understanding jitter prevention.
7
+ metadata:
8
+ internal: true
7
9
  ---
8
10
 
9
11
  # Real-Time Sync
@@ -20,7 +22,7 @@ The agent modifies data in SQL, but the UI runs in the browser. SSE bridges same
20
22
 
21
23
  1. **Server** increments a version counter on every database write. In-process events stream through the authenticated `/_agent-native/events` endpoint.
22
24
 
23
- 2. **Client** listens for SSE/poll events and updates per-source change counters:
25
+ 2. **Client** listens for sync events and updates per-source change counters:
24
26
 
25
27
  ```ts
26
28
  import { useDbSync } from "@agent-native/core";
@@ -47,9 +49,9 @@ The agent modifies data in SQL, but the UI runs in the browser. SSE bridges same
47
49
 
48
50
  For list/sidebar queries, use the same pattern — pass the counter into the queryKey of every list query you want to keep fresh.
49
51
 
50
- 3. **Fallback** polling calls `/_agent-native/poll?since=N`. It runs every 2 seconds until SSE is connected, then relaxes to 15 seconds. If SSE is disabled or unavailable, polling continues at the normal cadence.
52
+ 4. **Fallback** polling calls `/_agent-native/poll?since=N`. It runs every 2 seconds until SSE is connected, then relaxes to 15 seconds. If SSE is disabled or unavailable, polling continues at the normal cadence.
51
53
 
52
- 4. When the agent writes to the database, the version increments, SSE/polling detects it, and React Query refetches the affected queries.
54
+ 5. When the agent writes to the database, the version increments, SSE/polling detects it, and React Query refetches the affected queries.
53
55
 
54
56
  ## Don't
55
57
 
@@ -132,9 +134,9 @@ The `use-navigation-state.ts` hook sends the same `TAB_ID` in the `X-Request-Sou
132
134
 
133
135
  Without jitter prevention, a cycle occurs: the UI writes state, sync detects the change, the UI refetches and re-renders, potentially overwriting what the user is actively editing. With `ignoreSource`, the UI only reacts to changes from other sources (agent scripts, other browser tabs, other users).
134
136
 
135
- ## Action Routes and Polling
137
+ ## Action Routes and Live Sync
136
138
 
137
- Action routes (`/_agent-native/actions/:name`) work with the same sync system. When a POST/PUT/DELETE action writes to the database, the version counter increments and `useDbSync` picks up the change. Frontend mutations via `useActionMutation` automatically invalidate `["action"]` query keys on success, triggering refetches of `useActionQuery` hooks.
139
+ Actions work with the same sync system. When a mutating action writes to the database, the version counter increments and `useDbSync` picks up the change. Frontend mutations via `useActionMutation` automatically invalidate `["action"]` query keys on success, triggering refetches of `useActionQuery` hooks. Client components should call actions through those hooks, not with raw action-route fetches.
138
140
 
139
141
  For custom apps, the best out-of-the-box path is:
140
142
 
@@ -146,14 +148,13 @@ This avoids duplicate `/api/*` JSON CRUD routes and makes agent-created records
146
148
 
147
149
  ### Auto-emit on mutating actions
148
150
 
149
- The framework emits a poll event with `source: "action"` whenever any non-read-only action runs to completion — whether called via HTTP (`/_agent-native/actions/:name`) or as an agent tool call. Read-only actions (`http: { method: "GET" }` or explicit `readOnly: true`) are skipped.
151
+ The framework emits a change event with `source: "action"` whenever any non-read-only action runs to completion — whether called via HTTP (`/_agent-native/actions/:name`) or as an agent tool call. Read-only actions (`http: { method: "GET" }` or explicit `readOnly: true`) are skipped.
150
152
 
151
153
  This means UIs don't need the agent to remember to call `refresh-screen` after every mutation. A listener like this (used in the `macros` template) will refresh after any mutating agent call:
152
154
 
153
155
  ```ts
154
156
  useDbSync({
155
157
  queryClient,
156
- queryKeys: [],
157
158
  ignoreSource: TAB_ID,
158
159
  onEvent: (data) => {
159
160
  if (data.requestSource === TAB_ID) return;
@@ -165,9 +166,41 @@ useDbSync({
165
166
 
166
167
  `refresh-screen` remains available for unusual cases — e.g. the agent mutated data via a path the framework can't see (external system the app mirrors), or the agent wants to pass a `scope` hint for narrower invalidation.
167
168
 
169
+ ## Keeping Stateful Components In Sync
170
+
171
+ The `useChangeVersion` / `useActionQuery` pattern above keeps the **query layer** fresh. But components that copy a server value into local React state still go stale on agent edits — refetching the query updates the prop, yet the local copy never re-adopts it. This is a recurring bug.
172
+
173
+ **Never do this** for a value the agent can mutate:
174
+
175
+ ```ts
176
+ // BUG: `title` is captured once and never re-reads the prop.
177
+ const [title, setTitle] = useState(props.title);
178
+ ```
179
+
180
+ When the agent renames the record, the query refetches, `props.title` updates, but the input still shows the stale value until the component remounts.
181
+
182
+ **Derived-state surfaces (form fields, inline editors, popovers): use `useReconciledState`.** It re-adopts the authoritative external value when it changes, except while the user is actively editing that field — so agent mutations show up live without clobbering in-progress typing:
183
+
184
+ ```ts
185
+ import { useReconciledState } from "@agent-native/core/client";
186
+
187
+ // `active` = true while the user is editing this field (focused / dirty).
188
+ const [title, setTitle] = useReconciledState(props.title, { active: isEditing });
189
+ ```
190
+
191
+ **Collaborative rich-text editors are different** — they don't copy a value into `useState`. They reconcile authoritative SQL content into a shared Y.Doc under an `updatedAt` gate with lead-client election. See `real-time-collab` → "Agent edits as a real-time peer editor". Don't reach for `useReconciledState` for a Yjs-backed editor.
192
+
193
+ | Surface | Keep it fresh with |
194
+ | ------- | ------------------ |
195
+ | React Query reads | `useChangeVersion` / `useActionQuery` (above) |
196
+ | Local edit state copied from a server value (inputs, popovers, inline editors) | `useReconciledState(externalValue, { active })` |
197
+ | Collaborative rich-text editor (Yjs) | `updatedAt`-gated reconcile + `isReconcileLeadClient` — see `real-time-collab` |
198
+
168
199
  ## Related Skills
169
200
 
170
- - **storing-data** — Application-state and settings are the data stores that sync via polling
201
+ - **storing-data** — Application-state and settings are data stores that sync through change events
171
202
  - **context-awareness** — Navigation state writes use jitter prevention to avoid overwriting active edits
172
- - **actions** — Action routes auto-expose actions as HTTP endpoints; database writes trigger poll events
173
- - **self-modifying-code** — Agent code edits trigger poll events; rapid edits can cause event storms
203
+ - **actions** — Mutating actions trigger change events
204
+ - **client-methods** — Route details belong in helpers/hooks, not components
205
+ - **self-modifying-code** — Agent code edits trigger change events; rapid edits can cause event storms
206
+ - **real-time-collab** — Collaborative editors reconcile agent edits into a shared Y.Doc, driven by the same change-sync `updatedAt` bump