@codeyam/codeyam-cli 0.1.24 → 0.1.25

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 (50) hide show
  1. package/analyzer-template/.build-info.json +6 -6
  2. package/analyzer-template/log.txt +3 -3
  3. package/codeyam-cli/src/commands/editor.js +277 -83
  4. package/codeyam-cli/src/commands/editor.js.map +1 -1
  5. package/codeyam-cli/src/utils/__tests__/webappDetection.test.js +6 -0
  6. package/codeyam-cli/src/utils/__tests__/webappDetection.test.js.map +1 -1
  7. package/codeyam-cli/src/utils/editorScenarios.js +1 -0
  8. package/codeyam-cli/src/utils/editorScenarios.js.map +1 -1
  9. package/codeyam-cli/src/utils/entityChangeStatus.server.js +15 -0
  10. package/codeyam-cli/src/utils/entityChangeStatus.server.js.map +1 -1
  11. package/codeyam-cli/src/utils/scenariosManifest.js +8 -2
  12. package/codeyam-cli/src/utils/scenariosManifest.js.map +1 -1
  13. package/codeyam-cli/src/utils/testResultCache.js +53 -0
  14. package/codeyam-cli/src/utils/testResultCache.js.map +1 -0
  15. package/codeyam-cli/src/utils/testResultCache.server.js +81 -0
  16. package/codeyam-cli/src/utils/testResultCache.server.js.map +1 -0
  17. package/codeyam-cli/src/utils/testResultCache.server.test.js +187 -0
  18. package/codeyam-cli/src/utils/testResultCache.server.test.js.map +1 -0
  19. package/codeyam-cli/src/utils/testResultCache.test.js +230 -0
  20. package/codeyam-cli/src/utils/testResultCache.test.js.map +1 -0
  21. package/codeyam-cli/src/utils/webappDetection.js +4 -2
  22. package/codeyam-cli/src/utils/webappDetection.js.map +1 -1
  23. package/codeyam-cli/src/webserver/__tests__/idleDetector.test.js +32 -0
  24. package/codeyam-cli/src/webserver/__tests__/idleDetector.test.js.map +1 -1
  25. package/codeyam-cli/src/webserver/build/client/assets/editor.entity.(_sha)-DLM1-ZMt.js +96 -0
  26. package/codeyam-cli/src/webserver/build/client/assets/globals-9EkC9j9I.css +1 -0
  27. package/codeyam-cli/src/webserver/build/client/assets/{manifest-694b698a.js → manifest-7e749098.js} +1 -1
  28. package/codeyam-cli/src/webserver/build/client/assets/{root-DXjFYOxD.js → root-DGtly3mb.js} +1 -1
  29. package/codeyam-cli/src/webserver/build/server/assets/{analysisRunner-zEYtiv0T.js → analysisRunner-CO8xocj3.js} +1 -1
  30. package/codeyam-cli/src/webserver/build/server/assets/{index-CcHPEbhi.js → index-QKPqlUgg.js} +1 -1
  31. package/codeyam-cli/src/webserver/build/server/assets/{init-D68IyWbU.js → init-DlspChIk.js} +1 -1
  32. package/codeyam-cli/src/webserver/build/server/assets/server-build-ChzicV-B.js +689 -0
  33. package/codeyam-cli/src/webserver/build/server/index.js +1 -1
  34. package/codeyam-cli/src/webserver/build-info.json +5 -5
  35. package/codeyam-cli/src/webserver/idleDetector.js +12 -3
  36. package/codeyam-cli/src/webserver/idleDetector.js.map +1 -1
  37. package/codeyam-cli/src/webserver/terminalServer.js +3 -1
  38. package/codeyam-cli/src/webserver/terminalServer.js.map +1 -1
  39. package/codeyam-cli/templates/expo-react-native/MOBILE_SETUP.md +115 -0
  40. package/codeyam-cli/templates/expo-react-native/__tests__/.gitkeep +0 -0
  41. package/codeyam-cli/templates/expo-react-native/app/_layout.tsx +3 -2
  42. package/codeyam-cli/templates/expo-react-native/global.css +7 -0
  43. package/codeyam-cli/templates/expo-react-native/lib/theme.ts +73 -0
  44. package/codeyam-cli/templates/expo-react-native/package.json +16 -6
  45. package/codeyam-cli/templates/isolation-route/expo-router.tsx.template +54 -0
  46. package/codeyam-cli/templates/skills/codeyam-editor/SKILL.md +15 -0
  47. package/package.json +1 -1
  48. package/codeyam-cli/src/webserver/build/client/assets/editor.entity.(_sha)-oBrbke_R.js +0 -96
  49. package/codeyam-cli/src/webserver/build/client/assets/globals-oyPmV37k.css +0 -1
  50. package/codeyam-cli/src/webserver/build/server/assets/server-build-Cxzo0Zp2.js +0 -688
@@ -1,10 +1,10 @@
1
1
  {
2
- "buildTimestamp": "2026-03-26T12:55:43.726Z",
3
- "buildTime": 1774529743726,
4
- "gitCommit": "2c142d4185f95cc4ef22693e7f0cad3a09f021d4",
2
+ "buildTimestamp": "2026-03-27T13:51:55.725Z",
3
+ "buildTime": 1774619515725,
4
+ "gitCommit": "cefcc7c0aa462f7267e8912e66de352da511a931",
5
5
  "nodeVersion": "v20.20.1",
6
6
  "contentHash": "c92230c027acb71cab56d2a696876a6a52206bfadd59fbc31a512b00a7ee8826",
7
- "buildNumber": 1241,
8
- "semanticVersion": "0.1.1241",
9
- "version": "0.1.1241 (2026-03-26T12:55+c92230c)"
7
+ "buildNumber": 1256,
8
+ "semanticVersion": "0.1.1256",
9
+ "version": "0.1.1256 (2026-03-27T13:51+c92230c)"
10
10
  }
