@codeyam/codeyam-cli 0.1.0-staging.2ea44f6 → 0.1.0-staging.30dc541

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 (410) hide show
  1. package/analyzer-template/.build-info.json +8 -8
  2. package/analyzer-template/log.txt +3 -3
  3. package/analyzer-template/package.json +3 -3
  4. package/analyzer-template/packages/ai/package.json +1 -1
  5. package/analyzer-template/packages/aws/package.json +1 -1
  6. package/analyzer-template/packages/database/package.json +1 -1
  7. package/analyzer-template/packages/database/src/lib/kysely/tables/editorScenariosTable.ts +99 -0
  8. package/analyzer-template/packages/database/src/lib/loadEntities.ts +0 -6
  9. package/analyzer-template/packages/database/src/lib/updateCommitMetadata.ts +0 -65
  10. package/analyzer-template/packages/github/dist/database/src/lib/kysely/tables/editorScenariosTable.d.ts +8 -0
  11. package/analyzer-template/packages/github/dist/database/src/lib/kysely/tables/editorScenariosTable.d.ts.map +1 -1
  12. package/analyzer-template/packages/github/dist/database/src/lib/kysely/tables/editorScenariosTable.js +101 -0
  13. package/analyzer-template/packages/github/dist/database/src/lib/kysely/tables/editorScenariosTable.js.map +1 -1
  14. package/analyzer-template/packages/github/dist/database/src/lib/loadEntities.d.ts.map +1 -1
  15. package/analyzer-template/packages/github/dist/database/src/lib/loadEntities.js +0 -6
  16. package/analyzer-template/packages/github/dist/database/src/lib/loadEntities.js.map +1 -1
  17. package/analyzer-template/packages/github/dist/database/src/lib/updateCommitMetadata.d.ts.map +1 -1
  18. package/analyzer-template/packages/github/dist/database/src/lib/updateCommitMetadata.js +0 -25
  19. package/analyzer-template/packages/github/dist/database/src/lib/updateCommitMetadata.js.map +1 -1
  20. package/analyzer-template/packages/github/dist/types/src/enums/ProjectFramework.d.ts +2 -0
  21. package/analyzer-template/packages/github/dist/types/src/enums/ProjectFramework.d.ts.map +1 -1
  22. package/analyzer-template/packages/github/dist/types/src/enums/ProjectFramework.js +2 -0
  23. package/analyzer-template/packages/github/dist/types/src/enums/ProjectFramework.js.map +1 -1
  24. package/analyzer-template/packages/types/src/enums/ProjectFramework.ts +2 -0
  25. package/analyzer-template/packages/ui-components/package.json +1 -1
  26. package/analyzer-template/packages/utils/dist/types/src/enums/ProjectFramework.d.ts +2 -0
  27. package/analyzer-template/packages/utils/dist/types/src/enums/ProjectFramework.d.ts.map +1 -1
  28. package/analyzer-template/packages/utils/dist/types/src/enums/ProjectFramework.js +2 -0
  29. package/analyzer-template/packages/utils/dist/types/src/enums/ProjectFramework.js.map +1 -1
  30. package/codeyam-cli/src/cli.js +24 -0
  31. package/codeyam-cli/src/cli.js.map +1 -1
  32. package/codeyam-cli/src/commands/__tests__/editor.isolateArgs.test.js +51 -0
  33. package/codeyam-cli/src/commands/__tests__/editor.isolateArgs.test.js.map +1 -0
  34. package/codeyam-cli/src/commands/__tests__/editor.stepDispatch.test.js +56 -0
  35. package/codeyam-cli/src/commands/__tests__/editor.stepDispatch.test.js.map +1 -0
  36. package/codeyam-cli/src/commands/__tests__/init.gitignore.test.js +101 -47
  37. package/codeyam-cli/src/commands/__tests__/init.gitignore.test.js.map +1 -1
  38. package/codeyam-cli/src/commands/editor.js +3077 -509
  39. package/codeyam-cli/src/commands/editor.js.map +1 -1
  40. package/codeyam-cli/src/commands/editorIsolateArgs.js +25 -0
  41. package/codeyam-cli/src/commands/editorIsolateArgs.js.map +1 -0
  42. package/codeyam-cli/src/commands/init.js +69 -34
  43. package/codeyam-cli/src/commands/init.js.map +1 -1
  44. package/codeyam-cli/src/commands/telemetry.js +37 -0
  45. package/codeyam-cli/src/commands/telemetry.js.map +1 -0
  46. package/codeyam-cli/src/data/techStacks.js +77 -0
  47. package/codeyam-cli/src/data/techStacks.js.map +1 -0
  48. package/codeyam-cli/src/utils/__tests__/analyzerFinalization.test.js +173 -0
  49. package/codeyam-cli/src/utils/__tests__/analyzerFinalization.test.js.map +1 -0
  50. package/codeyam-cli/src/utils/__tests__/backgroundServer.test.js +46 -0
  51. package/codeyam-cli/src/utils/__tests__/backgroundServer.test.js.map +1 -0
  52. package/codeyam-cli/src/utils/__tests__/editorApi.test.js +18 -8
  53. package/codeyam-cli/src/utils/__tests__/editorApi.test.js.map +1 -1
  54. package/codeyam-cli/src/utils/__tests__/editorAudit.test.js +2201 -1
  55. package/codeyam-cli/src/utils/__tests__/editorAudit.test.js.map +1 -1
  56. package/codeyam-cli/src/utils/__tests__/editorBroadcastViewport.test.js +76 -0
  57. package/codeyam-cli/src/utils/__tests__/editorBroadcastViewport.test.js.map +1 -0
  58. package/codeyam-cli/src/utils/__tests__/editorCaptureScenarioSeeding.test.js +137 -0
  59. package/codeyam-cli/src/utils/__tests__/editorCaptureScenarioSeeding.test.js.map +1 -0
  60. package/codeyam-cli/src/utils/__tests__/editorDeleteScenario.test.js +100 -0
  61. package/codeyam-cli/src/utils/__tests__/editorDeleteScenario.test.js.map +1 -0
  62. package/codeyam-cli/src/utils/__tests__/editorDevServer.test.js +152 -3
  63. package/codeyam-cli/src/utils/__tests__/editorDevServer.test.js.map +1 -1
  64. package/codeyam-cli/src/utils/__tests__/editorEntityChangeStatus.test.js +76 -3
  65. package/codeyam-cli/src/utils/__tests__/editorEntityChangeStatus.test.js.map +1 -1
  66. package/codeyam-cli/src/utils/__tests__/editorEntityHelpers.test.js +381 -0
  67. package/codeyam-cli/src/utils/__tests__/editorEntityHelpers.test.js.map +1 -0
  68. package/codeyam-cli/src/utils/__tests__/editorGuardMiddleware.test.js +67 -0
  69. package/codeyam-cli/src/utils/__tests__/editorGuardMiddleware.test.js.map +1 -0
  70. package/codeyam-cli/src/utils/__tests__/editorImageVerifier.test.js +140 -12
  71. package/codeyam-cli/src/utils/__tests__/editorImageVerifier.test.js.map +1 -1
  72. package/codeyam-cli/src/utils/__tests__/editorJournal.test.js +238 -2
  73. package/codeyam-cli/src/utils/__tests__/editorJournal.test.js.map +1 -1
  74. package/codeyam-cli/src/utils/__tests__/editorLoaderHelpers.test.js +202 -1
  75. package/codeyam-cli/src/utils/__tests__/editorLoaderHelpers.test.js.map +1 -1
  76. package/codeyam-cli/src/utils/__tests__/editorMigration.test.js +435 -0
  77. package/codeyam-cli/src/utils/__tests__/editorMigration.test.js.map +1 -0
  78. package/codeyam-cli/src/utils/__tests__/editorPreview.test.js +191 -5
  79. package/codeyam-cli/src/utils/__tests__/editorPreview.test.js.map +1 -1
  80. package/codeyam-cli/src/utils/__tests__/editorProxySession.test.js +153 -0
  81. package/codeyam-cli/src/utils/__tests__/editorProxySession.test.js.map +1 -0
  82. package/codeyam-cli/src/utils/__tests__/editorScenarioLookup.test.js +139 -0
  83. package/codeyam-cli/src/utils/__tests__/editorScenarioLookup.test.js.map +1 -0
  84. package/codeyam-cli/src/utils/__tests__/editorScenarioSwitch.test.js +291 -0
  85. package/codeyam-cli/src/utils/__tests__/editorScenarioSwitch.test.js.map +1 -0
  86. package/codeyam-cli/src/utils/__tests__/editorScenarios.test.js +1437 -2
  87. package/codeyam-cli/src/utils/__tests__/editorScenarios.test.js.map +1 -1
  88. package/codeyam-cli/src/utils/__tests__/editorSeedAdapter.test.js +280 -0
  89. package/codeyam-cli/src/utils/__tests__/editorSeedAdapter.test.js.map +1 -0
  90. package/codeyam-cli/src/utils/__tests__/editorSeedAdapterPrismaValidation.test.js +143 -0
  91. package/codeyam-cli/src/utils/__tests__/editorSeedAdapterPrismaValidation.test.js.map +1 -0
  92. package/codeyam-cli/src/utils/__tests__/editorSessionFilter.test.js +66 -0
  93. package/codeyam-cli/src/utils/__tests__/editorSessionFilter.test.js.map +1 -0
  94. package/codeyam-cli/src/utils/__tests__/editorShouldRevalidate.test.js +53 -0
  95. package/codeyam-cli/src/utils/__tests__/editorShouldRevalidate.test.js.map +1 -0
  96. package/codeyam-cli/src/utils/__tests__/entityChangeStatus.test.js +641 -45
  97. package/codeyam-cli/src/utils/__tests__/entityChangeStatus.test.js.map +1 -1
  98. package/codeyam-cli/src/utils/__tests__/glossaryAdd.test.js +177 -0
  99. package/codeyam-cli/src/utils/__tests__/glossaryAdd.test.js.map +1 -0
  100. package/codeyam-cli/src/utils/__tests__/journalCaptureStabilization.test.js +122 -0
  101. package/codeyam-cli/src/utils/__tests__/journalCaptureStabilization.test.js.map +1 -0
  102. package/codeyam-cli/src/utils/__tests__/parseRegisterArg.test.js +129 -0
  103. package/codeyam-cli/src/utils/__tests__/parseRegisterArg.test.js.map +1 -0
  104. package/codeyam-cli/src/utils/__tests__/routePatternMatching.test.js +118 -0
  105. package/codeyam-cli/src/utils/__tests__/routePatternMatching.test.js.map +1 -0
  106. package/codeyam-cli/src/utils/__tests__/scenarioCoverage.test.js +284 -0
  107. package/codeyam-cli/src/utils/__tests__/scenarioCoverage.test.js.map +1 -0
  108. package/codeyam-cli/src/utils/__tests__/scenariosManifest.test.js +649 -223
  109. package/codeyam-cli/src/utils/__tests__/scenariosManifest.test.js.map +1 -1
  110. package/codeyam-cli/src/utils/__tests__/setupClaudeCodeSettings.test.js +14 -5
  111. package/codeyam-cli/src/utils/__tests__/setupClaudeCodeSettings.test.js.map +1 -1
  112. package/codeyam-cli/src/utils/__tests__/telemetry.test.js +159 -0
  113. package/codeyam-cli/src/utils/__tests__/telemetry.test.js.map +1 -0
  114. package/codeyam-cli/src/utils/__tests__/templateConsistency.test.js +51 -0
  115. package/codeyam-cli/src/utils/__tests__/templateConsistency.test.js.map +1 -0
  116. package/codeyam-cli/src/utils/__tests__/webappDetection.test.js +142 -0
  117. package/codeyam-cli/src/utils/__tests__/webappDetection.test.js.map +1 -0
  118. package/codeyam-cli/src/utils/analysisRunner.js +3 -1
  119. package/codeyam-cli/src/utils/analysisRunner.js.map +1 -1
  120. package/codeyam-cli/src/utils/analyzer.js +9 -0
  121. package/codeyam-cli/src/utils/analyzer.js.map +1 -1
  122. package/codeyam-cli/src/utils/analyzerFinalization.js +100 -0
  123. package/codeyam-cli/src/utils/analyzerFinalization.js.map +1 -0
  124. package/codeyam-cli/src/utils/backgroundServer.js +93 -17
  125. package/codeyam-cli/src/utils/backgroundServer.js.map +1 -1
  126. package/codeyam-cli/src/utils/database.js +37 -2
  127. package/codeyam-cli/src/utils/database.js.map +1 -1
  128. package/codeyam-cli/src/utils/editorApi.js +11 -5
  129. package/codeyam-cli/src/utils/editorApi.js.map +1 -1
  130. package/codeyam-cli/src/utils/editorAudit.js +410 -6
  131. package/codeyam-cli/src/utils/editorAudit.js.map +1 -1
  132. package/codeyam-cli/src/utils/editorBroadcastViewport.js +26 -0
  133. package/codeyam-cli/src/utils/editorBroadcastViewport.js.map +1 -0
  134. package/codeyam-cli/src/utils/editorDeleteScenario.js +67 -0
  135. package/codeyam-cli/src/utils/editorDeleteScenario.js.map +1 -0
  136. package/codeyam-cli/src/utils/editorDevServer.js +89 -1
  137. package/codeyam-cli/src/utils/editorDevServer.js.map +1 -1
  138. package/codeyam-cli/src/utils/editorEntityChangeStatus.js +13 -7
  139. package/codeyam-cli/src/utils/editorEntityChangeStatus.js.map +1 -1
  140. package/codeyam-cli/src/utils/editorEntityHelpers.js +144 -0
  141. package/codeyam-cli/src/utils/editorEntityHelpers.js.map +1 -0
  142. package/codeyam-cli/src/utils/editorGuard.js +36 -0
  143. package/codeyam-cli/src/utils/editorGuard.js.map +1 -0
  144. package/codeyam-cli/src/utils/editorImageVerifier.js +45 -10
  145. package/codeyam-cli/src/utils/editorImageVerifier.js.map +1 -1
  146. package/codeyam-cli/src/utils/editorJournal.js +78 -3
  147. package/codeyam-cli/src/utils/editorJournal.js.map +1 -1
  148. package/codeyam-cli/src/utils/editorLoaderHelpers.js +72 -1
  149. package/codeyam-cli/src/utils/editorLoaderHelpers.js.map +1 -1
  150. package/codeyam-cli/src/utils/editorMigration.js +224 -0
  151. package/codeyam-cli/src/utils/editorMigration.js.map +1 -0
  152. package/codeyam-cli/src/utils/editorPreview.js +43 -2
  153. package/codeyam-cli/src/utils/editorPreview.js.map +1 -1
  154. package/codeyam-cli/src/utils/editorRecapture.js +109 -0
  155. package/codeyam-cli/src/utils/editorRecapture.js.map +1 -0
  156. package/codeyam-cli/src/utils/editorScenarioSwitch.js +134 -0
  157. package/codeyam-cli/src/utils/editorScenarioSwitch.js.map +1 -0
  158. package/codeyam-cli/src/utils/editorScenarios.js +488 -0
  159. package/codeyam-cli/src/utils/editorScenarios.js.map +1 -1
  160. package/codeyam-cli/src/utils/editorSeedAdapter.js +422 -0
  161. package/codeyam-cli/src/utils/editorSeedAdapter.js.map +1 -0
  162. package/codeyam-cli/src/utils/editorShouldRevalidate.js +21 -0
  163. package/codeyam-cli/src/utils/editorShouldRevalidate.js.map +1 -0
  164. package/codeyam-cli/src/utils/entityChangeStatus.js +89 -21
  165. package/codeyam-cli/src/utils/entityChangeStatus.js.map +1 -1
  166. package/codeyam-cli/src/utils/entityChangeStatus.server.js +97 -8
  167. package/codeyam-cli/src/utils/entityChangeStatus.server.js.map +1 -1
  168. package/codeyam-cli/src/utils/fileWatcher.js +38 -0
  169. package/codeyam-cli/src/utils/fileWatcher.js.map +1 -1
  170. package/codeyam-cli/src/utils/glossaryAdd.js +74 -0
  171. package/codeyam-cli/src/utils/glossaryAdd.js.map +1 -0
  172. package/codeyam-cli/src/utils/install-skills.js +14 -0
  173. package/codeyam-cli/src/utils/install-skills.js.map +1 -1
  174. package/codeyam-cli/src/utils/parseRegisterArg.js +31 -0
  175. package/codeyam-cli/src/utils/parseRegisterArg.js.map +1 -0
  176. package/codeyam-cli/src/utils/progress.js +2 -2
  177. package/codeyam-cli/src/utils/progress.js.map +1 -1
  178. package/codeyam-cli/src/utils/routePatternMatching.js +129 -0
  179. package/codeyam-cli/src/utils/routePatternMatching.js.map +1 -0
  180. package/codeyam-cli/src/utils/scenarioCoverage.js +77 -0
  181. package/codeyam-cli/src/utils/scenarioCoverage.js.map +1 -0
  182. package/codeyam-cli/src/utils/scenariosManifest.js +269 -74
  183. package/codeyam-cli/src/utils/scenariosManifest.js.map +1 -1
  184. package/codeyam-cli/src/utils/serverState.js +30 -0
  185. package/codeyam-cli/src/utils/serverState.js.map +1 -1
  186. package/codeyam-cli/src/utils/setupClaudeCodeSettings.js +14 -5
  187. package/codeyam-cli/src/utils/setupClaudeCodeSettings.js.map +1 -1
  188. package/codeyam-cli/src/utils/simulationGateMiddleware.js +17 -1
  189. package/codeyam-cli/src/utils/simulationGateMiddleware.js.map +1 -1
  190. package/codeyam-cli/src/utils/slugUtils.js +25 -0
  191. package/codeyam-cli/src/utils/slugUtils.js.map +1 -0
  192. package/codeyam-cli/src/utils/syncMocksMiddleware.js +2 -2
  193. package/codeyam-cli/src/utils/syncMocksMiddleware.js.map +1 -1
  194. package/codeyam-cli/src/utils/telemetry.js +106 -0
  195. package/codeyam-cli/src/utils/telemetry.js.map +1 -0
  196. package/codeyam-cli/src/utils/telemetryMiddleware.js +22 -0
  197. package/codeyam-cli/src/utils/telemetryMiddleware.js.map +1 -0
  198. package/codeyam-cli/src/utils/webappDetection.js +21 -0
  199. package/codeyam-cli/src/utils/webappDetection.js.map +1 -1
  200. package/codeyam-cli/src/webserver/__tests__/buildPtyEnv.test.js +35 -0
  201. package/codeyam-cli/src/webserver/__tests__/buildPtyEnv.test.js.map +1 -0
  202. package/codeyam-cli/src/webserver/__tests__/clientErrors.test.js +80 -0
  203. package/codeyam-cli/src/webserver/__tests__/clientErrors.test.js.map +1 -0
  204. package/codeyam-cli/src/webserver/__tests__/editorProxy.test.js +628 -0
  205. package/codeyam-cli/src/webserver/__tests__/editorProxy.test.js.map +1 -0
  206. package/codeyam-cli/src/webserver/__tests__/idleDetector.test.js +228 -0
  207. package/codeyam-cli/src/webserver/__tests__/idleDetector.test.js.map +1 -0
  208. package/codeyam-cli/src/webserver/__tests__/stripClaudeCommand.test.js +79 -0
  209. package/codeyam-cli/src/webserver/__tests__/stripClaudeCommand.test.js.map +1 -0
  210. package/codeyam-cli/src/webserver/app/lib/clientErrors.js +71 -0
  211. package/codeyam-cli/src/webserver/app/lib/clientErrors.js.map +1 -0
  212. package/codeyam-cli/src/webserver/app/lib/git.js +3 -2
  213. package/codeyam-cli/src/webserver/app/lib/git.js.map +1 -1
  214. package/codeyam-cli/src/webserver/app/types/editor.js +8 -0
  215. package/codeyam-cli/src/webserver/app/types/editor.js.map +1 -0
  216. package/codeyam-cli/src/webserver/backgroundServer.js +60 -61
  217. package/codeyam-cli/src/webserver/backgroundServer.js.map +1 -1
  218. package/codeyam-cli/src/webserver/build/client/assets/CopyButton-CLe80MMu.js +1 -0
  219. package/codeyam-cli/src/webserver/build/client/assets/{EntityItem-C76mRRiF.js → EntityItem-Crt_KN_U.js} +5 -5
  220. package/codeyam-cli/src/webserver/build/client/assets/EntityTypeBadge-CQgyEGV-.js +1 -0
  221. package/codeyam-cli/src/webserver/build/client/assets/{EntityTypeIcon-CobE682z.js → EntityTypeIcon-CD7lGABo.js} +9 -9
  222. package/codeyam-cli/src/webserver/build/client/assets/InlineSpinner-CgTNOhnu.js +1 -0
  223. package/codeyam-cli/src/webserver/build/client/assets/InteractivePreview-CKeQT5Ty.js +25 -0
  224. package/codeyam-cli/src/webserver/build/client/assets/LibraryFunctionPreview-D3s1MFkb.js +3 -0
  225. package/codeyam-cli/src/webserver/build/client/assets/{LoadingDots-BU_OAEMP.js → LoadingDots-By5zI316.js} +1 -1
  226. package/codeyam-cli/src/webserver/build/client/assets/{LogViewer-ceAyBX-H.js → LogViewer-CM5zg40N.js} +3 -3
  227. package/codeyam-cli/src/webserver/build/client/assets/{ReportIssueModal-djPLI-WV.js → ReportIssueModal-C2PLkej3.js} +4 -4
  228. package/codeyam-cli/src/webserver/build/client/assets/SafeScreenshot-DanvyBPb.js +1 -0
  229. package/codeyam-cli/src/webserver/build/client/assets/{ScenarioViewer-ZlRKbhrq.js → ScenarioViewer-DUMfcNVK.js} +3 -3
  230. package/codeyam-cli/src/webserver/build/client/assets/Spinner-D0LgAaSa.js +34 -0
  231. package/codeyam-cli/src/webserver/build/client/assets/TruncatedFilePath-CK7-NaPZ.js +1 -0
  232. package/codeyam-cli/src/webserver/build/client/assets/ViewportInspectBar-BA_Ry-rs.js +1 -0
  233. package/codeyam-cli/src/webserver/build/client/assets/{_index-C96V0n15.js → _index-BAWd-Xjf.js} +4 -4
  234. package/codeyam-cli/src/webserver/build/client/assets/{activity.(_tab)-BpKzcsJz.js → activity.(_tab)-BOARiB-g.js} +8 -8
  235. package/codeyam-cli/src/webserver/build/client/assets/{addon-web-links-Duc5hnl7.js → addon-web-links-CHx25PAe.js} +1 -1
  236. package/codeyam-cli/src/webserver/build/client/assets/{agent-transcripts-D9hemwl6.js → agent-transcripts-Bg3e7q4S.js} +7 -7
  237. package/codeyam-cli/src/webserver/build/client/assets/api.editor-project-info-l0sNRNKZ.js +1 -0
  238. package/codeyam-cli/src/webserver/build/client/assets/api.editor-recapture-stale-l0sNRNKZ.js +1 -0
  239. package/codeyam-cli/src/webserver/build/client/assets/api.editor-rename-scenario-l0sNRNKZ.js +1 -0
  240. package/codeyam-cli/src/webserver/build/client/assets/api.editor-save-seed-state-l0sNRNKZ.js +1 -0
  241. package/codeyam-cli/src/webserver/build/client/assets/api.editor-scenario-coverage-l0sNRNKZ.js +1 -0
  242. package/codeyam-cli/src/webserver/build/client/assets/api.editor-scenario-prompt-l0sNRNKZ.js +1 -0
  243. package/codeyam-cli/src/webserver/build/client/assets/api.editor-session-l0sNRNKZ.js +1 -0
  244. package/codeyam-cli/src/webserver/build/client/assets/{book-open-D_nMCFmP.js → book-open-CL-lMgHh.js} +2 -2
  245. package/codeyam-cli/src/webserver/build/client/assets/{chevron-down-BH2h1Ea2.js → chevron-down-GmAjGS9-.js} +2 -2
  246. package/codeyam-cli/src/webserver/build/client/assets/chunk-JZWAC4HX-BAdwhyCx.js +43 -0
  247. package/codeyam-cli/src/webserver/build/client/assets/{circle-check-DyIKORY6.js → circle-check-DFcQkN5j.js} +2 -2
  248. package/codeyam-cli/src/webserver/build/client/assets/{copy-NDbZjXao.js → copy-C6iF61Xs.js} +3 -3
  249. package/codeyam-cli/src/webserver/build/client/assets/createLucideIcon-4ImjHTVC.js +41 -0
  250. package/codeyam-cli/src/webserver/build/client/assets/dev.empty-C8y4mmyv.js +1 -0
  251. package/codeyam-cli/src/webserver/build/client/assets/editor._tab-Gbk_i5Js.js +1 -0
  252. package/codeyam-cli/src/webserver/build/client/assets/editor.entity.(_sha)-CGzKlIHg.js +58 -0
  253. package/codeyam-cli/src/webserver/build/client/assets/editorPreview-oepecPae.js +41 -0
  254. package/codeyam-cli/src/webserver/build/client/assets/{entity._sha._-DItJnD8s.js → entity._sha._-Blfy9UlN.js} +3 -3
  255. package/codeyam-cli/src/webserver/build/client/assets/entity._sha.scenarios._scenarioId.dev-KTQuL0aj.js +6 -0
  256. package/codeyam-cli/src/webserver/build/client/assets/entity._sha.scenarios._scenarioId.fullscreen-C6eeL24i.js +6 -0
  257. package/codeyam-cli/src/webserver/build/client/assets/entity._sha_.create-scenario-DQM8E7L4.js +6 -0
  258. package/codeyam-cli/src/webserver/build/client/assets/{entity._sha_.edit._scenarioId-BMvVHNXU.js → entity._sha_.edit._scenarioId-CAoXLsQr.js} +2 -2
  259. package/codeyam-cli/src/webserver/build/client/assets/{entry.client-DTvKq3TY.js → entry.client-SuW9syRS.js} +6 -6
  260. package/codeyam-cli/src/webserver/build/client/assets/fileTableUtils-Daa96Fr1.js +1 -0
  261. package/codeyam-cli/src/webserver/build/client/assets/files-D-xGrg29.js +1 -0
  262. package/codeyam-cli/src/webserver/build/client/assets/git-Bq_fbXP5.js +1 -0
  263. package/codeyam-cli/src/webserver/build/client/assets/globals-Yn9W3zp3.css +1 -0
  264. package/codeyam-cli/src/webserver/build/client/assets/{index-BcvgDzbZ.js → index-Bp1l4hSv.js} +1 -1
  265. package/codeyam-cli/src/webserver/build/client/assets/{index-10oVnAAH.js → index-CWV9XZiG.js} +1 -1
  266. package/codeyam-cli/src/webserver/build/client/assets/{index-yHOVb4rc.js → index-DE3jI_dv.js} +1 -1
  267. package/codeyam-cli/src/webserver/build/client/assets/jsx-runtime-D_zvdyIk.js +9 -0
  268. package/codeyam-cli/src/webserver/build/client/assets/labs-B_IX45ih.js +1 -0
  269. package/codeyam-cli/src/webserver/build/client/assets/{loader-circle-BAXYRVEO.js → loader-circle-De-7qQ2u.js} +2 -2
  270. package/codeyam-cli/src/webserver/build/client/assets/manifest-2ef99f38.js +1 -0
  271. package/codeyam-cli/src/webserver/build/client/assets/memory-Cx2xEx7s.js +101 -0
  272. package/codeyam-cli/src/webserver/build/client/assets/{pause-DTAcYxBt.js → pause-CFxEKL1u.js} +3 -3
  273. package/codeyam-cli/src/webserver/build/client/assets/root-BxUQigda.js +67 -0
  274. package/codeyam-cli/src/webserver/build/client/assets/{search-fKo7v0Zo.js → search-BdBb5aqc.js} +2 -2
  275. package/codeyam-cli/src/webserver/build/client/assets/settings-DdE-Untf.js +1 -0
  276. package/codeyam-cli/src/webserver/build/client/assets/simulations-DSCdE99u.js +1 -0
  277. package/codeyam-cli/src/webserver/build/client/assets/{terminal-BG4heKCG.js → terminal-CrplD4b1.js} +3 -3
  278. package/codeyam-cli/src/webserver/build/client/assets/{triangle-alert-DtSmdtM4.js → triangle-alert-DqJ0j69l.js} +2 -2
  279. package/codeyam-cli/src/webserver/build/client/assets/useCustomSizes-DhXHbEjP.js +1 -0
  280. package/codeyam-cli/src/webserver/build/client/assets/{useLastLogLine-C14nCb1q.js → useLastLogLine-BNd5hYuW.js} +1 -1
  281. package/codeyam-cli/src/webserver/build/client/assets/useReportContext-Cy5Qg_UR.js +1 -0
  282. package/codeyam-cli/src/webserver/build/client/assets/useToast-5HR2j9ZE.js +1 -0
  283. package/codeyam-cli/src/webserver/build/client/sound-test.html +98 -0
  284. package/codeyam-cli/src/webserver/build/server/assets/analysisRunner-BPmOG9bE.js +13 -0
  285. package/codeyam-cli/src/webserver/build/server/assets/index-Cd-ufawF.js +1 -0
  286. package/codeyam-cli/src/webserver/build/server/assets/init-CzeBGOto.js +10 -0
  287. package/codeyam-cli/src/webserver/build/server/assets/progress-CHTtrxFG.js +1 -0
  288. package/codeyam-cli/src/webserver/build/server/assets/server-build-Dht7CKXY.js +552 -0
  289. package/codeyam-cli/src/webserver/build/server/index.js +1 -1
  290. package/codeyam-cli/src/webserver/build-info.json +5 -5
  291. package/codeyam-cli/src/webserver/editorProxy.js +586 -17
  292. package/codeyam-cli/src/webserver/editorProxy.js.map +1 -1
  293. package/codeyam-cli/src/webserver/idleDetector.js +106 -0
  294. package/codeyam-cli/src/webserver/idleDetector.js.map +1 -0
  295. package/codeyam-cli/src/webserver/mockStateEvents.js +28 -0
  296. package/codeyam-cli/src/webserver/mockStateEvents.js.map +1 -0
  297. package/codeyam-cli/src/webserver/public/sound-test.html +98 -0
  298. package/codeyam-cli/src/webserver/scripts/codeyam-preload.mjs +21 -3
  299. package/codeyam-cli/src/webserver/scripts/journalCapture.ts +130 -4
  300. package/codeyam-cli/src/webserver/server.js +100 -34
  301. package/codeyam-cli/src/webserver/server.js.map +1 -1
  302. package/codeyam-cli/src/webserver/terminalServer.js +242 -48
  303. package/codeyam-cli/src/webserver/terminalServer.js.map +1 -1
  304. package/codeyam-cli/templates/__tests__/editor-step-hook.prompt-capture.test.ts +118 -0
  305. package/codeyam-cli/templates/chrome-extension-react/EXTENSION_SETUP.md +75 -0
  306. package/codeyam-cli/templates/chrome-extension-react/README.md +46 -0
  307. package/codeyam-cli/templates/chrome-extension-react/gitignore +15 -0
  308. package/codeyam-cli/templates/chrome-extension-react/index.html +12 -0
  309. package/codeyam-cli/templates/chrome-extension-react/package.json +27 -0
  310. package/codeyam-cli/templates/chrome-extension-react/popup.html +12 -0
  311. package/codeyam-cli/templates/chrome-extension-react/public/manifest.json +15 -0
  312. package/codeyam-cli/templates/chrome-extension-react/src/background/service-worker.ts +7 -0
  313. package/codeyam-cli/templates/chrome-extension-react/src/globals.css +6 -0
  314. package/codeyam-cli/templates/chrome-extension-react/src/lib/storage.ts +37 -0
  315. package/codeyam-cli/templates/chrome-extension-react/src/popup/App.tsx +12 -0
  316. package/codeyam-cli/templates/chrome-extension-react/src/popup/main.tsx +10 -0
  317. package/codeyam-cli/templates/chrome-extension-react/tsconfig.json +24 -0
  318. package/codeyam-cli/templates/chrome-extension-react/vite.config.ts +41 -0
  319. package/codeyam-cli/templates/codeyam-editor-claude.md +86 -5
  320. package/codeyam-cli/templates/codeyam-editor-reference.md +214 -0
  321. package/codeyam-cli/templates/editor-step-hook.py +193 -54
  322. package/codeyam-cli/templates/expo-react-native/MOBILE_SETUP.md +89 -0
  323. package/codeyam-cli/templates/expo-react-native/README.md +41 -0
  324. package/codeyam-cli/templates/expo-react-native/app/(tabs)/_layout.tsx +33 -0
  325. package/codeyam-cli/templates/expo-react-native/app/(tabs)/index.tsx +12 -0
  326. package/codeyam-cli/templates/expo-react-native/app/(tabs)/settings.tsx +12 -0
  327. package/codeyam-cli/templates/expo-react-native/app/_layout.tsx +12 -0
  328. package/codeyam-cli/templates/expo-react-native/app.json +18 -0
  329. package/codeyam-cli/templates/expo-react-native/babel.config.js +9 -0
  330. package/codeyam-cli/templates/expo-react-native/gitignore +12 -0
  331. package/codeyam-cli/templates/expo-react-native/global.css +3 -0
  332. package/codeyam-cli/templates/expo-react-native/lib/storage.ts +32 -0
  333. package/codeyam-cli/templates/expo-react-native/metro.config.js +6 -0
  334. package/codeyam-cli/templates/expo-react-native/nativewind-env.d.ts +1 -0
  335. package/codeyam-cli/templates/expo-react-native/package.json +38 -0
  336. package/codeyam-cli/templates/expo-react-native/tailwind.config.js +10 -0
  337. package/codeyam-cli/templates/expo-react-native/tsconfig.json +10 -0
  338. package/codeyam-cli/templates/nextjs-prisma-sqlite/AUTH_PATTERNS.md +308 -0
  339. package/codeyam-cli/templates/nextjs-prisma-sqlite/AUTH_UPGRADE.md +304 -0
  340. package/codeyam-cli/templates/nextjs-prisma-sqlite/DATABASE.md +126 -0
  341. package/codeyam-cli/templates/nextjs-prisma-sqlite/FEATURE_PATTERNS.md +37 -0
  342. package/codeyam-cli/templates/nextjs-prisma-sqlite/README.md +53 -0
  343. package/codeyam-cli/templates/nextjs-prisma-sqlite/app/lib/prisma.ts +9 -4
  344. package/codeyam-cli/templates/nextjs-prisma-sqlite/env +4 -0
  345. package/codeyam-cli/templates/nextjs-prisma-sqlite/gitignore +1 -0
  346. package/codeyam-cli/templates/nextjs-prisma-sqlite/package.json +2 -1
  347. package/codeyam-cli/templates/nextjs-prisma-sqlite/prisma/seed.ts +4 -1
  348. package/codeyam-cli/templates/nextjs-prisma-sqlite/seed-adapter.ts +127 -0
  349. package/codeyam-cli/templates/nextjs-prisma-supabase/README.md +52 -0
  350. package/codeyam-cli/templates/{nextjs-prisma-sqlite/PRISMA_SETUP.md → nextjs-prisma-supabase/SUPABASE_SETUP.md} +37 -17
  351. package/codeyam-cli/templates/nextjs-prisma-supabase/app/api/todos/route.ts +17 -0
  352. package/codeyam-cli/templates/nextjs-prisma-supabase/app/globals.css +26 -0
  353. package/codeyam-cli/templates/nextjs-prisma-supabase/app/layout.tsx +34 -0
  354. package/codeyam-cli/templates/nextjs-prisma-supabase/app/lib/prisma.ts +20 -0
  355. package/codeyam-cli/templates/nextjs-prisma-supabase/app/lib/supabase.ts +12 -0
  356. package/codeyam-cli/templates/nextjs-prisma-supabase/app/page.tsx +10 -0
  357. package/codeyam-cli/templates/nextjs-prisma-supabase/env +9 -0
  358. package/codeyam-cli/templates/nextjs-prisma-supabase/eslint.config.mjs +11 -0
  359. package/codeyam-cli/templates/nextjs-prisma-supabase/gitignore +40 -0
  360. package/codeyam-cli/templates/nextjs-prisma-supabase/next.config.ts +11 -0
  361. package/codeyam-cli/templates/nextjs-prisma-supabase/package.json +37 -0
  362. package/codeyam-cli/templates/nextjs-prisma-supabase/postcss.config.mjs +7 -0
  363. package/codeyam-cli/templates/nextjs-prisma-supabase/prisma/schema.prisma +27 -0
  364. package/codeyam-cli/templates/nextjs-prisma-supabase/prisma/seed.ts +39 -0
  365. package/codeyam-cli/templates/nextjs-prisma-supabase/prisma.config.ts +12 -0
  366. package/codeyam-cli/templates/nextjs-prisma-supabase/tsconfig.json +34 -0
  367. package/codeyam-cli/templates/seed-adapters/supabase.ts +282 -0
  368. package/codeyam-cli/templates/skills/codeyam-dev-mode/SKILL.md +1 -1
  369. package/codeyam-cli/templates/skills/codeyam-editor/SKILL.md +90 -9
  370. package/package.json +2 -1
  371. package/packages/database/src/lib/kysely/tables/editorScenariosTable.js +101 -0
  372. package/packages/database/src/lib/kysely/tables/editorScenariosTable.js.map +1 -1
  373. package/packages/database/src/lib/loadEntities.js +0 -6
  374. package/packages/database/src/lib/loadEntities.js.map +1 -1
  375. package/packages/database/src/lib/updateCommitMetadata.js +0 -25
  376. package/packages/database/src/lib/updateCommitMetadata.js.map +1 -1
  377. package/packages/types/src/enums/ProjectFramework.js +2 -0
  378. package/packages/types/src/enums/ProjectFramework.js.map +1 -1
  379. package/codeyam-cli/src/webserver/build/client/assets/CopyButton-DmJveP3T.js +0 -1
  380. package/codeyam-cli/src/webserver/build/client/assets/EntityTypeBadge-g3saevPb.js +0 -1
  381. package/codeyam-cli/src/webserver/build/client/assets/InlineSpinner-Bu6c6aDe.js +0 -1
  382. package/codeyam-cli/src/webserver/build/client/assets/InteractivePreview-DYFW3lDD.js +0 -25
  383. package/codeyam-cli/src/webserver/build/client/assets/LibraryFunctionPreview-DLeucoVX.js +0 -3
  384. package/codeyam-cli/src/webserver/build/client/assets/SafeScreenshot-BED4B6sP.js +0 -1
  385. package/codeyam-cli/src/webserver/build/client/assets/Spinner-Bb5uFQ5V.js +0 -34
  386. package/codeyam-cli/src/webserver/build/client/assets/TruncatedFilePath-C8OKAR5x.js +0 -1
  387. package/codeyam-cli/src/webserver/build/client/assets/ViewportInspectBar-oAf2Kqsf.js +0 -1
  388. package/codeyam-cli/src/webserver/build/client/assets/chunk-JZWAC4HX-C4pqxYJB.js +0 -51
  389. package/codeyam-cli/src/webserver/build/client/assets/createLucideIcon-CMT1jU2q.js +0 -21
  390. package/codeyam-cli/src/webserver/build/client/assets/dev.empty-CltMNppm.js +0 -1
  391. package/codeyam-cli/src/webserver/build/client/assets/editor-Rfq_y0VR.js +0 -10
  392. package/codeyam-cli/src/webserver/build/client/assets/editorPreview-GNwaLSmC.js +0 -41
  393. package/codeyam-cli/src/webserver/build/client/assets/entity._sha.scenarios._scenarioId.dev-CCa2trIL.js +0 -6
  394. package/codeyam-cli/src/webserver/build/client/assets/entity._sha.scenarios._scenarioId.fullscreen-CF164ouH.js +0 -6
  395. package/codeyam-cli/src/webserver/build/client/assets/entity._sha_.create-scenario-p9hhkjJM.js +0 -6
  396. package/codeyam-cli/src/webserver/build/client/assets/fileTableUtils-cPo8LiG3.js +0 -1
  397. package/codeyam-cli/src/webserver/build/client/assets/files-DO4CZ16O.js +0 -1
  398. package/codeyam-cli/src/webserver/build/client/assets/git-CdN8sCqs.js +0 -1
  399. package/codeyam-cli/src/webserver/build/client/assets/globals-Bd0cs8vw.css +0 -1
  400. package/codeyam-cli/src/webserver/build/client/assets/labs-Zk7ryIM1.js +0 -1
  401. package/codeyam-cli/src/webserver/build/client/assets/manifest-9ab0aba3.js +0 -1
  402. package/codeyam-cli/src/webserver/build/client/assets/memory-Dg0mvYrI.js +0 -96
  403. package/codeyam-cli/src/webserver/build/client/assets/root-3ciuWk-c.js +0 -67
  404. package/codeyam-cli/src/webserver/build/client/assets/settings-DfuTtcJP.js +0 -1
  405. package/codeyam-cli/src/webserver/build/client/assets/simulations-B3aOzpCZ.js +0 -1
  406. package/codeyam-cli/src/webserver/build/client/assets/useCustomSizes-CrAK28Bc.js +0 -1
  407. package/codeyam-cli/src/webserver/build/client/assets/useReportContext-O-jkvSPx.js +0 -1
  408. package/codeyam-cli/src/webserver/build/client/assets/useToast-9FIWuYfK.js +0 -1
  409. package/codeyam-cli/src/webserver/build/server/assets/index-DCxIbVvl.js +0 -1
  410. package/codeyam-cli/src/webserver/build/server/assets/server-build-E-peu3XZ.js +0 -367
