reviewflow 3.19.1 → 3.20.0

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 (132) hide show
  1. package/CHANGELOG.md +14 -0
  2. package/dist/dashboard/index.html +107 -34
  3. package/dist/dashboard/modules/constants.d.ts +1 -0
  4. package/dist/dashboard/modules/constants.d.ts.map +1 -1
  5. package/dist/dashboard/modules/constants.js +1 -0
  6. package/dist/dashboard/modules/constants.js.map +1 -1
  7. package/dist/dashboard/modules/overview.d.ts +64 -0
  8. package/dist/dashboard/modules/overview.d.ts.map +1 -0
  9. package/dist/dashboard/modules/overview.js +246 -0
  10. package/dist/dashboard/modules/overview.js.map +1 -0
  11. package/dist/dashboard/modules/tabBar.d.ts +56 -0
  12. package/dist/dashboard/modules/tabBar.d.ts.map +1 -0
  13. package/dist/dashboard/modules/tabBar.js +98 -0
  14. package/dist/dashboard/modules/tabBar.js.map +1 -0
  15. package/dist/dashboard/styles.css +324 -0
  16. package/dist/main/cli.d.ts +1 -83
  17. package/dist/main/cli.d.ts.map +1 -1
  18. package/dist/main/cli.js +24 -606
  19. package/dist/main/cli.js.map +1 -1
  20. package/dist/main/commands/discover.command.d.ts +17 -0
  21. package/dist/main/commands/discover.command.d.ts.map +1 -0
  22. package/dist/main/commands/discover.command.js +76 -0
  23. package/dist/main/commands/discover.command.js.map +1 -0
  24. package/dist/main/commands/followupImportants.command.d.ts +14 -0
  25. package/dist/main/commands/followupImportants.command.d.ts.map +1 -0
  26. package/dist/main/commands/followupImportants.command.js +31 -0
  27. package/dist/main/commands/followupImportants.command.js.map +1 -0
  28. package/dist/main/commands/init.command.d.ts +29 -0
  29. package/dist/main/commands/init.command.d.ts.map +1 -0
  30. package/dist/main/commands/init.command.js +231 -0
  31. package/dist/main/commands/init.command.js.map +1 -0
  32. package/dist/main/commands/logs.command.d.ts +10 -0
  33. package/dist/main/commands/logs.command.d.ts.map +1 -0
  34. package/dist/main/commands/logs.command.js +42 -0
  35. package/dist/main/commands/logs.command.js.map +1 -0
  36. package/dist/main/commands/start.command.d.ts +21 -0
  37. package/dist/main/commands/start.command.d.ts.map +1 -0
  38. package/dist/main/commands/start.command.js +80 -0
  39. package/dist/main/commands/start.command.js.map +1 -0
  40. package/dist/main/commands/status.command.d.ts +9 -0
  41. package/dist/main/commands/status.command.d.ts.map +1 -0
  42. package/dist/main/commands/status.command.js +26 -0
  43. package/dist/main/commands/status.command.js.map +1 -0
  44. package/dist/main/commands/stop.command.d.ts +11 -0
  45. package/dist/main/commands/stop.command.d.ts.map +1 -0
  46. package/dist/main/commands/stop.command.js +30 -0
  47. package/dist/main/commands/stop.command.js.map +1 -0
  48. package/dist/main/commands/validate.command.d.ts +11 -0
  49. package/dist/main/commands/validate.command.d.ts.map +1 -0
  50. package/dist/main/commands/validate.command.js +54 -0
  51. package/dist/main/commands/validate.command.js.map +1 -0
  52. package/dist/main/routes.d.ts.map +1 -1
  53. package/dist/main/routes.js +20 -9
  54. package/dist/main/routes.js.map +1 -1
  55. package/dist/main/shared/cliConstants.d.ts +5 -0
  56. package/dist/main/shared/cliConstants.d.ts.map +1 -0
  57. package/dist/main/shared/cliConstants.js +86 -0
  58. package/dist/main/shared/cliConstants.js.map +1 -0
  59. package/dist/modules/cli-configuration/interface-adapters/controllers/http/repositories.routes.d.ts +7 -0
  60. package/dist/modules/cli-configuration/interface-adapters/controllers/http/repositories.routes.d.ts.map +1 -0
  61. package/dist/modules/cli-configuration/interface-adapters/controllers/http/repositories.routes.js +13 -0
  62. package/dist/modules/cli-configuration/interface-adapters/controllers/http/repositories.routes.js.map +1 -0
  63. package/dist/modules/statistics-insights/interface-adapters/controllers/http/overview.routes.d.ts +14 -0
  64. package/dist/modules/statistics-insights/interface-adapters/controllers/http/overview.routes.d.ts.map +1 -0
  65. package/dist/modules/statistics-insights/interface-adapters/controllers/http/overview.routes.js +39 -0
  66. package/dist/modules/statistics-insights/interface-adapters/controllers/http/overview.routes.js.map +1 -0
  67. package/dist/modules/statistics-insights/interface-adapters/presenters/overview.presenter.d.ts +88 -0
  68. package/dist/modules/statistics-insights/interface-adapters/presenters/overview.presenter.d.ts.map +1 -0
  69. package/dist/modules/statistics-insights/interface-adapters/presenters/overview.presenter.js +129 -0
  70. package/dist/modules/statistics-insights/interface-adapters/presenters/overview.presenter.js.map +1 -0
  71. package/dist/shared/services/pidFileManager.d.ts +6 -0
  72. package/dist/shared/services/pidFileManager.d.ts.map +1 -1
  73. package/dist/shared/services/pidFileManager.js.map +1 -1
  74. package/dist/tests/acceptance/91-dashboard-multi-project-overview.acceptance.test.d.ts +10 -0
  75. package/dist/tests/acceptance/91-dashboard-multi-project-overview.acceptance.test.d.ts.map +1 -0
  76. package/dist/tests/acceptance/91-dashboard-multi-project-overview.acceptance.test.js +272 -0
  77. package/dist/tests/acceptance/91-dashboard-multi-project-overview.acceptance.test.js.map +1 -0
  78. package/dist/tests/factories/projectStatsApiResponse.factory.d.ts +16 -0
  79. package/dist/tests/factories/projectStatsApiResponse.factory.d.ts.map +1 -0
  80. package/dist/tests/factories/projectStatsApiResponse.factory.js +39 -0
  81. package/dist/tests/factories/projectStatsApiResponse.factory.js.map +1 -0
  82. package/dist/tests/factories/recentReviewFile.factory.d.ts +5 -0
  83. package/dist/tests/factories/recentReviewFile.factory.d.ts.map +1 -0
  84. package/dist/tests/factories/recentReviewFile.factory.js +16 -0
  85. package/dist/tests/factories/recentReviewFile.factory.js.map +1 -0
  86. package/dist/tests/factories/repositoryConfig.factory.d.ts +5 -0
  87. package/dist/tests/factories/repositoryConfig.factory.d.ts.map +1 -0
  88. package/dist/tests/factories/repositoryConfig.factory.js +14 -0
  89. package/dist/tests/factories/repositoryConfig.factory.js.map +1 -0
  90. package/dist/tests/units/dashboard/modules/constants.test.js +2 -1
  91. package/dist/tests/units/dashboard/modules/constants.test.js.map +1 -1
  92. package/dist/tests/units/dashboard/modules/overview.test.d.ts +2 -0
  93. package/dist/tests/units/dashboard/modules/overview.test.d.ts.map +1 -0
  94. package/dist/tests/units/dashboard/modules/overview.test.js +176 -0
  95. package/dist/tests/units/dashboard/modules/overview.test.js.map +1 -0
  96. package/dist/tests/units/dashboard/modules/tabBar.test.d.ts +2 -0
  97. package/dist/tests/units/dashboard/modules/tabBar.test.d.ts.map +1 -0
  98. package/dist/tests/units/dashboard/modules/tabBar.test.js +99 -0
  99. package/dist/tests/units/dashboard/modules/tabBar.test.js.map +1 -0
  100. package/dist/tests/units/main/executeDiscover.test.js +1 -1
  101. package/dist/tests/units/main/executeDiscover.test.js.map +1 -1
  102. package/dist/tests/units/main/executeFollowupImportants.test.d.ts +2 -0
  103. package/dist/tests/units/main/executeFollowupImportants.test.d.ts.map +1 -0
  104. package/dist/tests/units/main/executeFollowupImportants.test.js +48 -0
  105. package/dist/tests/units/main/executeFollowupImportants.test.js.map +1 -0
  106. package/dist/tests/units/main/executeInit.test.js +1 -1
  107. package/dist/tests/units/main/executeInit.test.js.map +1 -1
  108. package/dist/tests/units/main/executeLogs.test.js +1 -1
  109. package/dist/tests/units/main/executeLogs.test.js.map +1 -1
  110. package/dist/tests/units/main/executeStart.test.js +1 -1
  111. package/dist/tests/units/main/executeStart.test.js.map +1 -1
  112. package/dist/tests/units/main/executeStatus.test.js +1 -1
  113. package/dist/tests/units/main/executeStatus.test.js.map +1 -1
  114. package/dist/tests/units/main/executeStop.test.js +1 -1
  115. package/dist/tests/units/main/executeStop.test.js.map +1 -1
  116. package/dist/tests/units/main/executeValidate.test.d.ts +2 -0
  117. package/dist/tests/units/main/executeValidate.test.d.ts.map +1 -0
  118. package/dist/tests/units/main/executeValidate.test.js +76 -0
  119. package/dist/tests/units/main/executeValidate.test.js.map +1 -0
  120. package/dist/tests/units/modules/cli-configuration/interface-adapters/controllers/http/repositories.routes.test.d.ts +2 -0
  121. package/dist/tests/units/modules/cli-configuration/interface-adapters/controllers/http/repositories.routes.test.d.ts.map +1 -0
  122. package/dist/tests/units/modules/cli-configuration/interface-adapters/controllers/http/repositories.routes.test.js +57 -0
  123. package/dist/tests/units/modules/cli-configuration/interface-adapters/controllers/http/repositories.routes.test.js.map +1 -0
  124. package/dist/tests/units/modules/statistics-insights/interface-adapters/controllers/http/overview.routes.test.d.ts +2 -0
  125. package/dist/tests/units/modules/statistics-insights/interface-adapters/controllers/http/overview.routes.test.d.ts.map +1 -0
  126. package/dist/tests/units/modules/statistics-insights/interface-adapters/controllers/http/overview.routes.test.js +156 -0
  127. package/dist/tests/units/modules/statistics-insights/interface-adapters/controllers/http/overview.routes.test.js.map +1 -0
  128. package/dist/tests/units/modules/statistics-insights/interface-adapters/presenters/overview.presenter.test.d.ts +2 -0
  129. package/dist/tests/units/modules/statistics-insights/interface-adapters/presenters/overview.presenter.test.d.ts.map +1 -0
  130. package/dist/tests/units/modules/statistics-insights/interface-adapters/presenters/overview.presenter.test.js +289 -0
  131. package/dist/tests/units/modules/statistics-insights/interface-adapters/presenters/overview.presenter.test.js.map +1 -0
  132. package/package.json +1 -1
