@codeyam/codeyam-cli 0.1.9 → 0.1.11

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 (87) hide show
  1. package/analyzer-template/.build-info.json +8 -8
  2. package/analyzer-template/log.txt +3 -3
  3. package/analyzer-template/packages/database/src/lib/kysely/tables/editorScenariosTable.ts +45 -0
  4. package/analyzer-template/packages/github/dist/database/src/lib/kysely/tables/editorScenariosTable.d.ts +2 -0
  5. package/analyzer-template/packages/github/dist/database/src/lib/kysely/tables/editorScenariosTable.d.ts.map +1 -1
  6. package/analyzer-template/packages/github/dist/database/src/lib/kysely/tables/editorScenariosTable.js +45 -0
  7. package/analyzer-template/packages/github/dist/database/src/lib/kysely/tables/editorScenariosTable.js.map +1 -1
  8. package/codeyam-cli/src/commands/editor.js +236 -76
  9. package/codeyam-cli/src/commands/editor.js.map +1 -1
  10. package/codeyam-cli/src/commands/init.js +1 -0
  11. package/codeyam-cli/src/commands/init.js.map +1 -1
  12. package/codeyam-cli/src/utils/__tests__/analyzerFinalization.test.js +29 -0
  13. package/codeyam-cli/src/utils/__tests__/analyzerFinalization.test.js.map +1 -1
  14. package/codeyam-cli/src/utils/__tests__/editorApi.test.js +18 -8
  15. package/codeyam-cli/src/utils/__tests__/editorApi.test.js.map +1 -1
  16. package/codeyam-cli/src/utils/__tests__/editorAudit.test.js +133 -1
  17. package/codeyam-cli/src/utils/__tests__/editorAudit.test.js.map +1 -1
  18. package/codeyam-cli/src/utils/__tests__/editorPreview.test.js +14 -0
  19. package/codeyam-cli/src/utils/__tests__/editorPreview.test.js.map +1 -1
  20. package/codeyam-cli/src/utils/__tests__/editorScenarios.test.js +205 -1
  21. package/codeyam-cli/src/utils/__tests__/editorScenarios.test.js.map +1 -1
  22. package/codeyam-cli/src/utils/__tests__/entityChangeStatus.test.js +58 -1
  23. package/codeyam-cli/src/utils/__tests__/entityChangeStatus.test.js.map +1 -1
  24. package/codeyam-cli/src/utils/__tests__/parseRegisterArg.test.js +30 -2
  25. package/codeyam-cli/src/utils/__tests__/parseRegisterArg.test.js.map +1 -1
  26. package/codeyam-cli/src/utils/__tests__/scenariosManifest.test.js +155 -1
  27. package/codeyam-cli/src/utils/__tests__/scenariosManifest.test.js.map +1 -1
  28. package/codeyam-cli/src/utils/__tests__/setupClaudeCodeSettings.test.js +1 -0
  29. package/codeyam-cli/src/utils/__tests__/setupClaudeCodeSettings.test.js.map +1 -1
  30. package/codeyam-cli/src/utils/analyzerFinalization.js +5 -1
  31. package/codeyam-cli/src/utils/analyzerFinalization.js.map +1 -1
  32. package/codeyam-cli/src/utils/editorApi.js +11 -5
  33. package/codeyam-cli/src/utils/editorApi.js.map +1 -1
  34. package/codeyam-cli/src/utils/editorAudit.js +34 -0
  35. package/codeyam-cli/src/utils/editorAudit.js.map +1 -1
  36. package/codeyam-cli/src/utils/editorPreview.js +9 -4
  37. package/codeyam-cli/src/utils/editorPreview.js.map +1 -1
  38. package/codeyam-cli/src/utils/editorScenarios.js +64 -9
  39. package/codeyam-cli/src/utils/editorScenarios.js.map +1 -1
  40. package/codeyam-cli/src/utils/entityChangeStatus.server.js +34 -0
  41. package/codeyam-cli/src/utils/entityChangeStatus.server.js.map +1 -1
  42. package/codeyam-cli/src/utils/parseRegisterArg.js.map +1 -1
  43. package/codeyam-cli/src/utils/progress.js +2 -2
  44. package/codeyam-cli/src/utils/progress.js.map +1 -1
  45. package/codeyam-cli/src/utils/scenariosManifest.js +82 -0
  46. package/codeyam-cli/src/utils/scenariosManifest.js.map +1 -1
  47. package/codeyam-cli/src/utils/setupClaudeCodeSettings.js +1 -0
  48. package/codeyam-cli/src/utils/setupClaudeCodeSettings.js.map +1 -1
  49. package/codeyam-cli/src/webserver/__tests__/idleDetector.test.js +146 -0
  50. package/codeyam-cli/src/webserver/__tests__/idleDetector.test.js.map +1 -0
  51. package/codeyam-cli/src/webserver/build/client/assets/{ScenarioViewer-Bd-hxofb.js → ScenarioViewer-TSD3C211.js} +1 -1
  52. package/codeyam-cli/src/webserver/build/client/assets/api.editor-session-l0sNRNKZ.js +1 -0
  53. package/codeyam-cli/src/webserver/build/client/assets/{dev.empty-BsDh6TSF.js → dev.empty-Ii3inc0_.js} +1 -1
  54. package/codeyam-cli/src/webserver/build/client/assets/editor-16o0AIFV.js +15 -0
  55. package/codeyam-cli/src/webserver/build/client/assets/editorPreview-7Uga8I59.js +41 -0
  56. package/codeyam-cli/src/webserver/build/client/assets/{entity._sha._-BsDXNp45.js → entity._sha._-DwCV5__E.js} +1 -1
  57. package/codeyam-cli/src/webserver/build/client/assets/{entity._sha.scenarios._scenarioId.dev-BgAqUtTZ.js → entity._sha.scenarios._scenarioId.dev-BwKcai0j.js} +1 -1
  58. package/codeyam-cli/src/webserver/build/client/assets/{entity._sha.scenarios._scenarioId.fullscreen-Bmshgrij.js → entity._sha.scenarios._scenarioId.fullscreen-CHMiAog3.js} +1 -1
  59. package/codeyam-cli/src/webserver/build/client/assets/globals-CQPR0pFR.css +1 -0
  60. package/codeyam-cli/src/webserver/build/client/assets/{manifest-65850841.js → manifest-76e7b62c.js} +1 -1
  61. package/codeyam-cli/src/webserver/build/client/assets/{root-BwX8YgFb.js → root-DBjt6o04.js} +4 -4
  62. package/codeyam-cli/src/webserver/build/client/assets/useCustomSizes-C-_hOl_g.js +1 -0
  63. package/codeyam-cli/src/webserver/build/client/sound-test.html +98 -0
  64. package/codeyam-cli/src/webserver/build/server/assets/{index-DEEQf4pi.js → index-DsZjKspK.js} +1 -1
  65. package/codeyam-cli/src/webserver/build/server/assets/init-DdqKD2p4.js +10 -0
  66. package/codeyam-cli/src/webserver/build/server/assets/server-build-CKKeWtVK.js +444 -0
  67. package/codeyam-cli/src/webserver/build/server/index.js +1 -1
  68. package/codeyam-cli/src/webserver/build-info.json +5 -5
  69. package/codeyam-cli/src/webserver/idleDetector.js +73 -0
  70. package/codeyam-cli/src/webserver/idleDetector.js.map +1 -0
  71. package/codeyam-cli/src/webserver/public/sound-test.html +98 -0
  72. package/codeyam-cli/src/webserver/server.js +46 -4
  73. package/codeyam-cli/src/webserver/server.js.map +1 -1
  74. package/codeyam-cli/src/webserver/terminalServer.js +65 -26
  75. package/codeyam-cli/src/webserver/terminalServer.js.map +1 -1
  76. package/codeyam-cli/templates/codeyam-editor-claude.md +1 -1
  77. package/codeyam-cli/templates/editor-step-hook.py +3 -2
  78. package/codeyam-cli/templates/skills/codeyam-editor/SKILL.md +10 -9
  79. package/package.json +1 -1
  80. package/packages/database/src/lib/kysely/tables/editorScenariosTable.js +45 -0
  81. package/packages/database/src/lib/kysely/tables/editorScenariosTable.js.map +1 -1
  82. package/codeyam-cli/src/webserver/build/client/assets/editor-PBc_6L9R.js +0 -10
  83. package/codeyam-cli/src/webserver/build/client/assets/editorPreview-4FzHlcNn.js +0 -41
  84. package/codeyam-cli/src/webserver/build/client/assets/globals-B8vTTNy2.css +0 -1
  85. package/codeyam-cli/src/webserver/build/client/assets/useCustomSizes-BE43Hjti.js +0 -1
  86. package/codeyam-cli/src/webserver/build/server/assets/init-CkWmyFY2.js +0 -10
  87. package/codeyam-cli/src/webserver/build/server/assets/server-build-BHi-9O8W.js +0 -439
