@codeyam/codeyam-cli 0.1.24 → 0.1.26

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 (62) 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 +306 -89
  4. package/codeyam-cli/src/commands/editor.js.map +1 -1
  5. package/codeyam-cli/src/utils/__tests__/editorAudit.test.js +10 -0
  6. package/codeyam-cli/src/utils/__tests__/editorAudit.test.js.map +1 -1
  7. package/codeyam-cli/src/utils/__tests__/testRunner.test.js +0 -1
  8. package/codeyam-cli/src/utils/__tests__/testRunner.test.js.map +1 -1
  9. package/codeyam-cli/src/utils/__tests__/webappDetection.test.js +6 -0
  10. package/codeyam-cli/src/utils/__tests__/webappDetection.test.js.map +1 -1
  11. package/codeyam-cli/src/utils/editorAudit.js +22 -5
  12. package/codeyam-cli/src/utils/editorAudit.js.map +1 -1
  13. package/codeyam-cli/src/utils/editorScenarios.js +1 -0
  14. package/codeyam-cli/src/utils/editorScenarios.js.map +1 -1
  15. package/codeyam-cli/src/utils/entityChangeStatus.server.js +15 -0
  16. package/codeyam-cli/src/utils/entityChangeStatus.server.js.map +1 -1
  17. package/codeyam-cli/src/utils/scenariosManifest.js +8 -2
  18. package/codeyam-cli/src/utils/scenariosManifest.js.map +1 -1
  19. package/codeyam-cli/src/utils/testResultCache.js +53 -0
  20. package/codeyam-cli/src/utils/testResultCache.js.map +1 -0
  21. package/codeyam-cli/src/utils/testResultCache.server.js +81 -0
  22. package/codeyam-cli/src/utils/testResultCache.server.js.map +1 -0
  23. package/codeyam-cli/src/utils/testResultCache.server.test.js +187 -0
  24. package/codeyam-cli/src/utils/testResultCache.server.test.js.map +1 -0
  25. package/codeyam-cli/src/utils/testResultCache.test.js +230 -0
  26. package/codeyam-cli/src/utils/testResultCache.test.js.map +1 -0
  27. package/codeyam-cli/src/utils/testRunner.js +1 -7
  28. package/codeyam-cli/src/utils/testRunner.js.map +1 -1
  29. package/codeyam-cli/src/utils/webappDetection.js +4 -2
  30. package/codeyam-cli/src/utils/webappDetection.js.map +1 -1
  31. package/codeyam-cli/src/webserver/__tests__/idleDetector.test.js +32 -0
  32. package/codeyam-cli/src/webserver/__tests__/idleDetector.test.js.map +1 -1
  33. package/codeyam-cli/src/webserver/build/client/assets/editor.entity.(_sha)-CRxPi2BB.js +96 -0
  34. package/codeyam-cli/src/webserver/build/client/assets/globals-BsGHu8WX.css +1 -0
  35. package/codeyam-cli/src/webserver/build/client/assets/{manifest-694b698a.js → manifest-9032538f.js} +1 -1
  36. package/codeyam-cli/src/webserver/build/client/assets/{root-DXjFYOxD.js → root-dKFRTYcy.js} +5 -5
  37. package/codeyam-cli/src/webserver/build/server/assets/{analysisRunner-zEYtiv0T.js → analysisRunner-B6HnVI5u.js} +1 -1
  38. package/codeyam-cli/src/webserver/build/server/assets/{index-CcHPEbhi.js → index-rV_xLS1u.js} +1 -1
  39. package/codeyam-cli/src/webserver/build/server/assets/{init-D68IyWbU.js → init-BdWDvetv.js} +1 -1
  40. package/codeyam-cli/src/webserver/build/server/assets/server-build-B_jdq5dT.js +689 -0
  41. package/codeyam-cli/src/webserver/build/server/index.js +1 -1
  42. package/codeyam-cli/src/webserver/build-info.json +5 -5
  43. package/codeyam-cli/src/webserver/idleDetector.js +12 -3
  44. package/codeyam-cli/src/webserver/idleDetector.js.map +1 -1
  45. package/codeyam-cli/src/webserver/terminalServer.js +3 -1
  46. package/codeyam-cli/src/webserver/terminalServer.js.map +1 -1
  47. package/codeyam-cli/templates/expo-react-native/MOBILE_SETUP.md +119 -5
  48. package/codeyam-cli/templates/expo-react-native/__tests__/.gitkeep +0 -0
  49. package/codeyam-cli/templates/expo-react-native/app/_layout.tsx +3 -2
  50. package/codeyam-cli/templates/expo-react-native/app/index.tsx +36 -0
  51. package/codeyam-cli/templates/expo-react-native/global.css +7 -0
  52. package/codeyam-cli/templates/expo-react-native/lib/theme.ts +73 -0
  53. package/codeyam-cli/templates/expo-react-native/package.json +16 -6
  54. package/codeyam-cli/templates/isolation-route/expo-router.tsx.template +54 -0
  55. package/codeyam-cli/templates/skills/codeyam-editor/SKILL.md +15 -0
  56. package/package.json +1 -1
  57. package/codeyam-cli/src/webserver/build/client/assets/editor.entity.(_sha)-oBrbke_R.js +0 -96
  58. package/codeyam-cli/src/webserver/build/client/assets/globals-oyPmV37k.css +0 -1
  59. package/codeyam-cli/src/webserver/build/server/assets/server-build-Cxzo0Zp2.js +0 -688
  60. package/codeyam-cli/templates/expo-react-native/app/(tabs)/_layout.tsx +0 -33
  61. package/codeyam-cli/templates/expo-react-native/app/(tabs)/index.tsx +0 -12
  62. package/codeyam-cli/templates/expo-react-native/app/(tabs)/settings.tsx +0 -12