@@ -1,7 +1,7 @@
1
1
 
2
- [3/26/2026, 12:55:43 PM] > codeyam-combo@1.0.0 mergeDependencies
3
- [3/26/2026, 12:55:43 PM] > node ./scripts/mergePackageJsonFiles.cjs
2
+ [3/27/2026, 1:51:55 PM] > codeyam-combo@1.0.0 mergeDependencies
3
+ [3/27/2026, 1:51:55 PM] > node ./scripts/mergePackageJsonFiles.cjs
4
4
 
5
5
 
6
- [3/26/2026, 12:55:43 PM] Merged dependencies into root package.json
6
+ [3/27/2026, 1:51:55 PM] Merged dependencies into root package.json
7
7
 
@@ -189,6 +189,44 @@ function getProjectDimensions(root) {
189
189
  return { defaultName: 'Desktop', names: [] };
190
190
  }
191
191
  }
192
+ function getTechStackContext(root) {
193
+ const state = readState(root);
194
+ const techStackId = state?.techStackId || '';
195
+ const stack = TECH_STACKS.find((s) => s.id === techStackId);
196
+ const isExpo = techStackId === 'expo-react-native';
197
+ const isChromeExt = techStackId === 'chrome-extension-react';
198
+ const isNextjs = techStackId.startsWith('nextjs-') || (!isExpo && !isChromeExt);
199
+ return {
200
+ id: techStackId || 'nextjs-prisma-sqlite',
201
+ isExpo,
202
+ isNextjs,
203
+ isChromeExt,
204
+ hasDatabase: isNextjs,
205
+ testRunner: isNextjs ? 'vitest' : 'jest',
206
+ testRunCommand: isNextjs ? 'npx vitest run' : 'npx jest',
207
+ storageType: isExpo
208
+ ? 'asyncStorage'
209
+ : isChromeExt
210
+ ? 'chromeStorage'
211
+ : 'prisma',
212
+ routerImport: isExpo
213
+ ? 'expo-router'
214
+ : isChromeExt
215
+ ? 'react-router-dom'
216
+ : 'next/navigation',
217
+ componentPrimitives: isExpo
218
+ ? '<View>, <Text>, <ScrollView>'
219
+ : '<div>, <span>, <h1>',
220
+ rawPrimitivesList: isExpo
221
+ ? '<View>, <Text>, <Image>, <ScrollView>, <FlatList>'
222
+ : '<div>, <span>, <h1>, <p>, <img>, <ul>',
223
+ patternsFile: isExpo
224
+ ? 'MOBILE_SETUP.md'
225
+ : isChromeExt
226
+ ? 'EXTENSION_SETUP.md'
227
+ : 'FEATURE_PATTERNS.md',
228
+ };
229
+ }
192
230
  /**
193
231
  * Print dimension guidance when the project has multiple screen sizes.
194
232
  * Tells Claude to pick the right dimension for the content being previewed
@@ -281,7 +319,12 @@ function printAppScenarioInstructions(pageName, route) {
281
319
  console.log(chalk.dim(' For external APIs: also add "externalApis":{"GET https://...":{"body":[...],"status":200}}'));
282
320
  }
283
321
  else {
322
+ const appCtx = getTechStackContext(root);
284
323
  checkbox('Include data in every app scenario — without it the page will be empty:');
324
+ if (appCtx.isExpo) {
325
+ console.log(chalk.dim(' Use "localStorage":{"items":"[...]"} to pre-populate AsyncStorage (values are JSON strings)'));
326
+ console.log(chalk.dim(" AsyncStorage uses localStorage on web — CodeYam's injection works automatically."));
327
+ }
285
328
  console.log(chalk.dim(' Use "mockData":{"routes":{"/api/...":{"body":[...]}}} to mock API responses'));
286
329
  console.log(chalk.dim(' For external APIs: add "externalApis":{"GET https://...":{"body":[...],"status":200}}'));
287
330
  }
@@ -356,9 +399,11 @@ function printExtractionPlanInstructions() {
356
399
  * Shared component capture instructions used by editor Step 7 and migration Steps 3/8.
357
400
  * Prints the full isolation route setup, codeyam-capture wrapper, register command, and error checking.
358
401
  */
