@lotics/cli 0.44.2 → 0.45.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.
package/dist/client.d.ts CHANGED
@@ -218,6 +218,17 @@ export declare class LoticsClient {
218
218
  thumbnail_url?: string;
219
219
  };
220
220
  }>;
221
+ appGetRecordComments(app_id: string, record_id: string): Promise<unknown[]>;
222
+ appCreateRecordComment(app_id: string, record_id: string, body: {
223
+ content: string;
224
+ file_ids?: string[];
225
+ }): Promise<unknown>;
226
+ appUpdateRecordComment(app_id: string, record_id: string, comment_id: string, body: {
227
+ content: string;
228
+ files?: unknown[];
229
+ }): Promise<unknown>;
230
+ appDeleteRecordComment(app_id: string, record_id: string, comment_id: string): Promise<void>;
231
+ appGetTableCommentCounts(app_id: string, table_id: string): Promise<Record<string, number>>;
221
232
  deployAppVersion(args: {
222
233
  app_id: string;
223
234
  source_archive: Buffer;
package/dist/client.js CHANGED
@@ -208,6 +208,24 @@ export class LoticsClient {
208
208
  async appCompleteFileUpload(app_id, body) {
209
209
  return this.request("POST", `/v1/apps/${encodeURIComponent(app_id)}/files/complete`, body);
210
210
  }
211
+ // App-scoped record comments — the `lotics app dev` loop forwards the iframe's
212
+ // `comments.*` ops to these (production routes them through the iframe host).
213
+ // App authority + tenant floor are enforced server-side; these are thin.
214
+ async appGetRecordComments(app_id, record_id) {
215
+ return this.request("GET", `/v1/apps/${encodeURIComponent(app_id)}/records/${encodeURIComponent(record_id)}/comments`);
216
+ }
217
+ async appCreateRecordComment(app_id, record_id, body) {
218
+ return this.request("POST", `/v1/apps/${encodeURIComponent(app_id)}/records/${encodeURIComponent(record_id)}/comments`, body);
219
+ }
220
+ async appUpdateRecordComment(app_id, record_id, comment_id, body) {
221
+ return this.request("PATCH", `/v1/apps/${encodeURIComponent(app_id)}/records/${encodeURIComponent(record_id)}/comments/${encodeURIComponent(comment_id)}`, body);
222
+ }
223
+ async appDeleteRecordComment(app_id, record_id, comment_id) {
224
+ await this.request("DELETE", `/v1/apps/${encodeURIComponent(app_id)}/records/${encodeURIComponent(record_id)}/comments/${encodeURIComponent(comment_id)}`);
225
+ }
226
+ async appGetTableCommentCounts(app_id, table_id) {
227
+ return this.request("GET", `/v1/apps/${encodeURIComponent(app_id)}/tables/${encodeURIComponent(table_id)}/comment-counts`);
228
+ }
211
229
  async deployAppVersion(args) {
212
230
  const formData = new FormData();
213
231
  // Wrap Buffers as Uint8Array views so the Blob constructor accepts them
@@ -6,7 +6,7 @@
6
6
  * { message }. Same shape as the production iframe-host's error path.
7
7
  */
8
8
  import { LoticsClient } from "../client.js";
9
- export type RpcOp = "query" | "workflow" | "members" | "context" | "upload_url" | "upload_complete";
9
+ export type RpcOp = "query" | "workflow" | "members" | "context" | "upload_url" | "upload_complete" | "comments.list" | "comments.create" | "comments.update" | "comments.delete" | "comments.counts";
10
10
  export interface RpcRequest {
11
11
  app_id: string;
12
12
  op: RpcOp;
@@ -12,6 +12,11 @@ const SUPPORTED_OPS = new Set([
12
12
  "context",
13
13
  "upload_url",
14
14
  "upload_complete",
15
+ "comments.list",
16
+ "comments.create",
17
+ "comments.update",
18
+ "comments.delete",
19
+ "comments.counts",
15
20
  ]);
16
21
  export async function dispatchRpc(client, body, opts) {
17
22
  if (!body || typeof body.app_id !== "string" || typeof body.op !== "string") {
@@ -86,6 +91,57 @@ export async function dispatchRpc(client, body, opts) {
86
91
  filename: p.filename,
87
92
  });
88
93
  }
94
+ // Comment ops mirror the production iframe-host's `handleCommentRpc` return
95
+ // shapes so the SDK hooks behave identically in dev. App authority + the
96
+ // `comments` capability + tenant floor are enforced server-side.
97
+ case "comments.list": {
98
+ const p = body.payload;
99
+ if (!p || typeof p.record_id !== "string") {
100
+ throw new Error("comments.list payload must include record_id");
101
+ }
102
+ const comments = await client.appGetRecordComments(body.app_id, p.record_id);
103
+ return { comments };
104
+ }
105
+ case "comments.create": {
106
+ const p = body.payload;
107
+ if (!p || typeof p.record_id !== "string" || typeof p.content !== "string") {
108
+ throw new Error("comments.create payload must include record_id and content");
109
+ }
110
+ const created = await client.appCreateRecordComment(body.app_id, p.record_id, {
111
+ content: p.content,
112
+ file_ids: p.file_ids,
113
+ });
114
+ return { comments: created ? [created] : [] };
115
+ }
116
+ case "comments.update": {
117
+ const p = body.payload;
118
+ if (!p ||
119
+ typeof p.record_id !== "string" ||
120
+ typeof p.comment_id !== "string" ||
121
+ typeof p.content !== "string") {
122
+ throw new Error("comments.update payload must include record_id, comment_id, content");
123
+ }
124
+ const comment = await client.appUpdateRecordComment(body.app_id, p.record_id, p.comment_id, {
125
+ content: p.content,
126
+ files: p.files,
127
+ });
128
+ return { comment };
129
+ }
130
+ case "comments.delete": {
131
+ const p = body.payload;
132
+ if (!p || typeof p.record_id !== "string" || typeof p.comment_id !== "string") {
133
+ throw new Error("comments.delete payload must include record_id and comment_id");
134
+ }
135
+ await client.appDeleteRecordComment(body.app_id, p.record_id, p.comment_id);
136
+ return undefined;
137
+ }
138
+ case "comments.counts": {
139
+ const p = body.payload;
140
+ if (!p || typeof p.table_id !== "string") {
141
+ throw new Error("comments.counts payload must include table_id");
142
+ }
143
+ return client.appGetTableCommentCounts(body.app_id, p.table_id);
144
+ }
89
145
  default: {
90
146
  // Unreachable — SUPPORTED_OPS gates above.
91
147
  throw new Error(`Unhandled RPC op: ${body.op}`);
@@ -26,3 +26,33 @@ describe("dispatchRpc — context op", () => {
26
26
  await expect(dispatchRpc(client, { app_id: "app_x", op: "bogus", payload: {} })).rejects.toThrow(/Unknown RPC op/);
27
27
  });
28
28
  });
29
+ describe("dispatchRpc — comment ops", () => {
30
+ it("comments.list forwards to appGetRecordComments, wrapped as { comments }", async () => {
31
+ const comments = [{ id: "cmt_1", content: "hi" }];
32
+ const client = mockClient({ appGetRecordComments: async () => comments });
33
+ const result = await dispatchRpc(client, {
34
+ app_id: "app_x",
35
+ op: "comments.list",
36
+ payload: { record_id: "rec_1" },
37
+ });
38
+ expect(result).toEqual({ comments });
39
+ });
40
+ it("comments.counts forwards to appGetTableCommentCounts (counts, no content)", async () => {
41
+ const counts = { rec_1: 2, rec_2: 1 };
42
+ const client = mockClient({ appGetTableCommentCounts: async () => counts });
43
+ const result = await dispatchRpc(client, {
44
+ app_id: "app_x",
45
+ op: "comments.counts",
46
+ payload: { table_id: "tbl_1" },
47
+ });
48
+ expect(result).toEqual(counts);
49
+ });
50
+ it("requires the right payload per comment op", async () => {
51
+ const client = mockClient({
52
+ appGetRecordComments: async () => [],
53
+ appGetTableCommentCounts: async () => ({}),
54
+ });
55
+ await expect(dispatchRpc(client, { app_id: "app_x", op: "comments.list", payload: {} })).rejects.toThrow(/record_id/);
56
+ await expect(dispatchRpc(client, { app_id: "app_x", op: "comments.counts", payload: {} })).rejects.toThrow(/table_id/);
57
+ });
58
+ });
package/dist/src/cli.js CHANGED
@@ -29801,6 +29801,41 @@ var LoticsClient = class {
29801
29801
  body
29802
29802
  );
29803
29803
  }
29804
+ // App-scoped record comments — the `lotics app dev` loop forwards the iframe's
29805
+ // `comments.*` ops to these (production routes them through the iframe host).
29806
+ // App authority + tenant floor are enforced server-side; these are thin.
29807
+ async appGetRecordComments(app_id, record_id) {
29808
+ return this.request(
29809
+ "GET",
29810
+ `/v1/apps/${encodeURIComponent(app_id)}/records/${encodeURIComponent(record_id)}/comments`
29811
+ );
29812
+ }
29813
+ async appCreateRecordComment(app_id, record_id, body) {
29814
+ return this.request(
29815
+ "POST",
29816
+ `/v1/apps/${encodeURIComponent(app_id)}/records/${encodeURIComponent(record_id)}/comments`,
29817
+ body
29818
+ );
29819
+ }
29820
+ async appUpdateRecordComment(app_id, record_id, comment_id, body) {
29821
+ return this.request(
29822
+ "PATCH",
29823
+ `/v1/apps/${encodeURIComponent(app_id)}/records/${encodeURIComponent(record_id)}/comments/${encodeURIComponent(comment_id)}`,
29824
+ body
29825
+ );
29826
+ }
29827
+ async appDeleteRecordComment(app_id, record_id, comment_id) {
29828
+ await this.request(
29829
+ "DELETE",
29830
+ `/v1/apps/${encodeURIComponent(app_id)}/records/${encodeURIComponent(record_id)}/comments/${encodeURIComponent(comment_id)}`
29831
+ );
29832
+ }
29833
+ async appGetTableCommentCounts(app_id, table_id) {
29834
+ return this.request(
29835
+ "GET",
29836
+ `/v1/apps/${encodeURIComponent(app_id)}/tables/${encodeURIComponent(table_id)}/comment-counts`
29837
+ );
29838
+ }
29804
29839
  async deployAppVersion(args) {
29805
29840
  const formData = new FormData();
29806
29841
  formData.append(
@@ -30179,7 +30214,7 @@ import { spawn as spawn2 } from "node:child_process";
30179
30214
  import { tmpdir } from "node:os";
30180
30215
 
30181
30216
  // src/starter_template.ts
30182
- var STARTER_FALLBACK_UI_VERSION = "1.8.0";
30217
+ var STARTER_FALLBACK_UI_VERSION = "2.3.0";
30183
30218
  var STARTER_FALLBACK_SDK_VERSION = "0.11.0";
30184
30219
  var STARTER_REACT_NATIVE_VERSION = "0.85.3";
30185
30220
  function buildStarterTemplate(args) {
@@ -30691,7 +30726,12 @@ var SUPPORTED_OPS = /* @__PURE__ */ new Set([
30691
30726
  "members",
30692
30727
  "context",
30693
30728
  "upload_url",
30694
- "upload_complete"
30729
+ "upload_complete",
30730
+ "comments.list",
30731
+ "comments.create",
30732
+ "comments.update",
30733
+ "comments.delete",
30734
+ "comments.counts"
30695
30735
  ]);
30696
30736
  async function dispatchRpc(client, body, opts) {
30697
30737
  if (!body || typeof body.app_id !== "string" || typeof body.op !== "string") {
@@ -30758,6 +30798,54 @@ async function dispatchRpc(client, body, opts) {
30758
30798
  filename: p.filename
30759
30799
  });
30760
30800
  }
30801
+ // Comment ops mirror the production iframe-host's `handleCommentRpc` return
30802
+ // shapes so the SDK hooks behave identically in dev. App authority + the
30803
+ // `comments` capability + tenant floor are enforced server-side.
30804
+ case "comments.list": {
30805
+ const p = body.payload;
30806
+ if (!p || typeof p.record_id !== "string") {
30807
+ throw new Error("comments.list payload must include record_id");
30808
+ }
30809
+ const comments = await client.appGetRecordComments(body.app_id, p.record_id);
30810
+ return { comments };
30811
+ }
30812
+ case "comments.create": {
30813
+ const p = body.payload;
30814
+ if (!p || typeof p.record_id !== "string" || typeof p.content !== "string") {
30815
+ throw new Error("comments.create payload must include record_id and content");
30816
+ }
30817
+ const created = await client.appCreateRecordComment(body.app_id, p.record_id, {
30818
+ content: p.content,
30819
+ file_ids: p.file_ids
30820
+ });
30821
+ return { comments: created ? [created] : [] };
30822
+ }
30823
+ case "comments.update": {
30824
+ const p = body.payload;
30825
+ if (!p || typeof p.record_id !== "string" || typeof p.comment_id !== "string" || typeof p.content !== "string") {
30826
+ throw new Error("comments.update payload must include record_id, comment_id, content");
30827
+ }
30828
+ const comment = await client.appUpdateRecordComment(body.app_id, p.record_id, p.comment_id, {
30829
+ content: p.content,
30830
+ files: p.files
30831
+ });
30832
+ return { comment };
30833
+ }
30834
+ case "comments.delete": {
30835
+ const p = body.payload;
30836
+ if (!p || typeof p.record_id !== "string" || typeof p.comment_id !== "string") {
30837
+ throw new Error("comments.delete payload must include record_id and comment_id");
30838
+ }
30839
+ await client.appDeleteRecordComment(body.app_id, p.record_id, p.comment_id);
30840
+ return void 0;
30841
+ }
30842
+ case "comments.counts": {
30843
+ const p = body.payload;
30844
+ if (!p || typeof p.table_id !== "string") {
30845
+ throw new Error("comments.counts payload must include table_id");
30846
+ }
30847
+ return client.appGetTableCommentCounts(body.app_id, p.table_id);
30848
+ }
30761
30849
  default: {
30762
30850
  throw new Error(`Unhandled RPC op: ${body.op}`);
30763
30851
  }
@@ -39484,7 +39572,7 @@ function parseSheetData(worksheet, styles, sharedStrings, hyperlinkMap, defaultR
39484
39572
  if (outlineLevel > 0) {
39485
39573
  rowOutlineLevels.set(rowIndex, outlineLevel);
39486
39574
  }
39487
- if (rowEl["@_hidden"] === "1" || rowEl["@_hidden"] === "true") continue;
39575
+ const rowHidden = rowEl["@_hidden"] === "1" || rowEl["@_hidden"] === "true";
39488
39576
  rowCount++;
39489
39577
  if (rowCount > maxRows) continue;
39490
39578
  const height = rowEl["@_ht"] ? parseFloat(rowEl["@_ht"]) : defaultRowHeight;
@@ -39496,7 +39584,7 @@ function parseSheetData(worksheet, styles, sharedStrings, hyperlinkMap, defaultR
39496
39584
  if (parsed) {
39497
39585
  cells.push(parsed);
39498
39586
  if (parsed.column > maxCol) maxCol = parsed.column;
39499
- if (rowCount <= 200) {
39587
+ if (rowCount <= 200 && !rowHidden) {
39500
39588
  const currentMax = maxContentWidth.get(parsed.column) ?? 0;
39501
39589
  if (parsed.value.length > currentMax) {
39502
39590
  maxContentWidth.set(parsed.column, parsed.value.length);
@@ -39509,7 +39597,7 @@ function parseSheetData(worksheet, styles, sharedStrings, hyperlinkMap, defaultR
39509
39597
  index: rowIndex,
39510
39598
  height,
39511
39599
  cells,
39512
- hidden: false
39600
+ hidden: rowHidden
39513
39601
  });
39514
39602
  }
39515
39603
  return {
@@ -39,7 +39,7 @@ export interface StarterFile {
39
39
  * scaffolds resolve the live version via `fetchLatestNpmVersion` and only
40
40
  * fall back here when the lookup fails.
41
41
  */
42
- export declare const STARTER_FALLBACK_UI_VERSION = "1.8.0";
42
+ export declare const STARTER_FALLBACK_UI_VERSION = "2.3.0";
43
43
  export declare const STARTER_FALLBACK_SDK_VERSION = "0.11.0";
44
44
  /**
45
45
  * react-native pin for scaffolded apps. Matches the monorepo frontend's pin so
@@ -35,7 +35,7 @@
35
35
  * scaffolds resolve the live version via `fetchLatestNpmVersion` and only
36
36
  * fall back here when the lookup fails.
37
37
  */
38
- export const STARTER_FALLBACK_UI_VERSION = "1.8.0";
38
+ export const STARTER_FALLBACK_UI_VERSION = "2.3.0";
39
39
  export const STARTER_FALLBACK_SDK_VERSION = "0.11.0";
40
40
  /**
41
41
  * react-native pin for scaffolded apps. Matches the monorepo frontend's pin so
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lotics/cli",
3
- "version": "0.44.2",
3
+ "version": "0.45.0",
4
4
  "description": "Lotics SDK and CLI for AI agents",
5
5
  "type": "module",
6
6
  "bin": {