@lotics/cli 0.44.1 → 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 = "2.0.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) {
@@ -30322,14 +30357,34 @@ export default defineConfig({
30322
30357
  dedupe: ["react", "react-dom", "react-native-web"],
30323
30358
  },
30324
30359
  optimizeDeps: {
30325
- // Force-prebundle react-native-web: it does \`import normalizeColor from
30326
- // "@react-native/normalize-colors"\` (a nested CJS module). Un-prebundled,
30327
- // Vite's dev server serves that as ESM with no default export \u2192 "does not
30328
- // provide an export named 'default'" and a blank iframe the moment a
30329
- // colour-touching RN-Web component (DatePicker, Picker) mounts under
30330
- // \`lotics app dev\`. Prebundling folds its CJS deps into one interop'd chunk.
30331
- // The rollup build handles the interop on its own, so this is dev-only.
30332
- include: ["react-native-web"],
30360
+ // \`recharts\` (used by @lotics/ui chart_* + sparkline) imports
30361
+ // \`es-toolkit/compat/get\` as a default-export CJS module. Vite's dev
30362
+ // server treats \`compat/*\` as ESM and won't synthesize a default,
30363
+ // so \`import get from "es-toolkit/compat/get"\` fails to resolve.
30364
+ // Pre-bundling forces Vite to convert it to an ESM shim with a default
30365
+ // export. Production build (rollup) handles it correctly without this.
30366
+ //
30367
+ // react-native-web itself is force-prebundled (folds its core CJS deps like
30368
+ // @react-native/normalize-colors into one interop'd chunk), and the deep
30369
+ // subpaths it reaches via *default* imports (e.g. @react-native-picker's web
30370
+ // <select> build) are each pre-bundled into an ESM shim \u2014 Vite's dev optimizer
30371
+ // otherwise serves them without a synthesized default export ("does not
30372
+ // provide an export named 'default'"), blanking the iframe. (Production/rollup
30373
+ // resolves the interop already, so this is dev-only.)
30374
+ include: [
30375
+ "recharts", "es-toolkit", "es-toolkit/compat",
30376
+ "react-native-web", "@react-native/normalize-colors",
30377
+ "inline-style-prefixer/lib/createPrefixer",
30378
+ "inline-style-prefixer/lib/plugins/crossFade",
30379
+ "inline-style-prefixer/lib/plugins/imageSet",
30380
+ "inline-style-prefixer/lib/plugins/logical",
30381
+ "inline-style-prefixer/lib/plugins/position",
30382
+ "inline-style-prefixer/lib/plugins/sizing",
30383
+ "inline-style-prefixer/lib/plugins/transition",
30384
+ "postcss-value-parser", "fbjs/lib/invariant", "fbjs/lib/warning",
30385
+ "styleq", "styleq/transform-localize-style",
30386
+ "react", "react-dom", "react-dom/client", "nullthrows",
30387
+ ],
30333
30388
  // The dep optimizer pre-bundles deps with a SEPARATE esbuild pass that
30334
30389
  // top-level \`define\` doesn't always reach, so a pre-bundled RN dep can
30335
30390
  // still hit \`__DEV__ is not defined\` under \`lotics app dev\`. Define it
@@ -30671,7 +30726,12 @@ var SUPPORTED_OPS = /* @__PURE__ */ new Set([
30671
30726
  "members",
30672
30727
  "context",
30673
30728
  "upload_url",
30674
- "upload_complete"
30729
+ "upload_complete",
30730
+ "comments.list",
30731
+ "comments.create",
30732
+ "comments.update",
30733
+ "comments.delete",
30734
+ "comments.counts"
30675
30735
  ]);
30676
30736
  async function dispatchRpc(client, body, opts) {
30677
30737
  if (!body || typeof body.app_id !== "string" || typeof body.op !== "string") {
@@ -30738,6 +30798,54 @@ async function dispatchRpc(client, body, opts) {
30738
30798
  filename: p.filename
30739
30799
  });
30740
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
+ }
30741
30849
  default: {
30742
30850
  throw new Error(`Unhandled RPC op: ${body.op}`);
30743
30851
  }
@@ -39228,7 +39336,7 @@ var drawingParser = new XMLParser({
39228
39336
  processEntities: false,
39229
39337
  isArray: (tagName) => tagName === "xdr:twoCellAnchor" || tagName === "xdr:oneCellAnchor" || tagName === "Relationship"
39230
39338
  });
39231
- function parseSheet(sheetXml, sheetRelsXml, styles, sharedStrings, theme, indexedColors, zipEntries, date1904) {
39339
+ function parseSheet(sheetXml, sheetRelsXml, styles, sharedStrings, theme, indexedColors, zipEntries, date1904, maxRows = MAX_ROWS_PER_SHEET) {
39232
39340
  const doc = xmlParser4.parse(sheetXml);
39233
39341
  const worksheet = doc?.["worksheet"];
39234
39342
  if (!worksheet) return emptySheet("Sheet");
@@ -39246,7 +39354,7 @@ function parseSheet(sheetXml, sheetRelsXml, styles, sharedStrings, theme, indexe
39246
39354
  maxCol,
39247
39355
  maxContentWidth,
39248
39356
  rowOutlineLevels
39249
- } = parseSheetData(worksheet, styles, sharedStrings, hyperlinkMap, defaultRowHeight, date1904);
39357
+ } = parseSheetData(worksheet, styles, sharedStrings, hyperlinkMap, defaultRowHeight, date1904, maxRows);
39250
39358
  const colCount = Math.max(parsedCols.length > 0 ? parsedCols[parsedCols.length - 1].max : 0, maxCol);
39251
39359
  const columns = buildColumns(parsedCols, colHidden, maxContentWidth, colCount, defaultColWidth);
39252
39360
  const mergedCells = parseMergedCells(worksheet);
@@ -39428,7 +39536,7 @@ function buildColumns(colDefs, colHidden, maxContentWidth, colCount, _defaultCol
39428
39536
  }
39429
39537
  return columns;
39430
39538
  }
39431
- function parseSheetData(worksheet, styles, sharedStrings, hyperlinkMap, defaultRowHeight, date1904) {
39539
+ function parseSheetData(worksheet, styles, sharedStrings, hyperlinkMap, defaultRowHeight, date1904, maxRows) {
39432
39540
  const sheetData = worksheet["sheetData"];
39433
39541
  if (!sheetData) {
39434
39542
  return {
@@ -39464,9 +39572,9 @@ function parseSheetData(worksheet, styles, sharedStrings, hyperlinkMap, defaultR
39464
39572
  if (outlineLevel > 0) {
39465
39573
  rowOutlineLevels.set(rowIndex, outlineLevel);
39466
39574
  }
39467
- if (rowEl["@_hidden"] === "1" || rowEl["@_hidden"] === "true") continue;
39575
+ const rowHidden = rowEl["@_hidden"] === "1" || rowEl["@_hidden"] === "true";
39468
39576
  rowCount++;
39469
- if (rowCount > MAX_ROWS_PER_SHEET) continue;
39577
+ if (rowCount > maxRows) continue;
39470
39578
  const height = rowEl["@_ht"] ? parseFloat(rowEl["@_ht"]) : defaultRowHeight;
39471
39579
  const cellArr = rowEl["c"];
39472
39580
  const cells = [];
@@ -39476,7 +39584,7 @@ function parseSheetData(worksheet, styles, sharedStrings, hyperlinkMap, defaultR
39476
39584
  if (parsed) {
39477
39585
  cells.push(parsed);
39478
39586
  if (parsed.column > maxCol) maxCol = parsed.column;
39479
- if (rowCount <= 200) {
39587
+ if (rowCount <= 200 && !rowHidden) {
39480
39588
  const currentMax = maxContentWidth.get(parsed.column) ?? 0;
39481
39589
  if (parsed.value.length > currentMax) {
39482
39590
  maxContentWidth.set(parsed.column, parsed.value.length);
@@ -39489,13 +39597,13 @@ function parseSheetData(worksheet, styles, sharedStrings, hyperlinkMap, defaultR
39489
39597
  index: rowIndex,
39490
39598
  height,
39491
39599
  cells,
39492
- hidden: false
39600
+ hidden: rowHidden
39493
39601
  });
39494
39602
  }
39495
39603
  return {
39496
39604
  rows,
39497
39605
  totalRowCount,
39498
- truncated: rowCount > MAX_ROWS_PER_SHEET,
39606
+ truncated: rowCount > maxRows,
39499
39607
  maxCol,
39500
39608
  maxContentWidth,
39501
39609
  rowOutlineLevels
@@ -39537,10 +39645,12 @@ function parseCell(cellEl, styles, sharedStrings, hyperlinkMap, date1904) {
39537
39645
  if (fEl !== void 0 && fEl !== null) {
39538
39646
  if (typeof fEl === "object") {
39539
39647
  const fObj = fEl;
39540
- formula = fObj["#text"];
39648
+ const text = fObj["#text"];
39649
+ formula = text == null ? void 0 : String(text);
39541
39650
  if (fObj["@_t"] === "array") {
39542
39651
  isArrayFormula = true;
39543
- arrayRange = fObj["@_ref"];
39652
+ const ref2 = fObj["@_ref"];
39653
+ arrayRange = ref2 == null ? void 0 : String(ref2);
39544
39654
  if (formula?.startsWith("{") && formula.endsWith("}")) {
39545
39655
  formula = formula.slice(1, -1);
39546
39656
  }
@@ -41109,11 +41219,11 @@ function ensureVmlArray(val) {
41109
41219
  }
41110
41220
 
41111
41221
  // ../xlsx/src/excel_parser.ts
41112
- function parseExcelBuffer(arrayBuffer) {
41222
+ function parseExcelBuffer(arrayBuffer, options) {
41113
41223
  const zip = unzipXlsx(arrayBuffer);
41114
- return parseExcelFromZip(zip);
41224
+ return parseExcelFromZip(zip, options);
41115
41225
  }
41116
- function parseExcelFromZip(zip) {
41226
+ function parseExcelFromZip(zip, options) {
41117
41227
  const themeEntry = findEntry(zip, "xl/theme/theme1.xml");
41118
41228
  const theme = themeEntry ? parseTheme(decodeUtf8(themeEntry)) : { colors: defaultThemeColors(), majorFont: "Calibri", minorFont: "Calibri" };
41119
41229
  const stylesEntry = findEntry(zip, "xl/styles.xml");
@@ -41175,7 +41285,8 @@ function parseExcelFromZip(zip) {
41175
41285
  theme,
41176
41286
  styles.indexedColors,
41177
41287
  zip,
41178
- workbookInfo.date1904
41288
+ workbookInfo.date1904,
41289
+ options?.maxRowsPerSheet
41179
41290
  );
41180
41291
  parsed.name = sheetInfo.name;
41181
41292
  if (isHidden) parsed.hidden = true;
@@ -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 = "2.0.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 = "2.0.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
@@ -184,14 +184,34 @@ export default defineConfig({
184
184
  dedupe: ["react", "react-dom", "react-native-web"],
185
185
  },
186
186
  optimizeDeps: {
187
- // Force-prebundle react-native-web: it does \`import normalizeColor from
188
- // "@react-native/normalize-colors"\` (a nested CJS module). Un-prebundled,
189
- // Vite's dev server serves that as ESM with no default export → "does not
190
- // provide an export named 'default'" and a blank iframe the moment a
191
- // colour-touching RN-Web component (DatePicker, Picker) mounts under
192
- // \`lotics app dev\`. Prebundling folds its CJS deps into one interop'd chunk.
193
- // The rollup build handles the interop on its own, so this is dev-only.
194
- include: ["react-native-web"],
187
+ // \`recharts\` (used by @lotics/ui chart_* + sparkline) imports
188
+ // \`es-toolkit/compat/get\` as a default-export CJS module. Vite's dev
189
+ // server treats \`compat/*\` as ESM and won't synthesize a default,
190
+ // so \`import get from "es-toolkit/compat/get"\` fails to resolve.
191
+ // Pre-bundling forces Vite to convert it to an ESM shim with a default
192
+ // export. Production build (rollup) handles it correctly without this.
193
+ //
194
+ // react-native-web itself is force-prebundled (folds its core CJS deps like
195
+ // @react-native/normalize-colors into one interop'd chunk), and the deep
196
+ // subpaths it reaches via *default* imports (e.g. @react-native-picker's web
197
+ // <select> build) are each pre-bundled into an ESM shim — Vite's dev optimizer
198
+ // otherwise serves them without a synthesized default export ("does not
199
+ // provide an export named 'default'"), blanking the iframe. (Production/rollup
200
+ // resolves the interop already, so this is dev-only.)
201
+ include: [
202
+ "recharts", "es-toolkit", "es-toolkit/compat",
203
+ "react-native-web", "@react-native/normalize-colors",
204
+ "inline-style-prefixer/lib/createPrefixer",
205
+ "inline-style-prefixer/lib/plugins/crossFade",
206
+ "inline-style-prefixer/lib/plugins/imageSet",
207
+ "inline-style-prefixer/lib/plugins/logical",
208
+ "inline-style-prefixer/lib/plugins/position",
209
+ "inline-style-prefixer/lib/plugins/sizing",
210
+ "inline-style-prefixer/lib/plugins/transition",
211
+ "postcss-value-parser", "fbjs/lib/invariant", "fbjs/lib/warning",
212
+ "styleq", "styleq/transform-localize-style",
213
+ "react", "react-dom", "react-dom/client", "nullthrows",
214
+ ],
195
215
  // The dep optimizer pre-bundles deps with a SEPARATE esbuild pass that
196
216
  // top-level \`define\` doesn't always reach, so a pre-bundled RN dep can
197
217
  // still hit \`__DEV__ is not defined\` under \`lotics app dev\`. Define it
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lotics/cli",
3
- "version": "0.44.1",
3
+ "version": "0.45.0",
4
4
  "description": "Lotics SDK and CLI for AI agents",
5
5
  "type": "module",
6
6
  "bin": {