@lotics/cli 0.143.0 → 0.143.3

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/src/cli.js CHANGED
@@ -16769,7 +16769,7 @@ var require_operators = __commonJS({
16769
16769
  }
16770
16770
  };
16771
16771
  var type2Number = { "boolean": 3, "string": 2, "number": 1 };
16772
- var Infix = {
16772
+ var Infix2 = {
16773
16773
  compareOp: (value1, infix, value2, isArray1, isArray22) => {
16774
16774
  if (value1 == null) value1 = 0;
16775
16775
  if (value2 == null) value2 = 0;
@@ -16860,7 +16860,7 @@ var require_operators = __commonJS({
16860
16860
  module2.exports = {
16861
16861
  Prefix,
16862
16862
  Postfix,
16863
- Infix,
16863
+ Infix: Infix2,
16864
16864
  Operators: {
16865
16865
  compareOp: ["<", ">", "=", "<>", "<=", ">="],
16866
16866
  concatOp: ["&"],
@@ -16875,7 +16875,7 @@ var require_math = __commonJS({
16875
16875
  "../../node_modules/fast-formula-parser/formulas/functions/math.js"(exports2, module2) {
16876
16876
  var FormulaError = require_error();
16877
16877
  var { FormulaHelpers, Types, Factorials, Criteria } = require_helpers();
16878
- var { Infix } = require_operators();
16878
+ var { Infix: Infix2 } = require_operators();
16879
16879
  var H = FormulaHelpers;
16880
16880
  var f = [];
16881
16881
  var fd2 = [];
@@ -17398,7 +17398,7 @@ var require_math = __commonJS({
17398
17398
  if (criteria.match === criteria.value.test(value2)) {
17399
17399
  sum2 += valueToAdd;
17400
17400
  }
17401
- } else if (Infix.compareOp(value2, criteria.op, criteria.value, Array.isArray(value2), isCriteriaArray)) {
17401
+ } else if (Infix2.compareOp(value2, criteria.op, criteria.value, Array.isArray(value2), isCriteriaArray)) {
17402
17402
  sum2 += valueToAdd;
17403
17403
  }
17404
17404
  });
@@ -23678,7 +23678,7 @@ var require_statistical = __commonJS({
23678
23678
  "../../node_modules/fast-formula-parser/formulas/functions/statistical.js"(exports2, module2) {
23679
23679
  var FormulaError = require_error();
23680
23680
  var { FormulaHelpers, Types, Criteria, Address } = require_helpers();
23681
- var { Infix } = require_operators();
23681
+ var { Infix: Infix2 } = require_operators();
23682
23682
  var H = FormulaHelpers;
23683
23683
  var { DistributionFunctions } = require_distribution();
23684
23684
  var StatisticalFunctions = {
@@ -23740,7 +23740,7 @@ var require_statistical = __commonJS({
23740
23740
  sum2 += valueToAdd;
23741
23741
  cnt++;
23742
23742
  }
23743
- } else if (Infix.compareOp(value2, criteria.op, criteria.value, Array.isArray(value2), isCriteriaArray)) {
23743
+ } else if (Infix2.compareOp(value2, criteria.op, criteria.value, Array.isArray(value2), isCriteriaArray)) {
23744
23744
  sum2 += valueToAdd;
23745
23745
  cnt++;
23746
23746
  }
@@ -23779,7 +23779,7 @@ var require_statistical = __commonJS({
23779
23779
  if (criteria.op === "wc") {
23780
23780
  if (criteria.match === criteria.value.test(value2))
23781
23781
  cnt++;
23782
- } else if (Infix.compareOp(value2, criteria.op, criteria.value, Array.isArray(value2), isCriteriaArray)) {
23782
+ } else if (Infix2.compareOp(value2, criteria.op, criteria.value, Array.isArray(value2), isCriteriaArray)) {
23783
23783
  cnt++;
23784
23784
  }
23785
23785
  });
@@ -33332,7 +33332,7 @@ var require_utils2 = __commonJS({
33332
33332
  "../../node_modules/fast-formula-parser/grammar/utils.js"(exports2, module2) {
33333
33333
  var FormulaError = require_error();
33334
33334
  var { Address } = require_helpers();
33335
- var { Prefix, Postfix, Infix, Operators } = require_operators();
33335
+ var { Prefix, Postfix, Infix: Infix2, Operators } = require_operators();
33336
33336
  var Collection = require_collection();
33337
33337
  var MAX_ROW2 = 1048576;
33338
33338
  var MAX_COLUMN2 = 16384;
@@ -33456,11 +33456,11 @@ var require_utils2 = __commonJS({
33456
33456
  if (this.isFormulaError(val2))
33457
33457
  return val2;
33458
33458
  if (Operators.compareOp.includes(infix))
33459
- return Infix.compareOp(val1, infix, val2, isArray1, isArray22);
33459
+ return Infix2.compareOp(val1, infix, val2, isArray1, isArray22);
33460
33460
  else if (Operators.concatOp.includes(infix))
33461
- return Infix.concatOp(val1, infix, val2, isArray1, isArray22);
33461
+ return Infix2.concatOp(val1, infix, val2, isArray1, isArray22);
33462
33462
  else if (Operators.mathOp.includes(infix))
33463
- return Infix.mathOp(val1, infix, val2, isArray1, isArray22);
33463
+ return Infix2.mathOp(val1, infix, val2, isArray1, isArray22);
33464
33464
  else
33465
33465
  throw new Error(`Unrecognized infix: ${infix}`);
33466
33466
  }
@@ -34012,7 +34012,7 @@ var require_utils3 = __commonJS({
34012
34012
  "../../node_modules/fast-formula-parser/grammar/dependency/utils.js"(exports2, module2) {
34013
34013
  var FormulaError = require_error();
34014
34014
  var { FormulaHelpers, Types, Address } = require_helpers();
34015
- var { Prefix, Postfix, Infix, Operators } = require_operators();
34015
+ var { Prefix, Postfix, Infix: Infix2, Operators } = require_operators();
34016
34016
  var Collection = require_collection();
34017
34017
  var MAX_ROW2 = 1048576;
34018
34018
  var MAX_COLUMN2 = 16384;
@@ -45731,8 +45731,8 @@ function renderDocIndex(areas) {
45731
45731
  lines.push(`
45732
45732
  ${a.pkg} ${a.version}`);
45733
45733
  }
45734
- const name2 = a.absPath.endsWith("AGENTS.md") ? `${a.area} \u2190 start here` : a.area;
45735
- lines.push(` ${name2.padEnd(24)}${a.title}`);
45734
+ const name2 = a.absPath.endsWith("AGENTS.md") ? `${a.area} \u2190 start here` : a.pkg === "@lotics/cli" && a.area === "building_an_app" ? `${a.area} \u2190 read once, first` : a.area;
45735
+ lines.push(` ${name2.length >= 24 ? `${name2} ` : name2.padEnd(24)}${a.title}`);
45736
45736
  }
45737
45737
  lines.push("\n lotics docs <area> print one, e.g. `lotics docs ai`");
45738
45738
  lines.push(" lotics docs <pkg>/<area> when two packages share a name");
@@ -46734,7 +46734,9 @@ router and render one screen directly.
46734
46734
 
46735
46735
  Run \`lotics docs\` for the SDK reference \u2014 it lists what the packages installed
46736
46736
  here actually ship, at the versions installed, so it can never point at a page
46737
- that moved. (\`lotics docs ai\`, \`lotics docs queries\`, \`lotics docs ui\`.)
46737
+ that moved. \`lotics docs building_an_app\` is the sequence, read once before
46738
+ building; the area docs (\`lotics docs ai\`, \`lotics docs queries\`,
46739
+ \`lotics docs ui\`) are its detail.
46738
46740
  `
46739
46741
  }
46740
46742
  ];
@@ -72889,7 +72891,8 @@ async function appPull(client, args) {
72889
72891
  console.error(`
72890
72892
  Ready. Next steps:`);
72891
72893
  console.error(` cd ${path9.relative(process.cwd(), targetPath) || "."}`);
72892
- console.error(` # edit src/App.tsx`);
72894
+ console.error(` lotics docs # the contracts, at the versions installed here`);
72895
+ console.error(` # edit src/App.tsx, then:`);
72893
72896
  console.error(` lotics app deploy`);
72894
72897
  }
72895
72898
  function readAppSourceText(projectDir) {
@@ -73018,6 +73021,7 @@ async function appDeploy(client, args) {
73018
73021
  }
73019
73022
  if (liveAfter) {
73020
73023
  warnIfUnbranded(liveAfter);
73024
+ warnIfUndescribed(liveAfter);
73021
73025
  warnIfUnboundAliases(liveAfter, called);
73022
73026
  warnIfInertAgentTools(liveAfter);
73023
73027
  }
@@ -73056,6 +73060,15 @@ function warnIfUnbranded(app) {
73056
73060
  Find an icon: lotics run search_app_icons '{"query":"<word>"}'`
73057
73061
  );
73058
73062
  }
73063
+ function warnIfUndescribed(app) {
73064
+ if (app.description && app.description.trim() !== "") return;
73065
+ console.error(
73066
+ `
73067
+ \u26A0 This app has no description \u2014 the capability catalog its chat agent reads every turn
73068
+ lists the aliases and says nothing about what to do with them.
73069
+ Set it: lotics run update_app '{"app_id":"${app.id}","description":"<what the app is for, and the standing job it does>"}'`
73070
+ );
73071
+ }
73059
73072
  async function readLiveQueries(client, appId) {
73060
73073
  try {
73061
73074
  const app = await client.getApp(appId);
@@ -73163,6 +73176,7 @@ async function appCheck(client, args = {}) {
73163
73176
  warnIfInertAgentTools(app);
73164
73177
  warnIfProjectFootguns(projectDir, sourceText);
73165
73178
  warnIfUnbranded(app);
73179
+ warnIfUndescribed(app);
73166
73180
  if (!nothingPending(pending)) {
73167
73181
  process.exit(1);
73168
73182
  }
@@ -84774,14 +84788,14 @@ function readSourceData(workbook, cache) {
84774
84788
  if (!range2) return void 0;
84775
84789
  const header = [];
84776
84790
  for (let col = range2.startCol; col <= range2.endCol; col++) {
84777
- const cell = sheet.getCell(rowColToRef(range2.startRow, col));
84791
+ const cell = sheet.getCellAt(range2.startRow, col);
84778
84792
  header.push(formatHeader(cell?.value));
84779
84793
  }
84780
84794
  const records = [];
84781
84795
  for (let row = range2.startRow + 1; row <= range2.endRow; row++) {
84782
84796
  const rec = [];
84783
84797
  for (let col = range2.startCol; col <= range2.endCol; col++) {
84784
- const cell = sheet.getCell(rowColToRef(row, col));
84798
+ const cell = sheet.getCellAt(row, col);
84785
84799
  rec.push(coerceValue(cell?.value));
84786
84800
  }
84787
84801
  records.push(rec);
@@ -86325,23 +86339,70 @@ var CellMap = class {
86325
86339
  get size() {
86326
86340
  return this.store.size;
86327
86341
  }
86342
+ /**
86343
+ * The populated extent of this map, memoized until a mutation invalidates it.
86344
+ *
86345
+ * It lives HERE rather than on `SheetModel` because it is a pure function of
86346
+ * the cell set: a cache held next to the data cannot outlive it, so replacing
86347
+ * a sheet's map can never serve the previous map's extent. Every mutator
86348
+ * below drops it, including the packed pair — those bypass `set`/`delete`
86349
+ * entirely and are what the row/column commands use.
86350
+ *
86351
+ * Walking PACKED keys is the other half. `keys()` unpacks each key into an A1
86352
+ * string, which the caller then reparsed with a regex, so the old form packed,
86353
+ * unpacked and reparsed the whole sheet on every call — and it is called once
86354
+ * per oversized range during a recompute.
86355
+ */
86356
+ usedRange() {
86357
+ if (!this.usedRangeCache) {
86358
+ let minRow2 = Infinity;
86359
+ let maxRow2 = 0;
86360
+ let minCol2 = Infinity;
86361
+ let maxCol2 = 0;
86362
+ for (const packed of this.store.keys()) {
86363
+ const { row, col } = unpackRowCol(packed);
86364
+ if (row < minRow2) minRow2 = row;
86365
+ if (row > maxRow2) maxRow2 = row;
86366
+ if (col < minCol2) minCol2 = col;
86367
+ if (col > maxCol2) maxCol2 = col;
86368
+ }
86369
+ this.usedRangeCache = maxRow2 === 0 ? { minRow: 1, maxRow: 1, minCol: 1, maxCol: 1 } : { minRow: minRow2, maxRow: maxRow2, minCol: minCol2, maxCol: maxCol2 };
86370
+ }
86371
+ const { minRow, maxRow, minCol, maxCol } = this.usedRangeCache;
86372
+ return { minRow, maxRow, minCol, maxCol };
86373
+ }
86328
86374
  get(ref2) {
86329
86375
  return this.store.get(packRef(ref2));
86330
86376
  }
86377
+ /**
86378
+ * Read by coordinate, skipping the A1 round trip.
86379
+ *
86380
+ * `get(ref)` costs a number→string→regex→number conversion per call, which
86381
+ * is invisible until a caller reads a range cell by cell: on one real
86382
+ * workbook `refToRowCol` alone was 39% of a full recompute. A caller already
86383
+ * holding (row, col) — the range resolver, any grid walk — should use this.
86384
+ */
86385
+ getAt(row, col) {
86386
+ return this.store.get(packRowCol(row, col));
86387
+ }
86331
86388
  set(ref2, cell) {
86332
86389
  const key = packRef(ref2);
86333
86390
  if (key < 0) return this;
86334
86391
  this.store.set(key, cell);
86392
+ this.usedRangeCache = void 0;
86335
86393
  return this;
86336
86394
  }
86337
86395
  has(ref2) {
86338
86396
  return this.store.has(packRef(ref2));
86339
86397
  }
86340
86398
  delete(ref2) {
86341
- return this.store.delete(packRef(ref2));
86399
+ const deleted = this.store.delete(packRef(ref2));
86400
+ if (deleted) this.usedRangeCache = void 0;
86401
+ return deleted;
86342
86402
  }
86343
86403
  clear() {
86344
86404
  this.store.clear();
86405
+ this.usedRangeCache = void 0;
86345
86406
  }
86346
86407
  *keys() {
86347
86408
  for (const k of this.store.keys()) yield unpackRef(k);
@@ -86369,10 +86430,13 @@ var CellMap = class {
86369
86430
  /** Direct packed-key set, for the same hot loops. */
86370
86431
  setPacked(packed, cell) {
86371
86432
  this.store.set(packed, cell);
86433
+ this.usedRangeCache = void 0;
86372
86434
  }
86373
86435
  /** Direct packed-key delete. */
86374
86436
  deletePacked(packed) {
86375
- return this.store.delete(packed);
86437
+ const deleted = this.store.delete(packed);
86438
+ if (deleted) this.usedRangeCache = void 0;
86439
+ return deleted;
86376
86440
  }
86377
86441
  };
86378
86442
  var StyleRegistry = class {
@@ -86555,6 +86619,10 @@ var SheetModel = class {
86555
86619
  getCell(ref2) {
86556
86620
  return this.cells.get(ref2);
86557
86621
  }
86622
+ /** Read by coordinate, skipping the A1 round trip — see `CellMap.getAt`. */
86623
+ getCellAt(row, col) {
86624
+ return this.cells.getAt(row, col);
86625
+ }
86558
86626
  /**
86559
86627
  * Set cell value with optional inline style. Interns style via the
86560
86628
  * StyleRegistry when available. When `style` is supplied and matches the
@@ -86649,22 +86717,12 @@ var SheetModel = class {
86649
86717
  this.set(ref2, label, style);
86650
86718
  });
86651
86719
  }
86652
- /** Compute the bounding box of all non-empty cells. */
86720
+ /**
86721
+ * The populated extent of the sheet. Derived from — and cached by — the cell
86722
+ * map, so it cannot outlive the data it describes.
86723
+ */
86653
86724
  getUsedRange() {
86654
- let minRow = Infinity;
86655
- let maxRow = 0;
86656
- let minCol = Infinity;
86657
- let maxCol = 0;
86658
- for (const ref2 of this.cells.keys()) {
86659
- const rc = refToRowCol(ref2);
86660
- if (!rc) continue;
86661
- if (rc.row < minRow) minRow = rc.row;
86662
- if (rc.row > maxRow) maxRow = rc.row;
86663
- if (rc.col < minCol) minCol = rc.col;
86664
- if (rc.col > maxCol) maxCol = rc.col;
86665
- }
86666
- if (maxRow === 0) return { minRow: 1, maxRow: 1, minCol: 1, maxCol: 1 };
86667
- return { minRow, maxRow, minCol, maxCol };
86725
+ return this.cells.usedRange();
86668
86726
  }
86669
86727
  };
86670
86728
  var WorkbookModel = class {
@@ -86911,7 +86969,7 @@ function applyAutoFilterHiddenRows(sheet) {
86911
86969
  let visible = true;
86912
86970
  for (const filterCol of activeFilters) {
86913
86971
  const col = startCol + filterCol.colIndex;
86914
- const cell = sheet.getCell(rowColToRef(r, col));
86972
+ const cell = sheet.getCellAt(r, col);
86915
86973
  const cellText = cell?.displayValue ?? "";
86916
86974
  if (filterCol.filterValues && filterCol.filterValues.length > 0) {
86917
86975
  if (!filterCol.filterValues.includes(cellText)) {
@@ -94141,6 +94199,28 @@ function parseCellKey(key) {
94141
94199
  const bang = key.indexOf("!");
94142
94200
  return { sheet: key.slice(0, bang), ref: key.slice(bang + 1) };
94143
94201
  }
94202
+ var COARSE_COL_SUFFIX = ":*";
94203
+ var COARSE_ROW_PREFIX = "*:";
94204
+ function columnKey(sheet, column) {
94205
+ return `${sheet}!${column}${COARSE_COL_SUFFIX}`;
94206
+ }
94207
+ function rowKey(sheet, row) {
94208
+ return `${sheet}!${COARSE_ROW_PREFIX}${row}`;
94209
+ }
94210
+ function isCoarseKey(key) {
94211
+ return key.endsWith(COARSE_COL_SUFFIX) || key.includes(`!${COARSE_ROW_PREFIX}`);
94212
+ }
94213
+ var A1 = /^([A-Z]+)(\d+)$/;
94214
+ function coarseKeysFor(cellKeyStr) {
94215
+ const bang = cellKeyStr.indexOf("!");
94216
+ if (bang < 0) return [];
94217
+ const sheet = cellKeyStr.slice(0, bang);
94218
+ const ref2 = cellKeyStr.slice(bang + 1);
94219
+ const m = A1.exec(ref2);
94220
+ if (!m) return [];
94221
+ return [columnKey(sheet, m[1]), rowKey(sheet, Number(m[2]))];
94222
+ }
94223
+ var EMPTY = /* @__PURE__ */ new Set();
94144
94224
  var DependencyGraph = class {
94145
94225
  constructor() {
94146
94226
  // Forward edges: cell → cells it references (precedents)
@@ -94175,19 +94255,35 @@ var DependencyGraph = class {
94175
94255
  }
94176
94256
  /** Get all cells that directly depend on the given cell. */
94177
94257
  getDirectDependents(cell) {
94178
- return Array.from(this.dependents.get(cell) ?? []);
94258
+ return Array.from(this.dependentsOf(cell));
94259
+ }
94260
+ /**
94261
+ * Everything that references this cell — directly, or through the column or
94262
+ * row node that contains it. The single place `dependents` is read, so a
94263
+ * whole-column reference cannot be forgotten by one traversal and honoured by
94264
+ * another.
94265
+ */
94266
+ dependentsOf(cell) {
94267
+ const direct = this.dependents.get(cell);
94268
+ if (isCoarseKey(cell)) return direct ?? EMPTY;
94269
+ const coarse = coarseKeysFor(cell);
94270
+ let widened;
94271
+ for (const key of coarse) {
94272
+ const via = this.dependents.get(key);
94273
+ if (!via || via.size === 0) continue;
94274
+ widened ??= new Set(direct);
94275
+ for (const d of via) widened.add(d);
94276
+ }
94277
+ return widened ?? direct ?? EMPTY;
94179
94278
  }
94180
94279
  /** Get all cells that need recalculation when the given cells change, in topological order. */
94181
94280
  getRecalcOrder(changed) {
94182
- const changedSet = new Set(changed);
94183
94281
  const affected = /* @__PURE__ */ new Set();
94184
94282
  const queue = [...changed];
94185
94283
  let head = 0;
94186
94284
  while (head < queue.length) {
94187
94285
  const cell = queue[head++];
94188
- const deps = this.dependents.get(cell);
94189
- if (!deps) continue;
94190
- for (const dep of deps) {
94286
+ for (const dep of this.dependentsOf(cell)) {
94191
94287
  if (!affected.has(dep)) {
94192
94288
  affected.add(dep);
94193
94289
  queue.push(dep);
@@ -94196,15 +94292,11 @@ var DependencyGraph = class {
94196
94292
  }
94197
94293
  if (affected.size === 0) return [];
94198
94294
  const inDegree = /* @__PURE__ */ new Map();
94295
+ for (const cell of affected) inDegree.set(cell, 0);
94199
94296
  for (const cell of affected) {
94200
- let count = 0;
94201
- const prec = this.precedents.get(cell);
94202
- if (prec) {
94203
- for (const p of prec) {
94204
- if (affected.has(p)) count++;
94205
- }
94297
+ for (const dep of this.dependentsOf(cell)) {
94298
+ if (affected.has(dep)) inDegree.set(dep, (inDegree.get(dep) ?? 0) + 1);
94206
94299
  }
94207
- inDegree.set(cell, count);
94208
94300
  }
94209
94301
  const result = [];
94210
94302
  const ready = [];
@@ -94215,9 +94307,7 @@ var DependencyGraph = class {
94215
94307
  while (readyHead < ready.length) {
94216
94308
  const cell = ready[readyHead++];
94217
94309
  result.push(cell);
94218
- const deps = this.dependents.get(cell);
94219
- if (!deps) continue;
94220
- for (const dep of deps) {
94310
+ for (const dep of this.dependentsOf(cell)) {
94221
94311
  if (!affected.has(dep)) continue;
94222
94312
  const newDeg = (inDegree.get(dep) ?? 1) - 1;
94223
94313
  inDegree.set(dep, newDeg);
@@ -94238,12 +94328,13 @@ var DependencyGraph = class {
94238
94328
  const stack = new Array();
94239
94329
  const prec = this.precedents.get(cell);
94240
94330
  if (!prec) return null;
94331
+ const originCoarse = new Set(coarseKeysFor(cell));
94241
94332
  for (const p of prec) {
94242
94333
  stack.push({ cell: p, path: [cell, p] });
94243
94334
  }
94244
94335
  while (stack.length > 0) {
94245
94336
  const { cell: current, path: path12 } = stack.pop();
94246
- if (current === cell) return path12;
94337
+ if (current === cell || originCoarse.has(current)) return path12;
94247
94338
  if (visited.has(current)) continue;
94248
94339
  visited.add(current);
94249
94340
  const nextPrec = this.precedents.get(current);
@@ -94356,6 +94447,20 @@ function parseTableRange(ref2) {
94356
94447
  };
94357
94448
  }
94358
94449
 
94450
+ // ../xlsx/src/blank_comparison.ts
94451
+ var import_operators = __toESM(require_operators(), 1);
94452
+ var patched = false;
94453
+ function applyExcelBlankSemantics() {
94454
+ if (patched) return;
94455
+ patched = true;
94456
+ const original = import_operators.Infix.compareOp;
94457
+ import_operators.Infix.compareOp = (value1, infix, value2, isArray1, isArray22) => {
94458
+ if (value1 == null && typeof value2 === "string") value1 = "";
94459
+ if (value2 == null && typeof value1 === "string") value2 = "";
94460
+ return original(value1, infix, value2, isArray1, isArray22);
94461
+ };
94462
+ }
94463
+
94359
94464
  // ../xlsx/src/formula_engine.ts
94360
94465
  var unwrapArg = (a) => a !== null && typeof a === "object" && "value" in a ? a.value : a;
94361
94466
  function adaptFormulajs(fn) {
@@ -94398,9 +94503,24 @@ function formulajsBridgeWithContext() {
94398
94503
  bridgeCtxCache = withCtx;
94399
94504
  return withCtx;
94400
94505
  }
94506
+ var MAX_EXPANDED_CELLS = 1e4;
94401
94507
  var FormulaEngine = class _FormulaEngine {
94402
94508
  constructor(workbook) {
94403
94509
  this.graph = new DependencyGraph();
94510
+ /**
94511
+ * Formula cells (by key) whose text the parser could not read AT ALL, found
94512
+ * while building the graph from a loaded file.
94513
+ *
94514
+ * They are held apart from every other failure because the honest response
94515
+ * differs: a formula that evaluates TO an error has been understood, so
94516
+ * writing `#DIV/0!` reports a real answer, while one we cannot parse has not
94517
+ * been — and overwriting the value its author computed would replace a
94518
+ * correct number with a guess. These keep their cached value and are named in
94519
+ * the report instead. The engine's own rule, one level down: degrade what you
94520
+ * cannot draw, refuse what you cannot read.
94521
+ */
94522
+ this.unreadableFormulas = /* @__PURE__ */ new Set();
94523
+ applyExcelBlankSemantics();
94404
94524
  this.workbook = workbook;
94405
94525
  this.parser = new import_fast_formula_parser.default({
94406
94526
  onCell: (ref2) => this.resolveCell(ref2),
@@ -94421,11 +94541,13 @@ var FormulaEngine = class _FormulaEngine {
94421
94541
  /** Build dependency graph from all formula cells in the workbook. */
94422
94542
  buildGraph() {
94423
94543
  this.graph.clear();
94544
+ this.unreadableFormulas.clear();
94424
94545
  for (const sheet of this.workbook.sheets) {
94425
94546
  for (const [ref2, cell] of sheet.cells) {
94426
94547
  if (!cell.formula) continue;
94427
94548
  const key = cellKey({ sheet: sheet.name, ref: ref2 });
94428
- const deps = this.extractDependencies(cell.formula, sheet.name);
94549
+ const { deps, readable } = this.analyzeFormula(cell.formula, sheet.name);
94550
+ if (!readable) this.unreadableFormulas.add(key);
94429
94551
  this.graph.setDependencies(key, deps);
94430
94552
  }
94431
94553
  }
@@ -94481,8 +94603,13 @@ var FormulaEngine = class _FormulaEngine {
94481
94603
  onCellChanged(sheetName, ref2, formula) {
94482
94604
  const key = cellKey({ sheet: sheetName, ref: ref2 });
94483
94605
  if (formula) {
94484
- const deps = this.extractDependencies(formula, sheetName);
94606
+ const { deps, readable } = this.analyzeFormula(formula, sheetName);
94485
94607
  this.graph.setDependencies(key, deps);
94608
+ this.unreadableFormulas.delete(key);
94609
+ if (!readable) {
94610
+ this.setCellError(key, "#ERROR!");
94611
+ return;
94612
+ }
94486
94613
  const cycle = this.graph.detectCycle(key);
94487
94614
  if (cycle) {
94488
94615
  this.setCellError(key, "#CIRC!");
@@ -94491,6 +94618,7 @@ var FormulaEngine = class _FormulaEngine {
94491
94618
  this.evaluateCell(key);
94492
94619
  } else {
94493
94620
  this.graph.removeCell(key);
94621
+ this.unreadableFormulas.delete(key);
94494
94622
  }
94495
94623
  const order = this.graph.getRecalcOrder([key]);
94496
94624
  for (const depKey of order) {
@@ -94507,6 +94635,7 @@ var FormulaEngine = class _FormulaEngine {
94507
94635
  // ===========================================================================
94508
94636
  /** Evaluate a single formula cell and update its value. */
94509
94637
  evaluateCell(key) {
94638
+ if (this.unreadableFormulas.has(key)) return;
94510
94639
  const { sheet: sheetName, ref: ref2 } = parseCellKey(key);
94511
94640
  const sheetIndex = this.workbook.sheets.findIndex((s) => s.name === sheetName);
94512
94641
  if (sheetIndex < 0) return;
@@ -94555,26 +94684,60 @@ var FormulaEngine = class _FormulaEngine {
94555
94684
  // ===========================================================================
94556
94685
  /** Extract cell/range references from a formula string. Returns empty array for unparseable formulas. */
94557
94686
  extractDependencies(formula, currentSheet) {
94558
- const sheet = this.workbook.sheets.find((s) => s.name === currentSheet);
94559
- const expanded = sheet ? expandStructuredRefs(formula, sheet.tables, 1) : formula;
94560
- const deps = this.depParser.parse(expanded, { sheet: currentSheet, row: 1, col: 1 });
94687
+ return this.analyzeFormula(formula, currentSheet).deps;
94688
+ }
94689
+ /**
94690
+ * Dependencies plus whether the parser could read the formula at all.
94691
+ *
94692
+ * `fast-formula-parser` reports a formula it cannot LEX by throwing, the same
94693
+ * channel it uses for `#VALUE!`, so the lexer failure of a single cell used to
94694
+ * escape `buildGraph` and abort the recompute for the entire workbook —
94695
+ * observed on a real workbook whose `VLOOKUP` pointed at another file
94696
+ * (`'[1]DATA'!B:C`, an external-workbook reference the lexer has no rule for).
94697
+ * Anything the grammar does not cover lands here the same way, so this catches
94698
+ * the class rather than that one syntax.
94699
+ */
94700
+ analyzeFormula(formula, currentSheet) {
94701
+ let deps;
94702
+ try {
94703
+ const sheet = this.workbook.sheets.find((s) => s.name === currentSheet);
94704
+ const expanded = sheet ? expandStructuredRefs(formula, sheet.tables, 1) : formula;
94705
+ deps = this.depParser.parse(expanded, { sheet: currentSheet, row: 1, col: 1 });
94706
+ } catch {
94707
+ return { deps: [], readable: false };
94708
+ }
94561
94709
  const refs = [];
94562
94710
  for (const dep of deps) {
94563
94711
  const depSheet = dep.sheet ?? currentSheet;
94564
94712
  if (dep.from && dep.to) {
94565
- const maxCells = 1e4;
94566
- let count = 0;
94567
- for (let r = dep.from.row; r <= dep.to.row && count < maxCells; r++) {
94568
- for (let c = dep.from.col; c <= dep.to.col && count < maxCells; c++) {
94713
+ const rows = dep.to.row - dep.from.row + 1;
94714
+ const cols = dep.to.col - dep.from.col + 1;
94715
+ if (rows * cols > MAX_EXPANDED_CELLS) {
94716
+ if (cols <= rows) {
94717
+ for (let c = dep.from.col; c <= dep.to.col; c++) {
94718
+ refs.push(columnKey(depSheet, colNumToLetters(c)));
94719
+ }
94720
+ } else {
94721
+ for (let r = dep.from.row; r <= dep.to.row; r++) {
94722
+ refs.push(rowKey(depSheet, r));
94723
+ }
94724
+ }
94725
+ continue;
94726
+ }
94727
+ for (let r = dep.from.row; r <= dep.to.row; r++) {
94728
+ for (let c = dep.from.col; c <= dep.to.col; c++) {
94569
94729
  refs.push(cellKey({ sheet: depSheet, ref: rowColToRef(r, c) }));
94570
- count++;
94571
94730
  }
94572
94731
  }
94573
94732
  } else if (dep.row != null && dep.col != null) {
94574
94733
  refs.push(cellKey({ sheet: depSheet, ref: rowColToRef(dep.row, dep.col) }));
94575
94734
  }
94576
94735
  }
94577
- return refs;
94736
+ return { deps: refs, readable: true };
94737
+ }
94738
+ /** Formula cells the parser could not read, by key. Read by the recompute report. */
94739
+ unreadable() {
94740
+ return this.unreadableFormulas;
94578
94741
  }
94579
94742
  // ===========================================================================
94580
94743
  // Cell value resolution callbacks
@@ -94611,8 +94774,7 @@ var FormulaEngine = class _FormulaEngine {
94611
94774
  for (let r = ref2.from.row; r <= lastRow; r++) {
94612
94775
  const row = [];
94613
94776
  for (let c = ref2.from.col; c <= lastCol; c++) {
94614
- const cell = sheet.getCell(rowColToRef(r, c));
94615
- row.push(cell?.value ?? null);
94777
+ row.push(sheet.getCellAt(r, c)?.value ?? null);
94616
94778
  }
94617
94779
  result.push(row);
94618
94780
  }
@@ -94696,15 +94858,19 @@ function recomputeAll(workbook) {
94696
94858
  const engine = new FormulaEngine(workbook);
94697
94859
  engine.buildGraph();
94698
94860
  engine.evaluateAll();
94699
- return collectRecomputeReport(workbook);
94861
+ return collectRecomputeReport(workbook, engine.unreadable());
94700
94862
  }
94701
- function collectRecomputeReport(workbook) {
94863
+ function collectRecomputeReport(workbook, unreadable) {
94702
94864
  let total = 0;
94703
94865
  const unevaluated = [];
94704
94866
  for (const sheet of workbook.sheets) {
94705
94867
  for (const [ref2, cell] of sheet.cells) {
94706
94868
  if (!cell.formula) continue;
94707
94869
  total++;
94870
+ if (unreadable.has(cellKey({ sheet: sheet.name, ref: ref2 }))) {
94871
+ unevaluated.push({ sheet: sheet.name, ref: ref2, formula: cell.formula, error: "#UNREADABLE!" });
94872
+ continue;
94873
+ }
94708
94874
  if (cell.value === null) {
94709
94875
  unevaluated.push({ sheet: sheet.name, ref: ref2, formula: cell.formula, error: cell.error ?? "#ERROR!" });
94710
94876
  }
@@ -95642,9 +95808,9 @@ function applySheetCells(workbook, sheetIndex, def) {
95642
95808
  }
95643
95809
  }
95644
95810
  if (def.rowHeights) {
95645
- for (const [rowKey, height] of Object.entries(def.rowHeights)) {
95646
- const row = Number(rowKey);
95647
- if (!Number.isInteger(row) || row < 1) fail2(`Invalid row in sheet "${def.name}".rowHeights: ${rowKey}`);
95811
+ for (const [rowKey2, height] of Object.entries(def.rowHeights)) {
95812
+ const row = Number(rowKey2);
95813
+ if (!Number.isInteger(row) || row < 1) fail2(`Invalid row in sheet "${def.name}".rowHeights: ${rowKey2}`);
95648
95814
  sheet.rowHeights.set(row, height);
95649
95815
  }
95650
95816
  }
@@ -102076,6 +102242,10 @@ USAGE
102076
102242
  Tools are grouped by category (tables, records, views, etc.).
102077
102243
  Query tools return IDs used as arguments to other tools.
102078
102244
 
102245
+ Building an APP? Read \`lotics docs building_an_app\` first \u2014 the sequence the
102246
+ \`lotics app *\` commands below go in \u2014 then \`lotics docs\` for the area
102247
+ references installed beside the project.
102248
+
102079
102249
  COMMANDS
102080
102250
  ${renderCommandsHelp()}
102081
102251
 
@@ -224,6 +224,10 @@ export declare class LoticsClient {
224
224
  getApp(app_id: string): Promise<{
225
225
  id: string;
226
226
  name: string;
227
+ /** What the app is for. Heads the capability catalog the member's chat agent
228
+ * reads every turn the app is open, so it is where a standing process is
229
+ * stated. Null when unset. */
230
+ description?: string | null;
227
231
  workspace_id: string;
228
232
  current_version_id: string | null;
229
233
  /** Launcher icon — a Lucide name, or an image ref. Null when unset. */