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.
Files changed (57) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +25 -0
  3. package/agents/fos-executor.md +22 -65
  4. package/agents/fos-plan-checker.md +13 -12
  5. package/agents/fos-planner.md +20 -67
  6. package/agents/fos-researcher.md +14 -10
  7. package/agents/fos-verifier.md +11 -5
  8. package/bin/fos-tools.cjs +48 -11
  9. package/bin/install.js +8 -5
  10. package/commands/fos/add-feature.md +1 -2
  11. package/commands/fos/discuss.md +0 -1
  12. package/commands/fos/new-app.md +1 -3
  13. package/commands/fos/new-milestone.md +1 -1
  14. package/commands/fos/plan.md +0 -2
  15. package/package.json +7 -1
  16. package/references/app-patterns.md +46 -28
  17. package/references/deployment.md +40 -74
  18. package/references/module-index.md +32 -0
  19. package/references/sdk/chain.md +92 -0
  20. package/references/sdk/communities.md +159 -0
  21. package/references/sdk/events.md +212 -0
  22. package/references/sdk/init.md +126 -0
  23. package/references/sdk/navigation.md +49 -0
  24. package/references/sdk/offices.md +76 -0
  25. package/references/sdk/partnerships.md +111 -0
  26. package/references/sdk/storage.md +44 -0
  27. package/references/sdk/thirdparty.md +240 -0
  28. package/references/sdk/token-amount.md +99 -0
  29. package/references/sdk/types.md +27 -0
  30. package/references/sdk/ui-utils.md +39 -0
  31. package/references/sdk/user.md +208 -0
  32. package/references/sdk/wallet.md +334 -0
  33. package/references/verification-rules.md +18 -18
  34. package/templates/app/frontier-services.tsx +75 -18
  35. package/templates/app/layout.tsx +19 -9
  36. package/templates/app/package.json +2 -1
  37. package/templates/app/public/favicon.svg +3 -0
  38. package/templates/app/sdk-context.tsx +7 -9
  39. package/templates/app/sdk-services.tsx +92 -117
  40. package/templates/app/vercel.json +8 -47
  41. package/templates/state/plan.md +32 -14
  42. package/templates/state/roadmap.md +2 -2
  43. package/templates/state/summary.md +26 -29
  44. package/workflows/add-feature.md +6 -1
  45. package/workflows/discuss.md +9 -3
  46. package/workflows/execute-plan.md +3 -3
  47. package/workflows/execute.md +17 -6
  48. package/workflows/new-app.md +54 -18
  49. package/workflows/new-milestone.md +9 -2
  50. package/workflows/plan.md +14 -5
  51. package/workflows/ship.md +26 -10
  52. package/workflows/status.md +0 -1
  53. package/references/module-inference.md +0 -348
  54. package/references/sdk-surface.md +0 -1600
  55. package/templates/app/main-simple-standalone.tsx +0 -19
  56. package/templates/app/main-simple.tsx +0 -19
  57. package/templates/state/manifest.json +0 -12
@@ -1,4 +1,4 @@
1
- import { createContext, useContext, useEffect, useRef, useState, type ReactNode } from 'react';
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 sdkRef = useRef<FrontierSDK | null>(null);
14
- const [ready, setReady] = useState(false);
13
+ const [sdk, setSdk] = useState<FrontierSDK | null>(null);
15
14
 
16
15
  useEffect(() => {
17
- const sdk = new FrontierSDK();
18
- sdkRef.current = sdk;
19
- setReady(true);
16
+ const instance = new FrontierSDK();
17
+ setSdk(instance);
20
18
 
21
19
  return () => {
22
- sdk.destroy();
20
+ instance.destroy();
23
21
  };
24
22
  }, []);
25
23
 
26
- if (!ready) return null;
24
+ if (!sdk) return null;
27
25
 
28
26
  return (
29
- <SdkContext.Provider value={sdkRef.current}>
27
+ <SdkContext.Provider value={sdk}>
30
28
  {children}
31
29
  </SdkContext.Provider>
32
30
  );
