@lotics/cli 0.191.0 → 0.191.1
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/AGENTS.md +1 -1
- package/README.md +2 -0
- package/dist/probe_page.js +25 -4
- package/dist/src/cli.js +459 -227
- package/docs/cli_reference.md +5 -5
- package/package.json +1 -1
package/dist/src/cli.js
CHANGED
|
@@ -39864,15 +39864,15 @@ var require_deflate = __commonJS({
|
|
|
39864
39864
|
if (!strm) {
|
|
39865
39865
|
return Z_STREAM_ERROR;
|
|
39866
39866
|
}
|
|
39867
|
-
var
|
|
39867
|
+
var wrap2 = 1;
|
|
39868
39868
|
if (level === Z_DEFAULT_COMPRESSION) {
|
|
39869
39869
|
level = 6;
|
|
39870
39870
|
}
|
|
39871
39871
|
if (windowBits < 0) {
|
|
39872
|
-
|
|
39872
|
+
wrap2 = 0;
|
|
39873
39873
|
windowBits = -windowBits;
|
|
39874
39874
|
} else if (windowBits > 15) {
|
|
39875
|
-
|
|
39875
|
+
wrap2 = 2;
|
|
39876
39876
|
windowBits -= 16;
|
|
39877
39877
|
}
|
|
39878
39878
|
if (memLevel < 1 || memLevel > MAX_MEM_LEVEL || method !== Z_DEFLATED || windowBits < 8 || windowBits > 15 || level < 0 || level > 9 || strategy < 0 || strategy > Z_FIXED) {
|
|
@@ -39884,7 +39884,7 @@ var require_deflate = __commonJS({
|
|
|
39884
39884
|
var s = new DeflateState();
|
|
39885
39885
|
strm.state = s;
|
|
39886
39886
|
s.strm = strm;
|
|
39887
|
-
s.wrap =
|
|
39887
|
+
s.wrap = wrap2;
|
|
39888
39888
|
s.gzhead = null;
|
|
39889
39889
|
s.w_bits = windowBits;
|
|
39890
39890
|
s.w_size = 1 << s.w_bits;
|
|
@@ -40177,7 +40177,7 @@ var require_deflate = __commonJS({
|
|
|
40177
40177
|
var dictLength = dictionary.length;
|
|
40178
40178
|
var s;
|
|
40179
40179
|
var str, n;
|
|
40180
|
-
var
|
|
40180
|
+
var wrap2;
|
|
40181
40181
|
var avail;
|
|
40182
40182
|
var next;
|
|
40183
40183
|
var input;
|
|
@@ -40186,16 +40186,16 @@ var require_deflate = __commonJS({
|
|
|
40186
40186
|
return Z_STREAM_ERROR;
|
|
40187
40187
|
}
|
|
40188
40188
|
s = strm.state;
|
|
40189
|
-
|
|
40190
|
-
if (
|
|
40189
|
+
wrap2 = s.wrap;
|
|
40190
|
+
if (wrap2 === 2 || wrap2 === 1 && s.status !== INIT_STATE || s.lookahead) {
|
|
40191
40191
|
return Z_STREAM_ERROR;
|
|
40192
40192
|
}
|
|
40193
|
-
if (
|
|
40193
|
+
if (wrap2 === 1) {
|
|
40194
40194
|
strm.adler = adler32(strm.adler, dictionary, dictLength, 0);
|
|
40195
40195
|
}
|
|
40196
40196
|
s.wrap = 0;
|
|
40197
40197
|
if (dictLength >= s.w_size) {
|
|
40198
|
-
if (
|
|
40198
|
+
if (wrap2 === 0) {
|
|
40199
40199
|
zero(s.head);
|
|
40200
40200
|
s.strstart = 0;
|
|
40201
40201
|
s.block_start = 0;
|
|
@@ -40235,7 +40235,7 @@ var require_deflate = __commonJS({
|
|
|
40235
40235
|
strm.next_in = next;
|
|
40236
40236
|
strm.input = input;
|
|
40237
40237
|
strm.avail_in = avail;
|
|
40238
|
-
s.wrap =
|
|
40238
|
+
s.wrap = wrap2;
|
|
40239
40239
|
return Z_OK;
|
|
40240
40240
|
}
|
|
40241
40241
|
exports2.deflateInit = deflateInit;
|
|
@@ -41255,17 +41255,17 @@ var require_inflate = __commonJS({
|
|
|
41255
41255
|
return inflateResetKeep(strm);
|
|
41256
41256
|
}
|
|
41257
41257
|
function inflateReset2(strm, windowBits) {
|
|
41258
|
-
var
|
|
41258
|
+
var wrap2;
|
|
41259
41259
|
var state;
|
|
41260
41260
|
if (!strm || !strm.state) {
|
|
41261
41261
|
return Z_STREAM_ERROR;
|
|
41262
41262
|
}
|
|
41263
41263
|
state = strm.state;
|
|
41264
41264
|
if (windowBits < 0) {
|
|
41265
|
-
|
|
41265
|
+
wrap2 = 0;
|
|
41266
41266
|
windowBits = -windowBits;
|
|
41267
41267
|
} else {
|
|
41268
|
-
|
|
41268
|
+
wrap2 = (windowBits >> 4) + 1;
|
|
41269
41269
|
if (windowBits < 48) {
|
|
41270
41270
|
windowBits &= 15;
|
|
41271
41271
|
}
|
|
@@ -41276,7 +41276,7 @@ var require_inflate = __commonJS({
|
|
|
41276
41276
|
if (state.window !== null && state.wbits !== windowBits) {
|
|
41277
41277
|
state.window = null;
|
|
41278
41278
|
}
|
|
41279
|
-
state.wrap =
|
|
41279
|
+
state.wrap = wrap2;
|
|
41280
41280
|
state.wbits = windowBits;
|
|
41281
41281
|
return inflateReset(strm);
|
|
41282
41282
|
}
|
|
@@ -46001,7 +46001,7 @@ function resultSideEffects(result) {
|
|
|
46001
46001
|
}
|
|
46002
46002
|
|
|
46003
46003
|
// src/version.ts
|
|
46004
|
-
var VERSION = "0.191.
|
|
46004
|
+
var VERSION = "0.191.1";
|
|
46005
46005
|
|
|
46006
46006
|
// src/timezone.ts
|
|
46007
46007
|
function machineTimezone() {
|
|
@@ -46110,6 +46110,167 @@ function matchDocArea(areas, query) {
|
|
|
46110
46110
|
if (exact.length > 0) return exact;
|
|
46111
46111
|
return areas.filter((a) => a.area.toLowerCase().includes(q));
|
|
46112
46112
|
}
|
|
46113
|
+
var PAGE_BYTES = 16e3;
|
|
46114
|
+
var NAV_BYTES = PAGE_BYTES / 2;
|
|
46115
|
+
var CUT_MARK = "\n[cut \u2014 the whole document is at the path above]";
|
|
46116
|
+
var slugOf = (title) => title.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "");
|
|
46117
|
+
function docSections(text) {
|
|
46118
|
+
const lines = text.split("\n");
|
|
46119
|
+
const found = [];
|
|
46120
|
+
let fenced = false;
|
|
46121
|
+
lines.forEach((line, index) => {
|
|
46122
|
+
if (line.startsWith("```")) {
|
|
46123
|
+
fenced = !fenced;
|
|
46124
|
+
return;
|
|
46125
|
+
}
|
|
46126
|
+
if (fenced) return;
|
|
46127
|
+
const m = /^(#{1,6})\s+(.+?)\s*$/.exec(line);
|
|
46128
|
+
if (m === null) return;
|
|
46129
|
+
const level = m[1].length;
|
|
46130
|
+
for (const open of found) {
|
|
46131
|
+
if (open.end === lines.length && open.level >= level) open.end = index;
|
|
46132
|
+
}
|
|
46133
|
+
found.push({ level, title: m[2], slug: slugOf(m[2]), line: index, end: lines.length });
|
|
46134
|
+
});
|
|
46135
|
+
return found;
|
|
46136
|
+
}
|
|
46137
|
+
function docRows(text) {
|
|
46138
|
+
const found = [];
|
|
46139
|
+
let fenced = false;
|
|
46140
|
+
let header = true;
|
|
46141
|
+
text.split("\n").forEach((line, index) => {
|
|
46142
|
+
if (line.startsWith("```")) {
|
|
46143
|
+
fenced = !fenced;
|
|
46144
|
+
return;
|
|
46145
|
+
}
|
|
46146
|
+
if (fenced) return;
|
|
46147
|
+
if (!line.startsWith("|")) {
|
|
46148
|
+
header = true;
|
|
46149
|
+
return;
|
|
46150
|
+
}
|
|
46151
|
+
const cells = line.split("|");
|
|
46152
|
+
if (cells.length < 3) return;
|
|
46153
|
+
const first3 = cells[1].trim();
|
|
46154
|
+
if (/^:?-{2,}:?$/.test(first3)) return;
|
|
46155
|
+
if (header) {
|
|
46156
|
+
header = false;
|
|
46157
|
+
return;
|
|
46158
|
+
}
|
|
46159
|
+
const key = first3.replace(/[`*[\]]/g, "").trim();
|
|
46160
|
+
if (key === "") return;
|
|
46161
|
+
found.push({ key, slug: slugOf(key), line: index });
|
|
46162
|
+
});
|
|
46163
|
+
return found;
|
|
46164
|
+
}
|
|
46165
|
+
var byteLength = (s) => Buffer.byteLength(s, "utf-8");
|
|
46166
|
+
var oneNewline = (s) => `${s.replace(/\n+$/, "")}
|
|
46167
|
+
`;
|
|
46168
|
+
var wrap = (names, width = 92) => names.reduce((out, name2) => {
|
|
46169
|
+
const last3 = out[out.length - 1];
|
|
46170
|
+
if (last3 !== void 0 && last3.length + name2.length + 1 <= width) {
|
|
46171
|
+
out[out.length - 1] = `${last3} ${name2}`;
|
|
46172
|
+
return out;
|
|
46173
|
+
}
|
|
46174
|
+
return [...out, ` ${name2}`];
|
|
46175
|
+
}, []);
|
|
46176
|
+
var fit = (lines, budget) => {
|
|
46177
|
+
const kept = [];
|
|
46178
|
+
let used = 0;
|
|
46179
|
+
for (const line of lines) {
|
|
46180
|
+
const cost = byteLength(line) + 1;
|
|
46181
|
+
if (used + cost > budget) break;
|
|
46182
|
+
kept.push(line);
|
|
46183
|
+
used += cost;
|
|
46184
|
+
}
|
|
46185
|
+
return { kept, dropped: lines.length - kept.length };
|
|
46186
|
+
};
|
|
46187
|
+
var childrenOf = (sections, parent) => {
|
|
46188
|
+
const within = parent === void 0 ? sections : sections.filter((s) => s.line > parent.line && s.line < parent.end);
|
|
46189
|
+
if (within.length === 0) return [];
|
|
46190
|
+
const top = Math.min(...within.map((s) => s.level));
|
|
46191
|
+
return within.filter((s) => s.level === top);
|
|
46192
|
+
};
|
|
46193
|
+
var linesOf = (lines, section) => section === void 0 ? lines : lines.slice(section.line, section.end);
|
|
46194
|
+
var tableTop = (lines, rowLine) => {
|
|
46195
|
+
for (let i2 = rowLine; i2 > 0; i2--) {
|
|
46196
|
+
if (!lines[i2].startsWith("|")) return i2 + 1;
|
|
46197
|
+
}
|
|
46198
|
+
return 0;
|
|
46199
|
+
};
|
|
46200
|
+
var tableHeader = (lines, rowLine) => {
|
|
46201
|
+
for (let i2 = rowLine; i2 > 0; i2--) {
|
|
46202
|
+
if (/^\|\s*:?-{2,}/.test(lines[i2])) return i2 > 0 ? [lines[i2 - 1], lines[i2]] : [];
|
|
46203
|
+
if (!lines[i2].startsWith("|")) return [];
|
|
46204
|
+
}
|
|
46205
|
+
return [];
|
|
46206
|
+
};
|
|
46207
|
+
function renderDocPage(text, args) {
|
|
46208
|
+
const lines = text.split("\n");
|
|
46209
|
+
const sections = docSections(text);
|
|
46210
|
+
const rows = docRows(text);
|
|
46211
|
+
const address = (slug) => ` lotics docs ${args.address}/${slug}`;
|
|
46212
|
+
const navFor = (parent2) => {
|
|
46213
|
+
const children2 = childrenOf(sections, parent2);
|
|
46214
|
+
if (children2.length > 0) {
|
|
46215
|
+
return children2.map(
|
|
46216
|
+
(s) => `${address(s.slug)} (${byteLength(linesOf(lines, s).join("\n"))} bytes)`
|
|
46217
|
+
);
|
|
46218
|
+
}
|
|
46219
|
+
const within = parent2 === void 0 ? rows : rows.filter((r) => r.line > parent2.line && r.line < parent2.end);
|
|
46220
|
+
if (within.length === 0) return [];
|
|
46221
|
+
return [` lotics docs ${args.address}/<name>, where <name> is one of:`, "", ...wrap(within.map((r) => r.slug))];
|
|
46222
|
+
};
|
|
46223
|
+
const titled = sections.length > 0 && sections[0].level === 1 ? sections[0] : void 0;
|
|
46224
|
+
const listed = (nav2) => {
|
|
46225
|
+
const shown = fit(nav2, NAV_BYTES);
|
|
46226
|
+
return [...shown.kept, ...shown.dropped > 0 ? [` \u2026 and ${shown.dropped} more`] : []].join(
|
|
46227
|
+
"\n"
|
|
46228
|
+
);
|
|
46229
|
+
};
|
|
46230
|
+
let current;
|
|
46231
|
+
if (args.section !== void 0 && args.section !== "") {
|
|
46232
|
+
const q = slugOf(args.section);
|
|
46233
|
+
const pick4 = (all) => {
|
|
46234
|
+
const exact = all.filter((s) => s.slug === q);
|
|
46235
|
+
return exact.length > 0 ? exact : all.filter((s) => s.slug.startsWith(q));
|
|
46236
|
+
};
|
|
46237
|
+
const hitSections = pick4(sections.filter((s) => s.level > 1));
|
|
46238
|
+
const hitRows = hitSections.length > 0 ? [] : pick4(rows);
|
|
46239
|
+
if (hitSections.length + hitRows.length !== 1) {
|
|
46240
|
+
const ambiguous = [...hitSections.map((s) => s.slug), ...hitRows.map((r) => r.slug)];
|
|
46241
|
+
const list2 = listed(ambiguous.length > 1 ? ambiguous.map(address) : navFor(titled));
|
|
46242
|
+
return {
|
|
46243
|
+
error: ambiguous.length > 1 ? `"${args.section}" names more than one part of ${args.address}:
|
|
46244
|
+
${list2}` : `No "${args.section}" in ${args.address}. It holds:
|
|
46245
|
+
${list2}`
|
|
46246
|
+
};
|
|
46247
|
+
}
|
|
46248
|
+
const row = hitRows[0];
|
|
46249
|
+
if (row !== void 0) {
|
|
46250
|
+
return { text: oneNewline([...tableHeader(lines, row.line), lines[row.line]].join("\n")) };
|
|
46251
|
+
}
|
|
46252
|
+
current = hitSections[0];
|
|
46253
|
+
}
|
|
46254
|
+
const whole = linesOf(lines, current).join("\n");
|
|
46255
|
+
if (byteLength(whole) <= PAGE_BYTES) return { text: oneNewline(whole) };
|
|
46256
|
+
const parent = current ?? titled;
|
|
46257
|
+
const nav = navFor(parent);
|
|
46258
|
+
const navText = nav.length === 0 ? "" : `
|
|
46259
|
+
|
|
46260
|
+
This is ${byteLength(whole)} bytes; one page is ${PAGE_BYTES}. It holds:
|
|
46261
|
+
|
|
46262
|
+
${listed(nav)}`;
|
|
46263
|
+
const children = childrenOf(sections, parent);
|
|
46264
|
+
const rowsWithin = parent === void 0 ? rows : rows.filter((r) => r.line > parent.line && r.line < parent.end);
|
|
46265
|
+
const openingTo = children.length > 0 ? children[0].line : rowsWithin.length > 0 ? tableTop(lines, rowsWithin[0].line) : current?.end ?? lines.length;
|
|
46266
|
+
const head = fit(
|
|
46267
|
+
lines.slice(current?.line ?? 0, openingTo),
|
|
46268
|
+
PAGE_BYTES - byteLength(navText) - byteLength(CUT_MARK) - 1
|
|
46269
|
+
);
|
|
46270
|
+
return {
|
|
46271
|
+
text: oneNewline(`${head.kept.join("\n")}${head.dropped > 0 ? CUT_MARK : ""}${navText}`)
|
|
46272
|
+
};
|
|
46273
|
+
}
|
|
46113
46274
|
function onlySelfReference(areas) {
|
|
46114
46275
|
return areas.length > 0 && areas.every((a) => a.pkg === "@lotics/cli");
|
|
46115
46276
|
}
|
|
@@ -46129,6 +46290,7 @@ ${a.pkg} ${a.version}`);
|
|
|
46129
46290
|
lines.push(` ${name2.length >= 24 ? `${name2} ` : name2.padEnd(24)}${a.title}`);
|
|
46130
46291
|
}
|
|
46131
46292
|
lines.push("\n lotics docs <area> print one, e.g. `lotics docs ai`");
|
|
46293
|
+
lines.push(" lotics docs <area>/<section> one section \u2014 a long doc prints its section list");
|
|
46132
46294
|
lines.push(" lotics docs <pkg>/<area> when two packages share a name");
|
|
46133
46295
|
lines.push("\n Versions are the INSTALLED ones; a doc describes the version beside it.");
|
|
46134
46296
|
if (onlySelfReference(areas)) {
|
|
@@ -46145,7 +46307,17 @@ function docsCommand(args) {
|
|
|
46145
46307
|
console.log(renderDocIndex(areas));
|
|
46146
46308
|
return;
|
|
46147
46309
|
}
|
|
46148
|
-
const
|
|
46310
|
+
const asked = args.area.trim();
|
|
46311
|
+
let hits = matchDocArea(areas, asked);
|
|
46312
|
+
let section;
|
|
46313
|
+
if (hits.length === 0 && asked.includes("/")) {
|
|
46314
|
+
const cut = asked.lastIndexOf("/");
|
|
46315
|
+
const head = matchDocArea(areas, asked.slice(0, cut));
|
|
46316
|
+
if (head.length > 0) {
|
|
46317
|
+
hits = head;
|
|
46318
|
+
section = asked.slice(cut + 1);
|
|
46319
|
+
}
|
|
46320
|
+
}
|
|
46149
46321
|
if (hits.length === 0) {
|
|
46150
46322
|
console.error(
|
|
46151
46323
|
`No reference area matching "${args.area}".
|
|
@@ -46156,14 +46328,22 @@ function docsCommand(args) {
|
|
|
46156
46328
|
if (hits.length > 1) {
|
|
46157
46329
|
console.error(
|
|
46158
46330
|
`"${args.area}" matches more than one reference \u2014 name the package:
|
|
46159
|
-
` + hits.map(
|
|
46331
|
+
` + hits.map(
|
|
46332
|
+
(a) => ` \u2022 lotics docs ${qualified(a)}${section === void 0 ? "" : `/${section}`} (${a.pkg} ${a.version})`
|
|
46333
|
+
).join("\n")
|
|
46160
46334
|
);
|
|
46161
46335
|
process.exit(1);
|
|
46162
46336
|
}
|
|
46163
46337
|
const hit = hits[0];
|
|
46338
|
+
const address = areas.filter((a) => a.area === hit.area).length > 1 ? qualified(hit) : hit.area;
|
|
46339
|
+
const page = renderDocPage(fs5.readFileSync(hit.absPath, "utf-8"), { address, section });
|
|
46340
|
+
if ("error" in page) {
|
|
46341
|
+
console.error(page.error);
|
|
46342
|
+
process.exit(1);
|
|
46343
|
+
}
|
|
46164
46344
|
console.error(`${hit.pkg} ${hit.version} \xB7 ${path5.relative(projectDir, hit.absPath)}
|
|
46165
46345
|
`);
|
|
46166
|
-
process.stdout.write(
|
|
46346
|
+
process.stdout.write(page.text);
|
|
46167
46347
|
}
|
|
46168
46348
|
|
|
46169
46349
|
// src/commands.ts
|
|
@@ -46295,7 +46475,8 @@ var COMMANDS = [
|
|
|
46295
46475
|
verbs: ["docs"],
|
|
46296
46476
|
help: [
|
|
46297
46477
|
" lotics docs List the reference docs the npm packages in node_modules ship",
|
|
46298
|
-
" lotics docs <area> Print one (e.g. 'lotics docs ai')"
|
|
46478
|
+
" lotics docs <area> Print one, capped at a page (e.g. 'lotics docs ai')",
|
|
46479
|
+
" lotics docs <area>/<section> One section, or one row of a table doc"
|
|
46299
46480
|
]
|
|
46300
46481
|
},
|
|
46301
46482
|
{
|
|
@@ -46639,7 +46820,6 @@ import { createHash as createHash3 } from "node:crypto";
|
|
|
46639
46820
|
import { tmpdir as tmpdir2 } from "node:os";
|
|
46640
46821
|
|
|
46641
46822
|
// src/starter_template.ts
|
|
46642
|
-
var STARTER_REACT_NATIVE_VERSION = "0.85.3";
|
|
46643
46823
|
var VITEST_SETUP_FILENAME = "vitest.setup.ts";
|
|
46644
46824
|
var VITEST_SETUP_CONTENT = `// Runs before every test file (wired by vite.config.ts \`test.setupFiles\`).
|
|
46645
46825
|
// Empty by default \u2014 put global test setup here: custom matchers, a fetch stub,
|
|
@@ -46675,7 +46855,7 @@ function buildStarterTemplate(args) {
|
|
|
46675
46855
|
// or a broken vitest config, indefinitely. A detector that cannot
|
|
46676
46856
|
// fire when the thing is ABSENT rather than excessive is not one.
|
|
46677
46857
|
//
|
|
46678
|
-
// What the scaffold seeds instead is `src/harness.test.
|
|
46858
|
+
// What the scaffold seeds instead is `src/harness.test.tsx`, which
|
|
46679
46859
|
// asserts the HARNESS and nothing else. The file it replaces
|
|
46680
46860
|
// (`src/App.test.tsx`) asserted the scaffold's placeholder copy
|
|
46681
46861
|
// ("First item"), so it failed the moment the author did the very
|
|
@@ -46693,15 +46873,14 @@ function buildStarterTemplate(args) {
|
|
|
46693
46873
|
// that IMPORTS @lotics/docx or @lotics/xlsx directly (its own doc/excel
|
|
46694
46874
|
// builders) declares its own dependency then.
|
|
46695
46875
|
"@lotics/ui": uiVersion,
|
|
46696
|
-
|
|
46697
|
-
|
|
46876
|
+
// @base-ui/react is deliberately absent: @lotics/ui declares it as an
|
|
46877
|
+
// EXACT peer, npm installs a peer, and a second declaration here is a
|
|
46878
|
+
// version that drifts from the kit's — which Base UI punishes
|
|
46879
|
+
// silently, since it passes state through React context and a nested
|
|
46880
|
+
// copy renders parts that never open.
|
|
46698
46881
|
"lucide-react": "^0.562.0",
|
|
46699
|
-
"lucide-react-native": "^0.562.0",
|
|
46700
46882
|
react: "^19.0.0",
|
|
46701
46883
|
"react-dom": "^19.0.0",
|
|
46702
|
-
"react-native": STARTER_REACT_NATIVE_VERSION,
|
|
46703
|
-
"react-native-svg": "^15.0.0",
|
|
46704
|
-
"react-native-web": "^0.21.0",
|
|
46705
46884
|
// In-app routing for the scaffolded list→detail example. The app owns
|
|
46706
46885
|
// its routing; the SDK's `isEmbedded()` picks memory vs browser history. `react-router` is the
|
|
46707
46886
|
// canonical package (react-router-dom is a shim over it).
|
|
@@ -46750,9 +46929,10 @@ function buildStarterTemplate(args) {
|
|
|
46750
46929
|
strict: true,
|
|
46751
46930
|
noFallthroughCasesInSwitch: true
|
|
46752
46931
|
},
|
|
46753
|
-
// Restrict tsc to the project's own sources
|
|
46754
|
-
//
|
|
46755
|
-
//
|
|
46932
|
+
// Restrict tsc to the project's own sources — a `noEmit` program has
|
|
46933
|
+
// no reason to walk anything else, and under the `LOTICS_UI_SRC`
|
|
46934
|
+
// dev-link `@lotics/ui` resolves to a working copy of the kit's own
|
|
46935
|
+
// .tsx, which strict rules (noUnusedLocals etc.) would then fire on.
|
|
46756
46936
|
// `.lotics/**/*` (not bare `.lotics`): TypeScript's include glob walk
|
|
46757
46937
|
// SKIPS dot-directories, so a bare `.lotics` silently loads zero of the
|
|
46758
46938
|
// generated `.d.ts` augmentations and typed useWorkflow/useQuery/useAgentRun
|
|
@@ -46780,87 +46960,25 @@ function buildStarterTemplate(args) {
|
|
|
46780
46960
|
path: "vite.config.ts",
|
|
46781
46961
|
content: `/// <reference types="vitest" />
|
|
46782
46962
|
import { defineConfig } from "vite";
|
|
46783
|
-
import type { Plugin } from "vite";
|
|
46784
46963
|
import react from "@vitejs/plugin-react";
|
|
46785
|
-
import {
|
|
46786
|
-
|
|
46787
|
-
// @lotics/ui/icon.tsx deep-imports \`lucide-react-native/dist/esm/icons/<name>\`.
|
|
46788
|
-
// lucide-react-native's \`exports\` map lists only "." and "./icons", so a strict
|
|
46789
|
-
// resolver (the sandbox's vite dev optimizer) blocks the deep path with
|
|
46790
|
-
// \`Missing "./dist/esm/icons/<name>" specifier\`. lucide-react ships the same
|
|
46791
|
-
// per-icon files with NO exports map, so redirect there. A \`resolveId\` PLUGIN
|
|
46792
|
-
// (not \`resolve.alias\`) is required: the dep-optimizer SCAN runs plugin
|
|
46793
|
-
// resolveId hooks but does NOT apply regex \`resolve.alias\`, so the alias alone
|
|
46794
|
-
// fixes the production build but leaves the dev preview broken.
|
|
46795
|
-
function lucideIconsWebAlias(): Plugin {
|
|
46796
|
-
return {
|
|
46797
|
-
name: "lucide-icons-web-alias",
|
|
46798
|
-
enforce: "pre",
|
|
46799
|
-
async resolveId(source, importer, options) {
|
|
46800
|
-
const m = /^lucide-react-native\\/dist\\/esm\\/icons\\/(.+)$/.exec(source);
|
|
46801
|
-
if (!m) return null;
|
|
46802
|
-
return this.resolve(\`lucide-react/dist/esm/icons/\${m[1]}\`, importer, {
|
|
46803
|
-
...options,
|
|
46804
|
-
skipSelf: true,
|
|
46805
|
-
});
|
|
46806
|
-
},
|
|
46807
|
-
};
|
|
46808
|
-
}
|
|
46964
|
+
import { loticsResolve } from "@lotics/ui/vite";
|
|
46809
46965
|
|
|
46810
46966
|
// Vite default base (/) emits absolute asset URLs in index.html. Lotics's
|
|
46811
46967
|
// render endpoint rewrites those to /v1/apps/{id}/asset/... so the bundle
|
|
46812
46968
|
// loads via the platform's asset proxy. Don't change \`base\` unless you
|
|
46813
46969
|
// also adjust the rewrite logic in backend/api/apps.ts:rewriteAssetPaths.
|
|
46814
46970
|
export default defineConfig({
|
|
46815
|
-
plugins: [
|
|
46816
|
-
//
|
|
46817
|
-
//
|
|
46818
|
-
//
|
|
46819
|
-
//
|
|
46820
|
-
// interrupted/torn down \u2014 e.g. toggling @lotics/ui's Switch. rollup hits it;
|
|
46821
|
-
// the dev esbuild path masks it, so it only surfaces in the deployed app.
|
|
46822
|
-
// rn-web only reads \`global.x\` as a free var, so mapping it to \`globalThis\`
|
|
46823
|
-
// is safe. Define both so dev and the deployed build behave identically.
|
|
46824
|
-
define: { __DEV__: "false", global: "globalThis" },
|
|
46825
|
-
// The kit owns its own resolution \u2014 the react-native \u2192 react-native-web alias,
|
|
46826
|
-
// \`.web.tsx\`-first extensions, the React dedupe, and the \`LOTICS_UI_SRC\`
|
|
46827
|
-
// dev-link \u2014 for the same reason it owns \`loticsOptimizeDeps\`: every entry is
|
|
46828
|
-
// dictated by @lotics/ui's internals, so shipping it WITH the kit means it can
|
|
46829
|
-
// never drift from the version installed here. It also stops being an app's job
|
|
46830
|
-
// to hand-carry a load-bearing alias that a regex edit twice deleted (GAP-133/142).
|
|
46971
|
+
plugins: [react()],
|
|
46972
|
+
// The kit owns this block: the \`LOTICS_UI_SRC\` dev-link and the React dedupe
|
|
46973
|
+
// are decided by @lotics/ui's own delivery, so shipping it WITH the kit means
|
|
46974
|
+
// it can never drift from the version installed here \u2014 and it stops being an
|
|
46975
|
+
// app's job to hand-carry a line a regex edit twice deleted (GAP-133/142).
|
|
46831
46976
|
// \`lotics app codegen\` writes the matching \`paths\` into
|
|
46832
|
-
// .lotics/tsconfig.link.json, so tsc/vitest/eslint/your editor resolve the
|
|
46833
|
-
// @lotics/ui this does. To add your own alias, spread it:
|
|
46977
|
+
// .lotics/tsconfig.link.json, so tsc/vitest/eslint/your editor resolve the
|
|
46978
|
+
// same @lotics/ui this does. To add your own alias, spread it:
|
|
46834
46979
|
// const base = loticsResolve();
|
|
46835
46980
|
// resolve: { ...base, alias: [...base.alias, { find: "x", replacement: "y" }] }
|
|
46836
46981
|
resolve: loticsResolve(),
|
|
46837
|
-
optimizeDeps: {
|
|
46838
|
-
// The dev dep-optimizer must pre-bundle @lotics/ui's RN-ecosystem + markdown
|
|
46839
|
-
// CJS-interop deps or \`lotics app dev\` blanks the iframe ("does not provide an
|
|
46840
|
-
// export named 'default'/'parse'"). Dev-ONLY: the prod rollup build resolves the
|
|
46841
|
-
// interop without it, so typecheck/lint/build stay green while the dev iframe
|
|
46842
|
-
// goes blank. The canonical list ships WITH @lotics/ui as \`loticsOptimizeDeps\`
|
|
46843
|
-
// (imported above) so it can never drift from what the kit's transitive deps
|
|
46844
|
-
// require across a major bump \u2014 the per-family rationale lives in that module.
|
|
46845
|
-
// To add app-specific entries, spread: include: [...loticsOptimizeDeps, "my-dep"].
|
|
46846
|
-
include: loticsOptimizeDeps,
|
|
46847
|
-
// The kit is ONE module graph, so it must never be pre-bundled: the optimizer
|
|
46848
|
-
// auto-discovers each @lotics/ui subpath and inlines a private copy of every
|
|
46849
|
-
// context-owning module it reaches (locale, tooltip, portal, popover), leaving
|
|
46850
|
-
// the provider filling one context and the component reading another.
|
|
46851
|
-
exclude: loticsOptimizeExclude,
|
|
46852
|
-
// The dep optimizer pre-bundles deps with a SEPARATE esbuild pass that
|
|
46853
|
-
// top-level \`define\` doesn't always reach, so a pre-bundled RN dep can
|
|
46854
|
-
// still hit \`__DEV__ is not defined\` under \`lotics app dev\`. Define it
|
|
46855
|
-
// here too \u2014 belt-and-suspenders with the top-level \`define\` above.
|
|
46856
|
-
// The optimizer pre-bundles with a SEPARATE esbuild resolver that ignores
|
|
46857
|
-
// \`resolve.extensions\`, so \`.web.js\` must be repeated here or a pre-bundled
|
|
46858
|
-
// RN dep still resolves \`./Picker\` to the native build.
|
|
46859
|
-
esbuildOptions: {
|
|
46860
|
-
define: { __DEV__: "false", global: "globalThis" },
|
|
46861
|
-
resolveExtensions: [".web.tsx", ".web.ts", ".web.js", ".tsx", ".ts", ".jsx", ".js", ".json", ".mjs", ".mts"],
|
|
46862
|
-
},
|
|
46863
|
-
},
|
|
46864
46982
|
build: {
|
|
46865
46983
|
outDir: "dist",
|
|
46866
46984
|
// No source maps in the production build: they ship the original source
|
|
@@ -46886,11 +47004,6 @@ export default defineConfig({
|
|
|
46886
47004
|
// cross-site WS hijack fix) \u2014 without this entry the proxied preview page
|
|
46887
47005
|
// loads over HTTP but HMR never connects, so edits stop live-updating.
|
|
46888
47006
|
allowedHosts: [".lotics-sandbox.app"],
|
|
46889
|
-
// @lotics/ui/fonts.css references the API's /iframe/fonts/*.woff2 files
|
|
46890
|
-
// by root-relative URL. A deployed app is served from the API origin so
|
|
46891
|
-
// they resolve directly; under \`lotics app dev\` the app runs on
|
|
46892
|
-
// localhost, so proxy /iframe to the API to load the real fonts.
|
|
46893
|
-
proxy: { "/iframe": { target: "https://api.lotics.ai", changeOrigin: true } },
|
|
46894
47007
|
},
|
|
46895
47008
|
test: {
|
|
46896
47009
|
environment: "jsdom",
|
|
@@ -46899,39 +47012,31 @@ export default defineConfig({
|
|
|
46899
47012
|
// EXIST either way: setupFiles naming a missing file makes vitest fail to
|
|
46900
47013
|
// load every test. See vitest.setup.ts.
|
|
46901
47014
|
setupFiles: ["./${VITEST_SETUP_FILENAME}"],
|
|
46902
|
-
//
|
|
46903
|
-
//
|
|
46904
|
-
//
|
|
46905
|
-
//
|
|
46906
|
-
//
|
|
46907
|
-
//
|
|
46908
|
-
//
|
|
46909
|
-
//
|
|
47015
|
+
// @lotics/ui ships built ESM whose every component module carries the
|
|
47016
|
+
// literal \`import "./button.css"\` that delivers its sheet. Vitest hands
|
|
47017
|
+
// node_modules to Node's ESM loader, which has no loader for \`.css\` \u2014 so
|
|
47018
|
+
// without this line EVERY suite that touches a kit component fails to load
|
|
47019
|
+
// with \`Unknown file extension ".css"\`, before a single assertion runs.
|
|
47020
|
+
// Inlining routes those modules through Vite's transform instead, where a
|
|
47021
|
+
// CSS import is inert under jsdom. Any package whose modules import CSS
|
|
47022
|
+
// needs the same entry.
|
|
47023
|
+
server: { deps: { inline: [/@lotics\\/ui/] } },
|
|
46910
47024
|
deps: {
|
|
46911
47025
|
optimizer: {
|
|
46912
47026
|
web: {
|
|
46913
47027
|
enabled: true,
|
|
46914
|
-
//
|
|
46915
|
-
//
|
|
46916
|
-
//
|
|
46917
|
-
//
|
|
46918
|
-
//
|
|
47028
|
+
// Everything here ships COMPILED JS, so vitest pre-bundles each into
|
|
47029
|
+
// its own chunk \u2014 and a chunk carrying its own react import splits the
|
|
47030
|
+
// instance and nulls the hooks dispatcher the moment a rendered tree
|
|
47031
|
+
// calls one ("Cannot read properties of null (reading 'useState')").
|
|
47032
|
+
// They ride in ONE optimized chunk with react and the renderer instead.
|
|
46919
47033
|
include: [
|
|
46920
47034
|
"react",
|
|
46921
47035
|
"react-dom",
|
|
46922
47036
|
"react-dom/client",
|
|
46923
47037
|
"react-dom/test-utils",
|
|
46924
|
-
"react-native",
|
|
46925
|
-
"react-native-web",
|
|
46926
|
-
"react-native-svg",
|
|
46927
|
-
"@react-native-picker/picker",
|
|
46928
47038
|
"@testing-library/react",
|
|
46929
47039
|
"@testing-library/dom",
|
|
46930
|
-
// @lotics/app-sdk ships compiled dist/ JS (unlike @lotics/ui, which
|
|
46931
|
-
// ships source vitest transforms inline) \u2014 pre-bundled into its own
|
|
46932
|
-
// chunk, its react import splits from the shared instance and nulls
|
|
46933
|
-
// the hooks dispatcher the instant a hook (useAgentRun / useQuery)
|
|
46934
|
-
// runs in a rendered App tree.
|
|
46935
47040
|
"@lotics/app-sdk",
|
|
46936
47041
|
// The /router subpath is a SEPARATE optimizer entry \u2014 without it,
|
|
46937
47042
|
// AppRouter's BrowserRouter bundles apart from the app's
|
|
@@ -46943,9 +47048,6 @@ export default defineConfig({
|
|
|
46943
47048
|
// ("Invalid hook call") when a test renders the routed App tree.
|
|
46944
47049
|
"react-router",
|
|
46945
47050
|
],
|
|
46946
|
-
esbuildOptions: {
|
|
46947
|
-
resolveExtensions: [".web.tsx", ".web.ts", ".web.js", ".tsx", ".ts", ".jsx", ".js", ".json", ".mjs", ".mts"],
|
|
46948
|
-
},
|
|
46949
47051
|
},
|
|
46950
47052
|
},
|
|
46951
47053
|
},
|
|
@@ -46969,8 +47071,8 @@ export default defineConfig({
|
|
|
46969
47071
|
* default of html/body being content-sized collapses every \`flex: 1\`
|
|
46970
47072
|
* to 0 and the surface shrinks to fit only its rendered content.
|
|
46971
47073
|
*
|
|
46972
|
-
* Also: #root is
|
|
46973
|
-
*
|
|
47074
|
+
* Also: #root is a flex column, so App.tsx's outermost \`flex: 1\` box
|
|
47075
|
+
* claims the full iframe height.
|
|
46974
47076
|
*/
|
|
46975
47077
|
html, body, #root { height: 100%; margin: 0; }
|
|
46976
47078
|
#root { display: flex; flex-direction: column; }
|
|
@@ -46985,45 +47087,47 @@ export default defineConfig({
|
|
|
46985
47087
|
},
|
|
46986
47088
|
{
|
|
46987
47089
|
path: "src/main.tsx",
|
|
46988
|
-
content: `import "@lotics/ui/
|
|
47090
|
+
content: `import "@lotics/ui/styles.css";
|
|
46989
47091
|
import "@lotics/ui/fonts.css";
|
|
46990
|
-
import { PortalHost } from "@lotics/ui/portal";
|
|
46991
47092
|
import { LoticsLocaleProvider, vi } from "@lotics/ui/locale";
|
|
46992
47093
|
import { mount } from "@lotics/app-sdk";
|
|
46993
47094
|
import App from "./App";
|
|
46994
47095
|
|
|
47096
|
+
// \`styles.css\` is not optional. It carries the cascade-layer order, every design
|
|
47097
|
+
// token and the reset, and the kit's chrome reads those variables with NO inline
|
|
47098
|
+
// fallback \u2014 without it every colour, size and radius is invalid and the screen
|
|
47099
|
+
// renders unstyled but working.
|
|
47100
|
+
//
|
|
46995
47101
|
// THIS APP LOOKS LIKE LOTICS unless you say otherwise, which is the right
|
|
46996
47102
|
// default: the kit's own palette and typeface are what every screen in the
|
|
46997
47103
|
// product wears, and an app that sets nothing belongs with them.
|
|
46998
47104
|
//
|
|
46999
|
-
// If this app carries someone else's brand
|
|
47000
|
-
//
|
|
47001
|
-
// \`
|
|
47105
|
+
// If this app carries someone else's brand, a theme is a STYLESHEET \u2014 there is
|
|
47106
|
+
// no theme function. Declare the shadcn/ui variables in your own CSS, imported
|
|
47107
|
+
// after \`styles.css\`; an unlayered rule of yours beats every layered kit rule at
|
|
47108
|
+
// any specificity, with no \`!important\`:
|
|
47002
47109
|
//
|
|
47003
|
-
//
|
|
47004
|
-
//
|
|
47110
|
+
// :root, .light { --primary: #0F766E; --primary-foreground: #fff; --ring: #0F766E; }
|
|
47111
|
+
// .dark { --primary: #5EEAD4; --primary-foreground: #042F2E; --ring: #5EEAD4; }
|
|
47005
47112
|
//
|
|
47006
|
-
//
|
|
47007
|
-
//
|
|
47113
|
+
// Paste BOTH blocks of whatever palette you start from: a token set only on
|
|
47114
|
+
// \`:root\` keeps its light value in dark mode, because your unlayered rule also
|
|
47115
|
+
// beats the kit's \`.dark\`. The roles worth knowing:
|
|
47008
47116
|
//
|
|
47009
|
-
//
|
|
47010
|
-
//
|
|
47011
|
-
//
|
|
47012
|
-
//
|
|
47013
|
-
// border
|
|
47014
|
-
//
|
|
47015
|
-
//
|
|
47016
|
-
//
|
|
47017
|
-
//
|
|
47018
|
-
//
|
|
47117
|
+
// --primary this is on \u2014 the filled button, a checkbox, an active tab's rail
|
|
47118
|
+
// --lotics-accent which of these \u2014 a selected row, an active filter (themed: set
|
|
47119
|
+
// it in both blocks, beside --primary)
|
|
47120
|
+
// --card the raised surface (Card, Drawer, Dialog)
|
|
47121
|
+
// --border hairlines \u2014 a register is mostly these, so a small change
|
|
47122
|
+
// here is felt against the whole screen
|
|
47123
|
+
// --font-sans the typeface for everything (a stack you know RESOLVES, and that
|
|
47124
|
+
// carries Vietnamese diacritics \u2014 a face without them turns
|
|
47125
|
+
// "Th\u1EA9m \u0111\u1ECBnh" into tofu). Declare its @font-face in your own CSS.
|
|
47126
|
+
// --lotics-font-display display type only; defaults to --font-sans
|
|
47019
47127
|
//
|
|
47020
|
-
//
|
|
47021
|
-
//
|
|
47022
|
-
//
|
|
47023
|
-
// semibold stay in the kit's face and the screen reads as a font that half-loaded.
|
|
47024
|
-
//
|
|
47025
|
-
// PortalHost is the render target for @lotics/ui overlays (Popover, Tooltip,
|
|
47026
|
-
// Dialog). Without it, Portal renders nothing \u2014 keep it wrapping the app.
|
|
47128
|
+
// Everything derived \u2014 the selection wash, hover and press shades, the hairline
|
|
47129
|
+
// ladder \u2014 is computed in CSS from those, so there is nothing to keep in step by
|
|
47130
|
+
// hand. The full contract is \`node_modules/@lotics/ui/docs/theming.md\`.
|
|
47027
47131
|
//
|
|
47028
47132
|
// LoticsLocaleProvider supplies the kit's own strings (Pagination, sort
|
|
47029
47133
|
// headers, select-all, the Drawer's record nav\u2026) in one language \u2014 Vietnamese
|
|
@@ -47031,9 +47135,7 @@ import App from "./App";
|
|
|
47031
47135
|
// LoticsLocale pack) for another language; remove it to fall back to English.
|
|
47032
47136
|
mount(
|
|
47033
47137
|
<LoticsLocaleProvider locale={vi}>
|
|
47034
|
-
<
|
|
47035
|
-
<App />
|
|
47036
|
-
</PortalHost>
|
|
47138
|
+
<App />
|
|
47037
47139
|
</LoticsLocaleProvider>,
|
|
47038
47140
|
);
|
|
47039
47141
|
`
|
|
@@ -47047,21 +47149,22 @@ mount(
|
|
|
47047
47149
|
// host never sees it, so navigation never reloads the app); standalone,
|
|
47048
47150
|
// real path URLs. Browser Back/Forward walk app screens in both.
|
|
47049
47151
|
// - The full-container layout pattern is preserved in `Screen`: an outer
|
|
47050
|
-
//
|
|
47051
|
-
// html/body/#root to 100% + #root is a flex column).
|
|
47052
|
-
//
|
|
47053
|
-
//
|
|
47152
|
+
// `flex: 1` box claims the iframe height (index.html sets
|
|
47153
|
+
// html/body/#root to 100% + #root is a flex column). It is a plain
|
|
47154
|
+
// `<div>` rather than a kit primitive because that chain has to stay
|
|
47155
|
+
// unbroken — `Stack` is the right thing INSIDE it, where the gutter is
|
|
47156
|
+
// what you want, not around a fill-remaining-space child.
|
|
47054
47157
|
// A single-screen app can delete the router and render one Screen directly.
|
|
47055
47158
|
content: `import type { ReactNode } from "react";
|
|
47056
|
-
import { View } from "react-native";
|
|
47057
47159
|
import { useNavigate, useParams } from "react-router";
|
|
47058
47160
|
import { AppRouter } from "@lotics/app-sdk/router";
|
|
47059
47161
|
import { useAiContext } from "@lotics/app-sdk";
|
|
47060
47162
|
import { Card } from "@lotics/ui/card";
|
|
47163
|
+
import { Stack } from "@lotics/ui/stack";
|
|
47061
47164
|
import { Text } from "@lotics/ui/text";
|
|
47062
47165
|
import { Button } from "@lotics/ui/button";
|
|
47063
47166
|
import { BackButton } from "@lotics/ui/back_button";
|
|
47064
|
-
import {
|
|
47167
|
+
import { RegionState } from "@lotics/ui/region_state";
|
|
47065
47168
|
|
|
47066
47169
|
// AppRouter makes the app's screens real URLs \u2014 write plain react-router
|
|
47067
47170
|
// (useNavigate / useParams / <Link>) and it handles both modes. The app owns its
|
|
@@ -47078,14 +47181,16 @@ const ITEMS = [
|
|
|
47078
47181
|
{ id: "3", name: "Third item" },
|
|
47079
47182
|
];
|
|
47080
47183
|
|
|
47081
|
-
//
|
|
47082
|
-
//
|
|
47083
|
-
//
|
|
47184
|
+
// The outer box claims the full iframe height \u2014 it works because index.html sets
|
|
47185
|
+
// html/body/#root to 100% and #root is a flex column. Keep this chain plain: a
|
|
47186
|
+
// box that says nothing about \`flex\` is what breaks it.
|
|
47084
47187
|
function Screen({ children }: { children: ReactNode }) {
|
|
47085
47188
|
return (
|
|
47086
|
-
<
|
|
47087
|
-
<
|
|
47088
|
-
|
|
47189
|
+
<div style={{ flex: 1, padding: 24, display: "flex", justifyContent: "center" }}>
|
|
47190
|
+
<Stack gap={16} style={{ maxWidth: 640, width: "100%" }}>
|
|
47191
|
+
{children}
|
|
47192
|
+
</Stack>
|
|
47193
|
+
</div>
|
|
47089
47194
|
);
|
|
47090
47195
|
}
|
|
47091
47196
|
|
|
@@ -47101,18 +47206,16 @@ function ListScreen() {
|
|
|
47101
47206
|
</Text>
|
|
47102
47207
|
{ITEMS.map((item) => (
|
|
47103
47208
|
<Card key={item.id}>
|
|
47104
|
-
<
|
|
47105
|
-
|
|
47106
|
-
|
|
47107
|
-
|
|
47108
|
-
|
|
47109
|
-
|
|
47110
|
-
gap: 12,
|
|
47111
|
-
}}
|
|
47209
|
+
<Stack
|
|
47210
|
+
direction="row"
|
|
47211
|
+
gap={8}
|
|
47212
|
+
align="center"
|
|
47213
|
+
justify="between"
|
|
47214
|
+
style={{ padding: 16 }}
|
|
47112
47215
|
>
|
|
47113
47216
|
<Text weight="medium">{item.name}</Text>
|
|
47114
47217
|
<Button title="Open" color="primary" onPress={() => navigate("/item/" + item.id)} />
|
|
47115
|
-
</
|
|
47218
|
+
</Stack>
|
|
47116
47219
|
</Card>
|
|
47117
47220
|
))}
|
|
47118
47221
|
</Screen>
|
|
@@ -47146,20 +47249,20 @@ function ItemDetailScreen() {
|
|
|
47146
47249
|
\`<Button title="Back">\` with no \`color\` draws NOTHING at rest \u2014 no
|
|
47147
47250
|
ground, no border, no underline \u2014 so its only affordance is a hover
|
|
47148
47251
|
wash, which touch and keyboard never see. BackButton also carries its
|
|
47149
|
-
own \`alignSelf\`, so it needs no wrapping
|
|
47252
|
+
own \`alignSelf\`, so it needs no wrapping box to sit left. */}
|
|
47150
47253
|
<BackButton onPress={() => navigate(-1)} />
|
|
47151
47254
|
<Text size="xl" weight="semibold">{item ? item.name : "Not found"}</Text>
|
|
47152
47255
|
{/* Every state gets its own content. Swapping only the TITLE left the card
|
|
47153
47256
|
below still describing the record the title had just said was missing. */}
|
|
47154
47257
|
{item ? (
|
|
47155
47258
|
<Card>
|
|
47156
|
-
<
|
|
47259
|
+
<Stack gap={4} style={{ padding: 16 }}>
|
|
47157
47260
|
<Text>Detail for item {id}.</Text>
|
|
47158
47261
|
<Text color="muted">Reached via in-app navigation, not a host route.</Text>
|
|
47159
|
-
</
|
|
47262
|
+
</Stack>
|
|
47160
47263
|
</Card>
|
|
47161
47264
|
) : (
|
|
47162
|
-
<
|
|
47265
|
+
<RegionState state="empty" message={"No item with id " + id + "."} />
|
|
47163
47266
|
)}
|
|
47164
47267
|
</Screen>
|
|
47165
47268
|
);
|
|
@@ -47230,25 +47333,6 @@ alias the binding is keyed by).
|
|
|
47230
47333
|
capabilities only. \`apps.workflows\` has exactly one author: \`set_app_workflow\`
|
|
47231
47334
|
(which \`lotics app workflow set\` calls). The typed \`inputs\`/\`outputs\` schema for
|
|
47232
47335
|
each alias lives in \`package.json#lotics.workflows.<alias>\`.
|
|
47233
|
-
`
|
|
47234
|
-
},
|
|
47235
|
-
{
|
|
47236
|
-
path: "src/lucide-react-native.d.ts",
|
|
47237
|
-
content: `// Type shim for lucide-react-native's deep-icon imports
|
|
47238
|
-
// (\`lucide-react-native/dist/esm/icons/<name>\`). The package ships JS-only
|
|
47239
|
-
// files without per-file .d.ts. The lucideIconsWebAlias() plugin in
|
|
47240
|
-
// vite.config.ts redirects these to lucide-react at resolve time; this
|
|
47241
|
-
// declaration covers the TypeScript compile-time gap.
|
|
47242
|
-
declare module "lucide-react-native/dist/esm/icons/*" {
|
|
47243
|
-
import type { ComponentType } from "react";
|
|
47244
|
-
const Icon: ComponentType<{
|
|
47245
|
-
size: number;
|
|
47246
|
-
color: string;
|
|
47247
|
-
fill?: string;
|
|
47248
|
-
strokeWidth?: number;
|
|
47249
|
-
}>;
|
|
47250
|
-
export default Icon;
|
|
47251
|
-
}
|
|
47252
47336
|
`
|
|
47253
47337
|
},
|
|
47254
47338
|
{
|
|
@@ -47258,13 +47342,16 @@ declare module "lucide-react-native/dist/esm/icons/*" {
|
|
|
47258
47342
|
export default classes;
|
|
47259
47343
|
}
|
|
47260
47344
|
|
|
47261
|
-
// Plain side-effect CSS imports
|
|
47345
|
+
// Plain side-effect CSS imports \u2014 @lotics/ui/styles.css, and any sheet of
|
|
47346
|
+
// your own.
|
|
47262
47347
|
declare module "*.css";
|
|
47263
47348
|
`
|
|
47264
47349
|
},
|
|
47265
47350
|
{
|
|
47266
|
-
path: "src/harness.test.
|
|
47351
|
+
path: "src/harness.test.tsx",
|
|
47267
47352
|
content: `import { describe, test, expect } from "vitest";
|
|
47353
|
+
import { render } from "@testing-library/react";
|
|
47354
|
+
import { Text } from "@lotics/ui/text";
|
|
47268
47355
|
|
|
47269
47356
|
/**
|
|
47270
47357
|
* The one test the scaffold seeds, and it is about the TEST SETUP rather than
|
|
@@ -47272,11 +47359,19 @@ declare module "*.css";
|
|
|
47272
47359
|
* never has to be green over zero tests. It imports nothing of yours and
|
|
47273
47360
|
* nothing generated, so it passes on a fresh clone before any codegen has run.
|
|
47274
47361
|
*
|
|
47362
|
+
* It renders a KIT component deliberately. @lotics/ui ships built ESM whose
|
|
47363
|
+
* modules import their own \`.css\`, and a vitest that externalizes them dies
|
|
47364
|
+
* with \`Unknown file extension ".css"\` on every suite that touches one \u2014 a
|
|
47365
|
+
* whole-suite failure that a harness test importing only vitest would never
|
|
47366
|
+
* see. \`test.server.deps.inline\` in vite.config.ts is what prevents it; this
|
|
47367
|
+
* is what notices when that line goes.
|
|
47368
|
+
*
|
|
47275
47369
|
* Delete it once you have tests of your own, or keep it; it costs nothing.
|
|
47276
47370
|
*/
|
|
47277
47371
|
describe("test harness", () => {
|
|
47278
|
-
test("
|
|
47279
|
-
|
|
47372
|
+
test("renders a kit component in a DOM", () => {
|
|
47373
|
+
const { container } = render(<Text>ok</Text>);
|
|
47374
|
+
expect(container.textContent).toBe("ok");
|
|
47280
47375
|
});
|
|
47281
47376
|
});
|
|
47282
47377
|
`
|
|
@@ -47317,6 +47412,59 @@ dist
|
|
|
47317
47412
|
.DS_Store
|
|
47318
47413
|
.lotics
|
|
47319
47414
|
coverage
|
|
47415
|
+
`
|
|
47416
|
+
},
|
|
47417
|
+
{
|
|
47418
|
+
path: "AGENTS.md",
|
|
47419
|
+
content: `# ${args.app_name} \u2014 for the agent editing this app
|
|
47420
|
+
|
|
47421
|
+
A Lotics custom-code app: React + Vite, data through \`@lotics/app-sdk\`, every pixel from
|
|
47422
|
+
\`@lotics/ui\`. Both are installed here \u2014 read them from \`node_modules\`, never from memory.
|
|
47423
|
+
|
|
47424
|
+
## Read in this order, and stop when you have the answer
|
|
47425
|
+
|
|
47426
|
+
1. **\`node_modules/@lotics/ui/llms.txt\`** \u2014 every kit entry by the data role it answers, its
|
|
47427
|
+
import, and the one question that says where it does NOT go. One read, ~12k tokens.
|
|
47428
|
+
**Start here for any UI question**, before writing a component of your own.
|
|
47429
|
+
2. **\`node_modules/@lotics/ui/dist/<module>.d.ts\`** \u2014 the props of the entry you picked, with the
|
|
47430
|
+
source's own doc comments. **Never guess a prop; open the declaration.**
|
|
47431
|
+
3. **\`lotics docs ui/catalog/<Entry>\`** \u2014 that entry's full wrong-place test, one row.
|
|
47432
|
+
**Never read \`catalog.md\` whole**: it is 334 entries, and you need one.
|
|
47433
|
+
4. **\`node_modules/@lotics/ui/examples/tpl_*.tsx\`** \u2014 a whole screen as source, when the SHAPE is
|
|
47434
|
+
the question. Copy and adapt; never import from \`examples/\`.
|
|
47435
|
+
5. **\`node_modules/@lotics/app-sdk/AGENTS.md\`** \u2014 queries, workflows, files, AI.
|
|
47436
|
+
|
|
47437
|
+
\`lotics docs\` lists what is installed here at the versions installed, so a reference always
|
|
47438
|
+
describes the code beside it. Every answer is capped at a page; a long one hands back its section
|
|
47439
|
+
list, and \`lotics docs <area>/<section>\` prints one section.
|
|
47440
|
+
|
|
47441
|
+
## The rules that do not change
|
|
47442
|
+
|
|
47443
|
+
- **Reuse before you build.** A capability the kit lacks is added to the kit, never hand-rolled
|
|
47444
|
+
here. Search \`llms.txt\` for the role before writing a component.
|
|
47445
|
+
- **Customize through TOKENS and PROPS.** Tokens are the shadcn variables, the space rungs, the
|
|
47446
|
+
radius ladder and the type ramp; props are \`variant\`, \`size\`, \`tone\`, \`render\` and slots.
|
|
47447
|
+
\`className\`/\`style\` place an entry in your layout \u2014 they do not restyle it. The kit's DOM and
|
|
47448
|
+
class names are private, so CSS that reaches inside an entry breaks on its next release.
|
|
47449
|
+
- **\`import "@lotics/ui/styles.css"\` once**, in \`src/main.tsx\` \u2014 it carries every token the kit's
|
|
47450
|
+
\`var()\`s read, and without it the screen renders unstyled but working.
|
|
47451
|
+
- **A workflow is the only way this app writes.** Queries read; \`useWorkflow\` writes.
|
|
47452
|
+
|
|
47453
|
+
## Before you commit \u2014 six checks
|
|
47454
|
+
|
|
47455
|
+
1. **One value, one rendering.** A chart draws the entity its table draws, and a value's filter
|
|
47456
|
+
renders it the way its cell does.
|
|
47457
|
+
2. **Every figure is formatted, by the reader's locale** \u2014 money, dates, quantities. A number
|
|
47458
|
+
joined to a string is a number in somebody else's convention.
|
|
47459
|
+
3. **One accent.** Everything else is neutral or a valence, whose colour IS the meaning.
|
|
47460
|
+
4. **Every gap is a rung and the ratio is 1 : 3 : 6**: 4 inside a pair, 8 inside a group, 24
|
|
47461
|
+
between bands, 48 between sections.
|
|
47462
|
+
5. **No bare native control.** A \`<button>\`, \`<input>\`, \`<select>\` or \`<table>\` in your source is a
|
|
47463
|
+
control outside the type ramp, the locale and the keyboard contract.
|
|
47464
|
+
6. **No raw id reaches the reader.** \`rec_\`, \`opt_\` and \`fld_\` are keys; a cell shows the name.
|
|
47465
|
+
|
|
47466
|
+
Then \`npm run typecheck && npm run lint && npm test\`, and \`lotics app deploy\`.
|
|
47467
|
+
The full review \u2014 the gates, the probes, the measurement snippets \u2014 is \`lotics docs ui/reviewing\`.
|
|
47320
47468
|
`
|
|
47321
47469
|
},
|
|
47322
47470
|
{
|
|
@@ -47357,12 +47505,13 @@ import { Stack } from "@lotics/ui/stack";
|
|
|
47357
47505
|
import { Card } from "@lotics/ui/card";
|
|
47358
47506
|
import { Button } from "@lotics/ui/button";
|
|
47359
47507
|
import { Text } from "@lotics/ui/text";
|
|
47360
|
-
// ... and Grid, Dialog, DatePicker,
|
|
47508
|
+
// ... and Grid, Dialog, DatePicker, Select, BarChart, LineChart, PieChart, Metric, Progress, etc.
|
|
47361
47509
|
\`\`\`
|
|
47362
47510
|
|
|
47363
|
-
@lotics/ui ships React
|
|
47364
|
-
|
|
47365
|
-
|
|
47511
|
+
@lotics/ui ships built React DOM components with their own CSS \u2014 import
|
|
47512
|
+
\`@lotics/ui/styles.css\` once (main.tsx does) and every one of them is themed
|
|
47513
|
+
and addressable. Which entry answers which role is
|
|
47514
|
+
\`node_modules/@lotics/ui/llms.txt\`, one read.
|
|
47366
47515
|
|
|
47367
47516
|
## Routing
|
|
47368
47517
|
|
|
@@ -47375,11 +47524,15 @@ shareable and survives a full refresh. Standalone (\`<slug>.lotics.app\`) it's a
|
|
|
47375
47524
|
normal browser router with real path URLs. A single-screen app can drop the
|
|
47376
47525
|
router and render one screen directly.
|
|
47377
47526
|
|
|
47378
|
-
|
|
47379
|
-
|
|
47380
|
-
|
|
47381
|
-
|
|
47382
|
-
|
|
47527
|
+
## References
|
|
47528
|
+
|
|
47529
|
+
\`AGENTS.md\` in this directory is the reading order and the six checks before a
|
|
47530
|
+
commit. The references themselves ship inside the packages, so they always
|
|
47531
|
+
describe the versions installed here: \`node_modules/@lotics/ui/llms.txt\` and
|
|
47532
|
+
\`node_modules/@lotics/app-sdk/AGENTS.md\` are readable with no tooling at all.
|
|
47533
|
+
With the CLI on PATH, \`lotics docs\` indexes the same set and prints one page at
|
|
47534
|
+
a time \u2014 \`lotics docs building_an_app\` is the sequence, read once before
|
|
47535
|
+
building.
|
|
47383
47536
|
`
|
|
47384
47537
|
}
|
|
47385
47538
|
];
|
|
@@ -73643,8 +73796,8 @@ function warnAboutDevLink(projectDir, command) {
|
|
|
73643
73796
|
const linked = config2.includes("loticsResolve") || config2.includes("LOTICS_UI_SRC");
|
|
73644
73797
|
if (!linked) {
|
|
73645
73798
|
console.error(
|
|
73646
|
-
`\u26A0 LOTICS_UI_SRC is set but vite.config.ts never reads it \u2014 @lotics/ui will still resolve from node_modules, so kit edits will NOT ${command === "deploy" ? "ship" : "appear"}. Hand the whole resolve block to the kit (it owns the dev-link
|
|
73647
|
-
import {
|
|
73799
|
+
`\u26A0 LOTICS_UI_SRC is set but vite.config.ts never reads it \u2014 @lotics/ui will still resolve from node_modules, so kit edits will NOT ${command === "deploy" ? "ship" : "appear"}. Hand the whole resolve block to the kit (it owns the dev-link and the React dedupe):
|
|
73800
|
+
import { loticsResolve } from "@lotics/ui/vite";
|
|
73648
73801
|
// \u2026
|
|
73649
73802
|
resolve: loticsResolve(),`
|
|
73650
73803
|
);
|
|
@@ -73722,6 +73875,71 @@ A newer kit is published \u2014 \`npm update ${findings.map((f) => f.pkg).join("
|
|
|
73722
73875
|
` + lines.join("\n")
|
|
73723
73876
|
);
|
|
73724
73877
|
}
|
|
73878
|
+
var KIT_SPECIFIER = "@lotics/ui/button";
|
|
73879
|
+
var KIT_INSTALLED_PATH = "/node_modules/@lotics/ui/dist/button.js";
|
|
73880
|
+
function inlineEntries(source) {
|
|
73881
|
+
const out = [];
|
|
73882
|
+
const key = /\binline\s*:\s*/g;
|
|
73883
|
+
for (let match2 = key.exec(source); match2 !== null; match2 = key.exec(source)) {
|
|
73884
|
+
const i2 = match2.index + match2[0].length;
|
|
73885
|
+
if (source.startsWith("true", i2)) {
|
|
73886
|
+
out.push({ kind: "everything" });
|
|
73887
|
+
continue;
|
|
73888
|
+
}
|
|
73889
|
+
if (source[i2] !== "[") continue;
|
|
73890
|
+
for (let depth = 1, i22 = i2 + 1; i22 < source.length && depth > 0; ) {
|
|
73891
|
+
const c = source[i22];
|
|
73892
|
+
if (c === "[") {
|
|
73893
|
+
depth++;
|
|
73894
|
+
i22++;
|
|
73895
|
+
} else if (c === "]") {
|
|
73896
|
+
depth--;
|
|
73897
|
+
i22++;
|
|
73898
|
+
} else if (c === '"' || c === "'" || c === "`") {
|
|
73899
|
+
let j = i22 + 1;
|
|
73900
|
+
while (j < source.length && source[j] !== c) j += source[j] === "\\" ? 2 : 1;
|
|
73901
|
+
out.push({ kind: "string", value: source.slice(i22 + 1, j) });
|
|
73902
|
+
i22 = j + 1;
|
|
73903
|
+
} else if (c === "/" && source[i22 + 1] === "/") {
|
|
73904
|
+
const nl = source.indexOf("\n", i22);
|
|
73905
|
+
i22 = nl === -1 ? source.length : nl + 1;
|
|
73906
|
+
} else if (c === "/" && source[i22 + 1] === "*") {
|
|
73907
|
+
const end = source.indexOf("*/", i22);
|
|
73908
|
+
i22 = end === -1 ? source.length : end + 2;
|
|
73909
|
+
} else if (c === "/") {
|
|
73910
|
+
let j = i22 + 1;
|
|
73911
|
+
for (let inClass = false; j < source.length; j++) {
|
|
73912
|
+
const ch = source[j];
|
|
73913
|
+
if (ch === "\\") j++;
|
|
73914
|
+
else if (ch === "[") inClass = true;
|
|
73915
|
+
else if (ch === "]") inClass = false;
|
|
73916
|
+
else if (ch === "/" && !inClass) break;
|
|
73917
|
+
else if (ch === "\n") break;
|
|
73918
|
+
}
|
|
73919
|
+
if (source[j] === "/") {
|
|
73920
|
+
let k = j + 1;
|
|
73921
|
+
while (k < source.length && /[a-z]/.test(source[k])) k++;
|
|
73922
|
+
out.push({ kind: "regex", source: source.slice(i22 + 1, j), flags: source.slice(j + 1, k) });
|
|
73923
|
+
i22 = k;
|
|
73924
|
+
} else i22++;
|
|
73925
|
+
} else i22++;
|
|
73926
|
+
}
|
|
73927
|
+
}
|
|
73928
|
+
return out;
|
|
73929
|
+
}
|
|
73930
|
+
function inlinesKitUnderVitest(viteConfig) {
|
|
73931
|
+
return inlineEntries(viteConfig).some((entry) => {
|
|
73932
|
+
if (entry.kind === "everything") return true;
|
|
73933
|
+
if (entry.kind === "string")
|
|
73934
|
+
return KIT_SPECIFIER === entry.value || KIT_SPECIFIER.startsWith(`${entry.value}/`);
|
|
73935
|
+
try {
|
|
73936
|
+
const re2 = new RegExp(entry.source, entry.flags.replace("g", ""));
|
|
73937
|
+
return re2.test(KIT_SPECIFIER) || re2.test(KIT_INSTALLED_PATH);
|
|
73938
|
+
} catch {
|
|
73939
|
+
return false;
|
|
73940
|
+
}
|
|
73941
|
+
});
|
|
73942
|
+
}
|
|
73725
73943
|
function ensureAppVitestSetup(projectDir) {
|
|
73726
73944
|
const setupPath = path9.join(projectDir, VITEST_SETUP_FILENAME);
|
|
73727
73945
|
if (!fs8.existsSync(setupPath)) {
|
|
@@ -73731,11 +73949,18 @@ function ensureAppVitestSetup(projectDir) {
|
|
|
73731
73949
|
const viteConfigPath = path9.join(projectDir, "vite.config.ts");
|
|
73732
73950
|
if (!fs8.existsSync(viteConfigPath)) return;
|
|
73733
73951
|
const viteConfig = fs8.readFileSync(viteConfigPath, "utf-8");
|
|
73734
|
-
if (/setupFiles\s*:/.test(viteConfig) && viteConfig.includes(VITEST_SETUP_FILENAME))
|
|
73735
|
-
|
|
73736
|
-
|
|
73952
|
+
if (!(/setupFiles\s*:/.test(viteConfig) && viteConfig.includes(VITEST_SETUP_FILENAME))) {
|
|
73953
|
+
console.error(
|
|
73954
|
+
`\u26A0 vite.config.ts does not wire ${VITEST_SETUP_FILENAME}. Nothing is broken by that \u2014 it is the slot for global test setup (matchers, stubs, timezone pinning). Add it to the \`test\` block when you need one:
|
|
73737
73955
|
setupFiles: ["./${VITEST_SETUP_FILENAME}"],`
|
|
73738
|
-
|
|
73956
|
+
);
|
|
73957
|
+
}
|
|
73958
|
+
if (!inlinesKitUnderVitest(viteConfig)) {
|
|
73959
|
+
console.error(
|
|
73960
|
+
`\u26A0 vite.config.ts does not inline @lotics/ui under vitest, so \`npm test\` fails to load every suite that renders a kit component ("Unknown file extension \\".css\\"") \u2014 the kit's modules import their own stylesheets and vitest hands node_modules to Node. Add it to the \`test\` block:
|
|
73961
|
+
server: { deps: { inline: [/@lotics\\/ui/] } },`
|
|
73962
|
+
);
|
|
73963
|
+
}
|
|
73739
73964
|
}
|
|
73740
73965
|
async function appCodegen(args) {
|
|
73741
73966
|
const projectDir = path9.resolve(args.projectDir ?? process.cwd());
|
|
@@ -74924,6 +75149,13 @@ function isVersionBelow(version2, floor2) {
|
|
|
74924
75149
|
if (minor !== floorMinor) return minor < floorMinor;
|
|
74925
75150
|
return patch < floorPatch;
|
|
74926
75151
|
}
|
|
75152
|
+
function bundlesReactNative(projectDir) {
|
|
75153
|
+
const manifest = path9.join(projectDir, "package.json");
|
|
75154
|
+
if (!fs8.existsSync(manifest)) return false;
|
|
75155
|
+
const pkg = JSON.parse(fs8.readFileSync(manifest, "utf-8"));
|
|
75156
|
+
const declared = { ...pkg.dependencies, ...pkg.devDependencies };
|
|
75157
|
+
return "react-native" in declared || "react-native-web" in declared;
|
|
75158
|
+
}
|
|
74927
75159
|
function warnIfProjectFootguns(projectDir, sourceText) {
|
|
74928
75160
|
const lines = [];
|
|
74929
75161
|
const sdkVersion = installedKitVersion(projectDir, "@lotics/app-sdk");
|
|
@@ -74939,7 +75171,7 @@ function warnIfProjectFootguns(projectDir, sourceText) {
|
|
|
74939
75171
|
);
|
|
74940
75172
|
}
|
|
74941
75173
|
const configName = VITE_CONFIG_NAMES.find((name2) => fs8.existsSync(path9.join(projectDir, name2)));
|
|
74942
|
-
if (configName !== void 0) {
|
|
75174
|
+
if (configName !== void 0 && bundlesReactNative(projectDir)) {
|
|
74943
75175
|
const config2 = fs8.readFileSync(path9.join(projectDir, configName), "utf-8");
|
|
74944
75176
|
const defined = defineBlockKeys(config2);
|
|
74945
75177
|
const missing = ["global", "__DEV__"].filter((name2) => !defined.has(name2));
|
|
@@ -90327,11 +90559,11 @@ function buildXfXml(s, fontId, fillId, borderId, numFmtId) {
|
|
|
90327
90559
|
if (s.horizontalAlign || s.verticalAlign || s.wrapText || s.indent || s.textRotation || s.shrinkToFit) {
|
|
90328
90560
|
const hAlign = s.horizontalAlign ? ` horizontal="${s.horizontalAlign}"` : "";
|
|
90329
90561
|
const vAlign = s.verticalAlign ? ` vertical="${s.verticalAlign}"` : "";
|
|
90330
|
-
const
|
|
90562
|
+
const wrap2 = s.wrapText ? ' wrapText="1"' : "";
|
|
90331
90563
|
const indent = s.indent ? ` indent="${s.indent}"` : "";
|
|
90332
90564
|
const rotation = s.textRotation !== void 0 ? ` textRotation="${s.textRotation === "vertical" ? 255 : s.textRotation}"` : "";
|
|
90333
90565
|
const shrink = s.shrinkToFit ? ' shrinkToFit="1"' : "";
|
|
90334
|
-
return `<xf ${attrs} applyAlignment="1"><alignment${hAlign}${vAlign}${
|
|
90566
|
+
return `<xf ${attrs} applyAlignment="1"><alignment${hAlign}${vAlign}${wrap2}${indent}${rotation}${shrink}/></xf>`;
|
|
90335
90567
|
}
|
|
90336
90568
|
return `<xf ${attrs}/>`;
|
|
90337
90569
|
}
|