package/CHANGELOG.md CHANGED
@@ -5,6 +5,20 @@ All notable changes to this project will be documented in this file.
5
5
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
6
6
  and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
7
 
8
+ ## [3.20.0](https://github.com/DGouron/review-flow/compare/reviewflow-v3.19.2...reviewflow-v3.20.0) (2026-05-25)
9
+
10
+
11
+ ### Added
12
+
13
+ * **dashboard:** implement SPEC-91 multi-project overview UI ([#200](https://github.com/DGouron/review-flow/issues/200)) ([05b1952](https://github.com/DGouron/review-flow/commit/05b1952617cbe2ed7d11953eb28aa37c53a14fae))
14
+
15
+ ## [3.19.2](https://github.com/DGouron/review-flow/compare/reviewflow-v3.19.1...reviewflow-v3.19.2) (2026-05-24)
16
+
17
+
18
+ ### Changed
19
+
20
+ * **cli:** split SPEC-92 god file into per-command modules ([#198](https://github.com/DGouron/review-flow/issues/198)) ([3d3d8e8](https://github.com/DGouron/review-flow/commit/3d3d8e86da6a7d94d315bb503dd3b9e11c28e7cd))
21
+
8
22
  ## [3.19.1](https://github.com/DGouron/review-flow/compare/reviewflow-v3.19.0...reviewflow-v3.19.1) (2026-05-24)
9
23
 
10
24
 
@@ -78,19 +78,8 @@
78
78
  </select>
79
79
  </div>
80
80
 
81
- <div class="project-loader">
82
- <select id="project-select" class="project-input" onchange="onProjectSelect(this.value)">
83
- <option value="" id="i18n-project-placeholder"></option>
84
- </select>
85
- <input type="text" id="project-path-input" class="project-input" value="">
86
- <button class="btn btn-primary" onclick="loadProjectConfig()">
87
- <i data-lucide="folder-open"></i> <span id="i18n-project-load"></span>
88
- </button>
89
- <button id="remove-project-btn" class="btn btn-secondary" onclick="removeCurrentProject()">
90
- <i data-lucide="trash-2"></i>
91
- </button>
92
- <span id="config-status" class="config-status hidden"></span>
93
- </div>
81
+ <nav id="dashboard-tabs" class="dashboard-tab-bar-wrapper" aria-label="Project tabs"></nav>
82
+ <span id="config-status" class="config-status hidden"></span>
94
83
 
95
84
  <div class="focus-strip">
96
85
  <div class="focus-chip focus-now">
@@ -121,6 +110,8 @@
121
110
  </aside>
122
111
 
123
112
  <main class="dashboard-main">
113
+ <section id="overview-section" class="overview-section" aria-label="Multi-project overview"></section>
114
+
124
115
  <div id="data-loading-state" class="data-loading hidden" role="status" aria-live="polite">
125
116
  <i data-lucide="loader-circle"></i>
126
117
  <span id="i18n-loading-data"></span>
@@ -319,6 +310,8 @@
319
310
  import { escapeHtml, markdownToHtml, sanitizeHttpUrl } from './modules/html.js';
320
311
  import { getAgentIcon, icon, refreshIcons } from './modules/icons.js';
321
312
  import { MAX_RECONNECT_ATTEMPTS, RECONNECT_DELAY, STORAGE_KEY_PROJECTS, STORAGE_KEY_CURRENT, STORAGE_KEY_FOCUS_STRIP_MODE, QUALITY_TARGET_SCORE } from './modules/constants.js';
313
+ import { buildTabBarModel, renderTabBarHtml, readActiveTab, writeActiveTab } from './modules/tabBar.js';
314
+ import { renderOverviewHtml } from './modules/overview.js';
322
315
  import { getDesktopNotificationPayload, shouldNotifyDesktop } from './modules/desktopNotifications.js';
323
316
  import { getLoadingPresentation, getQuietRefreshSectionIdentifiers } from './modules/loading.js';
324
317
  import { collectReviewNotifications, createReviewNotificationState } from './modules/notifications.js';
@@ -2088,6 +2081,9 @@
2088
2081
  if (message.type === 'state') {
2089
2082
  fetchMrTracking();
2090
2083
  }
2084
+ if (activeTabId === 'overview') {
2085
+ refreshOverviewSection();
2086
+ }
2091
2087
  break;
2092
2088
  case 'progress':
2093
2089
  handleProgressUpdate(message);
@@ -2305,6 +2301,7 @@
2305
2301
 
2306
2302
  function updateProjectSelect() {
2307
2303
  const select = document.getElementById('project-select');
2304
+ if (!select) return;
2308
2305
  const projects = getStoredProjects();
2309
2306
  const current = localStorage.getItem(STORAGE_KEY_CURRENT) || '';
2310
2307
 
@@ -2322,7 +2319,8 @@
2322
2319
 
2323
2320
  function onProjectSelect(path) {
2324
2321
  if (path) {
2325
- document.getElementById('project-path-input').value = '';
2322
+ const input = document.getElementById('project-path-input');
2323
+ if (input) input.value = '';
2326
2324
  loadProjectConfigFromPath(path);
2327
2325
  }
2328
2326
  }
@@ -2330,7 +2328,7 @@
2330
2328
  async function loadProjectConfig() {
2331
2329
  const input = document.getElementById('project-path-input');
2332
2330
  const select = document.getElementById('project-select');
2333
- const projectPath = input.value.trim() || select.value;
2331
+ const projectPath = (input?.value.trim() ?? '') || (select?.value ?? '');
2334
2332
 
2335
2333
  if (!projectPath) {
2336
2334
  showConfigStatus(t('error.selectOrEnterPath'), 'error');
@@ -2358,8 +2356,10 @@
2358
2356
  addProjectToHistory(projectPath);
2359
2357
  localStorage.setItem(STORAGE_KEY_CURRENT, projectPath);
2360
2358
 
2361
- document.getElementById('project-select').value = projectPath;
2362
- document.getElementById('project-path-input').value = '';
2359
+ const legacySelect = document.getElementById('project-select');
2360
+ if (legacySelect) legacySelect.value = projectPath;
2361
+ const legacyInput = document.getElementById('project-path-input');
2362
+ if (legacyInput) legacyInput.value = '';
2363
2363
 
2364
2364
  const shortName = projectPath.split('/').slice(-2).join('/');
2365
2365
  showConfigStatus(`<i data-lucide="check-circle"></i> ${escapeHtml(shortName)}`, 'success');
@@ -2415,7 +2415,7 @@
2415
2415
 
2416
2416
  function removeCurrentProject() {
2417
2417
  const select = document.getElementById('project-select');
2418
- const path = select.value;
2418
+ const path = select?.value ?? currentProjectPath ?? '';
2419
2419
  if (!path) {
2420
2420
  showConfigStatus(t('project.noProjectSelected'), 'error');
2421
2421
  return;
@@ -2426,7 +2426,7 @@
2426
2426
  localStorage.removeItem(STORAGE_KEY_CURRENT);
2427
2427
  currentProjectPath = null;
2428
2428
  currentProjectConfig = null;
2429
- document.getElementById('config-info').classList.add('hidden');
2429
+ document.getElementById('config-info')?.classList.add('hidden');
2430
2430
  showConfigStatus(t('project.removed'), 'success');
2431
2431
  }
2432
2432
  }
@@ -2450,22 +2450,95 @@
2450
2450
  }
2451
2451
  }
2452
2452
 
2453
- async function initProjectLoader() {
2453
+ // SPEC-91 Dashboard Multi-Project Overview
2454
+ let availableRepositories = [];
2455
+ let activeTabId = 'overview';
2456
+
2457
+ async function fetchAvailableRepositories() {
2458
+ try {
2459
+ const response = await fetch(`${API_URL}/api/repositories`);
2460
+ const data = await response.json();
2461
+ availableRepositories = Array.isArray(data.repositories)
2462
+ ? data.repositories.filter((repository) => repository.enabled)
2463
+ : [];
2464
+ } catch {
2465
+ availableRepositories = [];
2466
+ }
2467
+ }
2468
+
2469
+ function renderDashboardTabs() {
2470
+ const container = document.getElementById('dashboard-tabs');
2471
+ if (!container) return;
2472
+ const model = buildTabBarModel({
2473
+ repositories: availableRepositories,
2474
+ activeTabId: activeTabId === 'overview' ? null : activeTabId,
2475
+ });
2476
+ container.innerHTML = renderTabBarHtml(model);
2477
+ container.querySelectorAll('.dashboard-tab').forEach((button) => {
2478
+ button.addEventListener('click', () => {
2479
+ const tabId = button.dataset.tabId;
2480
+ if (!tabId) return;
2481
+ handleTabClick(tabId);
2482
+ });
2483
+ });
2484
+ }
2485
+
2486
+ function handleTabClick(tabId) {
2487
+ if (tabId === 'overview') {
2488
+ activateOverviewTab();
2489
+ return;
2490
+ }
2491
+ activateProjectTab(tabId);
2492
+ }
2493
+
2494
+ function activateOverviewTab() {
2495
+ activeTabId = 'overview';
2496
+ writeActiveTab('overview');
2497
+ document.body.classList.add('overview-tab-active');
2498
+ const overviewSection = document.getElementById('overview-section');
2499
+ if (overviewSection) overviewSection.classList.remove('hidden');
2500
+ renderDashboardTabs();
2501
+ refreshOverviewSection();
2502
+ }
2503
+
2504
+ function activateProjectTab(projectPath) {
2505
+ activeTabId = projectPath;
2506
+ writeActiveTab(projectPath);
2507
+ document.body.classList.remove('overview-tab-active');
2508
+ const overviewSection = document.getElementById('overview-section');
2509
+ if (overviewSection) overviewSection.classList.add('hidden');
2510
+ renderDashboardTabs();
2511
+ loadProjectConfigFromPath(projectPath);
2512
+ }
2513
+
2514
+ async function refreshOverviewSection() {
2515
+ const container = document.getElementById('overview-section');
2516
+ if (!container) return;
2517
+ if (activeTabId !== 'overview') return;
2518
+ try {
2519
+ const response = await fetch(`${API_URL}/api/overview`);
2520
+ const viewModel = await response.json();
2521
+ container.innerHTML = renderOverviewHtml(viewModel);
2522
+ container.querySelectorAll('.overview-project-card').forEach((card) => {
2523
+ card.addEventListener('click', () => {
2524
+ const projectPath = card.dataset.projectPath;
2525
+ if (projectPath) activateProjectTab(projectPath);
2526
+ });
2527
+ });
2528
+ } catch {
2529
+ // Silent: WS reconnection or next refresh will retry
2530
+ }
2531
+ }
2532
+
2533
+ async function initOverviewAndTabs() {
2534
+ await fetchAvailableRepositories();
2454
2535
  await syncServerRepositories();
2455
- updateProjectSelect();
2456
- const lastProject = localStorage.getItem(STORAGE_KEY_CURRENT);
2457
- if (lastProject) {
2458
- loadProjectConfigFromPath(lastProject);
2536
+ const persistedTab = readActiveTab();
2537
+ const repositoryPaths = availableRepositories.map((repository) => repository.localPath);
2538
+ if (persistedTab && persistedTab !== 'overview' && repositoryPaths.includes(persistedTab)) {
2539
+ activateProjectTab(persistedTab);
2459
2540
  } else {
2460
- const storedProjects = getStoredProjects();
2461
- if (storedProjects.length > 0) {
2462
- loadProjectConfigFromPath(storedProjects[0]);
2463
- } else {
2464
- document.getElementById('recent-reviews').innerHTML =
2465
- `<div class="empty-state">${t('empty.reviewsNoProject')}</div>`;
2466
- document.getElementById('project-stats').innerHTML =
2467
- `<div class="empty-state">${t('empty.statsNoProject')}</div>`;
2468
- }
2541
+ activateOverviewTab();
2469
2542
  }
2470
2543
  }
2471
2544
 
@@ -2926,7 +2999,7 @@
2926
2999
  checkClaudeStatus();
2927
3000
  loadModelSetting();
2928
3001
  loadLanguageSetting();
2929
- initProjectLoader();
3002
+ initOverviewAndTabs();
2930
3003
  refreshBudgetTile();
2931
3004
  initBudgetSlider();
2932
3005
 
@@ -3,5 +3,6 @@ export const RECONNECT_DELAY: 3000;
3
3
  export const STORAGE_KEY_PROJECTS: "review-flow-projects";
4
4
  export const STORAGE_KEY_CURRENT: "review-flow-current-project";
5
5
  export const STORAGE_KEY_FOCUS_STRIP_MODE: "review-flow-focus-strip-mode";
6
+ export const STORAGE_KEY_ACTIVE_TAB: "review-flow-active-tab";
6
7
  export const QUALITY_TARGET_SCORE: 8;
7
8
  //# sourceMappingURL=constants.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"constants.d.ts","sourceRoot":"","sources":["../../../src/dashboard/modules/constants.js"],"names":[],"mappings":"AAAA,qCAAsC,EAAE,CAAC;AACzC,8BAA+B,IAAI,CAAC;AACpC,mCAAoC,sBAAsB,CAAC;AAC3D,kCAAmC,6BAA6B,CAAC;AACjE,2CAA4C,8BAA8B,CAAC;AAC3E,mCAAoC,CAAC,CAAC"}
1
+ {"version":3,"file":"constants.d.ts","sourceRoot":"","sources":["../../../src/dashboard/modules/constants.js"],"names":[],"mappings":"AAAA,qCAAsC,EAAE,CAAC;AACzC,8BAA+B,IAAI,CAAC;AACpC,mCAAoC,sBAAsB,CAAC;AAC3D,kCAAmC,6BAA6B,CAAC;AACjE,2CAA4C,8BAA8B,CAAC;AAC3E,qCAAsC,wBAAwB,CAAC;AAC/D,mCAAoC,CAAC,CAAC"}
@@ -3,4 +3,5 @@ export const RECONNECT_DELAY = 3000;
3
3
  export const STORAGE_KEY_PROJECTS = 'review-flow-projects';
4
4
  export const STORAGE_KEY_CURRENT = 'review-flow-current-project';
5
5
  export const STORAGE_KEY_FOCUS_STRIP_MODE = 'review-flow-focus-strip-mode';
6
+ export const STORAGE_KEY_ACTIVE_TAB = 'review-flow-active-tab';
6
7
  export const QUALITY_TARGET_SCORE = 8;
@@ -1 +1 @@
1
- {"version":3,"file":"constants.js","sourceRoot":"","sources":["../../../src/dashboard/modules/constants.js"],"names":[],"mappings":"AAAA,MAAM,CAAC,MAAM,sBAAsB,GAAG,EAAE,CAAC;AACzC,MAAM,CAAC,MAAM,eAAe,GAAG,IAAI,CAAC;AACpC,MAAM,CAAC,MAAM,oBAAoB,GAAG,sBAAsB,CAAC;AAC3D,MAAM,CAAC,MAAM,mBAAmB,GAAG,6BAA6B,CAAC;AACjE,MAAM,CAAC,MAAM,4BAA4B,GAAG,8BAA8B,CAAC;AAC3E,MAAM,CAAC,MAAM,oBAAoB,GAAG,CAAC,CAAC"}
1
+ {"version":3,"file":"constants.js","sourceRoot":"","sources":["../../../src/dashboard/modules/constants.js"],"names":[],"mappings":"AAAA,MAAM,CAAC,MAAM,sBAAsB,GAAG,EAAE,CAAC;AACzC,MAAM,CAAC,MAAM,eAAe,GAAG,IAAI,CAAC;AACpC,MAAM,CAAC,MAAM,oBAAoB,GAAG,sBAAsB,CAAC;AAC3D,MAAM,CAAC,MAAM,mBAAmB,GAAG,6BAA6B,CAAC;AACjE,MAAM,CAAC,MAAM,4BAA4B,GAAG,8BAA8B,CAAC;AAC3E,MAAM,CAAC,MAAM,sBAAsB,GAAG,wBAAwB,CAAC;AAC/D,MAAM,CAAC,MAAM,oBAAoB,GAAG,CAAC,CAAC"}
@@ -0,0 +1,64 @@
1
+ /**
2
+ * Renders a vector-only sparkline. Returns '' when there is nothing to draw.
3
+ *
4
+ * @param {number[]} points
5
+ * @returns {string}
6
+ */
7
+ export function renderSparklineSvg(points: number[]): string;
8
+ /**
9
+ * Renders the overview HTML from a server-provided view-model.
10
+ * Tolerates missing sections by falling back to empty French defaults — the server
11
+ * is a boundary, so this guards against malformed payloads (deploys, partial fetches).
12
+ *
13
+ * @param {unknown} viewModel
14
+ * @returns {string}
15
+ */
16
+ export function renderOverviewHtml(viewModel: unknown): string;
17
+ export type OverviewActiveReviewItem = {
18
+ jobId: string;
19
+ projectName: string;
20
+ projectPath: string;
21
+ mrPrefix: "MR" | "PR";
22
+ mrNumber: number;
23
+ mrUrl: string;
24
+ elapsedLabel: string;
25
+ jobType: "review" | "followup";
26
+ };
27
+ export type OverviewActiveReviewsSection = {
28
+ items: OverviewActiveReviewItem[];
29
+ isEmpty: boolean;
30
+ emptyMessage: string;
31
+ };
32
+ export type OverviewProjectCardItem = {
33
+ projectName: string;
34
+ projectPath: string;
35
+ platform: "gitlab" | "github";
36
+ totalReviews: number;
37
+ averageScoreLabel: string;
38
+ sparklinePoints: number[];
39
+ isEmptyHistory: boolean;
40
+ };
41
+ export type OverviewProjectCardsSection = {
42
+ items: OverviewProjectCardItem[];
43
+ isEmpty: boolean;
44
+ emptyMessage: string;
45
+ };
46
+ export type OverviewRecentReviewItem = {
47
+ filename: string;
48
+ projectName: string;
49
+ mrPrefix: "MR" | "PR";
50
+ mrNumber: string;
51
+ title: string;
52
+ mtime: string;
53
+ };
54
+ export type OverviewRecentReviewsFeedSection = {
55
+ items: OverviewRecentReviewItem[];
56
+ isEmpty: boolean;
57
+ emptyMessage: string;
58
+ };
59
+ export type OverviewViewModel = {
60
+ activeReviews: OverviewActiveReviewsSection;
61
+ projectCards: OverviewProjectCardsSection;
62
+ recentReviewsFeed: OverviewRecentReviewsFeedSection;
63
+ };
64
+ //# sourceMappingURL=overview.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"overview.d.ts","sourceRoot":"","sources":["../../../src/dashboard/modules/overview.js"],"names":[],"mappings":"AA+FA;;;;;GAKG;AACH,2CAHW,MAAM,EAAE,GACN,MAAM,CAkBlB;AAoGD;;;;;;;GAOG;AACH,8CAHW,OAAO,GACL,MAAM,CAsBlB;;WApOa,MAAM;iBACN,MAAM;iBACN,MAAM;cACN,IAAI,GAAG,IAAI;cACX,MAAM;WACN,MAAM;kBACN,MAAM;aACN,QAAQ,GAAG,UAAU;;;WAKrB,wBAAwB,EAAE;aAC1B,OAAO;kBACP,MAAM;;;iBAKN,MAAM;iBACN,MAAM;cACN,QAAQ,GAAG,QAAQ;kBACnB,MAAM;uBACN,MAAM;qBACN,MAAM,EAAE;oBACR,OAAO;;;WAKP,uBAAuB,EAAE;aACzB,OAAO;kBACP,MAAM;;;cAKN,MAAM;iBACN,MAAM;cACN,IAAI,GAAG,IAAI;cACX,MAAM;WACN,MAAM;WACN,MAAM;;;WAKN,wBAAwB,EAAE;aAC1B,OAAO;kBACP,MAAM;;;mBAKN,4BAA4B;kBAC5B,2BAA2B;uBAC3B,gCAAgC"}
@@ -0,0 +1,246 @@
1
+ /**
2
+ * Dashboard module — multi-project overview humble object (SPEC-91).
3
+ * Pure functions, no global state, no DOM access.
4
+ * All presentation logic lives in OverviewPresenter (server-side TypeScript);
5
+ * this module renders its view-model as HTML.
6
+ *
7
+ * Visual DNA: "Agentic OS" — see project_agentic_os_design_dna.md.
8
+ */
9
+
10
+ import { escapeHtml, sanitizeHttpUrl } from './html.js';
11
+
12
+ const SPARKLINE_WIDTH = 96;
13
+ const SPARKLINE_HEIGHT = 28;
14
+ const SPARKLINE_PADDING = 2;
15
+
16
+ /**
17
+ * @typedef {Object} OverviewActiveReviewItem
18
+ * @property {string} jobId
19
+ * @property {string} projectName
20
+ * @property {string} projectPath
21
+ * @property {'MR' | 'PR'} mrPrefix
22
+ * @property {number} mrNumber
23
+ * @property {string} mrUrl
24
+ * @property {string} elapsedLabel
25
+ * @property {'review' | 'followup'} jobType
26
+ */
27
+
28
+ /**
29
+ * @typedef {Object} OverviewActiveReviewsSection
30
+ * @property {OverviewActiveReviewItem[]} items
31
+ * @property {boolean} isEmpty
32
+ * @property {string} emptyMessage
33
+ */
34
+
35
+ /**
36
+ * @typedef {Object} OverviewProjectCardItem
37
+ * @property {string} projectName
38
+ * @property {string} projectPath
39
+ * @property {'gitlab' | 'github'} platform
40
+ * @property {number} totalReviews
41
+ * @property {string} averageScoreLabel
42
+ * @property {number[]} sparklinePoints
43
+ * @property {boolean} isEmptyHistory
44
+ */
45
+
46
+ /**
47
+ * @typedef {Object} OverviewProjectCardsSection
48
+ * @property {OverviewProjectCardItem[]} items
49
+ * @property {boolean} isEmpty
50
+ * @property {string} emptyMessage
51
+ */
52
+
53
+ /**
54
+ * @typedef {Object} OverviewRecentReviewItem
55
+ * @property {string} filename
56
+ * @property {string} projectName
57
+ * @property {'MR' | 'PR'} mrPrefix
58
+ * @property {string} mrNumber
59
+ * @property {string} title
60
+ * @property {string} mtime
61
+ */
62
+
63
+ /**
64
+ * @typedef {Object} OverviewRecentReviewsFeedSection
65
+ * @property {OverviewRecentReviewItem[]} items
66
+ * @property {boolean} isEmpty
67
+ * @property {string} emptyMessage
68
+ */
69
+
70
+ /**
71
+ * @typedef {Object} OverviewViewModel
72
+ * @property {OverviewActiveReviewsSection} activeReviews
73
+ * @property {OverviewProjectCardsSection} projectCards
74
+ * @property {OverviewRecentReviewsFeedSection} recentReviewsFeed
75
+ */
76
+
77
+ /**
78
+ * @param {unknown} section
79
+ * @param {string} emptyMessage
80
+ * @returns {{ items: unknown[]; isEmpty: boolean; emptyMessage: string }}
81
+ */
82
+ function sectionWithDefaults(section, emptyMessage) {
83
+ if (!section || typeof section !== 'object') {
84
+ return { items: [], isEmpty: true, emptyMessage };
85
+ }
86
+ const typed = /** @type {Record<string, unknown>} */ (section);
87
+ const items = Array.isArray(typed.items) ? typed.items : [];
88
+ const explicitMessage = typeof typed.emptyMessage === 'string' ? typed.emptyMessage : emptyMessage;
89
+ return {
90
+ items,
91
+ isEmpty: items.length === 0,
92
+ emptyMessage: explicitMessage,
93
+ };
94
+ }
95
+
96
+ /**
97
+ * Renders a vector-only sparkline. Returns '' when there is nothing to draw.
98
+ *
99
+ * @param {number[]} points
100
+ * @returns {string}
101
+ */
102
+ export function renderSparklineSvg(points) {
103
+ if (!Array.isArray(points) || points.length === 0) return '';
104
+ const min = Math.min(...points);
105
+ const max = Math.max(...points);
106
+ const range = max - min || 1;
107
+ const usableWidth = SPARKLINE_WIDTH - SPARKLINE_PADDING * 2;
108
+ const usableHeight = SPARKLINE_HEIGHT - SPARKLINE_PADDING * 2;
109
+ const stepX = points.length === 1 ? 0 : usableWidth / (points.length - 1);
110
+ const coordinates = points
111
+ .map((value, index) => {
112
+ const x = SPARKLINE_PADDING + index * stepX;
113
+ const y = SPARKLINE_PADDING + usableHeight - ((value - min) / range) * usableHeight;
114
+ return `${x.toFixed(1)},${y.toFixed(1)}`;
115
+ })
116
+ .join(' ');
117
+ return `<svg class="overview-sparkline" viewBox="0 0 ${SPARKLINE_WIDTH} ${SPARKLINE_HEIGHT}" preserveAspectRatio="none" aria-hidden="true"><polyline fill="none" stroke="currentColor" stroke-width="1.5" points="${coordinates}" /></svg>`;
118
+ }
119
+
120
+ /**
121
+ * @param {OverviewActiveReviewItem} item
122
+ * @returns {string}
123
+ */
124
+ function renderActiveReviewRow(item) {
125
+ return `
126
+ <li class="overview-active-row" data-job-id="${escapeHtml(item.jobId)}">
127
+ <span class="overview-status-dot" data-status="running"></span>
128
+ <span class="overview-active-project">${escapeHtml(item.projectName)}</span>
129
+ <a class="overview-active-mr" href="${escapeHtml(sanitizeHttpUrl(item.mrUrl))}" target="_blank" rel="noopener">${escapeHtml(item.mrPrefix)} #${escapeHtml(String(item.mrNumber))}</a>
130
+ <span class="overview-active-elapsed">${escapeHtml(item.elapsedLabel)}</span>
131
+ </li>
132
+ `.trim();
133
+ }
134
+
135
+ /**
136
+ * @param {OverviewActiveReviewsSection} section
137
+ * @returns {string}
138
+ */
139
+ function renderActiveReviewsSection(section) {
140
+ const body = section.isEmpty
141
+ ? `<div class="overview-empty">${escapeHtml(section.emptyMessage)}</div>`
142
+ : `<ul class="overview-active-list">${section.items.map(renderActiveReviewRow).join('')}</ul>`;
143
+ return `
144
+ <section class="overview-panel" data-section="active">
145
+ <div class="overview-panel-title">// ACTIVE REVIEWS</div>
146
+ ${body}
147
+ </section>
148
+ `.trim();
149
+ }
150
+
151
+ /**
152
+ * @param {OverviewProjectCardItem} card
153
+ * @returns {string}
154
+ */
155
+ function renderProjectCard(card) {
156
+ const sparkline = card.sparklinePoints.length === 0 ? '' : renderSparklineSvg(card.sparklinePoints);
157
+ return `
158
+ <button type="button" class="overview-project-card" data-project-path="${escapeHtml(card.projectPath)}">
159
+ <div class="overview-project-card-header">
160
+ <span class="overview-project-card-name">${escapeHtml(card.projectName)}</span>
161
+ <span class="overview-project-card-platform" data-platform="${escapeHtml(card.platform)}">${escapeHtml(card.platform)}</span>
162
+ </div>
163
+ <div class="overview-project-card-totals">
164
+ <span class="overview-project-card-count">${escapeHtml(String(card.totalReviews))} reviews</span>
165
+ <span class="overview-project-card-score">Score ${escapeHtml(card.averageScoreLabel)}</span>
166
+ </div>
167
+ <div class="overview-project-card-sparkline">${sparkline}</div>
168
+ </button>
169
+ `.trim();
170
+ }
171
+
172
+ /**
173
+ * @param {OverviewProjectCardsSection} section
174
+ * @returns {string}
175
+ */
176
+ function renderProjectCardsSection(section) {
177
+ const body = section.isEmpty
178
+ ? `<div class="overview-empty">${escapeHtml(section.emptyMessage)}</div>`
179
+ : `<div class="overview-project-card-grid">${section.items.map(renderProjectCard).join('')}</div>`;
180
+ return `
181
+ <section class="overview-panel" data-section="projects">
182
+ <div class="overview-panel-title">// PROJECTS</div>
183
+ ${body}
184
+ </section>
185
+ `.trim();
186
+ }
187
+
188
+ /**
189
+ * @param {OverviewRecentReviewItem} item
190
+ * @returns {string}
191
+ */
192
+ function renderRecentReviewRow(item) {
193
+ return `
194
+ <li class="overview-recent-row" data-filename="${escapeHtml(item.filename)}">
195
+ <span class="overview-recent-project">${escapeHtml(item.projectName)}</span>
196
+ <span class="overview-recent-mr">${escapeHtml(item.mrPrefix)} #${escapeHtml(item.mrNumber)}</span>
197
+ <span class="overview-recent-title">${escapeHtml(item.title)}</span>
198
+ </li>
199
+ `.trim();
200
+ }
201
+
202
+ /**
203
+ * @param {OverviewRecentReviewsFeedSection} section
204
+ * @returns {string}
205
+ */
206
+ function renderRecentReviewsSection(section) {
207
+ const body = section.isEmpty
208
+ ? `<div class="overview-empty">${escapeHtml(section.emptyMessage)}</div>`
209
+ : `<ul class="overview-recent-list">${section.items.map(renderRecentReviewRow).join('')}</ul>`;
210
+ return `
211
+ <section class="overview-panel" data-section="recent">
212
+ <div class="overview-panel-title">// RECENT REVIEWS</div>
213
+ ${body}
214
+ </section>
215
+ `.trim();
216
+ }
217
+
218
+ /**
219
+ * Renders the overview HTML from a server-provided view-model.
220
+ * Tolerates missing sections by falling back to empty French defaults — the server
221
+ * is a boundary, so this guards against malformed payloads (deploys, partial fetches).
222
+ *
223
+ * @param {unknown} viewModel
224
+ * @returns {string}
225
+ */
226
+ export function renderOverviewHtml(viewModel) {
227
+ const source = viewModel && typeof viewModel === 'object'
228
+ ? /** @type {Record<string, unknown>} */ (viewModel)
229
+ : {};
230
+ const activeReviews = /** @type {OverviewActiveReviewsSection} */ (
231
+ sectionWithDefaults(source.activeReviews, 'Aucune review en cours')
232
+ );
233
+ const projectCards = /** @type {OverviewProjectCardsSection} */ (
234
+ sectionWithDefaults(source.projectCards, 'Aucun projet configuré')
235
+ );
236
+ const recentReviewsFeed = /** @type {OverviewRecentReviewsFeedSection} */ (
237
+ sectionWithDefaults(source.recentReviewsFeed, 'Aucune review récente')
238
+ );
239
+ return `
240
+ <div class="overview-grid">
241
+ ${renderActiveReviewsSection(activeReviews)}
242
+ ${renderProjectCardsSection(projectCards)}
243
+ ${renderRecentReviewsSection(recentReviewsFeed)}
244
+ </div>
245
+ `.trim();
246
+ }
@@ -0,0 +1 @@
1
+ {"version":3,"file":"overview.js","sourceRoot":"","sources":["../../../src/dashboard/modules/overview.js"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,EAAE,UAAU,EAAE,eAAe,EAAE,MAAM,WAAW,CAAC;AAExD,MAAM,eAAe,GAAG,EAAE,CAAC;AAC3B,MAAM,gBAAgB,GAAG,EAAE,CAAC;AAC5B,MAAM,iBAAiB,GAAG,CAAC,CAAC;AAE5B;;;;;;;;;;GAUG;AAEH;;;;;GAKG;AAEH;;;;;;;;;GASG;AAEH;;;;;GAKG;AAEH;;;;;;;;GAQG;AAEH;;;;;GAKG;AAEH;;;;;GAKG;AAEH;;;;GAIG;AACH,SAAS,mBAAmB,CAAC,OAAO,EAAE,YAAY;IAChD,IAAI,CAAC,OAAO,IAAI,OAAO,OAAO,KAAK,QAAQ,EAAE,CAAC;QAC5C,OAAO,EAAE,KAAK,EAAE,EAAE,EAAE,OAAO,EAAE,IAAI,EAAE,YAAY,EAAE,CAAC;IACpD,CAAC;IACD,MAAM,KAAK,GAAG,sCAAsC,CAAC,CAAC,OAAO,CAAC,CAAC;IAC/D,MAAM,KAAK,GAAG,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,CAAC;IAC5D,MAAM,eAAe,GAAG,OAAO,KAAK,CAAC,YAAY,KAAK,QAAQ,CAAC,CAAC,CAAC,KAAK,CAAC,YAAY,CAAC,CAAC,CAAC,YAAY,CAAC;IACnG,OAAO;QACL,KAAK;QACL,OAAO,EAAE,KAAK,CAAC,MAAM,KAAK,CAAC;QAC3B,YAAY,EAAE,eAAe;KAC9B,CAAC;AACJ,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,kBAAkB,CAAC,MAAM;IACvC,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,MAAM,CAAC,IAAI,MAAM,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,EAAE,CAAC;IAC7D,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG,CAAC,GAAG,MAAM,CAAC,CAAC;IAChC,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG,CAAC,GAAG,MAAM,CAAC,CAAC;IAChC,MAAM,KAAK,GAAG,GAAG,GAAG,GAAG,IAAI,CAAC,CAAC;IAC7B,MAAM,WAAW,GAAG,eAAe,GAAG,iBAAiB,GAAG,CAAC,CAAC;IAC5D,MAAM,YAAY,GAAG,gBAAgB,GAAG,iBAAiB,GAAG,CAAC,CAAC;IAC9D,MAAM,KAAK,GAAG,MAAM,CAAC,MAAM,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,WAAW,GAAG,CAAC,MAAM,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC;IAC1E,MAAM,WAAW,GAAG,MAAM;SACvB,GAAG,CAAC,CAAC,KAAK,EAAE,KAAK,EAAE,EAAE;QACpB,MAAM,CAAC,GAAG,iBAAiB,GAAG,KAAK,GAAG,KAAK,CAAC;QAC5C,MAAM,CAAC,GAAG,iBAAiB,GAAG,YAAY,GAAG,CAAC,CAAC,KAAK,GAAG,GAAG,CAAC,GAAG,KAAK,CAAC,GAAG,YAAY,CAAC;QACpF,OAAO,GAAG,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,CAAC;IAC3C,CAAC,CAAC;SACD,IAAI,CAAC,GAAG,CAAC,CAAC;IACb,OAAO,gDAAgD,eAAe,IAAI,gBAAgB,0HAA0H,WAAW,YAAY,CAAC;AAC9O,CAAC;AAED;;;GAGG;AACH,SAAS,qBAAqB,CAAC,IAAI;IACjC,OAAO;mDAC0C,UAAU,CAAC,IAAI,CAAC,KAAK,CAAC;;8CAE3B,UAAU,CAAC,IAAI,CAAC,WAAW,CAAC;4CAC9B,UAAU,CAAC,eAAe,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,oCAAoC,UAAU,CAAC,IAAI,CAAC,QAAQ,CAAC,KAAK,UAAU,CAAC,MAAM,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;8CACxI,UAAU,CAAC,IAAI,CAAC,YAAY,CAAC;;GAExE,CAAC,IAAI,EAAE,CAAC;AACX,CAAC;AAED;;;GAGG;AACH,SAAS,0BAA0B,CAAC,OAAO;IACzC,MAAM,IAAI,GAAG,OAAO,CAAC,OAAO;QAC1B,CAAC,CAAC,+BAA+B,UAAU,CAAC,OAAO,CAAC,YAAY,CAAC,QAAQ;QACzE,CAAC,CAAC,oCAAoC,OAAO,CAAC,KAAK,CAAC,GAAG,CAAC,qBAAqB,CAAC,CAAC,IAAI,CAAC,EAAE,CAAC,OAAO,CAAC;IACjG,OAAO;;;QAGD,IAAI;;GAET,CAAC,IAAI,EAAE,CAAC;AACX,CAAC;AAED;;;GAGG;AACH,SAAS,iBAAiB,CAAC,IAAI;IAC7B,MAAM,SAAS,GAAG,IAAI,CAAC,eAAe,CAAC,MAAM,KAAK,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,kBAAkB,CAAC,IAAI,CAAC,eAAe,CAAC,CAAC;IACpG,OAAO;6EACoE,UAAU,CAAC,IAAI,CAAC,WAAW,CAAC;;mDAEtD,UAAU,CAAC,IAAI,CAAC,WAAW,CAAC;sEACT,UAAU,CAAC,IAAI,CAAC,QAAQ,CAAC,KAAK,UAAU,CAAC,IAAI,CAAC,QAAQ,CAAC;;;oDAGzE,UAAU,CAAC,MAAM,CAAC,IAAI,CAAC,YAAY,CAAC,CAAC;0DAC/B,UAAU,CAAC,IAAI,CAAC,iBAAiB,CAAC;;qDAEvC,SAAS;;GAE3D,CAAC,IAAI,EAAE,CAAC;AACX,CAAC;AAED;;;GAGG;AACH,SAAS,yBAAyB,CAAC,OAAO;IACxC,MAAM,IAAI,GAAG,OAAO,CAAC,OAAO;QAC1B,CAAC,CAAC,+BAA+B,UAAU,CAAC,OAAO,CAAC,YAAY,CAAC,QAAQ;QACzE,CAAC,CAAC,2CAA2C,OAAO,CAAC,KAAK,CAAC,GAAG,CAAC,iBAAiB,CAAC,CAAC,IAAI,CAAC,EAAE,CAAC,QAAQ,CAAC;IACrG,OAAO;;;QAGD,IAAI;;GAET,CAAC,IAAI,EAAE,CAAC;AACX,CAAC;AAED;;;GAGG;AACH,SAAS,qBAAqB,CAAC,IAAI;IACjC,OAAO;qDAC4C,UAAU,CAAC,IAAI,CAAC,QAAQ,CAAC;8CAChC,UAAU,CAAC,IAAI,CAAC,WAAW,CAAC;yCACjC,UAAU,CAAC,IAAI,CAAC,QAAQ,CAAC,KAAK,UAAU,CAAC,IAAI,CAAC,QAAQ,CAAC;4CACpD,UAAU,CAAC,IAAI,CAAC,KAAK,CAAC;;GAE/D,CAAC,IAAI,EAAE,CAAC;AACX,CAAC;AAED;;;GAGG;AACH,SAAS,0BAA0B,CAAC,OAAO;IACzC,MAAM,IAAI,GAAG,OAAO,CAAC,OAAO;QAC1B,CAAC,CAAC,+BAA+B,UAAU,CAAC,OAAO,CAAC,YAAY,CAAC,QAAQ;QACzE,CAAC,CAAC,oCAAoC,OAAO,CAAC,KAAK,CAAC,GAAG,CAAC,qBAAqB,CAAC,CAAC,IAAI,CAAC,EAAE,CAAC,OAAO,CAAC;IACjG,OAAO;;;QAGD,IAAI;;GAET,CAAC,IAAI,EAAE,CAAC;AACX,CAAC;AAED;;;;;;;GAOG;AACH,MAAM,UAAU,kBAAkB,CAAC,SAAS;IAC1C,MAAM,MAAM,GAAG,SAAS,IAAI,OAAO,SAAS,KAAK,QAAQ;QACvD,CAAC,CAAC,sCAAsC,CAAC,CAAC,SAAS,CAAC;QACpD,CAAC,CAAC,EAAE,CAAC;IACP,MAAM,aAAa,GAAG,2CAA2C,CAAC,CAChE,mBAAmB,CAAC,MAAM,CAAC,aAAa,EAAE,wBAAwB,CAAC,CACpE,CAAC;IACF,MAAM,YAAY,GAAG,0CAA0C,CAAC,CAC9D,mBAAmB,CAAC,MAAM,CAAC,YAAY,EAAE,wBAAwB,CAAC,CACnE,CAAC;IACF,MAAM,iBAAiB,GAAG,+CAA+C,CAAC,CACxE,mBAAmB,CAAC,MAAM,CAAC,iBAAiB,EAAE,uBAAuB,CAAC,CACvE,CAAC;IACF,OAAO;;QAED,0BAA0B,CAAC,aAAa,CAAC;QACzC,yBAAyB,CAAC,YAAY,CAAC;QACvC,0BAA0B,CAAC,iBAAiB,CAAC;;GAElD,CAAC,IAAI,EAAE,CAAC;AACX,CAAC"}
@@ -0,0 +1,56 @@
1
+ /**
2
+ * @typedef {Object} TabBarRepositoryInput
3
+ * @property {string} name
4
+ * @property {string} localPath
5
+ */
6
+ /**
7
+ * @typedef {Object} TabBarBuildInput
8
+ * @property {TabBarRepositoryInput[]} repositories
9
+ * @property {string | null} activeTabId
10
+ */
11
+ /**
12
+ * @typedef {Object} TabBarTabViewModel
13
+ * @property {string} id
14
+ * @property {string} label
15
+ * @property {boolean} isActive
16
+ */
17
+ /**
18
+ * @typedef {Object} TabBarViewModel
19
+ * @property {TabBarTabViewModel[]} tabs
20
+ */
21
+ /**
22
+ * @param {TabBarBuildInput} input
23
+ * @returns {TabBarViewModel}
24
+ */
25
+ export function buildTabBarModel(input: TabBarBuildInput): TabBarViewModel;
26
+ /**
27
+ * @param {TabBarViewModel} viewModel
28
+ * @returns {string}
29
+ */
30
+ export function renderTabBarHtml(viewModel: TabBarViewModel): string;
31
+ /**
32
+ * @returns {string | null}
33
+ */
34
+ export function readActiveTab(): string | null;
35
+ /**
36
+ * @param {string} tabId
37
+ * @returns {void}
38
+ */
39
+ export function writeActiveTab(tabId: string): void;
40
+ export type TabBarRepositoryInput = {
41
+ name: string;
42
+ localPath: string;
43
+ };
44
+ export type TabBarBuildInput = {
45
+ repositories: TabBarRepositoryInput[];
46
+ activeTabId: string | null;
47
+ };
48
+ export type TabBarTabViewModel = {
49
+ id: string;
50
+ label: string;
51
+ isActive: boolean;
52
+ };
53
+ export type TabBarViewModel = {
54
+ tabs: TabBarTabViewModel[];
55
+ };
56
+ //# sourceMappingURL=tabBar.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tabBar.d.ts","sourceRoot":"","sources":["../../../src/dashboard/modules/tabBar.js"],"names":[],"mappings":"AAcA;;;;GAIG;AAEH;;;;GAIG;AAEH;;;;;GAKG;AAEH;;;GAGG;AAEH;;;GAGG;AACH,wCAHW,gBAAgB,GACd,eAAe,CAoB3B;AAED;;;GAGG;AACH,4CAHW,eAAe,GACb,MAAM,CAUlB;AAED;;GAEG;AACH,iCAFa,MAAM,GAAG,IAAI,CAQzB;AAED;;;GAGG;AACH,sCAHW,MAAM,GACJ,IAAI,CAQhB;;UAjFa,MAAM;eACN,MAAM;;;kBAKN,qBAAqB,EAAE;iBACvB,MAAM,GAAG,IAAI;;;QAKb,MAAM;WACN,MAAM;cACN,OAAO;;;UAKP,kBAAkB,EAAE"}