@@ -1,5 +1,5 @@
1
- import { buildReverseDependencyGraph, classifyDirectChanges, computeEntityChangeStatus, buildChangedFilesMap, pageNameFromUrl, buildEntityInfosFromScenarios, buildEntityInfosFromGlossary, filterGroupsByChangeStatus, filterGlossaryByChangeStatus, filterScenarioScreenshotsByChangeStatus, } from "../entityChangeStatus.js";
2
- import { scanPageFilePaths, detectFirstFeature, readFeatureStartedAt, } from "../entityChangeStatus.server.js";
1
+ import { buildReverseDependencyGraph, classifyDirectChanges, computeEntityChangeStatus, buildChangedFilesMap, pageNameFromUrl, isIsolationUrl, scenarioEntityName, buildEntityInfosFromScenarios, buildEntityInfosFromGlossary, filterGroupsByChangeStatus, filterGlossaryByChangeStatus, filterScenarioScreenshotsByChangeStatus, } from "../entityChangeStatus.js";
2
+ import { scanPageFilePaths, detectFirstFeature, readFeatureStartedAt, readEditorStep, readEditorSessionId, } from "../entityChangeStatus.server.js";
3
3
  import * as fsModule from 'fs';
4
4
  import * as pathModule from 'path';
5
5
  import * as os from 'os';
