reviewflow 3.19.2 → 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.
- package/CHANGELOG.md +7 -0
- package/dist/dashboard/index.html +107 -34
- package/dist/dashboard/modules/constants.d.ts +1 -0
- package/dist/dashboard/modules/constants.d.ts.map +1 -1
- package/dist/dashboard/modules/constants.js +1 -0
- package/dist/dashboard/modules/constants.js.map +1 -1
- package/dist/dashboard/modules/overview.d.ts +64 -0
- package/dist/dashboard/modules/overview.d.ts.map +1 -0
- package/dist/dashboard/modules/overview.js +246 -0
- package/dist/dashboard/modules/overview.js.map +1 -0
- package/dist/dashboard/modules/tabBar.d.ts +56 -0
- package/dist/dashboard/modules/tabBar.d.ts.map +1 -0
- package/dist/dashboard/modules/tabBar.js +98 -0
- package/dist/dashboard/modules/tabBar.js.map +1 -0
- package/dist/dashboard/styles.css +324 -0
- package/dist/main/routes.d.ts.map +1 -1
- package/dist/main/routes.js +20 -9
- package/dist/main/routes.js.map +1 -1
- package/dist/modules/cli-configuration/interface-adapters/controllers/http/repositories.routes.d.ts +7 -0
- package/dist/modules/cli-configuration/interface-adapters/controllers/http/repositories.routes.d.ts.map +1 -0
- package/dist/modules/cli-configuration/interface-adapters/controllers/http/repositories.routes.js +13 -0
- package/dist/modules/cli-configuration/interface-adapters/controllers/http/repositories.routes.js.map +1 -0
- package/dist/modules/statistics-insights/interface-adapters/controllers/http/overview.routes.d.ts +14 -0
- package/dist/modules/statistics-insights/interface-adapters/controllers/http/overview.routes.d.ts.map +1 -0
- package/dist/modules/statistics-insights/interface-adapters/controllers/http/overview.routes.js +39 -0
- package/dist/modules/statistics-insights/interface-adapters/controllers/http/overview.routes.js.map +1 -0
- package/dist/modules/statistics-insights/interface-adapters/presenters/overview.presenter.d.ts +88 -0
- package/dist/modules/statistics-insights/interface-adapters/presenters/overview.presenter.d.ts.map +1 -0
- package/dist/modules/statistics-insights/interface-adapters/presenters/overview.presenter.js +129 -0
- package/dist/modules/statistics-insights/interface-adapters/presenters/overview.presenter.js.map +1 -0
- package/dist/tests/acceptance/91-dashboard-multi-project-overview.acceptance.test.d.ts +10 -0
- package/dist/tests/acceptance/91-dashboard-multi-project-overview.acceptance.test.d.ts.map +1 -0
- package/dist/tests/acceptance/91-dashboard-multi-project-overview.acceptance.test.js +272 -0
- package/dist/tests/acceptance/91-dashboard-multi-project-overview.acceptance.test.js.map +1 -0
- package/dist/tests/factories/projectStatsApiResponse.factory.d.ts +16 -0
- package/dist/tests/factories/projectStatsApiResponse.factory.d.ts.map +1 -0
- package/dist/tests/factories/projectStatsApiResponse.factory.js +39 -0
- package/dist/tests/factories/projectStatsApiResponse.factory.js.map +1 -0
- package/dist/tests/factories/recentReviewFile.factory.d.ts +5 -0
- package/dist/tests/factories/recentReviewFile.factory.d.ts.map +1 -0
- package/dist/tests/factories/recentReviewFile.factory.js +16 -0
- package/dist/tests/factories/recentReviewFile.factory.js.map +1 -0
- package/dist/tests/factories/repositoryConfig.factory.d.ts +5 -0
- package/dist/tests/factories/repositoryConfig.factory.d.ts.map +1 -0
- package/dist/tests/factories/repositoryConfig.factory.js +14 -0
- package/dist/tests/factories/repositoryConfig.factory.js.map +1 -0
- package/dist/tests/units/dashboard/modules/constants.test.js +2 -1
- package/dist/tests/units/dashboard/modules/constants.test.js.map +1 -1
- package/dist/tests/units/dashboard/modules/overview.test.d.ts +2 -0
- package/dist/tests/units/dashboard/modules/overview.test.d.ts.map +1 -0
- package/dist/tests/units/dashboard/modules/overview.test.js +176 -0
- package/dist/tests/units/dashboard/modules/overview.test.js.map +1 -0
- package/dist/tests/units/dashboard/modules/tabBar.test.d.ts +2 -0
- package/dist/tests/units/dashboard/modules/tabBar.test.d.ts.map +1 -0
- package/dist/tests/units/dashboard/modules/tabBar.test.js +99 -0
- package/dist/tests/units/dashboard/modules/tabBar.test.js.map +1 -0
- package/dist/tests/units/modules/cli-configuration/interface-adapters/controllers/http/repositories.routes.test.d.ts +2 -0
- package/dist/tests/units/modules/cli-configuration/interface-adapters/controllers/http/repositories.routes.test.d.ts.map +1 -0
- package/dist/tests/units/modules/cli-configuration/interface-adapters/controllers/http/repositories.routes.test.js +57 -0
- package/dist/tests/units/modules/cli-configuration/interface-adapters/controllers/http/repositories.routes.test.js.map +1 -0
- package/dist/tests/units/modules/statistics-insights/interface-adapters/controllers/http/overview.routes.test.d.ts +2 -0
- package/dist/tests/units/modules/statistics-insights/interface-adapters/controllers/http/overview.routes.test.d.ts.map +1 -0
- package/dist/tests/units/modules/statistics-insights/interface-adapters/controllers/http/overview.routes.test.js +156 -0
- package/dist/tests/units/modules/statistics-insights/interface-adapters/controllers/http/overview.routes.test.js.map +1 -0
- package/dist/tests/units/modules/statistics-insights/interface-adapters/presenters/overview.presenter.test.d.ts +2 -0
- package/dist/tests/units/modules/statistics-insights/interface-adapters/presenters/overview.presenter.test.d.ts.map +1 -0
- package/dist/tests/units/modules/statistics-insights/interface-adapters/presenters/overview.presenter.test.js +289 -0
- package/dist/tests/units/modules/statistics-insights/interface-adapters/presenters/overview.presenter.test.js.map +1 -0
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,13 @@ 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
|
+
|
|
8
15
|
## [3.19.2](https://github.com/DGouron/review-flow/compare/reviewflow-v3.19.1...reviewflow-v3.19.2) (2026-05-24)
|
|
9
16
|
|
|
10
17
|
|
|
@@ -78,19 +78,8 @@
|
|
|
78
78
|
</select>
|
|
79
79
|
</div>
|
|
80
80
|
|
|
81
|
-
<
|
|
82
|
-
|
|
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')
|
|
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
|
|
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')
|
|
2362
|
-
|
|
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
|
|
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')
|
|
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
|
-
|
|
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
|
-
|
|
2456
|
-
const
|
|
2457
|
-
if (
|
|
2458
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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":"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"}
|
|
@@ -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"}
|