frontier-os-app-builder 1.0.0

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 (55) hide show
  1. package/README.md +92 -0
  2. package/agents/fos-executor.md +460 -0
  3. package/agents/fos-plan-checker.md +386 -0
  4. package/agents/fos-planner.md +416 -0
  5. package/agents/fos-researcher.md +358 -0
  6. package/agents/fos-verifier.md +491 -0
  7. package/bin/fos-tools.cjs +794 -0
  8. package/bin/install.js +234 -0
  9. package/commands/fos/add-feature.md +29 -0
  10. package/commands/fos/discuss.md +31 -0
  11. package/commands/fos/execute.md +35 -0
  12. package/commands/fos/new-app.md +39 -0
  13. package/commands/fos/new-milestone.md +28 -0
  14. package/commands/fos/next.md +29 -0
  15. package/commands/fos/plan.md +37 -0
  16. package/commands/fos/ship.md +29 -0
  17. package/commands/fos/status.md +22 -0
  18. package/package.json +30 -0
  19. package/references/app-patterns.md +501 -0
  20. package/references/deployment.md +395 -0
  21. package/references/module-inference.md +349 -0
  22. package/references/sdk-surface.md +1622 -0
  23. package/references/verification-rules.md +404 -0
  24. package/templates/app/gitignore +25 -0
  25. package/templates/app/index.css +111 -0
  26. package/templates/app/index.html +19 -0
  27. package/templates/app/layout.tsx +45 -0
  28. package/templates/app/main-router.tsx +17 -0
  29. package/templates/app/main-simple.tsx +19 -0
  30. package/templates/app/package.json +36 -0
  31. package/templates/app/postcss.config.js +5 -0
  32. package/templates/app/router.tsx +22 -0
  33. package/templates/app/sdk-context.tsx +33 -0
  34. package/templates/app/test-setup.ts +19 -0
  35. package/templates/app/tsconfig.json +22 -0
  36. package/templates/app/vercel.json +127 -0
  37. package/templates/app/vite.config.ts +15 -0
  38. package/templates/state/context.md +248 -0
  39. package/templates/state/manifest.json +11 -0
  40. package/templates/state/plan.md +187 -0
  41. package/templates/state/project.md +118 -0
  42. package/templates/state/requirements.md +133 -0
  43. package/templates/state/roadmap.md +129 -0
  44. package/templates/state/state.md +131 -0
  45. package/templates/state/summary.md +273 -0
  46. package/workflows/add-feature.md +234 -0
  47. package/workflows/discuss.md +310 -0
  48. package/workflows/execute-plan.md +222 -0
  49. package/workflows/execute.md +338 -0
  50. package/workflows/new-app.md +331 -0
  51. package/workflows/new-milestone.md +258 -0
  52. package/workflows/next.md +157 -0
  53. package/workflows/plan.md +310 -0
  54. package/workflows/ship.md +296 -0
  55. package/workflows/status.md +145 -0
