@agent-native/core 0.182.0 → 0.182.1-nightly-20260916202735

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 (68) hide show
  1. package/corpus/README.md +1 -1
  2. package/corpus/templates/analytics/.agents/skills/shadcn-ui/SKILL.md +65 -6
  3. package/corpus/templates/analytics/.mcp.json +8 -0
  4. package/corpus/templates/analytics/changelog/2026-09-16-dashboards-open-without-background-synchronization-scans-dur.md +6 -0
  5. package/corpus/templates/analytics/server/plugins/collab.ts +2 -0
  6. package/corpus/templates/assets/.agents/skills/shadcn-ui/SKILL.md +65 -6
  7. package/corpus/templates/assets/.mcp.json +8 -0
  8. package/corpus/templates/brain/.agents/skills/shadcn-ui/SKILL.md +65 -6
  9. package/corpus/templates/brain/.mcp.json +8 -0
  10. package/corpus/templates/calendar/.agents/skills/shadcn-ui/SKILL.md +65 -6
  11. package/corpus/templates/calendar/.mcp.json +8 -0
  12. package/corpus/templates/chat/.agents/skills/shadcn-ui/SKILL.md +65 -6
  13. package/corpus/templates/chat/.mcp.json +8 -0
  14. package/corpus/templates/clips/.agents/skills/shadcn-ui/SKILL.md +65 -6
  15. package/corpus/templates/clips/.mcp.json +8 -0
  16. package/corpus/templates/clips/desktop/.mcp.json +8 -0
  17. package/corpus/templates/content/.agents/skills/shadcn-ui/SKILL.md +65 -6
  18. package/corpus/templates/content/.mcp.json +8 -0
  19. package/corpus/templates/crm/.agents/skills/shadcn-ui/SKILL.md +65 -6
  20. package/corpus/templates/crm/.mcp.json +8 -0
  21. package/corpus/templates/design/.agents/skills/shadcn-ui/SKILL.md +65 -6
  22. package/corpus/templates/design/.mcp.json +8 -0
  23. package/corpus/templates/design/changelog/2026-09-16-design-files-avoid-cold-start-scans-and-open-faster-after-id.md +6 -0
  24. package/corpus/templates/dispatch/.agents/skills/shadcn-ui/SKILL.md +65 -6
  25. package/corpus/templates/dispatch/.mcp.json +8 -0
  26. package/corpus/templates/factory/.agents/skills/shadcn-ui/SKILL.md +65 -6
  27. package/corpus/templates/factory/.mcp.json +8 -0
  28. package/corpus/templates/forms/.agents/skills/shadcn-ui/SKILL.md +65 -6
  29. package/corpus/templates/forms/.mcp.json +8 -0
  30. package/corpus/templates/mail/.agents/skills/shadcn-ui/SKILL.md +65 -6
  31. package/corpus/templates/mail/.mcp.json +8 -0
  32. package/corpus/templates/plan/.agents/skills/shadcn-ui/SKILL.md +65 -6
  33. package/corpus/templates/plan/.mcp.json +8 -0
  34. package/corpus/templates/slides/.agents/skills/shadcn-ui/SKILL.md +65 -6
  35. package/corpus/templates/slides/.mcp.json +8 -0
  36. package/corpus/templates/slides/server/plugins/collab.ts +3 -0
  37. package/corpus/templates/tasks/.agents/skills/shadcn-ui/SKILL.md +65 -6
  38. package/corpus/templates/tasks/.mcp.json +8 -0
  39. package/dist/automation/index.d.ts +1 -1
  40. package/dist/cli/create.js +10 -5
  41. package/dist/collab/storage.d.ts +1 -3
  42. package/dist/collab/storage.js +2 -9
  43. package/dist/collab/ydoc-manager.d.ts +2 -2
  44. package/dist/collab/ydoc-manager.js +45 -11
  45. package/dist/deploy/build.js +1 -1
  46. package/dist/file-upload/actions/upload-image.d.ts +1 -1
  47. package/dist/notifications/routes.d.ts +1 -1
  48. package/dist/observability/routes.d.ts +8 -8
  49. package/dist/org/handlers.d.ts +1 -1
  50. package/dist/provider-api/actions/provider-api.d.ts +4 -4
  51. package/dist/resources/handlers.d.ts +1 -1
  52. package/dist/server/collab-plugin.d.ts +18 -5
  53. package/dist/server/collab-plugin.js +173 -63
  54. package/dist/templates/chat/.agents/skills/shadcn-ui/SKILL.md +65 -6
  55. package/dist/templates/chat/.mcp.json +8 -0
  56. package/dist/templates/default/.agents/skills/shadcn-ui/SKILL.md +65 -6
  57. package/dist/templates/default/.mcp.json +8 -0
  58. package/dist/templates/workspace-core/.agents/skills/shadcn-ui/SKILL.md +65 -6
  59. package/dist/templates/workspace-root/package.json +1 -1
  60. package/dist/templates/workspace-root/pnpm-workspace.yaml +18 -0
  61. package/package.json +4 -4
  62. package/src/templates/chat/.agents/skills/shadcn-ui/SKILL.md +65 -6
  63. package/src/templates/chat/.mcp.json +8 -0
  64. package/src/templates/default/.agents/skills/shadcn-ui/SKILL.md +65 -6
  65. package/src/templates/default/.mcp.json +8 -0
  66. package/src/templates/workspace-core/.agents/skills/shadcn-ui/SKILL.md +65 -6
  67. package/src/templates/workspace-root/package.json +1 -1
  68. package/src/templates/workspace-root/pnpm-workspace.yaml +18 -0