@@ -11,11 +11,11 @@ import { IS_INTERNAL_BUILD } from "../utils/buildFlags.js";
11
11
  import { startBackgroundServer } from "../utils/backgroundServer.js";
12
12
  import { installClaudeCodeSkills } from "../utils/install-skills.js";
13
13
  import { setupClaudeCodeSettings } from "../utils/setupClaudeCodeSettings.js";
14
- import { ensureAnalyzerFinalized } from "../utils/analyzerFinalization.js";
14
+ import { ensureAnalyzerFinalized, } from "../utils/analyzerFinalization.js";
15
15
  import { APP_FORMATS, TECH_STACKS } from "../data/techStacks.js";
16
16
  import { getProjectRoot as getStateProjectRoot } from "../state.js";
17
17
  import initCommand from "./init.js";
18
- import { scanScenarioFiles, syncScenarioFilesToDatabase, backfillScenarioMetadata, } from "../utils/scenariosManifest.js";
18
+ import { scanScenarioFiles, syncScenarioFilesToDatabase, backfillScenarioMetadata, migrateScenarioFormats, } from "../utils/scenariosManifest.js";
19
19
  import { clearEditorState, clearEditorUserPrompt, validateStepTransition, } from "../utils/editorScenarios.js";
20
20
  import { validateSeedData, detectSeedAdapter, } from "../utils/editorSeedAdapter.js";
21
21
  import { buildEditorApiRequest, callEditorApi, EDITOR_API_SUBCOMMANDS, } from "../utils/editorApi.js";
@@ -143,6 +143,39 @@ function getServerPort() {
143
143
  }
144
144
  return process.env.CODEYAM_PORT || '3111';
145
145
  }