@@ -128,10 +128,20 @@ describe('entityChangeStatus', () => {
128
128
  // This is an edge case — empty string is falsy so returns Home
129
129
  expect(pageNameFromUrl('')).toBe('Home');
130
130
  });
131
+ it('should return Home for root path with query string', () => {
132
+ expect(pageNameFromUrl('/?demo=empty')).toBe('Home');
133
+ expect(pageNameFromUrl('/?state=seeded')).toBe('Home');
134
+ expect(pageNameFromUrl('/?tab=overview&sort=date')).toBe('Home');
135
+ });
131
136
  it('should handle dynamic route segments', () => {
132
137
  expect(pageNameFromUrl('/drinks/[id]')).toBe('Drinks');
133
138
  expect(pageNameFromUrl('/users/[userId]/posts')).toBe('Users');
134
139
  });
140
+ it('should strip file extensions from URL segments', () => {
141
+ expect(pageNameFromUrl('/library.html')).toBe('Library');
142
+ expect(pageNameFromUrl('/about.htm')).toBe('About');
143
+ expect(pageNameFromUrl('/library.html?article=a1')).toBe('Library');
144
+ });
135
145
  });
136
146
  // ── buildEntityInfosFromScenarios ─────────────────────────────────────
137
147
  describe('buildEntityInfosFromScenarios', () => {
@@ -1044,6 +1054,34 @@ describe('entityChangeStatus', () => {
1044
1054
  expect(result).toHaveLength(1);
1045
1055
  expect(result[0].importedBy).toBeDefined();
1046
1056
  });
1057
+ it('should use displayName for non-app/ pageFilePath instead of collapsing to Home', () => {
1058
+ // Margo bug: Chrome extension has two entry points:
1059
+ // "/" → src/popup/App.tsx (Home)
1060
+ // "/library.html" → src/library/FullPageLibrary.tsx (Library)
1061
+ // Without displayName, buildRoutePattern('src/library/FullPageLibrary.tsx')
1062
+ // returns '/' since it doesn't start with 'app/', collapsing both to "Home".
1063
+ const scenarios = [
1064
+ {
1065
+ componentName: null,
1066
+ componentPath: null,
1067
+ url: '/',
1068
+ pageFilePath: 'src/popup/App.tsx',
1069
+ displayName: 'Home',
1070
+ },
1071
+ {
1072
+ componentName: null,
1073
+ componentPath: null,
1074
+ url: '/library.html',
1075
+ pageFilePath: 'src/library/FullPageLibrary.tsx',
1076
+ displayName: 'Library',
1077
+ },
1078
+ ];
1079
+ const result = buildEntityInfosFromScenarios(scenarios, {}, []);
1080
+ expect(result).toHaveLength(2);
1081
+ expect(result.map((e) => e.name).sort()).toEqual(['Home', 'Library']);
1082
+ expect(result.find((e) => e.name === 'Home')?.filePath).toBe('src/popup/App.tsx');
1083
+ expect(result.find((e) => e.name === 'Library')?.filePath).toBe('src/library/FullPageLibrary.tsx');
1084
+ });
1047
1085
  });
