@codeyam/codeyam-cli 0.1.13 → 0.1.15

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 (95) hide show
  1. package/analyzer-template/.build-info.json +6 -6
  2. package/analyzer-template/log.txt +3 -3
  3. package/codeyam-cli/src/commands/editor.js +62 -27
  4. package/codeyam-cli/src/commands/editor.js.map +1 -1
  5. package/codeyam-cli/src/utils/__tests__/editorAudit.test.js +440 -1
  6. package/codeyam-cli/src/utils/__tests__/editorAudit.test.js.map +1 -1
  7. package/codeyam-cli/src/utils/__tests__/editorEntityHelpers.test.js +58 -4
  8. package/codeyam-cli/src/utils/__tests__/editorEntityHelpers.test.js.map +1 -1
  9. package/codeyam-cli/src/utils/__tests__/editorScenarios.test.js +76 -0
  10. package/codeyam-cli/src/utils/__tests__/editorScenarios.test.js.map +1 -1
  11. package/codeyam-cli/src/utils/__tests__/scenariosManifest.test.js +139 -1
  12. package/codeyam-cli/src/utils/__tests__/scenariosManifest.test.js.map +1 -1
  13. package/codeyam-cli/src/utils/backgroundServer.js +1 -1
  14. package/codeyam-cli/src/utils/backgroundServer.js.map +1 -1
  15. package/codeyam-cli/src/utils/editorAudit.js +107 -6
  16. package/codeyam-cli/src/utils/editorAudit.js.map +1 -1
  17. package/codeyam-cli/src/utils/editorEntityHelpers.js +18 -3
  18. package/codeyam-cli/src/utils/editorEntityHelpers.js.map +1 -1
  19. package/codeyam-cli/src/utils/editorScenarios.js +10 -1
  20. package/codeyam-cli/src/utils/editorScenarios.js.map +1 -1
  21. package/codeyam-cli/src/utils/scenariosManifest.js +30 -0
  22. package/codeyam-cli/src/utils/scenariosManifest.js.map +1 -1
  23. package/codeyam-cli/src/webserver/backgroundServer.js +42 -57
  24. package/codeyam-cli/src/webserver/backgroundServer.js.map +1 -1
  25. package/codeyam-cli/src/webserver/build/client/assets/{CopyButton-CzTDWkF2.js → CopyButton-CLe80MMu.js} +1 -1
  26. package/codeyam-cli/src/webserver/build/client/assets/{EntityItem-BFbq6iFk.js → EntityItem-Crt_KN_U.js} +1 -1
  27. package/codeyam-cli/src/webserver/build/client/assets/{EntityTypeIcon-B6OMi58N.js → EntityTypeIcon-CD7lGABo.js} +1 -1
  28. package/codeyam-cli/src/webserver/build/client/assets/{InlineSpinner-DuYodzo1.js → InlineSpinner-CgTNOhnu.js} +1 -1
  29. package/codeyam-cli/src/webserver/build/client/assets/{InteractivePreview-CXo9EeCl.js → InteractivePreview-CKeQT5Ty.js} +2 -2
  30. package/codeyam-cli/src/webserver/build/client/assets/{LibraryFunctionPreview-DYCNb2It.js → LibraryFunctionPreview-D3s1MFkb.js} +1 -1
  31. package/codeyam-cli/src/webserver/build/client/assets/{LogViewer-CZgY3sxX.js → LogViewer-CM5zg40N.js} +1 -1
  32. package/codeyam-cli/src/webserver/build/client/assets/{ReportIssueModal-CnYYwRDw.js → ReportIssueModal-C2PLkej3.js} +1 -1
  33. package/codeyam-cli/src/webserver/build/client/assets/{SafeScreenshot-CDoF7ZpU.js → SafeScreenshot-DanvyBPb.js} +1 -1
  34. package/codeyam-cli/src/webserver/build/client/assets/{ScenarioViewer-DrnfvaLL.js → ScenarioViewer-DUMfcNVK.js} +1 -1
  35. package/codeyam-cli/src/webserver/build/client/assets/{Spinner-Df3UCi8k.js → Spinner-D0LgAaSa.js} +1 -1
  36. package/codeyam-cli/src/webserver/build/client/assets/{ViewportInspectBar-DRKR9T0U.js → ViewportInspectBar-BA_Ry-rs.js} +1 -1
  37. package/codeyam-cli/src/webserver/build/client/assets/{_index-ClR-g3tY.js → _index-BAWd-Xjf.js} +1 -1
  38. package/codeyam-cli/src/webserver/build/client/assets/{activity.(_tab)-DTH6ydEA.js → activity.(_tab)-BOARiB-g.js} +1 -1
  39. package/codeyam-cli/src/webserver/build/client/assets/{addon-web-links-74hnHF59.js → addon-web-links-CHx25PAe.js} +1 -1
  40. package/codeyam-cli/src/webserver/build/client/assets/{agent-transcripts-B8CYhCO9.js → agent-transcripts-Bg3e7q4S.js} +1 -1
  41. package/codeyam-cli/src/webserver/build/client/assets/{book-open-CLaoh4ac.js → book-open-CL-lMgHh.js} +1 -1
  42. package/codeyam-cli/src/webserver/build/client/assets/{chevron-down-BZ2DZxbW.js → chevron-down-GmAjGS9-.js} +1 -1
  43. package/codeyam-cli/src/webserver/build/client/assets/{chunk-JZWAC4HX-BBXArFPl.js → chunk-JZWAC4HX-BAdwhyCx.js} +11 -11
  44. package/codeyam-cli/src/webserver/build/client/assets/{circle-check-CT4unAk-.js → circle-check-DFcQkN5j.js} +1 -1
  45. package/codeyam-cli/src/webserver/build/client/assets/{copy-zK0B6Nu-.js → copy-C6iF61Xs.js} +1 -1
  46. package/codeyam-cli/src/webserver/build/client/assets/{createLucideIcon-DJB0YQJL.js → createLucideIcon-4ImjHTVC.js} +1 -1
  47. package/codeyam-cli/src/webserver/build/client/assets/{dev.empty-CkXFP_i-.js → dev.empty-C8y4mmyv.js} +1 -1
  48. package/codeyam-cli/src/webserver/build/client/assets/editor._tab-Gbk_i5Js.js +1 -0
  49. package/codeyam-cli/src/webserver/build/client/assets/editor.entity.(_sha)-B7xQ9Sjy.js +58 -0
  50. package/codeyam-cli/src/webserver/build/client/assets/editorPreview-CxmrE6AF.js +41 -0
  51. package/codeyam-cli/src/webserver/build/client/assets/{entity._sha._-BqAN7hyG.js → entity._sha._-Blfy9UlN.js} +1 -1
  52. package/codeyam-cli/src/webserver/build/client/assets/{entity._sha.scenarios._scenarioId.dev-BOi8kpwd.js → entity._sha.scenarios._scenarioId.dev-CUobbQdQ.js} +1 -1
  53. package/codeyam-cli/src/webserver/build/client/assets/{entity._sha.scenarios._scenarioId.fullscreen-Dg1NhIms.js → entity._sha.scenarios._scenarioId.fullscreen-C6eeL24i.js} +1 -1
  54. package/codeyam-cli/src/webserver/build/client/assets/{entity._sha_.create-scenario-CJX6kkkV.js → entity._sha_.create-scenario-DQM8E7L4.js} +1 -1
  55. package/codeyam-cli/src/webserver/build/client/assets/{entity._sha_.edit._scenarioId-BhVjZhKg.js → entity._sha_.edit._scenarioId-CAoXLsQr.js} +1 -1
  56. package/codeyam-cli/src/webserver/build/client/assets/{entry.client-_gzKltPN.js → entry.client-SuW9syRS.js} +1 -1
  57. package/codeyam-cli/src/webserver/build/client/assets/{files-CV_17tZS.js → files-D-xGrg29.js} +1 -1
  58. package/codeyam-cli/src/webserver/build/client/assets/{git-D-YXmMbR.js → git-Bq_fbXP5.js} +1 -1
  59. package/codeyam-cli/src/webserver/build/client/assets/globals-fAqOD9ex.css +1 -0
  60. package/codeyam-cli/src/webserver/build/client/assets/{index-CCrgCshv.js → index-Bp1l4hSv.js} +1 -1
  61. package/codeyam-cli/src/webserver/build/client/assets/{index-BsX0F-9C.js → index-CWV9XZiG.js} +1 -1
  62. package/codeyam-cli/src/webserver/build/client/assets/{index-Blo6EK8G.js → index-DE3jI_dv.js} +1 -1
  63. package/codeyam-cli/src/webserver/build/client/assets/{labs-Byazq8Pv.js → labs-B_IX45ih.js} +1 -1
  64. package/codeyam-cli/src/webserver/build/client/assets/{loader-circle-DVQ0oHR7.js → loader-circle-De-7qQ2u.js} +1 -1
  65. package/codeyam-cli/src/webserver/build/client/assets/manifest-5d53342d.js +1 -0
  66. package/codeyam-cli/src/webserver/build/client/assets/{memory-b-VmA2Vj.js → memory-Cx2xEx7s.js} +1 -1
  67. package/codeyam-cli/src/webserver/build/client/assets/{pause-DGcndCAa.js → pause-CFxEKL1u.js} +1 -1
  68. package/codeyam-cli/src/webserver/build/client/assets/root-DB3O9_9j.js +67 -0
  69. package/codeyam-cli/src/webserver/build/client/assets/{search-C0Uw0bcK.js → search-BdBb5aqc.js} +1 -1
  70. package/codeyam-cli/src/webserver/build/client/assets/{settings-OoNgHIfW.js → settings-DdE-Untf.js} +1 -1
  71. package/codeyam-cli/src/webserver/build/client/assets/{simulations-Bcemfu8a.js → simulations-DSCdE99u.js} +1 -1
  72. package/codeyam-cli/src/webserver/build/client/assets/{terminal-BgMmG7R9.js → terminal-CrplD4b1.js} +1 -1
  73. package/codeyam-cli/src/webserver/build/client/assets/{triangle-alert-Cs87hJYK.js → triangle-alert-DqJ0j69l.js} +1 -1
  74. package/codeyam-cli/src/webserver/build/client/assets/{useCustomSizes-BR3Rs7JY.js → useCustomSizes-DhXHbEjP.js} +1 -1
  75. package/codeyam-cli/src/webserver/build/client/assets/{useLastLogLine-BxxP_XF9.js → useLastLogLine-BNd5hYuW.js} +1 -1
  76. package/codeyam-cli/src/webserver/build/client/assets/{useReportContext-BermyNU5.js → useReportContext-Cy5Qg_UR.js} +1 -1
  77. package/codeyam-cli/src/webserver/build/client/assets/{useToast-a_QN_W9_.js → useToast-5HR2j9ZE.js} +1 -1
  78. package/codeyam-cli/src/webserver/build/server/assets/{analysisRunner-lv2ooewK.js → analysisRunner-DcJSnBCE.js} +1 -1
  79. package/codeyam-cli/src/webserver/build/server/assets/{index-Im3Smyei.js → index-CEaDhUiv.js} +1 -1
  80. package/codeyam-cli/src/webserver/build/server/assets/{init-BjuAFKGM.js → init-DA7guOrE.js} +1 -1
  81. package/codeyam-cli/src/webserver/build/server/assets/server-build-juyiY2m6.js +551 -0
  82. package/codeyam-cli/src/webserver/build/server/index.js +1 -1
  83. package/codeyam-cli/src/webserver/build-info.json +5 -5
  84. package/codeyam-cli/src/webserver/terminalServer.js +1 -1
  85. package/codeyam-cli/src/webserver/terminalServer.js.map +1 -1
  86. package/codeyam-cli/templates/nextjs-prisma-sqlite/package.json +1 -1
  87. package/codeyam-cli/templates/nextjs-prisma-supabase/package.json +1 -1
  88. package/package.json +1 -1
  89. package/codeyam-cli/src/webserver/build/client/assets/editor._tab-DPw7NZHc.js +0 -1
  90. package/codeyam-cli/src/webserver/build/client/assets/editor.entity.(_sha)-CjC3_6JI.js +0 -58
  91. package/codeyam-cli/src/webserver/build/client/assets/editorPreview-DBa7T2FK.js +0 -41
  92. package/codeyam-cli/src/webserver/build/client/assets/globals-DRvOjyO3.css +0 -1
  93. package/codeyam-cli/src/webserver/build/client/assets/manifest-75b1b319.js +0 -1
  94. package/codeyam-cli/src/webserver/build/client/assets/root-F-k2uYj5.js +0 -67
  95. package/codeyam-cli/src/webserver/build/server/assets/server-build-CNjF0B9B.js +0 -551