146
+ /**
147
+ * Read the project's default dimension name and available screen size names.
148
+ * Used by step instructions to show project-specific dimension examples
149
+ * instead of hardcoded "Desktop".
150
+ */
151
+ function getProjectDimensions(root) {
152
+ try {
153
+ const configPath = path.join(root, '.codeyam', 'config.json');
154
+ const config = JSON.parse(fs.readFileSync(configPath, 'utf8'));
155
+ const defaultName = config.defaultScreenSize?.name ||
156
+ (config.screenSizes ? Object.keys(config.screenSizes)[0] : null) ||
157
+ 'Desktop';
158
+ const names = config.screenSizes ? Object.keys(config.screenSizes) : [];
159
+ return { defaultName, names };
160
+ }
161
+ catch {
162
+ return { defaultName: 'Desktop', names: [] };
163
+ }
164
+ }
165
+ /**
166
+ * Print dimension guidance when the project has multiple screen sizes.
167
+ * Tells Claude to pick the right dimension for the content being previewed
168
+ * instead of blindly using the default for everything.
169
+ */
170
+ function printDimensionGuidance(defaultName, names) {
171
+ if (names.length <= 1)
172
+ return;
173
+ // Find a non-default dimension to suggest as the "other" option
174
+ const otherName = names.find((n) => n !== defaultName) || names[1];
175
+ console.log(chalk.yellow(` IMPORTANT: Choose the dimension that matches what you're previewing. Available: ${names.map((n) => `"${n}"`).join(', ')}.`));
176
+ console.log(chalk.yellow(` Do NOT always use "${defaultName}". Full pages, standalone views, and desktop layouts should use "${otherName}".`));
177
+ console.log(chalk.yellow(` Think about the CONTENT — a full-page library view needs a large viewport, not a popup-sized one.`));
178
+ }
146
179
  /**
147
180
  * Print a checklist item.
148
181
  * Inline backtick-wrapped text is highlighted in cyan for visibility.
@@ -511,13 +544,15 @@ function printSetup(root) {
511
544
  console.log();
512
545
  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).'));
513
546
  console.log(chalk.dim(' If only one obvious choice, confirm it rather than asking.'));
514
- 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}}\''));
547
+ 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}}'`));
515
548
  console.log();
516
549
  console.log(chalk.bold('Named Screen Sizes (for multi-resolution apps):'));
517
550
  console.log(chalk.dim(' For mobile-responsive web apps or apps that need screenshots at multiple resolutions,'));
518
551
  console.log(chalk.dim(' also save named screen sizes. Each scenario can reference a dimension name.'));
519
- console.log(chalk.dim(' curl -s -X POST http://localhost:PORT/api/editor-project-info -H "Content-Type: application/json" \\'));
552
+ console.log(chalk.dim(` curl -s -X POST http://localhost:${port}/api/editor-project-info -H "Content-Type: application/json" \\`));
520
553
  console.log(chalk.dim(' -d \'{"screenSizes":{"Desktop":{"width":1440,"height":900},"Mobile":{"width":375,"height":667}}}\''));
554
+ console.log(chalk.dim(' If you discover a new viewport is needed mid-workflow, add it to screenSizes the same way and reference by name.'));
555
+ console.log(chalk.dim(' NOTE: This REPLACES all screenSizes — always include every named size, not just the new one.'));
521
556
  console.log();
522
557
  console.log(chalk.bold.red('━━━ STOP ━━━'));
523
558
  console.log();
@@ -663,6 +698,7 @@ function printStep1(root, feature, options, userPrompt) {
663
698
  // ─── Step 2: Prototype ────────────────────────────────────────────────
664
699
  function printStep2(root, feature) {
665
700
  const port = getServerPort();
701
+ const { defaultName: dim, names: dimNames } = getProjectDimensions(root);
666
702
  const projectExists = hasProject(root);
667
703
  const prevState = readState(root);
668
704
  const isResuming = prevState?.step === 2;
@@ -751,11 +787,12 @@ function printStep2(root, feature) {
751
787
  console.log();
752
788
  console.log(chalk.bold.cyan('Keep the preview moving:'));
753
789
  console.log(chalk.cyan(' The user is watching the preview. Refresh it after each meaningful change:'));
754
- console.log(chalk.cyan(` codeyam editor preview`));
790
+ console.log(chalk.cyan(` codeyam editor preview '{"dimension":"${dim}"}'`));
755
791
  console.log(chalk.cyan(' Refresh after: first visible page, adding each UI section, seeding data, styling.'));
756
792
  console.log(chalk.cyan(' Aim for 4-8+ refreshes during prototyping — not one big reveal at the end.'));
757
793
  console.log(chalk.cyan(' If you build a NEW page (e.g., /drinks/[id]), navigate the preview there:'));
758
- console.log(chalk.cyan(` codeyam editor preview '{"path":"/drinks/1"}'`));
794
+ console.log(chalk.cyan(` codeyam editor preview '{"path":"/drinks/1","dimension":"${dim}"}'`));
795
+ printDimensionGuidance(dim, dimNames);
759
796
  console.log();
760
797
  console.log(chalk.bold('Verify the dev server:'));
761
798
  console.log(chalk.dim(` # Get dev server URL: codeyam editor dev-server`));
@@ -788,6 +825,7 @@ function printStep2(root, feature) {
788
825
  // ─── Step 3: Confirm ──────────────────────────────────────────────────
789
826
  function printStep3(root, feature) {
790
827
  const port = getServerPort();
828
+ const { defaultName: dim, names: dimNames } = getProjectDimensions(root);
791
829
  const prevState = readState(root);
792
830
  const isResuming = prevState?.step === 3;
793
831
  const now = new Date().toISOString();
@@ -806,7 +844,7 @@ function printStep3(root, feature) {
806
844
  console.log('Summarize what was built and get user confirmation.');
807
845
  console.log();
808
846
  console.log(chalk.bold('Before presenting — verify everything works:'));
809
- checkbox(`Refresh the preview: \`codeyam editor preview\` — check the \`preview\` field for \`healthy: false\``);
847
+ checkbox(`Refresh the preview: \`codeyam editor preview '{"dimension":"${dim}"}'\` — check the \`preview\` field for \`healthy: false\``);
810
848
  checkbox('Verify API routes return valid data (curl each route)');
811
849
  console.log();
812
850
  console.log(chalk.bold.red(' Verify EVERY image loads (this is the #1 source of broken prototypes):'));
@@ -820,7 +858,8 @@ function printStep3(root, feature) {
820
858
  console.log();
821
859
  console.log(chalk.bold('Then present to the user:'));
822
860
  checkbox('Summarize what was built (routes, components, data)');
823
- checkbox('Navigate the preview to the feature\'s primary page: `codeyam editor preview \'{"path":"/your-page"}\'`');
861
+ checkbox(`Navigate the preview to the feature's primary page: \`codeyam editor preview '{"path":"/your-page","dimension":"${dim}"}'\``);
862
+ printDimensionGuidance(dim, dimNames);
824
863
  console.log(chalk.dim(' The user needs to SEE the new feature. If you built a new page, navigate there.'));
825
864
  console.log(chalk.dim(' If you modified an existing page, refresh the preview to show the changes.'));
826
865
  console.log();
@@ -890,6 +929,7 @@ function printStep4(root, feature) {
890
929
  // ─── Step 5: Extract ──────────────────────────────────────────────────
891
930
  function printStep5(root, feature) {
892
931
  const port = getServerPort();
932
+ const { defaultName: dim, names: dimNames } = getProjectDimensions(root);
893
933
  const prevState = readState(root);
894
934
  const isResuming = prevState?.step === 5;
895
935
  const now = new Date().toISOString();
@@ -931,7 +971,8 @@ function printStep5(root, feature) {
931
971
  checkbox('Run all tests and verify they pass');
932
972
  checkbox('Page files contain ONLY imports + component composition — no raw HTML tags');
933
973
  checkbox('Every component renders ONE thing or composes sub-components — no multi-section JSX');
934
- checkbox(`Refresh the preview after each batch of extractions: \`codeyam editor preview\``);
974
+ checkbox(`Refresh the preview after each batch of extractions: \`codeyam editor preview '{"dimension":"${dim}"}'\``);
975
+ printDimensionGuidance(dim, dimNames);
935
976
  console.log(chalk.dim(' The user should see the preview stay healthy as you refactor — refresh periodically, not just at the end.'));
936
977
  console.log(chalk.dim('Reuse glossary functions when they fit naturally. Extract a new function when the use case diverges.'));
937
978
  console.log();
@@ -978,6 +1019,7 @@ function printStep6(root, feature) {
978
1019
  // ─── Step 7: Analyze ──────────────────────────────────────────────────
979
1020
  function printStep7(root, feature) {
980
1021
  const port = getServerPort();
1022
+ const { defaultName: dim, names: dimNames } = getProjectDimensions(root);
981
1023
  const prevState = readState(root);
982
1024
  const isResuming = prevState?.step === 7;
983
1025
  const now = new Date().toISOString();
@@ -1029,9 +1071,15 @@ function printStep7(root, feature) {
1029
1071
  console.log(chalk.dim(` codeyam editor register '{"name":"ComponentName - Scenario",`));
1030
1072
  console.log(chalk.dim(` "componentName":"ComponentName","componentPath":"path/to/file.tsx",`));
1031
1073
  console.log(chalk.dim(` "url":"/isolated-components/ComponentName?s=Scenario",`));
1074
+ console.log(chalk.dim(` "dimensions":["${dim}"],`));
1032
1075
  console.log(chalk.dim(` "mockData":{"routes":{"/api/...":{"body":[...]}}}}'`));
1033
- console.log(chalk.dim(' Optional: add "dimension" to use a named screen size, or "viewportWidth"/"viewportHeight" for raw override'));
1034
- console.log(chalk.dim(' If omitted, captures use the project default from setup. Only override for scenarios that need a different size.'));
1076
+ console.log(chalk.yellow(' ALWAYS include "dimensions" use the named screen size that matches the component\'s context.'));
1077
+ console.log(chalk.dim(dimNames.length > 0
1078
+ ? ` Available dimensions: ${dimNames.map((n) => `"${n}"`).join(', ')}. Default: "${dim}".`
1079
+ : ` Use "${dim}" for most components. Names must match a key in screenSizes from setup.`));
1080
+ console.log(chalk.dim(' Names must match a key in screenSizes from setup. If you need a new size, add it first:'));
1081
+ console.log(chalk.dim(` curl -s -X POST http://localhost:${port}/api/editor-project-info -H "Content-Type: application/json" \\`));
1082
+ console.log(chalk.dim(` -d '{"screenSizes":{...existing..., "New Name":{"width":W,"height":H}}}' (replaces all — include every size)`));
1035
1083
  console.log(chalk.dim(' url is a PATH (starts with /) — the proxy routes it and intercepts API calls'));
1036
1084
  console.log(chalk.dim(' mockData.routes provides data for API calls the component makes internally'));
1037
1085
  console.log(chalk.dim(' (omit mockData if the component has no internal API calls)'));
@@ -1059,6 +1107,7 @@ function printStep7(root, feature) {
1059
1107
  // ─── Step 8: App Scenarios ────────────────────────────────────────────
1060
1108
  function printStep8(root, feature) {
1061
1109
  const port = getServerPort();
1110
+ const { defaultName: dim, names: dimNames } = getProjectDimensions(root);
1062
1111
  const prevState = readState(root);
1063
1112
  const isResuming = prevState?.step === 8;
1064
1113
  const now = new Date().toISOString();
@@ -1101,12 +1150,20 @@ function printStep8(root, feature) {
1101
1150
  checkbox('Re-register every existing scenario whose page was affected by this feature');
1102
1151
  console.log(chalk.dim(' Use the SAME name to update. Enhance seed data to showcase this feature where relevant.'));
1103
1152
  console.log(chalk.yellow(" Don't just re-register unchanged data — enrich it so the feature is thoroughly demonstrated."));
1153
+ checkbox('ALWAYS include "dimensions" — pick the viewport that best demonstrates the page');
1154
+ console.log(chalk.yellow(' Every app scenario MUST have a "dimensions" field referencing a named screen size from setup.'));
1155
+ console.log(chalk.dim(dimNames.length > 0
1156
+ ? ` Available dimensions: ${dimNames.map((n) => `"${n}"`).join(', ')}. Default: "${dim}".`
1157
+ : ` Use "${dim}" for most scenarios. Names must match a key in screenSizes from setup.`));
1158
+ if (dimNames.length > 1) {
1159
+ const otherDim = dimNames.find((n) => n !== dim) || dimNames[1];
1160
+ console.log(chalk.yellow(` Think about what fits each scenario: full pages/standalone views → "${otherDim}", popups/widgets → "${dim}".`));
1161
+ }
1162
+ console.log(chalk.dim(' For responsive apps, include multiple dimensions: "dimensions":["Desktop","Mobile"] captures both viewports.'));
1163
+ console.log(chalk.dim(' If you need a new named size mid-workflow, add it via editor-project-info API (include ALL existing sizes — the API replaces, not merges).'));
1104
1164
  checkbox('If the project has a database + seed adapter, use seed-based scenarios:');
1105
- console.log(chalk.dim(` codeyam editor register '{"name":"Full Catalog","type":"application","url":"/","seed":{"products":[...],"categories":[...]}}'`));
1165
+ console.log(chalk.dim(` codeyam editor register '{"name":"Full Catalog","type":"application","url":"/","dimensions":["${dim}"],"seed":{"products":[...],"categories":[...]}}'`));
1106
1166
  console.log(chalk.dim(' Seed data is written to the DB via the seed adapter. Real app renders real data.'));
1107
- checkbox('Optional: add "dimension" to use a named screen size (e.g. "Mobile", "Desktop")');
1108
- console.log(chalk.dim(' References a named size from config screenSizes. For multi-resolution: register "Home - Desktop" with dimension "Desktop"'));
1109
- console.log(chalk.dim(' and "Home - Mobile" with dimension "Mobile". Or use raw "viewportWidth"/"viewportHeight" to override directly.'));
1110
1167
  checkbox('IMPORTANT: Always include "url" — the page path to screenshot for this scenario');
1111
1168
  console.log(chalk.dim(' Use "/" for home page, "/drinks/1" for detail pages, etc.'));
1112
1169
  console.log(chalk.dim(' Without url, the screenshot captures the root page regardless of the scenario.'));
@@ -1118,11 +1175,11 @@ function printStep8(root, feature) {
1118
1175
  console.log(chalk.dim(` "externalApis":{"GET https://api.stripe.com/v1/prices":{"body":[...],"status":200}}`));
1119
1176
  checkbox('If the app uses auth, email, or other patterns: see FEATURE_PATTERNS.md for scenario guidance');
1120
1177
  checkbox('If the app uses localStorage/sessionStorage instead of a database, use localStorage scenarios:');
1121
- console.log(chalk.dim(` codeyam editor register '{"name":"Full Library","type":"application","url":"/","localStorage":{"articles":[...],"collections":[...]}}'`));
1178
+ console.log(chalk.dim(` codeyam editor register '{"name":"Full Library","type":"application","url":"/","dimensions":["${dim}"],"localStorage":{"articles":[...],"collections":[...]}}'`));
1122
1179
  console.log(chalk.dim(' The proxy injects data into localStorage before the app loads. Fully interactive — the app can read and write normally.'));
1123
1180
  console.log(chalk.dim(' For an empty state, register with an empty localStorage or omit it entirely.'));
1124
1181
  checkbox('If no database and no localStorage, use component-style mock scenarios:');
1125
- console.log(chalk.dim(` codeyam editor register '{"name":"Empty","description":"...","url":"/","mockData":{"routes":{"/api/...":{"body":[...]}}}}'`));
1182
+ console.log(chalk.dim(` codeyam editor register '{"name":"Empty","description":"...","url":"/","dimensions":["${dim}"],"mockData":{"routes":{"/api/...":{"body":[...]}}}}'`));
1126
1183
  checkbox('After each registration, check the response for `clientErrors`');
1127
1184
  console.log(chalk.yellow(' If clientErrors is non-empty → fix the issue and re-register the scenario'));
1128
1185
  console.log(chalk.yellow(' Fix client errors and re-register before moving on'));
@@ -1182,6 +1239,8 @@ function printStep9(root, feature) {
1182
1239
  console.log(chalk.dim(' Step 8 scenarios already serve as logged-out versions (no session cookie)'));
1183
1240
  checkbox('If there are multiple user roles (admin, regular, etc.), create role-specific variations too');
1184
1241
  console.log(chalk.dim(' Each persona scenario layers user-specific seed data on top of an app scenario'));
1242
+ checkbox('Include "dimensions" — inherit from the base app scenario, or override if the persona implies a different device');
1243
+ console.log(chalk.dim(' e.g. a mobile-first user persona should use "dimensions":["Mobile"] even if the base scenario is Desktop.'));
1185
1244
  checkbox('After each registration, check the response for `clientErrors`');
1186
1245
  console.log(chalk.yellow(' If clientErrors is non-empty → fix the issue and re-register the scenario'));
1187
1246
  console.log();
@@ -1191,6 +1250,7 @@ function printStep9(root, feature) {
1191
1250
  // ─── Step 10: Verify ──────────────────────────────────────────────────
1192
1251
  function printStep10(root, feature) {
1193
1252
  const port = getServerPort();
1253
+ const { defaultName: dim, names: dimNames } = getProjectDimensions(root);
1194
1254
  const prevState = readState(root);
1195
1255
  const isResuming = prevState?.step === 10;
1196
1256
  const now = new Date().toISOString();
@@ -1215,7 +1275,8 @@ function printStep10(root, feature) {
1215
1275
  console.log(chalk.dim(` codeyam editor register '{"name":"ComponentName - Scenario",...}'`));
1216
1276
  console.log();
1217
1277
  console.log(chalk.bold('Editor scenarios (App tab) — visual + error check:'));
1218
- checkbox(`Refresh the preview: \`codeyam editor preview\``);
1278
+ checkbox(`Refresh the preview: \`codeyam editor preview '{"dimension":"${dim}"}'\``);
1279
+ printDimensionGuidance(dim, dimNames);
1219
1280
  checkbox('Click through each app-level and user-persona scenario in the preview');
1220
1281
  checkbox('For seed-based scenarios: verify data renders correctly after switching');
1221
1282
  console.log(chalk.dim(' Switch between scenarios and confirm the app reflects the seeded data each time'));
@@ -1268,6 +1329,7 @@ function printStep11(root, feature) {
1268
1329
  // ─── Step 12: Review ──────────────────────────────────────────────────
1269
1330
  function printStep12(root, feature) {
1270
1331
  const port = getServerPort();
1332
+ const { defaultName: dim, names: dimNames } = getProjectDimensions(root);
1271
1333
  const prevState = readState(root);
1272
1334
  const isResuming = prevState?.step === 12;
1273
1335
  const now = new Date().toISOString();
@@ -1286,7 +1348,8 @@ function printStep12(root, feature) {
1286
1348
  console.log('Verify all screenshots and checks pass before presenting to the user.');
1287
1349
  console.log();
1288
1350
  console.log(chalk.bold('Checklist (do all of this silently):'));
1289
- checkbox(`Refresh the preview: \`codeyam editor preview\``);
1351
+ checkbox(`Refresh the preview: \`codeyam editor preview '{"dimension":"${dim}"}'\``);
1352
+ printDimensionGuidance(dim, dimNames);
1290
1353
  checkbox('Verify each component has screenshots in the App tab (grouped under Components)');
1291
1354
  checkbox('If any are missing, re-register them using `codeyam editor register`');
1292
1355
  checkbox(`Check for client errors: \`codeyam editor client-errors\``);
@@ -1300,6 +1363,7 @@ function printStep12(root, feature) {
1300
1363
  // ─── Step 13: Present ─────────────────────────────────────────────────
1301
1364
  function printStep13(root, feature) {
1302
1365
  const port = getServerPort();
1366
+ const { defaultName: dim, names: dimNames } = getProjectDimensions(root);
1303
1367
  const prevState = readState(root);
1304
1368
  const isResuming = prevState?.step === 13;
1305
1369
  const now = new Date().toISOString();
@@ -1324,8 +1388,10 @@ function printStep13(root, feature) {
1324
1388
  console.log(chalk.dim(' Prefer scenarios with changeStatus "new" or "edited" (directly changed in this session).'));
1325
1389
  console.log(chalk.dim(' For app-level scenarios, prefer those showing the new/changed functionality.'));
1326
1390
  console.log(chalk.dim(' NEVER pick a scenario with changeStatus null — those are unchanged from a previous commit.'));
1327
- checkbox('Switch the preview to that scenario using its `id`:');
1328
- console.log(chalk.dim(` codeyam editor preview '{"scenarioId":"<id>"}'`));
1391
+ checkbox('Switch the preview to that scenario using its `id` and `dimension`:');
1392
+ console.log(chalk.dim(` codeyam editor preview '{"scenarioId":"<id>","dimension":"${dim}"}'`));
1393
+ console.log(chalk.dim(' Use the dimension that matches the scenario — check its registered dimensions.'));
1394
+ printDimensionGuidance(dim, dimNames);
1329
1395
  checkbox(`Show the results panel: \`codeyam editor show-results\``);
1330
1396
  console.log(chalk.dim(' This opens a visual panel below the terminal showing all scenarios with screenshots.'));
1331
1397
  console.log(chalk.dim(' The user can click scenarios to switch the live preview.'));
@@ -1769,8 +1835,9 @@ function formatApiSubcommandResult(subcommand, data) {
1769
1835
  */