1048
1086
  // ── Full pipeline regression tests ───────────────────────────────
1049
1087
  describe('full pipeline regression tests', () => {
@@ -1298,81 +1336,274 @@ describe('entityChangeStatus', () => {
1298
1336
  expect(result).toEqual([]);
1299
1337
  });
1300
1338
  });
1339
+ // ── scenarioEntityName ──────────────────────────────────────────────
1340
+ describe('scenarioEntityName', () => {
1341
+ it('should return componentName when set', () => {
1342
+ expect(scenarioEntityName({
1343
+ componentName: 'ReviewCard',
1344
+ url: '/isolated-components/ReviewCard?s=Default',
1345
+ })).toBe('ReviewCard');
1346
+ });
1347
+ it('should return page name from URL when componentName is null', () => {
1348
+ expect(scenarioEntityName({ componentName: null, url: '/drinks/1' })).toBe('Drinks');
1349
+ });
1350
+ it('should return Home for root URL when componentName is null', () => {
1351
+ expect(scenarioEntityName({ componentName: null, url: '/' })).toBe('Home');
1352
+ });
1353
+ it('should return Home when both componentName and url are null', () => {
1354
+ expect(scenarioEntityName({ componentName: null, url: null })).toBe('Home');
1355
+ });
1356
+ it('should return page name when componentName is undefined', () => {
1357
+ expect(scenarioEntityName({ url: '/settings' })).toBe('Settings');
1358
+ });
1359
+ it('should return Home when no fields provided', () => {
1360
+ expect(scenarioEntityName({})).toBe('Home');
1361
+ });
1362
+ });
1301
1363
  // ── filterScenarioScreenshotsByChangeStatus ───────────────────────────