@@ -1,123 +1,98 @@
1
- import { FrontierSDK } from '@frontiertower/frontier-sdk';
2
- import type { FrontierServices } from './frontier-services';
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
- const wallet = sdk.getWallet();
6
- const storage = sdk.getStorage();
7
- const chain = sdk.getChain();
8
- const user = sdk.getUser();
9
- const partnerships = sdk.getPartnerships();
10
- const thirdParty = sdk.getThirdParty();
11
- const communities = sdk.getCommunities();
12
- const events = sdk.getEvents();
13
- const offices = sdk.getOffices();
14
- const navigation = sdk.getNavigation();
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
- getBalance: () => wallet.getBalance(),
19
- getBalanceFormatted: () => wallet.getBalanceFormatted(),
20
- getAddress: () => wallet.getAddress(),
21
- getSmartAccount: () => wallet.getSmartAccount(),
22
- transferERC20: (tokenAddress, to, amount, overrides) => wallet.transferERC20(tokenAddress, to, amount, overrides),
23
- approveERC20: (tokenAddress, spender, amount, overrides) => wallet.approveERC20(tokenAddress, spender, amount, overrides),
24
- transferNative: (to, amount, overrides) => wallet.transferNative(to, amount, overrides),
25
- executeCall: (call, overrides) => wallet.executeCall(call, overrides),
26
- executeBatchCall: (calls, overrides) => wallet.executeBatchCall(calls, overrides),
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": "Access-Control-Allow-Methods",
46
- "value": "GET, OPTIONS"
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": "Access-Control-Allow-Headers",
50
- "value": "Content-Type"
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": "Access-Control-Allow-Methods",
70
- "value": "GET, OPTIONS"
30
+ "key": "Referrer-Policy",
31
+ "value": "strict-origin-when-cross-origin"
71
32
  },
72
33
  {
73
- "key": "Access-Control-Allow-Headers",
74
- "value": "Content-Type"
34
+ "key": "Permissions-Policy",
35
+ "value": "camera=(), microphone=(), geolocation=()"
75
36
  }
76
37
  ]
77
38
  }
@@ -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
- # SDK reference for modules used in this plan:
46
- @frontier-sdk/docs/[module].md
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
- - SdkProvider from `templates/app/sdk-context.tsx`
142
- - Layout with iframe detection from `templates/app/layout.tsx`
143
- - Dark theme setup via Tailwind
144
- - Standalone fallback UI
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
- **SDK usage in tasks:**
148
- - Always specify the exact import: `import { FrontierSDK } from '@frontiertower/frontier-sdk'`
149
- - Always use `useSdk()` hook, never instantiate SDK directly in components
150
- - Always wrap SDK calls in try/catch SDK may not be available in standalone mode
151
- - Reference the specific SDK module docs in `<context>` section
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
- - App works in both iframe and standalone modes
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 all 3 CORS origin blocks
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-simple-standalone.tsx` or `main-router.tsx` → entry point
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/App.tsx` — What it does
75
- - `src/lib/sdk-context.tsx` — What it does
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 SdkProvider, iframe detection, and dark Tailwind theme"
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, frontier-sdk, iframe-detection]
167
+ tags: [react, vite, tailwind, mock-services, standalone]
168
168
 
169
169
  requires: []
170
170
  provides:
171
171
  - "Vite + React project structure"
172
- - "SdkProvider context with useSdk hook"
173
- - "Iframe detection with standalone fallback"
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, tailwind, @frontiertower/frontier-sdk]
179
- patterns: [SdkProvider wrapper, useSdk hook, iframe detection utility]
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/App.tsx, src/lib/sdk-context.tsx, src/lib/iframe.ts, src/components/Layout.tsx]
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
- - "Iframe detection via window.self !== window.top with fallback UI"
187
+ - "Standalone-first: feature code consumes useServices(), never the SDK directly"
188
188
 
189
189
  patterns-established:
190
- - "SdkProvider: All SDK access via useSdk() hook, never direct instantiation"
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, mock data, dark Tailwind theme on port 5180**
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
- - SdkProvider wraps app, useSdk() available everywhere
217
- - Iframe detection works shows standalone banner when not in Frontier OS
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: SdkProvider + iframe detection** — `e4f5g6h` (feat: sdk integration)
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/App.tsx` — Root component with SdkProvider
229
- - `src/lib/sdk-context.tsx` — SdkProvider + useSdk hook
230
- - `src/lib/iframe.ts` — Iframe detection utility
231
- - `src/components/Layout.tsx` — Dark theme shell
232
- - `vite.config.ts` — Dev server on port 5180
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
- - SDK initialized once in SdkProvider via useRef to prevent re-instantiation
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
- - Iframe detection uses window.self !== window.top simple, reliable
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: pass (renders in Frontier OS)
260
- - [x] Standalone mode: pass (shows fallback banner)
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
- - SdkProvider available for module integration in Phase 2
262
+ - useServices() available for feature hooks in Phase 2
266
263
  - No blockers
267
264
 
268
265
  ---
@@ -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
 
@@ -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 must include all 3 Frontier OS origins (os.frontiertower.io, sandbox.os.frontiertower.io, localhost:5173).
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.