@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.
- package/dist/render_page.js +100 -100
- package/dist/src/cli.js +100 -100
- package/dist/{client.d.ts → src/client.d.ts} +1 -1
- package/dist/src/package_content_types.d.ts +70 -0
- package/dist/src/package_content_types.js +12 -0
- package/package.json +2 -2
- package/dist/app_commands.d.ts +0 -401
- package/dist/app_commands.js +0 -1520
- package/dist/app_commands.test.d.ts +0 -1
- package/dist/app_commands.test.js +0 -898
- package/dist/app_workflow_check.d.ts +0 -77
- package/dist/app_workflow_check.js +0 -169
- package/dist/app_workflow_check.test.d.ts +0 -1
- package/dist/app_workflow_check.test.js +0 -166
- package/dist/args.d.ts +0 -42
- package/dist/args.js +0 -121
- package/dist/args.test.d.ts +0 -1
- package/dist/args.test.js +0 -88
- package/dist/child_env.d.ts +0 -13
- package/dist/child_env.js +0 -24
- package/dist/cli.d.ts +0 -2
- package/dist/cli.js +0 -1095
- package/dist/cli_dispatch.test.d.ts +0 -1
- package/dist/cli_dispatch.test.js +0 -89
- package/dist/client.test.d.ts +0 -1
- package/dist/client.test.js +0 -47
- package/dist/config.d.ts +0 -124
- package/dist/config.js +0 -315
- package/dist/config.test.d.ts +0 -1
- package/dist/config.test.js +0 -319
- package/dist/dev/file_relay.d.ts +0 -39
- package/dist/dev/file_relay.js +0 -87
- package/dist/dev/file_relay.test.d.ts +0 -1
- package/dist/dev/file_relay.test.js +0 -87
- package/dist/dev/rpc_handler.d.ts +0 -21
- package/dist/dev/rpc_handler.js +0 -166
- package/dist/dev/rpc_handler.test.d.ts +0 -1
- package/dist/dev/rpc_handler.test.js +0 -107
- package/dist/dev/server.d.ts +0 -75
- package/dist/dev/server.js +0 -443
- package/dist/dev/upload_relay.d.ts +0 -36
- package/dist/dev/upload_relay.js +0 -61
- package/dist/dev/upload_relay.test.d.ts +0 -1
- package/dist/dev/upload_relay.test.js +0 -76
- package/dist/dev/wrapper_page.d.ts +0 -40
- package/dist/dev/wrapper_page.js +0 -335
- package/dist/docx.d.ts +0 -2
- package/dist/docx.js +0 -341
- package/dist/docx.test.d.ts +0 -1
- package/dist/docx.test.js +0 -145
- package/dist/file_command_io.d.ts +0 -12
- package/dist/file_command_io.js +0 -34
- package/dist/generate_app_agents_dts.d.ts +0 -16
- package/dist/generate_app_agents_dts.js +0 -62
- package/dist/generate_app_agents_dts.test.d.ts +0 -1
- package/dist/generate_app_agents_dts.test.js +0 -45
- package/dist/generate_app_fields.d.ts +0 -56
- package/dist/generate_app_fields.js +0 -148
- package/dist/generate_app_fields.test.d.ts +0 -1
- package/dist/generate_app_fields.test.js +0 -108
- package/dist/generate_app_queries_dts.d.ts +0 -21
- package/dist/generate_app_queries_dts.js +0 -57
- package/dist/generate_app_workflows_dts.d.ts +0 -39
- package/dist/generate_app_workflows_dts.js +0 -229
- package/dist/generate_app_workflows_dts.test.d.ts +0 -1
- package/dist/generate_app_workflows_dts.test.js +0 -109
- package/dist/generate_package_fields.d.ts +0 -49
- package/dist/generate_package_fields.js +0 -157
- package/dist/generate_package_fields.test.d.ts +0 -1
- package/dist/generate_package_fields.test.js +0 -59
- package/dist/inputs.d.ts +0 -38
- package/dist/inputs.js +0 -50
- package/dist/inputs.test.d.ts +0 -1
- package/dist/inputs.test.js +0 -89
- package/dist/preview.d.ts +0 -3
- package/dist/preview.js +0 -233
- package/dist/starter_template.d.ts +0 -64
- package/dist/starter_template.js +0 -753
- package/dist/starter_template.test.d.ts +0 -1
- package/dist/starter_template.test.js +0 -115
- package/dist/version.d.ts +0 -1
- package/dist/version.js +0 -6
- package/dist/xlsx.d.ts +0 -2
- package/dist/xlsx.js +0 -489
- package/dist/xlsx.test.d.ts +0 -1
- package/dist/xlsx.test.js +0 -131
- /package/dist/{client.js → src/client.js} +0 -0
package/dist/starter_template.js
DELETED
|
@@ -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, "&").replace(/</g, "<").replace(/>/g, ">");
|
|
753
|
-
}
|