1770
1836
  async function handleRegister(jsonArg) {
1771
1837
  if (!jsonArg) {
1838
+ const { defaultName: dim } = getProjectDimensions(getProjectRoot());
1772
1839
  console.error(chalk.red('Error: JSON argument required.'));
1773
- console.error(chalk.dim(' Usage: codeyam editor register \'{"name":"DrinkCard - Default","componentName":"DrinkCard","url":"/isolated-components/DrinkCard?s=Default"}\''));
1840
+ console.error(chalk.dim(` Usage: codeyam editor register '{"name":"DrinkCard - Default","componentName":"DrinkCard","url":"/isolated-components/DrinkCard?s=Default","dimensions":["${dim}"]}'`));
1774
1841
  console.error(chalk.dim(' For large payloads: codeyam editor register @/tmp/scenario.json'));
1775
1842
  process.exit(1);
1776
1843
  }
@@ -1782,57 +1849,74 @@ async function handleRegister(jsonArg) {
1782
1849
  }
1783
1850
  process.exit(1);
1784
1851
  }
1785
- const body = parsed.body;
1852
+ // Normalize to array for uniform handling
1853
+ const items = Array.isArray(parsed.body) ? parsed.body : [parsed.body];
1786
1854
  const port = getServerPort();
1787
1855
  const url = `http://localhost:${port}/api/editor-register-scenario`;
