@lumerahq/cli 0.19.10 → 0.19.12

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.
@@ -4,7 +4,7 @@ import {
4
4
  import {
5
5
  createApiClient,
6
6
  isApiErrorStatus
7
- } from "./chunk-EDRAYUWN.js";
7
+ } from "./chunk-MDCAEFUH.js";
8
8
  import {
9
9
  findProjectRoot,
10
10
  getAppName
@@ -14,17 +14,7 @@ import {
14
14
  import pc from "picocolors";
15
15
  import { existsSync, readFileSync, writeFileSync, mkdirSync, readdirSync } from "fs";
16
16
  import { join } from "path";
17
- var LEGACY_DEPS_FILE = "platform/project_deps.json";
18
17
  var SHARED_COLLECTIONS_DIR = "platform/collections/shared";
19
- function loadLegacyDeps(projectRoot) {
20
- const depsPath = join(projectRoot, LEGACY_DEPS_FILE);
21
- if (!existsSync(depsPath)) return null;
22
- try {
23
- return JSON.parse(readFileSync(depsPath, "utf-8"));
24
- } catch {
25
- return null;
26
- }
27
- }
28
18
  function sharedCollectionsDir(projectRoot) {
29
19
  return join(projectRoot, SHARED_COLLECTIONS_DIR);
30
20
  }
@@ -58,13 +48,6 @@ function loadSharedCollectionDeps(projectRoot) {
58
48
  }
59
49
  return deps2.sort((a, b) => a.file.localeCompare(b.file));
60
50
  }
61
- async function projectPermissionsModeEnabled(api) {
62
- try {
63
- return await api.isProjectPermissionsModeEnabled();
64
- } catch {
65
- return false;
66
- }
67
- }
68
51
  function declarationForShare(share, existing) {
69
52
  return {
70
53
  ...existing ?? {},
@@ -134,156 +117,76 @@ async function syncResourceShareDeps(projectRoot, api, projectExternalId, opts)
134
117
  }
135
118
  return true;
136
119
  }
137
- async function syncLegacyDeps(projectRoot, api, quiet = false) {
138
- const deps2 = loadLegacyDeps(projectRoot);
139
- if (!deps2 || !deps2.dependencies || Object.keys(deps2.dependencies).length === 0) {
140
- return true;
141
- }
142
- let allOk = true;
143
- for (const [depProject, config] of Object.entries(deps2.dependencies)) {
144
- try {
145
- const manifest = await api.getProjectManifest(depProject);
146
- const remoteNames = new Set(manifest.collections.map((c) => c.name));
147
- for (const col of config.collections) {
148
- if (remoteNames.has(col)) {
149
- if (!quiet) console.log(pc.green(" \u2713"), `${depProject}/${col}`);
150
- } else {
151
- if (!quiet) console.log(pc.red(" \u2717"), `${depProject}/${col} \u2014 not found in project manifest`);
152
- allOk = false;
153
- }
154
- }
155
- } catch (e) {
156
- if (!quiet) console.log(pc.red(" \u2717"), `${depProject}: failed to fetch manifest \u2014 ${e}`);
157
- allOk = false;
158
- }
159
- }
160
- return allOk;
161
- }
162
- async function projectResourceDepsEnabled(projectRoot) {
163
- const root = projectRoot ?? findProjectRoot();
164
- loadEnv(root);
165
- const appName = getAppName(root);
166
- const api = createApiClient(void 0, void 0, appName);
167
- return projectPermissionsModeEnabled(api);
168
- }
169
120
  async function syncDeps(projectRoot, options = {}) {
170
121
  const root = projectRoot ?? findProjectRoot();
171
122
  loadEnv(root);
172
123
  const appName = getAppName(root);
173
124
  const api = createApiClient(void 0, void 0, appName);
174
- const flagOn = await projectPermissionsModeEnabled(api);
175
125
  const quiet = options.quiet === true;
176
126
  const write = options.write === true;
177
- if (flagOn) {
178
- return syncResourceShareDeps(root, api, appName, {
179
- write,
180
- quiet,
181
- ignorePermissionDenied: options.ignorePermissionDenied === true
182
- });
183
- }
184
- if (options.legacyWhenDisabled === false) {
185
- return true;
186
- }
187
- return syncLegacyDeps(root, api, quiet);
127
+ return syncResourceShareDeps(root, api, appName, {
128
+ write,
129
+ quiet,
130
+ ignorePermissionDenied: options.ignorePermissionDenied === true
131
+ });
188
132
  }
189
133
  async function deps(args) {
190
134
  const sub = args[0];
191
135
  const projectRoot = findProjectRoot();
192
136
  loadEnv(projectRoot);
193
- let cachedFlagOn;
194
- async function flagOn() {
195
- if (cachedFlagOn !== void 0) return cachedFlagOn;
196
- cachedFlagOn = await projectResourceDepsEnabled(projectRoot);
197
- return cachedFlagOn;
198
- }
199
137
  if (sub === "sync") {
200
- const enabled = await flagOn();
201
138
  console.log();
202
139
  console.log(pc.cyan(pc.bold(" Deps Sync")));
203
- console.log(pc.dim(enabled ? " Syncing incoming resource shares into platform/collections/shared..." : " Verifying legacy cross-project dependencies..."));
140
+ console.log(pc.dim(" Syncing incoming resource shares into platform/collections/shared..."));
204
141
  console.log();
205
- const ok = await syncDeps(projectRoot, { write: enabled });
142
+ const ok = await syncDeps(projectRoot, { write: true });
206
143
  if (!ok) {
207
144
  console.log();
208
- console.log(pc.red(enabled ? " Some resource-share dependencies could not be synced." : " Some dependencies could not be resolved."));
145
+ console.log(pc.red(" Some resource-share dependencies could not be synced."));
209
146
  process.exit(1);
210
147
  }
211
148
  console.log();
212
- console.log(pc.green(enabled ? " Resource shares synced." : " All dependencies verified."));
149
+ console.log(pc.green(" Resource shares synced."));
213
150
  return;
214
151
  }
215
152
  if (sub === "list") {
216
- if (await flagOn()) {
217
- const sharedDeps = loadSharedCollectionDeps(projectRoot);
218
- if (sharedDeps.length === 0) {
219
- console.log(pc.dim(" No shared collection declarations in platform/collections/shared"));
220
- return;
221
- }
222
- console.log();
223
- console.log(pc.cyan(pc.bold(" Shared Collection Dependencies")));
224
- console.log();
225
- for (const { file, dep } of sharedDeps) {
226
- const source = dep.source_project ?? "unknown-source";
227
- const collection = dep.collection ?? "unknown-table";
228
- const privilege = dep.privilege ?? "collection.read";
229
- console.log(` ${pc.bold(file)}`);
230
- console.log(` ${source}/${collection} ${pc.dim(`(${privilege})`)}`);
231
- }
232
- console.log();
233
- return;
234
- }
235
- const depsData = loadLegacyDeps(projectRoot);
236
- if (!depsData || Object.keys(depsData.dependencies).length === 0) {
237
- console.log(pc.dim(" No dependencies declared in platform/project_deps.json"));
153
+ const sharedDeps = loadSharedCollectionDeps(projectRoot);
154
+ if (sharedDeps.length === 0) {
155
+ console.log(pc.dim(" No shared collection declarations in platform/collections/shared"));
238
156
  return;
239
157
  }
240
158
  console.log();
241
- console.log(pc.cyan(pc.bold(" Project Dependencies")));
159
+ console.log(pc.cyan(pc.bold(" Shared Collection Dependencies")));
242
160
  console.log();
243
- for (const [depProject, config] of Object.entries(depsData.dependencies)) {
244
- console.log(` ${pc.bold(depProject)}`);
245
- for (const col of config.collections) {
246
- console.log(` \u2022 ${col}`);
247
- }
161
+ for (const { file, dep } of sharedDeps) {
162
+ const source = dep.source_project ?? "unknown-source";
163
+ const collection = dep.collection ?? "unknown-table";
164
+ const privilege = dep.privilege ?? "collection.read";
165
+ console.log(` ${pc.bold(file)}`);
166
+ console.log(` ${source}/${collection} ${pc.dim(`(${privilege})`)}`);
248
167
  }
249
168
  console.log();
250
169
  return;
251
170
  }
252
171
  if (sub === "init") {
253
- if (await flagOn()) {
254
- const dir = sharedCollectionsDir(projectRoot);
255
- mkdirSync(dir, { recursive: true });
256
- console.log(pc.green(" \u2713"), "Created platform/collections/shared/");
257
- return;
258
- }
259
- const depsPath = join(projectRoot, LEGACY_DEPS_FILE);
260
- if (existsSync(depsPath)) {
261
- console.log(pc.yellow(" platform/project_deps.json already exists."));
262
- return;
263
- }
264
- const platformDir = join(projectRoot, "platform");
265
- mkdirSync(platformDir, { recursive: true });
266
- const initial = { dependencies: {} };
267
- writeFileSync(depsPath, JSON.stringify(initial, null, 2) + "\n");
268
- console.log(pc.green(" \u2713"), "Created platform/project_deps.json");
172
+ const dir = sharedCollectionsDir(projectRoot);
173
+ mkdirSync(dir, { recursive: true });
174
+ console.log(pc.green(" \u2713"), "Created platform/collections/shared/");
269
175
  return;
270
176
  }
271
177
  console.log(`
272
178
  ${pc.bold("lumera deps")} \u2014 manage cross-project dependencies
273
179
 
274
180
  ${pc.bold("Commands:")}
275
- sync Sync dependencies. With project permissions on, writes incoming
276
- resource shares to platform/collections/shared/*.json
181
+ sync Sync incoming resource shares to platform/collections/shared/*.json
277
182
  list Show declared dependencies
278
- init Create dependency storage for the current feature mode
183
+ init Create dependency storage
279
184
 
280
- ${pc.bold("Project permissions mode:")} platform/collections/shared/*.json
281
- ${pc.bold("Legacy mode:")} platform/project_deps.json
185
+ ${pc.bold("Dependencies:")} platform/collections/shared/*.json
282
186
  `);
283
187
  }
284
188
 
285
189
  export {
286
- projectResourceDepsEnabled,
287
190
  syncDeps,
288
191
  deps
289
192
  };
@@ -90,10 +90,6 @@ var ApiClient = class {
90
90
  async getMe() {
91
91
  return this.request("/api/me");
92
92
  }
93
- async isProjectPermissionsModeEnabled() {
94
- const me = await this.getMe();
95
- return me.user?.feature_flags?.project_permissions_mode === true;
96
- }
97
93
  async listCollections() {
98
94
  const result = await this.request("/api/pb/collections");
99
95
  return result.items;
@@ -116,9 +116,10 @@ async function deploy(options) {
116
116
  throw new Error(`Deploy failed: ${await res.text()}`);
117
117
  }
118
118
  const result = await res.json();
119
+ const launchUrl = result.launch_url || result.url;
119
120
  console.log(pc.green(`
120
- Deployed! ${result.url}`));
121
- return { url: result.url, version };
121
+ Deployed! ${launchUrl}`));
122
+ return { url: launchUrl, version };
122
123
  }
123
124
  function isPortInUse(port, host = "127.0.0.1") {
124
125
  return new Promise((resolve2) => {
@@ -1,15 +1,13 @@
1
1
  import {
2
2
  deps,
3
- projectResourceDepsEnabled,
4
3
  syncDeps
5
- } from "./chunk-WWEIOMW6.js";
4
+ } from "./chunk-4WZMUFC7.js";
6
5
  import "./chunk-2CR762KB.js";
7
- import "./chunk-EDRAYUWN.js";
6
+ import "./chunk-MDCAEFUH.js";
8
7
  import "./chunk-ZH3NVYEQ.js";
9
8
  import "./chunk-FJFIWC7G.js";
10
9
  import "./chunk-PNKVD2UK.js";
11
10
  export {
12
11
  deps,
13
- projectResourceDepsEnabled,
14
12
  syncDeps
15
13
  };
@@ -1,16 +1,15 @@
1
1
  import {
2
2
  dev
3
- } from "./chunk-53NOF33P.js";
3
+ } from "./chunk-OGG5TR4Y.js";
4
4
  import {
5
- projectResourceDepsEnabled,
6
5
  syncDeps
7
- } from "./chunk-WWEIOMW6.js";
6
+ } from "./chunk-4WZMUFC7.js";
8
7
  import {
9
8
  loadEnv
10
9
  } from "./chunk-2CR762KB.js";
11
10
  import {
12
11
  createApiClient
13
- } from "./chunk-EDRAYUWN.js";
12
+ } from "./chunk-MDCAEFUH.js";
14
13
  import {
15
14
  findProjectRoot,
16
15
  getApiUrl,
@@ -125,14 +124,11 @@ async function dev2(args) {
125
124
  const appName = getAppName(projectRoot);
126
125
  const appTitle = getAppTitle(projectRoot);
127
126
  const apiUrl = getApiUrl();
128
- if (await projectResourceDepsEnabled(projectRoot)) {
129
- await syncDeps(projectRoot, {
130
- write: true,
131
- quiet: true,
132
- legacyWhenDisabled: false,
133
- ignorePermissionDenied: true
134
- });
135
- }
127
+ await syncDeps(projectRoot, {
128
+ write: true,
129
+ quiet: true,
130
+ ignorePermissionDenied: true
131
+ });
136
132
  if (!flags["skip-setup"]) {
137
133
  const fresh = await isFreshProject(projectRoot);
138
134
  if (fresh) {
package/dist/index.js CHANGED
@@ -219,39 +219,39 @@ async function main() {
219
219
  switch (command) {
220
220
  // Resource commands
221
221
  case "plan":
222
- await import("./resources-M3ILBD6X.js").then((m) => m.plan(args.slice(1)));
222
+ await import("./resources-DPGY7KC3.js").then((m) => m.plan(args.slice(1)));
223
223
  break;
224
224
  case "apply":
225
- await import("./resources-M3ILBD6X.js").then((m) => m.apply(args.slice(1)));
225
+ await import("./resources-DPGY7KC3.js").then((m) => m.apply(args.slice(1)));
226
226
  break;
227
227
  case "pull":
228
- await import("./resources-M3ILBD6X.js").then((m) => m.pull(args.slice(1)));
228
+ await import("./resources-DPGY7KC3.js").then((m) => m.pull(args.slice(1)));
229
229
  break;
230
230
  case "destroy":
231
- await import("./resources-M3ILBD6X.js").then((m) => m.destroy(args.slice(1)));
231
+ await import("./resources-DPGY7KC3.js").then((m) => m.destroy(args.slice(1)));
232
232
  break;
233
233
  case "list":
234
- await import("./resources-M3ILBD6X.js").then((m) => m.list(args.slice(1)));
234
+ await import("./resources-DPGY7KC3.js").then((m) => m.list(args.slice(1)));
235
235
  break;
236
236
  case "show":
237
- await import("./resources-M3ILBD6X.js").then((m) => m.show(args.slice(1)));
237
+ await import("./resources-DPGY7KC3.js").then((m) => m.show(args.slice(1)));
238
238
  break;
239
239
  case "diff":
240
- await import("./resources-M3ILBD6X.js").then((m) => m.diff(args.slice(1)));
240
+ await import("./resources-DPGY7KC3.js").then((m) => m.diff(args.slice(1)));
241
241
  break;
242
242
  // Development
243
243
  case "dev":
244
- await import("./dev-35XSSGOG.js").then((m) => m.dev(args.slice(1)));
244
+ await import("./dev-EZTXUSD2.js").then((m) => m.dev(args.slice(1)));
245
245
  break;
246
246
  case "run":
247
- await import("./run-YUL73K5O.js").then((m) => m.run(args.slice(1)));
247
+ await import("./run-R6MO23U7.js").then((m) => m.run(args.slice(1)));
248
248
  break;
249
249
  // Project
250
250
  case "init":
251
- await import("./init-FW4RFXLL.js").then((m) => m.init(args.slice(1)));
251
+ await import("./init-QCNR4ULM.js").then((m) => m.init(args.slice(1)));
252
252
  break;
253
253
  case "register":
254
- await import("./register-HR2QQFWX.js").then((m) => m.register(args.slice(1)));
254
+ await import("./register-QBRKXWNX.js").then((m) => m.register(args.slice(1)));
255
255
  break;
256
256
  case "templates":
257
257
  await import("./templates-LNUOTNLN.js").then((m) => m.templates(subcommand, args.slice(2)));
@@ -268,7 +268,7 @@ async function main() {
268
268
  break;
269
269
  // Dependencies
270
270
  case "deps":
271
- await import("./deps-WSZGH35V.js").then((m) => m.deps(args.slice(1)));
271
+ await import("./deps-KVHWZARX.js").then((m) => m.deps(args.slice(1)));
272
272
  break;
273
273
  // Auth
274
274
  case "login":
@@ -7,7 +7,7 @@ import {
7
7
  } from "./chunk-BHYDYR75.js";
8
8
  import {
9
9
  createApiClient
10
- } from "./chunk-EDRAYUWN.js";
10
+ } from "./chunk-MDCAEFUH.js";
11
11
  import {
12
12
  getToken,
13
13
  init_auth,
@@ -6,7 +6,7 @@ import {
6
6
  } from "./chunk-BHYDYR75.js";
7
7
  import {
8
8
  createApiClient
9
- } from "./chunk-EDRAYUWN.js";
9
+ } from "./chunk-MDCAEFUH.js";
10
10
  import {
11
11
  findProjectRoot,
12
12
  getAppName,
@@ -1,17 +1,16 @@
1
1
  import {
2
2
  deploy
3
- } from "./chunk-53NOF33P.js";
3
+ } from "./chunk-OGG5TR4Y.js";
4
4
  import {
5
- projectResourceDepsEnabled,
6
5
  syncDeps
7
- } from "./chunk-WWEIOMW6.js";
6
+ } from "./chunk-4WZMUFC7.js";
8
7
  import {
9
8
  loadEnv
10
9
  } from "./chunk-2CR762KB.js";
11
10
  import {
12
11
  ApiError,
13
12
  createApiClient
14
- } from "./chunk-EDRAYUWN.js";
13
+ } from "./chunk-MDCAEFUH.js";
15
14
  import {
16
15
  findProjectRoot,
17
16
  getApiUrl,
@@ -3385,9 +3384,9 @@ async function pull(args) {
3385
3384
  console.log(pc2.bold(" Collections:"));
3386
3385
  await pullCollections(api, platformDir, name || void 0, appName);
3387
3386
  console.log();
3388
- if (!name && await projectResourceDepsEnabled(projectRoot)) {
3387
+ if (!name) {
3389
3388
  console.log(pc2.bold(" Resource shares:"));
3390
- await syncDeps(projectRoot, { write: true, legacyWhenDisabled: false, ignorePermissionDenied: true });
3389
+ await syncDeps(projectRoot, { write: true, ignorePermissionDenied: true });
3391
3390
  console.log();
3392
3391
  }
3393
3392
  }
@@ -3,7 +3,7 @@ import {
3
3
  } from "./chunk-2CR762KB.js";
4
4
  import {
5
5
  createApiClient
6
- } from "./chunk-EDRAYUWN.js";
6
+ } from "./chunk-MDCAEFUH.js";
7
7
  import {
8
8
  findProjectRoot,
9
9
  getApiUrl,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lumerahq/cli",
3
- "version": "0.19.10",
3
+ "version": "0.19.12",
4
4
  "description": "CLI for building and deploying Lumera apps",
5
5
  "type": "module",
6
6
  "engines": {
@@ -13,6 +13,11 @@
13
13
  "dist",
14
14
  "templates"
15
15
  ],
16
+ "scripts": {
17
+ "build": "tsup src/index.ts --format esm --clean",
18
+ "dev": "tsup src/index.ts --format esm --watch",
19
+ "prepublishOnly": "pnpm build"
20
+ },
16
21
  "dependencies": {
17
22
  "acorn": "^8.16.0",
18
23
  "archiver": "^7.0.1",
@@ -30,9 +35,5 @@
30
35
  },
31
36
  "publishConfig": {
32
37
  "access": "public"
33
- },
34
- "scripts": {
35
- "build": "tsup src/index.ts --format esm --clean",
36
- "dev": "tsup src/index.ts --format esm --watch"
37
38
  }
38
- }
39
+ }
@@ -17,6 +17,12 @@ src/
17
17
  scripts/ # Utility scripts (seed data, migrations, etc.)
18
18
  ```
19
19
 
20
+ The starter UI is intentionally throwaway. `src/routes/index.tsx` is just the
21
+ default landing page shown before the real app exists, and `index.html` is only
22
+ the Vite shell. When building the user's first real workflow or screen, replace
23
+ the starter home page with the app experience instead of preserving or building
24
+ around the placeholder content.
25
+
20
26
  ## Lumera Concepts
21
27
 
22
28
  A Lumera app is built from these primitives — all defined as code in `platform/`:
@@ -110,6 +116,29 @@ fetch('https://app.lumerahq.com/api/pb/sql', ...);
110
116
  - Token is not needed — the bridge handles auth via the parent session
111
117
  - `X-Lumera-Project` header is not needed — the parent adds it automatically
112
118
 
119
+ ## Shareable Links
120
+
121
+ For any URL a human will open (copy-link, invites, emails, QR codes), use `getShareableAppUrl()` or `buildShareableAppUrl()` from `@lumerahq/ui/lib` — **never `window.location.href`**. The app runs inside an iframe, so `window.location` returns the internal `/_apps/{company}/{app}/...` URL instead of the shell's `/app/{appId}` URL.
122
+
123
+ ```ts
124
+ import { buildShareableAppUrl, getShareableAppUrl } from '@lumerahq/ui/lib';
125
+ const link = getShareableAppUrl(); // preserves current path/search/hash
126
+ const invoiceLink = buildShareableAppUrl('/invoices/123', { router: 'hash' });
127
+ ```
128
+
129
+ When reporting a deployed app, use `launch_url` or `url` from the deploy response. Do not share `iframe_url`; it is only the internal iframe/static asset mount.
130
+
131
+ ## Preview and Verification
132
+
133
+ The Studio environment already runs the Vite dev server and exposes it through
134
+ the portal's **Preview tab**. Users are not expected to run local developer
135
+ commands, and raw localhost URLs are not reachable from the Studio portal.
136
+
137
+ **Never tell users to run `pnpm dev`, `pnpm dev:vite`, `npm run dev`, or any
138
+ other dev-server command. Never tell users to open `localhost`, `127.0.0.1`, or
139
+ Vite ports such as `http://localhost:5173/`.** When explaining how to verify UI
140
+ changes, tell them to open the **Preview tab**.
141
+
113
142
 
114
143
  ## Workflow
115
144
 
@@ -122,7 +151,7 @@ Follow the user's lead. If they tell you exactly what to build, build it. The wo
122
151
 
123
152
  ### Step 2: Build (one slice at a time)
124
153
  4. **Build horizontally** — Pick the first step. Build the full slice: collection schema → `lumera apply` → seed data → UI route/components → commit. Each slice should be deployable and usable on its own.
125
- 5. **Stop and ask for feedback** — Tell the user to open the **Preview tab** to see the app. The dev server starts automatically you do NOT need to run `pnpm dev` or start any server. Iterate on the slice until they're happy.
154
+ 5. **Stop and ask for feedback** — Tell the user to open the **Preview tab** to see the app. Do not mention local dev-server commands or localhost URLs. Iterate on the slice until they're happy.
126
155
  6. **Repeat** — Move to the next step. Build, deploy, get feedback.
127
156
 
128
157
  ### Rules
@@ -1,5 +1,5 @@
1
1
  {
2
- "$schema": "https://biomejs.dev/schemas/2.0.0/schema.json",
2
+ "$schema": "https://biomejs.dev/schemas/2.4.16/schema.json",
3
3
  "vcs": {
4
4
  "enabled": true,
5
5
  "clientKind": "git",
@@ -24,7 +24,7 @@
24
24
  "check:ci": "biome check . && tsr generate && tsc --noEmit"
25
25
  },
26
26
  "dependencies": {
27
- "@lumerahq/ui": "^0.7.6",
27
+ "@lumerahq/ui": "^0.9.1",
28
28
  "@tanstack/react-query": "^5.90.11",
29
29
  "@tanstack/react-router": "1.155.0",
30
30
  "@tanstack/router-plugin": "1.155.0",
@@ -1,5 +1,5 @@
1
- import { type ClassValue, clsx } from "clsx";
2
- import { twMerge } from "tailwind-merge";
1
+ import { type ClassValue, clsx } from 'clsx';
2
+ import { twMerge } from 'tailwind-merge';
3
3
 
4
4
  export function cn(...inputs: ClassValue[]) {
5
5
  return twMerge(clsx(inputs));
@@ -125,6 +125,6 @@ if (rootElement && !rootElement.innerHTML) {
125
125
  ReactDOM.createRoot(rootElement).render(
126
126
  <StrictMode>
127
127
  <App />
128
- </StrictMode>,
128
+ </StrictMode>
129
129
  );
130
130
  }
@@ -1,6 +1,6 @@
1
1
  import { createFileRoute } from '@tanstack/react-router';
2
+ import { Bot, LayoutGrid, MessageSquare, Workflow } from 'lucide-react';
2
3
  import { useContext } from 'react';
3
- import { MessageSquare, LayoutGrid, Bot, Workflow } from 'lucide-react';
4
4
  import { AuthContext } from '../lib/auth';
5
5
 
6
6
  export const Route = createFileRoute('/')({
@@ -14,8 +14,12 @@ function HomePage() {
14
14
  <div className="space-y-10">
15
15
  {/* Header */}
16
16
  <div>
17
- <h1 className="text-2xl font-semibold tracking-tight">Welcome{auth?.user?.name ? `, ${auth.user.name}` : ''}</h1>
18
- <p className="text-muted-foreground mt-2 text-[0.95rem] leading-relaxed">Your app is ready. Use Studio to start building.</p>
17
+ <h1 className="text-2xl font-semibold tracking-tight">
18
+ Welcome{auth?.user?.name ? `, ${auth.user.name}` : ''}
19
+ </h1>
20
+ <p className="text-muted-foreground mt-2 text-[0.95rem] leading-relaxed">
21
+ Your app is ready. Use Studio to start building.
22
+ </p>
19
23
  </div>
20
24
 
21
25
  {/* Getting Started */}
@@ -27,8 +31,8 @@ function HomePage() {
27
31
  <div className="space-y-2">
28
32
  <h2 className="font-semibold text-lg tracking-tight">Build with Studio</h2>
29
33
  <p className="text-muted-foreground text-sm leading-relaxed">
30
- Switch to the <strong>Chat</strong> tab and tell the agent what you want to build.
31
- It will set up your data, write the logic, and build your UI — all from a conversation.
34
+ Switch to the <strong>Chat</strong> tab and tell the agent what you want to build. It
35
+ will set up your data, write the logic, and build your UI — all from a conversation.
32
36
  </p>
33
37
  </div>
34
38
  </div>
@@ -36,7 +40,9 @@ function HomePage() {
36
40
 
37
41
  {/* Example prompts */}
38
42
  <div>
39
- <h2 className="text-sm font-semibold uppercase tracking-wider text-muted-foreground mb-4">Try asking the agent</h2>
43
+ <h2 className="text-sm font-semibold uppercase tracking-wider text-muted-foreground mb-4">
44
+ Try asking the agent
45
+ </h2>
40
46
  <div className="flex flex-wrap gap-2">
41
47
  {[
42
48
  'Build an invoice processing app with approval workflows',
@@ -57,27 +63,32 @@ function HomePage() {
57
63
 
58
64
  {/* What you can build */}
59
65
  <div>
60
- <h2 className="text-sm font-semibold uppercase tracking-wider text-muted-foreground mb-5">What you can build</h2>
66
+ <h2 className="text-sm font-semibold uppercase tracking-wider text-muted-foreground mb-5">
67
+ What you can build
68
+ </h2>
61
69
  <div className="grid gap-4 md:grid-cols-3">
62
70
  <div className="group rounded-xl bg-card p-5 space-y-3 shadow-[0_1px_2px_0_oklch(0_0_0/0.03)] border border-border/60 hover:shadow-[0_4px_12px_-2px_oklch(0_0_0/0.06)] hover:border-border transition-all duration-300">
63
71
  <LayoutGrid className="size-5 text-primary transition-colors duration-300" />
64
72
  <h3 className="font-medium text-sm tracking-tight">Internal apps</h3>
65
73
  <p className="text-xs text-muted-foreground leading-relaxed">
66
- Back-office tools for your team — dashboards, approval queues, and operational workflows.
74
+ Back-office tools for your team — dashboards, approval queues, and operational
75
+ workflows.
67
76
  </p>
68
77
  </div>
69
78
  <div className="group rounded-xl bg-card p-5 space-y-3 shadow-[0_1px_2px_0_oklch(0_0_0/0.03)] border border-border/60 hover:shadow-[0_4px_12px_-2px_oklch(0_0_0/0.06)] hover:border-border transition-all duration-300">
70
79
  <Bot className="size-5 text-primary transition-colors duration-300" />
71
80
  <h3 className="font-medium text-sm tracking-tight">Agent-powered workflows</h3>
72
81
  <p className="text-xs text-muted-foreground leading-relaxed">
73
- AI agents that extract data, run reviews, draft outputs, and route exceptions — humans stay in the loop.
82
+ AI agents that extract data, run reviews, draft outputs, and route exceptions — humans
83
+ stay in the loop.
74
84
  </p>
75
85
  </div>
76
86
  <div className="group rounded-xl bg-card p-5 space-y-3 shadow-[0_1px_2px_0_oklch(0_0_0/0.03)] border border-border/60 hover:shadow-[0_4px_12px_-2px_oklch(0_0_0/0.06)] hover:border-border transition-all duration-300">
77
87
  <Workflow className="size-5 text-primary transition-colors duration-300" />
78
88
  <h3 className="font-medium text-sm tracking-tight">Automations</h3>
79
89
  <p className="text-xs text-muted-foreground leading-relaxed">
80
- Connect to your systems, process inbound emails, and trigger actions automatically — with a full audit trail.
90
+ Connect to your systems, process inbound emails, and trigger actions automatically —
91
+ with a full audit trail.
81
92
  </p>
82
93
  </div>
83
94
  </div>
@@ -34,7 +34,7 @@ body {
34
34
  --secondary-foreground: oklch(0.16 0.01 60);
35
35
  /* Warm stone muted tones */
36
36
  --muted: oklch(0.955 0.005 60);
37
- --muted-foreground: oklch(0.50 0.01 60);
37
+ --muted-foreground: oklch(0.5 0.01 60);
38
38
  /* Accent — lighter orange tint */
39
39
  --accent: oklch(0.95 0.03 50);
40
40
  --accent-foreground: oklch(0.35 0.12 35);
@@ -20,9 +20,6 @@ export default defineConfig({
20
20
  dedupe: ['react', 'react-dom'],
21
21
  },
22
22
  server: {
23
- allowedHosts: [
24
- 'mac.lumerahq.com',
25
- 'untunable-del-nonephemerally.ngrok-free.dev',
26
- ],
23
+ allowedHosts: ['mac.lumerahq.com'],
27
24
  },
28
25
  });