makersclaw 0.6.0 → 0.8.0

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 (152) hide show
  1. package/README.md +56 -9
  2. package/package.json +1 -1
  3. package/r/annotate.json +4 -4
  4. package/r/approval-request.json +3 -3
  5. package/r/audio-player.json +3 -3
  6. package/r/auth-form.json +2 -2
  7. package/r/board.json +4 -4
  8. package/r/calendar-view.json +4 -4
  9. package/r/cart.json +3 -3
  10. package/r/chart.json +6 -6
  11. package/r/chat.json +3 -3
  12. package/r/code-editor.json +4 -4
  13. package/r/comment-box.json +4 -4
  14. package/r/contact-section.json +3 -3
  15. package/r/core.json +10 -4
  16. package/r/csv-import.json +4 -4
  17. package/r/data-table.json +3 -3
  18. package/r/diagram.json +3 -3
  19. package/r/doc-editor.json +4 -4
  20. package/r/doc-page.json +4 -4
  21. package/r/email-render.json +2 -2
  22. package/r/faq.json +2 -2
  23. package/r/file-upload.json +3 -3
  24. package/r/flow-canvas.json +4 -4
  25. package/r/footer.json +3 -3
  26. package/r/gantt.json +4 -4
  27. package/r/job-panel.json +3 -3
  28. package/r/json-editor.json +4 -4
  29. package/r/map.json +5 -5
  30. package/r/menubar.json +2 -2
  31. package/r/mind-map.json +4 -4
  32. package/r/navigation-menu.json +2 -2
  33. package/r/order-detail.json +2 -2
  34. package/r/org-chart.json +4 -4
  35. package/r/page-builder.json +6 -6
  36. package/r/payment-methods.json +2 -2
  37. package/r/pdf-document.json +2 -2
  38. package/r/pdf-viewer.json +6 -6
  39. package/r/product-detail.json +3 -3
  40. package/r/product-grid.json +4 -4
  41. package/r/record-form.json +3 -3
  42. package/r/record-notes.json +5 -5
  43. package/r/registry.json +126 -121
  44. package/r/search-dialog.json +3 -3
  45. package/r/settings-list.json +3 -3
  46. package/r/setup-checklist.json +3 -3
  47. package/r/share-dialog.json +3 -3
  48. package/r/sidebar.json +3 -3
  49. package/r/signature-pad.json +3 -3
  50. package/r/sketch-canvas.json +5 -5
  51. package/r/spreadsheet.json +3 -3
  52. package/r/stat-cards.json +4 -4
  53. package/r/task-list.json +4 -4
  54. package/r/task-model.json +3 -3
  55. package/r/testimonials.json +2 -2
  56. package/r/transaction-list.json +3 -3
  57. package/r/whiteboard.json +4 -4
  58. package/registry/blocks/annotate/README.md +24 -12
  59. package/registry/blocks/approval-request/README.md +15 -8
  60. package/registry/blocks/audio-player/README.md +17 -10
  61. package/registry/blocks/board/README.md +13 -9
  62. package/registry/blocks/board/board.jsx +1 -1
  63. package/registry/blocks/calendar-view/README.md +28 -4
  64. package/registry/blocks/cart/README.md +14 -10
  65. package/registry/blocks/chart/README.md +14 -7
  66. package/registry/blocks/chart/cartesian.jsx +1 -1
  67. package/registry/blocks/chart/core.jsx +33 -23
  68. package/registry/blocks/chart/pie.jsx +1 -1
  69. package/registry/blocks/chat/README.md +47 -14
  70. package/registry/blocks/chat/demo.jsx +2 -2
  71. package/registry/blocks/code-editor/README.md +4 -0
  72. package/registry/blocks/code-editor/code-editor.jsx +1 -1
  73. package/registry/blocks/comment-box/README.md +20 -13
  74. package/registry/blocks/contact-section/README.md +7 -6
  75. package/registry/blocks/csv-import/README.md +34 -12
  76. package/registry/blocks/data-table/README.md +41 -8
  77. package/registry/blocks/diagram/diagram.jsx +1 -1
  78. package/registry/blocks/doc-editor/README.md +18 -6
  79. package/registry/blocks/doc-editor/doc-editor.jsx +1 -1
  80. package/registry/blocks/doc-page/README.md +16 -6
  81. package/registry/blocks/file-upload/README.md +7 -1
  82. package/registry/blocks/flow-canvas/README.md +30 -12
  83. package/registry/blocks/flow-canvas/flow-canvas.jsx +1 -1
  84. package/registry/blocks/footer/README.md +7 -4
  85. package/registry/blocks/gantt/README.md +18 -12
  86. package/registry/blocks/job-panel/README.md +1 -1
  87. package/registry/blocks/json-editor/README.md +16 -4
  88. package/registry/blocks/map/README.md +19 -3
  89. package/registry/blocks/map/map-view.jsx +1 -1
  90. package/registry/blocks/map/maplibre.js +1 -1
  91. package/registry/blocks/mind-map/README.md +20 -7
  92. package/registry/blocks/org-chart/README.md +14 -9
  93. package/registry/blocks/page-builder/README.md +14 -7
  94. package/registry/blocks/page-builder/demo.jsx +3 -3
  95. package/registry/blocks/page-builder/page-builder.jsx +1 -1
  96. package/registry/blocks/page-builder/page-view.jsx +1 -1
  97. package/registry/blocks/page-builder/page.js +1 -1
  98. package/registry/blocks/pdf-viewer/README.md +26 -21
  99. package/registry/blocks/pdf-viewer/pdf-viewer.jsx +1 -1
  100. package/registry/blocks/pdf-viewer/pdfjs.js +1 -1
  101. package/registry/blocks/pdf-viewer/ssr-shim.js +1 -1
  102. package/registry/blocks/product-detail/README.md +13 -7
  103. package/registry/blocks/product-detail/demo.jsx +1 -1
  104. package/registry/blocks/product-grid/README.md +12 -10
  105. package/registry/blocks/record-form/README.md +26 -9
  106. package/registry/blocks/record-notes/README.md +42 -30
  107. package/registry/blocks/search-dialog/README.md +38 -7
  108. package/registry/blocks/settings-list/README.md +29 -17
  109. package/registry/blocks/setup-checklist/README.md +12 -14
  110. package/registry/blocks/share-dialog/README.md +1 -1
  111. package/registry/blocks/sidebar/README.md +23 -13
  112. package/registry/blocks/signature-pad/README.md +15 -4
  113. package/registry/blocks/sketch-canvas/README.md +25 -10
  114. package/registry/blocks/sketch-canvas/sketch-canvas.jsx +1 -1
  115. package/registry/blocks/sketch-canvas/ssr-shim.js +3 -3
  116. package/registry/blocks/spreadsheet/README.md +17 -8
  117. package/registry/blocks/stat-cards/README.md +17 -8
  118. package/registry/blocks/task-list/README.md +14 -11
  119. package/registry/blocks/task-model/README.md +3 -3
  120. package/registry/blocks/transaction-list/README.md +15 -7
  121. package/registry/blocks/whiteboard/README.md +34 -22
  122. package/src/cli.mjs +33 -6
  123. package/src/install.mjs +285 -26
  124. package/src/registry.mjs +109 -1
  125. package/templates/next/browser/app/auto-refresh.jsx +67 -0
  126. package/templates/next/browser/app/layout.jsx +94 -0
  127. package/templates/next/browser/app/page.jsx +60 -0
  128. package/templates/next/browser/app/s/[key]/page.jsx +95 -0
  129. package/templates/next/browser/lib/actions.js +16 -0
  130. package/templates/next/browser/lib/data.js +43 -0
  131. package/templates/next/files/README.md +1100 -89
  132. package/templates/next/files/app/frame.js +128 -4
  133. package/templates/next/files/app/shell.jsx +54 -37
  134. package/templates/next/files/check/run.mjs +1514 -45
  135. package/templates/next/files/components/ui/index.js +1 -0
  136. package/templates/next/files/components/ui/item.jsx +2 -2
  137. package/templates/next/files/components/ui/layout.jsx +307 -0
  138. package/templates/next/files/components/ui/notice.jsx +1 -1
  139. package/templates/next/files/components/ui/separator.jsx +1 -1
  140. package/templates/next/files/lib/actions.js +170 -51
  141. package/templates/next/files/lib/brain-server.js +96 -0
  142. package/templates/next/files/lib/brain.js +413 -0
  143. package/templates/next/files/lib/claw.js +15 -1
  144. package/templates/next/files/lib/data.js +119 -29
  145. package/templates/next/files/lib/db.js +97 -17
  146. package/templates/next/files/lib/door.js +680 -0
  147. package/templates/next/files/lib/hooks.js +71 -2
  148. package/templates/next/files/lib/platform.js +93 -4
  149. package/templates/next/files/next.config.mjs +36 -2
  150. package/templates/next/files/surfaces.js +3 -2
  151. package/templates/next/template.json +18 -3
  152. package/templates/next/files/schema.sql +0 -22