@@ -1967,7 +1967,7 @@ function patchReactRouterServerManifestSource(source, serverBuildFile, clientMan
1967
1967
  const replacement = JSON.stringify(mergeReactRouterServerManifest(serverManifest, clientManifest));
1968
1968
  return (source.slice(0, valueStart) + replacement + source.slice(assignmentEnd));
1969
1969
  }
1970
- const assignments = /\b[A-Za-z_$][\w$]*\s*=\s*(\{\s*(?:entry|["']entry["'])\s*:)/g;
1970
+ const assignments = /(?:\b[A-Za-z_][\w$]*|\$[\w$]*)\s*=\s*(\{\s*(?:entry|["']entry["'])\s*:)/g;
1971
1971
  for (const match of source.matchAll(assignments)) {
1972
1972
  const valueStart = match.index + match[0].lastIndexOf("{");
1973
1973
  const valueEnd = findJavaScriptObjectEnd(source, valueStart);
@@ -17,11 +17,11 @@ declare const _default: import("../../action.js").ActionDefinition<{
17
17
  id?: undefined;
18
18
  provider?: undefined;
19
19
  } | {
20
- error?: undefined;
21
20
  configured?: undefined;
22
21
  connectPath?: undefined;
23
22
  url: string;
24
23
  id: string;
25
24
  provider: string;
25
+ error?: undefined;
26
26
  }>;
27
27
  export default _default;
@@ -28,6 +28,6 @@ export declare function createNotificationsHandler(): import("h3").EventHandlerW
28
28
  } | {
29
29
  count?: undefined;
30
30
  updated?: undefined;
31
- ok: boolean;
32
31
  error?: undefined;
32
+ ok: boolean;
33
33
  }>>;
@@ -41,27 +41,27 @@ export declare function createObservabilityHandler(): import("h3").EventHandlerW
41
41
  thumbsUpRate: number;
42
42
  avgEvalScore: number;
43
43
  } | {
44
- id?: undefined;
44
+ error?: undefined;
45
+ ok?: undefined;
45
46
  summary: import("./types.js").TraceSummary;
46
47
  spans: import("./types.js").TraceSpan[];
48
+ id?: undefined;
49
+ } | {
47
50
  error?: undefined;
48
51
  ok?: undefined;
49
- } | {
50
52
  summary?: undefined;
51
53
  spans?: undefined;
52
54
  id: string;
53
- error?: undefined;
54
- ok?: undefined;
55
55
  } | {
56
- id?: undefined;
56
+ ok?: undefined;
57
57
  summary?: undefined;
58
58
  spans?: undefined;
59
+ id?: undefined;
59
60
  error: any;
60
- ok?: undefined;
61
61
  } | {
62
- id?: undefined;
62
+ error?: undefined;
63
63
  summary?: undefined;
64
64
  spans?: undefined;
65
+ id?: undefined;
65
66
  ok: boolean;
66
- error?: undefined;
67
67
  }>>;
@@ -52,9 +52,9 @@ export declare const setWorkspaceAppDefaultVisibilityHandler: import("h3").Event
52
52
  }>>;
53
53
  /** POST /_agent-native/org — create a new organization */
54
54
  export declare const createOrgHandler: import("h3").EventHandlerWithFetch<import("h3").EventHandlerRequest, Promise<{
55
+ name?: undefined;
55
56
  success: boolean;
56
57
  id?: undefined;
57
- name?: undefined;
58
58
  role?: undefined;
59
59
  } | {
60
60
  success?: undefined;
@@ -311,7 +311,6 @@ export declare function createProviderApiActions(runtime: Pick<ProviderApiRuntim
311
311
  notes?: string | undefined;
312
312
  scope?: "org" | "user" | undefined;
313
313
  }, {
314
- message?: undefined;
315
314
  id?: undefined;
316
315
  provider?: undefined;
317
316
  deleted?: undefined;
@@ -328,8 +327,8 @@ export declare function createProviderApiActions(runtime: Pick<ProviderApiRuntim
328
327
  count: number;
329
328
  registered?: undefined;
330
329
  label?: undefined;
331
- } | {
332
330
  message?: undefined;
331
+ } | {
333
332
  id?: undefined;
334
333
  deleted?: undefined;
335
334
  providers?: undefined;
@@ -338,8 +337,8 @@ export declare function createProviderApiActions(runtime: Pick<ProviderApiRuntim
338
337
  provider: import("../custom-registry.js").CustomProviderConfig;
339
338
  registered?: undefined;
340
339
  label?: undefined;
341
- } | {
342
340
  message?: undefined;
341
+ } | {
343
342
  provider?: undefined;
344
343
  deleted?: undefined;
345
344
  providers?: undefined;
@@ -348,8 +347,8 @@ export declare function createProviderApiActions(runtime: Pick<ProviderApiRuntim
348
347
  id: string;
349
348
  registered?: undefined;
350
349
  label?: undefined;
351
- } | {
352
350
  message?: undefined;
351
+ } | {
353
352
  provider?: undefined;
354
353
  found?: undefined;
355
354
  providers?: undefined;
@@ -358,6 +357,7 @@ export declare function createProviderApiActions(runtime: Pick<ProviderApiRuntim
358
357
  id: string;
359
358
  registered?: undefined;
360
359
  label?: undefined;
360
+ message?: undefined;
361
361
  } | {
362
362
  provider?: undefined;
363
363
  deleted?: undefined;
@@ -48,8 +48,8 @@ export declare function handleUpdateResource(event: any): Promise<import("./stor
48
48
  }>;
49
49
  /** DELETE /_agent-native/resources/:id — delete a resource */
50
50
  export declare function handleDeleteResource(event: any): Promise<{
51
- error: string;
52
51
  ok?: undefined;
52
+ error: string;
53
53
  } | {
54
54
  error?: undefined;
55
55
  ok: boolean;
@@ -12,6 +12,7 @@
12
12
  * });
13
13
  * ```
14
14
  */
15
+ import { type DbExec } from "../db/client.js";
15
16
  type NitroPluginDef = (nitroApp: any) => void | Promise<void>;
16
17
  export type CollabResourceIdResolver = (docId: string) => string | null | Promise<string | null>;
17
18
  export type CollabAccess = {
@@ -39,10 +40,16 @@ export interface CollabPluginOptions {
39
40
  contentColumn?: string;
40
41
  /** Column name for the document ID. Default: "id" */
41
42
  idColumn?: string;
42
- /** Whether to auto-seed existing documents on startup. Default: true */
43
+ /** Whether to lazily seed a source row on its first collab request. Default: true */
43
44
  autoSeed?: boolean;
44
45
  /** Map a source-table id to the id used by the collab document store. */
45
46
  resolveCollabDocumentId?: (sourceId: string) => string;
47
+ /**
48
+ * Map a collab document id back to the source-table id for lazy seeding.
49
+ * Without this, the legacy forward resolver is supported with a first-load
50
+ * compatibility scan because arbitrary functions are not invertible.
51
+ */
52
+ resolveSourceIdFromCollabDocumentId?: (docId: string) => string;
46
53
  /**
47
54
  * Callback invoked after a collab update to sync the content column.
48
55
  * If not provided, the plugin auto-syncs using table/contentColumn/idColumn.
@@ -79,9 +86,15 @@ export interface CollabPluginOptions {
79
86
  maxPayloadBytes?: number;
80
87
  }
81
88
  export declare function normalizeCollabAccess(options: Pick<CollabPluginOptions, "access" | "resourceType" | "resolveResourceId">): NormalizedCollabAccess;
89
+ /**
90
+ * Coalesce concurrent first loads for one document. The optional lock lets a
91
+ * database-backed caller extend that guarantee across server instances.
92
+ */
93
+ export declare function createCollabSourceSeeder(options: {
94
+ hasState: (docId: string) => Promise<boolean>;
95
+ loadSource: (docId: string) => Promise<string | null>;
96
+ seed: (docId: string, source: string, client?: DbExec) => Promise<void>;
97
+ withLock?: (docId: string, run: (client?: DbExec) => Promise<void>) => Promise<void>;
98
+ }): (docId: string) => Promise<void>;
82
99
  export declare function createCollabPlugin(options?: CollabPluginOptions): NitroPluginDef;
83
- export declare function selectUnseededCollabRows(rows: ReadonlyArray<Record<string, unknown>>, idColumn: string, existingDocIds: ReadonlySet<string>, resolveCollabDocumentId?: (sourceId: string) => string): Array<{
84
- row: Record<string, unknown>;
85
- docId: string;
86
- }>;
87
100
  export {};
@@ -16,10 +16,10 @@ import { defineEventHandler, getMethod, setResponseStatus, } from "h3";
16
16
  import { postAwareness, getActiveUsers } from "../collab/awareness.js";
17
17
  import { getCollabEmitter } from "../collab/emitter.js";
18
18
  import { getCollabState, postCollabUpdate, postCollabText, postCollabSearchReplace, } from "../collab/routes.js";
19
- import { listCollabDocIds } from "../collab/storage.js";
19
+ import { hasCollabState } from "../collab/storage.js";
20
20
  import { postCollabJson, getCollabJson, postCollabPatch, } from "../collab/struct-routes.js";
21
21
  import { seedFromText, seedFromJson } from "../collab/ydoc-manager.js";
22
- import { getDbExec } from "../db/client.js";
22
+ import { getDbExec, withDbExec } from "../db/client.js";
23
23
  import { getOrgContext } from "../org/context.js";
24
24
  import { resolveAccess, assertAccess } from "../sharing/access.js";
25
25
  import { getSession } from "./auth.js";
@@ -79,10 +79,60 @@ function warnForImplicitAllAuthenticatedAccess(table) {
79
79
  'Use access: { mode: "resource", resourceType: "..." } for access-scoped delivery, ' +
80
80
  'or access: { mode: "all-authenticated" } to explicitly acknowledge deployment-wide delivery.');
81
81
  }
82
+ /**
83
+ * Coalesce concurrent first loads for one document. The optional lock lets a
84
+ * database-backed caller extend that guarantee across server instances.
85
+ */
86
+ export function createCollabSourceSeeder(options) {
87
+ const inFlight = new Map();
88
+ return async (docId) => {
89
+ const existing = inFlight.get(docId);
90
+ if (existing)
91
+ return existing;
92
+ const seed = async (client) => {
93
+ if (await options.hasState(docId))
94
+ return;
95
+ const source = await options.loadSource(docId);
96
+ if (source === null)
97
+ return;
98
+ if (client) {
99
+ await options.seed(docId, source, client);
100
+ }
101
+ else {
102
+ await options.seed(docId, source);
103
+ }
104
+ };
105
+ const pending = (async () => {
106
+ // Keep the normal seeded-document path out of a transaction. The second
107
+ // check inside the lock closes the cross-instance race for cold docs.
108
+ if (await options.hasState(docId))
109
+ return;
110
+ if (options.withLock) {
111
+ await options.withLock(docId, seed);
112
+ }
113
+ else {
114
+ await seed();
115
+ }
116
+ })();
117
+ inFlight.set(docId, pending);
118
+ try {
119
+ await pending;
120
+ }
121
+ finally {
122
+ if (inFlight.get(docId) === pending)
123
+ inFlight.delete(docId);
124
+ }
125
+ };
126
+ }
82
127
  export function createCollabPlugin(options = {}) {
83
128
  const normalizedAccess = normalizeCollabAccess(options);
84
129
  const { table = "documents", contentColumn = "content", idColumn = "id", autoSeed = true, maxPayloadBytes = DEFAULT_MAX_PAYLOAD_BYTES, } = options;
85
- const resolveCollabDocumentId = options.resolveCollabDocumentId ?? ((sourceId) => sourceId);
130
+ const resolveSourceIdFromCollabDocumentId = options.resolveSourceIdFromCollabDocumentId ?? ((docId) => docId);
131
+ const isJson = options.contentType === "json";
132
+ const seedColumn = isJson
133
+ ? options.jsonColumn || contentColumn
134
+ : contentColumn;
135
+ const legacyResolveCollabDocumentId = options.resolveCollabDocumentId;
86
136
  const resourceType = normalizedAccess.mode === "resource"
87
137
  ? normalizedAccess.resourceType
88
138
  : undefined;
@@ -93,6 +143,115 @@ export function createCollabPlugin(options = {}) {
93
143
  !normalizedAccess.explicit) {
94
144
  warnForImplicitAllAuthenticatedAccess(table);
95
145
  }
146
+ const ensureDocumentSeeded = autoSeed
147
+ ? createCollabSourceSeeder({
148
+ hasState: hasCollabState,
149
+ loadSource: async (docId) => {
150
+ const readSource = (row, sourceId) => {
151
+ const source = row[seedColumn];
152
+ if (typeof source !== "string") {
153
+ throw new Error(`[collab] ${table}.${seedColumn} for ${sourceId} is unreadable`);
154
+ }
155
+ return source;
156
+ };
157
+ if (legacyResolveCollabDocumentId &&
158
+ !options.resolveSourceIdFromCollabDocumentId) {
159
+ // An arbitrary forward resolver cannot be inverted. Preserve
160
+ // existing consumers with a request-lazy compatibility scan; new
161
+ // configs should provide the reverse resolver to use the indexed
162
+ // source-id lookup.
163
+ const { rows } = await getDbExec().execute({
164
+ sql: `SELECT ${idColumn}, ${seedColumn} FROM ${table}`,
165
+ });
166
+ for (const row of rows) {
167
+ const rawSourceId = row[idColumn];
168
+ if (rawSourceId === null ||
169
+ rawSourceId === undefined ||
170
+ (typeof rawSourceId !== "string" &&
171
+ typeof rawSourceId !== "number" &&
172
+ typeof rawSourceId !== "bigint")) {
173
+ throw new Error(`[collab] ${table}.${idColumn} for a legacy lazy seed is unreadable`);
174
+ }
175
+ const sourceId = String(rawSourceId);
176
+ if (!sourceId) {
177
+ throw new Error(`[collab] ${table}.${idColumn} for a legacy lazy seed is unreadable`);
178
+ }
179
+ if (legacyResolveCollabDocumentId(sourceId) !== docId)
180
+ continue;
181
+ return readSource(row, sourceId);
182
+ }
183
+ return null;
184
+ }
185
+ const sourceId = resolveSourceIdFromCollabDocumentId(docId);
186
+ const { rows } = await getDbExec().execute({
187
+ sql: `SELECT ${seedColumn} FROM ${table} WHERE ${idColumn} = ?`,
188
+ args: [sourceId],
189
+ });
190
+ if (rows.length === 0)
191
+ return null;
192
+ return readSource(rows[0], sourceId);
193
+ },
194
+ seed: async (docId, source, client) => {
195
+ if (!isJson) {
196
+ if (client) {
197
+ await seedFromText(docId, source, "content", client);
198
+ }
199
+ else {
200
+ await seedFromText(docId, source);
201
+ }
202
+ return;
203
+ }
204
+ let parsed;
205
+ try {
206
+ parsed = JSON.parse(source);
207
+ }
208
+ catch (error) {
209
+ throw new Error(`[collab] ${table}.${seedColumn} for ${docId} contains invalid JSON`, { cause: error });
210
+ }
211
+ const type = Array.isArray(parsed) ? "array" : "map";
212
+ if (client) {
213
+ await seedFromJson(docId, parsed, "data", type, client);
214
+ }
215
+ else {
216
+ await seedFromJson(docId, parsed, "data", type);
217
+ }
218
+ },
219
+ withLock: async (docId, run) => {
220
+ const client = getDbExec();
221
+ if (typeof client.transaction !== "function")
222
+ return run();
223
+ for (let attempt = 0; attempt < 20; attempt += 1) {
224
+ const acquired = await client.transaction(async (tx) => {
225
+ const { rows } = await tx.execute({
226
+ sql: "SELECT pg_try_advisory_xact_lock(hashtextextended(?, 0)) AS acquired",
227
+ args: [`${table}:${docId}`],
228
+ timeoutMs: 1_000,
229
+ });
230
+ const result = rows[0]?.acquired;
231
+ const acquired = result === true || result === "t"
232
+ ? true
233
+ : result === false || result === "f"
234
+ ? false
235
+ : null;
236
+ if (acquired === null) {
237
+ throw new Error(`[collab] advisory lock result for ${docId} is unreadable`);
238
+ }
239
+ if (!acquired)
240
+ return false;
241
+ await withDbExec(tx, () => run(tx));
242
+ return true;
243
+ });
244
+ if (acquired)
245
+ return;
246
+ // Do not leave every cold instance blocked on a database session
247
+ // while the first seed runs. This bounded retry keeps the wait in
248
+ // application memory and fails loudly if a seed cannot finish.
249
+ await new Promise((resolve) => setTimeout(resolve, Math.min(200, 25 * (attempt + 1))));
250
+ }
251
+ throw new Error(`[collab] timed out waiting to seed ${docId}; retry the request`);
252
+ },
253
+ })
254
+ : async () => { };
96
255
  return async (nitroApp) => {
97
256
  await awaitBootstrap(nitroApp);
98
257
  const P = FRAMEWORK_ROUTE_PREFIX;
@@ -278,6 +437,14 @@ export function createCollabPlugin(options = {}) {
278
437
  event.context._collabMaxPayloadBytes = maxPayloadBytes;
279
438
  }
280
439
  }
440
+ const needsSeed = (action === "state" && method === "GET") ||
441
+ (action === "update" && method === "POST") ||
442
+ (action === "text" && method === "POST") ||
443
+ (action === "search-replace" && method === "POST") ||
444
+ (action === "json" && (method === "GET" || method === "POST")) ||
445
+ (action === "patch" && method === "POST");
446
+ if (needsSeed)
447
+ await ensureDocumentSeeded(docId);
281
448
  if (action === "state" && method === "GET")
282
449
  return getCollabState(event);
283
450
  if (action === "update" && method === "POST")
@@ -300,65 +467,8 @@ export function createCollabPlugin(options = {}) {
300
467
  return { error: "Not found" };
301
468
  });
302
469
  }));
303
- // Auto-seed existing documents into collab state
304
- if (autoSeed) {
305
- const isJson = options.contentType === "json";
306
- const seedColumn = isJson
307
- ? options.jsonColumn || contentColumn
308
- : contentColumn;
309
- // Run in background so it doesn't block startup
310
- setTimeout(async () => {
311
- try {
312
- // Read existing ids once, then apply the plugin's document-id
313
- // mapping before filtering. A source-table SQL predicate can use
314
- // the wrong id for integrations such as Analytics' dash-* docs.
315
- const existingDocIds = await listCollabDocIds();
316
- const client = getDbExec();
317
- const { rows } = await client.execute(`SELECT ${idColumn}, ${seedColumn} FROM ${table}`);
318
- for (const { row, docId } of selectUnseededCollabRows(rows, idColumn, existingDocIds, resolveCollabDocumentId)) {
319
- let seeded = false;
320
- if (isJson) {
321
- const raw = row[seedColumn] ?? "{}";
322
- try {
323
- const parsed = JSON.parse(raw);
324
- const inferredType = Array.isArray(parsed)
325
- ? "array"
326
- : "map";
327
- await seedFromJson(docId, parsed, "data", inferredType);
328
- seeded = true;
329
- }
330
- catch {
331
- // Invalid JSON — skip
332
- }
333
- }
334
- else {
335
- const content = row[seedColumn] ?? "";
336
- await seedFromText(docId, content);
337
- seeded = true;
338
- }
339
- if (seeded)
340
- existingDocIds.add(docId);
341
- }
342
- }
343
- catch {
344
- // Table may not exist yet on first boot — that's fine
345
- }
346
- }, 1000);
347
- }
470
+ // Source rows are seeded lazily by the request path above. A cold-start
471
+ // scan here stampedes serverless instances and does work for documents no
472
+ // caller will ever open.
348
473
  };
349
474
  }
350
- export function selectUnseededCollabRows(rows, idColumn, existingDocIds, resolveCollabDocumentId = (sourceId) => sourceId) {
351
- const seenDocIds = new Set(existingDocIds);
352
- const unseeded = [];
353
- for (const row of rows) {
354
- const sourceId = String(row[idColumn] ?? "");
355
- if (!sourceId)
356
- continue;
357
- const docId = resolveCollabDocumentId(sourceId);
358
- if (seenDocIds.has(docId))
359
- continue;
360
- seenDocIds.add(docId);
361
- unseeded.push({ row, docId });
362
- }
363
- return unseeded;
364
- }
@@ -1,10 +1,9 @@
1
1
  ---
2
2
  name: shadcn-ui
3
3
  description: >-
4
- The shadcn CLI, component composition, theming, and registry workflow. Use
5
- when adding, replacing, upgrading, or debugging a shadcn/ui primitive itself,
6
- or when changing a theme or registry. Do not load it for ordinary edits to a
7
- file that happens to import a shadcn component.
4
+ Project-aware shadcn/ui components, registry MCP, theming, and design-system
5
+ lint feedback. Use when adding or changing React/Tailwind UI, a primitive,
6
+ theme, registry, or component contract in an Agent-Native app.
8
7
  scope: dev
9
8
  source: https://ui.shadcn.com/docs/skills
10
9
  local-changes: >-
@@ -16,11 +15,18 @@ metadata:
16
15
 
17
16
  # shadcn/ui
18
17
 
19
- This skill keeps shadcn/ui work project-aware. Components are source files in the app, so always inspect the local project before adding, importing, or rewriting them.
18
+ This skill keeps shadcn/ui work project-aware. Components are source files in the
19
+ app, so always inspect the local project before adding, importing, or rewriting
20
+ them. When `@shadcn/lint` is configured, treat its feedback as implementation
21
+ guidance, not cosmetic noise. The framework repository runs it through the root
22
+ Oxlint configuration; generated workspaces carry this skill and the MCP
23
+ workflow, but do not automatically inherit that repository-only lint setup.
20
24
 
21
25
  ## First Steps
22
26
 
23
- 1. Work from the app root that owns `components.json`.
27
+ 1. Work from the app root that owns `components.json`. In this monorepo, that is
28
+ the selected app/template directory (usually under `templates/`), not the
29
+ repository root.
24
30
  2. In an Agent-Native app, inspect `app/design-system.ts` and
25
31
  `ToolkitProvider` before choosing a primitive. A registered company design
26
32
  system takes precedence over the default shadcn adapter.
@@ -29,6 +35,59 @@ This skill keeps shadcn/ui work project-aware. Components are source files in th
29
35
  5. Check `app/components/ui/` or the resolved `ui` path before importing a component.
30
36
  6. For unfamiliar components, run `pnpm dlx shadcn@latest docs <component>` and read the returned docs or examples before coding.
31
37
 
38
+ ## MCP Registry Workflow
39
+
40
+ Each first-party app keeps the official shadcn MCP configuration in `.mcp.json`
41
+ next to its `components.json`. Start the agent from that app root so the MCP
42
+ server resolves the correct aliases, local UI directory, and Tailwind theme.
43
+ Do not add a single repository-root server: the root is a workspace, not a
44
+ shadcn project, and would make installs target the wrong project.
45
+
46
+ - Use the shadcn MCP to search, inspect, and install registry items when it is
47
+ connected. Prefer the app's configured registries over copied source.
48
+ - Use `pnpm dlx shadcn@latest info --json` for project context; the MCP has no
49
+ equivalent project-info tool.
50
+ - If the MCP is unavailable, use the equivalent `pnpm dlx shadcn@latest`
51
+ command from the same app root and continue following this skill.
52
+ - Keep registry URLs and aliases in that app's `components.json`. Never put
53
+ registry credentials in MCP config or checked-in source.
54
+
55
+ ## Design-System Lint Feedback
56
+
57
+ In the framework repository, `pnpm lint` runs Oxlint with `@shadcn/lint` for the
58
+ scoped template UI paths. Run it after UI changes and fix every `shadcn/*`
59
+ finding before handing off. Template packages use the workspace lint
60
+ configuration; do not create a second local lint configuration just for one
61
+ app.
62
+
63
+ Before claiming that shadcn lint ran in another workspace, inspect its root
64
+ `package.json` and `.oxlintrc.json`. The generic generated workspace currently
65
+ exposes `pnpm lint` as formatting-only and does not ship `@shadcn/lint` or the
66
+ framework repository's Oxlint config. In that scaffold, use this skill plus the
67
+ connected shadcn MCP (or the CLI fallback) for design-system guidance, and do
68
+ not report `shadcn/*` checks as having run. If the workspace owner adopts the
69
+ linter, add the dependency, scoped config, and lint script together.
70
+
71
+ The Plan template is intentionally excluded from this rollout. Its files remain
72
+ under `templates/plan/**`, but the root Oxlint ignore list keeps them out of the
73
+ shadcn checks until that template has a separate lint baseline.
74
+
75
+ - `no-restyle`: use the component's `variant`, `size`, and semantic props; put
76
+ layout on a parent and add a component variant only when the design system
77
+ genuinely needs a new treatment.
78
+ - `no-raw-colors`: use the theme tokens in the app's `global.css`, not raw
79
+ Tailwind palette classes or hard-coded color attributes.
80
+ - `no-arbitrary-values`: use the theme scale or a declared token instead of
81
+ one-off bracket values.
82
+ - `no-inline-styles`: use classes or CSS custom properties for dynamic layout;
83
+ keep intentional editor/export exceptions scoped to their owning subsystem.
84
+ - `no-unknown-classes` and `require-static-classes`: use classes Tailwind can
85
+ generate and keep class strings statically discoverable.
86
+
87
+ The local primitive source is the contract boundary. Product code should consume
88
+ its props and variants; update `packages/toolkit/src/ui/` or an app's
89
+ `components/ui/` only when the design-system contract itself changes.
90
+
32
91
  ## Agent-Native Adapter Rule
33
92
 
34
93
  Pages, routes, and domain components import controls through the app's local UI
@@ -0,0 +1,8 @@
1
+ {
2
+ "mcpServers": {
3
+ "shadcn": {
4
+ "command": "npx",
5
+ "args": ["shadcn@latest", "mcp"]
6
+ }
7
+ }
8
+ }
@@ -1,10 +1,9 @@
1
1
  ---
2
2
  name: shadcn-ui
3
3
  description: >-
4
- The shadcn CLI, component composition, theming, and registry workflow. Use
5
- when adding, replacing, upgrading, or debugging a shadcn/ui primitive itself,
6
- or when changing a theme or registry. Do not load it for ordinary edits to a
7
- file that happens to import a shadcn component.
4
+ Project-aware shadcn/ui components, registry MCP, theming, and design-system
5
+ lint feedback. Use when adding or changing React/Tailwind UI, a primitive,
6
+ theme, registry, or component contract in an Agent-Native app.
8
7
  scope: dev
9
8
  source: https://ui.shadcn.com/docs/skills
10
9
  local-changes: >-
@@ -16,11 +15,18 @@ metadata:
16
15
 
17
16
  # shadcn/ui
18
17
 
19
- This skill keeps shadcn/ui work project-aware. Components are source files in the app, so always inspect the local project before adding, importing, or rewriting them.
18
+ This skill keeps shadcn/ui work project-aware. Components are source files in the
19
+ app, so always inspect the local project before adding, importing, or rewriting
20
+ them. When `@shadcn/lint` is configured, treat its feedback as implementation
21
+ guidance, not cosmetic noise. The framework repository runs it through the root
22
+ Oxlint configuration; generated workspaces carry this skill and the MCP
23
+ workflow, but do not automatically inherit that repository-only lint setup.
20
24
 
21
25
  ## First Steps
22
26
 
23
- 1. Work from the app root that owns `components.json`.
27
+ 1. Work from the app root that owns `components.json`. In this monorepo, that is
28
+ the selected app/template directory (usually under `templates/`), not the
29
+ repository root.
24
30
  2. In an Agent-Native app, inspect `app/design-system.ts` and
25
31
  `ToolkitProvider` before choosing a primitive. A registered company design
26
32
  system takes precedence over the default shadcn adapter.
@@ -29,6 +35,59 @@ This skill keeps shadcn/ui work project-aware. Components are source files in th
29
35
  5. Check `app/components/ui/` or the resolved `ui` path before importing a component.
30
36
  6. For unfamiliar components, run `pnpm dlx shadcn@latest docs <component>` and read the returned docs or examples before coding.
31
37
 
38
+ ## MCP Registry Workflow
39
+
40
+ Each first-party app keeps the official shadcn MCP configuration in `.mcp.json`
41
+ next to its `components.json`. Start the agent from that app root so the MCP
42
+ server resolves the correct aliases, local UI directory, and Tailwind theme.
43
+ Do not add a single repository-root server: the root is a workspace, not a
44
+ shadcn project, and would make installs target the wrong project.
45
+
46
+ - Use the shadcn MCP to search, inspect, and install registry items when it is
47
+ connected. Prefer the app's configured registries over copied source.
48
+ - Use `pnpm dlx shadcn@latest info --json` for project context; the MCP has no
49
+ equivalent project-info tool.
50
+ - If the MCP is unavailable, use the equivalent `pnpm dlx shadcn@latest`
51
+ command from the same app root and continue following this skill.
52
+ - Keep registry URLs and aliases in that app's `components.json`. Never put
53
+ registry credentials in MCP config or checked-in source.
54
+
55
+ ## Design-System Lint Feedback
56
+
57
+ In the framework repository, `pnpm lint` runs Oxlint with `@shadcn/lint` for the
58
+ scoped template UI paths. Run it after UI changes and fix every `shadcn/*`
59
+ finding before handing off. Template packages use the workspace lint
60
+ configuration; do not create a second local lint configuration just for one
61
+ app.
62
+
63
+ Before claiming that shadcn lint ran in another workspace, inspect its root
64
+ `package.json` and `.oxlintrc.json`. The generic generated workspace currently
65
+ exposes `pnpm lint` as formatting-only and does not ship `@shadcn/lint` or the
66
+ framework repository's Oxlint config. In that scaffold, use this skill plus the
67
+ connected shadcn MCP (or the CLI fallback) for design-system guidance, and do
68
+ not report `shadcn/*` checks as having run. If the workspace owner adopts the
69
+ linter, add the dependency, scoped config, and lint script together.
70
+
71
+ The Plan template is intentionally excluded from this rollout. Its files remain
72
+ under `templates/plan/**`, but the root Oxlint ignore list keeps them out of the
73
+ shadcn checks until that template has a separate lint baseline.
74
+
75
+ - `no-restyle`: use the component's `variant`, `size`, and semantic props; put
76
+ layout on a parent and add a component variant only when the design system
77
+ genuinely needs a new treatment.
78
+ - `no-raw-colors`: use the theme tokens in the app's `global.css`, not raw
79
+ Tailwind palette classes or hard-coded color attributes.
80
+ - `no-arbitrary-values`: use the theme scale or a declared token instead of
81
+ one-off bracket values.
82
+ - `no-inline-styles`: use classes or CSS custom properties for dynamic layout;
83
+ keep intentional editor/export exceptions scoped to their owning subsystem.
84
+ - `no-unknown-classes` and `require-static-classes`: use classes Tailwind can
85
+ generate and keep class strings statically discoverable.
86
+
87
+ The local primitive source is the contract boundary. Product code should consume
88
+ its props and variants; update `packages/toolkit/src/ui/` or an app's
89
+ `components/ui/` only when the design-system contract itself changes.
90
+
32
91
  ## Agent-Native Adapter Rule
33
92
 
34
93
  Pages, routes, and domain components import controls through the app's local UI
@@ -0,0 +1,8 @@
1
+ {
2
+ "mcpServers": {
3
+ "shadcn": {
4
+ "command": "npx",
5
+ "args": ["shadcn@latest", "mcp"]
6
+ }
7
+ }
8
+ }