@@ -0,0 +1,404 @@
1
+ # Verification Rules
2
+
3
+ Defines what the fos-verifier agent must check after generating or modifying a Frontier OS app. Every rule has a category, a description, and the expected pass condition.
4
+
5
+ ---
6
+
7
+ ## Structure Checks
8
+
9
+ These verify the generated file tree matches the standard Frontier OS app layout.
10
+
11
+ ### S-01: Required files exist
12
+
13
+ The following files must be present:
14
+
15
+ ```
16
+ index.html
17
+ package.json
18
+ postcss.config.js
19
+ tsconfig.json
20
+ vercel.json
21
+ vite.config.ts
22
+ src/main.tsx
23
+ src/lib/sdk-context.tsx
24
+ src/views/Layout.tsx
25
+ src/styles/index.css
26
+ ```
27
+
28
+ **Pass condition:** All files exist at the project root (or under `src/` as indicated).
29
+
30
+ ### S-02: Directory structure matches pattern
31
+
32
+ The `src/` directory must contain at minimum:
33
+
34
+ ```
35
+ src/
36
+ main.tsx
37
+ lib/
38
+ sdk-context.tsx
39
+ views/
40
+ Layout.tsx
41
+ styles/
42
+ index.css
43
+ ```
44
+
45
+ Optional but expected directories:
46
+
47
+ ```
48
+ src/
49
+ router.tsx # Present if app uses react-router-dom
50
+ components/ # Present if app has reusable components
51
+ hooks/ # Present if app has custom hooks
52
+ test/ # Present if app has tests
53
+ setup.ts
54
+ lib/
55
+ views/
56
+ hooks/
57
+ components/
58
+ ```
59
+
60
+ **Pass condition:** All required paths exist. Optional paths should be present when the corresponding feature is used (e.g., `router.tsx` when `react-router-dom` is a dependency).
61
+
62
+ ### S-03: No extraneous top-level files
63
+
64
+ The project root should not contain unexpected configuration files. Allowed top-level files:
65
+
66
+ ```
67
+ index.html
68
+ package.json
69
+ postcss.config.js
70
+ tsconfig.json
71
+ vercel.json
72
+ vite.config.ts
73
+ .gitignore
74
+ .env.local
75
+ favicon.svg
76
+ README.md
77
+ ```
78
+
79
+ **Pass condition:** No unexpected configuration files in the project root.
80
+
81
+ ---
82
+
83
+ ## SDK Integration Checks
84
+
85
+ These verify the app correctly integrates with the Frontier SDK.
86
+
87
+ ### I-01: isInFrontierApp() call in Layout.tsx
88
+
89
+ `src/views/Layout.tsx` must import and call `isInFrontierApp` from `@frontiertower/frontier-sdk/ui-utils`.
90
+
91
+ **Pass condition:** The import statement `import { isInFrontierApp, createStandaloneHTML } from '@frontiertower/frontier-sdk/ui-utils'` is present, and `isInFrontierApp()` is called inside a `useEffect`.
92
+
93
+ ### I-02: createStandaloneHTML() fallback in Layout.tsx
94
+
95
+ When `isInFrontierApp()` returns `false`, the Layout must call `createStandaloneHTML('App Name')` and render the result.
96
+
97
+ **Pass condition:** The following pattern exists in Layout.tsx:
98
+ ```tsx
99
+ if (!inFrontier) {
100
+ setStandaloneHtml(createStandaloneHTML('<AppName>'));
101
+ setLoading(false);
102
+ return;
103
+ }
104
+ ```
105
+
106
+ And the standalone HTML is rendered via `dangerouslySetInnerHTML`:
107
+ ```tsx
108
+ if (standaloneHtml) {
109
+ return (
110
+ <div
111
+ className="min-h-screen bg-background text-foreground"
112
+ dangerouslySetInnerHTML={{ __html: standaloneHtml }}
113
+ />
114
+ );
115
+ }
116
+ ```
117
+
118
+ ### I-03: SdkProvider wrapping children in Layout.tsx
119
+
120
+ When inside the Frontier app, the Layout must wrap its children (either `<Outlet />` or a single component) with `<SdkProvider>`.
121
+
122
+ **Pass condition:** The return statement for the "in Frontier" case contains:
123
+ ```tsx
124
+ <SdkProvider>
125
+ <Outlet /> {/* or a single component */}
126
+ </SdkProvider>
127
+ ```
128
+
129
+ ### I-04: useSdk() hook available and used
130
+
131
+ `src/lib/sdk-context.tsx` must export `useSdk` and `SdkProvider`. Any view component that accesses the SDK must call `useSdk()`.
132
+
133
+ **Pass condition:**
134
+ - `sdk-context.tsx` exports `useSdk` and `SdkProvider`.
135
+ - Every file that accesses SDK methods imports `useSdk` from `../lib/sdk-context` (or appropriate relative path) and calls it within the component body.
136
+ - No direct `new FrontierSDK()` calls outside of `sdk-context.tsx`.
137
+
138
+ ---
139
+
140
+ ## Configuration Checks
141
+
142
+ These verify configuration files have the correct content.
143
+
144
+ ### C-01: vercel.json has all 5 CORS origin blocks
145
+
146
+ The `vercel.json` file must contain exactly 5 header blocks, one for each allowed origin:
147
+
148
+ 1. `https://os.frontiertower.io`
149
+ 2. `https://alpha.os.frontiertower.io`
150
+ 3. `https://beta.os.frontiertower.io`
151
+ 4. `https://sandbox.os.frontiertower.io`
152
+ 5. `http://localhost:5173`
153
+
154
+ Each block must include:
155
+ - `Access-Control-Allow-Origin` matching the origin
156
+ - `Access-Control-Allow-Methods: GET, OPTIONS`
157
+ - `Access-Control-Allow-Headers: Content-Type`
158
+
159
+ It must also include the SPA rewrite:
160
+ ```json
161
+ { "source": "/(.*)", "destination": "/index.html" }
162
+ ```
163
+
164
+ **Pass condition:** All 5 origin blocks are present with correct headers. The rewrite rule exists.
165
+
166
+ ### C-02: tsconfig.json has strict mode and vitest types
167
+
168
+ `tsconfig.json` must include:
169
+ - `"strict": true`
170
+ - `"types": ["vitest/globals", "@testing-library/jest-dom"]` (when tests exist)
171
+ - `"noEmit": true`
172
+ - `"jsx": "react-jsx"`
173
+ - `"exclude": ["src/test", "**/*.test.ts", "**/*.test.tsx"]`
174
+
175
+ **Pass condition:** All listed fields are present with the expected values.
176
+
177
+ ### C-03: postcss.config.js imports @tailwindcss/postcss
178
+
179
+ `postcss.config.js` must import from `@tailwindcss/postcss` and include `tailwindcss()` in its plugins array.
180
+
181
+ **Pass condition:** The file contains:
182
+ ```js
183
+ import tailwindcss from '@tailwindcss/postcss';
184
+
185
+ export default {
186
+ plugins: [tailwindcss()],
187
+ };
188
+ ```
189
+
190
+ ### C-04: package.json has correct scripts
191
+
192
+ `package.json` must include:
193
+
194
+ | Script | Command |
195
+ | --------- | -------------------- |
196
+ | `dev` | `vite` |
197
+ | `build` | `tsc && vite build` |
198
+ | `preview` | `vite preview` |
199
+ | `lint` | `tsc --noEmit` |
200
+ | `test` | `vitest run` |
201
+
202
+ The `test` script may be omitted if the app has no tests and vitest is not in devDependencies.
203
+
204
+ **Pass condition:** All listed scripts are present with exact command values. If vitest is in devDependencies, the `test` script must be present.
205
+
206
+ ### C-05: package.json has required dependencies
207
+
208
+ The following must be in `dependencies`:
209
+ - `@frontiertower/frontier-sdk`
210
+ - `react`
211
+ - `react-dom`
212
+
213
+ The following must be in `devDependencies`:
214
+ - `@tailwindcss/postcss`
215
+ - `@types/react`
216
+ - `@types/react-dom`
217
+ - `@vitejs/plugin-react`
218
+ - `postcss`
219
+ - `tailwindcss`
220
+ - `typescript`
221
+ - `vite`
222
+
223
+ When the app uses routing, `react-router-dom` must be in `dependencies`.
224
+
225
+ When the app has tests, the following must be in `devDependencies`:
226
+ - `@testing-library/jest-dom`
227
+ - `@testing-library/react`
228
+ - `@testing-library/user-event`
229
+ - `@vitest/coverage-v8`
230
+ - `jsdom`
231
+ - `vitest`
232
+
233
+ **Pass condition:** All listed packages are present in the correct section.
234
+
235
+ ---
236
+
237
+ ## Permission Checks
238
+
239
+ These verify that declared permissions match actual SDK usage.
240
+
241
+ ### P-01: Manifest permissions match SDK method calls
242
+
243
+ Every SDK method called in source code must have a corresponding permission declared in the app's manifest/registry entry.
244
+
245
+ To verify:
246
+ 1. Scan all `.ts` and `.tsx` files under `src/` (excluding `src/test/`).
247
+ 2. Identify all SDK method calls (e.g., `sdk.getWallet().getBalance()` requires `wallet:getBalance`).
248
+ 3. Compare against the permissions declared in the app manifest.
249
+
250
+ **Pass condition:** Every SDK method call in source has a corresponding permission. No undeclared SDK methods are called.
251
+
252
+ ### P-02: No SDK methods called without corresponding permission
253
+
254
+ The inverse check: no source file should call an SDK method that is not in the manifest.
255
+
256
+ Method-to-permission mapping follows the pattern:
257
+ - `sdk.getWallet().<method>()` --> `wallet:<method>`
258
+ - `sdk.getStorage().<method>()` --> `storage:<method>`
259
+ - `sdk.getUser().<method>()` --> `user:<method>`
260
+ - `sdk.getCommunities().<method>()` --> `communities:<method>`
261
+ - `sdk.getPartnerships().<method>()` --> `partnerships:<method>`
262
+ - `sdk.getEvents().<method>()` --> `events:<method>`
263
+ - `sdk.getOffices().<method>()` --> `offices:<method>`
264
+ - `sdk.getThirdParty().<method>()` --> `thirdParty:<method>`
265
+ - `sdk.getChain().<method>()` --> `chain:<method>`
266
+
267
+ **Pass condition:** No SDK method is called without the corresponding permission being declared.
268
+
269
+ ### P-03: No unnecessary permissions
270
+
271
+ Permissions should not be declared if no corresponding SDK method is called in the source code. Unnecessary permissions violate the principle of least privilege.
272
+
273
+ **Pass condition:** Every declared permission has at least one corresponding SDK method call in the source code.
274
+
275
+ ---
276
+
277
+ ## Build Checks
278
+
279
+ These verify the app compiles and passes automated tests.
280
+
281
+ ### B-01: tsc --noEmit passes
282
+
283
+ TypeScript type checking must complete without errors.
284
+
285
+ **Command:** `npx tsc --noEmit`
286
+
287
+ **Pass condition:** Exit code 0, no error output.
288
+
289
+ ### B-02: vite build succeeds
290
+
291
+ The production build must complete successfully.
292
+
293
+ **Command:** `npm run build` (which runs `tsc && vite build`)
294
+
295
+ **Pass condition:** Exit code 0, `dist/` directory is created with `index.html` and bundled assets.
296
+
297
+ ### B-03: vitest run passes
298
+
299
+ If tests exist (any `.test.ts` or `.test.tsx` files under `src/test/`), they must pass.
300
+
301
+ **Command:** `npx vitest run`
302
+
303
+ **Pass condition:** Exit code 0, all tests pass. If no test files exist, this check is skipped.
304
+
305
+ ---
306
+
307
+ ## Theme Checks
308
+
309
+ These verify the app uses the standard Frontier dark theme.
310
+
311
+ ### T-01: Dark theme CSS variables defined in index.css
312
+
313
+ `src/styles/index.css` must contain a `@theme` block with at minimum these variables:
314
+
315
+ ```
316
+ --font-sans
317
+ --color-primary
318
+ --color-primary-foreground
319
+ --color-accent
320
+ --color-accent-foreground
321
+ --color-alert
322
+ --color-alert-foreground
323
+ --color-danger
324
+ --color-danger-foreground
325
+ --color-success
326
+ --color-background
327
+ --color-foreground
328
+ --color-muted
329
+ --color-muted-foreground
330
+ --color-muted-background
331
+ --color-card
332
+ --color-card-foreground
333
+ --color-border
334
+ --color-input
335
+ --color-ring
336
+ --color-outline
337
+ ```
338
+
339
+ **Pass condition:** All listed CSS custom properties are defined inside the `@theme` block.
340
+
341
+ ### T-02: body class="dark" in index.html
342
+
343
+ `index.html` must have `class="dark"` on the `<body>` tag.
344
+
345
+ **Pass condition:** The `<body>` tag contains `class="dark"`.
346
+
347
+ ### T-03: Plus Jakarta Sans font loaded
348
+
349
+ `index.html` must load the Plus Jakarta Sans font from Google Fonts with weights 400, 500, 600, and 700.
350
+
351
+ **Pass condition:** The following elements are in `<head>`:
352
+ ```html
353
+ <link rel="preconnect" href="https://fonts.googleapis.com" />
354
+ <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
355
+ <link href="https://fonts.googleapis.com/css2?family=Plus+Jakarta+Sans:wght@400;500;600;700&display=swap" rel="stylesheet" />
356
+ ```
357
+
358
+ ### T-04: @import "tailwindcss" in index.css
359
+
360
+ `src/styles/index.css` must begin with `@import "tailwindcss";` to load Tailwind 4.
361
+
362
+ **Pass condition:** The first non-comment, non-empty line is `@import "tailwindcss";`.
363
+
364
+ ### T-05: Base layer styles
365
+
366
+ The `@layer base` block must include:
367
+ - `box-sizing: border-box` on `*`
368
+ - `font-family: var(--font-sans)` on `body`
369
+ - `color: var(--color-foreground)` on `body`
370
+ - `background-color: var(--color-background)` on `body`
371
+ - `min-height: 100vh` on `body` and `#root`
372
+ - `#root` as flex column (`display: flex; flex-direction: column`)
373
+
374
+ **Pass condition:** All listed styles are present in the `@layer base` block.
375
+
376
+ ---
377
+
378
+ ## Summary Checklist
379
+
380
+ | ID | Category | Rule | Severity |
381
+ | ---- | ------------- | --------------------------------------------------- | -------- |
382
+ | S-01 | Structure | Required files exist | Error |
383
+ | S-02 | Structure | Directory structure matches pattern | Error |
384
+ | S-03 | Structure | No extraneous top-level files | Warning |
385
+ | I-01 | SDK | isInFrontierApp() call in Layout.tsx | Error |
386
+ | I-02 | SDK | createStandaloneHTML() fallback in Layout.tsx | Error |
387
+ | I-03 | SDK | SdkProvider wrapping children in Layout.tsx | Error |
388
+ | I-04 | SDK | useSdk() hook available and used | Error |
389
+ | C-01 | Configuration | vercel.json has all 5 CORS origin blocks | Error |
390
+ | C-02 | Configuration | tsconfig.json has strict mode and vitest types | Error |
391
+ | C-03 | Configuration | postcss.config.js imports @tailwindcss/postcss | Error |
392
+ | C-04 | Configuration | package.json has correct scripts | Error |
393
+ | C-05 | Configuration | package.json has required dependencies | Error |
394
+ | P-01 | Permissions | Manifest permissions match SDK method calls | Error |
395
+ | P-02 | Permissions | No SDK methods called without permission | Error |
396
+ | P-03 | Permissions | No unnecessary permissions declared | Warning |
397
+ | B-01 | Build | tsc --noEmit passes | Error |
398
+ | B-02 | Build | vite build succeeds | Error |
399
+ | B-03 | Build | vitest run passes (if tests exist) | Error |
400
+ | T-01 | Theme | Dark theme CSS variables defined in index.css | Error |
401
+ | T-02 | Theme | body class="dark" in index.html | Error |
402
+ | T-03 | Theme | Plus Jakarta Sans font loaded | Error |
403
+ | T-04 | Theme | @import "tailwindcss" in index.css | Error |
404
+ | T-05 | Theme | Base layer styles present | Error |
@@ -0,0 +1,25 @@
1
+ # dependencies
2
+ node_modules
3
+ *.code-workspace
4
+ # production builds
5
+ dist
6
+
7
+ # caches & logs
8
+ .eslintcache
9
+ *.log
10
+ npm-debug.log*
11
+ yarn-debug.log*
12
+ pnpm-debug.log*
13
+ .pnpm-debug.log*
14
+
15
+ # environment files
16
+ .env
17
+ .env.*.local
18
+
19
+ # editor
20
+ .DS_Store
21
+ .idea
22
+ .vscode
23
+ .claude
24
+ .playwright-mcp/
25
+ TODO.md
@@ -0,0 +1,111 @@
1
+ @import "tailwindcss";
2
+
3
+ @theme {
4
+ --font-sans: "Plus Jakarta Sans", ui-sans-serif, system-ui, sans-serif;
5
+
6
+ --color-primary: #764AE2;
7
+ --color-primary-foreground: #ffffff;
8
+ --color-accent: #E4DCF9;
9
+ --color-accent-foreground: #0A0A0A;
10
+ --color-alert: #EF4444;
11
+ --color-alert-foreground: #F5F5F5;
12
+ --color-danger: #F87171;
13
+ --color-danger-foreground: #ffffff;
14
+ --color-success: #10b981;
15
+
16
+ --color-background: #000000;
17
+ --color-foreground: #FAFAFA;
18
+ --color-muted: #1E293B;
19
+ --color-muted-foreground: #A3A3A3;
20
+ --color-muted-background: #262626;
21
+ --color-card: #0A0A0A;
22
+ --color-card-foreground: #F5F5F5;
23
+ --color-border: #242424;
24
+ --color-input: #242424;
25
+ --color-ring: #B6B1F6;
26
+ --color-outline: #404040;
27
+ }
28
+
29
+ @layer base {
30
+ * {
31
+ box-sizing: border-box;
32
+ margin: 0;
33
+ padding: 0;
34
+ }
35
+
36
+ html {
37
+ font-size: 16px;
38
+ -webkit-font-smoothing: antialiased;
39
+ -moz-osx-font-smoothing: grayscale;
40
+ }
41
+
42
+ body {
43
+ font-family: var(--font-sans);
44
+ font-size: 0.875rem;
45
+ line-height: 1.5;
46
+ color: var(--color-foreground);
47
+ background-color: var(--color-background);
48
+ min-height: 100vh;
49
+ -webkit-user-select: none;
50
+ -webkit-tap-highlight-color: transparent;
51
+ touch-action: manipulation;
52
+ }
53
+
54
+ #root {
55
+ min-height: 100vh;
56
+ display: flex;
57
+ flex-direction: column;
58
+ }
59
+
60
+ h1, h2, h3, h4, h5, h6 {
61
+ font-weight: 600;
62
+ line-height: 1.25;
63
+ color: var(--color-foreground);
64
+ }
65
+
66
+ p {
67
+ color: var(--color-muted-foreground);
68
+ line-height: 1.6;
69
+ }
70
+
71
+ a {
72
+ color: var(--color-primary);
73
+ text-decoration: none;
74
+ }
75
+
76
+ a:hover {
77
+ text-decoration: underline;
78
+ }
79
+ }
80
+
81
+ @layer components {
82
+ /* Loading screen */
83
+ .loading-screen {
84
+ min-height: 100vh;
85
+ display: flex;
86
+ flex-direction: column;
87
+ align-items: center;
88
+ justify-content: center;
89
+ text-align: center;
90
+ gap: 1rem;
91
+ }
92
+
93
+ .spinner {
94
+ width: 24px;
95
+ height: 24px;
96
+ border: 2px solid var(--color-border);
97
+ border-top-color: var(--color-primary);
98
+ border-radius: 50%;
99
+ animation: spin 0.8s linear infinite;
100
+ }
101
+
102
+ .spinner-lg {
103
+ width: 40px;
104
+ height: 40px;
105
+ border-width: 3px;
106
+ }
107
+
108
+ @keyframes spin {
109
+ to { transform: rotate(360deg); }
110
+ }
111
+ }
@@ -0,0 +1,19 @@
1
+ <!doctype html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="utf-8" />
5
+ <title>{{APP_NAME}}</title>
6
+
7
+ <meta name="viewport" content="width=device-width, initial-scale=1" />
8
+ <meta name="description" content="{{APP_DESCRIPTION}}">
9
+
10
+ <link rel="icon" type="image/svg+xml" href="/favicon.svg" />
11
+ <link rel="preconnect" href="https://fonts.googleapis.com" />
12
+ <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
13
+ <link href="https://fonts.googleapis.com/css2?family=Plus+Jakarta+Sans:wght@400;500;600;700&display=swap" rel="stylesheet" />
14
+ </head>
15
+ <body class="dark">
16
+ <div id="root"></div>
17
+ <script type="module" src="/src/main.tsx"></script>
18
+ </body>
19
+ </html>
@@ -0,0 +1,45 @@
1
+ import { useEffect, useState } from 'react';
2
+ import { Outlet } from 'react-router-dom';
3
+ import { isInFrontierApp, createStandaloneHTML } from '@frontiertower/frontier-sdk/ui-utils';
4
+ import { SdkProvider } from '../lib/sdk-context';
5
+
6
+ export const Layout = () => {
7
+ const [loading, setLoading] = useState(true);
8
+ const [standaloneHtml, setStandaloneHtml] = useState('');
9
+
10
+ useEffect(() => {
11
+ const inFrontier = isInFrontierApp();
12
+
13
+ if (!inFrontier) {
14
+ setStandaloneHtml(createStandaloneHTML('{{APP_NAME}}'));
15
+ setLoading(false);
16
+ return;
17
+ }
18
+
19
+ setLoading(false);
20
+ }, []);
21
+
22
+ if (standaloneHtml) {
23
+ return (
24
+ <div
25
+ className="min-h-screen bg-background text-foreground"
26
+ dangerouslySetInnerHTML={{ __html: standaloneHtml }}
27
+ />
28
+ );
29
+ }
30
+
31
+ if (loading) {
32
+ return (
33
+ <div className="loading-screen">
34
+ <div className="spinner spinner-lg" />
35
+ <p className="text-sm text-muted-foreground">Loading...</p>
36
+ </div>
37
+ );
38
+ }
39
+
40
+ return (
41
+ <SdkProvider>
42
+ <Outlet />
43
+ </SdkProvider>
44
+ );
45
+ };
@@ -0,0 +1,17 @@
1
+ import { StrictMode } from 'react';
2
+ import { createRoot } from 'react-dom/client';
3
+ import { RouterProvider } from 'react-router-dom';
4
+ import { router } from './router';
5
+ import './styles/index.css';
6
+
7
+ const rootElement = document.getElementById('root');
8
+
9
+ if (!rootElement) {
10
+ throw new Error('Root element #root not found in document.');
11
+ }
12
+
13
+ createRoot(rootElement).render(
14
+ <StrictMode>
15
+ <RouterProvider router={router} />
16
+ </StrictMode>
17
+ );
@@ -0,0 +1,19 @@
1
+ import { StrictMode } from 'react';
2
+ import { createRoot } from 'react-dom/client';
3
+ import { SdkProvider } from './lib/sdk-context';
4
+ import { App } from './views/App';
5
+ import './styles/index.css';
6
+
7
+ const rootElement = document.getElementById('root');
8
+
9
+ if (!rootElement) {
10
+ throw new Error('Root element #root not found in document.');
11
+ }
12
+
13
+ createRoot(rootElement).render(
14
+ <StrictMode>
15
+ <SdkProvider>
16
+ <App />
17
+ </SdkProvider>
18
+ </StrictMode>
19
+ );
@@ -0,0 +1,36 @@
1
+ {
2
+ "name": "{{PACKAGE_NAME}}",
3
+ "version": "1.0.0",
4
+ "private": true,
5
+ "type": "module",
6
+ "scripts": {
7
+ "dev": "vite",
8
+ "build": "tsc && vite build",
9
+ "preview": "vite preview",
10
+ "lint": "tsc --noEmit",
11
+ "test": "vitest run"
12
+ },
13
+ "dependencies": {
14
+ "@frontiertower/frontier-sdk": "{{SDK_VERSION}}",
15
+ "react": "^19.2.3",
16
+ "react-dom": "^19.2.3",
17
+ "react-icons": "^5.5.0",
18
+ "react-router-dom": "^7.12.0"
19
+ },
20
+ "devDependencies": {
21
+ "@tailwindcss/postcss": "^4.1.18",
22
+ "@testing-library/jest-dom": "^6.9.1",
23
+ "@testing-library/react": "^16.3.1",
24
+ "@testing-library/user-event": "^14.6.1",
25
+ "@types/react": "^19.2.7",
26
+ "@types/react-dom": "^19.2.3",
27
+ "@vitejs/plugin-react": "^5.1.2",
28
+ "@vitest/coverage-v8": "^4.0.18",
29
+ "jsdom": "^27.4.0",
30
+ "postcss": "^8.5.6",
31
+ "tailwindcss": "^4.1.18",
32
+ "typescript": "^5.9.3",
33
+ "vite": "^7.3.0",
34
+ "vitest": "^4.0.16"
35
+ }
36
+ }
@@ -0,0 +1,5 @@
1
+ import tailwindcss from '@tailwindcss/postcss';
2
+
3
+ export default {
4
+ plugins: [tailwindcss()],
5
+ };