frontier-os-app-builder 1.1.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 +25 -0
- package/agents/fos-executor.md +22 -65
- package/agents/fos-plan-checker.md +13 -12
- package/agents/fos-planner.md +20 -67
- package/agents/fos-researcher.md +14 -10
- package/agents/fos-verifier.md +11 -5
- package/bin/fos-tools.cjs +48 -11
- 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 +1 -3
- 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 +46 -28
- package/references/deployment.md +40 -74
- 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 +18 -18
- package/templates/app/frontier-services.tsx +75 -18
- package/templates/app/layout.tsx +19 -9
- 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 +92 -117
- package/templates/app/vercel.json +8 -47
- package/templates/state/plan.md +32 -14
- package/templates/state/roadmap.md +2 -2
- package/templates/state/summary.md +26 -29
- package/workflows/add-feature.md +6 -1
- package/workflows/discuss.md +9 -3
- package/workflows/execute-plan.md +3 -3
- package/workflows/execute.md +17 -6
- package/workflows/new-app.md +54 -18
- package/workflows/new-milestone.md +9 -2
- package/workflows/plan.md +14 -5
- package/workflows/ship.md +26 -10
- package/workflows/status.md +0 -1
- package/references/module-inference.md +0 -348
- package/references/sdk-surface.md +0 -1600
- package/templates/app/main-simple-standalone.tsx +0 -19
- package/templates/app/main-simple.tsx +0 -19
- package/templates/state/manifest.json +0 -12
|
@@ -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
|
);
|
|
@@ -1,123 +1,98 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
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
|
+
}
|
|
3
31
|
|
|
4
32
|
export function createSdkServices(sdk: FrontierSDK): FrontierServices {
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
const
|
|
12
|
-
const
|
|
13
|
-
|
|
14
|
-
|
|
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
|
+
});
|
|
15
85
|
|
|
16
86
|
return {
|
|
17
|
-
wallet
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
transferFrontierDollar: (to, amount, overrides) => wallet.transferFrontierDollar(to, amount, overrides),
|
|
28
|
-
transferInternalFrontierDollar: (to, amount, overrides) => wallet.transferInternalFrontierDollar(to, amount, overrides),
|
|
29
|
-
transferOverallFrontierDollar: (to, amount, overrides) => wallet.transferOverallFrontierDollar(to, amount, overrides),
|
|
30
|
-
getSupportedTokens: () => wallet.getSupportedTokens(),
|
|
31
|
-
swap: (sourceToken, targetToken, sourceNetwork, targetNetwork, amount) => wallet.swap(sourceToken, targetToken, sourceNetwork, targetNetwork, amount),
|
|
32
|
-
quoteSwap: (sourceToken, targetToken, sourceNetwork, targetNetwork, amount) => wallet.quoteSwap(sourceToken, targetToken, sourceNetwork, targetNetwork, amount),
|
|
33
|
-
getUsdDepositInstructions: () => wallet.getUsdDepositInstructions(),
|
|
34
|
-
getEurDepositInstructions: () => wallet.getEurDepositInstructions(),
|
|
35
|
-
getLinkedBanks: () => wallet.getLinkedBanks(),
|
|
36
|
-
linkUsBankAccount: (accountOwnerName, bankName, routingNumber, accountNumber, checkingOrSavings, address) => wallet.linkUsBankAccount(accountOwnerName, bankName, routingNumber, accountNumber, checkingOrSavings, address),
|
|
37
|
-
linkEuroAccount: (accountOwnerName, accountOwnerType, firstName, lastName, ibanAccountNumber, bic) => wallet.linkEuroAccount(accountOwnerName, accountOwnerType, firstName, lastName, ibanAccountNumber, bic),
|
|
38
|
-
deleteLinkedBank: (bankId) => wallet.deleteLinkedBank(bankId),
|
|
39
|
-
getDeprecatedSmartAccounts: () => wallet.getDeprecatedSmartAccounts(),
|
|
40
|
-
},
|
|
41
|
-
storage: {
|
|
42
|
-
get: <T,>(key: string) => storage.get<T>(key),
|
|
43
|
-
set: (key, value) => storage.set(key, value),
|
|
44
|
-
remove: (key) => storage.remove(key),
|
|
45
|
-
clear: () => storage.clear(),
|
|
46
|
-
},
|
|
47
|
-
chain: {
|
|
48
|
-
getCurrentNetwork: () => chain.getCurrentNetwork(),
|
|
49
|
-
getAvailableNetworks: () => chain.getAvailableNetworks(),
|
|
50
|
-
switchNetwork: (network) => chain.switchNetwork(network),
|
|
51
|
-
getCurrentChainConfig: () => chain.getCurrentChainConfig(),
|
|
52
|
-
getContractAddresses: () => chain.getContractAddresses(),
|
|
53
|
-
},
|
|
54
|
-
user: {
|
|
55
|
-
getDetails: () => user.getDetails(),
|
|
56
|
-
getProfile: () => user.getProfile(),
|
|
57
|
-
getReferralOverview: () => user.getReferralOverview(),
|
|
58
|
-
getReferralDetails: (page) => user.getReferralDetails(page),
|
|
59
|
-
addUserContact: (data) => user.addUserContact(data),
|
|
60
|
-
getOrCreateKyc: (redirectUri) => user.getOrCreateKyc(redirectUri),
|
|
61
|
-
createSignupRequest: (payload) => user.createSignupRequest(payload),
|
|
62
|
-
getVerifiedAccessControls: () => user.getVerifiedAccessControls(),
|
|
63
|
-
},
|
|
64
|
-
partnerships: {
|
|
65
|
-
createSponsorPass: (payload) => partnerships.createSponsorPass(payload),
|
|
66
|
-
listActiveSponsorPasses: (payload) => partnerships.listActiveSponsorPasses(payload),
|
|
67
|
-
listAllSponsorPasses: (payload) => partnerships.listAllSponsorPasses(payload),
|
|
68
|
-
listSponsors: (payload) => partnerships.listSponsors(payload),
|
|
69
|
-
getSponsor: (payload) => partnerships.getSponsor(payload),
|
|
70
|
-
getSponsorPass: (payload) => partnerships.getSponsorPass(payload),
|
|
71
|
-
revokeSponsorPass: (payload) => partnerships.revokeSponsorPass(payload),
|
|
72
|
-
},
|
|
73
|
-
thirdParty: {
|
|
74
|
-
listDevelopers: (payload) => thirdParty.listDevelopers(payload),
|
|
75
|
-
getDeveloper: (payload) => thirdParty.getDeveloper(payload),
|
|
76
|
-
updateDeveloper: (payload) => thirdParty.updateDeveloper(payload),
|
|
77
|
-
rotateDeveloperApiKey: (payload) => thirdParty.rotateDeveloperApiKey(payload),
|
|
78
|
-
listApps: (payload) => thirdParty.listApps(payload),
|
|
79
|
-
createApp: (payload) => thirdParty.createApp(payload),
|
|
80
|
-
getApp: (payload) => thirdParty.getApp(payload),
|
|
81
|
-
updateApp: (payload) => thirdParty.updateApp(payload),
|
|
82
|
-
deleteApp: (payload) => thirdParty.deleteApp(payload),
|
|
83
|
-
listWebhooks: (payload) => thirdParty.listWebhooks(payload),
|
|
84
|
-
createWebhook: (payload) => thirdParty.createWebhook(payload),
|
|
85
|
-
getWebhook: (payload) => thirdParty.getWebhook(payload),
|
|
86
|
-
updateWebhook: (payload) => thirdParty.updateWebhook(payload),
|
|
87
|
-
deleteWebhook: (payload) => thirdParty.deleteWebhook(payload),
|
|
88
|
-
rotateWebhookSigningKey: (payload) => thirdParty.rotateWebhookSigningKey(payload),
|
|
89
|
-
},
|
|
90
|
-
communities: {
|
|
91
|
-
listCommunities: (payload) => communities.listCommunities(payload),
|
|
92
|
-
getCommunity: (payload) => communities.getCommunity(payload),
|
|
93
|
-
createInternshipPass: (payload) => communities.createInternshipPass(payload),
|
|
94
|
-
listInternshipPasses: (payload) => communities.listInternshipPasses(payload),
|
|
95
|
-
getInternshipPass: (payload) => communities.getInternshipPass(payload),
|
|
96
|
-
revokeInternshipPass: (payload) => communities.revokeInternshipPass(payload),
|
|
97
|
-
createReassignRequest: (payload) => communities.createReassignRequest(payload),
|
|
98
|
-
listReassignRequests: (payload) => communities.listReassignRequests(payload),
|
|
99
|
-
getReassignRequest: (payload) => communities.getReassignRequest(payload),
|
|
100
|
-
acceptReassignRequest: (payload) => communities.acceptReassignRequest(payload),
|
|
101
|
-
rejectReassignRequest: (payload) => communities.rejectReassignRequest(payload),
|
|
102
|
-
},
|
|
103
|
-
events: {
|
|
104
|
-
listEvents: (payload) => events.listEvents(payload),
|
|
105
|
-
createEvent: (payload) => events.createEvent(payload),
|
|
106
|
-
addEventHost: (payload) => events.addEventHost(payload),
|
|
107
|
-
listLocations: (payload) => events.listLocations(payload),
|
|
108
|
-
listRoomBookings: (payload) => events.listRoomBookings(payload),
|
|
109
|
-
createRoomBooking: (payload) => events.createRoomBooking(payload),
|
|
110
|
-
},
|
|
111
|
-
offices: {
|
|
112
|
-
createAccessPass: (payload) => offices.createAccessPass(payload),
|
|
113
|
-
listAccessPasses: (payload) => offices.listAccessPasses(payload),
|
|
114
|
-
getAccessPass: (payload) => offices.getAccessPass(payload),
|
|
115
|
-
revokeAccessPass: (payload) => offices.revokeAccessPass(payload),
|
|
116
|
-
},
|
|
117
|
-
navigation: {
|
|
118
|
-
openApp: (appId, options) => navigation.openApp(appId, options),
|
|
119
|
-
close: () => navigation.close(),
|
|
120
|
-
onDeepLink: (callback) => navigation.onDeepLink(callback),
|
|
121
|
-
},
|
|
87
|
+
wallet,
|
|
88
|
+
storage,
|
|
89
|
+
chain,
|
|
90
|
+
user,
|
|
91
|
+
partnerships,
|
|
92
|
+
thirdParty,
|
|
93
|
+
communities,
|
|
94
|
+
events,
|
|
95
|
+
offices,
|
|
96
|
+
navigation,
|
|
122
97
|
};
|
|
123
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,54 +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://sandbox.os.frontiertower.io"
|
|
37
|
-
}
|
|
38
|
-
],
|
|
39
|
-
"headers": [
|
|
40
|
-
{
|
|
41
|
-
"key": "Access-Control-Allow-Origin",
|
|
42
|
-
"value": "https://sandbox.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": "http://localhost:5173"
|
|
61
|
-
}
|
|
62
|
-
],
|
|
63
|
-
"headers": [
|
|
64
|
-
{
|
|
65
|
-
"key": "Access-Control-Allow-Origin",
|
|
66
|
-
"value": "http://localhost:5173"
|
|
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": "
|
|
34
|
+
"key": "Permissions-Policy",
|
|
35
|
+
"value": "camera=(), microphone=(), geolocation=()"
|
|
75
36
|
}
|
|
76
37
|
]
|
|
77
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
|
|
|
@@ -71,7 +71,7 @@ Plans:
|
|
|
71
71
|
1. sdk-context.tsx exists and exports useSdk + SdkProvider
|
|
72
72
|
2. sdk-services.tsx maps all service methods to real SDK calls
|
|
73
73
|
3. Layout.tsx has isInFrontierApp() detection and SdkProvider wrapping
|
|
74
|
-
4. vercel.json has
|
|
74
|
+
4. vercel.json has CORS + CSP frame-ancestors (3 origins) + security headers
|
|
75
75
|
5. App works both standalone (mocks) and in iframe (real SDK)
|
|
76
76
|
6. npm run build succeeds
|
|
77
77
|
**Plans**: 1 plan (mechanical)
|
|
@@ -142,7 +142,7 @@ Plans:
|
|
|
142
142
|
- `templates/app/vite.config.ts` → configured with app's dev port
|
|
143
143
|
- `templates/app/frontier-services.tsx` → useServices() provider + mock services
|
|
144
144
|
- `templates/app/layout-standalone.tsx` → dark theme shell with FrontierServicesProvider
|
|
145
|
-
- `templates/app/main-
|
|
145
|
+
- `templates/app/main-router.tsx` → entry point
|
|
146
146
|
- `templates/app/package-standalone.json` → dependencies without SDK
|
|
147
147
|
- `templates/app/vercel-standalone.json` → SPA rewrite only (no CORS)
|
|
148
148
|
- `templates/app/tsconfig.json` → TypeScript config
|
|
@@ -71,8 +71,8 @@ Each task was committed atomically:
|
|
|
71
71
|
|
|
72
72
|
## Files Created/Modified
|
|
73
73
|
|
|
74
|
-
- `src/
|
|
75
|
-
- `src/lib/
|
|
74
|
+
- `src/main.tsx` — What it does
|
|
75
|
+
- `src/lib/frontier-services.tsx` — What it does
|
|
76
76
|
|
|
77
77
|
## SDK Integration Notes
|
|
78
78
|
|
|
@@ -136,7 +136,7 @@ Or "No SDK module integration in this plan" if scaffold-only.]
|
|
|
136
136
|
|
|
137
137
|
**One-liner:**
|
|
138
138
|
Must be substantive. Examples:
|
|
139
|
-
- "Vite + React scaffold with
|
|
139
|
+
- "Vite + React scaffold with mock services layer, FrontierServicesProvider, and dark Tailwind theme"
|
|
140
140
|
- "Event listing page with real-time updates via Events SDK module"
|
|
141
141
|
- "Wallet integration for USDC payments with transaction confirmation UI"
|
|
142
142
|
|
|
@@ -164,30 +164,30 @@ NOT: "Phase complete" / "Scaffold done" / "Feature implemented"
|
|
|
164
164
|
phase: 01-scaffold
|
|
165
165
|
plan: 01
|
|
166
166
|
subsystem: scaffold
|
|
167
|
-
tags: [react, vite, tailwind,
|
|
167
|
+
tags: [react, vite, tailwind, mock-services, standalone]
|
|
168
168
|
|
|
169
169
|
requires: []
|
|
170
170
|
provides:
|
|
171
171
|
- "Vite + React project structure"
|
|
172
|
-
- "
|
|
173
|
-
- "
|
|
172
|
+
- "FrontierServicesProvider context with useServices hook"
|
|
173
|
+
- "Mock services layer via createMockServices()"
|
|
174
174
|
- "Dark theme via Tailwind"
|
|
175
175
|
affects: [02-event-listing, 03-event-creation]
|
|
176
176
|
|
|
177
177
|
tech-stack:
|
|
178
|
-
added: [react, vite,
|
|
179
|
-
patterns: [
|
|
178
|
+
added: [react, react-dom, react-router-dom, vite, tailwindcss, vitest]
|
|
179
|
+
patterns: [FrontierServicesProvider + useServices(), mock services layer, dark Tailwind 4 @theme]
|
|
180
180
|
|
|
181
181
|
key-files:
|
|
182
|
-
created: [src/
|
|
182
|
+
created: [src/main.tsx, src/lib/frontier-services.tsx, src/views/Layout.tsx, src/styles/index.css, vite.config.ts, tsconfig.json, postcss.config.js, index.html]
|
|
183
183
|
modified: []
|
|
184
184
|
|
|
185
185
|
key-decisions:
|
|
186
186
|
- "Used Tailwind for styling — matches Frontier OS design system"
|
|
187
|
-
- "
|
|
187
|
+
- "Standalone-first: feature code consumes useServices(), never the SDK directly"
|
|
188
188
|
|
|
189
189
|
patterns-established:
|
|
190
|
-
- "
|
|
190
|
+
- "Services: All data access via useServices() hook; mocks until SDK Integration"
|
|
191
191
|
- "Dark theme: bg-neutral-950 base, text-white, no light mode support"
|
|
192
192
|
|
|
193
193
|
sdk-modules-used: []
|
|
@@ -200,7 +200,7 @@ completed: 2026-03-27
|
|
|
200
200
|
|
|
201
201
|
# Phase 1: Scaffold + Standalone Shell — Plan 1 Summary
|
|
202
202
|
|
|
203
|
-
**Vite + React scaffold with services layer,
|
|
203
|
+
**Vite + React scaffold with mock services layer, FrontierServicesProvider, and dark Tailwind theme on port 5180**
|
|
204
204
|
|
|
205
205
|
## Performance
|
|
206
206
|
|
|
@@ -213,38 +213,35 @@ completed: 2026-03-27
|
|
|
213
213
|
## Accomplishments
|
|
214
214
|
|
|
215
215
|
- Full Vite + React + TypeScript project scaffolded
|
|
216
|
-
-
|
|
217
|
-
-
|
|
216
|
+
- FrontierServicesProvider wraps app, useServices() available everywhere
|
|
217
|
+
- Mock services layer returns realistic data via createMockServices()
|
|
218
218
|
- Dark theme via Tailwind — bg-neutral-950 base, all components dark
|
|
219
219
|
|
|
220
220
|
## Task Commits
|
|
221
221
|
|
|
222
222
|
1. **Task 1: Scaffold Vite project** — `a1b2c3d` (feat: scaffold)
|
|
223
|
-
2. **Task 2:
|
|
223
|
+
2. **Task 2: FrontierServicesProvider + mock services** — `e4f5g6h` (feat: services layer)
|
|
224
224
|
3. **Task 3: Dark theme + layout** — `i7j8k9l` (feat: dark theme)
|
|
225
225
|
|
|
226
226
|
## Files Created/Modified
|
|
227
227
|
|
|
228
|
-
- `src/
|
|
229
|
-
- `src/lib/
|
|
230
|
-
- `src/
|
|
231
|
-
- `src/
|
|
232
|
-
- `vite.config.ts` —
|
|
233
|
-
- `tailwind.config.ts` — Dark theme configuration
|
|
228
|
+
- `src/main.tsx` — React root; wraps the app in FrontierServicesProvider
|
|
229
|
+
- `src/lib/frontier-services.tsx` — FrontierServicesProvider + useServices() hook + createMockServices()
|
|
230
|
+
- `src/views/Layout.tsx` — Dark theme shell
|
|
231
|
+
- `src/styles/index.css` — Tailwind + dark theme variables
|
|
232
|
+
- `vite.config.ts` — Vite + Vitest, dev server on port 5180
|
|
234
233
|
- `tsconfig.json` — TypeScript strict mode
|
|
235
234
|
- `postcss.config.js` — Tailwind PostCSS setup
|
|
235
|
+
- `index.html` — Entry HTML with `<body class="dark">`
|
|
236
236
|
|
|
237
237
|
## SDK Integration Notes
|
|
238
238
|
|
|
239
|
-
|
|
240
|
-
- SDK destroyed on unmount via cleanup in useEffect
|
|
241
|
-
- useSdk() throws if used outside SdkProvider — fail-fast pattern
|
|
242
|
-
- No SDK modules used yet — just core initialization
|
|
239
|
+
No SDK in this phase — services come from `createMockServices()`; the real SDK is wired in the final SDK Integration phase.
|
|
243
240
|
|
|
244
241
|
## Decisions Made
|
|
245
242
|
|
|
246
243
|
- Used Tailwind instead of CSS modules — better dark theme support, matches Frontier OS
|
|
247
|
-
-
|
|
244
|
+
- Standalone-first: feature code consumes useServices(), keeping the SDK out of Phase 1
|
|
248
245
|
|
|
249
246
|
## Deviations from Plan
|
|
250
247
|
|
|
@@ -256,13 +253,13 @@ None — plan executed exactly as written
|
|
|
256
253
|
- [x] Dev server: pass on port 5180
|
|
257
254
|
- [x] TypeScript: pass (strict mode, no errors)
|
|
258
255
|
- [x] Dark theme: pass (no white backgrounds)
|
|
259
|
-
- [x] Iframe mode:
|
|
260
|
-
- [x] Standalone mode: pass (
|
|
256
|
+
- [x] Iframe mode: N/A (wired during SDK Integration phase)
|
|
257
|
+
- [x] Standalone mode: pass (renders with mock data)
|
|
261
258
|
|
|
262
259
|
## Next Phase Readiness
|
|
263
260
|
|
|
264
261
|
- Scaffold complete, ready for feature development
|
|
265
|
-
-
|
|
262
|
+
- useServices() available for feature hooks in Phase 2
|
|
266
263
|
- No blockers
|
|
267
264
|
|
|
268
265
|
---
|
package/workflows/add-feature.md
CHANGED
|
@@ -35,10 +35,15 @@ Check if `$ARGUMENTS` contains a feature description.
|
|
|
35
35
|
Adding feature: "[description]"
|
|
36
36
|
```
|
|
37
37
|
|
|
38
|
-
**If no description:** Use AskUserQuestion:
|
|
38
|
+
**If no description:** Use AskUserQuestion (if available):
|
|
39
39
|
- header: "New Feature"
|
|
40
40
|
- question: "What feature do you want to add to [App Name]? Describe what it does."
|
|
41
41
|
|
|
42
|
+
If AskUserQuestion denied: error and exit — a description is required:
|
|
43
|
+
```
|
|
44
|
+
Error: No feature description provided. Usage: /fos:add-feature "describe the feature"
|
|
45
|
+
```
|
|
46
|
+
|
|
42
47
|
**Store the description** for module inference.
|
|
43
48
|
</step>
|
|
44
49
|
|
package/workflows/discuss.md
CHANGED
|
@@ -108,7 +108,7 @@ Extract from ROADMAP.md for this phase:
|
|
|
108
108
|
|
|
109
109
|
**If `has_context` is true:**
|
|
110
110
|
|
|
111
|
-
Use AskUserQuestion:
|
|
111
|
+
Use AskUserQuestion (if available):
|
|
112
112
|
- header: "Context Exists"
|
|
113
113
|
- question: "Phase [X] already has context decisions. What do you want to do?"
|
|
114
114
|
- options:
|
|
@@ -120,6 +120,8 @@ Use AskUserQuestion:
|
|
|
120
120
|
**If "View":** Display CONTEXT.md, then offer Update/Skip.
|
|
121
121
|
**If "Skip":** Exit workflow with next-up pointing to `/fos:plan N`.
|
|
122
122
|
|
|
123
|
+
**If AskUserQuestion denied:** Skip — use existing context as-is, proceed to planning.
|
|
124
|
+
|
|
123
125
|
**If `has_context` is false:** Continue to analyze_phase.
|
|
124
126
|
</step>
|
|
125
127
|
|
|
@@ -294,7 +296,7 @@ I've identified [count] areas where your input will shape the implementation:
|
|
|
294
296
|
4. **[Area 4]** — [One sentence]
|
|
295
297
|
```
|
|
296
298
|
|
|
297
|
-
Use AskUserQuestion:
|
|
299
|
+
Use AskUserQuestion (if available):
|
|
298
300
|
- header: "Topics"
|
|
299
301
|
- question: "Which areas do you want to discuss? I'll handle the rest with sensible defaults."
|
|
300
302
|
- multiSelect: true
|
|
@@ -302,12 +304,16 @@ Use AskUserQuestion:
|
|
|
302
304
|
|
|
303
305
|
**If "None":** Set all areas as "Claude's Discretion" and skip to write_context.
|
|
304
306
|
**If specific areas selected:** Discuss those, mark unselected as "Claude's Discretion".
|
|
307
|
+
|
|
308
|
+
**If AskUserQuestion denied (autonomous/don't-ask mode):** Present the gray areas and your recommended defaults, then mark all as "Claude's Discretion" and proceed to write_context. Briefly explain each choice so the user can review in CONTEXT.md.
|
|
305
309
|
</step>
|
|
306
310
|
|
|
307
311
|
<step name="discuss_areas">
|
|
308
312
|
**Deep-dive each selected area.**
|
|
309
313
|
|
|
310
|
-
For each selected gray area, ask ONE focused question at a time. Use AskUserQuestion with concrete options — never open-ended "what do you think?"
|
|
314
|
+
For each selected gray area, ask ONE focused question at a time. Use AskUserQuestion (if available) with concrete options — never open-ended "what do you think?"
|
|
315
|
+
|
|
316
|
+
**If AskUserQuestion denied (autonomous/don't-ask mode):** This step is skipped — all areas were already resolved with defaults in the present_gray_areas step.
|
|
311
317
|
|
|
312
318
|
**Prior decision awareness:** If `prior_decisions` contains a relevant decision for this area, present it as context: "In Phase [N], you decided [decision]. Apply the same approach here, or different?" Offer "Same as Phase [N]" as the first option, then the other alternatives.
|
|
313
319
|
|
|
@@ -32,7 +32,7 @@ This workflow runs inside a subagent — it does not spawn further subagents.
|
|
|
32
32
|
8. **Iframe detection:** `isInFrontierApp()` check in Layout.tsx. Standalone mode shows fallback banner.
|
|
33
33
|
9. **SdkProvider wrapping:** Entire app wrapped in SdkProvider when inside iframe. SDK initialized once via useRef, destroyed on unmount.
|
|
34
34
|
10. **Permissions:** Every SDK method used must have its permission declared in manifest.json.
|
|
35
|
-
11. **CORS:** vercel.json
|
|
35
|
+
11. **CORS:** vercel.json sets CORS for the production origin + a CSP `frame-ancestors` listing the 3 Frontier OS origins (os.frontiertower.io, sandbox.os.frontiertower.io, localhost:5173) + security headers.
|
|
36
36
|
12. **SDK imports:** Use `@frontiertower/frontier-sdk` for SDK classes and types. Exact import paths, not barrel imports.
|
|
37
37
|
</frontier_os_rules>
|
|
38
38
|
|
|
@@ -154,8 +154,8 @@ npx tsc --noEmit # No TypeScript errors
|
|
|
154
154
|
|
|
155
155
|
Also run FOS-specific validation:
|
|
156
156
|
```bash
|
|
157
|
-
node "$HOME/.claude/frontier-os-app-builder/bin/fos-tools.cjs" validate structure
|
|
158
|
-
node "$HOME/.claude/frontier-os-app-builder/bin/fos-tools.cjs" validate permissions
|
|
157
|
+
node "$HOME/.claude/frontier-os-app-builder/bin/fos-tools.cjs" validate structure --phase "$PHASE"
|
|
158
|
+
node "$HOME/.claude/frontier-os-app-builder/bin/fos-tools.cjs" validate permissions --phase "$PHASE"
|
|
159
159
|
```
|
|
160
160
|
|
|
161
161
|
Document results for SUMMARY.md.
|