@@ -1,6 +1,6 @@
1
1
  import Database from 'better-sqlite3';
2
2
  import { Kysely, SqliteDialect } from 'kysely';
3
- import { isComponent, classifyGlossaryEntries, computeAudit, filterGlossaryByChangeStatus, resolveAuditSessionScope, queryScenarioCounts, queryIncompleteEntities, queryMiscategorizedScenarios, isOnlyIncompleteEntities, isAutoRemediable, } from "../editorAudit.js";
3
+ import { isComponent, classifyGlossaryEntries, computeAudit, filterGlossaryByChangeStatus, resolveAuditSessionScope, queryScenarioCounts, queryPageScenarioCounts, queryIncompleteEntities, queryMiscategorizedScenarios, isOnlyIncompleteEntities, isAutoRemediable, identifyScenariosNeedingRecapture, } from "../editorAudit.js";
4
4
  describe('editorAudit', () => {
5
5
  describe('isComponent', () => {
6
6
  it('should return true for JSX.Element return type', () => {
@@ -290,6 +290,29 @@ describe('editorAudit', () => {
290
290
  expect(result.functions[0].testsPassing).toBe(false);
291
291
  expect(result.functions[0].testsVisibleInUi).toBe(true);
292
292
  });
293
+ it('should distinguish runner errors from test failures and include error message', () => {
294
+ const result = computeAudit({
295
+ components: [],
296
+ functions: [
297
+ {
298
+ name: 'getTimeAgo',
299
+ filePath: 'src/lib/format.ts',
300
+ testFile: 'src/lib/format.test.ts',
301
+ },
302
+ ],
303
+ scenarioCounts: {},
304
+ testFileExistence: { 'src/lib/format.test.ts': true },
305
+ testResults: {
306
+ 'src/lib/format.test.ts': {
307
+ passing: false,
308
+ hasEntityNameDescribe: false,
309
+ errorMessage: 'Error: Cannot find module "@/lib/format" from "src/lib/format.test.ts"',
310
+ },
311
+ },
312
+ });
313
+ expect(result.functions[0].status).toBe('runner_error');
314
+ expect(result.functions[0].errorMessage).toBe('Error: Cannot find module "@/lib/format" from "src/lib/format.test.ts"');
315
+ });
293
316
  it('should mark function as name_mismatch when tests pass but no describe matches', () => {
294
317
  const result = computeAudit({
295
318
  components: [],
@@ -983,6 +1006,158 @@ describe('editorAudit', () => {
983
1006
  expect(counts).toEqual({ ArticleCard: 1, ArticleRow: 1 });
984
1007
  });
985
1008
  });
1009
+ // ── queryPageScenarioCounts ──────────────────────────────────────────
1010
+ describe('queryPageScenarioCounts', () => {
1011
+ let db;
1012
+ let rawDb;
1013
+ const projectId = 'test-project-id';
1014
+ beforeEach(async () => {
1015
+ rawDb = new Database(':memory:');
1016
+ db = new Kysely({ dialect: new SqliteDialect({ database: rawDb }) });
1017
+ await db.schema
1018
+ .createTable('editor_scenarios')
1019
+ .addColumn('id', 'varchar', (col) => col.primaryKey())
1020
+ .addColumn('project_id', 'varchar', (col) => col.notNull())
1021
+ .addColumn('name', 'varchar', (col) => col.notNull())
1022
+ .addColumn('description', 'text')
1023
+ .addColumn('component_name', 'varchar')
1024
+ .addColumn('component_path', 'varchar')
1025
+ .addColumn('page_file_path', 'varchar')
1026
+ .addColumn('url', 'varchar')
1027
+ .addColumn('type', 'varchar')
1028
+ .addColumn('screenshot_path', 'varchar')
1029
+ .addColumn('viewport_width', 'integer')
1030
+ .addColumn('viewport_height', 'integer')
1031
+ .addColumn('dimension', 'varchar')
1032
+ .addColumn('created_at', 'datetime')
1033
+ .addColumn('updated_at', 'datetime')
1034
+ .execute();
1035
+ });
1036
+ afterEach(async () => {
1037
+ await db.destroy();
1038
+ });
1039
+ it('should count app-level scenarios by page_file_path', async () => {
1040
+ // App-level scenario: has page_file_path but no component_name
1041
+ await db
1042
+ .insertInto('editor_scenarios')
1043
+ .values({
1044
+ id: 'sc-1',
1045
+ project_id: projectId,
1046
+ name: 'Library - Default',
1047
+ component_name: null,
1048
+ page_file_path: 'app/library/page.tsx',
1049
+ created_at: '2026-03-12 13:00:00',
1050
+ })
1051
+ .execute();
1052
+ await db
1053
+ .insertInto('editor_scenarios')
1054
+ .values({
1055
+ id: 'sc-2',
1056
+ project_id: projectId,
1057
+ name: 'Library - Empty',
1058
+ component_name: null,
1059
+ page_file_path: 'app/library/page.tsx',
1060
+ created_at: '2026-03-12 13:05:00',
1061
+ })
1062
+ .execute();
1063
+ const counts = await queryPageScenarioCounts(db, projectId, null);
1064
+ expect(counts).toEqual({ 'app/library/page.tsx': 2 });
1065
+ });
1066
+ it('should not count component scenarios (those have component_name)', async () => {
1067
+ // Component scenario — should NOT appear in page counts
1068
+ await db
1069
+ .insertInto('editor_scenarios')
1070
+ .values({
1071
+ id: 'sc-1',
1072
+ project_id: projectId,
1073
+ name: 'ArticleRow - Default',
1074
+ component_name: 'ArticleRow',
1075
+ page_file_path: null,
1076
+ created_at: '2026-03-12 13:00:00',
1077
+ })
1078
+ .execute();
1079
+ const counts = await queryPageScenarioCounts(db, projectId, null);
1080
+ expect(counts).toEqual({});
1081
+ });
1082
+ it('should respect featureStartedAt filter', async () => {
1083
+ await db
1084
+ .insertInto('editor_scenarios')
1085
+ .values({
1086
+ id: 'sc-1',
1087
+ project_id: projectId,
1088
+ name: 'Library - Old',
1089
+ component_name: null,
1090
+ page_file_path: 'app/library/page.tsx',
1091
+ created_at: '2026-03-12 13:00:00',
1092
+ })
1093
+ .execute();
1094
+ await db
1095
+ .insertInto('editor_scenarios')
1096
+ .values({
1097
+ id: 'sc-2',
1098
+ project_id: projectId,
1099
+ name: 'Library - New',
1100
+ component_name: null,
1101
+ page_file_path: 'app/library/page.tsx',
1102
+ created_at: '2026-03-12 15:00:00',
1103
+ })
1104
+ .execute();
1105
+ const counts = await queryPageScenarioCounts(db, projectId, '2026-03-12T14:01:31.291Z');
1106
+ expect(counts).toEqual({ 'app/library/page.tsx': 1 });
1107
+ });
1108
+ it('should count re-registered page scenarios via updated_at', async () => {
1109
+ await db
1110
+ .insertInto('editor_scenarios')
1111
+ .values({
1112
+ id: 'sc-1',
1113
+ project_id: projectId,
1114
+ name: 'Library - Default',
1115
+ component_name: null,
1116
+ page_file_path: 'app/library/page.tsx',
1117
+ created_at: '2026-03-12 13:00:00',
1118
+ updated_at: '2026-03-12 15:00:00',
1119
+ })
1120
+ .execute();
1121
+ const counts = await queryPageScenarioCounts(db, projectId, '2026-03-12T14:01:31.291Z');
1122
+ expect(counts).toEqual({ 'app/library/page.tsx': 1 });
1123
+ });
1124
+ it('should group counts by page_file_path across multiple pages', async () => {
1125
+ await db
1126
+ .insertInto('editor_scenarios')
1127
+ .values([
1128
+ {
1129
+ id: 'sc-1',
1130
+ project_id: projectId,
1131
+ name: 'Library - Default',
1132
+ component_name: null,
1133
+ page_file_path: 'app/library/page.tsx',
1134
+ created_at: '2026-03-12 13:00:00',
1135
+ },
1136
+ {
1137
+ id: 'sc-2',
1138
+ project_id: projectId,
1139
+ name: 'Library - Rich',
1140
+ component_name: null,
1141
+ page_file_path: 'app/library/page.tsx',
1142
+ created_at: '2026-03-12 13:00:00',
1143
+ },
1144
+ {
1145
+ id: 'sc-3',
1146
+ project_id: projectId,
1147
+ name: 'Collections - Default',
1148
+ component_name: null,
1149
+ page_file_path: 'app/library/collections/page.tsx',
1150
+ created_at: '2026-03-12 13:00:00',
1151
+ },
1152
+ ])
1153
+ .execute();
1154
+ const counts = await queryPageScenarioCounts(db, projectId, null);
1155
+ expect(counts).toEqual({
1156
+ 'app/library/page.tsx': 2,
1157
+ 'app/library/collections/page.tsx': 1,
1158
+ });
1159
+ });
1160
+ });
986
1161
  // ── Audit + entity completeness integration ─────────────────────────
987
1162
  describe('audit should catch incomplete entities (bug reproduction)', () => {
988
1163
  let db;
@@ -1856,6 +2031,84 @@ describe('editorAudit', () => {
1856
2031
  const result = await queryIncompleteEntities(db, projectId, null);
1857
2032
  expect(result).toEqual([]);
1858
2033
  });
2034
+ it('should not flag entities when a sibling version (same name+filePath) has analyses', async () => {
2035
+ // Old entity version WITH analysis
2036
+ await db
2037
+ .insertInto('entities')
2038
+ .values({
2039
+ sha: 'sha-btn-v1',
2040
+ name: 'OpenLibraryButton',
2041
+ entity_type: 'visual',
2042
+ file_path: 'src/components/OpenLibraryButton.tsx',
2043
+ })
2044
+ .execute();
2045
+ await db
2046
+ .insertInto('analyses')
2047
+ .values({
2048
+ id: 'a-btn-v1',
2049
+ entity_sha: 'sha-btn-v1',
2050
+ entity_name: 'OpenLibraryButton',
2051
+ project_id: projectId,
2052
+ })
2053
+ .execute();
2054
+ // New entity version WITHOUT analysis (created by file watcher)
2055
+ await db
2056
+ .insertInto('entities')
2057
+ .values({
2058
+ sha: 'sha-btn-v2',
2059
+ name: 'OpenLibraryButton',
2060
+ entity_type: 'visual',
2061
+ file_path: 'src/components/OpenLibraryButton.tsx',
2062
+ })
2063
+ .execute();
2064
+ // Scenario points to the NEW version (backfilled after file watcher)
2065
+ await db
2066
+ .insertInto('editor_scenarios')
2067
+ .values({
2068
+ id: 'sc-btn',
2069
+ project_id: projectId,
2070
+ name: 'OpenLibraryButton - Default',
2071
+ component_name: 'OpenLibraryButton',
2072
+ entity_sha: 'sha-btn-v2',
2073
+ created_at: '2026-03-16 23:00:00',
2074
+ })
2075
+ .execute();
2076
+ // Should NOT flag as incomplete — sibling version has analyses
2077
+ const result = await queryIncompleteEntities(db, projectId, null);
2078
+ expect(result).toEqual([]);
2079
+ });
2080
+ it('should still flag entities when no sibling version has analyses', async () => {
2081
+ // Only one version, no analyses
2082
+ await db
2083
+ .insertInto('entities')
2084
+ .values({
2085
+ sha: 'sha-icon',
2086
+ name: 'ExternalLinkIcon',
2087
+ entity_type: 'visual',
2088
+ file_path: 'src/components/ExternalLinkIcon.tsx',
2089
+ })
2090
+ .execute();
2091
+ await db
2092
+ .insertInto('editor_scenarios')
2093
+ .values({
2094
+ id: 'sc-icon',
2095
+ project_id: projectId,
2096
+ name: 'ExternalLinkIcon - Default',
2097
+ component_name: 'ExternalLinkIcon',
2098
+ entity_sha: 'sha-icon',
2099
+ created_at: '2026-03-16 23:00:00',
2100
+ })
2101
+ .execute();
2102
+ // Should flag as incomplete — no version has analyses
2103
+ const result = await queryIncompleteEntities(db, projectId, null);
2104
+ expect(result).toEqual([
2105
+ {
2106
+ entitySha: 'sha-icon',
2107
+ name: 'ExternalLinkIcon',
2108
+ scenarioCount: 1,
2109
+ },
2110
+ ]);
2111
+ });
1859
2112
  it('should use entity name from entities table, falling back to component_name', async () => {
1860
2113
  // Scenario has entity_sha but entity record doesn't exist
1861
2114
  await db
@@ -1875,5 +2128,191 @@ describe('editorAudit', () => {
1875
2128
  ]);
1876
2129
  });
1877
2130
  });
2131
+ // ── identifyScenariosNeedingRecapture ──────────────────────────────
2132
+ describe('identifyScenariosNeedingRecapture', () => {
2133
+ // Reproduces the Margo bug: Feature 1 built app-level popup scenarios,
2134
+ // Feature 2 edited LibraryView (used by App), but app-level scenarios
2135
+ // were never flagged for recapture because the audit only checked
2136
+ // component scenario existence — not whether app-level scenarios are stale.
2137
+ //
2138
+ // Each scenario's entityName is resolved by the caller via
2139
+ // entity_sha → entities.name (the default export for app-level scenarios).
2140
+ it('should flag app-level scenario when its entity is impacted by transitive dependency change', () => {
2141
+ // LibraryView was edited → App is impacted (imports LibraryView)
2142
+ // App-level scenario "Library - Rich Library" has entity_sha pointing to App
2143
+ // It was NOT recaptured during Feature 2 → should be flagged
2144
+ const entityChangeStatus = {
2145
+ LibraryView: { status: 'edited' },
2146
+ App: {
2147
+ status: 'impacted',
2148
+ impactedBy: [
2149
+ {
2150
+ name: 'LibraryView',
2151
+ filePath: 'src/components/LibraryView.tsx',
2152
+ changeType: 'edited',
2153
+ },
2154
+ ],
2155
+ },
2156
+ };
2157
+ const result = identifyScenariosNeedingRecapture({
2158
+ scenarios: [
2159
+ {
2160
+ name: 'Library - Rich Library',
2161
+ entityName: 'App', // resolved from entity_sha → entities.name
2162
+ updatedInSession: false,
2163
+ },
2164
+ ],
2165
+ entityChangeStatus,
2166
+ });
2167
+ expect(result).toHaveLength(1);
2168
+ expect(result[0].scenarioName).toBe('Library - Rich Library');
2169
+ expect(result[0].entityName).toBe('App');
2170
+ expect(result[0].status.status).toBe('impacted');
2171
+ });
2172
+ it('should flag component scenario when its entity is directly edited and not recaptured', () => {
2173
+ const entityChangeStatus = {
2174
+ LibraryView: { status: 'edited' },
2175
+ };
2176
+ const result = identifyScenariosNeedingRecapture({
2177
+ scenarios: [
2178
+ {
2179
+ name: 'LibraryView - Empty',
2180
+ entityName: 'LibraryView',
2181
+ updatedInSession: false,
2182
+ },
2183
+ ],
2184
+ entityChangeStatus,
2185
+ });
2186
+ expect(result).toHaveLength(1);
2187
+ expect(result[0].scenarioName).toBe('LibraryView - Empty');
2188
+ expect(result[0].entityName).toBe('LibraryView');
2189
+ expect(result[0].status.status).toBe('edited');
2190
+ });
2191
+ it('should NOT flag scenario that was already recaptured in the current session', () => {
2192
+ const entityChangeStatus = {
2193
+ App: {
2194
+ status: 'impacted',
2195
+ impactedBy: [
2196
+ {
2197
+ name: 'LibraryView',
2198
+ filePath: 'src/components/LibraryView.tsx',
2199
+ changeType: 'edited',
2200
+ },
2201
+ ],
2202
+ },
2203
+ };
2204
+ const result = identifyScenariosNeedingRecapture({
2205
+ scenarios: [
2206
+ {
2207
+ name: 'App - Default',
2208
+ entityName: 'App',
2209
+ updatedInSession: true, // re-registered during Feature 2
2210
+ },
2211
+ ],
2212
+ entityChangeStatus,
2213
+ });
2214
+ expect(result).toHaveLength(0);
2215
+ });
2216
+ it('should NOT flag scenario whose entity has no change status', () => {
2217
+ const entityChangeStatus = {
2218
+ LibraryView: { status: 'edited' },
2219
+ };
2220
+ const result = identifyScenariosNeedingRecapture({
2221
+ scenarios: [
2222
+ {
2223
+ name: 'WelcomeScreen - Default',
2224
+ entityName: 'WelcomeScreen',
2225
+ updatedInSession: false,
2226
+ },
2227
+ ],
2228
+ entityChangeStatus,
2229
+ });
2230
+ expect(result).toHaveLength(0);
2231
+ });
2232
+ it('should return empty array when entityChangeStatus is undefined', () => {
2233
+ const result = identifyScenariosNeedingRecapture({
2234
+ scenarios: [
2235
+ {
2236
+ name: 'Library - Rich Library',
2237
+ entityName: 'App',
2238
+ updatedInSession: false,
2239
+ },
2240
+ ],
2241
+ entityChangeStatus: undefined,
2242
+ });
2243
+ expect(result).toHaveLength(0);
2244
+ });
2245
+ it('should return empty array when entityChangeStatus is empty', () => {
2246
+ const result = identifyScenariosNeedingRecapture({
2247
+ scenarios: [
2248
+ {
2249
+ name: 'Library - Rich Library',
2250
+ entityName: 'App',
2251
+ updatedInSession: false,
2252
+ },
2253
+ ],
2254
+ entityChangeStatus: {},
2255
+ });
2256
+ expect(result).toHaveLength(0);
2257
+ });
2258
+ it('should flag multiple app-level scenarios sharing the same impacted entity', () => {
2259
+ const entityChangeStatus = {
2260
+ LibraryView: { status: 'edited' },
2261
+ App: {
2262
+ status: 'impacted',
2263
+ impactedBy: [
2264
+ {
2265
+ name: 'LibraryView',
2266
+ filePath: 'src/components/LibraryView.tsx',
2267
+ changeType: 'edited',
2268
+ },
2269
+ ],
2270
+ },
2271
+ };
2272
+ const result = identifyScenariosNeedingRecapture({
2273
+ scenarios: [
2274
+ {
2275
+ name: 'Library - Empty',
2276
+ entityName: 'App',
2277
+ updatedInSession: false,
2278
+ },
2279
+ {
2280
+ name: 'Library - Rich Library',
2281
+ entityName: 'App',
2282
+ updatedInSession: false,
2283
+ },
2284
+ {
2285
+ name: 'First Article Saved',
2286
+ entityName: 'App',
2287
+ updatedInSession: false,
2288
+ },
2289
+ ],
2290
+ entityChangeStatus,
2291
+ });
2292
+ expect(result).toHaveLength(3);
2293
+ expect(result.map((r) => r.scenarioName).sort()).toEqual([
2294
+ 'First Article Saved',
2295
+ 'Library - Empty',
2296
+ 'Library - Rich Library',
2297
+ ]);
2298
+ expect(result.every((r) => r.entityName === 'App')).toBe(true);
2299
+ });
2300
+ it('should skip scenarios with null entityName (no entity_sha set)', () => {
2301
+ const entityChangeStatus = {
2302
+ App: { status: 'edited' },
2303
+ };
2304
+ const result = identifyScenariosNeedingRecapture({
2305
+ scenarios: [
2306
+ {
2307
+ name: 'Mystery Scenario',
2308
+ entityName: null, // no entity_sha → no entity name
2309
+ updatedInSession: false,
2310
+ },
2311
+ ],
2312
+ entityChangeStatus,
2313
+ });
2314
+ expect(result).toHaveLength(0);
2315
+ });
2316
+ });
1878
2317
  });
1879
2318
  //# sourceMappingURL=editorAudit.test.js.map