@lotics/cli 0.88.0 → 0.88.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.
Files changed (87) hide show
  1. package/dist/render_page.js +100 -100
  2. package/dist/src/cli.js +100 -100
  3. package/dist/{client.d.ts → src/client.d.ts} +1 -1
  4. package/dist/src/package_content_types.d.ts +70 -0
  5. package/dist/src/package_content_types.js +12 -0
  6. package/package.json +2 -2
  7. package/dist/app_commands.d.ts +0 -401
  8. package/dist/app_commands.js +0 -1520
  9. package/dist/app_commands.test.d.ts +0 -1
  10. package/dist/app_commands.test.js +0 -898
  11. package/dist/app_workflow_check.d.ts +0 -77
  12. package/dist/app_workflow_check.js +0 -169
  13. package/dist/app_workflow_check.test.d.ts +0 -1
  14. package/dist/app_workflow_check.test.js +0 -166
  15. package/dist/args.d.ts +0 -42
  16. package/dist/args.js +0 -121
  17. package/dist/args.test.d.ts +0 -1
  18. package/dist/args.test.js +0 -88
  19. package/dist/child_env.d.ts +0 -13
  20. package/dist/child_env.js +0 -24
  21. package/dist/cli.d.ts +0 -2
  22. package/dist/cli.js +0 -1095
  23. package/dist/cli_dispatch.test.d.ts +0 -1
  24. package/dist/cli_dispatch.test.js +0 -89
  25. package/dist/client.test.d.ts +0 -1
  26. package/dist/client.test.js +0 -47
  27. package/dist/config.d.ts +0 -124
  28. package/dist/config.js +0 -315
  29. package/dist/config.test.d.ts +0 -1
  30. package/dist/config.test.js +0 -319
  31. package/dist/dev/file_relay.d.ts +0 -39
  32. package/dist/dev/file_relay.js +0 -87
  33. package/dist/dev/file_relay.test.d.ts +0 -1
  34. package/dist/dev/file_relay.test.js +0 -87
  35. package/dist/dev/rpc_handler.d.ts +0 -21
  36. package/dist/dev/rpc_handler.js +0 -166
  37. package/dist/dev/rpc_handler.test.d.ts +0 -1
  38. package/dist/dev/rpc_handler.test.js +0 -107
  39. package/dist/dev/server.d.ts +0 -75
  40. package/dist/dev/server.js +0 -443
  41. package/dist/dev/upload_relay.d.ts +0 -36
  42. package/dist/dev/upload_relay.js +0 -61
  43. package/dist/dev/upload_relay.test.d.ts +0 -1
  44. package/dist/dev/upload_relay.test.js +0 -76
  45. package/dist/dev/wrapper_page.d.ts +0 -40
  46. package/dist/dev/wrapper_page.js +0 -335
  47. package/dist/docx.d.ts +0 -2
  48. package/dist/docx.js +0 -341
  49. package/dist/docx.test.d.ts +0 -1
  50. package/dist/docx.test.js +0 -145
  51. package/dist/file_command_io.d.ts +0 -12
  52. package/dist/file_command_io.js +0 -34
  53. package/dist/generate_app_agents_dts.d.ts +0 -16
  54. package/dist/generate_app_agents_dts.js +0 -62
  55. package/dist/generate_app_agents_dts.test.d.ts +0 -1
  56. package/dist/generate_app_agents_dts.test.js +0 -45
  57. package/dist/generate_app_fields.d.ts +0 -56
  58. package/dist/generate_app_fields.js +0 -148
  59. package/dist/generate_app_fields.test.d.ts +0 -1
  60. package/dist/generate_app_fields.test.js +0 -108
  61. package/dist/generate_app_queries_dts.d.ts +0 -21
  62. package/dist/generate_app_queries_dts.js +0 -57
  63. package/dist/generate_app_workflows_dts.d.ts +0 -39
  64. package/dist/generate_app_workflows_dts.js +0 -229
  65. package/dist/generate_app_workflows_dts.test.d.ts +0 -1
  66. package/dist/generate_app_workflows_dts.test.js +0 -109
  67. package/dist/generate_package_fields.d.ts +0 -49
  68. package/dist/generate_package_fields.js +0 -157
  69. package/dist/generate_package_fields.test.d.ts +0 -1
  70. package/dist/generate_package_fields.test.js +0 -59
  71. package/dist/inputs.d.ts +0 -38
  72. package/dist/inputs.js +0 -50
  73. package/dist/inputs.test.d.ts +0 -1
  74. package/dist/inputs.test.js +0 -89
  75. package/dist/preview.d.ts +0 -3
  76. package/dist/preview.js +0 -233
  77. package/dist/starter_template.d.ts +0 -64
  78. package/dist/starter_template.js +0 -753
  79. package/dist/starter_template.test.d.ts +0 -1
  80. package/dist/starter_template.test.js +0 -115
  81. package/dist/version.d.ts +0 -1
  82. package/dist/version.js +0 -6
  83. package/dist/xlsx.d.ts +0 -2
  84. package/dist/xlsx.js +0 -489
  85. package/dist/xlsx.test.d.ts +0 -1
  86. package/dist/xlsx.test.js +0 -131
  87. /package/dist/{client.js → src/client.js} +0 -0
