reviewflow 3.20.0 → 3.22.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 +14 -0
- package/dist/config/projectConfig.d.ts +7 -0
- package/dist/config/projectConfig.d.ts.map +1 -1
- package/dist/config/projectConfig.js +18 -0
- package/dist/config/projectConfig.js.map +1 -1
- package/dist/dashboard/index.html +626 -175
- package/dist/dashboard/modules/animations.d.ts +164 -0
- package/dist/dashboard/modules/animations.d.ts.map +1 -0
- package/dist/dashboard/modules/animations.js +405 -0
- package/dist/dashboard/modules/animations.js.map +1 -0
- package/dist/dashboard/modules/cardCounters.d.ts +47 -0
- package/dist/dashboard/modules/cardCounters.d.ts.map +1 -0
- package/dist/dashboard/modules/cardCounters.js +74 -0
- package/dist/dashboard/modules/cardCounters.js.map +1 -0
- package/dist/dashboard/modules/i18n.d.ts.map +1 -1
- package/dist/dashboard/modules/i18n.js +20 -2
- package/dist/dashboard/modules/i18n.js.map +1 -1
- package/dist/dashboard/modules/managePanel.d.ts +49 -0
- package/dist/dashboard/modules/managePanel.d.ts.map +1 -0
- package/dist/dashboard/modules/managePanel.js +123 -0
- package/dist/dashboard/modules/managePanel.js.map +1 -0
- package/dist/dashboard/modules/overview.d.ts +1 -0
- package/dist/dashboard/modules/overview.d.ts.map +1 -1
- package/dist/dashboard/modules/overview.js +14 -0
- package/dist/dashboard/modules/overview.js.map +1 -1
- package/dist/dashboard/modules/settingsModal.d.ts +77 -0
- package/dist/dashboard/modules/settingsModal.d.ts.map +1 -0
- package/dist/dashboard/modules/settingsModal.js +202 -0
- package/dist/dashboard/modules/settingsModal.js.map +1 -0
- package/dist/dashboard/modules/tabBar.d.ts +4 -0
- package/dist/dashboard/modules/tabBar.d.ts.map +1 -1
- package/dist/dashboard/modules/tabBar.js +6 -1
- package/dist/dashboard/modules/tabBar.js.map +1 -1
- package/dist/dashboard/styles.css +1373 -402
- package/dist/frameworks/config/configLoader.d.ts +8 -0
- package/dist/frameworks/config/configLoader.d.ts.map +1 -1
- package/dist/frameworks/config/configLoader.js +18 -0
- package/dist/frameworks/config/configLoader.js.map +1 -1
- package/dist/main/routes.d.ts.map +1 -1
- package/dist/main/routes.js +47 -2
- package/dist/main/routes.js.map +1 -1
- package/dist/main/server.d.ts.map +1 -1
- package/dist/main/server.js +22 -0
- package/dist/main/server.js.map +1 -1
- package/dist/modules/claude-invocation/interface-adapters/gateways/reviewReport.fileSystem.gateway.d.ts +1 -0
- package/dist/modules/claude-invocation/interface-adapters/gateways/reviewReport.fileSystem.gateway.d.ts.map +1 -1
- package/dist/modules/claude-invocation/interface-adapters/gateways/reviewReport.fileSystem.gateway.js +26 -5
- package/dist/modules/claude-invocation/interface-adapters/gateways/reviewReport.fileSystem.gateway.js.map +1 -1
- package/dist/modules/cli-configuration/entities/projectConfig/projectConfig.gateway.d.ts +20 -0
- package/dist/modules/cli-configuration/entities/projectConfig/projectConfig.gateway.d.ts.map +1 -0
- package/dist/modules/cli-configuration/entities/projectConfig/projectConfig.gateway.js +2 -0
- package/dist/modules/cli-configuration/entities/projectConfig/projectConfig.gateway.js.map +1 -0
- package/dist/modules/cli-configuration/entities/repositoryEntry/repositoryEntry.d.ts +13 -0
- package/dist/modules/cli-configuration/entities/repositoryEntry/repositoryEntry.d.ts.map +1 -0
- package/dist/modules/cli-configuration/entities/repositoryEntry/repositoryEntry.js +2 -0
- package/dist/modules/cli-configuration/entities/repositoryEntry/repositoryEntry.js.map +1 -0
- package/dist/modules/cli-configuration/interface-adapters/controllers/http/projectConfig.routes.d.ts +6 -1
- package/dist/modules/cli-configuration/interface-adapters/controllers/http/projectConfig.routes.d.ts.map +1 -1
- package/dist/modules/cli-configuration/interface-adapters/controllers/http/projectConfig.routes.js +116 -13
- package/dist/modules/cli-configuration/interface-adapters/controllers/http/projectConfig.routes.js.map +1 -1
- package/dist/modules/cli-configuration/interface-adapters/controllers/http/repositories.routes.d.ts +36 -0
- package/dist/modules/cli-configuration/interface-adapters/controllers/http/repositories.routes.d.ts.map +1 -1
- package/dist/modules/cli-configuration/interface-adapters/controllers/http/repositories.routes.js +78 -8
- package/dist/modules/cli-configuration/interface-adapters/controllers/http/repositories.routes.js.map +1 -1
- package/dist/modules/cli-configuration/interface-adapters/gateways/projectConfig.fileSystem.gateway.d.ts +7 -0
- package/dist/modules/cli-configuration/interface-adapters/gateways/projectConfig.fileSystem.gateway.d.ts.map +1 -0
- package/dist/modules/cli-configuration/interface-adapters/gateways/projectConfig.fileSystem.gateway.js +48 -0
- package/dist/modules/cli-configuration/interface-adapters/gateways/projectConfig.fileSystem.gateway.js.map +1 -0
- package/dist/modules/cli-configuration/usecases/cli/addRepositoriesToConfig.usecase.d.ts +1 -6
- package/dist/modules/cli-configuration/usecases/cli/addRepositoriesToConfig.usecase.d.ts.map +1 -1
- package/dist/modules/cli-configuration/usecases/cli/addRepositoriesToConfig.usecase.js.map +1 -1
- package/dist/modules/cli-configuration/usecases/cli/removeRepositoryFromConfig.usecase.d.ts +21 -0
- package/dist/modules/cli-configuration/usecases/cli/removeRepositoryFromConfig.usecase.d.ts.map +1 -0
- package/dist/modules/cli-configuration/usecases/cli/removeRepositoryFromConfig.usecase.js +27 -0
- package/dist/modules/cli-configuration/usecases/cli/removeRepositoryFromConfig.usecase.js.map +1 -0
- package/dist/modules/cli-configuration/usecases/cli/toggleRepositoryEnabled.usecase.d.ts +22 -0
- package/dist/modules/cli-configuration/usecases/cli/toggleRepositoryEnabled.usecase.d.ts.map +1 -0
- package/dist/modules/cli-configuration/usecases/cli/toggleRepositoryEnabled.usecase.js +27 -0
- package/dist/modules/cli-configuration/usecases/cli/toggleRepositoryEnabled.usecase.js.map +1 -0
- package/dist/modules/cli-configuration/usecases/cli/writeInitConfig.usecase.d.ts +1 -6
- package/dist/modules/cli-configuration/usecases/cli/writeInitConfig.usecase.d.ts.map +1 -1
- package/dist/modules/cli-configuration/usecases/cli/writeInitConfig.usecase.js.map +1 -1
- package/dist/modules/cli-configuration/usecases/dashboardRepositories/addRepositoryFromDashboard.usecase.d.ts +19 -0
- package/dist/modules/cli-configuration/usecases/dashboardRepositories/addRepositoryFromDashboard.usecase.d.ts.map +1 -0
- package/dist/modules/cli-configuration/usecases/dashboardRepositories/addRepositoryFromDashboard.usecase.js +30 -0
- package/dist/modules/cli-configuration/usecases/dashboardRepositories/addRepositoryFromDashboard.usecase.js.map +1 -0
- package/dist/modules/cli-configuration/usecases/dashboardRepositories/removeRepositoryFromDashboard.usecase.d.ts +16 -0
- package/dist/modules/cli-configuration/usecases/dashboardRepositories/removeRepositoryFromDashboard.usecase.d.ts.map +1 -0
- package/dist/modules/cli-configuration/usecases/dashboardRepositories/removeRepositoryFromDashboard.usecase.js +27 -0
- package/dist/modules/cli-configuration/usecases/dashboardRepositories/removeRepositoryFromDashboard.usecase.js.map +1 -0
- package/dist/modules/cli-configuration/usecases/dashboardRepositories/updateRepositoryEnabledFromDashboard.usecase.d.ts +17 -0
- package/dist/modules/cli-configuration/usecases/dashboardRepositories/updateRepositoryEnabledFromDashboard.usecase.d.ts.map +1 -0
- package/dist/modules/cli-configuration/usecases/dashboardRepositories/updateRepositoryEnabledFromDashboard.usecase.js +28 -0
- package/dist/modules/cli-configuration/usecases/dashboardRepositories/updateRepositoryEnabledFromDashboard.usecase.js.map +1 -0
- package/dist/modules/cli-configuration/usecases/projectConfig/updateProjectConfig.usecase.d.ts +31 -0
- package/dist/modules/cli-configuration/usecases/projectConfig/updateProjectConfig.usecase.d.ts.map +1 -0
- package/dist/modules/cli-configuration/usecases/projectConfig/updateProjectConfig.usecase.js +102 -0
- package/dist/modules/cli-configuration/usecases/projectConfig/updateProjectConfig.usecase.js.map +1 -0
- package/dist/modules/platform-integration/interface-adapters/controllers/webhook/github.controller.d.ts.map +1 -1
- package/dist/modules/platform-integration/interface-adapters/controllers/webhook/github.controller.js +3 -0
- package/dist/modules/platform-integration/interface-adapters/controllers/webhook/github.controller.js.map +1 -1
- package/dist/modules/platform-integration/interface-adapters/controllers/webhook/gitlab.controller.d.ts.map +1 -1
- package/dist/modules/platform-integration/interface-adapters/controllers/webhook/gitlab.controller.js +3 -0
- package/dist/modules/platform-integration/interface-adapters/controllers/webhook/gitlab.controller.js.map +1 -1
- package/dist/modules/review-execution/entities/reviewContext/reviewContext.gateway.d.ts +1 -0
- package/dist/modules/review-execution/entities/reviewContext/reviewContext.gateway.d.ts.map +1 -1
- package/dist/modules/review-execution/entities/reviewContext/reviewContext.schema.d.ts +10 -2
- package/dist/modules/review-execution/entities/reviewContext/reviewContext.schema.d.ts.map +1 -1
- package/dist/modules/review-execution/entities/reviewContext/reviewContextResult.factory.d.ts +11 -0
- package/dist/modules/review-execution/entities/reviewContext/reviewContextResult.factory.d.ts.map +1 -0
- package/dist/modules/review-execution/entities/reviewContext/reviewContextResult.factory.js +22 -0
- package/dist/modules/review-execution/entities/reviewContext/reviewContextResult.factory.js.map +1 -0
- package/dist/modules/review-execution/entities/reviewContext/reviewContextResult.guard.d.ts +10 -0
- package/dist/modules/review-execution/entities/reviewContext/reviewContextResult.guard.d.ts.map +1 -1
- package/dist/modules/review-execution/entities/reviewContext/reviewContextResult.schema.d.ts +31 -1
- package/dist/modules/review-execution/entities/reviewContext/reviewContextResult.schema.d.ts.map +1 -1
- package/dist/modules/review-execution/entities/reviewContext/reviewContextResult.schema.js +11 -1
- package/dist/modules/review-execution/entities/reviewContext/reviewContextResult.schema.js.map +1 -1
- package/dist/modules/review-execution/interface-adapters/gateways/reviewContext.fileSystem.gateway.d.ts +1 -0
- package/dist/modules/review-execution/interface-adapters/gateways/reviewContext.fileSystem.gateway.d.ts.map +1 -1
- package/dist/modules/review-execution/interface-adapters/gateways/reviewContext.fileSystem.gateway.js +27 -1
- package/dist/modules/review-execution/interface-adapters/gateways/reviewContext.fileSystem.gateway.js.map +1 -1
- package/dist/modules/review-execution/services/reviewRecovery.service.d.ts +33 -0
- package/dist/modules/review-execution/services/reviewRecovery.service.d.ts.map +1 -0
- package/dist/modules/review-execution/services/reviewRecovery.service.js +80 -0
- package/dist/modules/review-execution/services/reviewRecovery.service.js.map +1 -0
- package/dist/modules/statistics-insights/interface-adapters/controllers/http/overview.routes.d.ts +2 -0
- package/dist/modules/statistics-insights/interface-adapters/controllers/http/overview.routes.d.ts.map +1 -1
- package/dist/modules/statistics-insights/interface-adapters/controllers/http/overview.routes.js +14 -0
- package/dist/modules/statistics-insights/interface-adapters/controllers/http/overview.routes.js.map +1 -1
- package/dist/modules/statistics-insights/interface-adapters/presenters/overview.presenter.d.ts +5 -0
- package/dist/modules/statistics-insights/interface-adapters/presenters/overview.presenter.d.ts.map +1 -1
- package/dist/modules/statistics-insights/interface-adapters/presenters/overview.presenter.js +20 -4
- package/dist/modules/statistics-insights/interface-adapters/presenters/overview.presenter.js.map +1 -1
- package/dist/tests/acceptance/177-dashboard-add-project-ui.acceptance.test.d.ts +12 -0
- package/dist/tests/acceptance/177-dashboard-add-project-ui.acceptance.test.d.ts.map +1 -0
- package/dist/tests/acceptance/177-dashboard-add-project-ui.acceptance.test.js +304 -0
- package/dist/tests/acceptance/177-dashboard-add-project-ui.acceptance.test.js.map +1 -0
- package/dist/tests/acceptance/178-dashboard-tabs-reposition.acceptance.test.d.ts +12 -0
- package/dist/tests/acceptance/178-dashboard-tabs-reposition.acceptance.test.d.ts.map +1 -0
- package/dist/tests/acceptance/178-dashboard-tabs-reposition.acceptance.test.js +131 -0
- package/dist/tests/acceptance/178-dashboard-tabs-reposition.acceptance.test.js.map +1 -0
- package/dist/tests/acceptance/179-dashboard-project-settings-modal.acceptance.test.d.ts +12 -0
- package/dist/tests/acceptance/179-dashboard-project-settings-modal.acceptance.test.d.ts.map +1 -0
- package/dist/tests/acceptance/179-dashboard-project-settings-modal.acceptance.test.js +312 -0
- package/dist/tests/acceptance/179-dashboard-project-settings-modal.acceptance.test.js.map +1 -0
- package/dist/tests/acceptance/91-dashboard-multi-project-overview.acceptance.test.js +5 -0
- package/dist/tests/acceptance/91-dashboard-multi-project-overview.acceptance.test.js.map +1 -1
- package/dist/tests/stubs/projectConfigGateway.stub.d.ts +15 -0
- package/dist/tests/stubs/projectConfigGateway.stub.d.ts.map +1 -0
- package/dist/tests/stubs/projectConfigGateway.stub.js +40 -0
- package/dist/tests/stubs/projectConfigGateway.stub.js.map +1 -0
- package/dist/tests/stubs/reviewContextGateway.stub.d.ts +1 -0
- package/dist/tests/stubs/reviewContextGateway.stub.d.ts.map +1 -1
- package/dist/tests/stubs/reviewContextGateway.stub.js +3 -0
- package/dist/tests/stubs/reviewContextGateway.stub.js.map +1 -1
- package/dist/tests/units/config/projectConfig.test.js +43 -0
- package/dist/tests/units/config/projectConfig.test.js.map +1 -1
- package/dist/tests/units/dashboard/modules/animations.test.d.ts +10 -0
- package/dist/tests/units/dashboard/modules/animations.test.d.ts.map +1 -0
- package/dist/tests/units/dashboard/modules/animations.test.js +223 -0
- package/dist/tests/units/dashboard/modules/animations.test.js.map +1 -0
- package/dist/tests/units/dashboard/modules/cardCounters.test.d.ts +2 -0
- package/dist/tests/units/dashboard/modules/cardCounters.test.d.ts.map +1 -0
- package/dist/tests/units/dashboard/modules/cardCounters.test.js +162 -0
- package/dist/tests/units/dashboard/modules/cardCounters.test.js.map +1 -0
- package/dist/tests/units/dashboard/modules/managePanel.test.d.ts +2 -0
- package/dist/tests/units/dashboard/modules/managePanel.test.d.ts.map +1 -0
- package/dist/tests/units/dashboard/modules/managePanel.test.js +112 -0
- package/dist/tests/units/dashboard/modules/managePanel.test.js.map +1 -0
- package/dist/tests/units/dashboard/modules/overview.test.js +92 -0
- package/dist/tests/units/dashboard/modules/overview.test.js.map +1 -1
- package/dist/tests/units/dashboard/modules/settingsModal.test.d.ts +2 -0
- package/dist/tests/units/dashboard/modules/settingsModal.test.d.ts.map +1 -0
- package/dist/tests/units/dashboard/modules/settingsModal.test.js +166 -0
- package/dist/tests/units/dashboard/modules/settingsModal.test.js.map +1 -0
- package/dist/tests/units/dashboard/modules/tabBar.test.js +40 -11
- package/dist/tests/units/dashboard/modules/tabBar.test.js.map +1 -1
- package/dist/tests/units/entities/reviewContext/reviewContext.schema.test.js +1 -0
- package/dist/tests/units/entities/reviewContext/reviewContext.schema.test.js.map +1 -1
- package/dist/tests/units/entities/reviewContext/reviewContextResult.factory.test.d.ts +2 -0
- package/dist/tests/units/entities/reviewContext/reviewContextResult.factory.test.d.ts.map +1 -0
- package/dist/tests/units/entities/reviewContext/reviewContextResult.factory.test.js +43 -0
- package/dist/tests/units/entities/reviewContext/reviewContextResult.factory.test.js.map +1 -0
- package/dist/tests/units/entities/reviewContext/reviewContextResult.guard.test.js +7 -3
- package/dist/tests/units/entities/reviewContext/reviewContextResult.guard.test.js.map +1 -1
- package/dist/tests/units/entities/reviewContext/reviewContextResult.schema.test.js +12 -1
- package/dist/tests/units/entities/reviewContext/reviewContextResult.schema.test.js.map +1 -1
- package/dist/tests/units/frameworks/config/configLoader.test.js +35 -1
- package/dist/tests/units/frameworks/config/configLoader.test.js.map +1 -1
- package/dist/tests/units/interface-adapters/controllers/webhook/github.controller.test.js +57 -0
- package/dist/tests/units/interface-adapters/controllers/webhook/github.controller.test.js.map +1 -1
- package/dist/tests/units/interface-adapters/controllers/webhook/gitlab.controller.test.js +1 -0
- package/dist/tests/units/interface-adapters/controllers/webhook/gitlab.controller.test.js.map +1 -1
- package/dist/tests/units/interface-adapters/gateways/reviewContext.fileSystem.gateway.test.js +64 -4
- package/dist/tests/units/interface-adapters/gateways/reviewContext.fileSystem.gateway.test.js.map +1 -1
- package/dist/tests/units/modules/claude-invocation/interface-adapters/gateways/reviewReport.fileSystem.gateway.test.js +56 -0
- package/dist/tests/units/modules/claude-invocation/interface-adapters/gateways/reviewReport.fileSystem.gateway.test.js.map +1 -1
- package/dist/tests/units/modules/cli-configuration/interface-adapters/controllers/http/projectConfig.routes.test.js +111 -0
- package/dist/tests/units/modules/cli-configuration/interface-adapters/controllers/http/projectConfig.routes.test.js.map +1 -1
- package/dist/tests/units/modules/cli-configuration/interface-adapters/controllers/http/repositories.routes.test.js +244 -3
- package/dist/tests/units/modules/cli-configuration/interface-adapters/controllers/http/repositories.routes.test.js.map +1 -1
- package/dist/tests/units/modules/cli-configuration/interface-adapters/gateways/projectConfig.fileSystem.gateway.test.d.ts +2 -0
- package/dist/tests/units/modules/cli-configuration/interface-adapters/gateways/projectConfig.fileSystem.gateway.test.d.ts.map +1 -0
- package/dist/tests/units/modules/cli-configuration/interface-adapters/gateways/projectConfig.fileSystem.gateway.test.js +72 -0
- package/dist/tests/units/modules/cli-configuration/interface-adapters/gateways/projectConfig.fileSystem.gateway.test.js.map +1 -0
- package/dist/tests/units/modules/cli-configuration/usecases/cli/removeRepositoryFromConfig.usecase.test.d.ts +2 -0
- package/dist/tests/units/modules/cli-configuration/usecases/cli/removeRepositoryFromConfig.usecase.test.d.ts.map +1 -0
- package/dist/tests/units/modules/cli-configuration/usecases/cli/removeRepositoryFromConfig.usecase.test.js +76 -0
- package/dist/tests/units/modules/cli-configuration/usecases/cli/removeRepositoryFromConfig.usecase.test.js.map +1 -0
- package/dist/tests/units/modules/cli-configuration/usecases/cli/toggleRepositoryEnabled.usecase.test.d.ts +2 -0
- package/dist/tests/units/modules/cli-configuration/usecases/cli/toggleRepositoryEnabled.usecase.test.d.ts.map +1 -0
- package/dist/tests/units/modules/cli-configuration/usecases/cli/toggleRepositoryEnabled.usecase.test.js +84 -0
- package/dist/tests/units/modules/cli-configuration/usecases/cli/toggleRepositoryEnabled.usecase.test.js.map +1 -0
- package/dist/tests/units/modules/cli-configuration/usecases/projectConfig/updateProjectConfig.usecase.test.d.ts +2 -0
- package/dist/tests/units/modules/cli-configuration/usecases/projectConfig/updateProjectConfig.usecase.test.d.ts.map +1 -0
- package/dist/tests/units/modules/cli-configuration/usecases/projectConfig/updateProjectConfig.usecase.test.js +141 -0
- package/dist/tests/units/modules/cli-configuration/usecases/projectConfig/updateProjectConfig.usecase.test.js.map +1 -0
- package/dist/tests/units/modules/statistics-insights/interface-adapters/controllers/http/overview.routes.test.js +44 -0
- package/dist/tests/units/modules/statistics-insights/interface-adapters/controllers/http/overview.routes.test.js.map +1 -1
- package/dist/tests/units/modules/statistics-insights/interface-adapters/presenters/overview.presenter.test.js +42 -0
- package/dist/tests/units/modules/statistics-insights/interface-adapters/presenters/overview.presenter.test.js.map +1 -1
- package/dist/tests/units/services/reviewRecovery.service.test.d.ts +2 -0
- package/dist/tests/units/services/reviewRecovery.service.test.d.ts.map +1 -0
- package/dist/tests/units/services/reviewRecovery.service.test.js +49 -0
- package/dist/tests/units/services/reviewRecovery.service.test.js.map +1 -0
- package/dist/tests/units/services/runReviewRecovery.service.test.d.ts +2 -0
- package/dist/tests/units/services/runReviewRecovery.service.test.d.ts.map +1 -0
- package/dist/tests/units/services/runReviewRecovery.service.test.js +199 -0
- package/dist/tests/units/services/runReviewRecovery.service.test.js.map +1 -0
- package/package.json +1 -1
|
@@ -4,13 +4,25 @@
|
|
|
4
4
|
<meta charset="UTF-8">
|
|
5
5
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
6
6
|
<title>Reviewflow Dashboard</title>
|
|
7
|
+
<link rel="preconnect" href="https://fonts.googleapis.com">
|
|
8
|
+
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
|
9
|
+
<link href="https://fonts.googleapis.com/css2?family=Geist:wght@300;400;500;600;700&family=JetBrains+Mono:wght@400;500;600&display=swap" rel="stylesheet">
|
|
7
10
|
<link rel="stylesheet" href="styles.css">
|
|
8
11
|
<script src="https://unpkg.com/lucide@latest"></script>
|
|
9
12
|
</head>
|
|
10
13
|
<body>
|
|
11
14
|
<div class="container">
|
|
12
15
|
<header>
|
|
13
|
-
<div class="logo"
|
|
16
|
+
<div class="logo" aria-label="Reviewflow">
|
|
17
|
+
<svg viewBox="0 0 44 44" xmlns="http://www.w3.org/2000/svg" aria-hidden="true">
|
|
18
|
+
<rect class="logo-frame" x="2" y="2" width="40" height="40" rx="10"/>
|
|
19
|
+
<line class="logo-line" x1="10" y1="14" x2="26" y2="14"/>
|
|
20
|
+
<line class="logo-line logo-line--accent" x1="10" y1="22" x2="20" y2="22"/>
|
|
21
|
+
<line class="logo-line" x1="10" y1="30" x2="24" y2="30"/>
|
|
22
|
+
<path class="logo-check" d="M28 26 L31 29 L36 22"/>
|
|
23
|
+
<circle class="logo-pulse" cx="20" cy="22" r="1.7"/>
|
|
24
|
+
</svg>
|
|
25
|
+
</div>
|
|
14
26
|
<h1>Reviewflow</h1>
|
|
15
27
|
<div class="header-actions">
|
|
16
28
|
<div class="version-update-wrapper">
|
|
@@ -30,6 +42,43 @@
|
|
|
30
42
|
</div>
|
|
31
43
|
</header>
|
|
32
44
|
|
|
45
|
+
<div class="project-bar" role="region" aria-label="Project navigation">
|
|
46
|
+
<button type="button" id="manage-projects-toggle" class="manage-projects-toggle" aria-expanded="false" aria-controls="manage-panel">
|
|
47
|
+
<span class="manage-projects-toggle-label">// MANAGE PROJECTS</span>
|
|
48
|
+
<i data-lucide="chevron-down" class="manage-projects-toggle-icon"></i>
|
|
49
|
+
</button>
|
|
50
|
+
<section id="manage-panel" class="manage-panel" aria-label="Manage projects" data-open="false"></section>
|
|
51
|
+
<nav id="dashboard-tabs" class="dashboard-tab-bar-wrapper" aria-label="Project tabs"></nav>
|
|
52
|
+
<div class="context-chips" aria-label="Toolchain status">
|
|
53
|
+
<div class="toolchain-chip">
|
|
54
|
+
<span class="toolchain-chip-label" id="i18n-card-claude-cli"></span>
|
|
55
|
+
<div id="claude-status" class="card-claude checking">
|
|
56
|
+
<span class="status" id="i18n-claude-checking"></span>
|
|
57
|
+
<span class="version"></span>
|
|
58
|
+
</div>
|
|
59
|
+
</div>
|
|
60
|
+
<div class="toolchain-chip" id="git-cli-card">
|
|
61
|
+
<span class="toolchain-chip-label" id="git-cli-label"></span>
|
|
62
|
+
<div id="git-cli-status" class="card-claude checking">
|
|
63
|
+
<span class="status" id="i18n-git-load-project"></span>
|
|
64
|
+
<span class="version"></span>
|
|
65
|
+
</div>
|
|
66
|
+
</div>
|
|
67
|
+
<div class="toolchain-chip toolchain-chip-model">
|
|
68
|
+
<span class="toolchain-chip-label" id="i18n-card-model"></span>
|
|
69
|
+
<select id="model-select" class="model-select" onchange="changeModel(this.value)">
|
|
70
|
+
<option value="opus" id="i18n-model-opus"></option>
|
|
71
|
+
<option value="sonnet" id="i18n-model-sonnet"></option>
|
|
72
|
+
</select>
|
|
73
|
+
</div>
|
|
74
|
+
</div>
|
|
75
|
+
</div>
|
|
76
|
+
|
|
77
|
+
<div id="cards-scope-marker" class="cards-scope-marker" data-scope-kind="overview">
|
|
78
|
+
<span class="cards-scope-prefix">// SCOPE</span>
|
|
79
|
+
<span class="cards-scope-label">TOUS LES PROJETS</span>
|
|
80
|
+
</div>
|
|
81
|
+
|
|
33
82
|
<div class="cards">
|
|
34
83
|
<div class="card card-priority">
|
|
35
84
|
<div class="card-label" id="i18n-card-running"></div>
|
|
@@ -43,45 +92,21 @@
|
|
|
43
92
|
<div class="card-label" id="i18n-card-completed"></div>
|
|
44
93
|
<div id="completed-count" class="card-value">-</div>
|
|
45
94
|
</div>
|
|
46
|
-
<div class="card">
|
|
47
|
-
<div class="card-label" id="i18n-card-claude-cli"></div>
|
|
48
|
-
<div id="claude-status" class="card-claude checking">
|
|
49
|
-
<span class="status" id="i18n-claude-checking"></span>
|
|
50
|
-
<span class="version"></span>
|
|
51
|
-
</div>
|
|
52
|
-
</div>
|
|
53
|
-
<div class="card" id="git-cli-card">
|
|
54
|
-
<div class="card-label" id="git-cli-label"></div>
|
|
55
|
-
<div id="git-cli-status" class="card-claude checking">
|
|
56
|
-
<span class="status" id="i18n-git-load-project"></span>
|
|
57
|
-
<span class="version"></span>
|
|
58
|
-
</div>
|
|
59
|
-
</div>
|
|
60
|
-
<div class="card">
|
|
61
|
-
<div class="card-label" id="i18n-card-model"></div>
|
|
62
|
-
<div class="card-model">
|
|
63
|
-
<select id="model-select" class="model-select" onchange="changeModel(this.value)">
|
|
64
|
-
<option value="opus" id="i18n-model-opus"></option>
|
|
65
|
-
<option value="sonnet" id="i18n-model-sonnet"></option>
|
|
66
|
-
</select>
|
|
67
|
-
</div>
|
|
68
|
-
</div>
|
|
69
95
|
</div>
|
|
70
96
|
|
|
71
97
|
<div class="dashboard-layout">
|
|
72
98
|
<aside class="dashboard-sidebar" aria-label="Project tools">
|
|
73
|
-
<
|
|
74
|
-
<
|
|
75
|
-
|
|
76
|
-
<option value="en">English</option>
|
|
77
|
-
<option value="fr">Français</option>
|
|
78
|
-
</select>
|
|
79
|
-
</div>
|
|
99
|
+
<button type="button" id="open-settings-modal-btn" class="sidebar-settings-button" hidden>
|
|
100
|
+
<span class="sidebar-settings-button__prefix">// SETTINGS</span>
|
|
101
|
+
</button>
|
|
80
102
|
|
|
81
|
-
<nav id="dashboard-tabs" class="dashboard-tab-bar-wrapper" aria-label="Project tabs"></nav>
|
|
82
103
|
<span id="config-status" class="config-status hidden"></span>
|
|
83
104
|
|
|
84
|
-
<
|
|
105
|
+
<section id="worktree-section" aria-label="Worktree pool"></section>
|
|
106
|
+
</aside>
|
|
107
|
+
|
|
108
|
+
<main class="dashboard-main">
|
|
109
|
+
<div class="focus-strip attention-strip" aria-label="Attention indicators">
|
|
85
110
|
<div class="focus-chip focus-now">
|
|
86
111
|
<div class="focus-copy">
|
|
87
112
|
<span class="focus-label" id="i18n-strip-now"></span>
|
|
@@ -106,10 +131,6 @@
|
|
|
106
131
|
<button id="focus-strip-toggle" class="focus-toggle-btn" onclick="toggleFocusStripMode()"></button>
|
|
107
132
|
</div>
|
|
108
133
|
|
|
109
|
-
<section id="worktree-section" aria-label="Worktree pool"></section>
|
|
110
|
-
</aside>
|
|
111
|
-
|
|
112
|
-
<main class="dashboard-main">
|
|
113
134
|
<section id="overview-section" class="overview-section" aria-label="Multi-project overview"></section>
|
|
114
135
|
|
|
115
136
|
<div id="data-loading-state" class="data-loading hidden" role="status" aria-live="polite">
|
|
@@ -137,7 +158,11 @@
|
|
|
137
158
|
<span id="pending-reviews-count" class="badge-count hidden">0</span>
|
|
138
159
|
</div>
|
|
139
160
|
<div id="pending-reviews" class="section-content">
|
|
140
|
-
<div class="empty-state" id="
|
|
161
|
+
<div class="heartbeat-empty-state" id="pending-reviews-empty-state">
|
|
162
|
+
<span class="heartbeat-label">// STANDBY</span>
|
|
163
|
+
<span class="heartbeat-message" id="i18n-empty-pending-reviews">No reviews waiting for confirmation</span>
|
|
164
|
+
<div class="heartbeat-line-container" id="heartbeat-line" aria-hidden="true"></div>
|
|
165
|
+
</div>
|
|
141
166
|
</div>
|
|
142
167
|
</div>
|
|
143
168
|
|
|
@@ -304,16 +329,41 @@
|
|
|
304
329
|
<div id="dev-sheet-content" class="sheet-content"></div>
|
|
305
330
|
</div>
|
|
306
331
|
|
|
332
|
+
<dialog id="settings-modal" class="settings-modal" aria-labelledby="settings-modal-title"></dialog>
|
|
333
|
+
|
|
334
|
+
<!-- UI language select — kept in DOM for JS wiring; visual control lives in settings modal -->
|
|
335
|
+
<select id="language-select" style="display:none" aria-hidden="true" onchange="changeLanguage(this.value)">
|
|
336
|
+
<option value="en">English</option>
|
|
337
|
+
<option value="fr">Français</option>
|
|
338
|
+
</select>
|
|
339
|
+
|
|
307
340
|
<script type="module">
|
|
308
341
|
import { t, setLanguage, getLanguage } from './modules/i18n.js';
|
|
309
342
|
import { formatTime, formatDuration, formatPhase, formatLogTime } from './modules/formatting.js';
|
|
343
|
+
import {
|
|
344
|
+
reducedMotion,
|
|
345
|
+
animateMount,
|
|
346
|
+
animateCounter as animateCounterValue,
|
|
347
|
+
slideTabUnderline,
|
|
348
|
+
heartbeat,
|
|
349
|
+
pulseLive,
|
|
350
|
+
springIn,
|
|
351
|
+
liftCard,
|
|
352
|
+
unliftCard,
|
|
353
|
+
pulseStatusDot,
|
|
354
|
+
breatheLogo,
|
|
355
|
+
crossFadeTab,
|
|
356
|
+
toggleHeight,
|
|
357
|
+
} from './modules/animations.js';
|
|
310
358
|
import { escapeHtml, markdownToHtml, sanitizeHttpUrl } from './modules/html.js';
|
|
311
359
|
import { getAgentIcon, icon, refreshIcons } from './modules/icons.js';
|
|
312
|
-
import { MAX_RECONNECT_ATTEMPTS, RECONNECT_DELAY,
|
|
360
|
+
import { MAX_RECONNECT_ATTEMPTS, RECONNECT_DELAY, STORAGE_KEY_CURRENT, STORAGE_KEY_FOCUS_STRIP_MODE, QUALITY_TARGET_SCORE } from './modules/constants.js';
|
|
313
361
|
import { buildTabBarModel, renderTabBarHtml, readActiveTab, writeActiveTab } from './modules/tabBar.js';
|
|
362
|
+
import { buildManagePanelModel, renderManagePanelHtml, buildOptimisticAddedRow, validateLocalPathInput } from './modules/managePanel.js';
|
|
314
363
|
import { renderOverviewHtml } from './modules/overview.js';
|
|
315
364
|
import { getDesktopNotificationPayload, shouldNotifyDesktop } from './modules/desktopNotifications.js';
|
|
316
365
|
import { getLoadingPresentation, getQuietRefreshSectionIdentifiers } from './modules/loading.js';
|
|
366
|
+
import { computeCardCounters, extractGithubSlug } from './modules/cardCounters.js';
|
|
317
367
|
import { collectReviewNotifications, createReviewNotificationState } from './modules/notifications.js';
|
|
318
368
|
import { resolveReviewAssigneeDisplay } from './modules/assignee.js';
|
|
319
369
|
import { buildQueueLanesModel } from './modules/queueLanes.js';
|
|
@@ -345,6 +395,12 @@
|
|
|
345
395
|
fetchBudgetStatus,
|
|
346
396
|
submitBudget,
|
|
347
397
|
} from './modules/budgetSettings.js';
|
|
398
|
+
import {
|
|
399
|
+
buildSettingsViewModel,
|
|
400
|
+
renderSettingsModalHtml,
|
|
401
|
+
validateExternalLink,
|
|
402
|
+
extractFormPayload,
|
|
403
|
+
} from './modules/settingsModal.js';
|
|
348
404
|
|
|
349
405
|
const API_URL = window.location.origin;
|
|
350
406
|
const WS_URL = `ws://${window.location.host}/ws`;
|
|
@@ -767,18 +823,23 @@
|
|
|
767
823
|
const reviews = currentData.activeReviews.filter(r => r.jobType !== 'followup');
|
|
768
824
|
const followups = currentData.activeReviews.filter(r => r.jobType === 'followup');
|
|
769
825
|
|
|
826
|
+
renderCardCounters();
|
|
827
|
+
const blocked = currentData.pendingFix.length;
|
|
770
828
|
const running = currentData.activeReviews.filter(r => r.status === 'running').length;
|
|
771
829
|
const queued = currentData.activeReviews.filter(r => r.status === 'queued').length;
|
|
772
|
-
const blocked = currentData.pendingFix.length;
|
|
773
830
|
const nowCount = running + blocked;
|
|
774
831
|
const nextCount = queued + currentData.pendingApproval.length;
|
|
775
|
-
document.getElementById('running-count').textContent = running;
|
|
776
|
-
document.getElementById('queued-count').textContent = queued;
|
|
777
|
-
document.getElementById('completed-count').textContent = currentData.reviewFiles.length;
|
|
778
832
|
document.getElementById('focus-now-count').textContent = String(nowCount);
|
|
779
833
|
document.getElementById('focus-next-count').textContent = String(nextCount);
|
|
780
834
|
document.getElementById('focus-blocked-count').textContent = String(blocked);
|
|
781
835
|
|
|
836
|
+
const chipNow = document.querySelector('.focus-chip.focus-now');
|
|
837
|
+
const chipNext = document.querySelector('.focus-chip.focus-next');
|
|
838
|
+
const chipBlocked = document.querySelector('.focus-chip.focus-blocked');
|
|
839
|
+
if (chipNow) chipNow.dataset.active = nowCount > 0 ? 'true' : 'false';
|
|
840
|
+
if (chipNext) chipNext.dataset.active = nextCount > 0 ? 'true' : 'false';
|
|
841
|
+
if (chipBlocked) chipBlocked.dataset.active = blocked > 0 ? 'critical' : 'false';
|
|
842
|
+
|
|
782
843
|
const activeReviewsSection = document.getElementById('active-reviews-section');
|
|
783
844
|
const activeReviewsEl = document.getElementById('active-reviews');
|
|
784
845
|
const activeReviewsCount = document.getElementById('active-reviews-count');
|
|
@@ -2273,71 +2334,6 @@
|
|
|
2273
2334
|
let currentProjectConfig = null;
|
|
2274
2335
|
let currentProjectPath = null;
|
|
2275
2336
|
|
|
2276
|
-
function getStoredProjects() {
|
|
2277
|
-
try {
|
|
2278
|
-
return JSON.parse(localStorage.getItem(STORAGE_KEY_PROJECTS) || '[]');
|
|
2279
|
-
} catch {
|
|
2280
|
-
return [];
|
|
2281
|
-
}
|
|
2282
|
-
}
|
|
2283
|
-
|
|
2284
|
-
function saveProjects(projects) {
|
|
2285
|
-
localStorage.setItem(STORAGE_KEY_PROJECTS, JSON.stringify(projects));
|
|
2286
|
-
}
|
|
2287
|
-
|
|
2288
|
-
function addProjectToHistory(path) {
|
|
2289
|
-
const projects = getStoredProjects();
|
|
2290
|
-
const filtered = projects.filter(p => p !== path);
|
|
2291
|
-
filtered.unshift(path);
|
|
2292
|
-
saveProjects(filtered.slice(0, 10));
|
|
2293
|
-
updateProjectSelect();
|
|
2294
|
-
}
|
|
2295
|
-
|
|
2296
|
-
function removeProjectFromHistory(path) {
|
|
2297
|
-
const projects = getStoredProjects().filter(p => p !== path);
|
|
2298
|
-
saveProjects(projects);
|
|
2299
|
-
updateProjectSelect();
|
|
2300
|
-
}
|
|
2301
|
-
|
|
2302
|
-
function updateProjectSelect() {
|
|
2303
|
-
const select = document.getElementById('project-select');
|
|
2304
|
-
if (!select) return;
|
|
2305
|
-
const projects = getStoredProjects();
|
|
2306
|
-
const current = localStorage.getItem(STORAGE_KEY_CURRENT) || '';
|
|
2307
|
-
|
|
2308
|
-
select.innerHTML = `<option value="">${t('project.selectPlaceholder')}</option>`;
|
|
2309
|
-
for (const path of projects) {
|
|
2310
|
-
const shortName = path.split('/').slice(-2).join('/');
|
|
2311
|
-
const option = document.createElement('option');
|
|
2312
|
-
option.value = path;
|
|
2313
|
-
option.textContent = shortName;
|
|
2314
|
-
option.title = path;
|
|
2315
|
-
if (path === current) option.selected = true;
|
|
2316
|
-
select.appendChild(option);
|
|
2317
|
-
}
|
|
2318
|
-
}
|
|
2319
|
-
|
|
2320
|
-
function onProjectSelect(path) {
|
|
2321
|
-
if (path) {
|
|
2322
|
-
const input = document.getElementById('project-path-input');
|
|
2323
|
-
if (input) input.value = '';
|
|
2324
|
-
loadProjectConfigFromPath(path);
|
|
2325
|
-
}
|
|
2326
|
-
}
|
|
2327
|
-
|
|
2328
|
-
async function loadProjectConfig() {
|
|
2329
|
-
const input = document.getElementById('project-path-input');
|
|
2330
|
-
const select = document.getElementById('project-select');
|
|
2331
|
-
const projectPath = (input?.value.trim() ?? '') || (select?.value ?? '');
|
|
2332
|
-
|
|
2333
|
-
if (!projectPath) {
|
|
2334
|
-
showConfigStatus(t('error.selectOrEnterPath'), 'error');
|
|
2335
|
-
return;
|
|
2336
|
-
}
|
|
2337
|
-
|
|
2338
|
-
await loadProjectConfigFromPath(projectPath);
|
|
2339
|
-
}
|
|
2340
|
-
|
|
2341
2337
|
async function loadProjectConfigFromPath(projectPath) {
|
|
2342
2338
|
const status = document.getElementById('config-status');
|
|
2343
2339
|
const info = document.getElementById('config-info');
|
|
@@ -2353,14 +2349,8 @@
|
|
|
2353
2349
|
currentProjectConfig = data.config;
|
|
2354
2350
|
currentProjectPath = projectPath;
|
|
2355
2351
|
|
|
2356
|
-
addProjectToHistory(projectPath);
|
|
2357
2352
|
localStorage.setItem(STORAGE_KEY_CURRENT, projectPath);
|
|
2358
2353
|
|
|
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
|
-
|
|
2364
2354
|
const shortName = projectPath.split('/').slice(-2).join('/');
|
|
2365
2355
|
showConfigStatus(`<i data-lucide="check-circle"></i> ${escapeHtml(shortName)}`, 'success');
|
|
2366
2356
|
refreshIcons();
|
|
@@ -2413,43 +2403,6 @@
|
|
|
2413
2403
|
refreshIcons();
|
|
2414
2404
|
}
|
|
2415
2405
|
|
|
2416
|
-
function removeCurrentProject() {
|
|
2417
|
-
const select = document.getElementById('project-select');
|
|
2418
|
-
const path = select?.value ?? currentProjectPath ?? '';
|
|
2419
|
-
if (!path) {
|
|
2420
|
-
showConfigStatus(t('project.noProjectSelected'), 'error');
|
|
2421
|
-
return;
|
|
2422
|
-
}
|
|
2423
|
-
const shortName = path.split('/').slice(-2).join('/');
|
|
2424
|
-
if (confirm(t('confirm.removeProject', { name: shortName }))) {
|
|
2425
|
-
removeProjectFromHistory(path);
|
|
2426
|
-
localStorage.removeItem(STORAGE_KEY_CURRENT);
|
|
2427
|
-
currentProjectPath = null;
|
|
2428
|
-
currentProjectConfig = null;
|
|
2429
|
-
document.getElementById('config-info')?.classList.add('hidden');
|
|
2430
|
-
showConfigStatus(t('project.removed'), 'success');
|
|
2431
|
-
}
|
|
2432
|
-
}
|
|
2433
|
-
|
|
2434
|
-
async function syncServerRepositories() {
|
|
2435
|
-
try {
|
|
2436
|
-
const response = await fetch(`${API_URL}/api/repositories`);
|
|
2437
|
-
const data = await response.json();
|
|
2438
|
-
if (!data.repositories) return;
|
|
2439
|
-
|
|
2440
|
-
const stored = getStoredProjects();
|
|
2441
|
-
for (const repository of data.repositories) {
|
|
2442
|
-
if (repository.enabled && !stored.includes(repository.localPath)) {
|
|
2443
|
-
stored.push(repository.localPath);
|
|
2444
|
-
}
|
|
2445
|
-
}
|
|
2446
|
-
saveProjects(stored);
|
|
2447
|
-
updateProjectSelect();
|
|
2448
|
-
} catch {
|
|
2449
|
-
// Server unreachable — use localStorage only
|
|
2450
|
-
}
|
|
2451
|
-
}
|
|
2452
|
-
|
|
2453
2406
|
// SPEC-91 — Dashboard Multi-Project Overview
|
|
2454
2407
|
let availableRepositories = [];
|
|
2455
2408
|
let activeTabId = 'overview';
|
|
@@ -2458,14 +2411,48 @@
|
|
|
2458
2411
|
try {
|
|
2459
2412
|
const response = await fetch(`${API_URL}/api/repositories`);
|
|
2460
2413
|
const data = await response.json();
|
|
2461
|
-
availableRepositories = Array.isArray(data.repositories)
|
|
2462
|
-
? data.repositories.filter((repository) => repository.enabled)
|
|
2463
|
-
: [];
|
|
2414
|
+
availableRepositories = Array.isArray(data.repositories) ? data.repositories : [];
|
|
2464
2415
|
} catch {
|
|
2465
2416
|
availableRepositories = [];
|
|
2466
2417
|
}
|
|
2467
2418
|
}
|
|
2468
2419
|
|
|
2420
|
+
function syncAvailableRepositoriesFromResponse(payload) {
|
|
2421
|
+
availableRepositories = Array.isArray(payload?.repositories) ? payload.repositories : [];
|
|
2422
|
+
}
|
|
2423
|
+
|
|
2424
|
+
function renderCardCounters() {
|
|
2425
|
+
let scope;
|
|
2426
|
+
if (activeTabId === 'overview') {
|
|
2427
|
+
scope = { kind: 'overview' };
|
|
2428
|
+
} else {
|
|
2429
|
+
const repository = availableRepositories.find((r) => r.localPath === activeTabId);
|
|
2430
|
+
const projectName = repository?.name ?? activeTabId.split('/').filter(Boolean).pop() ?? activeTabId;
|
|
2431
|
+
const aliases = [];
|
|
2432
|
+
const slug = extractGithubSlug(repository?.remoteUrl);
|
|
2433
|
+
if (slug) aliases.push(slug);
|
|
2434
|
+
if (repository?.name) aliases.push(repository.name);
|
|
2435
|
+
scope = { kind: 'project', localPath: activeTabId, projectName, aliases };
|
|
2436
|
+
}
|
|
2437
|
+
const counters = computeCardCounters({
|
|
2438
|
+
activeReviews: currentData.activeReviews,
|
|
2439
|
+
reviewFiles: currentData.reviewFiles,
|
|
2440
|
+
scope,
|
|
2441
|
+
});
|
|
2442
|
+
const runningEl = document.getElementById('running-count');
|
|
2443
|
+
const queuedEl = document.getElementById('queued-count');
|
|
2444
|
+
const completedEl = document.getElementById('completed-count');
|
|
2445
|
+
if (runningEl) runningEl.textContent = counters.running;
|
|
2446
|
+
if (queuedEl) queuedEl.textContent = counters.queued;
|
|
2447
|
+
if (completedEl) completedEl.textContent = counters.completed;
|
|
2448
|
+
const markerEl = document.getElementById('cards-scope-marker');
|
|
2449
|
+
if (markerEl) {
|
|
2450
|
+
markerEl.dataset.scopeKind = counters.markerKind;
|
|
2451
|
+
const labelEl = markerEl.querySelector('.cards-scope-label');
|
|
2452
|
+
if (labelEl) labelEl.textContent = counters.markerLabel;
|
|
2453
|
+
}
|
|
2454
|
+
}
|
|
2455
|
+
|
|
2469
2456
|
function renderDashboardTabs() {
|
|
2470
2457
|
const container = document.getElementById('dashboard-tabs');
|
|
2471
2458
|
if (!container) return;
|
|
@@ -2481,6 +2468,7 @@
|
|
|
2481
2468
|
handleTabClick(tabId);
|
|
2482
2469
|
});
|
|
2483
2470
|
});
|
|
2471
|
+
setupTabUnderline();
|
|
2484
2472
|
}
|
|
2485
2473
|
|
|
2486
2474
|
function handleTabClick(tabId) {
|
|
@@ -2498,6 +2486,8 @@
|
|
|
2498
2486
|
const overviewSection = document.getElementById('overview-section');
|
|
2499
2487
|
if (overviewSection) overviewSection.classList.remove('hidden');
|
|
2500
2488
|
renderDashboardTabs();
|
|
2489
|
+
renderCardCounters();
|
|
2490
|
+
syncSettingsButtonVisibility();
|
|
2501
2491
|
refreshOverviewSection();
|
|
2502
2492
|
}
|
|
2503
2493
|
|
|
@@ -2508,9 +2498,125 @@
|
|
|
2508
2498
|
const overviewSection = document.getElementById('overview-section');
|
|
2509
2499
|
if (overviewSection) overviewSection.classList.add('hidden');
|
|
2510
2500
|
renderDashboardTabs();
|
|
2501
|
+
renderCardCounters();
|
|
2502
|
+
syncSettingsButtonVisibility();
|
|
2511
2503
|
loadProjectConfigFromPath(projectPath);
|
|
2512
2504
|
}
|
|
2513
2505
|
|
|
2506
|
+
function syncSettingsButtonVisibility() {
|
|
2507
|
+
const button = document.getElementById('open-settings-modal-btn');
|
|
2508
|
+
if (!button) return;
|
|
2509
|
+
if (activeTabId === 'overview') {
|
|
2510
|
+
button.hidden = true;
|
|
2511
|
+
} else {
|
|
2512
|
+
button.hidden = false;
|
|
2513
|
+
}
|
|
2514
|
+
}
|
|
2515
|
+
|
|
2516
|
+
function resolveActiveProjectName() {
|
|
2517
|
+
if (activeTabId === 'overview') return '—';
|
|
2518
|
+
const repository = availableRepositories.find((r) => r.localPath === activeTabId);
|
|
2519
|
+
if (repository?.name) return repository.name;
|
|
2520
|
+
return activeTabId.split('/').filter(Boolean).pop() ?? activeTabId;
|
|
2521
|
+
}
|
|
2522
|
+
|
|
2523
|
+
async function openSettingsModal() {
|
|
2524
|
+
if (activeTabId === 'overview') return;
|
|
2525
|
+
const dialog = document.getElementById('settings-modal');
|
|
2526
|
+
if (!dialog || typeof dialog.showModal !== 'function') return;
|
|
2527
|
+
try {
|
|
2528
|
+
const response = await fetch(
|
|
2529
|
+
`${API_URL}/api/project-config?path=${encodeURIComponent(activeTabId)}`,
|
|
2530
|
+
);
|
|
2531
|
+
const payload = await response.json();
|
|
2532
|
+
if (!payload?.success) {
|
|
2533
|
+
dialog.innerHTML = `<form method="dialog" class="settings-modal__form"><p class="settings-modal__error">${escapeHtml(payload?.error || 'Configuration projet illisible')}</p><div class="settings-modal__actions"><button type="submit" class="settings-modal__cancel">Fermer</button></div></form>`;
|
|
2534
|
+
dialog.showModal();
|
|
2535
|
+
return;
|
|
2536
|
+
}
|
|
2537
|
+
const viewModel = buildSettingsViewModel({
|
|
2538
|
+
config: payload.config,
|
|
2539
|
+
projectName: resolveActiveProjectName(),
|
|
2540
|
+
});
|
|
2541
|
+
dialog.innerHTML = renderSettingsModalHtml(viewModel);
|
|
2542
|
+
bindSettingsModalForm(dialog);
|
|
2543
|
+
const uiLangSelect = dialog.querySelector('#settings-modal-ui-language');
|
|
2544
|
+
if (uiLangSelect) {
|
|
2545
|
+
const hiddenSelect = document.getElementById('language-select');
|
|
2546
|
+
uiLangSelect.value = hiddenSelect ? hiddenSelect.value : getLanguage();
|
|
2547
|
+
}
|
|
2548
|
+
dialog.showModal();
|
|
2549
|
+
} catch (error) {
|
|
2550
|
+
showToast(t('toast.error') || 'Erreur', 'error');
|
|
2551
|
+
}
|
|
2552
|
+
}
|
|
2553
|
+
|
|
2554
|
+
function closeSettingsModal() {
|
|
2555
|
+
const dialog = document.getElementById('settings-modal');
|
|
2556
|
+
if (dialog && dialog.open) dialog.close();
|
|
2557
|
+
}
|
|
2558
|
+
|
|
2559
|
+
function bindSettingsModalForm(dialog) {
|
|
2560
|
+
const form = dialog.querySelector('form.settings-modal__form');
|
|
2561
|
+
const cancelBtn = dialog.querySelector('.settings-modal__cancel');
|
|
2562
|
+
const errorEl = dialog.querySelector('.settings-modal__error');
|
|
2563
|
+
if (cancelBtn) {
|
|
2564
|
+
cancelBtn.addEventListener('click', (event) => {
|
|
2565
|
+
event.preventDefault();
|
|
2566
|
+
closeSettingsModal();
|
|
2567
|
+
});
|
|
2568
|
+
}
|
|
2569
|
+
if (!form) return;
|
|
2570
|
+
form.addEventListener('submit', async (event) => {
|
|
2571
|
+
event.preventDefault();
|
|
2572
|
+
if (errorEl) errorEl.textContent = '';
|
|
2573
|
+
const formData = new FormData(form);
|
|
2574
|
+
const payload = extractFormPayload(formData);
|
|
2575
|
+
const linkValidation = validateExternalLink(payload.externalLink ?? '');
|
|
2576
|
+
if (!linkValidation.ok) {
|
|
2577
|
+
if (errorEl) errorEl.textContent = linkValidation.message;
|
|
2578
|
+
return;
|
|
2579
|
+
}
|
|
2580
|
+
try {
|
|
2581
|
+
const response = await fetch(
|
|
2582
|
+
`${API_URL}/api/project-config?path=${encodeURIComponent(activeTabId)}`,
|
|
2583
|
+
{
|
|
2584
|
+
method: 'PATCH',
|
|
2585
|
+
headers: { 'Content-Type': 'application/json' },
|
|
2586
|
+
body: JSON.stringify(payload),
|
|
2587
|
+
},
|
|
2588
|
+
);
|
|
2589
|
+
if (!response.ok) {
|
|
2590
|
+
const errorPayload = await response.json().catch(() => ({ error: 'Échec de la sauvegarde' }));
|
|
2591
|
+
if (errorEl) errorEl.textContent = errorPayload?.error || 'Échec de la sauvegarde';
|
|
2592
|
+
return;
|
|
2593
|
+
}
|
|
2594
|
+
closeSettingsModal();
|
|
2595
|
+
refreshOverviewSection();
|
|
2596
|
+
} catch {
|
|
2597
|
+
if (errorEl) errorEl.textContent = 'Échec de la sauvegarde';
|
|
2598
|
+
}
|
|
2599
|
+
});
|
|
2600
|
+
}
|
|
2601
|
+
|
|
2602
|
+
function bindSettingsModalDismissals() {
|
|
2603
|
+
const dialog = document.getElementById('settings-modal');
|
|
2604
|
+
if (!dialog) return;
|
|
2605
|
+
dialog.addEventListener('click', (event) => {
|
|
2606
|
+
if (event.target === dialog) {
|
|
2607
|
+
closeSettingsModal();
|
|
2608
|
+
}
|
|
2609
|
+
});
|
|
2610
|
+
}
|
|
2611
|
+
|
|
2612
|
+
function bindSettingsModalTrigger() {
|
|
2613
|
+
const button = document.getElementById('open-settings-modal-btn');
|
|
2614
|
+
if (!button) return;
|
|
2615
|
+
button.addEventListener('click', () => {
|
|
2616
|
+
openSettingsModal();
|
|
2617
|
+
});
|
|
2618
|
+
}
|
|
2619
|
+
|
|
2514
2620
|
async function refreshOverviewSection() {
|
|
2515
2621
|
const container = document.getElementById('overview-section');
|
|
2516
2622
|
if (!container) return;
|
|
@@ -2525,6 +2631,9 @@
|
|
|
2525
2631
|
if (projectPath) activateProjectTab(projectPath);
|
|
2526
2632
|
});
|
|
2527
2633
|
});
|
|
2634
|
+
container.querySelectorAll('.project-card__external').forEach((anchor) => {
|
|
2635
|
+
anchor.addEventListener('click', (event) => event.stopPropagation());
|
|
2636
|
+
});
|
|
2528
2637
|
} catch {
|
|
2529
2638
|
// Silent: WS reconnection or next refresh will retry
|
|
2530
2639
|
}
|
|
@@ -2532,7 +2641,10 @@
|
|
|
2532
2641
|
|
|
2533
2642
|
async function initOverviewAndTabs() {
|
|
2534
2643
|
await fetchAvailableRepositories();
|
|
2535
|
-
|
|
2644
|
+
renderManagePanel();
|
|
2645
|
+
bindManagePanelToggle();
|
|
2646
|
+
bindSettingsModalTrigger();
|
|
2647
|
+
bindSettingsModalDismissals();
|
|
2536
2648
|
const persistedTab = readActiveTab();
|
|
2537
2649
|
const repositoryPaths = availableRepositories.map((repository) => repository.localPath);
|
|
2538
2650
|
if (persistedTab && persistedTab !== 'overview' && repositoryPaths.includes(persistedTab)) {
|
|
@@ -2542,6 +2654,202 @@
|
|
|
2542
2654
|
}
|
|
2543
2655
|
}
|
|
2544
2656
|
|
|
2657
|
+
let isManagePanelOpen = false;
|
|
2658
|
+
|
|
2659
|
+
function renderManagePanel() {
|
|
2660
|
+
const panel = document.getElementById('manage-panel');
|
|
2661
|
+
if (!panel) return;
|
|
2662
|
+
const model = buildManagePanelModel({ repositories: availableRepositories, isOpen: isManagePanelOpen });
|
|
2663
|
+
panel.innerHTML = renderManagePanelHtml(model);
|
|
2664
|
+
panel.dataset.open = isManagePanelOpen ? 'true' : 'false';
|
|
2665
|
+
bindManagePanelHandlers();
|
|
2666
|
+
}
|
|
2667
|
+
|
|
2668
|
+
function bindManagePanelToggle() {
|
|
2669
|
+
const toggle = document.getElementById('manage-projects-toggle');
|
|
2670
|
+
if (!toggle || toggle.dataset.bound === 'true') return;
|
|
2671
|
+
toggle.dataset.bound = 'true';
|
|
2672
|
+
toggle.addEventListener('click', () => {
|
|
2673
|
+
isManagePanelOpen = !isManagePanelOpen;
|
|
2674
|
+
toggle.setAttribute('aria-expanded', isManagePanelOpen ? 'true' : 'false');
|
|
2675
|
+
renderManagePanel();
|
|
2676
|
+
const panel = document.getElementById('manage-panel');
|
|
2677
|
+
if (panel) {
|
|
2678
|
+
loadAnimeApi().then((anime) => {
|
|
2679
|
+
if (anime) toggleHeight(panel, isManagePanelOpen, { animeApi: anime });
|
|
2680
|
+
}).catch(() => {});
|
|
2681
|
+
}
|
|
2682
|
+
});
|
|
2683
|
+
}
|
|
2684
|
+
|
|
2685
|
+
function bindManagePanelHandlers() {
|
|
2686
|
+
const panel = document.getElementById('manage-panel');
|
|
2687
|
+
if (!panel) return;
|
|
2688
|
+
const form = panel.querySelector('form.add-form');
|
|
2689
|
+
if (form) {
|
|
2690
|
+
form.addEventListener('submit', handleAddProjectSubmit);
|
|
2691
|
+
const input = form.querySelector('input.add-form-input');
|
|
2692
|
+
if (input) {
|
|
2693
|
+
input.addEventListener('keydown', (event) => {
|
|
2694
|
+
if (event.key === 'Escape') {
|
|
2695
|
+
input.value = '';
|
|
2696
|
+
clearManagePanelError();
|
|
2697
|
+
}
|
|
2698
|
+
});
|
|
2699
|
+
}
|
|
2700
|
+
}
|
|
2701
|
+
panel.querySelectorAll('.manage-row-delete').forEach((button) => {
|
|
2702
|
+
button.addEventListener('click', () => {
|
|
2703
|
+
const row = button.closest('.manage-row');
|
|
2704
|
+
const localPath = row?.dataset.localPath;
|
|
2705
|
+
if (localPath) handleDeleteProject(localPath, row);
|
|
2706
|
+
});
|
|
2707
|
+
});
|
|
2708
|
+
panel.querySelectorAll('.manage-row-toggle').forEach((button) => {
|
|
2709
|
+
button.addEventListener('click', () => {
|
|
2710
|
+
const row = button.closest('.manage-row');
|
|
2711
|
+
const localPath = row?.dataset.localPath;
|
|
2712
|
+
const currentlyEnabled = row?.dataset.enabled === 'true';
|
|
2713
|
+
if (localPath) handleToggleProject(localPath, !currentlyEnabled);
|
|
2714
|
+
});
|
|
2715
|
+
});
|
|
2716
|
+
}
|
|
2717
|
+
|
|
2718
|
+
function setManagePanelError(message) {
|
|
2719
|
+
const panel = document.getElementById('manage-panel');
|
|
2720
|
+
const target = panel?.querySelector('[data-role="error-message"]');
|
|
2721
|
+
if (!target) return;
|
|
2722
|
+
target.textContent = message;
|
|
2723
|
+
target.hidden = false;
|
|
2724
|
+
}
|
|
2725
|
+
|
|
2726
|
+
function clearManagePanelError() {
|
|
2727
|
+
const panel = document.getElementById('manage-panel');
|
|
2728
|
+
const target = panel?.querySelector('[data-role="error-message"]');
|
|
2729
|
+
if (!target) return;
|
|
2730
|
+
target.textContent = '';
|
|
2731
|
+
target.hidden = true;
|
|
2732
|
+
}
|
|
2733
|
+
|
|
2734
|
+
function flashAddFormError() {
|
|
2735
|
+
const form = document.querySelector('#manage-panel form.add-form');
|
|
2736
|
+
if (!form) return;
|
|
2737
|
+
form.classList.remove('is-error');
|
|
2738
|
+
requestAnimationFrame(() => form.classList.add('is-error'));
|
|
2739
|
+
setTimeout(() => form.classList.remove('is-error'), 320);
|
|
2740
|
+
}
|
|
2741
|
+
|
|
2742
|
+
function flashAddFormSuccess() {
|
|
2743
|
+
const input = document.querySelector('#manage-panel input.add-form-input');
|
|
2744
|
+
if (!input) return;
|
|
2745
|
+
input.classList.remove('is-success');
|
|
2746
|
+
requestAnimationFrame(() => input.classList.add('is-success'));
|
|
2747
|
+
setTimeout(() => input.classList.remove('is-success'), 1500);
|
|
2748
|
+
}
|
|
2749
|
+
|
|
2750
|
+
function flashTabEntering(localPath) {
|
|
2751
|
+
const tab = document.querySelector(`.dashboard-tab[data-tab-id="${CSS.escape(localPath)}"]`);
|
|
2752
|
+
if (!tab) return;
|
|
2753
|
+
tab.classList.add('is-entering');
|
|
2754
|
+
setTimeout(() => tab.classList.remove('is-entering'), 1500);
|
|
2755
|
+
}
|
|
2756
|
+
|
|
2757
|
+
async function handleAddProjectSubmit(event) {
|
|
2758
|
+
event.preventDefault();
|
|
2759
|
+
const form = event.currentTarget;
|
|
2760
|
+
const input = form.querySelector('input.add-form-input');
|
|
2761
|
+
const submit = form.querySelector('button.add-form-submit');
|
|
2762
|
+
const rawValue = input ? input.value : '';
|
|
2763
|
+
const validation = validateLocalPathInput(rawValue);
|
|
2764
|
+
clearManagePanelError();
|
|
2765
|
+
if (!validation.ok) {
|
|
2766
|
+
const message = validation.reason === 'empty' ? 'Chemin du projet requis' : 'Le chemin doit être absolu';
|
|
2767
|
+
setManagePanelError(message);
|
|
2768
|
+
flashAddFormError();
|
|
2769
|
+
return;
|
|
2770
|
+
}
|
|
2771
|
+
const trimmed = rawValue.trim();
|
|
2772
|
+
if (submit) submit.classList.add('is-busy');
|
|
2773
|
+
try {
|
|
2774
|
+
const response = await fetch(`${API_URL}/api/repositories`, {
|
|
2775
|
+
method: 'POST',
|
|
2776
|
+
headers: { 'Content-Type': 'application/json' },
|
|
2777
|
+
body: JSON.stringify({ localPath: trimmed }),
|
|
2778
|
+
});
|
|
2779
|
+
const body = await response.json().catch(() => ({}));
|
|
2780
|
+
if (!response.ok) {
|
|
2781
|
+
setManagePanelError(typeof body?.error === 'string' ? body.error : 'Erreur inconnue');
|
|
2782
|
+
flashAddFormError();
|
|
2783
|
+
return;
|
|
2784
|
+
}
|
|
2785
|
+
syncAvailableRepositoriesFromResponse(body);
|
|
2786
|
+
if (input) input.value = '';
|
|
2787
|
+
flashAddFormSuccess();
|
|
2788
|
+
renderManagePanel();
|
|
2789
|
+
renderDashboardTabs();
|
|
2790
|
+
flashTabEntering(trimmed);
|
|
2791
|
+
} catch {
|
|
2792
|
+
setManagePanelError('Erreur réseau');
|
|
2793
|
+
flashAddFormError();
|
|
2794
|
+
} finally {
|
|
2795
|
+
if (submit) submit.classList.remove('is-busy');
|
|
2796
|
+
}
|
|
2797
|
+
}
|
|
2798
|
+
|
|
2799
|
+
async function handleDeleteProject(localPath, rowElement) {
|
|
2800
|
+
try {
|
|
2801
|
+
const response = await fetch(`${API_URL}/api/repositories?localPath=${encodeURIComponent(localPath)}`, {
|
|
2802
|
+
method: 'DELETE',
|
|
2803
|
+
});
|
|
2804
|
+
const body = await response.json().catch(() => ({}));
|
|
2805
|
+
if (!response.ok) {
|
|
2806
|
+
setManagePanelError(typeof body?.error === 'string' ? body.error : 'Erreur inconnue');
|
|
2807
|
+
return;
|
|
2808
|
+
}
|
|
2809
|
+
syncAvailableRepositoriesFromResponse(body);
|
|
2810
|
+
if (rowElement) {
|
|
2811
|
+
rowElement.classList.add('is-leaving');
|
|
2812
|
+
setTimeout(() => {
|
|
2813
|
+
renderManagePanel();
|
|
2814
|
+
}, 250);
|
|
2815
|
+
} else {
|
|
2816
|
+
renderManagePanel();
|
|
2817
|
+
}
|
|
2818
|
+
const tab = document.querySelector(`.dashboard-tab[data-tab-id="${CSS.escape(localPath)}"]`);
|
|
2819
|
+
if (tab) {
|
|
2820
|
+
tab.classList.add('is-leaving');
|
|
2821
|
+
setTimeout(() => renderDashboardTabs(), 250);
|
|
2822
|
+
} else {
|
|
2823
|
+
renderDashboardTabs();
|
|
2824
|
+
}
|
|
2825
|
+
if (activeTabId === localPath) {
|
|
2826
|
+
activateOverviewTab();
|
|
2827
|
+
}
|
|
2828
|
+
} catch {
|
|
2829
|
+
setManagePanelError('Erreur réseau');
|
|
2830
|
+
}
|
|
2831
|
+
}
|
|
2832
|
+
|
|
2833
|
+
async function handleToggleProject(localPath, enabled) {
|
|
2834
|
+
try {
|
|
2835
|
+
const response = await fetch(`${API_URL}/api/repositories?localPath=${encodeURIComponent(localPath)}`, {
|
|
2836
|
+
method: 'PATCH',
|
|
2837
|
+
headers: { 'Content-Type': 'application/json' },
|
|
2838
|
+
body: JSON.stringify({ enabled }),
|
|
2839
|
+
});
|
|
2840
|
+
const body = await response.json().catch(() => ({}));
|
|
2841
|
+
if (!response.ok) {
|
|
2842
|
+
setManagePanelError(typeof body?.error === 'string' ? body.error : 'Erreur inconnue');
|
|
2843
|
+
return;
|
|
2844
|
+
}
|
|
2845
|
+
syncAvailableRepositoriesFromResponse(body);
|
|
2846
|
+
renderManagePanel();
|
|
2847
|
+
renderDashboardTabs();
|
|
2848
|
+
} catch {
|
|
2849
|
+
setManagePanelError('Erreur réseau');
|
|
2850
|
+
}
|
|
2851
|
+
}
|
|
2852
|
+
|
|
2545
2853
|
function getMrLabel(platform = activePlatform) {
|
|
2546
2854
|
return platform === 'github' ? 'PR' : 'MR';
|
|
2547
2855
|
}
|
|
@@ -2681,19 +2989,6 @@
|
|
|
2681
2989
|
const modelSonnet = document.getElementById('i18n-model-sonnet');
|
|
2682
2990
|
if (modelSonnet) modelSonnet.textContent = t('model.sonnet');
|
|
2683
2991
|
|
|
2684
|
-
// Project loader
|
|
2685
|
-
const projectPlaceholder = document.getElementById('i18n-project-placeholder');
|
|
2686
|
-
if (projectPlaceholder) projectPlaceholder.textContent = t('project.selectPlaceholder');
|
|
2687
|
-
|
|
2688
|
-
const projectPathInput = document.getElementById('project-path-input');
|
|
2689
|
-
if (projectPathInput) projectPathInput.placeholder = t('project.inputPlaceholder');
|
|
2690
|
-
|
|
2691
|
-
const projectLoad = document.getElementById('i18n-project-load');
|
|
2692
|
-
if (projectLoad) projectLoad.textContent = t('project.load');
|
|
2693
|
-
|
|
2694
|
-
const removeProjectBtn = document.getElementById('remove-project-btn');
|
|
2695
|
-
if (removeProjectBtn) removeProjectBtn.title = t('project.removeTooltip');
|
|
2696
|
-
|
|
2697
2992
|
// Login sections
|
|
2698
2993
|
const claudeLoginTitle = document.getElementById('i18n-claude-login-title');
|
|
2699
2994
|
if (claudeLoginTitle) claudeLoginTitle.textContent = t('login.claude.title');
|
|
@@ -2792,9 +3087,6 @@
|
|
|
2792
3087
|
|
|
2793
3088
|
const modalConfirm = document.getElementById('cancel-modal-confirm');
|
|
2794
3089
|
if (modalConfirm) modalConfirm.textContent = t('modal.confirm');
|
|
2795
|
-
|
|
2796
|
-
// Update project select placeholder (re-render the select)
|
|
2797
|
-
updateProjectSelect();
|
|
2798
3090
|
}
|
|
2799
3091
|
|
|
2800
3092
|
async function checkForUpdates() {
|
|
@@ -2952,9 +3244,6 @@
|
|
|
2952
3244
|
window.toggleStats = toggleStats;
|
|
2953
3245
|
window.changeModel = changeModel;
|
|
2954
3246
|
window.changeLanguage = changeLanguage;
|
|
2955
|
-
window.onProjectSelect = onProjectSelect;
|
|
2956
|
-
window.loadProjectConfig = loadProjectConfig;
|
|
2957
|
-
window.removeCurrentProject = removeCurrentProject;
|
|
2958
3247
|
window.toggleReviewAccordion = toggleReviewAccordion;
|
|
2959
3248
|
window.toggleReviewDescription = toggleReviewDescription;
|
|
2960
3249
|
window.deleteReviewFile = deleteReviewFile;
|
|
@@ -3153,7 +3442,169 @@
|
|
|
3153
3442
|
}
|
|
3154
3443
|
|
|
3155
3444
|
refreshWorktreeSection({ animate: true });
|
|
3156
|
-
setInterval(() => refreshWorktreeSection({ animate: false }),
|
|
3445
|
+
setInterval(() => refreshWorktreeSection({ animate: false }), 5000);
|
|
3446
|
+
|
|
3447
|
+
// === Operator's Console boot animations ===
|
|
3448
|
+
|
|
3449
|
+
let heartbeatStop = null;
|
|
3450
|
+
|
|
3451
|
+
async function bootAnimations() {
|
|
3452
|
+
const anime = await loadAnimeApi();
|
|
3453
|
+
if (!anime) return;
|
|
3454
|
+
|
|
3455
|
+
// Logo breath — daemon-alive signal
|
|
3456
|
+
const logo = document.querySelector('.logo');
|
|
3457
|
+
if (logo) breatheLogo(logo, { animeApi: anime });
|
|
3458
|
+
|
|
3459
|
+
// Online status dot pulse
|
|
3460
|
+
const statusDot = document.querySelector('.status-dot');
|
|
3461
|
+
if (statusDot) pulseStatusDot(statusDot, { animeApi: anime });
|
|
3462
|
+
|
|
3463
|
+
// Metric cards stagger mount
|
|
3464
|
+
const cards = document.querySelectorAll('.cards > .card');
|
|
3465
|
+
if (cards.length > 0) animateMount(cards, { animeApi: anime });
|
|
3466
|
+
|
|
3467
|
+
// Sidebar slide-in
|
|
3468
|
+
const sidebar = document.querySelector('.dashboard-sidebar');
|
|
3469
|
+
if (sidebar && !reducedMotion()) {
|
|
3470
|
+
anime.animate(sidebar, {
|
|
3471
|
+
opacity: [{ from: 0, to: 1 }],
|
|
3472
|
+
translateX: [{ from: -8, to: 0 }],
|
|
3473
|
+
duration: 280,
|
|
3474
|
+
easing: 'easeOutCubic',
|
|
3475
|
+
});
|
|
3476
|
+
}
|
|
3477
|
+
|
|
3478
|
+
// Attention strip mount (focus-strip promoted to main)
|
|
3479
|
+
const focusChips = document.querySelectorAll('.focus-chip');
|
|
3480
|
+
if (focusChips.length > 0) animateMount(focusChips, { animeApi: anime, staggerMs: 40, yOffset: 8 });
|
|
3481
|
+
|
|
3482
|
+
// Heartbeat on pending-reviews empty state
|
|
3483
|
+
startHeartbeat(anime);
|
|
3484
|
+
|
|
3485
|
+
// Card hover lift — wire to existing cards
|
|
3486
|
+
wireCardHovers(anime);
|
|
3487
|
+
|
|
3488
|
+
// Settings modal spring-in
|
|
3489
|
+
wireSettingsModal(anime);
|
|
3490
|
+
}
|
|
3491
|
+
|
|
3492
|
+
function startHeartbeat(anime) {
|
|
3493
|
+
const container = document.getElementById('heartbeat-line');
|
|
3494
|
+
if (!container) return;
|
|
3495
|
+
if (heartbeatStop) heartbeatStop.stop();
|
|
3496
|
+
heartbeatStop = heartbeat(container, { animeApi: anime });
|
|
3497
|
+
}
|
|
3498
|
+
|
|
3499
|
+
function wireCardHovers(anime) {
|
|
3500
|
+
const cards = document.querySelectorAll('.cards > .card');
|
|
3501
|
+
for (const card of cards) {
|
|
3502
|
+
card.addEventListener('mouseenter', () => liftCard(card, { animeApi: anime }));
|
|
3503
|
+
card.addEventListener('mouseleave', () => unliftCard(card, { animeApi: anime }));
|
|
3504
|
+
}
|
|
3505
|
+
}
|
|
3506
|
+
|
|
3507
|
+
function wireSettingsModal(anime) {
|
|
3508
|
+
const modal = document.getElementById('settings-modal');
|
|
3509
|
+
if (!modal) return;
|
|
3510
|
+
modal.addEventListener('animationsjs-open', () => springIn(modal, { animeApi: anime }));
|
|
3511
|
+
}
|
|
3512
|
+
|
|
3513
|
+
// Tab underline glide — set up after tab bar renders
|
|
3514
|
+
let tabUnderlineEl = null;
|
|
3515
|
+
|
|
3516
|
+
function setupTabUnderline() {
|
|
3517
|
+
const tabBar = document.querySelector('.dashboard-tab-bar');
|
|
3518
|
+
if (!tabBar) return;
|
|
3519
|
+
|
|
3520
|
+
if (!tabUnderlineEl) {
|
|
3521
|
+
tabUnderlineEl = document.createElement('div');
|
|
3522
|
+
tabUnderlineEl.className = 'tab-underline-indicator';
|
|
3523
|
+
tabBar.style.position = 'relative';
|
|
3524
|
+
tabBar.appendChild(tabUnderlineEl);
|
|
3525
|
+
}
|
|
3526
|
+
|
|
3527
|
+
const activeTab = tabBar.querySelector('.is-active');
|
|
3528
|
+
if (activeTab) {
|
|
3529
|
+
loadAnimeApi().then((anime) => {
|
|
3530
|
+
if (anime && tabUnderlineEl) slideTabUnderline(tabUnderlineEl, activeTab, { animeApi: anime });
|
|
3531
|
+
}).catch(() => {});
|
|
3532
|
+
}
|
|
3533
|
+
}
|
|
3534
|
+
|
|
3535
|
+
// Counter animation — wrap renderCardCounters and focus strip to animate changes
|
|
3536
|
+
const originalCounterIds = ['running-count', 'queued-count', 'completed-count', 'focus-now-count', 'focus-next-count', 'focus-blocked-count'];
|
|
3537
|
+
const previousCounterValues = {};
|
|
3538
|
+
|
|
3539
|
+
function animateCounterChanges(anime) {
|
|
3540
|
+
for (const id of originalCounterIds) {
|
|
3541
|
+
const el = document.getElementById(id);
|
|
3542
|
+
if (!el) continue;
|
|
3543
|
+
if (el.dataset.animating === 'true') continue;
|
|
3544
|
+
const currentText = el.textContent;
|
|
3545
|
+
const currentVal = parseInt(currentText, 10);
|
|
3546
|
+
const prevVal = previousCounterValues[id];
|
|
3547
|
+
if (!Number.isNaN(currentVal) && prevVal !== undefined && prevVal !== currentVal) {
|
|
3548
|
+
el.dataset.animating = 'true';
|
|
3549
|
+
animateCounterValue(el, prevVal, currentVal, {
|
|
3550
|
+
animeApi: anime,
|
|
3551
|
+
onComplete: () => {
|
|
3552
|
+
el.dataset.animating = 'false';
|
|
3553
|
+
previousCounterValues[id] = currentVal;
|
|
3554
|
+
},
|
|
3555
|
+
});
|
|
3556
|
+
} else if (!Number.isNaN(currentVal)) {
|
|
3557
|
+
previousCounterValues[id] = currentVal;
|
|
3558
|
+
}
|
|
3559
|
+
}
|
|
3560
|
+
}
|
|
3561
|
+
|
|
3562
|
+
// Observe counter element changes
|
|
3563
|
+
async function observeCounters() {
|
|
3564
|
+
const anime = await loadAnimeApi();
|
|
3565
|
+
if (!anime) return;
|
|
3566
|
+
|
|
3567
|
+
// Snapshot initial values
|
|
3568
|
+
for (const id of originalCounterIds) {
|
|
3569
|
+
const el = document.getElementById(id);
|
|
3570
|
+
if (el) {
|
|
3571
|
+
const val = parseInt(el.textContent, 10);
|
|
3572
|
+
if (!Number.isNaN(val)) previousCounterValues[id] = val;
|
|
3573
|
+
}
|
|
3574
|
+
}
|
|
3575
|
+
|
|
3576
|
+
const observer = new MutationObserver(() => animateCounterChanges(anime));
|
|
3577
|
+
for (const id of originalCounterIds) {
|
|
3578
|
+
const el = document.getElementById(id);
|
|
3579
|
+
if (el) observer.observe(el, { childList: true, characterData: true, subtree: true });
|
|
3580
|
+
}
|
|
3581
|
+
}
|
|
3582
|
+
|
|
3583
|
+
// Visibility change — pause/resume heartbeat to save battery
|
|
3584
|
+
document.addEventListener('visibilitychange', () => {
|
|
3585
|
+
loadAnimeApi().then((anime) => {
|
|
3586
|
+
if (!anime) return;
|
|
3587
|
+
if (document.hidden) {
|
|
3588
|
+
if (heartbeatStop) heartbeatStop.stop();
|
|
3589
|
+
} else {
|
|
3590
|
+
startHeartbeat(anime);
|
|
3591
|
+
}
|
|
3592
|
+
}).catch(() => {});
|
|
3593
|
+
});
|
|
3594
|
+
|
|
3595
|
+
// Boot animations & observers exactly once, regardless of readyState timing
|
|
3596
|
+
let __animationsBooted = false;
|
|
3597
|
+
function bootOnce() {
|
|
3598
|
+
if (__animationsBooted) return;
|
|
3599
|
+
__animationsBooted = true;
|
|
3600
|
+
bootAnimations();
|
|
3601
|
+
observeCounters();
|
|
3602
|
+
}
|
|
3603
|
+
if (document.readyState !== 'loading') {
|
|
3604
|
+
bootOnce();
|
|
3605
|
+
} else {
|
|
3606
|
+
window.addEventListener('DOMContentLoaded', bootOnce);
|
|
3607
|
+
}
|
|
3157
3608
|
</script>
|
|
3158
3609
|
</body>
|
|
3159
3610
|
</html>
|