@@ -1,5 +1,5 @@
1
1
  import { headers } from "next/headers";
2
- import { artifactId, ident, query } from "./db";
2
+ import { ident, query } from "./db";
3
3
  import { markSample } from "./sample";
4
4
 
5
5
  // SERVER reads. Import from server components and server actions only —
@@ -38,21 +38,111 @@ export function plain(row) {
38
38
  return out;
39
39
  }
40
40
 
41
- // { column: value } → "WHERE ..." text; params is filled in place.
42
- // null → IS NULL, an array → = ANY, anything else → equality.
43
- function whereClause(filter, params) {
44
- const where = [];
45
- for (const [col, val] of Object.entries(filter ?? {})) {
46
- if (val === null) {
47
- where.push(`${ident(col)} IS NULL`);
48
- } else if (Array.isArray(val)) {
49
- params.push(val);
50
- where.push(`${ident(col)} = ANY($${params.length})`);
51
- } else {
52
- params.push(val);
53
- where.push(`${ident(col)} = $${params.length}`);
41
+ // A filter is the platform data door's grammar (claw's STATIC-SPEC §1,
42
+ // "The routes"), so a surface's filter means the same in a server app,
43
+ // read here, and in a browser one, read by the door. Up to 0.6.x it knew
44
+ // equals, lists and null, and any other object went to pg as a value.
45
+ const WHERE_MAX = 20;
46
+ const LIST_MAX = 1000;
47
+ const BOUNDS = [
48
+ ["gt", ">"],
49
+ ["gte", ">="],
50
+ ["lt", "<"],
51
+ ["lte", "<="],
52
+ ];
53
+ const GRAMMAR = "takes a value, [values], null, or one of {not}, {gt|gte|lt|lte}, {has}, {prefix}";
54
+
55
+ /** A value a condition compares with: text, a finite number, true or
56
+ * false, or a Date (JSON sends it as its ISO time). */
57
+ function isValue(v) {
58
+ if (typeof v === "string" || typeof v === "boolean") return true;
59
+ if (typeof v === "number") return Number.isFinite(v);
60
+ return v instanceof Date && !Number.isNaN(v.getTime());
61
+ }
62
+
63
+ /** A prefix for LIKE: lower-cased, LIKE's own marks escaped (Postgres's
64
+ * default escape is a backslash), then "%". */
65
+ function likePrefix(text) {
66
+ return `${text.toLowerCase().replace(/[\\%_]/g, (m) => `\\${m}`)}%`;
67
+ }
68
+
69
+ /** One `column: condition` as SQL; `bind` adds a value and names it. */
70
+ function condition(name, cond, bind) {
71
+ const refuse = (what) => {
72
+ throw new Error(`filter: ${name} ${what}`);
73
+ };
74
+ if (name.includes(".")) refuse("is not a column: name the column alone");
75
+ const col = ident(name);
76
+ if (cond === null) return `${col} IS NULL`;
77
+ if (isValue(cond)) return `${col} = ${bind(cond)}`;
78
+ if (Array.isArray(cond)) {
79
+ if (cond.length > LIST_MAX) refuse(`takes at most ${LIST_MAX.toLocaleString("en-US")} values in a list`);
80
+ if (!cond.every(isValue)) refuse("takes a list of values (text, numbers, true or false): filter empty ones with null");
81
+ return `${col} = ANY(${bind(cond)})`;
82
+ }
83
+ if (typeof cond !== "object" || cond instanceof Date) refuse(GRAMMAR);
84
+ // A key left undefined is not there, as JSON leaves it out on its way
85
+ // to the door: { gte: from, lte: to } with no `to` is one bound.
86
+ const keys = Object.keys(cond).filter((k) => cond[k] !== undefined);
87
+ if (keys.length === 1 && keys[0] === "not") {
88
+ if (cond.not === null) return `${col} IS NOT NULL`;
89
+ if (!isValue(cond.not)) refuse("takes {not: value} or {not: null}");
90
+ // Distinct, not <>: a row with no value is not the value either.
91
+ return `${col} IS DISTINCT FROM ${bind(cond.not)}`;
92
+ }
93
+ if (keys.length === 1 && keys[0] === "has") {
94
+ if (!isValue(cond.has)) refuse("takes {has: value}");
95
+ // An array column holds it. (The door also reads a jsonb column's
96
+ // `?`; it knows the column's type, which a filter here does not.)
97
+ return `${bind(cond.has)} = ANY(${col})`;
98
+ }
99
+ if (keys.length === 1 && keys[0] === "prefix") {
100
+ if (typeof cond.prefix !== "string" || !cond.prefix) refuse('wants {prefix: "some text"}');
101
+ return `lower(${col}) LIKE ${bind(likePrefix(cond.prefix))}`;
102
+ }
103
+ const bounds = BOUNDS.map(([k]) => k);
104
+ if (keys.length && keys.every((k) => bounds.includes(k))) {
105
+ const lower = keys.filter((k) => k === "gt" || k === "gte");
106
+ const upper = keys.filter((k) => k === "lt" || k === "lte");
107
+ if (lower.length > 1 || upper.length > 1) refuse("takes one lower bound (gt or gte) and one upper (lt or lte)");
108
+ const parts = [];
109
+ for (const [k, op] of BOUNDS) {
110
+ if (!keys.includes(k)) continue;
111
+ if (!isValue(cond[k])) refuse(`takes {${k}: value}: a number, a day or a time`);
112
+ parts.push(`${col} ${op} ${bind(cond[k])}`);
54
113
  }
114
+ return parts.join(" AND ");
115
+ }
116
+ return refuse(GRAMMAR);
117
+ }
118
+
119
+ /**
120
+ * `filter` → " WHERE …" text, "" for none; `params` is filled in place and
121
+ * every value is bound. An AND of `{ column: condition }`, at most 20:
122
+ *
123
+ * value equals
124
+ * [value, …] one of (at most 1,000)
125
+ * null empty (IS NULL)
126
+ * { not: value | null } anything else (IS DISTINCT FROM), or not empty
127
+ * { gt | gte | lt | lte: v } a bound; one lower and one upper may combine
128
+ * { has: value } an array column holds it
129
+ * { prefix: "text" } starts with, case ignored
130
+ *
131
+ * A column whose condition is undefined is left out, as JSON leaves it
132
+ * out. Anything else throws, and the read returns it as its error.
133
+ */
134
+ export function whereClause(filter, params) {
135
+ if (filter === undefined || filter === null) return "";
136
+ if (typeof filter !== "object" || Array.isArray(filter)) {
137
+ throw new Error('filter is an object of column names to conditions, like { stage: "won" }');
55
138
  }
139
+ const entries = Object.entries(filter).filter(([, cond]) => cond !== undefined);
140
+ if (entries.length > WHERE_MAX) throw new Error(`filter holds at most ${WHERE_MAX} conditions`);
141
+ const bind = (value) => {
142
+ params.push(value);
143
+ return `$${params.length}`;
144
+ };
145
+ const where = entries.map(([name, cond]) => condition(name, cond, bind));
56
146
  return where.length ? ` WHERE ${where.join(" AND ")}` : "";
57
147
  }
58
148
 
@@ -63,7 +153,8 @@ function failed(what, error) {
63
153
  }
64
154
 
65
155
  /**
66
- * Rows of a source table. `filter` is { column: value }; `orderBy` is
156
+ * Rows of a source table. `filter` is whereClause's grammar (above:
157
+ * { column: condition }, the data door's); `orderBy` is
67
158
  * "updated_at desc" (default: idColumn desc); `limit` caps at 5000.
68
159
  * → { rows, error }
69
160
  */
@@ -121,37 +212,36 @@ export async function countBy({ source, column, filter }) {
121
212
  }
122
213
 
123
214
  /**
124
- * What happened, newest first: public.artifact_activity for this artifact
215
+ * What happened, newest first: this app's lines in claw_app.activity
125
216
  * (every lib/actions.js write and everything the agent recorded with the
126
- * sql verb). `surface` narrows to one surface key; `since` (ISO or Date)
127
- * to rows after that time. Rows: { id, at, action, surface, row_ref,
128
- * detail, data, by }. → { rows, error }
217
+ * sql verb). The view holds only this app's lines, so nothing here names
218
+ * the app. `surface` narrows to one surface key; `since` (ISO or Date) to
219
+ * rows after that time. Rows: { id, at, action, surface, row_ref, detail,
220
+ * data, by }. Outside Makersclaw there is no such view: `error` says so.
221
+ * → { rows, error }
129
222
  */
130
223
  export async function loadActivity({ limit = 100, surface, since } = {}) {
131
- const id = artifactId();
132
- if (!id) {
133
- return { rows: [], error: failed("public.artifact_activity", "no CLAW_ARTIFACT_ID; product_run sets it") };
134
- }
135
224
  try {
136
- const params = [id];
137
- let where = `WHERE artifact_id = $1`;
225
+ const params = [];
226
+ const where = [];
138
227
  if (surface) {
139
228
  params.push(String(surface));
140
- where += ` AND surface = $${params.length}`;
229
+ where.push(`surface = $${params.length}`);
141
230
  }
142
231
  if (since) {
143
232
  params.push(since instanceof Date ? since.toISOString() : String(since));
144
- where += ` AND at > $${params.length}::timestamptz`;
233
+ where.push(`at > $${params.length}::timestamptz`);
145
234
  }
146
235
  params.push(Math.max(1, Math.min(1000, Number(limit) || 100)));
236
+ const filter = where.length ? ` WHERE ${where.join(" AND ")}` : "";
147
237
  const { rows } = await query(
148
238
  `SELECT id, at, action, surface, row_ref, detail, data, by
149
- FROM public.artifact_activity ${where} ORDER BY at DESC, id DESC LIMIT $${params.length}`,
239
+ FROM claw_app.activity${filter} ORDER BY at DESC, id DESC LIMIT $${params.length}`,
150
240
  params,
151
241
  );
152
242
  return { rows: rows.map(plain), error: null };
153
243
  } catch (error) {
154
- return { rows: [], error: failed("public.artifact_activity", error) };
244
+ return { rows: [], error: failed("claw_app.activity", error) };
155
245
  }
156
246
  }
157
247
 
@@ -1,3 +1,4 @@
1
+ import { headers } from "next/headers";
1
2
  import { Pool, types } from "pg";
2
3
 
3
4
  // A `date` column is a day on a wall calendar, not an instant, so it is
@@ -26,18 +27,84 @@ globalThis._pgPool = pool;
26
27
 
27
28
  export const query = (text, params) => pool.query(text, params);
28
29
 
30
+ /**
31
+ * Who is asking: the member the platform's proxy named on this request
32
+ * (`x-claw-user-id`, `x-claw-user-email`; the proxy drops a browser's own
33
+ * copies first, so they are only ever its word). `''` for each one it did
34
+ * not name: a local run, a render outside a request, a job.
35
+ * → { id, email }
36
+ */
37
+ export async function requestMember() {
38
+ try {
39
+ const asked = await headers();
40
+ return { id: asked.get("x-claw-user-id") || "", email: asked.get("x-claw-user-email") || "" };
41
+ } catch {
42
+ return { id: "", email: "" };
43
+ }
44
+ }
45
+
46
+ // The member, told to the database inside the write's own transaction. The
47
+ // third argument, true, makes each setting local: the COMMIT ends it, so the
48
+ // next statement on this pooled connection never runs as this member. Never
49
+ // false (the platform's rule since the Neon probe; test/data.test.mjs greps
50
+ // the template for it). Values are bound, never written into the text.
51
+ const MEMBER_SETTINGS =
52
+ "SELECT set_config('claw.member_id', $1, true), set_config('claw.member_email', $2, true)";
53
+
54
+ /**
55
+ * Run `work(query)` in one transaction on one connection, after telling
56
+ * the database who is asking. A default, a trigger or an action function
57
+ * reads it the same way in a server app and a browser one (where the
58
+ * platform's data door sets the same two):
59
+ *
60
+ * nullif(current_setting('claw.member_email', true), '')
61
+ *
62
+ * It is a label, not a permission: nothing in the platform trusts it, and
63
+ * a column the page must not choose (`created_by`) is set by a trigger
64
+ * that reads it. Every lib/actions.js write runs here. `work` gets a
65
+ * `query(text, params)` bound to the transaction's connection; use only
66
+ * that inside it. → whatever `work` returns. A failure rolls back and
67
+ * throws on.
68
+ */
69
+ export async function transaction(work) {
70
+ const member = await requestMember();
71
+ const client = await pool.connect();
72
+ let broken;
73
+ try {
74
+ await client.query("BEGIN");
75
+ await client.query(MEMBER_SETTINGS, [member.id, member.email]);
76
+ const result = await work((text, params) => client.query(text, params));
77
+ await client.query("COMMIT");
78
+ return result;
79
+ } catch (error) {
80
+ try {
81
+ await client.query("ROLLBACK");
82
+ } catch (lost) {
83
+ // The connection itself failed: the pool must not lend it again.
84
+ broken = lost;
85
+ }
86
+ throw error;
87
+ } finally {
88
+ client.release(broken);
89
+ }
90
+ }
91
+
29
92
  /** The artifact this app belongs to. product_run sets it from the folder
30
- * the app runs in; every activity row keys on it, and so does the
31
- * Activity tab the OS draws on the app window. */
93
+ * the app runs in; app/layout.jsx reads it to turn examples off, and an
94
+ * app may use it to name itself. The activity lines no longer need it:
95
+ * claw_app.activity fills in the app itself (logActivity, below). */
32
96
  export function artifactId() {
33
97
  return process.env.CLAW_ARTIFACT_ID || null;
34
98
  }
35
99
 
36
100
  const IDENT = /^[a-zA-Z_][a-zA-Z0-9_]*$/;
37
101
 
38
- /** Quote a table or column name for SQL: "investors" → "investors",
39
- * "public.investors" → "public"."investors". Anything else throws, so a
40
- * typo in a surface config can never reach the database as SQL. */
102
+ /** Quote a table or column name for SQL: "investors" → "investors".
103
+ * An app names its tables bare: its own schema is first on its role's
104
+ * search_path. Two parts still pass ("public.investors" →
105
+ * "public"."investors"), for an app from before 0.7.0 whose tables are
106
+ * in public and whose code says so. Anything else throws, so a typo in a
107
+ * surface config can never reach the database as SQL. */
41
108
  export function ident(name) {
42
109
  const parts = String(name ?? "").split(".");
43
110
  if (!parts.length || parts.length > 2 || !parts.every((p) => IDENT.test(p))) {
@@ -46,13 +113,26 @@ export function ident(name) {
46
113
  return parts.map((p) => `"${p}"`).join(".");
47
114
  }
48
115
 
116
+ // Postgres's "relation does not exist": no claw_app.activity to write to.
117
+ const NO_SUCH_RELATION = "42P01";
118
+
49
119
  /**
50
120
  * Record what happened, for the Activity tab the OS draws on the app
51
- * window (and any surface that reads the table with loadActivity). Every
52
- * lib/actions.js write calls this; the artifact's agent writes the same
53
- * table with the sql verb.
121
+ * window (and any surface that reads the lines with loadActivity). Every
122
+ * lib/actions.js write calls this; the app's agent writes the same lines
123
+ * with the sql verb.
54
124
  * Never throws — an activity line must not fail the write it describes.
55
125
  *
126
+ * The line goes into claw_app.activity: the platform's view of the
127
+ * workspace's activity table, holding only this app's lines. The view
128
+ * fills in `artifact_id` (the app whose role the connection runs as) and
129
+ * `id`, so this names neither: the app needs no CLAW_ARTIFACT_ID, and it
130
+ * cannot write a line as another app. Up to 0.6.x it inserted into
131
+ * public.artifact_activity with CLAW_ARTIFACT_ID; an app on its own schema
132
+ * can reach nothing in public. Outside Makersclaw, or on a platform from
133
+ * before the view, there is no claw_app.activity: the line is not
134
+ * recorded, and this returns false, as on any other failure.
135
+ *
56
136
  * action found, drafted, sent, replied, skipped, edited, moved, added…
57
137
  * surface the surface key the row belongs to (the OS tab shows its
58
138
  * label; a link into the app is /s/<surface>?row=<row_ref>)
@@ -62,17 +142,11 @@ export function ident(name) {
62
142
  * by 'agent' | 'user:<email>' | 'run:<id>'
63
143
  */
64
144
  export async function logActivity({ action, surface, row_ref, detail, data, by }) {
65
- const id = artifactId();
66
- if (!id) {
67
- console.warn(`[activity] not recorded (no CLAW_ARTIFACT_ID): ${action} ${detail ?? ""}`);
68
- return false;
69
- }
70
145
  try {
71
146
  await query(
72
- `INSERT INTO public.artifact_activity (artifact_id, action, surface, row_ref, detail, data, by)
73
- VALUES ($1, $2, $3, $4, $5, $6::jsonb, $7)`,
147
+ `INSERT INTO claw_app.activity (action, surface, row_ref, detail, data, by)
148
+ VALUES ($1, $2, $3, $4, $5::jsonb, $6)`,
74
149
  [
75
- id,
76
150
  String(action || "edited"),
77
151
  surface ?? null,
78
152
  row_ref == null ? null : String(row_ref),
@@ -83,7 +157,13 @@ export async function logActivity({ action, surface, row_ref, detail, data, by }
83
157
  );
84
158
  return true;
85
159
  } catch (error) {
86
- console.error(`[activity] not recorded: ${String(error)}`);
160
+ // No view is where the app runs, not a fault in it: a warning, as the
161
+ // missing CLAW_ARTIFACT_ID was up to 0.6.x. Anything else is an error.
162
+ if (error?.code === NO_SUCH_RELATION) {
163
+ console.warn(`[activity] not recorded (no claw_app.activity here): ${action} ${detail ?? ""}`);
164
+ } else {
165
+ console.error(`[activity] not recorded: ${String(error)}`);
166
+ }
87
167
  return false;
88
168
  }
89
169
  }