frontier-os-app-builder 1.0.0 → 1.2.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/LICENSE +21 -0
- package/README.md +90 -14
- package/agents/fos-executor.md +105 -39
- package/agents/fos-plan-checker.md +62 -25
- package/agents/fos-planner.md +80 -72
- package/agents/fos-researcher.md +26 -15
- package/agents/fos-verifier.md +96 -27
- package/bin/fos-tools.cjs +146 -42
- package/bin/install.js +8 -5
- package/commands/fos/add-feature.md +1 -2
- package/commands/fos/discuss.md +0 -1
- package/commands/fos/new-app.md +2 -4
- package/commands/fos/new-milestone.md +1 -1
- package/commands/fos/plan.md +0 -2
- package/package.json +7 -1
- package/references/app-patterns.md +128 -21
- package/references/deployment.md +40 -124
- package/references/module-index.md +32 -0
- package/references/sdk/chain.md +92 -0
- package/references/sdk/communities.md +159 -0
- package/references/sdk/events.md +212 -0
- package/references/sdk/init.md +126 -0
- package/references/sdk/navigation.md +49 -0
- package/references/sdk/offices.md +76 -0
- package/references/sdk/partnerships.md +111 -0
- package/references/sdk/storage.md +44 -0
- package/references/sdk/thirdparty.md +240 -0
- package/references/sdk/token-amount.md +99 -0
- package/references/sdk/types.md +27 -0
- package/references/sdk/ui-utils.md +39 -0
- package/references/sdk/user.md +208 -0
- package/references/sdk/wallet.md +334 -0
- package/references/verification-rules.md +111 -50
- package/templates/app/frontier-services.tsx +871 -0
- package/templates/app/layout-standalone.tsx +8 -0
- package/templates/app/layout.tsx +19 -9
- package/templates/app/package-standalone.json +35 -0
- package/templates/app/package.json +2 -1
- package/templates/app/public/favicon.svg +3 -0
- package/templates/app/sdk-context.tsx +7 -9
- package/templates/app/sdk-services.tsx +98 -0
- package/templates/app/vercel-standalone.json +5 -0
- package/templates/app/vercel.json +8 -95
- package/templates/state/plan.md +32 -14
- package/templates/state/requirements.md +1 -1
- package/templates/state/roadmap.md +57 -24
- package/templates/state/summary.md +27 -30
- package/workflows/add-feature.md +6 -1
- package/workflows/discuss.md +126 -11
- package/workflows/execute-plan.md +21 -14
- package/workflows/execute.md +204 -24
- package/workflows/new-app.md +64 -23
- package/workflows/new-milestone.md +10 -3
- package/workflows/plan.md +16 -5
- package/workflows/ship.md +91 -34
- package/workflows/status.md +1 -2
- package/references/module-inference.md +0 -349
- package/references/sdk-surface.md +0 -1622
- package/templates/app/main-simple.tsx +0 -19
- package/templates/state/manifest.json +0 -11
package/templates/app/layout.tsx
CHANGED
|
@@ -1,21 +1,27 @@
|
|
|
1
|
-
import { useEffect, useState } from 'react';
|
|
1
|
+
import { useEffect, useMemo, useState, type ReactNode } from 'react';
|
|
2
2
|
import { Outlet } from 'react-router-dom';
|
|
3
3
|
import { isInFrontierApp, createStandaloneHTML } from '@frontiertower/frontier-sdk/ui-utils';
|
|
4
|
-
import { SdkProvider } from '../lib/sdk-context';
|
|
4
|
+
import { SdkProvider, useSdk } from '../lib/sdk-context';
|
|
5
|
+
import { FrontierServicesProvider } from '../lib/frontier-services';
|
|
6
|
+
import { createSdkServices } from '../lib/sdk-services';
|
|
7
|
+
|
|
8
|
+
// Bridges the live SDK (from SdkProvider) into the FrontierServices seam that
|
|
9
|
+
// feature code consumes via useServices(). Keeps feature code SDK-agnostic: the
|
|
10
|
+
// exact same components run against mocks (standalone) and the real SDK (iframe).
|
|
11
|
+
const SdkServicesBridge = ({ children }: { children: ReactNode }) => {
|
|
12
|
+
const sdk = useSdk();
|
|
13
|
+
const services = useMemo(() => createSdkServices(sdk), [sdk]);
|
|
14
|
+
return <FrontierServicesProvider services={services}>{children}</FrontierServicesProvider>;
|
|
15
|
+
};
|
|
5
16
|
|
|
6
17
|
export const Layout = () => {
|
|
7
18
|
const [loading, setLoading] = useState(true);
|
|
8
19
|
const [standaloneHtml, setStandaloneHtml] = useState('');
|
|
9
20
|
|
|
10
21
|
useEffect(() => {
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
if (!inFrontier) {
|
|
22
|
+
if (!isInFrontierApp()) {
|
|
14
23
|
setStandaloneHtml(createStandaloneHTML('{{APP_NAME}}'));
|
|
15
|
-
setLoading(false);
|
|
16
|
-
return;
|
|
17
24
|
}
|
|
18
|
-
|
|
19
25
|
setLoading(false);
|
|
20
26
|
}, []);
|
|
21
27
|
|
|
@@ -37,9 +43,13 @@ export const Layout = () => {
|
|
|
37
43
|
);
|
|
38
44
|
}
|
|
39
45
|
|
|
46
|
+
// In-frame: provide both the raw SDK (useSdk) and the SDK-backed services
|
|
47
|
+
// seam (useServices) so every feature component works unchanged from standalone.
|
|
40
48
|
return (
|
|
41
49
|
<SdkProvider>
|
|
42
|
-
<
|
|
50
|
+
<SdkServicesBridge>
|
|
51
|
+
<Outlet />
|
|
52
|
+
</SdkServicesBridge>
|
|
43
53
|
</SdkProvider>
|
|
44
54
|
);
|
|
45
55
|
};
|
|
@@ -0,0 +1,35 @@
|
|
|
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
|
+
"react": "^19.2.3",
|
|
15
|
+
"react-dom": "^19.2.3",
|
|
16
|
+
"react-icons": "^5.5.0",
|
|
17
|
+
"react-router-dom": "^7.12.0"
|
|
18
|
+
},
|
|
19
|
+
"devDependencies": {
|
|
20
|
+
"@tailwindcss/postcss": "^4.1.18",
|
|
21
|
+
"@testing-library/jest-dom": "^6.9.1",
|
|
22
|
+
"@testing-library/react": "^16.3.1",
|
|
23
|
+
"@testing-library/user-event": "^14.6.1",
|
|
24
|
+
"@types/react": "^19.2.7",
|
|
25
|
+
"@types/react-dom": "^19.2.3",
|
|
26
|
+
"@vitejs/plugin-react": "^5.1.2",
|
|
27
|
+
"@vitest/coverage-v8": "^4.0.18",
|
|
28
|
+
"jsdom": "^27.4.0",
|
|
29
|
+
"postcss": "^8.5.6",
|
|
30
|
+
"tailwindcss": "^4.1.18",
|
|
31
|
+
"typescript": "^5.9.3",
|
|
32
|
+
"vite": "^7.3.0",
|
|
33
|
+
"vitest": "^4.0.16"
|
|
34
|
+
}
|
|
35
|
+
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { createContext, useContext, useEffect,
|
|
1
|
+
import { createContext, useContext, useEffect, useState, type ReactNode } from 'react';
|
|
2
2
|
import { FrontierSDK } from '@frontiertower/frontier-sdk';
|
|
3
3
|
|
|
4
4
|
const SdkContext = createContext<FrontierSDK | null>(null);
|
|
@@ -10,23 +10,21 @@ export const useSdk = (): FrontierSDK => {
|
|
|
10
10
|
};
|
|
11
11
|
|
|
12
12
|
export const SdkProvider = ({ children }: { children: ReactNode }) => {
|
|
13
|
-
const
|
|
14
|
-
const [ready, setReady] = useState(false);
|
|
13
|
+
const [sdk, setSdk] = useState<FrontierSDK | null>(null);
|
|
15
14
|
|
|
16
15
|
useEffect(() => {
|
|
17
|
-
const
|
|
18
|
-
|
|
19
|
-
setReady(true);
|
|
16
|
+
const instance = new FrontierSDK();
|
|
17
|
+
setSdk(instance);
|
|
20
18
|
|
|
21
19
|
return () => {
|
|
22
|
-
|
|
20
|
+
instance.destroy();
|
|
23
21
|
};
|
|
24
22
|
}, []);
|
|
25
23
|
|
|
26
|
-
if (!
|
|
24
|
+
if (!sdk) return null;
|
|
27
25
|
|
|
28
26
|
return (
|
|
29
|
-
<SdkContext.Provider value={
|
|
27
|
+
<SdkContext.Provider value={sdk}>
|
|
30
28
|
{children}
|
|
31
29
|
</SdkContext.Provider>
|
|
32
30
|
);
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SDK Services Adapter
|
|
3
|
+
*
|
|
4
|
+
* Maps the FrontierServices interface to real SDK module calls.
|
|
5
|
+
* Used when the app runs inside the Frontier OS iframe.
|
|
6
|
+
* Only modules declared in manifest.json are wired; unused modules throw.
|
|
7
|
+
*
|
|
8
|
+
* During SDK Integration the executor should:
|
|
9
|
+
* 1. Wire modules the app actually uses (replace Proxy stubs with real SDK calls)
|
|
10
|
+
* 2. Keep Proxy stubs for unused modules
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
import type { FrontierSDK } from '@frontiertower/frontier-sdk';
|
|
14
|
+
import type {
|
|
15
|
+
FrontierServices,
|
|
16
|
+
WalletService,
|
|
17
|
+
StorageService,
|
|
18
|
+
ChainService,
|
|
19
|
+
UserService,
|
|
20
|
+
PartnershipsService,
|
|
21
|
+
ThirdPartyService,
|
|
22
|
+
CommunitiesService,
|
|
23
|
+
EventsService,
|
|
24
|
+
OfficesService,
|
|
25
|
+
NavigationService,
|
|
26
|
+
} from './frontier-services';
|
|
27
|
+
|
|
28
|
+
function notAvailable(module: string): never {
|
|
29
|
+
throw new Error(`[SDK] ${module} module is not available in this app`);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export function createSdkServices(sdk: FrontierSDK): FrontierServices {
|
|
33
|
+
void sdk; // marks `sdk` used while modules are still stubs; remove once you wire one below (e.g. sdk.getWallet())
|
|
34
|
+
// ── Wire modules your app uses ──────────────────────────────
|
|
35
|
+
// Replace the Proxy stubs below with real SDK adapter objects
|
|
36
|
+
// for each module declared in manifest.json.
|
|
37
|
+
//
|
|
38
|
+
// Example (wallet):
|
|
39
|
+
// const walletAccess = sdk.getWallet();
|
|
40
|
+
// const wallet: WalletService = {
|
|
41
|
+
// getBalance: () => walletAccess.getBalance(),
|
|
42
|
+
// ...
|
|
43
|
+
// };
|
|
44
|
+
|
|
45
|
+
// ── Stub modules not used by this app ───────────────────────
|
|
46
|
+
const wallet = new Proxy({} as WalletService, {
|
|
47
|
+
get: () => () => notAvailable('Wallet'),
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
const storage = new Proxy({} as StorageService, {
|
|
51
|
+
get: () => () => notAvailable('Storage'),
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
const chain = new Proxy({} as ChainService, {
|
|
55
|
+
get: () => () => notAvailable('Chain'),
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
const user = new Proxy({} as UserService, {
|
|
59
|
+
get: () => () => notAvailable('User'),
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
const partnerships = new Proxy({} as PartnershipsService, {
|
|
63
|
+
get: () => () => notAvailable('Partnerships'),
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
const thirdParty = new Proxy({} as ThirdPartyService, {
|
|
67
|
+
get: () => () => notAvailable('ThirdParty'),
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
const communities = new Proxy({} as CommunitiesService, {
|
|
71
|
+
get: () => () => notAvailable('Communities'),
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
const events = new Proxy({} as EventsService, {
|
|
75
|
+
get: () => () => notAvailable('Events'),
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
const offices = new Proxy({} as OfficesService, {
|
|
79
|
+
get: () => () => notAvailable('Offices'),
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
const navigation = new Proxy({} as NavigationService, {
|
|
83
|
+
get: () => () => notAvailable('Navigation'),
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
return {
|
|
87
|
+
wallet,
|
|
88
|
+
storage,
|
|
89
|
+
chain,
|
|
90
|
+
user,
|
|
91
|
+
partnerships,
|
|
92
|
+
thirdParty,
|
|
93
|
+
communities,
|
|
94
|
+
events,
|
|
95
|
+
offices,
|
|
96
|
+
navigation,
|
|
97
|
+
};
|
|
98
|
+
}
|
|
@@ -5,13 +5,6 @@
|
|
|
5
5
|
"headers": [
|
|
6
6
|
{
|
|
7
7
|
"source": "/(.*)",
|
|
8
|
-
"has": [
|
|
9
|
-
{
|
|
10
|
-
"type": "header",
|
|
11
|
-
"key": "Origin",
|
|
12
|
-
"value": "https://os.frontiertower.io"
|
|
13
|
-
}
|
|
14
|
-
],
|
|
15
8
|
"headers": [
|
|
16
9
|
{
|
|
17
10
|
"key": "Access-Control-Allow-Origin",
|
|
@@ -24,102 +17,22 @@
|
|
|
24
17
|
{
|
|
25
18
|
"key": "Access-Control-Allow-Headers",
|
|
26
19
|
"value": "Content-Type"
|
|
27
|
-
}
|
|
28
|
-
]
|
|
29
|
-
},
|
|
30
|
-
{
|
|
31
|
-
"source": "/(.*)",
|
|
32
|
-
"has": [
|
|
33
|
-
{
|
|
34
|
-
"type": "header",
|
|
35
|
-
"key": "Origin",
|
|
36
|
-
"value": "https://alpha.os.frontiertower.io"
|
|
37
|
-
}
|
|
38
|
-
],
|
|
39
|
-
"headers": [
|
|
40
|
-
{
|
|
41
|
-
"key": "Access-Control-Allow-Origin",
|
|
42
|
-
"value": "https://alpha.os.frontiertower.io"
|
|
43
20
|
},
|
|
44
21
|
{
|
|
45
|
-
"key": "
|
|
46
|
-
"value": "
|
|
22
|
+
"key": "Content-Security-Policy",
|
|
23
|
+
"value": "frame-ancestors https://os.frontiertower.io https://sandbox.os.frontiertower.io http://localhost:5173;"
|
|
47
24
|
},
|
|
48
25
|
{
|
|
49
|
-
"key": "
|
|
50
|
-
"value": "
|
|
51
|
-
}
|
|
52
|
-
]
|
|
53
|
-
},
|
|
54
|
-
{
|
|
55
|
-
"source": "/(.*)",
|
|
56
|
-
"has": [
|
|
57
|
-
{
|
|
58
|
-
"type": "header",
|
|
59
|
-
"key": "Origin",
|
|
60
|
-
"value": "https://beta.os.frontiertower.io"
|
|
61
|
-
}
|
|
62
|
-
],
|
|
63
|
-
"headers": [
|
|
64
|
-
{
|
|
65
|
-
"key": "Access-Control-Allow-Origin",
|
|
66
|
-
"value": "https://beta.os.frontiertower.io"
|
|
26
|
+
"key": "X-Content-Type-Options",
|
|
27
|
+
"value": "nosniff"
|
|
67
28
|
},
|
|
68
29
|
{
|
|
69
|
-
"key": "
|
|
70
|
-
"value": "
|
|
30
|
+
"key": "Referrer-Policy",
|
|
31
|
+
"value": "strict-origin-when-cross-origin"
|
|
71
32
|
},
|
|
72
33
|
{
|
|
73
|
-
"key": "
|
|
74
|
-
"value": "
|
|
75
|
-
}
|
|
76
|
-
]
|
|
77
|
-
},
|
|
78
|
-
{
|
|
79
|
-
"source": "/(.*)",
|
|
80
|
-
"has": [
|
|
81
|
-
{
|
|
82
|
-
"type": "header",
|
|
83
|
-
"key": "Origin",
|
|
84
|
-
"value": "https://sandbox.os.frontiertower.io"
|
|
85
|
-
}
|
|
86
|
-
],
|
|
87
|
-
"headers": [
|
|
88
|
-
{
|
|
89
|
-
"key": "Access-Control-Allow-Origin",
|
|
90
|
-
"value": "https://sandbox.os.frontiertower.io"
|
|
91
|
-
},
|
|
92
|
-
{
|
|
93
|
-
"key": "Access-Control-Allow-Methods",
|
|
94
|
-
"value": "GET, OPTIONS"
|
|
95
|
-
},
|
|
96
|
-
{
|
|
97
|
-
"key": "Access-Control-Allow-Headers",
|
|
98
|
-
"value": "Content-Type"
|
|
99
|
-
}
|
|
100
|
-
]
|
|
101
|
-
},
|
|
102
|
-
{
|
|
103
|
-
"source": "/(.*)",
|
|
104
|
-
"has": [
|
|
105
|
-
{
|
|
106
|
-
"type": "header",
|
|
107
|
-
"key": "Origin",
|
|
108
|
-
"value": "http://localhost:5173"
|
|
109
|
-
}
|
|
110
|
-
],
|
|
111
|
-
"headers": [
|
|
112
|
-
{
|
|
113
|
-
"key": "Access-Control-Allow-Origin",
|
|
114
|
-
"value": "http://localhost:5173"
|
|
115
|
-
},
|
|
116
|
-
{
|
|
117
|
-
"key": "Access-Control-Allow-Methods",
|
|
118
|
-
"value": "GET, OPTIONS"
|
|
119
|
-
},
|
|
120
|
-
{
|
|
121
|
-
"key": "Access-Control-Allow-Headers",
|
|
122
|
-
"value": "Content-Type"
|
|
34
|
+
"key": "Permissions-Policy",
|
|
35
|
+
"value": "camera=(), microphone=(), geolocation=()"
|
|
123
36
|
}
|
|
124
37
|
]
|
|
125
38
|
}
|
package/templates/state/plan.md
CHANGED
|
@@ -42,8 +42,9 @@ SDK Modules: [Which SDK modules are used in this plan, if any]
|
|
|
42
42
|
@.frontier-app/ROADMAP.md
|
|
43
43
|
@.frontier-app/STATE.md
|
|
44
44
|
|
|
45
|
-
#
|
|
46
|
-
|
|
45
|
+
# Feature phases consume the mock service layer (useServices) — no SDK docs in <context>.
|
|
46
|
+
# SDK module docs are referenced ONLY in the final SDK Integration plan, via:
|
|
47
|
+
# @frontier-os-app-builder/references/sdk/[module].md
|
|
47
48
|
|
|
48
49
|
# Only reference prior plan SUMMARYs if genuinely needed:
|
|
49
50
|
# - This plan uses types/exports from prior plan
|
|
@@ -88,7 +89,7 @@ SDK Modules: [Which SDK modules are used in this plan, if any]
|
|
|
88
89
|
<how-to-verify>Visit http://localhost:{{DEV_PORT}} and verify:
|
|
89
90
|
- [Visual check 1]
|
|
90
91
|
- [Visual check 2]
|
|
91
|
-
Open Frontier OS at localhost:3000 and verify app loads in iframe.</how-to-verify>
|
|
92
|
+
(SDK Integration phase only) Open Frontier OS at localhost:3000 and verify the app loads in the iframe.</how-to-verify>
|
|
92
93
|
<resume-signal>Type "approved" or describe issues</resume-signal>
|
|
93
94
|
</task>
|
|
94
95
|
|
|
@@ -136,26 +137,43 @@ After completion, create `.frontier-app/phases/XX-name/{phase}-{plan}-SUMMARY.md
|
|
|
136
137
|
|
|
137
138
|
## Frontier OS Specifics
|
|
138
139
|
|
|
139
|
-
**Phase 1 plans always include:**
|
|
140
|
+
**Phase 1 (scaffold) plans always include — standalone-first:**
|
|
140
141
|
- Vite scaffold from `templates/app/vite.config.ts`
|
|
141
|
-
-
|
|
142
|
-
- Layout
|
|
143
|
-
-
|
|
144
|
-
-
|
|
142
|
+
- `FrontierServicesProvider` + mock services from `templates/app/frontier-services.tsx` → `src/lib/frontier-services.tsx` (feature code calls `useServices()`)
|
|
143
|
+
- Layout from `templates/app/layout-standalone.tsx` → `src/views/Layout.tsx` (NO iframe detection, NO SdkProvider)
|
|
144
|
+
- Entry from `templates/app/main-router.tsx` (router entry — all apps use the router)
|
|
145
|
+
- Dark theme via Tailwind 4 `@theme` in `src/styles/index.css` (CSS-only — no `tailwind.config`)
|
|
146
|
+
- Standalone UI rendering with mock data
|
|
145
147
|
- Dev server on assigned port
|
|
146
148
|
|
|
147
|
-
**
|
|
148
|
-
-
|
|
149
|
-
-
|
|
150
|
-
-
|
|
151
|
-
-
|
|
149
|
+
**Phase 1 BLOCKLIST — NEVER include these in a scaffold plan:**
|
|
150
|
+
- ❌ `sdk-context.tsx` — this file is created during SDK Integration phase, NOT Phase 1
|
|
151
|
+
- ❌ `layout.tsx` template — use `layout-standalone.tsx` instead (no iframe detection, no SdkProvider)
|
|
152
|
+
- ❌ single-component entries — use `main-router.tsx` instead (all apps use the router; no single-component entry)
|
|
153
|
+
- ❌ `package.json` template — use `package-standalone.json` instead (no SDK dependency)
|
|
154
|
+
- ❌ `vercel.json` template — use `vercel-standalone.json` instead (no CORS headers)
|
|
155
|
+
- ❌ `@frontiertower/frontier-sdk` in dependencies — SDK is added during SDK Integration phase
|
|
156
|
+
- ❌ `isInFrontierApp()` or `createStandaloneHTML()` in Layout — these are SDK Integration concerns
|
|
157
|
+
- ❌ `SdkProvider` wrapping — use `FrontierServicesProvider` instead
|
|
158
|
+
- ❌ `useSdk()` — use `useServices()` instead
|
|
159
|
+
- ❌ Any import from `@frontiertower/frontier-sdk` — the SDK package does not exist in Phase 1
|
|
160
|
+
|
|
161
|
+
**Service usage in feature phases (Phase 2+):**
|
|
162
|
+
- Call service methods through the mock seam: `services.<module>.<method>()` (e.g. `services.wallet.getBalance()`)
|
|
163
|
+
- Always obtain services via `useServices()` — `import { useServices } from '../lib/frontier-services'`
|
|
164
|
+
- Wrap service calls in try/catch and handle loading/error states
|
|
165
|
+
- Never import `@frontiertower/frontier-sdk` or call `useSdk()` in feature code
|
|
166
|
+
- The `FrontierSDK` import + `useSdk()` pattern belongs ONLY to the final SDK Integration phase
|
|
152
167
|
|
|
153
168
|
**Verification always includes:**
|
|
154
169
|
- Build succeeds (`npm run build`)
|
|
155
170
|
- Dev server starts on correct port
|
|
156
171
|
- No TypeScript errors
|
|
157
172
|
- Dark theme renders correctly (no white backgrounds)
|
|
158
|
-
-
|
|
173
|
+
- Standalone renders with mock services (`useServices()` resolves)
|
|
174
|
+
|
|
175
|
+
**SDK Integration tier (final phase) additionally verifies:**
|
|
176
|
+
- App works in both iframe and standalone modes (in-frame `services.*` resolve via the SDK bridge)
|
|
159
177
|
|
|
160
178
|
---
|
|
161
179
|
|
|
@@ -87,7 +87,7 @@ Which phases cover which requirements. Updated during roadmap creation.
|
|
|
87
87
|
|
|
88
88
|
**Standard Platform Requirements:**
|
|
89
89
|
- PLAT-01 through PLAT-05 are ALWAYS included for every Frontier OS app
|
|
90
|
-
- They always map to Phase 1 (Scaffold +
|
|
90
|
+
- They always map to Phase 1 (Scaffold + Standalone Shell)
|
|
91
91
|
- They are non-negotiable — an app without these cannot run in Frontier OS
|
|
92
92
|
|
|
93
93
|
**Requirement Format:**
|
|
@@ -14,27 +14,28 @@ What makes this app worth building and what does the finished product look like?
|
|
|
14
14
|
|
|
15
15
|
## {{MILESTONE_VERSION}} Phases
|
|
16
16
|
|
|
17
|
-
- [ ] **Phase 1: Scaffold +
|
|
17
|
+
- [ ] **Phase 1: Scaffold + Standalone Shell** — Project setup, services layer, mock data, dark theme
|
|
18
18
|
- [ ] **Phase 2: [Feature Name]** — [One-line description]
|
|
19
19
|
- [ ] **Phase 3: [Feature Name]** — [One-line description]
|
|
20
|
-
- [ ] **Phase N: [Feature Name]** — [One-line description]
|
|
20
|
+
- [ ] **Phase N-1: [Feature Name]** — [One-line description]
|
|
21
|
+
- [ ] **Phase N: SDK Integration** — Wire SDK, create adapter, upgrade Layout for iframe
|
|
21
22
|
|
|
22
23
|
## Phase Details
|
|
23
24
|
|
|
24
|
-
### Phase 1: Scaffold +
|
|
25
|
-
**Goal**: Working app shell
|
|
25
|
+
### Phase 1: Scaffold + Standalone Shell
|
|
26
|
+
**Goal**: Working app shell running standalone in browser with mock data
|
|
26
27
|
**Depends on**: Nothing (always first)
|
|
27
28
|
**Requirements**: PLAT-01, PLAT-02, PLAT-03, PLAT-04, PLAT-05
|
|
28
29
|
**Success Criteria** (what must be TRUE):
|
|
29
|
-
1. App renders
|
|
30
|
-
2.
|
|
31
|
-
3.
|
|
32
|
-
4.
|
|
33
|
-
5.
|
|
30
|
+
1. App renders standalone in browser with mock data
|
|
31
|
+
2. Dark theme applied — no white backgrounds, no light-mode artifacts
|
|
32
|
+
3. useServices() returns mock wallet balance, user data, storage
|
|
33
|
+
4. Dev server runs on assigned port with HMR working
|
|
34
|
+
5. npm run build succeeds
|
|
34
35
|
**Plans**: 1 plan
|
|
35
36
|
|
|
36
37
|
Plans:
|
|
37
|
-
- [ ] 01-01: Vite + React scaffold,
|
|
38
|
+
- [ ] 01-01: Vite + React scaffold, services layer, mock data, dark theme, dev config
|
|
38
39
|
|
|
39
40
|
### Phase 2: [Feature Name]
|
|
40
41
|
**Goal**: [What this phase delivers — one sentence]
|
|
@@ -50,9 +51,9 @@ Plans:
|
|
|
50
51
|
- [ ] 02-01: [Brief description of first plan]
|
|
51
52
|
- [ ] 02-02: [Brief description of second plan]
|
|
52
53
|
|
|
53
|
-
### Phase N: [Feature Name]
|
|
54
|
+
### Phase N-1: [Feature Name]
|
|
54
55
|
**Goal**: [What this phase delivers]
|
|
55
|
-
**Depends on**: Phase [N-
|
|
56
|
+
**Depends on**: Phase [N-2]
|
|
56
57
|
**Requirements**: [REQ-XX, REQ-YY]
|
|
57
58
|
**Success Criteria** (what must be TRUE):
|
|
58
59
|
1. [Observable behavior from user perspective]
|
|
@@ -62,13 +63,30 @@ Plans:
|
|
|
62
63
|
Plans:
|
|
63
64
|
- [ ] NN-01: [Brief description]
|
|
64
65
|
|
|
66
|
+
### Phase N: SDK Integration
|
|
67
|
+
**Goal**: Wire real Frontier SDK into the standalone app shell
|
|
68
|
+
**Depends on**: All feature phases
|
|
69
|
+
**Requirements**: PLAT-SDK-01
|
|
70
|
+
**Success Criteria** (what must be TRUE):
|
|
71
|
+
1. sdk-context.tsx exists and exports useSdk + SdkProvider
|
|
72
|
+
2. sdk-services.tsx maps all service methods to real SDK calls
|
|
73
|
+
3. Layout.tsx has isInFrontierApp() detection and SdkProvider wrapping
|
|
74
|
+
4. vercel.json has CORS + CSP frame-ancestors (3 origins) + security headers
|
|
75
|
+
5. App works both standalone (mocks) and in iframe (real SDK)
|
|
76
|
+
6. npm run build succeeds
|
|
77
|
+
**Plans**: 1 plan (mechanical)
|
|
78
|
+
|
|
79
|
+
Plans:
|
|
80
|
+
- [ ] NN-01: SDK dependency, adapter, Layout upgrade, CORS
|
|
81
|
+
|
|
65
82
|
## Progress
|
|
66
83
|
|
|
67
84
|
| Phase | Plans Complete | Status | Completed |
|
|
68
85
|
|-------|----------------|--------|-----------|
|
|
69
|
-
| 1. Scaffold +
|
|
86
|
+
| 1. Scaffold + Standalone Shell | 0/1 | Not started | - |
|
|
70
87
|
| 2. [Name] | 0/N | Not started | - |
|
|
71
|
-
| N. [Name] | 0/N | Not started | - |
|
|
88
|
+
| N-1. [Name] | 0/N | Not started | - |
|
|
89
|
+
| N. SDK Integration | 0/1 | Not started | - |
|
|
72
90
|
|
|
73
91
|
---
|
|
74
92
|
*Roadmap created: {{DATE}}*
|
|
@@ -80,15 +98,22 @@ Plans:
|
|
|
80
98
|
<guidelines>
|
|
81
99
|
|
|
82
100
|
**Phase 1 is always the same:**
|
|
83
|
-
- "Scaffold +
|
|
101
|
+
- "Scaffold + Standalone Shell" — never skip, never rename
|
|
84
102
|
- Covers all PLAT-* requirements
|
|
85
103
|
- Always 1 plan (the scaffold is well-defined)
|
|
86
104
|
- Success criteria are standardized (see template)
|
|
87
|
-
- Uses templates from `templates/app/` directory
|
|
105
|
+
- Uses standalone templates from `templates/app/` directory (frontier-services.tsx, layout-standalone.tsx, package-standalone.json, vercel-standalone.json)
|
|
106
|
+
|
|
107
|
+
**Final phase is always SDK Integration:**
|
|
108
|
+
- Auto-added as the last phase — never skip, never rename
|
|
109
|
+
- Always 1 plan (mechanical, no user decisions)
|
|
110
|
+
- Wires real SDK: adds dependency, creates adapter, upgrades Layout, adds CORS
|
|
111
|
+
- Fixed success criteria (see template)
|
|
88
112
|
|
|
89
113
|
**Phase structure:**
|
|
90
|
-
- Phase 1: Scaffold (always)
|
|
91
|
-
- Phases 2-
|
|
114
|
+
- Phase 1: Scaffold + Standalone Shell (always)
|
|
115
|
+
- Phases 2 to N-1: Feature phases (from requirements)
|
|
116
|
+
- Phase N: SDK Integration (always last)
|
|
92
117
|
- Keep to 3-6 total phases for v1 — ship fast
|
|
93
118
|
- Each phase delivers something coherent and testable
|
|
94
119
|
|
|
@@ -113,17 +138,25 @@ Plans:
|
|
|
113
138
|
|
|
114
139
|
<frontier_specifics>
|
|
115
140
|
|
|
116
|
-
**Phase 1 always generates from templates:**
|
|
141
|
+
**Phase 1 always generates from standalone templates:**
|
|
117
142
|
- `templates/app/vite.config.ts` → configured with app's dev port
|
|
118
|
-
- `templates/app/
|
|
119
|
-
- `templates/app/layout.tsx` → dark theme shell with
|
|
120
|
-
- `templates/app/main-
|
|
143
|
+
- `templates/app/frontier-services.tsx` → useServices() provider + mock services
|
|
144
|
+
- `templates/app/layout-standalone.tsx` → dark theme shell with FrontierServicesProvider
|
|
145
|
+
- `templates/app/main-router.tsx` → entry point
|
|
146
|
+
- `templates/app/package-standalone.json` → dependencies without SDK
|
|
147
|
+
- `templates/app/vercel-standalone.json` → SPA rewrite only (no CORS)
|
|
121
148
|
- `templates/app/tsconfig.json` → TypeScript config
|
|
122
149
|
- `templates/app/postcss.config.js` → Tailwind setup
|
|
123
150
|
|
|
124
|
-
**Feature phases should reference
|
|
151
|
+
**Feature phases should reference service modules:**
|
|
125
152
|
- If a phase uses Events module, note it in the goal
|
|
126
153
|
- If a phase uses Wallet module, note it in the goal
|
|
127
|
-
- This helps the planner know which
|
|
154
|
+
- This helps the planner know which service methods to use via useServices()
|
|
155
|
+
|
|
156
|
+
**SDK Integration phase generates from SDK templates:**
|
|
157
|
+
- `templates/app/sdk-context.tsx` → SdkProvider + useSdk hook
|
|
158
|
+
- `templates/app/sdk-services.tsx` → adapter mapping services to real SDK
|
|
159
|
+
- `templates/app/layout.tsx` → Layout upgrade with iframe detection
|
|
160
|
+
- `templates/app/vercel.json` → full CORS origins
|
|
128
161
|
|
|
129
162
|
</frontier_specifics>
|