@@ -1,10 +1,10 @@
1
1
  {
2
- "buildTimestamp": "2026-03-26T12:55:43.726Z",
3
- "buildTime": 1774529743726,
4
- "gitCommit": "2c142d4185f95cc4ef22693e7f0cad3a09f021d4",
2
+ "buildTimestamp": "2026-03-27T20:00:52.922Z",
3
+ "buildTime": 1774641652922,
4
+ "gitCommit": "2a3a412834b1cc8d46883263d3695bd29b8157dc",
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": 1262,
8
+ "semanticVersion": "0.1.1262",
9
+ "version": "0.1.1262 (2026-03-27T20:00+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, 8:00:52 PM] > codeyam-combo@1.0.0 mergeDependencies
3
+ [3/27/2026, 8:00:52 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, 8:00:52 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
@@ -267,6 +305,10 @@ function printAppScenarioInstructions(pageName, route) {
267
305
  console.log(chalk.dim(' Example: "Page - Full Data", "Page - Empty State", "Page - Error State"'));
268
306
  }
269
307
  console.log();
308
+ checkbox('Ensure every page file used in app scenarios has a glossary entry with its filePath:');
309
+ console.log(chalk.dim(' The register command validates pageFilePath against the glossary — add missing pages now.'));
310
+ console.log(chalk.dim(' Example: {"name":"CounterScreen","filePath":"app/(tabs)/index.tsx","type":"page","description":"Main counter page"}'));
311
+ console.log();
270
312
  checkbox('Register each scenario with type "application", the real page URL, and pageFilePath:');
271
313
  console.log(chalk.dim(` codeyam editor register '{"name":"${pageName || 'Page'} - Full Data",`));
272
314
  console.log(chalk.dim(` "type":"application","url":"${route || '/'}","pageFilePath":"src/path/to/Page.tsx",...}'`));
@@ -281,7 +323,12 @@ function printAppScenarioInstructions(pageName, route) {
281
323
  console.log(chalk.dim(' For external APIs: also add "externalApis":{"GET https://...":{"body":[...],"status":200}}'));
282
324
  }
283
325
  else {
326
+ const appCtx = getTechStackContext(root);
284
327
  checkbox('Include data in every app scenario — without it the page will be empty:');
328
+ if (appCtx.isExpo) {
329
+ console.log(chalk.dim(' Use "localStorage":{"items":"[...]"} to pre-populate AsyncStorage (values are JSON strings)'));
330
+ console.log(chalk.dim(" AsyncStorage uses localStorage on web — CodeYam's injection works automatically."));
331
+ }
285
332
  console.log(chalk.dim(' Use "mockData":{"routes":{"/api/...":{"body":[...]}}} to mock API responses'));
286
333
  console.log(chalk.dim(' For external APIs: add "externalApis":{"GET https://...":{"body":[...],"status":200}}'));
287
334
  }
@@ -356,10 +403,18 @@ function printExtractionPlanInstructions() {
356
403
  * Shared component capture instructions used by editor Step 7 and migration Steps 3/8.
357
404
  * Prints the full isolation route setup, codeyam-capture wrapper, register command, and error checking.
358
405
  */
359
- function printComponentCaptureInstructions() {
360
- 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'));
362
- console.log(chalk.dim(' a directory per component. List ALL components that need isolation routes.'));
406
+ function printComponentCaptureInstructions(root) {
407
+ const ctx = root ? getTechStackContext(root) : undefined;
408
+ const isExpo = ctx?.isExpo ?? false;
409
+ checkbox('Set up isolation routes: `codeyam editor isolate ComponentA ComponentB ...`');
410
+ if (isExpo) {
411
+ console.log(chalk.dim(' This creates app/isolated-components/_layout.tsx (with __DEV__ guard).'));
412
+ console.log(chalk.dim(' You then create a flat .tsx file per component (NOT subdirectories — Expo Router uses flat files).'));
413
+ }
414
+ else {
415
+ console.log(chalk.dim(` This creates app/isolated-components/layout.tsx (with notFound() guard) and`));
416
+ console.log(chalk.dim(' a directory per component. List ALL components that need isolation routes.'));
417
+ }
363
418
  checkbox('For each visual component:');
364
419
  console.log(chalk.dim(' 1. Read the source AND find where it is used in the app to understand:'));
365
420
  console.log(chalk.dim(' — Props/interface'));
@@ -370,14 +425,28 @@ function printComponentCaptureInstructions() {
370
425
  console.log(chalk.dim(' — Different visual states: loading, error, disabled, selected, hover'));
371
426
  console.log(chalk.dim(' — Boundary values: single item vs many, min/max ratings, very long names'));
372
427
  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'));
428
+ if (isExpo) {
429
+ console.log(chalk.dim(' Expo: app/isolated-components/ComponentName.tsx → /isolated-components/ComponentName'));
430
+ console.log(chalk.dim(' Use useLocalSearchParams() from expo-router to read ?s=ScenarioName.'));
431
+ }
432
+ else {
433
+ console.log(chalk.dim(' Remix: app/routes/isolated-components.ComponentName.tsx → /isolated-components/ComponentName'));
434
+ console.log(chalk.dim(' Next.js: app/isolated-components/ComponentName/page.tsx → /isolated-components/ComponentName'));
435
+ }
375
436
  console.log(chalk.dim(' The route defines a `scenarios` object mapping scenario names to props,'));
376
437
  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'));
438
+ if (isExpo) {
439
+ console.log(chalk.dim(' Wrap the component in a capture container with nativeID="codeyam-capture":'));
440
+ console.log(chalk.dim(' <View nativeID="codeyam-capture" style={{ display:"flex" }}>'));
441
+ }
442
+ else {
443
+ console.log(chalk.dim(' Wrap the component in a capture container with id="codeyam-capture":'));
444
+ console.log(chalk.dim(' <div id="codeyam-capture" style={{ display:"inline-block" }}>'));
445
+ }
446
+ console.log(chalk.dim(isExpo
447
+ ? ' <View style={{ width:"100%", maxWidth:... }}> ← match the app\'s container width'
448
+ : ' <div style={{ width:"100%", maxWidth:"..." }}> ← match the app\'s container width'));
449
+ console.log(chalk.dim(' e.g. card in a 3-col grid → maxWidth: 384, full-width component → omit maxWidth'));
381
450
  console.log(chalk.dim(' The screenshot captures just this wrapper, so the component fills the image.'));
382
451
  console.log(chalk.dim(' Center the wrapper on the page (flexbox center both axes) and set a page background'));
383
452
  console.log(chalk.dim(' color that matches where the component normally appears (e.g. white for light UIs).'));
@@ -746,8 +815,8 @@ function printSetup(root) {
746
815
  console.log(chalk.bold('Tech Stack Selection:'));
747
816
  console.log(chalk.dim(' Based on the selected formats, present ONLY the matching tech stacks.'));
748
817
  console.log(chalk.dim(' A stack matches if ANY of the user\'s selected formats appears in its "Supports" list.'));
749
- console.log(chalk.dim(' Show ALL matching stacks even if there is only one. Mark the recommended option.'));
750
- console.log(chalk.dim(' Use AskUserQuestion to let the user pick one.'));
818
+ console.log(chalk.dim(' If only ONE stack matches, confirm it directly (e.g. "Expo + React Native is the only stack for mobile apps — using that.").'));
819
+ console.log(chalk.dim(' If MULTIPLE stacks match, use AskUserQuestion to let the user pick one.'));
751
820
  console.log();
752
821
  console.log(chalk.bold(' Available Tech Stacks:'));
753
822
  for (const stack of TECH_STACKS) {
@@ -780,7 +849,7 @@ function printSetup(root) {
780
849
  console.log(chalk.yellow(' ( ) Mobile') + chalk.dim(' — 375 × 667'));
781
850
  console.log(chalk.yellow(' ( ) Custom') + chalk.dim(' — ask for width × height'));
782
851
  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).'));
852
+ 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
853
  console.log(chalk.dim(' If only one obvious choice, confirm it rather than asking.'));
785
854
  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
855
  console.log();
@@ -989,33 +1058,64 @@ function printStep2(root, feature) {
989
1058
  }
990
1059
  console.log('Get the project ready to build.');
991
1060
  console.log();
1061
+ const ctx = getTechStackContext(root);
992
1062
  // If no project exists yet, include scaffolding instructions first
993
1063
  if (!projectExists) {
994
1064
  console.log(chalk.bold('Scaffold the project:'));
995
1065
  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,'));
1066
+ if (ctx.isExpo) {
1067
+ console.log(chalk.dim(' This copies the Expo + React Native template, runs npm install,'));
1068
+ }
1069
+ else if (ctx.isChromeExt) {
1070
+ console.log(chalk.dim(' This copies the Chrome Extension + React template, runs npm install,'));
1071
+ }
1072
+ else {
1073
+ console.log(chalk.dim(' This copies the Next.js + Prisma 7 + SQLite template, runs npm install,'));
1074
+ }
997
1075
  console.log(chalk.dim(' initializes git, runs codeyam init, and refreshes the editor — all in one command.'));
998
1076
  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();
1077
+ if (ctx.isExpo) {
1078
+ // Expo: no database, use AsyncStorage + theme
1079
+ checkbox('Define your data types in `lib/types.ts`');
1080
+ console.log(chalk.dim(" Create TypeScript interfaces for your app's data models"));
1081
+ console.log();
1082
+ checkbox('Set up initial data using the storage helper in `lib/storage.ts`');
1083
+ console.log(chalk.dim(' import { storage } from "@/lib/storage";'));
1084
+ console.log(chalk.dim(' await storage.set("items", [{ id: "1", title: "First item" }]);'));
1085
+ console.log();
1086
+ console.log(chalk.dim(' Read MOBILE_SETUP.md for data storage, navigation, and testing patterns.'));
1087
+ console.log();
1088
+ }
1089
+ else if (ctx.isChromeExt) {
1090
+ // Chrome Extension: use chrome.storage
1091
+ checkbox('Set up data storage using chrome.storage (with localStorage fallback for dev)');
1092
+ console.log();
1093
+ console.log(chalk.dim(' Read EXTENSION_SETUP.md for storage, messaging, and manifest patterns.'));
1094
+ console.log();
1095
+ }
1096
+ else {
1097
+ // Next.js: Prisma + database
1098
+ checkbox('Define your data models in `prisma/schema.prisma`');
1099
+ console.log(chalk.dim(" Replace the placeholder Todo model with your app's models"));
1100
+ console.log();
1101
+ checkbox('Push schema and seed the database');
1102
+ console.log(chalk.dim(' npm run db:push'));
1103
+ console.log(chalk.dim(' # Edit prisma/seed.ts with your seed data, then:'));
1104
+ console.log(chalk.dim(' npm run db:seed'));
1105
+ console.log(chalk.dim(` # After re-seeding, restart the dev server to pick up fresh data:`));
1106
+ console.log(chalk.dim(` # codeyam editor dev-server '{"action":"restart"}'`));
1107
+ console.log();
1108
+ console.log(chalk.yellow(' IMPORTANT: When adding new required columns to existing tables,'));
1109
+ console.log(chalk.yellow(' provide a @default(...) value so `db push` can fill existing rows.'));
1110
+ console.log(chalk.dim(' Example: userId String @default("anonymous") — existing rows get "anonymous"'));
1111
+ console.log(chalk.dim(' Without a default, Prisma requires --force-reset which drops ALL data.'));
1112
+ console.log(chalk.dim(' NEVER use --force-reset — it is blocked in this environment.'));
1113
+ console.log();
1114
+ console.log(chalk.dim(' See DATABASE.md for Prisma patterns and important warnings.'));
1115
+ console.log(chalk.dim(' Key: import { prisma } from "@/app/lib/prisma" in API routes.'));
1116
+ console.log(chalk.dim(' Key: Seed scripts must use the adapter pattern (see prisma/seed.ts).'));
1117
+ console.log();
1118
+ }
1019
1119
  printDataStructureInstructions();
1020
1120
  }
1021
1121
  else {
@@ -1057,26 +1157,42 @@ function printStep3(root, feature) {
1057
1157
  if (isResuming) {
1058
1158
  printResumptionHeader(3);
1059
1159
  }
1160
+ const ctx = getTechStackContext(root);
1060
1161
  console.log('Build fast with real data. Prioritize speed over quality.');
1061
1162
  console.log();
1062
1163
  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'));
1164
+ if (ctx.isExpo) {
1165
+ checkbox('Build screens that read from AsyncStorage via `lib/storage.ts` or fetch from APIs');
1166
+ if (!projectExists) {
1167
+ checkbox('Populate initial data in AsyncStorage for development');
1168
+ console.log(chalk.dim(' import { storage } from "@/lib/storage";'));
1169
+ console.log(chalk.dim(' await storage.set("items", [{ id: "1", title: "Buy groceries" }]);'));
1170
+ console.log();
1171
+ console.log(chalk.bold.cyan('Make data visually rich:'));
1172
+ console.log(chalk.cyan(' Write realistic, varied content — not "Item 1", "Item 2", "Test Description"'));
1173
+ console.log(chalk.cyan(' • Include different text lengths, categories, dates, and statuses'));
1174
+ console.log(chalk.cyan(' • Rich data makes the prototype look real and surfaces layout issues early'));
1175
+ }
1176
+ }
1177
+ else {
1178
+ checkbox('Create API routes that read from the database via Prisma');
1179
+ if (!projectExists) {
1180
+ checkbox('Seed the database with demo data');
1181
+ checkbox('Create `.codeyam/seed-adapter.ts` so CodeYam can seed the database for scenarios');
1182
+ console.log(chalk.dim(' The seed adapter reads a JSON file (path passed as CLI arg), wipes tables, inserts rows.'));
1183
+ console.log(chalk.dim(" Use the project's own ORM (Prisma, Drizzle, etc.). See template for example."));
1184
+ console.log(chalk.dim(' Run with: npx tsx .codeyam/seed-adapter.ts <path-to-seed-data.json>'));
1185
+ console.log();
1186
+ console.log(chalk.bold.cyan('Make seed data visually rich:'));
1187
+ console.log(chalk.cyan(' • Use real placeholder images from Unsplash (https://images.unsplash.com/photo-<id>?w=400&h=300&fit=crop)'));
1188
+ console.log(chalk.cyan(' • Use avatar services like i.pravatar.cc for user profile photos'));
1189
+ console.log(chalk.cyan(' • Write realistic, varied content — not "Item 1", "Item 2", "Test Description"'));
1190
+ console.log(chalk.cyan(' • Include different text lengths, categories, dates, and statuses'));
1191
+ console.log(chalk.cyan(' • Rich seed data makes the prototype look real and surfaces layout issues early'));
1192
+ }
1077
1193
  }
1078
1194
  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');
1195
+ checkbox(`If the feature involves auth, email, payments, or other common patterns: read ${ctx.patternsFile}`);
1080
1196
  // Responsive design guidance when building a mobile-responsive web app
1081
1197
  if (prevState?.appFormats?.includes('mobile-responsive-web-app')) {
1082
1198
  console.log();
@@ -1093,16 +1209,29 @@ function printStep3(root, feature) {
1093
1209
  console.log();
1094
1210
  console.log(designSystem);
1095
1211
  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.'));
1212
+ if (ctx.isExpo) {
1213
+ checkbox('Define ALL design tokens in `lib/theme.ts` — this is the single source of truth');
1214
+ console.log(chalk.dim(' Colors: theme.colors.bgSurface, theme.colors.textPrimary, etc.'));
1215
+ console.log(chalk.dim(' Typography: theme.fontSize.sm, theme.fontSize.lg, theme.fontFamily.mono'));
1216
+ console.log(chalk.dim(' Spacing: theme.spacing.sm, theme.spacing.md, theme.spacing.lg, etc.'));
1217
+ console.log(chalk.dim(' Border radius: theme.borderRadius.sm, theme.borderRadius.lg, etc.'));
1218
+ checkbox('Import theme in every component — ZERO hardcoded color strings or pixel values');
1219
+ console.log(chalk.dim(' Bad: color: "#333", fontSize: 14, padding: 12'));
1220
+ console.log(chalk.dim(' Good: color: theme.colors.textPrimary, fontSize: theme.fontSize.sm, padding: theme.spacing.md'));
1221
+ console.log(chalk.dim(' Do NOT use CSS custom properties (var(--token)) they do not work in React Native.'));
1222
+ }
1223
+ else {
1224
+ checkbox('Define ALL design tokens as CSS custom properties in globals.css — not just colors');
1225
+ console.log(chalk.dim(' Colors: --bg-surface, --text-primary, --accent-green-a, etc.'));
1226
+ console.log(chalk.dim(' Typography: --text-xs, --text-sm, --text-lg, --text-2xl (font-size values)'));
1227
+ console.log(chalk.dim(' Font weights: --font-weight-normal, --font-weight-medium, --font-weight-semibold'));
1228
+ console.log(chalk.dim(' Spacing: --spacing-xs, --spacing-sm, --spacing-md, --spacing-lg, etc.'));
1229
+ console.log(chalk.dim(' Border radius, shadows, transitions — every value the design system defines.'));
1230
+ checkbox('Reference tokens from components — ZERO hardcoded px values for font-size, spacing, or colors');
1231
+ console.log(chalk.dim(' Bad: fontSize: 14, padding: "12px 16px", gap: 8'));
1232
+ console.log(chalk.dim(' Good: fontSize: "var(--text-sm)", padding: "var(--spacing-md) var(--spacing-lg)", gap: "var(--spacing-sm)"'));
1233
+ console.log(chalk.dim(' This ensures the entire app updates when the design system changes.'));
1234
+ }
1106
1235
  }
1107
1236
  console.log();
1108
1237
  console.log(chalk.bold.cyan('Keep the preview moving:'));
@@ -1156,6 +1285,14 @@ function printStep4(root, feature) {
1156
1285
  console.log(chalk.dim(' The user is looking at the preview — a blank page means something is broken.'));
1157
1286
  console.log(chalk.dim(' If any check fails, fix the issue and re-verify before proceeding.'));
1158
1287
  console.log();
1288
+ const ctx4 = getTechStackContext(root);
1289
+ if (ctx4.isExpo) {
1290
+ console.log(chalk.magenta.bold(' EXPO WEB PREVIEW NOTE:'));
1291
+ console.log(chalk.magenta(' The preview renders via react-native-web in a browser. Some differences'));
1292
+ console.log(chalk.magenta(' from native devices are expected (fonts, SafeAreaView, shadows, Platform.OS).'));
1293
+ console.log(chalk.magenta(' The preview is for layout and data verification. Test final polish on device.'));
1294
+ console.log();
1295
+ }
1159
1296
  console.log(chalk.bold('Update README and setup script:'));
1160
1297
  checkbox('Update `README.md`: set the project name, write a one-line description, and list any setup prerequisites');
1161
1298
  checkbox('Update `npm run setup` in `package.json` if setup requires extra steps (e.g. env vars, external services)');
@@ -1268,6 +1405,7 @@ function printStep7(root, feature) {
1268
1405
  if (isResuming) {
1269
1406
  printResumptionHeader(7);
1270
1407
  }
1408
+ const ctx = getTechStackContext(root);
1271
1409
  console.log('Execute your extraction plan from step 6.');
1272
1410
  console.log();
1273
1411
  console.log(chalk.bold('Components:'));
@@ -1281,18 +1419,24 @@ function printStep7(root, feature) {
1281
1419
  console.log(chalk.dim(' Cover: typical inputs, edge cases, empty/null inputs, error conditions'));
1282
1420
  console.log(chalk.dim(' Aim for 3-8 test cases per function depending on complexity'));
1283
1421
  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`');
1422
+ console.log(chalk.dim(' Wrap all tests in a describe("FunctionName", ...) block — the audit matches on this name'));
1423
+ if (ctx.isExpo) {
1424
+ 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`');
1425
+ }
1426
+ else {
1427
+ checkbox('Place test files next to source: `app/lib/drinks.ts` → `app/lib/drinks.test.ts`');
1428
+ }
1285
1429
  console.log(chalk.yellow(' Tests ARE the only coverage for library functions/hooks — step 9 only captures component screenshots.'));
1286
1430
  console.log();
1287
1431
  console.log(chalk.bold('Recursive pass:'));
1288
1432
  checkbox('Re-read EVERY new file you just created — extract components from components, functions from functions');
1289
1433
  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>?'));
1434
+ console.log(chalk.yellow(` Check: does any file contain raw ${ctx.rawPrimitivesList}?`));
1291
1435
  console.log(chalk.yellow(' If yes → that JSX section is a component waiting to be extracted.'));
1292
1436
  console.log();
1293
1437
  console.log(chalk.bold('Verify before proceeding:'));
1294
1438
  checkbox('Run all tests and verify they pass');
1295
- checkbox('Page files contain ONLY imports + component composition — no raw HTML tags');
1439
+ checkbox(`Page files contain ONLY imports + component composition — no raw ${ctx.isExpo ? 'React Native primitives' : 'HTML tags'}`);
1296
1440
  checkbox('Every component renders ONE thing or composes sub-components — no multi-section JSX');
1297
1441
  checkbox(`Refresh the preview after each batch of extractions: \`codeyam editor preview '{"dimension":"${dim}"}'\``);
1298
1442
  printDimensionGuidance(dim, dimNames);
@@ -1353,11 +1497,12 @@ function printStep9(root, feature) {
1353
1497
  console.log(chalk.dim(' Reuse and improve existing scenarios where possible — update mock data'));
1354
1498
  console.log(chalk.dim(' to reflect current changes. Add new scenarios only for genuinely new states.'));
1355
1499
  console.log(chalk.dim(' Ensure at least one scenario clearly demonstrates what changed in this session.'));
1356
- printComponentCaptureInstructions();
1500
+ const ctx9 = getTechStackContext(root);
1501
+ printComponentCaptureInstructions(root);
1357
1502
  console.log();
1358
1503
  console.log(chalk.bold('Library Functions — run tests:'));
1359
1504
  checkbox('Run ALL test files created in step 7');
1360
- console.log(chalk.dim(' Example: npx vitest run app/lib/drinks.test.ts'));
1505
+ console.log(chalk.dim(` Example: ${ctx9.testRunCommand} app/lib/drinks.test.ts`));
1361
1506
  checkbox('Verify every test passes');
1362
1507
  checkbox('If any test fails, fix the source code and re-run');
1363
1508
  console.log();
@@ -1838,7 +1983,7 @@ function printMigrateStep3(root) {
1838
1983
  console.log(chalk.dim(' Component scenarios use isolation routes with a codeyam-capture wrapper for tight screenshots.'));
1839
1984
  console.log(chalk.dim(' These supplement the app scenarios from step 2 with focused component-level views.'));
1840
1985
  console.log();
1841
- printComponentCaptureInstructions();
1986
+ printComponentCaptureInstructions(root);
1842
1987
  console.log();
1843
1988
  migrationStopGate(3, pageName, pageIndex, totalPages);
1844
1989
  }
@@ -1981,7 +2126,7 @@ function printMigrateStep8(root) {
1981
2126
  console.log();
1982
2127
  console.log(chalk.bold('Component Scenarios:'));
1983
2128
  console.log(chalk.dim(' For each visual component extracted in step 7, create isolation routes and register scenarios.'));
1984
- printComponentCaptureInstructions();
2129
+ printComponentCaptureInstructions(root);
1985
2130
  console.log();
1986
2131
  console.log(chalk.bold('Verify:'));
1987
2132
  checkbox('Run `codeyam editor analyze-imports` to populate import graph (or `codeyam editor analyze-imports path/to/file.tsx ...` for specific files)');
@@ -2744,41 +2889,78 @@ function handleDesignSystem(designSystemId) {
2744
2889
  }
2745
2890
  function handleIsolate(componentNames) {
2746
2891
  const root = process.cwd();
2892
+ const ctx = getTechStackContext(root);
2747
2893
  if (componentNames.length === 0) {
2748
2894
  console.error(chalk.red('Usage: codeyam editor isolate "ComponentA ComponentB ..."'));
2749
2895
  process.exit(1);
2750
2896
  }
2751
2897
  const isolateDir = path.join(root, 'app', 'isolated-components');
2752
- // Create layout.tsx with production guard if missing
2753
- const layoutPath = path.join(isolateDir, 'layout.tsx');
2898
+ // Create the framework-appropriate layout guard if missing.
2899
+ // Clean up wrong-framework layout from a previous CLI version.
2900
+ const layoutPath = path.join(isolateDir, ctx.isExpo ? '_layout.tsx' : 'layout.tsx');
2901
+ const wrongLayoutPath = path.join(isolateDir, ctx.isExpo ? 'layout.tsx' : '_layout.tsx');
2902
+ if (fs.existsSync(wrongLayoutPath)) {
2903
+ fs.unlinkSync(wrongLayoutPath);
2904
+ }
2754
2905
  if (!fs.existsSync(layoutPath)) {
2755
2906
  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
2907
+ if (ctx.isExpo) {
2908
+ fs.writeFileSync(layoutPath, [
2909
+ 'import { Redirect, Slot } from "expo-router";',
2910
+ '',
2911
+ 'export default function CaptureLayout() {',
2912
+ ' if (!__DEV__) return <Redirect href="/" />;',
2913
+ ' return <Slot />;',
2914
+ '}',
2915
+ '',
2916
+ ].join('\n'), 'utf8');
2917
+ }
2918
+ else {
2919
+ fs.writeFileSync(layoutPath, [
2920
+ 'import { notFound } from "next/navigation";',
2921
+ '',
2922
+ 'export default function CaptureLayout({ children }: { children: React.ReactNode }) {',
2923
+ ' if (process.env.NODE_ENV === "production") notFound();',
2924
+ ' return <>{children}</>;',
2925
+ '}',
2926
+ '',
2927
+ ].join('\n'), 'utf8');
2928
+ }
2929
+ console.log(chalk.green(`Created layout guard: app/isolated-components/${ctx.isExpo ? '_layout.tsx' : 'layout.tsx'}`));
2930
+ }
2931
+ // Create isolation route for each component.
2932
+ // Expo Router uses flat files (ComponentName.tsx), Next.js uses subdirectories (ComponentName/page.tsx).
2768
2933
  const created = [];
2769
2934
  const existed = [];
2770
2935
  for (const name of componentNames) {
2771
- const dir = path.join(isolateDir, name);
2772
- if (fs.existsSync(dir)) {
2773
- existed.push(name);
2936
+ if (ctx.isExpo) {
2937
+ const filePath = path.join(isolateDir, `${name}.tsx`);
2938
+ if (fs.existsSync(filePath)) {
2939
+ existed.push(name);
2940
+ }
2941
+ else {
2942
+ created.push(name);
2943
+ }
2774
2944
  }
2775
2945
  else {
2776
- fs.mkdirSync(dir, { recursive: true });
2777
- created.push(name);
2946
+ const dir = path.join(isolateDir, name);
2947
+ if (fs.existsSync(dir)) {
2948
+ existed.push(name);
2949
+ }
2950
+ else {
2951
+ fs.mkdirSync(dir, { recursive: true });
2952
+ created.push(name);
2953
+ }
2778
2954
  }
2779
2955
  }
2780
2956
  if (created.length > 0) {
2781
- console.log(chalk.green(`Created ${created.length} isolation route dir(s): ${created.join(', ')}`));
2957
+ if (ctx.isExpo) {
2958
+ console.log(chalk.green(`Ready for ${created.length} isolation route(s): ${created.map((n) => `app/isolated-components/${n}.tsx`).join(', ')}`));
2959
+ console.log(chalk.dim(' Create each file with useLocalSearchParams, a scenarios map, and nativeID="codeyam-capture"'));
2960
+ }
2961
+ else {
2962
+ console.log(chalk.green(`Created ${created.length} isolation route dir(s): ${created.join(', ')}`));
2963
+ }
2782
2964
  }
2783
2965
  if (existed.length > 0) {
2784
2966
  console.log(chalk.dim(`Already existed: ${existed.join(', ')}`));
@@ -2832,6 +3014,12 @@ function formatApiSubcommandResult(subcommand, data) {
2832
3014
  parts.push(`scenarioId="${data.scenarioId}"`);
2833
3015
  if (data.sessionsNotified != null)
2834
3016
  parts.push(`notified=${data.sessionsNotified}`);
3017
+ // Surface the HTTP health check from the dev server
3018
+ if (data.preview) {
3019
+ parts.push(`healthy=${data.preview.healthy}`);
3020
+ if (data.preview.error)
3021
+ parts.push(`error="${data.preview.error}"`);
3022
+ }
2835
3023
  return parts.join(' ');
2836
3024
  }
2837
3025
  case 'dev-server': {
@@ -3788,7 +3976,8 @@ async function handleAudit(options) {
3788
3976
  if (f.errorMessage) {
3789
3977
  detail += `\n ${chalk.red(f.errorMessage)}`;
3790
3978
  detail += `\n ${chalk.yellow('Note: This is NOT a test failure — the test runner itself could not execute.')}`;
3791
- detail += `\n ${chalk.yellow('Try running the test manually: npx vitest run ' + f.testFile)}`;
3979
+ const ctx = getTechStackContext(process.cwd());
3980
+ detail += `\n ${chalk.yellow('Try running the test manually: ' + ctx.testRunCommand + ' ' + f.testFile)}`;
3792
3981
  }
3793
3982
  break;
3794
3983
  case 'failing':
@@ -4371,7 +4560,7 @@ async function handleTemplate() {
4371
4560
  _: [],
4372
4561
  });
4373
4562
  console.log(chalk.green(' CodeYam initialized.'));
4374
- // 5. Verify config has startCommand
4563
+ // 5. Verify config has startCommand and set format-specific defaults
4375
4564
  const configPath = path.join(root, '.codeyam', 'config.json');
4376
4565
  if (fs.existsSync(configPath)) {
4377
4566
  try {
@@ -4382,6 +4571,26 @@ async function handleTemplate() {
4382
4571
  console.log(chalk.yellow(' Warning: No startCommand found in .codeyam/config.json webapps.'));
4383
4572
  console.log(chalk.dim(' You may need to add: "startCommand": { "command": "sh", "args": ["-c", "npm run dev -- --port $PORT"] }'));
4384
4573
  }
4574
+ // Store appFormats from the tech stack so the editor UI can adapt
4575
+ if (stack?.supportedFormats) {
4576
+ config.appFormats = stack.supportedFormats;
4577
+ }
4578
+ // Set mobile-first defaults for mobile-app projects
4579
+ if (stack?.supportedFormats?.includes('mobile-app')) {
4580
+ config.defaultScreenSize = {
4581
+ name: 'iPhone 16',
4582
+ width: 393,
4583
+ height: 852,
4584
+ };
4585
+ config.screenSizes = {
4586
+ 'iPhone 16': { width: 393, height: 852 },
4587
+ 'iPhone 16 Pro Max': { width: 430, height: 932 },
4588
+ 'iPhone SE': { width: 375, height: 667 },
4589
+ 'Pixel 8': { width: 412, height: 915 },
4590
+ 'iPad mini': { width: 744, height: 1133 },
4591
+ };
4592
+ }
4593
+ fs.writeFileSync(configPath, JSON.stringify(config, null, 2));
4385
4594
  }
4386
4595
  catch {
4387
4596
  // Config parse error is non-fatal
@@ -4416,7 +4625,15 @@ async function handleTemplate() {
4416
4625
  }
4417
4626
  console.log();
4418
4627
  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.'));
4628
+ if (stack?.id === 'expo-react-native') {
4629
+ console.log(chalk.dim('Next: Set up your data types, configure the theme in lib/theme.ts, and build your feature.'));
4630
+ }
4631
+ else if (stack?.id === 'chrome-extension-react') {
4632
+ console.log(chalk.dim('Next: Configure your extension manifest and build your feature.'));
4633
+ }
4634
+ else {
4635
+ console.log(chalk.dim('Next: Define your Prisma models, push the schema, seed the database, and build your feature.'));
4636
+ }
4420
4637
  }
4421
4638
  // ─── Sync subcommand ─────────────────────────────────────────────────
4422
4639
  /**