@@ -1,753 +0,0 @@
1
- /**
2
- * Starter template for `lotics app create`.
3
- *
4
- * The CLI scaffolds these files into the user's target directory, then runs
5
- * `npm install` + `npm run build` + `lotics app deploy` to populate v1.
6
- *
7
- * Embedded as string literals here (rather than packaged as files alongside
8
- * the CLI) so the CLI binary is self-contained and the starter survives
9
- * `npm publish` cleanly. Updates to the starter ship in CLI releases.
10
- *
11
- * Conventions (locked decisions):
12
- * - Vite + React + TypeScript (strict mode).
13
- * - Entry: src/App.tsx with `export default`. main.tsx wires
14
- * `mount(<LoticsLocaleProvider locale={vi}><PortalHost><App/></PortalHost></LoticsLocaleProvider>)` —
15
- * LoticsLocaleProvider sets the kit's strings to Vietnamese (Lotics apps are
16
- * Vietnamese; swap to `en` for English); PortalHost is the render target
17
- * @lotics/ui overlays (Popover/Tooltip/Dialog) need — and imports
18
- * @lotics/ui/index.css (base style reset) + @lotics/ui/fonts.css
19
- * (path-independent Inter @font-face bundle — Text renders unstyled without it).
20
- * - Vite default `base: "/"` so emitted asset URLs are absolute; the render
21
- * endpoint rewrites root-relative paths to `/v1/apps/{id}/asset/...`.
22
- * Don't switch to `base: "./"` without updating `rewriteAssetPaths`.
23
- * - react-native aliased to react-native-web so @lotics/ui's RN primitives
24
- * render in a pure-web environment. `.web.tsx` is prioritized in the
25
- * resolve.extensions list.
26
- * - @lotics/app-sdk + @lotics/ui peer-supplied; user installs both via npm.
27
- * - oxlint + vitest + tsc out of the box, plus a GitHub Actions CI
28
- * workflow that runs all three on every PR.
29
- */
30
- /**
31
- * Pin defaults for `@lotics/ui` and `@lotics/app-sdk` in the scaffolded
32
- * package.json. Used when `lotics app create` can't resolve the latest
33
- * versions from npm (no network, registry blip) — keeps `app create`
34
- * working offline at the cost of a stale starter pin.
35
- *
36
- * Bump these in tandem with new releases of either package. Production
37
- * scaffolds resolve the live version via `fetchLatestNpmVersion` and only
38
- * fall back here when the lookup fails.
39
- */
40
- export const STARTER_FALLBACK_UI_VERSION = "6.1.0";
41
- // Must be ≥ the release that added `getAppBinding` (0.51.0, used by package
42
- // projects' generated .lotics/app_fields.ts) — and note ^0.x caret ranges never
43
- // cross a minor, so a stale floor here permanently pins scaffolds below the
44
- // APIs the generated code imports.
45
- export const STARTER_FALLBACK_SDK_VERSION = "0.51.0";
46
- /**
47
- * react-native pin for scaffolded apps. Matches the monorepo frontend's pin so
48
- * an app deep-typechecks `@lotics/ui`'s `.tsx` source against the SAME RN types
49
- * the package is developed and CI'd against. A mismatch surfaces RN-version-
50
- * sensitive type errors (e.g. `StyleSheet.absoluteFill`, which `frontend/` CI
51
- * never sees because `packages/ui` is only typechecked through `frontend/`).
52
- * RN is aliased to react-native-web at runtime, so this pin is effectively
53
- * types-only.
54
- */
55
- export const STARTER_REACT_NATIVE_VERSION = "0.85.3";
56
- export function buildStarterTemplate(args) {
57
- const uiVersion = args.ui_version ?? `^${STARTER_FALLBACK_UI_VERSION}`;
58
- const sdkVersion = args.sdk_version ?? `^${STARTER_FALLBACK_SDK_VERSION}`;
59
- const sanitizedPkgName = args.app_name
60
- .toLowerCase()
61
- .replace(/[^a-z0-9-]/g, "-")
62
- .replace(/-+/g, "-")
63
- .replace(/^-|-$/g, "")
64
- .slice(0, 64) || "lotics-app";
65
- return [
66
- {
67
- path: "package.json",
68
- content: JSON.stringify({
69
- name: sanitizedPkgName,
70
- version: "0.0.1",
71
- private: true,
72
- type: "module",
73
- scripts: {
74
- dev: "vite",
75
- build: "vite build",
76
- preview: "vite preview",
77
- typecheck: "tsc --noEmit",
78
- // Scope to src/ — oxlint's default scan walks node_modules too.
79
- lint: "oxlint src",
80
- test: "vitest run",
81
- },
82
- dependencies: {
83
- "@lotics/app-sdk": sdkVersion,
84
- // The FilePreview engines (pdfjs-dist / @lotics/docx / @lotics/xlsx) are
85
- // NOT listed here: they ship as @lotics/ui dependencies (7.14.0+), so
86
- // every app gets them transitively — one version source, no skew. An app
87
- // that IMPORTS @lotics/docx or @lotics/xlsx directly (its own doc/excel
88
- // builders) declares its own dependency then.
89
- "@lotics/ui": uiVersion,
90
- "@react-native-picker/picker": "^2.7.0",
91
- "expo-image": "~3.0.9",
92
- "lucide-react": "^0.562.0",
93
- "lucide-react-native": "^0.562.0",
94
- react: "^19.0.0",
95
- "react-dom": "^19.0.0",
96
- "react-native": STARTER_REACT_NATIVE_VERSION,
97
- "react-native-svg": "^15.0.0",
98
- "react-native-web": "^0.21.0",
99
- // In-app routing for the scaffolded list→detail example. The app owns
100
- // its routing; the SDK's `isEmbedded()` picks memory vs browser history.
101
- "react-router-dom": "^7.0.0",
102
- },
103
- devDependencies: {
104
- "@testing-library/react": "^16.1.0",
105
- "@types/react": "^19.0.0",
106
- "@types/react-dom": "^19.0.0",
107
- "@vitejs/plugin-react": "^4.3.0",
108
- jsdom: "^25.0.0",
109
- oxlint: "^0.13.0",
110
- // Track the platform's TS major so `lotics app workflow check` (which runs
111
- // the app's OWN typescript) gives the same verdict the server does at set-time.
112
- typescript: "~6.0.3",
113
- vite: "^5.4.0",
114
- vitest: "^2.1.0",
115
- },
116
- lotics: {
117
- app_id: args.app_id,
118
- workspace_id: args.workspace_id,
119
- current_version_id: null,
120
- version_number: null,
121
- },
122
- }, null, 2) + "\n",
123
- },
124
- {
125
- path: "tsconfig.json",
126
- content: JSON.stringify({
127
- compilerOptions: {
128
- target: "ES2022",
129
- useDefineForClassFields: true,
130
- lib: ["ES2022", "DOM", "DOM.Iterable"],
131
- module: "ESNext",
132
- skipLibCheck: true,
133
- moduleResolution: "Bundler",
134
- allowImportingTsExtensions: true,
135
- resolveJsonModule: true,
136
- isolatedModules: true,
137
- noEmit: true,
138
- jsx: "react-jsx",
139
- strict: true,
140
- noFallthroughCasesInSwitch: true,
141
- },
142
- // Restrict tsc to the project's own sources. tsgo follows
143
- // transitive imports into node_modules — strict rules
144
- // (noUnusedLocals etc.) would fire on @lotics/ui's source.
145
- // `.lotics/**/*` (not bare `.lotics`): TypeScript's include glob walk
146
- // SKIPS dot-directories, so a bare `.lotics` silently loads zero of the
147
- // generated `.d.ts` augmentations and typed useWorkflow/useQuery/useAgentRun
148
- // fall to `unknown`. The explicit glob makes the dot-dir the non-wildcard
149
- // base, which IS read. (GAP-38.)
150
- include: ["src", ".lotics/**/*"],
151
- // src/workflows/<alias>.ts bodies run on the server (workflow globals:
152
- // trigger / runtime / tool calls), and each pulls its own per-alias
153
- // ambient globals under `.lotics/workflows/`. BOTH are excluded from
154
- // the main typecheck: the bodies would apply the app's DOM lib (the
155
- // server doesn't), and the per-alias `.globals.d.ts` files each declare
156
- // their OWN ambient `trigger` — loading several into one program
157
- // collides those declarations and reports false errors. Type-check the
158
- // bodies with `lotics app workflow check` instead — it builds ONE
159
- // isolated program per alias (mirroring the server's set-time verify),
160
- // so the ambient `trigger` is unambiguous. The server stays the single
161
- // verifier on `lotics app workflow set`.
162
- exclude: ["node_modules", "src/workflows", ".lotics/workflows"],
163
- }, null, 2) + "\n",
164
- },
165
- {
166
- path: "vite.config.ts",
167
- content: `/// <reference types="vitest" />
168
- import { defineConfig } from "vite";
169
- import react from "@vitejs/plugin-react";
170
-
171
- // Vite default base (/) emits absolute asset URLs in index.html. Lotics's
172
- // render endpoint rewrites those to /v1/apps/{id}/asset/... so the bundle
173
- // loads via the platform's asset proxy. Don't change \`base\` unless you
174
- // also adjust the rewrite logic in backend/api/apps.ts:rewriteAssetPaths.
175
- //
176
- // react-native → react-native-web alias lets @lotics/ui's RN primitives
177
- // (View, Text, Pressable, StyleSheet, etc.) render in a pure-web environment.
178
- // .web.tsx is prioritized in resolve.extensions so per-target variants
179
- // (avatar.web.tsx, wave_avatar.web.tsx) win over the native .tsx file.
180
- //
181
- // @lotics/ui/icon.tsx imports from \`lucide-react-native/dist/esm/icons/<name>\`
182
- // — deep paths that lucide-react-native's package \`exports\` map blocks under
183
- // Vite's strict resolution. lucide-react has the same per-icon files with no
184
- // exports map, so we alias the deep path to use it. Web rendering of <svg>
185
- // is identical to RN-SVG when running under RN-Web.
186
- export default defineConfig({
187
- plugins: [react()],
188
- // RN libraries reference globals Metro injects but Vite does not — undefined
189
- // ⇒ the bundle throws. \`__DEV__\` (react-native-web, @react-native-picker's
190
- // UnimplementedView): throws at load, blank iframe. \`global\` (rn-web
191
- // Animated's \`global.cancelAnimationFrame\`): throws when a spring animation is
192
- // interrupted/torn down — e.g. toggling @lotics/ui's Switch. rollup hits it;
193
- // the dev esbuild path masks it, so it only surfaces in the deployed app.
194
- // rn-web only reads \`global.x\` as a free var, so mapping it to \`globalThis\`
195
- // is safe. Define both so dev and the deployed build behave identically.
196
- define: { __DEV__: "false", global: "globalThis" },
197
- resolve: {
198
- alias: [
199
- {
200
- find: /^lucide-react-native\\/dist\\/esm\\/icons\\/(.+)$/,
201
- replacement: "lucide-react/dist/esm/icons/$1",
202
- },
203
- { find: "react-native", replacement: "react-native-web" },
204
- ],
205
- // \`.web.js\` resolves the web build of RN packages that ship \`X.js\` (native)
206
- // beside \`X.web.js\` (web) — e.g. @react-native-picker/picker, whose compiled
207
- // \`Picker.web.js\` renders a real <select>. Without it the extensionless
208
- // \`require("./Picker")\` picks native \`Picker.js\` and the standard Picker
209
- // renders nothing on web.
210
- extensions: [".web.tsx", ".web.ts", ".web.js", ".tsx", ".ts", ".jsx", ".js"],
211
- // @lotics/ui ships source and is consumed across many subpath entries
212
- // (./card, ./metric, ./use_screen_size, …). Without dedupe, Vite can
213
- // pre-bundle a subpath into its own chunk with a second React copy — a
214
- // hook called from there hits a null dispatcher ("Invalid hook call").
215
- // Pin React (and RN-Web) to a single instance shared by every chunk.
216
- dedupe: ["react", "react-dom", "react-native-web"],
217
- },
218
- optimizeDeps: {
219
- // \`react-native-svg\` (used by @lotics/ui's svg charts — PieChart, LineChart,
220
- // Sparkline, RingGauge) ships a named-export entry Vite's dev optimizer won't
221
- // resolve unbundled ("does not provide an export named 'parse'"), blanking the
222
- // iframe the moment one of those charts renders. Pre-bundling it fixes the
223
- // interop. Production build (rollup) resolves it without this — dev-only.
224
- //
225
- // react-native-web itself is force-prebundled (folds its core CJS deps like
226
- // @react-native/normalize-colors into one interop'd chunk), and the deep
227
- // subpaths it reaches via *default* imports (e.g. @react-native-picker's web
228
- // <select> build) are each pre-bundled into an ESM shim — Vite's dev optimizer
229
- // otherwise serves them without a synthesized default export ("does not
230
- // provide an export named 'default'"), blanking the iframe. (Production/rollup
231
- // resolves the interop already, so this is dev-only.)
232
- //
233
- // \`react-markdown\` (@lotics/ui's Markdown renderer, reached by AgentRun and
234
- // any markdown surface) pulls a transitive CJS dep, \`style-to-js\`, whose
235
- // default export the dev optimizer won't synthesize unbundled ("does not
236
- // provide an export named 'default'") — blanking the iframe the moment a
237
- // markdown component mounts. Pre-bundling react-markdown folds the whole
238
- // subtree (incl. style-to-js) into one interop'd chunk. It is a regular dep
239
- // of @lotics/ui (hoisted), so it needs no package.json entry here. (The GFM
240
- // plugin @lotics/ui composes is pure ESM — no CJS interop to force.)
241
- include: [
242
- "react-native-svg",
243
- "react-markdown",
244
- "react-native-web", "@react-native/normalize-colors",
245
- "inline-style-prefixer/lib/createPrefixer",
246
- "inline-style-prefixer/lib/plugins/crossFade",
247
- "inline-style-prefixer/lib/plugins/imageSet",
248
- "inline-style-prefixer/lib/plugins/logical",
249
- "inline-style-prefixer/lib/plugins/position",
250
- "inline-style-prefixer/lib/plugins/sizing",
251
- "inline-style-prefixer/lib/plugins/transition",
252
- "postcss-value-parser", "fbjs/lib/invariant", "fbjs/lib/warning",
253
- "styleq", "styleq/transform-localize-style",
254
- "react", "react-dom", "react-dom/client", "nullthrows",
255
- ],
256
- // The dep optimizer pre-bundles deps with a SEPARATE esbuild pass that
257
- // top-level \`define\` doesn't always reach, so a pre-bundled RN dep can
258
- // still hit \`__DEV__ is not defined\` under \`lotics app dev\`. Define it
259
- // here too — belt-and-suspenders with the top-level \`define\` above.
260
- // The optimizer pre-bundles with a SEPARATE esbuild resolver that ignores
261
- // \`resolve.extensions\`, so \`.web.js\` must be repeated here or a pre-bundled
262
- // RN dep still resolves \`./Picker\` to the native build.
263
- esbuildOptions: {
264
- define: { __DEV__: "false", global: "globalThis" },
265
- resolveExtensions: [".web.tsx", ".web.ts", ".web.js", ".tsx", ".ts", ".jsx", ".js", ".json"],
266
- },
267
- },
268
- build: {
269
- outDir: "dist",
270
- sourcemap: true,
271
- // Top-level await (package projects' generated .lotics/app_fields.ts
272
- // resolves the installation binding at module load) needs es2022 — Vite's
273
- // default 'modules' baseline is es2020 and esbuild hard-fails TLA there.
274
- // Dev already transforms at esnext, so this only aligns the prod build.
275
- target: "es2022",
276
- },
277
- server: {
278
- // Allow the sandboxed null-origin iframe used by \`lotics app dev\` to
279
- // fetch HMR client + source modules from the dev server. Production
280
- // iframe loads modules from api.lotics.ai which already permits null
281
- // origin via CORS.
282
- cors: { origin: "*" },
283
- // @lotics/ui/fonts.css references the API's /iframe/fonts/*.woff2 files
284
- // by root-relative URL. A deployed app is served from the API origin so
285
- // they resolve directly; under \`lotics app dev\` the app runs on
286
- // localhost, so proxy /iframe to the API to load the real fonts.
287
- proxy: { "/iframe": { target: "https://api.lotics.ai", changeOrigin: true } },
288
- },
289
- test: {
290
- environment: "jsdom",
291
- // RN packages ship Flow (\`import typeof\`) in their native source, reached
292
- // transitively by RN-Web components (pickers, calendars, anything touching
293
- // Animated). Vitest's web optimizer is OFF by default and ignores the
294
- // \`optimizeDeps.resolveExtensions\` above, so it pre-bundles the native
295
- // \`.js\` / \`src\` Flow files and esbuild can't parse them. Enable it with the
296
- // same \`.web.js\`-first resolution so it bundles the compiled web variants
297
- // (Picker.web.js, RN-Web dist) — without this, any test that renders a tree
298
- // pulling a Picker / DatePicker fails with "Unexpected token 'typeof'".
299
- deps: {
300
- optimizer: {
301
- web: {
302
- enabled: true,
303
- // react + react-dom + the @testing-library renderer must ride in the
304
- // SAME optimized chunk as react-native-web, so a rendered tree and the
305
- // renderer share ONE react instance. Without this, a test that renders a
306
- // non-trivial App tree hits a null hooks dispatcher ("Cannot read
307
- // properties of null (reading 'useState')") the moment it mounts.
308
- include: [
309
- "react",
310
- "react-dom",
311
- "react-dom/client",
312
- "react-dom/test-utils",
313
- "react-native",
314
- "react-native-web",
315
- "react-native-svg",
316
- "@react-native-picker/picker",
317
- "@testing-library/react",
318
- "@testing-library/dom",
319
- // @lotics/app-sdk ships compiled dist/ JS (unlike @lotics/ui, which
320
- // ships source vitest transforms inline) — pre-bundled into its own
321
- // chunk, its react import splits from the shared instance and nulls
322
- // the hooks dispatcher the instant a hook (useAgentRun / useQuery)
323
- // runs in a rendered App tree.
324
- "@lotics/app-sdk",
325
- // The /router subpath is a SEPARATE optimizer entry — without it,
326
- // AppRouter's BrowserRouter bundles apart from the app's
327
- // react-router hooks and the Router context never matches
328
- // ("useNavigate() may be used only in the context of a <Router>").
329
- "@lotics/app-sdk/router",
330
- // react-router-dom (App's router) also ships compiled dist JS — pin it
331
- // into the shared chunk so its hooks don't split the react instance
332
- // ("Invalid hook call") when a test renders the routed App tree.
333
- "react-router-dom",
334
- // v7: react-router-dom is a thin wrapper — the actual context
335
- // lives in react-router; both must ride the shared chunk.
336
- "react-router",
337
- ],
338
- esbuildOptions: {
339
- resolveExtensions: [".web.tsx", ".web.ts", ".web.js", ".tsx", ".ts", ".jsx", ".js", ".json"],
340
- },
341
- },
342
- },
343
- },
344
- },
345
- });
346
- `,
347
- },
348
- {
349
- path: "index.html",
350
- content: `<!doctype html>
351
- <html lang="en">
352
- <head>
353
- <meta charset="UTF-8" />
354
- <meta name="viewport" content="width=device-width, initial-scale=1.0" />
355
- <title>${escapeHtml(args.app_name)}</title>
356
- <style>
357
- /*
358
- * Full-viewport container chain for iframe apps. Any layout that uses
359
- * \`flex: 1\` to fill available space (a scrollable table, list, or
360
- * full-page surface) relies on a measured parent height. The browser
361
- * default of html/body being content-sized collapses every \`flex: 1\`
362
- * to 0 and the surface shrinks to fit only its rendered content.
363
- *
364
- * Also: #root is made a flex column so the outermost RN \`<View flex:1>\`
365
- * inside App.tsx claims the full iframe height.
366
- */
367
- html, body, #root { height: 100%; margin: 0; }
368
- #root { display: flex; flex-direction: column; }
369
- </style>
370
- </head>
371
- <body>
372
- <div id="root"></div>
373
- <script type="module" src="/src/main.tsx"></script>
374
- </body>
375
- </html>
376
- `,
377
- },
378
- {
379
- path: "src/main.tsx",
380
- content: `import "@lotics/ui/index.css";
381
- import "@lotics/ui/fonts.css";
382
- import { PortalHost } from "@lotics/ui/portal";
383
- import { LoticsLocaleProvider, vi } from "@lotics/ui/locale";
384
- import { mount } from "@lotics/app-sdk";
385
- import App from "./App";
386
-
387
- // PortalHost is the render target for @lotics/ui overlays (Popover, Tooltip,
388
- // Dialog). Without it, Portal renders nothing — keep it wrapping the app.
389
- //
390
- // LoticsLocaleProvider supplies the kit's own strings (Pagination, sort
391
- // headers, select-all, the Drawer's record nav…) in one language — Vietnamese
392
- // by default, since Lotics apps are Vietnamese. Swap to \`en\` (or your own
393
- // LoticsLocale pack) for another language; remove it to fall back to English.
394
- mount(
395
- <LoticsLocaleProvider locale={vi}>
396
- <PortalHost>
397
- <App />
398
- </PortalHost>
399
- </LoticsLocaleProvider>,
400
- );
401
- `,
402
- },
403
- {
404
- path: "src/App.tsx",
405
- // Default scaffold = a minimal in-app router example (a list screen and a
406
- // detail screen), so a new app starts with the recommended routing shape:
407
- // - The app uses react-router via `AppRouter` (from @lotics/app-sdk/router).
408
- // The app owns its own url — embedded, that's the iframe's own url (the
409
- // host never sees it, so navigation never reloads the app); standalone,
410
- // real path URLs. Browser Back/Forward walk app screens in both.
411
- // - The full-container layout pattern is preserved in `Screen`: an outer
412
- // <View flex:1> claims the iframe height (index.html sets
413
- // html/body/#root to 100% + #root is a flex column). Keep that chain
414
- // plain — @lotics/ui/stack wraps children in an unstyled <View> that
415
- // breaks `flex: 1` propagation to a fill-remaining-space child.
416
- // A single-screen app can delete the router and render one Screen directly.
417
- content: `import type { ReactNode } from "react";
418
- import { View } from "react-native";
419
- import { useNavigate, useParams } from "react-router-dom";
420
- import { AppRouter } from "@lotics/app-sdk/router";
421
- import { Card } from "@lotics/ui/card";
422
- import { Text } from "@lotics/ui/text";
423
- import { Button } from "@lotics/ui/button";
424
-
425
- // AppRouter makes the app's screens real URLs — write plain react-router
426
- // (useNavigate / useParams / <Link>) and it handles both modes. The app owns its
427
- // own url, so navigation never reloads the app:
428
- // - Embedded in the Lotics host: the app drives the iframe's OWN url (invisible
429
- // to the user, never seen by the host). Browser Back / Forward walk app
430
- // screens; the screen is also mirrored to the host url, so it's shareable and
431
- // survives a full refresh (handled by AppRouter — no extra code).
432
- // - Standalone at <slug>.lotics.app: a normal browser router with real path URLs.
433
-
434
- const ITEMS = [
435
- { id: "1", name: "First item" },
436
- { id: "2", name: "Second item" },
437
- { id: "3", name: "Third item" },
438
- ];
439
-
440
- // Outer <View flex:1> claims the full iframe height — works because index.html
441
- // sets html/body/#root to 100% and #root is a flex column. Keep this flex chain
442
- // plain (not @lotics/ui/stack) so a fill-remaining-space child can claim height.
443
- function Screen({ children }: { children: ReactNode }) {
444
- return (
445
- <View style={{ flex: 1, padding: 24, alignItems: "center" }}>
446
- <View style={{ maxWidth: 640, width: "100%", gap: 16 }}>{children}</View>
447
- </View>
448
- );
449
- }
450
-
451
- function ListScreen() {
452
- const navigate = useNavigate();
453
- return (
454
- <Screen>
455
- <Text size="xl" weight="semibold">${escapeHtml(args.app_name)}</Text>
456
- <Text color="muted">
457
- Tap an item to open its detail screen — the app routes itself. Edit{" "}
458
- <Text weight="medium">src/App.tsx</Text> and run{" "}
459
- <Text weight="medium">lotics app deploy</Text> to publish.
460
- </Text>
461
- {ITEMS.map((item) => (
462
- <Card key={item.id}>
463
- <View
464
- style={{
465
- padding: 16,
466
- flexDirection: "row",
467
- alignItems: "center",
468
- justifyContent: "space-between",
469
- gap: 12,
470
- }}
471
- >
472
- <Text weight="medium">{item.name}</Text>
473
- <Button title="Open" color="primary" onPress={() => navigate("/item/" + item.id)} />
474
- </View>
475
- </Card>
476
- ))}
477
- </Screen>
478
- );
479
- }
480
-
481
- function ItemDetailScreen() {
482
- const { id } = useParams();
483
- const navigate = useNavigate();
484
- const item = ITEMS.find((i) => i.id === id);
485
- return (
486
- <Screen>
487
- {/* An in-app Back control; navigate(-1) walks the history (the browser Back
488
- button walks app screens too). */}
489
- <View style={{ alignItems: "flex-start" }}>
490
- <Button title="Back" onPress={() => navigate(-1)} />
491
- </View>
492
- <Text size="xl" weight="semibold">{item ? item.name : "Not found"}</Text>
493
- <Card>
494
- <View style={{ padding: 16, gap: 8 }}>
495
- <Text>Detail for item {id}.</Text>
496
- <Text color="muted">Reached via in-app navigation, not a host route.</Text>
497
- </View>
498
- </Card>
499
- </Screen>
500
- );
501
- }
502
-
503
- const routes = [
504
- { path: "/", element: <ListScreen /> },
505
- { path: "/item/:id", element: <ItemDetailScreen /> },
506
- ];
507
-
508
- export default function App() {
509
- return <AppRouter routes={routes} />;
510
- }
511
- `,
512
- },
513
- {
514
- // The home for editable workflow bodies. `lotics app pull` writes one
515
- // `src/workflows/<alias>.ts` per bound workflow (faithful server source);
516
- // edit it, then `lotics app workflow set <alias>` pushes it back through
517
- // set_app_workflow (the server verifies). A new app has no bound workflows
518
- // yet, so this is just the docked directory + the loop reference. It's a
519
- // .md (not a .ts) so tsc's `include: ["src"]` never tries to compile it —
520
- // a workflow body is a JS-subset expression over server globals
521
- // (trigger / runtime / tool calls) that does NOT typecheck standalone.
522
- path: "src/workflows/README.md",
523
- content: `# Workflow bodies
524
-
525
- Editable JS-subset bodies of this app's workflows live here, one file per alias:
526
- \`src/workflows/<alias>.ts\`.
527
-
528
- ## Loop
529
-
530
- \`\`\`bash
531
- lotics app workflow pull # write/refresh every src/workflows/<alias>.ts from the server
532
- # edit src/workflows/<alias>.ts
533
- lotics app workflow check <alias> # typecheck the body locally (same verdict as \`set\`)
534
- lotics app workflow set <alias> # push it back through set_app_workflow (the server verifies)
535
- \`\`\`
536
-
537
- \`lotics app pull <app_id>\` also writes these files (alongside the rest of the project).
538
-
539
- ## What a body is
540
-
541
- A workflow body is a **JS-subset expression** that runs server-side using workflow
542
- globals (\`trigger\`, \`runtime\`, tool calls) and ends with \`return({ data })\`. Each
543
- pulled file wraps the body in the server's \`__workflow\` envelope and references its
544
- per-alias ambient globals at \`.lotics/workflows/<alias>.globals.d.ts\`, so it **is**
545
- locally typecheckable:
546
-
547
- \`\`\`bash
548
- lotics app workflow check # check every body; [alias] to check one
549
- \`\`\`
550
-
551
- \`check\` builds ONE isolated program per alias (the app's own \`typescript\`, the
552
- server's compile options — strict, lib es2022 with no DOM), so each body's ambient
553
- \`trigger\` is unambiguous and the verdict mirrors set-time verify. The main
554
- \`npm run typecheck\` excludes \`src/workflows\` + \`.lotics/workflows\` (the bodies would
555
- apply the app's DOM lib, and the per-alias globals collide on \`trigger\`). The
556
- verification that matters still runs on the **server** when you \`set\` — the same
557
- guarantee as authoring via \`set_app_workflow\` directly. Edit only the body BETWEEN
558
- the wrapper lines; the wrapper, the \`/// <reference>\`, and the \`export {};\` marker
559
- are CLI bookkeeping (stripped on \`set\`). Do not rename a file (the filename is the
560
- alias the binding is keyed by).
561
-
562
- ## Authority
563
-
564
- \`lotics app deploy\` never authors workflows — it carries code, queries, and
565
- capabilities only. \`apps.workflows\` has exactly one author: \`set_app_workflow\`
566
- (which \`lotics app workflow set\` calls). The typed \`inputs\`/\`outputs\` schema for
567
- each alias lives in \`package.json#lotics.workflows.<alias>\`.
568
- `,
569
- },
570
- {
571
- path: "src/lucide-react-native.d.ts",
572
- content: `// Type shim for lucide-react-native's deep-icon imports
573
- // (\`lucide-react-native/dist/esm/icons/<name>\`). The package ships JS-only
574
- // files without per-file .d.ts. The Vite alias in vite.config.ts rewrites
575
- // these to lucide-react at bundle time; this declaration covers the
576
- // TypeScript compile-time gap.
577
- declare module "lucide-react-native/dist/esm/icons/*" {
578
- import type { ComponentType } from "react";
579
- const Icon: ComponentType<{
580
- size: number;
581
- color: string;
582
- fill?: string;
583
- strokeWidth?: number;
584
- }>;
585
- export default Icon;
586
- }
587
- `,
588
- },
589
- {
590
- path: "src/css_modules.d.ts",
591
- content: `declare module "*.module.css" {
592
- const classes: { [key: string]: string };
593
- export default classes;
594
- }
595
-
596
- // Plain side-effect CSS imports (@lotics/ui ships .tsx with import "./x.css").
597
- declare module "*.css";
598
- `,
599
- },
600
- {
601
- path: "src/react_native.d.ts",
602
- content: `import "react-native";
603
-
604
- // Augments react-native's types with the web-only fields @lotics/ui consumes:
605
- // Pressable's \`hovered\` callback state, plus web-only ViewStyle / TextStyle
606
- // properties (cursor, outline, boxShadow, etc.) used by its primitives.
607
- // Each iframe app needs its own copy — TypeScript doesn't auto-pick-up
608
- // \`.d.ts\` files inside dependencies.
609
- declare module "react-native" {
610
- interface PressableStateCallbackType {
611
- hovered: boolean;
612
- }
613
-
614
- interface ViewStyle {
615
- backdropFilter?: string;
616
- backgroundImage?: string;
617
- boxShadow?: string;
618
- boxSizing?: string;
619
- cursor?: string;
620
- touchAction?: string;
621
- transitionDuration?: string;
622
- transitionProperty?: string;
623
- appearance?: string;
624
- outline?: string;
625
- outlineColor?: string;
626
- outlineStyle?: string;
627
- outlineWidth?: number;
628
- outlineOffset?: number;
629
- }
630
-
631
- interface TextStyle {
632
- outline?: string;
633
- outlineColor?: string;
634
- outlineStyle?: string;
635
- outlineWidth?: number;
636
- outlineOffset?: number;
637
- appearance?: string;
638
- }
639
- }
640
- `,
641
- },
642
- {
643
- path: "src/App.test.tsx",
644
- content: `import { describe, test, expect } from "vitest";
645
- import { render } from "@testing-library/react";
646
- import App from "./App";
647
-
648
- describe("App", () => {
649
- test("renders the default route", () => {
650
- const { container } = render(<App />);
651
- expect(container.textContent).toContain("First item");
652
- });
653
- });
654
- `,
655
- },
656
- {
657
- path: ".github/workflows/ci.yml",
658
- content: `name: CI
659
-
660
- on:
661
- push:
662
- branches: [main]
663
- pull_request:
664
-
665
- jobs:
666
- ci:
667
- runs-on: ubuntu-latest
668
- steps:
669
- - uses: actions/checkout@v4
670
- - uses: actions/setup-node@v4
671
- with:
672
- node-version: 22
673
- - run: npm ci
674
- - run: npm run typecheck
675
- - run: npm run lint
676
- - run: npm test
677
- - run: npm run build
678
- `,
679
- },
680
- {
681
- path: ".gitignore",
682
- content: `node_modules
683
- dist
684
- *.tsbuildinfo
685
- .DS_Store
686
- .lotics
687
- coverage
688
- `,
689
- },
690
- {
691
- path: "README.md",
692
- content: `# ${args.app_name}
693
-
694
- A Lotics custom-code app. Authored locally, deployed via CLI.
695
-
696
- ## Develop
697
-
698
- \`\`\`bash
699
- npm install # already done by 'lotics app create'
700
- npm run dev # local Vite dev server (UI only — real data needs deploy)
701
- \`\`\`
702
-
703
- ## Quality
704
-
705
- \`\`\`bash
706
- npm run typecheck # tsc --noEmit
707
- npm run lint # oxlint
708
- npm test # vitest run
709
- \`\`\`
710
-
711
- CI runs all three on every PR via \`.github/workflows/ci.yml\`.
712
-
713
- ## Deploy
714
-
715
- \`\`\`bash
716
- lotics app deploy # build + upload as a new version
717
- lotics app deploy -m "what" # with a commit-message-style note
718
- \`\`\`
719
-
720
- ## SDK + UI components
721
-
722
- \`\`\`tsx
723
- import { mount, useQuery, useWorkflow } from "@lotics/app-sdk";
724
- import { Stack } from "@lotics/ui/stack";
725
- import { Card } from "@lotics/ui/card";
726
- import { Button } from "@lotics/ui/button";
727
- import { Text } from "@lotics/ui/text";
728
- // ... and Grid, Dialog, DatePicker, FormPicker, BarChart, LineChart, PieChart, Metric, ProgressBar, etc.
729
- \`\`\`
730
-
731
- @lotics/ui ships React Native primitives that render via react-native-web
732
- in this Vite app (the alias is preconfigured in \`vite.config.ts\`). See
733
- the full export list at https://www.npmjs.com/package/@lotics/ui.
734
-
735
- ## Routing
736
-
737
- \`src/App.tsx\` ships a minimal in-app router. Write plain react-router and wrap
738
- your routes in \`AppRouter\` from \`@lotics/app-sdk/router\` — the app owns its own
739
- url, so navigation never reloads the app. Embedded in the Lotics host it drives
740
- the iframe's own url (invisible to the user, never seen by the host); browser
741
- Back/Forward walk app screens, and the screen is mirrored to the host url so it's
742
- shareable and survives a full refresh. Standalone (\`<slug>.lotics.app\`) it's a
743
- normal browser router with real path URLs. A single-screen app can drop the
744
- router and render one screen directly.
745
-
746
- See https://lotics.ai/docs/app-sdk for the SDK reference.
747
- `,
748
- },
749
- ];
750
- }
751
- function escapeHtml(s) {
752
- return s.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
753
- }