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.
- package/README.md +92 -0
- package/agents/fos-executor.md +460 -0
- package/agents/fos-plan-checker.md +386 -0
- package/agents/fos-planner.md +416 -0
- package/agents/fos-researcher.md +358 -0
- package/agents/fos-verifier.md +491 -0
- package/bin/fos-tools.cjs +794 -0
- package/bin/install.js +234 -0
- package/commands/fos/add-feature.md +29 -0
- package/commands/fos/discuss.md +31 -0
- package/commands/fos/execute.md +35 -0
- package/commands/fos/new-app.md +39 -0
- package/commands/fos/new-milestone.md +28 -0
- package/commands/fos/next.md +29 -0
- package/commands/fos/plan.md +37 -0
- package/commands/fos/ship.md +29 -0
- package/commands/fos/status.md +22 -0
- package/package.json +30 -0
- package/references/app-patterns.md +501 -0
- package/references/deployment.md +395 -0
- package/references/module-inference.md +349 -0
- package/references/sdk-surface.md +1622 -0
- package/references/verification-rules.md +404 -0
- package/templates/app/gitignore +25 -0
- package/templates/app/index.css +111 -0
- package/templates/app/index.html +19 -0
- package/templates/app/layout.tsx +45 -0
- package/templates/app/main-router.tsx +17 -0
- package/templates/app/main-simple.tsx +19 -0
- package/templates/app/package.json +36 -0
- package/templates/app/postcss.config.js +5 -0
- package/templates/app/router.tsx +22 -0
- package/templates/app/sdk-context.tsx +33 -0
- package/templates/app/test-setup.ts +19 -0
- package/templates/app/tsconfig.json +22 -0
- package/templates/app/vercel.json +127 -0
- package/templates/app/vite.config.ts +15 -0
- package/templates/state/context.md +248 -0
- package/templates/state/manifest.json +11 -0
- package/templates/state/plan.md +187 -0
- package/templates/state/project.md +118 -0
- package/templates/state/requirements.md +133 -0
- package/templates/state/roadmap.md +129 -0
- package/templates/state/state.md +131 -0
- package/templates/state/summary.md +273 -0
- package/workflows/add-feature.md +234 -0
- package/workflows/discuss.md +310 -0
- package/workflows/execute-plan.md +222 -0
- package/workflows/execute.md +338 -0
- package/workflows/new-app.md +331 -0
- package/workflows/new-milestone.md +258 -0
- package/workflows/next.md +157 -0
- package/workflows/plan.md +310 -0
- package/workflows/ship.md +296 -0
- 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
|
+
}
|