1302
1364
  describe('filterScenarioScreenshotsByChangeStatus', () => {
1303
- const screenshots = [
1304
- { name: 'Busy Month', path: 'busy.png' },
1305
- { name: 'Empty Calendar', path: 'empty.png' },
1306
- { name: 'EventPill - Default', path: 'pill.png' },
1307
- { name: 'ErrorState - Server Error', path: 'error.png' },
1308
- { name: 'CalendarGrid - Default', path: 'grid.png' },
1309
- ];
1310
- const pageNames = new Set(['Home']);
1311
1365
  it('should return all screenshots when entityChangeStatus is undefined', () => {
1312
- expect(filterScenarioScreenshotsByChangeStatus(screenshots, undefined, pageNames)).toEqual(screenshots);
1366
+ const screenshots = [
1367
+ { name: 'Busy Month', path: 'busy.png', componentName: null, url: '/' },
1368
+ {
1369
+ name: 'EventPill - Default',
1370
+ path: 'pill.png',
1371
+ componentName: 'EventPill',
1372
+ url: '/isolated-components/EventPill',
1373
+ },
1374
+ ];
1375
+ expect(filterScenarioScreenshotsByChangeStatus(screenshots, undefined)).toEqual(screenshots);
1313
1376
  });
1314
1377
  it('should return all screenshots when entityChangeStatus is empty', () => {
1315
- expect(filterScenarioScreenshotsByChangeStatus(screenshots, {}, pageNames)).toEqual(screenshots);
1378
+ const screenshots = [
1379
+ { name: 'Busy Month', path: 'busy.png', componentName: null, url: '/' },
1380
+ ];
1381
+ expect(filterScenarioScreenshotsByChangeStatus(screenshots, {})).toEqual(screenshots);
1316
1382
  });
1317
- it('should filter screenshots to only those matching entities with a change status', () => {
1383
+ it('should include component scenarios whose entity has a change status', () => {
1384
+ const screenshots = [
1385
+ {
1386
+ name: 'EventPill - Default',
1387
+ path: 'pill.png',
1388
+ componentName: 'EventPill',
1389
+ url: '/isolated-components/EventPill',
1390
+ },
1391
+ {
1392
+ name: 'ErrorState - Server Error',
1393
+ path: 'error.png',
1394
+ componentName: 'ErrorState',
1395
+ url: '/isolated-components/ErrorState',
1396
+ },
1397
+ {
1398
+ name: 'CalendarGrid - Default',
1399
+ path: 'grid.png',
1400
+ componentName: 'CalendarGrid',
1401
+ url: '/isolated-components/CalendarGrid',
1402
+ },
1403
+ ];
1318
1404
  const status = {
1319
1405
  ErrorState: { status: 'new' },
1320
- Home: { status: 'edited' },
1406
+ EventPill: { status: 'edited' },
1321
1407
  };
1322
- const result = filterScenarioScreenshotsByChangeStatus(screenshots, status, pageNames);
1323
- // App-level screenshots (no prefix) match because Home page has status
1324
- // "ErrorState - *" matches the ErrorState entity
1408
+ const result = filterScenarioScreenshotsByChangeStatus(screenshots, status);
1325
1409
  expect(result.map((s) => s.name)).toEqual([
1326
- 'Busy Month',
1327
- 'Empty Calendar',
1410
+ 'EventPill - Default',
1328
1411
  'ErrorState - Server Error',
1329
1412
  ]);
1330
1413
  });
1331
- it('should handle screenshots without component prefix (app-level)', () => {
1332
- const appScreenshots = [
1333
- { name: 'Busy Month', path: 'busy.png' },
1334
- { name: 'Empty Calendar', path: 'empty.png' },
1414
+ it('should include page scenarios whose page entity has a change status', () => {
1415
+ const screenshots = [
1416
+ { name: 'Busy Month', path: 'busy.png', componentName: null, url: '/' },
1417
+ {
1418
+ name: 'Empty Calendar',
1419
+ path: 'empty.png',
1420
+ componentName: null,
1421
+ url: '/',
1422
+ },
1335
1423
  ];
1336
1424
  const status = {
1337
1425
  Home: { status: 'edited' },
1338
1426
  };
1339
- const result = filterScenarioScreenshotsByChangeStatus(appScreenshots, status, pageNames);
1340
- expect(result).toEqual(appScreenshots);
1427
+ const result = filterScenarioScreenshotsByChangeStatus(screenshots, status);
1428
+ expect(result).toEqual(screenshots);
1341
1429
  });
1342
- it('should exclude app-level screenshots when no page entity has a status', () => {
1430
+ it('should exclude page scenarios when their page entity has no status', () => {
1431
+ const screenshots = [
1432
+ { name: 'Busy Month', path: 'busy.png', componentName: null, url: '/' },
1433
+ {
1434
+ name: 'EventPill - Default',
1435
+ path: 'pill.png',
1436
+ componentName: 'EventPill',
1437
+ url: '/isolated-components/EventPill',
1438
+ },
1439
+ ];
1343
1440
  const status = {
1344
1441
  SomeComponent: { status: 'new' },
1345
1442
  };
1346
- const result = filterScenarioScreenshotsByChangeStatus(screenshots, status, pageNames);
1347
- // No page entity has status, so app-level screenshots are excluded
1348
- // SomeComponent doesn't match any screenshot prefix either
1443
+ const result = filterScenarioScreenshotsByChangeStatus(screenshots, status);
1444
+ // Home page has no status, so "Busy Month" is excluded
1445
+ // SomeComponent doesn't match EventPill either
1349
1446
  expect(result).toEqual([]);
1350
1447
  });
1351
- it('should include app-level and matching component screenshots together', () => {
1448
+ it('should include both page and component scenarios when both entities have status', () => {
1449
+ const screenshots = [
1450
+ { name: 'Busy Month', path: 'busy.png', componentName: null, url: '/' },
1451
+ {
1452
+ name: 'Empty Calendar',
1453
+ path: 'empty.png',
1454
+ componentName: null,
1455
+ url: '/',
1456
+ },
1457
+ {
1458
+ name: 'EventPill - Default',
1459
+ path: 'pill.png',
1460
+ componentName: 'EventPill',
1461
+ url: '/isolated-components/EventPill',
1462
+ },
1463
+ ];
1352
1464
  const status = {
1353
1465
  Home: { status: 'edited' },
1354
1466
  EventPill: { status: 'new' },
1355
1467
  };
1356
- const result = filterScenarioScreenshotsByChangeStatus(screenshots, status, pageNames);
1468
+ const result = filterScenarioScreenshotsByChangeStatus(screenshots, status);
1357
1469
  expect(result.map((s) => s.name)).toEqual([
1358
1470
  'Busy Month',
1359
1471
  'Empty Calendar',
1360
1472
  'EventPill - Default',
1361
1473
  ]);
1362
1474
  });
1363
- it('should handle legacy "App - " prefixed scenario names (backward compat)', () => {
1364
- const legacyScreenshots = [
1365
- { name: 'App - Busy Month', path: 'busy.png' },
1366
- { name: 'App - Empty Calendar', path: 'empty.png' },
1475
+ it('should handle page scenarios with dashes in names using URL, not name parsing', () => {
1476
+ // The original bug: "Drink Detail - Earl Grey" was parsed as component "Drink Detail"
1477
+ const screenshots = [
1478
+ {
1479
+ name: 'Drink Detail - Earl Grey',
1480
+ path: 'earl.png',
1481
+ componentName: null,
1482
+ url: '/drinks/1',
1483
+ },
1484
+ {
1485
+ name: 'Drink Detail - No Reviews',
1486
+ path: 'no-reviews.png',
1487
+ componentName: null,
1488
+ url: '/drinks/1',
1489
+ },
1490
+ {
1491
+ name: 'ReviewCard - Default',
1492
+ path: 'review.png',
1493
+ componentName: 'ReviewCard',
1494
+ url: '/isolated-components/ReviewCard',
1495
+ },
1496
+ {
1497
+ name: 'Full Catalog',
1498
+ path: 'catalog.png',
1499
+ componentName: null,
1500
+ url: '/',
1501
+ },
1502
+ ];
1503
+ const status = {
1504
+ Drinks: { status: 'new' },
1505
+ ReviewCard: { status: 'new' },
1506
+ Home: { status: 'edited' },
1507
+ };
1508
+ const result = filterScenarioScreenshotsByChangeStatus(screenshots, status);
1509
+ expect(result.map((s) => s.name)).toEqual([
1510
+ 'Drink Detail - Earl Grey',
1511
+ 'Drink Detail - No Reviews',
1512
+ 'ReviewCard - Default',
1513
+ 'Full Catalog',
1514
+ ]);
1515
+ });
1516
+ it('should exclude page scenarios for unchanged pages even with dashes in names', () => {
1517
+ const screenshots = [
1518
+ {
1519
+ name: 'Drink Detail - Earl Grey',
1520
+ path: 'earl.png',
1521
+ componentName: null,
1522
+ url: '/drinks/1',
1523
+ },
1524
+ {
1525
+ name: 'ReviewCard - Default',
1526
+ path: 'review.png',
1527
+ componentName: 'ReviewCard',
1528
+ url: '/isolated-components/ReviewCard',
1529
+ },
1530
+ ];
1531
+ const status = {
1532
+ ReviewCard: { status: 'edited' },
1533
+ };
1534
+ const result = filterScenarioScreenshotsByChangeStatus(screenshots, status);
1535
+ // Drinks page has no status, so "Drink Detail" page scenarios are excluded
1536
+ expect(result.map((s) => s.name)).toEqual(['ReviewCard - Default']);
1537
+ });
1538
+ it('should use componentName for matching, not the name prefix', () => {
1539
+ const screenshots = [
1540
+ {
1541
+ name: 'Card Loading State - Spinner',
1542
+ path: 'spinner.png',
1543
+ componentName: 'LoadingCard',
1544
+ url: '/isolated-components/LoadingCard',
1545
+ },
1546
+ ];
1547
+ const status = {
1548
+ LoadingCard: { status: 'new' },
1549
+ };
1550
+ const result = filterScenarioScreenshotsByChangeStatus(screenshots, status);
1551
+ expect(result.map((s) => s.name)).toEqual([
1552
+ 'Card Loading State - Spinner',
1553
+ ]);
1554
+ });
1555
+ it('should exclude component scenario when its entity has no status', () => {
1556
+ const screenshots = [
1557
+ {
1558
+ name: 'Card Loading State - Spinner',
1559
+ path: 'spinner.png',
1560
+ componentName: 'LoadingCard',
1561
+ url: '/isolated-components/LoadingCard',
1562
+ },
1563
+ ];
1564
+ const status = {
1565
+ SomeOtherComponent: { status: 'new' },
1566
+ };
1567
+ const result = filterScenarioScreenshotsByChangeStatus(screenshots, status);
1568
+ expect(result).toEqual([]);
1569
+ });
1570
+ it('should filter per-page independently (not all-or-nothing for pages)', () => {
1571
+ // Only Drinks page changed, not Home — scenarios for each page should be filtered independently
1572
+ const screenshots = [
1573
+ {
1574
+ name: 'Full Catalog',
1575
+ path: 'catalog.png',
1576
+ componentName: null,
1577
+ url: '/',
1578
+ },
1579
+ {
1580
+ name: 'Drink Detail - Earl Grey',
1581
+ path: 'earl.png',
1582
+ componentName: null,
1583
+ url: '/drinks/1',
1584
+ },
1585
+ ];
1586
+ const status = {
1587
+ Drinks: { status: 'new' },
1588
+ };
1589
+ const result = filterScenarioScreenshotsByChangeStatus(screenshots, status);
1590
+ // Home page has no status, so "Full Catalog" is excluded
1591
+ // Drinks page has status, so "Drink Detail" is included
1592
+ expect(result.map((s) => s.name)).toEqual(['Drink Detail - Earl Grey']);
1593
+ });
1594
+ it('should fall back to name-based parsing when componentName is not present (backward compat)', () => {
1595
+ const screenshots = [
1367
1596
  { name: 'EventPill - Default', path: 'pill.png' },
1597
+ { name: 'Full Calendar', path: 'cal.png' },
1368
1598
  ];
1369
1599
  const status = {
1600
+ EventPill: { status: 'new' },
1370
1601
  Home: { status: 'edited' },
1371
1602
  };
1372
- const result = filterScenarioScreenshotsByChangeStatus(legacyScreenshots, status, pageNames);
1603
+ const result = filterScenarioScreenshotsByChangeStatus(screenshots, status);
1373
1604
  expect(result.map((s) => s.name)).toEqual([
1374
- 'App - Busy Month',
1375
- 'App - Empty Calendar',
1605
+ 'EventPill - Default',
1606
+ 'Full Calendar',
1376
1607
  ]);
1377
1608
  });
1378
1609
  });
@@ -1398,7 +1629,7 @@ describe('entityChangeStatus', () => {
1398
1629
  });
1399
1630
  fsModule.writeFileSync(pathModule.join(appDir, 'settings', 'profile', 'page.tsx'), '');
1400
1631
  const result = scanPageFilePaths(tempDir);
1401
- expect(result).toEqual({
1632
+ expect(result.map).toEqual({
1402
1633
  Home: 'app/page.tsx',
1403
1634
  Drinks: 'app/drinks/page.tsx',
1404
1635
  Settings: 'app/settings/profile/page.tsx',
@@ -1409,20 +1640,96 @@ describe('entityChangeStatus', () => {
1409
1640
  fsModule.mkdirSync(appDir, { recursive: true });
1410
1641
  fsModule.writeFileSync(pathModule.join(appDir, 'page.js'), '');
1411
1642
  const result = scanPageFilePaths(tempDir);
1412
- expect(result).toEqual({ Home: 'app/page.js' });
1643
+ expect(result.map).toEqual({ Home: 'app/page.js' });
1413
1644
  });
1414
- it('should skip codeyam-isolate directories', () => {
1645
+ it('should skip isolated-components directories', () => {
1415
1646
  const appDir = pathModule.join(tempDir, 'app');
1416
- fsModule.mkdirSync(pathModule.join(appDir, 'codeyam-isolate', 'test'), {
1647
+ fsModule.mkdirSync(pathModule.join(appDir, 'isolated-components', 'test'), {
1417
1648
  recursive: true,
1418
1649
  });
1419
- fsModule.writeFileSync(pathModule.join(appDir, 'codeyam-isolate', 'test', 'page.tsx'), '');
1650
+ fsModule.writeFileSync(pathModule.join(appDir, 'isolated-components', 'test', 'page.tsx'), '');
1420
1651
  const result = scanPageFilePaths(tempDir);
1421
- expect(result).toEqual({});
1652
+ expect(result.map).toEqual({});
1422
1653
  });
1423
1654
  it('should return empty map when app/ does not exist', () => {
1424
1655
  const result = scanPageFilePaths(tempDir);
1425
- expect(result).toEqual({});
1656
+ expect(result.map).toEqual({});
1657
+ });
1658
+ // ── Expo Router support ──────────────────────────────────────────────
1659
+ it('should find index.tsx as Home page (Expo Router)', () => {
1660
+ const appDir = pathModule.join(tempDir, 'app');
1661
+ fsModule.mkdirSync(appDir, { recursive: true });
1662
+ fsModule.writeFileSync(pathModule.join(appDir, 'index.tsx'), '');
1663
+ const result = scanPageFilePaths(tempDir);
1664
+ expect(result.map).toEqual({ Home: 'app/index.tsx' });
1665
+ });
1666
+ it('should find named route files as pages (Expo Router)', () => {
1667
+ const appDir = pathModule.join(tempDir, 'app');
1668
+ fsModule.mkdirSync(appDir, { recursive: true });
1669
+ fsModule.writeFileSync(pathModule.join(appDir, 'index.tsx'), '');
1670
+ fsModule.writeFileSync(pathModule.join(appDir, 'add-tea.tsx'), '');
1671
+ fsModule.mkdirSync(pathModule.join(appDir, 'tea'), { recursive: true });
1672
+ fsModule.writeFileSync(pathModule.join(appDir, 'tea', '[id].tsx'), '');
1673
+ const result = scanPageFilePaths(tempDir);
1674
+ expect(result.map).toEqual({
1675
+ Home: 'app/index.tsx',
1676
+ 'Add-tea': 'app/add-tea.tsx',
1677
+ Tea: 'app/tea/[id].tsx',
1678
+ });
1679
+ });
1680
+ it('should skip _layout.tsx files (Expo Router)', () => {
1681
+ const appDir = pathModule.join(tempDir, 'app');
1682
+ fsModule.mkdirSync(appDir, { recursive: true });
1683
+ fsModule.writeFileSync(pathModule.join(appDir, '_layout.tsx'), '');
1684
+ fsModule.writeFileSync(pathModule.join(appDir, 'index.tsx'), '');
1685
+ const result = scanPageFilePaths(tempDir);
1686
+ expect(result.map).toEqual({ Home: 'app/index.tsx' });
1687
+ });
1688
+ it('should handle route groups like (tabs) transparently (Expo Router)', () => {
1689
+ const appDir = pathModule.join(tempDir, 'app');
1690
+ const tabsDir = pathModule.join(appDir, '(tabs)');
1691
+ fsModule.mkdirSync(tabsDir, { recursive: true });
1692
+ fsModule.writeFileSync(pathModule.join(tabsDir, '_layout.tsx'), '');
1693
+ fsModule.writeFileSync(pathModule.join(tabsDir, 'index.tsx'), '');
1694
+ fsModule.writeFileSync(pathModule.join(tabsDir, 'settings.tsx'), '');
1695
+ const result = scanPageFilePaths(tempDir);
1696
+ expect(result.map).toEqual({
1697
+ Home: 'app/(tabs)/index.tsx',
1698
+ Settings: 'app/(tabs)/settings.tsx',
1699
+ });
1700
+ });
1701
+ it('should prefer page.tsx over index.tsx when both exist (Next.js priority)', () => {
1702
+ const appDir = pathModule.join(tempDir, 'app');
1703
+ fsModule.mkdirSync(appDir, { recursive: true });
1704
+ fsModule.writeFileSync(pathModule.join(appDir, 'page.tsx'), '');
1705
+ fsModule.writeFileSync(pathModule.join(appDir, 'index.tsx'), '');
1706
+ const result = scanPageFilePaths(tempDir);
1707
+ expect(result.map).toEqual({ Home: 'app/page.tsx' });
1708
+ });
1709
+ it('should include all page files in allFiles when multiple routes share a page name', () => {
1710
+ const appDir = pathModule.join(tempDir, 'app');
1711
+ fsModule.mkdirSync(appDir, { recursive: true });
1712
+ fsModule.writeFileSync(pathModule.join(appDir, 'page.tsx'), '');
1713
+ fsModule.mkdirSync(pathModule.join(appDir, 'feedback', '[id]', 'edit'), {
1714
+ recursive: true,
1715
+ });
1716
+ fsModule.mkdirSync(pathModule.join(appDir, 'feedback', 'new'), {
1717
+ recursive: true,
1718
+ });
1719
+ fsModule.writeFileSync(pathModule.join(appDir, 'feedback', '[id]', 'page.tsx'), '');
1720
+ fsModule.writeFileSync(pathModule.join(appDir, 'feedback', '[id]', 'edit', 'page.tsx'), '');
1721
+ fsModule.writeFileSync(pathModule.join(appDir, 'feedback', 'new', 'page.tsx'), '');
1722
+ const result = scanPageFilePaths(tempDir);
1723
+ // Map picks one canonical file per page name
1724
+ expect(result.map['Home']).toBe('app/page.tsx');
1725
+ expect(result.map['Feedback']).toBeDefined();
1726
+ // allFiles includes ALL page files, even those that collided on page name
1727
+ expect(result.allFiles.sort()).toEqual([
1728
+ 'app/feedback/[id]/edit/page.tsx',
1729
+ 'app/feedback/[id]/page.tsx',
1730
+ 'app/feedback/new/page.tsx',
1731
+ 'app/page.tsx',
1732
+ ]);
1426
1733
  });
1427
1734
  });
1428
1735
  // ── detectFirstFeature ─────────────────────────────────────────────────
@@ -1489,5 +1796,294 @@ describe('entityChangeStatus', () => {
1489
1796
  expect(readFeatureStartedAt(tempDir)).toBeNull();
1490
1797
  });
1491
1798
  });
1799
+ // ── readEditorStep ──────────────────────────────────────────────────────
1800
+ describe('readEditorStep', () => {
1801
+ let tempDir;
1802
+ beforeEach(() => {
1803
+ tempDir = fsModule.mkdtempSync(pathModule.join(os.tmpdir(), 'editorstep-test-'));
1804
+ fsModule.mkdirSync(pathModule.join(tempDir, '.codeyam'), {
1805
+ recursive: true,
1806
+ });
1807
+ });
1808
+ afterEach(() => {
1809
+ fsModule.rmSync(tempDir, { recursive: true, force: true });
1810
+ });
1811
+ it('should return step and label from editor-step.json', () => {
1812
+ fsModule.writeFileSync(pathModule.join(tempDir, '.codeyam', 'editor-step.json'), JSON.stringify({ step: 3, label: 'Confirm', feature: 'Add login' }));
1813
+ expect(readEditorStep(tempDir)).toEqual({ step: 3, label: 'Confirm' });
1814
+ });
1815
+ it('should return null when file does not exist', () => {
1816
+ expect(readEditorStep(tempDir)).toBeNull();
1817
+ });
1818
+ it('should return null when step field is missing', () => {
1819
+ fsModule.writeFileSync(pathModule.join(tempDir, '.codeyam', 'editor-step.json'), JSON.stringify({ feature: 'Add login' }));
1820
+ expect(readEditorStep(tempDir)).toBeNull();
1821
+ });
1822
+ it('should return null for invalid JSON', () => {
1823
+ fsModule.writeFileSync(pathModule.join(tempDir, '.codeyam', 'editor-step.json'), 'not json');
1824
+ expect(readEditorStep(tempDir)).toBeNull();
1825
+ });
1826
+ });
1827
+ // ── readEditorSessionId ─────────────────────────────────────────────────
1828
+ describe('readEditorSessionId', () => {
1829
+ let tempDir;
1830
+ beforeEach(() => {
1831
+ tempDir = fsModule.mkdtempSync(pathModule.join(os.tmpdir(), 'sessionid-test-'));
1832
+ fsModule.mkdirSync(pathModule.join(tempDir, '.codeyam'), {
1833
+ recursive: true,
1834
+ });
1835
+ });
1836
+ afterEach(() => {
1837
+ fsModule.rmSync(tempDir, { recursive: true, force: true });
1838
+ });
1839
+ it('should return session ID from claude-session-id.txt', () => {
1840
+ const uuid = '550e8400-e29b-41d4-a716-446655440000';
1841
+ fsModule.writeFileSync(pathModule.join(tempDir, '.codeyam', 'claude-session-id.txt'), uuid);
1842
+ expect(readEditorSessionId(tempDir)).toBe(uuid);
1843
+ });
1844
+ it('should trim whitespace from session ID', () => {
1845
+ fsModule.writeFileSync(pathModule.join(tempDir, '.codeyam', 'claude-session-id.txt'), ' abc-123 \n');
1846
+ expect(readEditorSessionId(tempDir)).toBe('abc-123');
1847
+ });
1848
+ it('should return null when file does not exist', () => {
1849
+ expect(readEditorSessionId(tempDir)).toBeNull();
1850
+ });
1851
+ it('should return null when file is empty', () => {
1852
+ fsModule.writeFileSync(pathModule.join(tempDir, '.codeyam', 'claude-session-id.txt'), '');
1853
+ expect(readEditorSessionId(tempDir)).toBeNull();
1854
+ });
1855
+ });
1856
+ describe('isIsolationUrl', () => {
1857
+ it('should return true for /isolated-components/ URLs', () => {
1858
+ expect(isIsolationUrl('/isolated-components/Foo')).toBe(true);
1859
+ expect(isIsolationUrl('/isolated-components/Foo?s=Default')).toBe(true);
1860
+ });
1861
+ it('should return true for /codeyam-isolate/ URLs', () => {
1862
+ expect(isIsolationUrl('/codeyam-isolate/Foo')).toBe(true);
1863
+ expect(isIsolationUrl('/codeyam-isolate/Foo?s=Default')).toBe(true);
1864
+ });
1865
+ it('should return false for regular page URLs', () => {
1866
+ expect(isIsolationUrl('/library')).toBe(false);
1867
+ expect(isIsolationUrl('/drinks/1')).toBe(false);
1868
+ expect(isIsolationUrl('/')).toBe(false);
1869
+ });
1870
+ it('should return false for null/undefined', () => {
1871
+ expect(isIsolationUrl(null)).toBe(false);
1872
+ expect(isIsolationUrl(undefined)).toBe(false);
1873
+ });
1874
+ });
1875
+ // ── buildReverseDependencyGraph: filePath-based importer resolution ────
1876
+ describe('buildReverseDependencyGraph — filePath-based resolution', () => {
1877
+ it('should resolve importer via filePath when DB name differs from display name', () => {
1878
+ // In Margo Reader: ArticleTable's importedBy lists "LibraryPage" as importer name
1879
+ // under filePath "app/library/page.tsx". But the entity list has name "Library"
1880
+ // with filePath "app/library/page.tsx". The reverse graph should map
1881
+ // ArticleTable → {"Library"} (not "LibraryPage").
1882
+ const entities = [
1883
+ {
1884
+ name: 'ArticleTable',
1885
+ filePath: 'app/components/ArticleTable.tsx',
1886
+ importedBy: {
1887
+ 'app/library/page.tsx': {
1888
+ LibraryPage: { shas: ['sha1'] },
1889
+ },
1890
+ },
1891
+ },
1892
+ {
1893
+ name: 'Library',
1894
+ filePath: 'app/library/page.tsx',
1895
+ },
1896
+ ];
1897
+ const graph = buildReverseDependencyGraph(entities);
1898
+ expect(graph.get('ArticleTable')).toEqual(new Set(['Library']));
1899
+ });
1900
+ it('should fall back to DB importer name when filePath has no matching entity', () => {
1901
+ // When the importer filePath doesn't match any entity in the list,
1902
+ // fall back to the DB's importer name.
1903
+ const entities = [
1904
+ {
1905
+ name: 'Button',
1906
+ filePath: 'components/Button.tsx',
1907
+ importedBy: {
1908
+ 'components/UnknownPage.tsx': {
1909
+ SomeComponent: { shas: ['sha1'] },
1910
+ },
1911
+ },
1912
+ },
1913
+ ];
1914
+ const graph = buildReverseDependencyGraph(entities);
1915
+ expect(graph.get('Button')).toEqual(new Set(['SomeComponent']));
1916
+ });
1917
+ it('should resolve multiple importers via filePath correctly', () => {
1918
+ const entities = [
1919
+ {
1920
+ name: 'ArticleTableRow',
1921
+ filePath: 'app/components/ArticleTableRow.tsx',
1922
+ importedBy: {
1923
+ 'app/components/ArticleTable.tsx': {
1924
+ ArticleTable: { shas: ['sha1'] },
1925
+ },
1926
+ 'app/library/page.tsx': {
1927
+ LibraryPage: { shas: ['sha2'] },
1928
+ },
1929
+ },
1930
+ },
1931
+ {
1932
+ name: 'ArticleTable',
1933
+ filePath: 'app/components/ArticleTable.tsx',
1934
+ },
1935
+ {
1936
+ name: 'Library',
1937
+ filePath: 'app/library/page.tsx',
1938
+ },
1939
+ ];
1940
+ const graph = buildReverseDependencyGraph(entities);
1941
+ expect(graph.get('ArticleTableRow')).toEqual(new Set(['ArticleTable', 'Library']));
1942
+ });
1943
+ });
1944
+ // ── buildEntityInfosFromScenarios: filePath-based metadata matching ────
1945
+ describe('buildEntityInfosFromScenarios — filePath-based metadata matching', () => {
1946
+ it('should attach importedBy metadata when entity names differ but filePaths match', () => {
1947
+ // Page scenario with displayName "Library" but DB entity named "LibraryPage"
1948
+ // with filePath "app/library/page.tsx". The metadata lookup should match by
1949
+ // filePath and attach importedBy.
1950
+ const scenarios = [
1951
+ {
1952
+ componentName: null,
1953
+ componentPath: null,
1954
+ pageFilePath: 'app/library/page.tsx',
1955
+ url: '/library',
1956
+ displayName: 'Library',
1957
+ },
1958
+ ];
1959
+ const entitiesWithMetadata = [
1960
+ {
1961
+ name: 'LibraryPage',
1962
+ filePath: 'app/library/page.tsx',
1963
+ metadata: {
1964
+ importedBy: {
1965
+ 'app/layout.tsx': {
1966
+ RootLayout: { shas: ['sha1'] },
1967
+ },
1968
+ },
1969
+ },
1970
+ },
1971
+ ];
1972
+ const result = buildEntityInfosFromScenarios(scenarios, {}, entitiesWithMetadata);
1973
+ expect(result).toHaveLength(1);
1974
+ expect(result[0].name).toBe('Library');
1975
+ expect(result[0].importedBy).toEqual({
1976
+ 'app/layout.tsx': {
1977
+ RootLayout: { shas: ['sha1'] },
1978
+ },
1979
+ });
1980
+ });
1981
+ it('should prefer name match over filePath match in multi-entity files', () => {
1982
+ // Bug: app/components/ArticleTable.tsx exports both Props (rowid 33) and
1983
+ // ArticleTable (rowid 34). find() with OR condition matches Props first
1984
+ // via filePath, so ArticleTable gets Props' importedBy (missing the
1985
+ // critical ArticleTable → LibraryPage edge). Fix: try name match first.
1986
+ const scenarios = [
1987
+ {
1988
+ componentName: 'ArticleTable',
1989
+ componentPath: 'app/components/ArticleTable.tsx',
1990
+ },
1991
+ ];
1992
+ const entitiesWithMetadata = [
1993
+ {
1994
+ name: 'Props',
1995
+ filePath: 'app/components/ArticleTable.tsx',
1996
+ metadata: {
1997
+ importedBy: {
1998
+ 'app/components/ArticleTable.tsx': {
1999
+ ArticleTable: { shas: ['sha-internal'] },
2000
+ },
2001
+ },
2002
+ },
2003
+ },
2004
+ {
2005
+ name: 'ArticleTable',
2006
+ filePath: 'app/components/ArticleTable.tsx',
2007
+ metadata: {
2008
+ importedBy: {
2009
+ 'app/library/page.tsx': {
2010
+ LibraryPage: { shas: ['sha1'] },
2011
+ },
2012
+ },
2013
+ },
2014
+ },
2015
+ ];
2016
+ const result = buildEntityInfosFromScenarios(scenarios, {}, entitiesWithMetadata);
2017
+ expect(result).toHaveLength(1);
2018
+ expect(result[0].name).toBe('ArticleTable');
2019
+ // Must get ArticleTable's importedBy (LibraryPage), NOT Props' importedBy
2020
+ expect(result[0].importedBy).toEqual({
2021
+ 'app/library/page.tsx': {
2022
+ LibraryPage: { shas: ['sha1'] },
2023
+ },
2024
+ });
2025
+ });
2026
+ it('should still match by name when names are identical', () => {
2027
+ const scenarios = [
2028
+ {
2029
+ componentName: 'ArticleTable',
2030
+ componentPath: 'app/components/ArticleTable.tsx',
2031
+ },
2032
+ ];
2033
+ const entitiesWithMetadata = [
2034
+ {
2035
+ name: 'ArticleTable',
2036
+ filePath: 'app/components/ArticleTable.tsx',
2037
+ metadata: {
2038
+ importedBy: {
2039
+ 'app/library/page.tsx': {
2040
+ LibraryPage: { shas: ['sha1'] },
2041
+ },
2042
+ },
2043
+ },
2044
+ },
2045
+ ];
2046
+ const result = buildEntityInfosFromScenarios(scenarios, {}, entitiesWithMetadata);
2047
+ expect(result[0].importedBy).toBeDefined();
2048
+ });
2049
+ });
2050
+ // ── End-to-end: filePath resolution enables impact propagation ────
2051
+ describe('end-to-end: filePath resolution enables page impact detection', () => {
2052
+ it('should detect Library page as impacted when ArticleTable is edited', () => {
2053
+ // This is the Margo Reader bug: ArticleTable is edited, Library page
2054
+ // should be detected as impacted because ArticleTable's importedBy
2055
+ // metadata shows it's imported by "app/library/page.tsx" → "Library".
2056
+ const changedFiles = new Map([
2057
+ ['app/components/ArticleTable.tsx', 'edited'],
2058
+ ]);
2059
+ const entities = [
2060
+ {
2061
+ name: 'ArticleTable',
2062
+ filePath: 'app/components/ArticleTable.tsx',
2063
+ importedBy: {
2064
+ 'app/library/page.tsx': {
2065
+ LibraryPage: { shas: ['sha1'] },
2066
+ },
2067
+ },
2068
+ },
2069
+ {
2070
+ name: 'Library',
2071
+ filePath: 'app/library/page.tsx',
2072
+ },
2073
+ ];
2074
+ const result = computeEntityChangeStatus(changedFiles, entities);
2075
+ expect(result['ArticleTable']).toEqual({ status: 'edited' });
2076
+ expect(result['Library']).toEqual({
2077
+ status: 'impacted',
2078
+ impactedBy: [
2079
+ {
2080
+ name: 'ArticleTable',
2081
+ filePath: 'app/components/ArticleTable.tsx',
2082
+ changeType: 'edited',
2083
+ },
2084
+ ],
2085
+ });
2086
+ });
2087
+ });
1492
2088
  });
1493
2089
  //# sourceMappingURL=entityChangeStatus.test.js.map