359
- function printComponentCaptureInstructions() {
402
+ function printComponentCaptureInstructions(root) {
403
+ const ctx = root ? getTechStackContext(root) : undefined;
404
+ const isExpo = ctx?.isExpo ?? false;
360
405
  checkbox('Create isolation route dirs: `codeyam editor isolate ComponentA ComponentB ...`');
361
- console.log(chalk.dim(' This creates app/isolated-components/layout.tsx (with production notFound() guard) and'));
406
+ console.log(chalk.dim(` This creates app/isolated-components/layout.tsx (with production ${isExpo ? '__DEV__' : 'notFound()'} guard) and`));
362
407
  console.log(chalk.dim(' a directory per component. List ALL components that need isolation routes.'));
363
408
  checkbox('For each visual component:');
364
409
  console.log(chalk.dim(' 1. Read the source AND find where it is used in the app to understand:'));
@@ -370,14 +415,28 @@ function printComponentCaptureInstructions() {
370
415
  console.log(chalk.dim(' — Different visual states: loading, error, disabled, selected, hover'));
371
416
  console.log(chalk.dim(' — Boundary values: single item vs many, min/max ratings, very long names'));
372
417
  console.log(chalk.dim(' 3. Create ONE isolation route per component with a scenarios map and ?s= query param:'));
373
- console.log(chalk.dim(' Remix: app/routes/isolated-components.ComponentName.tsx → /isolated-components/ComponentName'));
374
- console.log(chalk.dim(' Next.js: app/isolated-components/ComponentName/page.tsx → /isolated-components/ComponentName'));
418
+ if (isExpo) {
419
+ console.log(chalk.dim(' Expo: app/isolated-components/ComponentName.tsx → /isolated-components/ComponentName'));
420
+ console.log(chalk.dim(' Use useLocalSearchParams() from expo-router to read ?s=ScenarioName.'));
421
+ }
422
+ else {
423
+ console.log(chalk.dim(' Remix: app/routes/isolated-components.ComponentName.tsx → /isolated-components/ComponentName'));
424
+ console.log(chalk.dim(' Next.js: app/isolated-components/ComponentName/page.tsx → /isolated-components/ComponentName'));
425
+ }
375
426
  console.log(chalk.dim(' The route defines a `scenarios` object mapping scenario names to props,'));
376
427
  console.log(chalk.dim(' reads `?s=ScenarioName` from the URL, and renders the component with those props.'));
377
- console.log(chalk.dim(' Wrap the component in a capture container with id="codeyam-capture":'));
378
- console.log(chalk.dim(' <div id="codeyam-capture" style={{ display:"inline-block" }}>'));
379
- console.log(chalk.dim(' <div style={{ width:"100%", maxWidth:"..." }}> ← match the app\'s container width'));
380
- console.log(chalk.dim(' e.g. card in a 3-col grid → maxWidth:"24rem", full-width component → omit maxWidth'));
428
+ if (isExpo) {
429
+ console.log(chalk.dim(' Wrap the component in a capture container with nativeID="codeyam-capture":'));
430
+ console.log(chalk.dim(' <View nativeID="codeyam-capture" style={{ display:"flex" }}>'));
431
+ }
432
+ else {
433
+ console.log(chalk.dim(' Wrap the component in a capture container with id="codeyam-capture":'));
434
+ console.log(chalk.dim(' <div id="codeyam-capture" style={{ display:"inline-block" }}>'));
435
+ }
436
+ console.log(chalk.dim(isExpo
437
+ ? ' <View style={{ width:"100%", maxWidth:... }}> ← match the app\'s container width'
438
+ : ' <div style={{ width:"100%", maxWidth:"..." }}> ← match the app\'s container width'));
439
+ console.log(chalk.dim(' e.g. card in a 3-col grid → maxWidth: 384, full-width component → omit maxWidth'));
381
440
  console.log(chalk.dim(' The screenshot captures just this wrapper, so the component fills the image.'));
382
441
  console.log(chalk.dim(' Center the wrapper on the page (flexbox center both axes) and set a page background'));
383
442
  console.log(chalk.dim(' color that matches where the component normally appears (e.g. white for light UIs).'));
@@ -780,7 +839,7 @@ function printSetup(root) {
780
839
  console.log(chalk.yellow(' ( ) Mobile') + chalk.dim(' — 375 × 667'));
781
840
  console.log(chalk.yellow(' ( ) Custom') + chalk.dim(' — ask for width × height'));
782
841
  console.log();
783
- console.log(chalk.dim(' Pre-select based on app format: mobile-responsive-web-app/desktop-app → Desktop, mobile-app → Mobile, chrome-extension → Custom (400×600).'));
842
+ console.log(chalk.dim(' Pre-select based on app format: mobile-responsive-web-app/desktop-app → Desktop, mobile-app → iPhone 16 (393×852), chrome-extension → Custom (400×600).'));
784
843
  console.log(chalk.dim(' If only one obvious choice, confirm it rather than asking.'));
785
844
  console.log(chalk.dim(` Save the choice via: curl -s -X POST http://localhost:${port}/api/editor-project-info -H "Content-Type: application/json" -d '{"defaultScreenSize":{"name":"Desktop","width":1440,"height":900}}'`));
786
845
  console.log();
@@ -989,33 +1048,64 @@ function printStep2(root, feature) {
989
1048
  }
990
1049
  console.log('Get the project ready to build.');
991
1050
  console.log();
1051
+ const ctx = getTechStackContext(root);
992
1052
  // If no project exists yet, include scaffolding instructions first
993
1053
  if (!projectExists) {
994
1054
  console.log(chalk.bold('Scaffold the project:'));
995
1055
  checkbox('Run `codeyam editor template` to scaffold, install dependencies, init git, and configure CodeYam');
996
- console.log(chalk.dim(' This copies the Next.js + Prisma 7 + SQLite template, runs npm install,'));
1056
+ if (ctx.isExpo) {
1057
+ console.log(chalk.dim(' This copies the Expo + React Native template, runs npm install,'));
1058
+ }
1059
+ else if (ctx.isChromeExt) {
1060
+ console.log(chalk.dim(' This copies the Chrome Extension + React template, runs npm install,'));
1061
+ }
1062
+ else {
1063
+ console.log(chalk.dim(' This copies the Next.js + Prisma 7 + SQLite template, runs npm install,'));
1064
+ }
997
1065
  console.log(chalk.dim(' initializes git, runs codeyam init, and refreshes the editor — all in one command.'));
998
1066
  console.log();
999
- checkbox('Define your data models in `prisma/schema.prisma`');
1000
- console.log(chalk.dim(" Replace the placeholder Todo model with your app's models"));
1001
- console.log();
1002
- checkbox('Push schema and seed the database');
1003
- console.log(chalk.dim(' npm run db:push'));
1004
- console.log(chalk.dim(' # Edit prisma/seed.ts with your seed data, then:'));
1005
- console.log(chalk.dim(' npm run db:seed'));
1006
- console.log(chalk.dim(` # After re-seeding, restart the dev server to pick up fresh data:`));
1007
- console.log(chalk.dim(` # codeyam editor dev-server '{"action":"restart"}'`));
1008
- console.log();
1009
- console.log(chalk.yellow(' IMPORTANT: When adding new required columns to existing tables,'));
1010
- console.log(chalk.yellow(' provide a @default(...) value so `db push` can fill existing rows.'));
1011
- console.log(chalk.dim(' Example: userId String @default("anonymous") existing rows get "anonymous"'));
1012
- console.log(chalk.dim(' Without a default, Prisma requires --force-reset which drops ALL data.'));
1013
- console.log(chalk.dim(' NEVER use --force-reset it is blocked in this environment.'));
1014
- console.log();
1015
- console.log(chalk.dim(' See DATABASE.md for Prisma patterns and important warnings.'));
1016
- console.log(chalk.dim(' Key: import { prisma } from "@/app/lib/prisma" in API routes.'));
1017
- console.log(chalk.dim(' Key: Seed scripts must use the adapter pattern (see prisma/seed.ts).'));
1018
- console.log();
1067
+ if (ctx.isExpo) {
1068
+ // Expo: no database, use AsyncStorage + theme
1069
+ checkbox('Define your data types in `lib/types.ts`');
1070
+ console.log(chalk.dim(" Create TypeScript interfaces for your app's data models"));
1071
+ console.log();
1072
+ checkbox('Set up initial data using the storage helper in `lib/storage.ts`');
1073
+ console.log(chalk.dim(' import { storage } from "@/lib/storage";'));
1074
+ console.log(chalk.dim(' await storage.set("items", [{ id: "1", title: "First item" }]);'));
1075
+ console.log();
1076
+ console.log(chalk.dim(' Read MOBILE_SETUP.md for data storage, navigation, and testing patterns.'));
1077
+ console.log();
1078
+ }
1079
+ else if (ctx.isChromeExt) {
1080
+ // Chrome Extension: use chrome.storage
1081
+ checkbox('Set up data storage using chrome.storage (with localStorage fallback for dev)');
1082
+ console.log();
1083
+ console.log(chalk.dim(' Read EXTENSION_SETUP.md for storage, messaging, and manifest patterns.'));
1084
+ console.log();
1085
+ }
1086
+ else {
1087
+ // Next.js: Prisma + database
1088
+ checkbox('Define your data models in `prisma/schema.prisma`');
1089
+ console.log(chalk.dim(" Replace the placeholder Todo model with your app's models"));
1090
+ console.log();
1091
+ checkbox('Push schema and seed the database');
1092
+ console.log(chalk.dim(' npm run db:push'));
1093
+ console.log(chalk.dim(' # Edit prisma/seed.ts with your seed data, then:'));
1094
+ console.log(chalk.dim(' npm run db:seed'));
1095
+ console.log(chalk.dim(` # After re-seeding, restart the dev server to pick up fresh data:`));
1096
+ console.log(chalk.dim(` # codeyam editor dev-server '{"action":"restart"}'`));
1097
+ console.log();
1098
+ console.log(chalk.yellow(' IMPORTANT: When adding new required columns to existing tables,'));
1099
+ console.log(chalk.yellow(' provide a @default(...) value so `db push` can fill existing rows.'));
1100
+ console.log(chalk.dim(' Example: userId String @default("anonymous") — existing rows get "anonymous"'));
1101
+ console.log(chalk.dim(' Without a default, Prisma requires --force-reset which drops ALL data.'));
1102
+ console.log(chalk.dim(' NEVER use --force-reset — it is blocked in this environment.'));
1103
+ console.log();
1104
+ console.log(chalk.dim(' See DATABASE.md for Prisma patterns and important warnings.'));
1105
+ console.log(chalk.dim(' Key: import { prisma } from "@/app/lib/prisma" in API routes.'));
1106
+ console.log(chalk.dim(' Key: Seed scripts must use the adapter pattern (see prisma/seed.ts).'));
1107
+ console.log();
1108
+ }
1019
1109
  printDataStructureInstructions();
1020
1110
  }
1021
1111
  else {
@@ -1057,26 +1147,42 @@ function printStep3(root, feature) {
1057
1147
  if (isResuming) {
1058
1148
  printResumptionHeader(3);
1059
1149
  }
1150
+ const ctx = getTechStackContext(root);
1060
1151
  console.log('Build fast with real data. Prioritize speed over quality.');
1061
1152
  console.log();
1062
1153
  console.log(chalk.bold('Checklist:'));
1063
- checkbox('Create API routes that read from the database via Prisma');
1064
- if (!projectExists) {
1065
- checkbox('Seed the database with demo data');
1066
- checkbox('Create `.codeyam/seed-adapter.ts` so CodeYam can seed the database for scenarios');
1067
- console.log(chalk.dim(' The seed adapter reads a JSON file (path passed as CLI arg), wipes tables, inserts rows.'));
1068
- console.log(chalk.dim(" Use the project's own ORM (Prisma, Drizzle, etc.). See template for example."));
1069
- console.log(chalk.dim(' Run with: npx tsx .codeyam/seed-adapter.ts <path-to-seed-data.json>'));
1070
- console.log();
1071
- console.log(chalk.bold.cyan('Make seed data visually rich:'));
1072
- console.log(chalk.cyan(' • Use real placeholder images from Unsplash (https://images.unsplash.com/photo-<id>?w=400&h=300&fit=crop)'));
1073
- console.log(chalk.cyan(' • Use avatar services like i.pravatar.cc for user profile photos'));
1074
- console.log(chalk.cyan(' • Write realistic, varied content — not "Item 1", "Item 2", "Test Description"'));
1075
- console.log(chalk.cyan(' • Include different text lengths, categories, dates, and statuses'));
1076
- console.log(chalk.cyan(' • Rich seed data makes the prototype look real and surfaces layout issues early'));
1154
+ if (ctx.isExpo) {
1155
+ checkbox('Build screens that read from AsyncStorage via `lib/storage.ts` or fetch from APIs');
1156
+ if (!projectExists) {
1157
+ checkbox('Populate initial data in AsyncStorage for development');
1158
+ console.log(chalk.dim(' import { storage } from "@/lib/storage";'));
1159
+ console.log(chalk.dim(' await storage.set("items", [{ id: "1", title: "Buy groceries" }]);'));
1160
+ console.log();
1161
+ console.log(chalk.bold.cyan('Make data visually rich:'));
1162
+ console.log(chalk.cyan(' Write realistic, varied content — not "Item 1", "Item 2", "Test Description"'));
1163
+ console.log(chalk.cyan(' • Include different text lengths, categories, dates, and statuses'));
1164
+ console.log(chalk.cyan(' • Rich data makes the prototype look real and surfaces layout issues early'));
1165
+ }
1166
+ }
1167
+ else {
1168
+ checkbox('Create API routes that read from the database via Prisma');
1169
+ if (!projectExists) {
1170
+ checkbox('Seed the database with demo data');
1171
+ checkbox('Create `.codeyam/seed-adapter.ts` so CodeYam can seed the database for scenarios');
1172
+ console.log(chalk.dim(' The seed adapter reads a JSON file (path passed as CLI arg), wipes tables, inserts rows.'));
1173
+ console.log(chalk.dim(" Use the project's own ORM (Prisma, Drizzle, etc.). See template for example."));
1174
+ console.log(chalk.dim(' Run with: npx tsx .codeyam/seed-adapter.ts <path-to-seed-data.json>'));
1175
+ console.log();
1176
+ console.log(chalk.bold.cyan('Make seed data visually rich:'));
1177
+ console.log(chalk.cyan(' • Use real placeholder images from Unsplash (https://images.unsplash.com/photo-<id>?w=400&h=300&fit=crop)'));
1178
+ console.log(chalk.cyan(' • Use avatar services like i.pravatar.cc for user profile photos'));
1179
+ console.log(chalk.cyan(' • Write realistic, varied content — not "Item 1", "Item 2", "Test Description"'));
1180
+ console.log(chalk.cyan(' • Include different text lengths, categories, dates, and statuses'));
1181
+ console.log(chalk.cyan(' • Rich seed data makes the prototype look real and surfaces layout issues early'));
1182
+ }
1077
1183
  }
1078
1184
  checkbox('Verify the dev server shows the changes');
1079
- checkbox('If the feature involves auth, email, payments, or other common patterns: read FEATURE_PATTERNS.md');
1185
+ checkbox(`If the feature involves auth, email, payments, or other common patterns: read ${ctx.patternsFile}`);
1080
1186
  // Responsive design guidance when building a mobile-responsive web app
1081
1187
  if (prevState?.appFormats?.includes('mobile-responsive-web-app')) {
1082
1188
  console.log();
@@ -1093,16 +1199,29 @@ function printStep3(root, feature) {
1093
1199
  console.log();
1094
1200
  console.log(designSystem);
1095
1201
  console.log();
1096
- checkbox('Define ALL design tokens as CSS custom properties in globals.css — not just colors');
1097
- console.log(chalk.dim(' Colors: --bg-surface, --text-primary, --accent-green-a, etc.'));
1098
- console.log(chalk.dim(' Typography: --text-xs, --text-sm, --text-lg, --text-2xl (font-size values)'));
1099
- console.log(chalk.dim(' Font weights: --font-weight-normal, --font-weight-medium, --font-weight-semibold'));
1100
- console.log(chalk.dim(' Spacing: --spacing-xs, --spacing-sm, --spacing-md, --spacing-lg, etc.'));
1101
- console.log(chalk.dim(' Border radius, shadows, transitions — every value the design system defines.'));
1102
- checkbox('Reference tokens from components — ZERO hardcoded px values for font-size, spacing, or colors');
1103
- console.log(chalk.dim(' Bad: fontSize: 14, padding: "12px 16px", gap: 8'));
1104
- console.log(chalk.dim(' Good: fontSize: "var(--text-sm)", padding: "var(--spacing-md) var(--spacing-lg)", gap: "var(--spacing-sm)"'));
1105
- console.log(chalk.dim(' This ensures the entire app updates when the design system changes.'));
1202
+ if (ctx.isExpo) {
1203
+ checkbox('Define ALL design tokens in `lib/theme.ts` — this is the single source of truth');
1204
+ console.log(chalk.dim(' Colors: theme.colors.bgSurface, theme.colors.textPrimary, etc.'));
1205
+ console.log(chalk.dim(' Typography: theme.fontSize.sm, theme.fontSize.lg, theme.fontFamily.mono'));
1206
+ console.log(chalk.dim(' Spacing: theme.spacing.sm, theme.spacing.md, theme.spacing.lg, etc.'));
1207
+ console.log(chalk.dim(' Border radius: theme.borderRadius.sm, theme.borderRadius.lg, etc.'));
1208
+ checkbox('Import theme in every component — ZERO hardcoded color strings or pixel values');
1209
+ console.log(chalk.dim(' Bad: color: "#333", fontSize: 14, padding: 12'));
1210
+ console.log(chalk.dim(' Good: color: theme.colors.textPrimary, fontSize: theme.fontSize.sm, padding: theme.spacing.md'));
1211
+ console.log(chalk.dim(' Do NOT use CSS custom properties (var(--token)) they do not work in React Native.'));
1212
+ }
1213
+ else {
1214
+ checkbox('Define ALL design tokens as CSS custom properties in globals.css — not just colors');
1215
+ console.log(chalk.dim(' Colors: --bg-surface, --text-primary, --accent-green-a, etc.'));
1216
+ console.log(chalk.dim(' Typography: --text-xs, --text-sm, --text-lg, --text-2xl (font-size values)'));
1217
+ console.log(chalk.dim(' Font weights: --font-weight-normal, --font-weight-medium, --font-weight-semibold'));
1218
+ console.log(chalk.dim(' Spacing: --spacing-xs, --spacing-sm, --spacing-md, --spacing-lg, etc.'));
1219
+ console.log(chalk.dim(' Border radius, shadows, transitions — every value the design system defines.'));
1220
+ checkbox('Reference tokens from components — ZERO hardcoded px values for font-size, spacing, or colors');
1221
+ console.log(chalk.dim(' Bad: fontSize: 14, padding: "12px 16px", gap: 8'));
1222
+ console.log(chalk.dim(' Good: fontSize: "var(--text-sm)", padding: "var(--spacing-md) var(--spacing-lg)", gap: "var(--spacing-sm)"'));
1223
+ console.log(chalk.dim(' This ensures the entire app updates when the design system changes.'));
1224
+ }
1106
1225
  }
1107
1226
  console.log();
1108
1227
  console.log(chalk.bold.cyan('Keep the preview moving:'));
@@ -1156,6 +1275,14 @@ function printStep4(root, feature) {
1156
1275
  console.log(chalk.dim(' The user is looking at the preview — a blank page means something is broken.'));
1157
1276
  console.log(chalk.dim(' If any check fails, fix the issue and re-verify before proceeding.'));
1158
1277
  console.log();
1278
+ const ctx4 = getTechStackContext(root);
1279
+ if (ctx4.isExpo) {
1280
+ console.log(chalk.magenta.bold(' EXPO WEB PREVIEW NOTE:'));
1281
+ console.log(chalk.magenta(' The preview renders via react-native-web in a browser. Some differences'));
1282
+ console.log(chalk.magenta(' from native devices are expected (fonts, SafeAreaView, shadows, Platform.OS).'));
1283
+ console.log(chalk.magenta(' The preview is for layout and data verification. Test final polish on device.'));
1284
+ console.log();
1285
+ }
1159
1286
  console.log(chalk.bold('Update README and setup script:'));
1160
1287
  checkbox('Update `README.md`: set the project name, write a one-line description, and list any setup prerequisites');
1161
1288
  checkbox('Update `npm run setup` in `package.json` if setup requires extra steps (e.g. env vars, external services)');
@@ -1268,6 +1395,7 @@ function printStep7(root, feature) {
1268
1395
  if (isResuming) {
1269
1396
  printResumptionHeader(7);
1270
1397
  }
1398
+ const ctx = getTechStackContext(root);
1271
1399
  console.log('Execute your extraction plan from step 6.');
1272
1400
  console.log();
1273
1401
  console.log(chalk.bold('Components:'));
@@ -1281,18 +1409,23 @@ function printStep7(root, feature) {
1281
1409
  console.log(chalk.dim(' Cover: typical inputs, edge cases, empty/null inputs, error conditions'));
1282
1410
  console.log(chalk.dim(' Aim for 3-8 test cases per function depending on complexity'));
1283
1411
  console.log(chalk.dim(' Hooks count as functions — useDrinks, useAuth, etc. all need test files'));
1284
- checkbox('Place test files next to source: `app/lib/drinks.ts` → `app/lib/drinks.test.ts`');
1412
+ if (ctx.isExpo) {
1413
+ checkbox('Place test files next to source but OUTSIDE `app/` (Expo Router treats all files in app/ as routes): `lib/storage.ts` → `lib/storage.test.ts`, `app/hooks/useCounter.ts` → `__tests__/hooks/useCounter.test.ts`');
1414
+ }
1415
+ else {
1416
+ checkbox('Place test files next to source: `app/lib/drinks.ts` → `app/lib/drinks.test.ts`');
1417
+ }
1285
1418
  console.log(chalk.yellow(' Tests ARE the only coverage for library functions/hooks — step 9 only captures component screenshots.'));
1286
1419
  console.log();
1287
1420
  console.log(chalk.bold('Recursive pass:'));
1288
1421
  checkbox('Re-read EVERY new file you just created — extract components from components, functions from functions');
1289
1422
  checkbox('Keep going until every file is a thin shell: just imports and composition');
1290
- console.log(chalk.yellow(' Check: does any file contain raw <div>, <span>, <h1>, <p>, <img>, or <ul>?'));
1423
+ console.log(chalk.yellow(` Check: does any file contain raw ${ctx.rawPrimitivesList}?`));
1291
1424
  console.log(chalk.yellow(' If yes → that JSX section is a component waiting to be extracted.'));
1292
1425
  console.log();
1293
1426
  console.log(chalk.bold('Verify before proceeding:'));
1294
1427
  checkbox('Run all tests and verify they pass');
1295
- checkbox('Page files contain ONLY imports + component composition — no raw HTML tags');
1428
+ checkbox(`Page files contain ONLY imports + component composition — no raw ${ctx.isExpo ? 'React Native primitives' : 'HTML tags'}`);
1296
1429
  checkbox('Every component renders ONE thing or composes sub-components — no multi-section JSX');
1297
1430
  checkbox(`Refresh the preview after each batch of extractions: \`codeyam editor preview '{"dimension":"${dim}"}'\``);
1298
1431
  printDimensionGuidance(dim, dimNames);
@@ -1353,11 +1486,12 @@ function printStep9(root, feature) {
1353
1486
  console.log(chalk.dim(' Reuse and improve existing scenarios where possible — update mock data'));
1354
1487
  console.log(chalk.dim(' to reflect current changes. Add new scenarios only for genuinely new states.'));
1355
1488
  console.log(chalk.dim(' Ensure at least one scenario clearly demonstrates what changed in this session.'));
1356
- printComponentCaptureInstructions();
1489
+ const ctx9 = getTechStackContext(root);
1490
+ printComponentCaptureInstructions(root);
1357
1491
  console.log();
1358
1492
  console.log(chalk.bold('Library Functions — run tests:'));
1359
1493
  checkbox('Run ALL test files created in step 7');
1360
- console.log(chalk.dim(' Example: npx vitest run app/lib/drinks.test.ts'));
1494
+ console.log(chalk.dim(` Example: ${ctx9.testRunCommand} app/lib/drinks.test.ts`));
1361
1495
  checkbox('Verify every test passes');
1362
1496
  checkbox('If any test fails, fix the source code and re-run');
1363
1497
  console.log();
@@ -1838,7 +1972,7 @@ function printMigrateStep3(root) {
1838
1972
  console.log(chalk.dim(' Component scenarios use isolation routes with a codeyam-capture wrapper for tight screenshots.'));
1839
1973
  console.log(chalk.dim(' These supplement the app scenarios from step 2 with focused component-level views.'));
1840
1974
  console.log();
1841
- printComponentCaptureInstructions();
1975
+ printComponentCaptureInstructions(root);
1842
1976
  console.log();
1843
1977
  migrationStopGate(3, pageName, pageIndex, totalPages);
1844
1978
  }
@@ -1981,7 +2115,7 @@ function printMigrateStep8(root) {
1981
2115
  console.log();
1982
2116
  console.log(chalk.bold('Component Scenarios:'));
1983
2117
  console.log(chalk.dim(' For each visual component extracted in step 7, create isolation routes and register scenarios.'));
1984
- printComponentCaptureInstructions();
2118
+ printComponentCaptureInstructions(root);
1985
2119
  console.log();
1986
2120
  console.log(chalk.bold('Verify:'));
1987
2121
  checkbox('Run `codeyam editor analyze-imports` to populate import graph (or `codeyam editor analyze-imports path/to/file.tsx ...` for specific files)');
@@ -2744,41 +2878,67 @@ function handleDesignSystem(designSystemId) {
2744
2878
  }
2745
2879
  function handleIsolate(componentNames) {
2746
2880
  const root = process.cwd();
2881
+ const ctx = getTechStackContext(root);
2747
2882
  if (componentNames.length === 0) {
2748
2883
  console.error(chalk.red('Usage: codeyam editor isolate "ComponentA ComponentB ..."'));
2749
2884
  process.exit(1);
2750
2885
  }
2751
2886
  const isolateDir = path.join(root, 'app', 'isolated-components');
2752
2887
  // Create layout.tsx with production guard if missing
2753
- const layoutPath = path.join(isolateDir, 'layout.tsx');
2888
+ const layoutPath = path.join(isolateDir, ctx.isExpo ? '_layout.tsx' : 'layout.tsx');
2754
2889
  if (!fs.existsSync(layoutPath)) {
2755
2890
  fs.mkdirSync(isolateDir, { recursive: true });
2756
- fs.writeFileSync(layoutPath, [
2757
- 'import { notFound } from "next/navigation";',
2758
- '',
2759
- 'export default function CaptureLayout({ children }: { children: React.ReactNode }) {',
2760
- ' if (process.env.NODE_ENV === "production") notFound();',
2761
- ' return <>{children}</>;',
2762
- '}',
2763
- '',
2764
- ].join('\n'), 'utf8');
2765
- console.log(chalk.green(`Created layout guard: app/isolated-components/layout.tsx`));
2766
- }
2767
- // Create a directory for each component
2891
+ if (ctx.isExpo) {
2892
+ fs.writeFileSync(layoutPath, [
2893
+ 'import { Redirect, Slot } from "expo-router";',
2894
+ '',
2895
+ 'export default function CaptureLayout() {',
2896
+ ' if (!__DEV__) return <Redirect href="/" />;',
2897
+ ' return <Slot />;',
2898
+ '}',
2899
+ '',
2900
+ ].join('\n'), 'utf8');
2901
+ }
2902
+ else {
2903
+ fs.writeFileSync(layoutPath, [
2904
+ 'import { notFound } from "next/navigation";',
2905
+ '',
2906
+ 'export default function CaptureLayout({ children }: { children: React.ReactNode }) {',
2907
+ ' if (process.env.NODE_ENV === "production") notFound();',
2908
+ ' return <>{children}</>;',
2909
+ '}',
2910
+ '',
2911
+ ].join('\n'), 'utf8');
2912
+ }
2913
+ console.log(chalk.green(`Created layout guard: app/isolated-components/${ctx.isExpo ? '_layout.tsx' : 'layout.tsx'}`));
2914
+ }
2915
+ // Create isolation route for each component.
2916
+ // Expo Router uses flat files (ComponentName.tsx), Next.js uses subdirectories (ComponentName/page.tsx).
2768
2917
  const created = [];
2769
2918
  const existed = [];
2770
2919
  for (const name of componentNames) {
2771
- const dir = path.join(isolateDir, name);
2772
- if (fs.existsSync(dir)) {
2773
- existed.push(name);
2920
+ if (ctx.isExpo) {
2921
+ const filePath = path.join(isolateDir, `${name}.tsx`);
2922
+ if (fs.existsSync(filePath)) {
2923
+ existed.push(name);
2924
+ }
2925
+ else {
2926
+ created.push(name);
2927
+ }
2774
2928
  }
2775
2929
  else {
2776
- fs.mkdirSync(dir, { recursive: true });
2777
- created.push(name);
2930
+ const dir = path.join(isolateDir, name);
2931
+ if (fs.existsSync(dir)) {
2932
+ existed.push(name);
2933
+ }
2934
+ else {
2935
+ fs.mkdirSync(dir, { recursive: true });
2936
+ created.push(name);
2937
+ }
2778
2938
  }
2779
2939
  }
2780
2940
  if (created.length > 0) {
2781
- console.log(chalk.green(`Created ${created.length} isolation route dir(s): ${created.join(', ')}`));
2941
+ console.log(chalk.green(`Created ${created.length} isolation route(s): ${created.join(', ')}`));
2782
2942
  }
2783
2943
  if (existed.length > 0) {
2784
2944
  console.log(chalk.dim(`Already existed: ${existed.join(', ')}`));
@@ -2832,6 +2992,12 @@ function formatApiSubcommandResult(subcommand, data) {
2832
2992
  parts.push(`scenarioId="${data.scenarioId}"`);
2833
2993
  if (data.sessionsNotified != null)
2834
2994
  parts.push(`notified=${data.sessionsNotified}`);
2995
+ // Surface the HTTP health check from the dev server
2996
+ if (data.preview) {
2997
+ parts.push(`healthy=${data.preview.healthy}`);
2998
+ if (data.preview.error)
2999
+ parts.push(`error="${data.preview.error}"`);
3000
+ }
2835
3001
  return parts.join(' ');
2836
3002
  }
2837
3003
  case 'dev-server': {
@@ -4371,7 +4537,7 @@ async function handleTemplate() {
4371
4537
  _: [],
4372
4538
  });
4373
4539
  console.log(chalk.green(' CodeYam initialized.'));
4374
- // 5. Verify config has startCommand
4540
+ // 5. Verify config has startCommand and set format-specific defaults
4375
4541
  const configPath = path.join(root, '.codeyam', 'config.json');
4376
4542
  if (fs.existsSync(configPath)) {
4377
4543
  try {
@@ -4382,6 +4548,26 @@ async function handleTemplate() {
4382
4548
  console.log(chalk.yellow(' Warning: No startCommand found in .codeyam/config.json webapps.'));
4383
4549
  console.log(chalk.dim(' You may need to add: "startCommand": { "command": "sh", "args": ["-c", "npm run dev -- --port $PORT"] }'));
4384
4550
  }
4551
+ // Store appFormats from the tech stack so the editor UI can adapt
4552
+ if (stack?.supportedFormats) {
4553
+ config.appFormats = stack.supportedFormats;
4554
+ }
4555
+ // Set mobile-first defaults for mobile-app projects
4556
+ if (stack?.supportedFormats?.includes('mobile-app')) {
4557
+ config.defaultScreenSize = {
4558
+ name: 'iPhone 16',
4559
+ width: 393,
4560
+ height: 852,
4561
+ };
4562
+ config.screenSizes = {
4563
+ 'iPhone 16': { width: 393, height: 852 },
4564
+ 'iPhone 16 Pro Max': { width: 430, height: 932 },
4565
+ 'iPhone SE': { width: 375, height: 667 },
4566
+ 'Pixel 8': { width: 412, height: 915 },
4567
+ 'iPad mini': { width: 744, height: 1133 },
4568
+ };
4569
+ }
4570
+ fs.writeFileSync(configPath, JSON.stringify(config, null, 2));
4385
4571
  }
4386
4572
  catch {
4387
4573
  // Config parse error is non-fatal
@@ -4416,7 +4602,15 @@ async function handleTemplate() {
4416
4602
  }
4417
4603
  console.log();
4418
4604
  console.log(chalk.green.bold('Project scaffolded and ready!'));
4419
- console.log(chalk.dim('Next: Define your Prisma models, push the schema, seed the database, and build your feature.'));
4605
+ if (stack?.id === 'expo-react-native') {
4606
+ console.log(chalk.dim('Next: Set up your data types, configure the theme in lib/theme.ts, and build your feature.'));
4607
+ }
4608
+ else if (stack?.id === 'chrome-extension-react') {
4609
+ console.log(chalk.dim('Next: Configure your extension manifest and build your feature.'));
4610
+ }
4611
+ else {
4612
+ console.log(chalk.dim('Next: Define your Prisma models, push the schema, seed the database, and build your feature.'));
4613
+ }
4420
4614
  }
4421
4615
  // ─── Sync subcommand ─────────────────────────────────────────────────
4422
4616
  /**