1788
- try {
1789
- const res = await fetch(url, {
1790
- method: 'POST',
1791
- headers: { 'Content-Type': 'application/json' },
1792
- body: JSON.stringify(body),
1793
- });
1794
- const data = await res.json();
1795
- // Print concise summary instead of raw JSON so Claude doesn't need python3
1796
- const parts = [];
1797
- parts.push(`success=${data.success}`);
1798
- if (data.scenario?.name)
1799
- parts.push(`name="${data.scenario.name}"`);
1800
- parts.push(`screenshot=${!!data.screenshotCaptured}`);
1801
- if (data.capturedViewport) {
1802
- parts.push(`viewport=${data.capturedViewport.width}×${data.capturedViewport.height}`);
1803
- }
1804
- if (data.scenario?.dimension)
1805
- parts.push(`dimension="${data.scenario.dimension}"`);
1806
- if (data.clientErrors?.length > 0) {
1807
- parts.push(chalk.red(`errors=${data.clientErrors.length}`));
1808
- }
1809
- else {
1810
- parts.push(`errors=0`);
1811
- }
1812
- if (data.seedResult)
1813
- parts.push(`seed=${data.seedResult.success}`);
1814
- if (data.captureError)
1815
- parts.push(chalk.yellow(`captureError="${data.captureError}"`));
1816
- console.log(parts.join(' '));
1817
- // Surface client errors prominently so they can't be missed
1818
- if (data.clientErrors && data.clientErrors.length > 0) {
1819
- console.log(chalk.red.bold(`⚠ WARNING: ${data.clientErrors.length} client error${data.clientErrors.length !== 1 ? 's' : ''} detected during capture:`));
1820
- for (const err of data.clientErrors) {
1821
- console.log(chalk.red(`${err}`));
1856
+ const isBatch = items.length > 1;
1857
+ let succeeded = 0;
1858
+ let failed = 0;
1859
+ for (let i = 0; i < items.length; i++) {
1860
+ const body = items[i];
1861
+ const prefix = isBatch ? chalk.dim(`[${i + 1}/${items.length}] `) : '';
1862
+ try {
1863
+ const res = await fetch(url, {
1864
+ method: 'POST',
1865
+ headers: { 'Content-Type': 'application/json' },
1866
+ body: JSON.stringify(body),
1867
+ });
1868
+ const data = await res.json();
1869
+ // Print concise summary instead of raw JSON so Claude doesn't need python3
1870
+ const parts = [];
1871
+ parts.push(`success=${data.success}`);
1872
+ if (data.scenario?.name)
1873
+ parts.push(`name="${data.scenario.name}"`);
1874
+ parts.push(`screenshot=${!!data.screenshotCaptured}`);
1875
+ if (data.capturedViewport) {
1876
+ parts.push(`viewport=${data.capturedViewport.width}×${data.capturedViewport.height}`);
1877
+ }
1878
+ if (data.scenario?.dimension)
1879
+ parts.push(`dimension="${data.scenario.dimension}"`);
1880
+ if (data.clientErrors?.length > 0) {
1881
+ parts.push(chalk.red(`errors=${data.clientErrors.length}`));
1882
+ }
1883
+ else {
1884
+ parts.push(`errors=0`);
1885
+ }
1886
+ if (data.seedResult)
1887
+ parts.push(`seed=${data.seedResult.success}`);
1888
+ if (data.captureError)
1889
+ parts.push(chalk.yellow(`captureError="${data.captureError}"`));
1890
+ console.log(prefix + parts.join(' '));
1891
+ // Surface client errors prominently so they can't be missed
1892
+ if (data.clientErrors && data.clientErrors.length > 0) {
1893
+ console.log(chalk.red.bold(`⚠ WARNING: ${data.clientErrors.length} client error${data.clientErrors.length !== 1 ? 's' : ''} detected during capture:`));
1894
+ for (const err of data.clientErrors) {
1895
+ console.log(chalk.red(` → ${err}`));
1896
+ }
1897
+ console.log(chalk.yellow(' The screenshot may show an error screen instead of the actual component.'));
1898
+ console.log(chalk.yellow(' Fix the issue and re-register this scenario.'));
1899
+ }
1900
+ if (!res.ok) {
1901
+ console.error(chalk.dim(JSON.stringify(data, null, 2)));
1902
+ failed++;
1903
+ }
1904
+ else {
1905
+ succeeded++;
1822
1906
  }
1823
- console.log(chalk.yellow(' The screenshot may show an error screen instead of the actual component.'));
1824
- console.log(chalk.yellow(' Fix the issue and re-register this scenario.'));
1825
1907
  }
1826
- if (!res.ok) {
1827
- console.error(chalk.dim(JSON.stringify(data, null, 2)));
1828
- process.exit(1);
1908
+ catch (error) {
1909
+ const msg = error instanceof Error ? error.message : String(error);
1910
+ console.error(prefix + chalk.red(`Error: Could not reach editor server at ${url}`));
1911
+ console.error(chalk.dim(` ${msg}`));
1912
+ console.error(chalk.dim(' Is the editor running? Start it with: codeyam editor'));
1913
+ failed++;
1829
1914
  }
1830
1915
  }
1831
- catch (error) {
1832
- const msg = error instanceof Error ? error.message : String(error);
1833
- console.error(chalk.red(`Error: Could not reach editor server at ${url}`));
1834
- console.error(chalk.dim(` ${msg}`));
1835
- console.error(chalk.dim(' Is the editor running? Start it with: codeyam editor'));
1916
+ if (isBatch) {
1917
+ console.log(chalk.dim(`\nBatch complete: ${succeeded}/${items.length} succeeded`));
1918
+ }
1919
+ if (failed > 0) {
1836
1920
  process.exit(1);
1837
1921
  }
1838
1922
  }
@@ -1962,6 +2046,7 @@ async function handleDependents(entityName) {
1962
2046
  */
1963
2047
  function handleChange(feature) {
1964
2048
  const root = getProjectRoot();
2049
+ const { defaultName: dim, names: dimNames } = getProjectDimensions(root);
1965
2050
  const state = readState(root);
1966
2051
  if (!feature) {
1967
2052
  // Try to read feature from state
@@ -1993,7 +2078,8 @@ function handleChange(feature) {
1993
2078
  }
1994
2079
  console.log(chalk.bold.cyan('Keep the preview moving:'));
1995
2080
  console.log(chalk.cyan(` Refresh after EACH individual change — not after all changes are done:`));
1996
- console.log(chalk.cyan(` codeyam editor preview`));
2081
+ console.log(chalk.cyan(` codeyam editor preview '{"dimension":"${dim}"}'`));
2082
+ printDimensionGuidance(dim, dimNames);
1997
2083
  console.log(chalk.cyan(' The user is watching the preview. Let them see progress incrementally.'));
1998
2084
  console.log();
1999
2085
  console.log(chalk.bold('0. Close the results panel:'));
@@ -2001,7 +2087,7 @@ function handleChange(feature) {
2001
2087
  console.log();
2002
2088
  console.log(chalk.bold('1. Re-register affected component scenarios:'));
2003
2089
  checkbox('For each component you modified, re-register ALL its scenarios');
2004
- console.log(chalk.dim(` codeyam editor register '{"name":"ComponentName - ScenarioName","componentName":"ComponentName","componentPath":"app/components/ComponentName.tsx","url":"/isolated-components/ComponentName?s=ScenarioName"}'`));
2090
+ console.log(chalk.dim(` codeyam editor register '{"name":"ComponentName - ScenarioName","componentName":"ComponentName","componentPath":"app/components/ComponentName.tsx","url":"/isolated-components/ComponentName?s=ScenarioName","dimensions":["${dim}"]}'`));
2005
2091
  checkbox('For any NEW components: `codeyam editor isolate NewComponent` then create isolation routes and register scenarios');
2006
2092
  console.log();
2007
2093
  console.log(chalk.bold('2. Re-run affected tests:'));
@@ -2025,8 +2111,9 @@ function handleChange(feature) {
2025
2111
  console.log(chalk.dim(" If the screenshot doesn't show the data you put in, the scenario is broken."));
2026
2112
  console.log();
2027
2113
  console.log(chalk.bold('4. Verify completeness:'));
2028
- checkbox(`Refresh the preview: \`codeyam editor preview\``);
2029
- checkbox(`Navigate to key pages to verify changes: \`codeyam editor preview '{"path":"/your-route"}'\``);
2114
+ checkbox(`Refresh the preview: \`codeyam editor preview '{"dimension":"${dim}"}'\``);
2115
+ checkbox(`Navigate to key pages to verify changes: \`codeyam editor preview '{"path":"/your-route","dimension":"${dim}"}'\``);
2116
+ printDimensionGuidance(dim, dimNames);
2030
2117
  checkbox('Run `codeyam editor scenario-coverage` — all affected scenarios must be fresh');
2031
2118
  checkbox('Run `codeyam editor audit` — all checks must pass');
2032
2119
  checkbox(`Check for client-side errors: \`codeyam editor client-errors\``);
@@ -2534,6 +2621,34 @@ async function handleSync() {
2534
2621
  parts.push(`${result.skipped} unchanged`);
2535
2622
  console.log(chalk.green(`Synced scenarios: ${parts.join(', ')}`));
2536
2623
  }
2624
+ // Migrate legacy scenario formats after sync, then re-sync if anything was fixed
2625
+ try {
2626
+ const migrateResult = migrateScenarioFormats(root);
2627
+ if (migrateResult.fixed > 0) {
2628
+ console.log(chalk.green(`Migrated ${migrateResult.fixed} scenario file${migrateResult.fixed > 1 ? 's' : ''} to current format`));
2629
+ // Re-sync so the DB reflects the corrected file metadata
2630
+ const refreshedRows = await db
2631
+ .selectFrom('editor_scenarios')
2632
+ .select(['id', 'updated_at'])
2633
+ .where('project_id', '=', project.id)
2634
+ .execute();
2635
+ await syncScenarioFilesToDatabase(root, project.id, refreshedRows, async (row) => {
2636
+ await db
2637
+ .insertInto('editor_scenarios')
2638
+ .values(row)
2639
+ .execute();
2640
+ }, async (id, row) => {
2641
+ await db
2642
+ .updateTable('editor_scenarios')
2643
+ .set(row)
2644
+ .where('id', '=', id)
2645
+ .execute();
2646
+ });
2647
+ }
2648
+ }
2649
+ catch {
2650
+ // Non-fatal
2651
+ }
2537
2652
  }
2538
2653
  // ─── Verify Images subcommand ─────────────────────────────────────────
2539
2654
  async function handleVerifyImages(jsonArg) {
@@ -3031,6 +3146,43 @@ const editorCommand = {
3031
3146
  // Non-fatal — sync failure shouldn't block editor startup
3032
3147
  }
3033
3148
  }
3149
+ // Migrate legacy scenario formats: resolve null viewports, populate
3150
+ // dimensions arrays, and build screenshotPaths maps from single values.
3151
+ // If files were fixed, re-sync to update the database with corrected values.
3152
+ try {
3153
+ const migrateResult = migrateScenarioFormats(projectRoot);
3154
+ if (migrateResult.fixed > 0) {
3155
+ console.log(chalk.green(` Migrated ${migrateResult.fixed} scenario file${migrateResult.fixed > 1 ? 's' : ''} to current format`));
3156
+ // Re-sync so the DB reflects the fixed file metadata
3157
+ try {
3158
+ const { getDatabase } = await import('../../../packages/database/index.js');
3159
+ const db = getDatabase();
3160
+ const rows = await db
3161
+ .selectFrom('editor_scenarios')
3162
+ .select(['id', 'updated_at'])
3163
+ .where('project_id', '=', project.id)
3164
+ .execute();
3165
+ await syncScenarioFilesToDatabase(projectRoot, project.id, rows, async (row) => {
3166
+ await db
3167
+ .insertInto('editor_scenarios')
3168
+ .values(row)
3169
+ .execute();
3170
+ }, async (id, row) => {
3171
+ await db
3172
+ .updateTable('editor_scenarios')
3173
+ .set(row)
3174
+ .where('id', '=', id)
3175
+ .execute();
3176
+ });
3177
+ }
3178
+ catch {
3179
+ // Non-fatal — DB re-sync failure shouldn't block startup
3180
+ }
3181
+ }
3182
+ }
3183
+ catch {
3184
+ // Non-fatal — migration failure shouldn't block editor startup
3185
+ }
3034
3186
  // `codeyam editor` (no step) always implies editor mode.
3035
3187
  // The empty-folder heuristic is no longer needed here — running this
3036
3188
  // command IS the signal. We still detect empty folders so that
@@ -3070,15 +3222,23 @@ const editorCommand = {
3070
3222
  });
3071
3223
  // Auto-finalize analyzer so codeyam analyze works
3072
3224
  if (editorMode) {
3073
- const finalization = ensureAnalyzerFinalized();
3074
- if (finalization.needed) {
3075
- console.log(` Setting up simulations (${finalization.stepsRun.join(', ')})...`);
3076
- }
3225
+ const stepLabels = {
3226
+ 'npm-install': 'Installing simulation dependencies...',
3227
+ 'playwright-install': 'Installing browser (Chromium)...',
3228
+ build: 'Building simulation engine...',
3229
+ };
3230
+ const simProgress = new ProgressReporter();
3231
+ const finalization = ensureAnalyzerFinalized({
3232
+ onProgress: (step) => simProgress.start(stepLabels[step]),
3233
+ });
3077
3234
  if (finalization.errors.length > 0) {
3078
3235
  for (const err of finalization.errors) {
3079
- console.warn(` Warning: ${err.step} failed: ${err.message}`);
3236
+ simProgress.warn(`${stepLabels[err.step].replace('...', '')} failed: ${err.message}`);
3080
3237
  }
3081
3238
  }
3239
+ else if (finalization.needed) {
3240
+ simProgress.succeed('Simulation engine ready');
3241
+ }
3082
3242
  }
3083
3243
  // Start background server (handles killing existing servers internally)
3084
3244
  const editorPort = argv.port || 3111;