reviewflow 3.21.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 +7 -0
- package/dist/dashboard/index.html +258 -44
- 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 +16 -1
- package/dist/dashboard/modules/cardCounters.d.ts.map +1 -1
- package/dist/dashboard/modules/cardCounters.js +36 -2
- package/dist/dashboard/modules/cardCounters.js.map +1 -1
- 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/settingsModal.d.ts.map +1 -1
- package/dist/dashboard/modules/settingsModal.js +28 -8
- package/dist/dashboard/modules/settingsModal.js.map +1 -1
- package/dist/dashboard/styles.css +731 -372
- 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/interface-adapters/controllers/http/repositories.routes.d.ts.map +1 -1
- package/dist/modules/cli-configuration/interface-adapters/controllers/http/repositories.routes.js +1 -0
- package/dist/modules/cli-configuration/interface-adapters/controllers/http/repositories.routes.js.map +1 -1
- 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/tests/acceptance/91-dashboard-multi-project-overview.acceptance.test.js +2 -0
- package/dist/tests/acceptance/91-dashboard-multi-project-overview.acceptance.test.js.map +1 -1
- 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/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.js +56 -0
- package/dist/tests/units/dashboard/modules/cardCounters.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/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/repositories.routes.test.js +1 -1
- package/dist/tests/units/modules/cli-configuration/interface-adapters/controllers/http/repositories.routes.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
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.22.0](https://github.com/DGouron/review-flow/compare/reviewflow-v3.21.0...reviewflow-v3.22.0) (2026-05-25)
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
### Added
|
|
12
|
+
|
|
13
|
+
* **dashboard:** operator's console redesign + animations ([#204](https://github.com/DGouron/review-flow/issues/204)) ([8b33ebd](https://github.com/DGouron/review-flow/commit/8b33ebd28bc09a85280371c499f68d73927eb18e))
|
|
14
|
+
|
|
8
15
|
## [3.21.0](https://github.com/DGouron/review-flow/compare/reviewflow-v3.20.0...reviewflow-v3.21.0) (2026-05-25)
|
|
9
16
|
|
|
10
17
|
|
|
@@ -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">
|
|
@@ -37,6 +49,29 @@
|
|
|
37
49
|
</button>
|
|
38
50
|
<section id="manage-panel" class="manage-panel" aria-label="Manage projects" data-open="false"></section>
|
|
39
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>
|
|
40
75
|
</div>
|
|
41
76
|
|
|
42
77
|
<div id="cards-scope-marker" class="cards-scope-marker" data-scope-kind="overview">
|
|
@@ -57,44 +92,21 @@
|
|
|
57
92
|
<div class="card-label" id="i18n-card-completed"></div>
|
|
58
93
|
<div id="completed-count" class="card-value">-</div>
|
|
59
94
|
</div>
|
|
60
|
-
<div class="card">
|
|
61
|
-
<div class="card-label" id="i18n-card-claude-cli"></div>
|
|
62
|
-
<div id="claude-status" class="card-claude checking">
|
|
63
|
-
<span class="status" id="i18n-claude-checking"></span>
|
|
64
|
-
<span class="version"></span>
|
|
65
|
-
</div>
|
|
66
|
-
</div>
|
|
67
|
-
<div class="card" id="git-cli-card">
|
|
68
|
-
<div class="card-label" id="git-cli-label"></div>
|
|
69
|
-
<div id="git-cli-status" class="card-claude checking">
|
|
70
|
-
<span class="status" id="i18n-git-load-project"></span>
|
|
71
|
-
<span class="version"></span>
|
|
72
|
-
</div>
|
|
73
|
-
</div>
|
|
74
|
-
<div class="card">
|
|
75
|
-
<div class="card-label" id="i18n-card-model"></div>
|
|
76
|
-
<div class="card-model">
|
|
77
|
-
<select id="model-select" class="model-select" onchange="changeModel(this.value)">
|
|
78
|
-
<option value="opus" id="i18n-model-opus"></option>
|
|
79
|
-
<option value="sonnet" id="i18n-model-sonnet"></option>
|
|
80
|
-
</select>
|
|
81
|
-
</div>
|
|
82
|
-
</div>
|
|
83
95
|
</div>
|
|
84
96
|
|
|
85
97
|
<div class="dashboard-layout">
|
|
86
98
|
<aside class="dashboard-sidebar" aria-label="Project tools">
|
|
87
|
-
<
|
|
88
|
-
<
|
|
89
|
-
|
|
90
|
-
<option value="en">English</option>
|
|
91
|
-
<option value="fr">Français</option>
|
|
92
|
-
</select>
|
|
93
|
-
</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>
|
|
94
102
|
|
|
95
103
|
<span id="config-status" class="config-status hidden"></span>
|
|
96
104
|
|
|
97
|
-
<
|
|
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">
|
|
98
110
|
<div class="focus-chip focus-now">
|
|
99
111
|
<div class="focus-copy">
|
|
100
112
|
<span class="focus-label" id="i18n-strip-now"></span>
|
|
@@ -119,14 +131,6 @@
|
|
|
119
131
|
<button id="focus-strip-toggle" class="focus-toggle-btn" onclick="toggleFocusStripMode()"></button>
|
|
120
132
|
</div>
|
|
121
133
|
|
|
122
|
-
<section id="worktree-section" aria-label="Worktree pool"></section>
|
|
123
|
-
|
|
124
|
-
<button type="button" id="open-settings-modal-btn" class="sidebar-settings-button" hidden>
|
|
125
|
-
<span class="sidebar-settings-button__prefix">// SETTINGS</span>
|
|
126
|
-
</button>
|
|
127
|
-
</aside>
|
|
128
|
-
|
|
129
|
-
<main class="dashboard-main">
|
|
130
134
|
<section id="overview-section" class="overview-section" aria-label="Multi-project overview"></section>
|
|
131
135
|
|
|
132
136
|
<div id="data-loading-state" class="data-loading hidden" role="status" aria-live="polite">
|
|
@@ -154,7 +158,11 @@
|
|
|
154
158
|
<span id="pending-reviews-count" class="badge-count hidden">0</span>
|
|
155
159
|
</div>
|
|
156
160
|
<div id="pending-reviews" class="section-content">
|
|
157
|
-
<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>
|
|
158
166
|
</div>
|
|
159
167
|
</div>
|
|
160
168
|
|
|
@@ -323,9 +331,30 @@
|
|
|
323
331
|
|
|
324
332
|
<dialog id="settings-modal" class="settings-modal" aria-labelledby="settings-modal-title"></dialog>
|
|
325
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
|
+
|
|
326
340
|
<script type="module">
|
|
327
341
|
import { t, setLanguage, getLanguage } from './modules/i18n.js';
|
|
328
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';
|
|
329
358
|
import { escapeHtml, markdownToHtml, sanitizeHttpUrl } from './modules/html.js';
|
|
330
359
|
import { getAgentIcon, icon, refreshIcons } from './modules/icons.js';
|
|
331
360
|
import { MAX_RECONNECT_ATTEMPTS, RECONNECT_DELAY, STORAGE_KEY_CURRENT, STORAGE_KEY_FOCUS_STRIP_MODE, QUALITY_TARGET_SCORE } from './modules/constants.js';
|
|
@@ -334,7 +363,7 @@
|
|
|
334
363
|
import { renderOverviewHtml } from './modules/overview.js';
|
|
335
364
|
import { getDesktopNotificationPayload, shouldNotifyDesktop } from './modules/desktopNotifications.js';
|
|
336
365
|
import { getLoadingPresentation, getQuietRefreshSectionIdentifiers } from './modules/loading.js';
|
|
337
|
-
import { computeCardCounters } from './modules/cardCounters.js';
|
|
366
|
+
import { computeCardCounters, extractGithubSlug } from './modules/cardCounters.js';
|
|
338
367
|
import { collectReviewNotifications, createReviewNotificationState } from './modules/notifications.js';
|
|
339
368
|
import { resolveReviewAssigneeDisplay } from './modules/assignee.js';
|
|
340
369
|
import { buildQueueLanesModel } from './modules/queueLanes.js';
|
|
@@ -804,6 +833,13 @@
|
|
|
804
833
|
document.getElementById('focus-next-count').textContent = String(nextCount);
|
|
805
834
|
document.getElementById('focus-blocked-count').textContent = String(blocked);
|
|
806
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
|
+
|
|
807
843
|
const activeReviewsSection = document.getElementById('active-reviews-section');
|
|
808
844
|
const activeReviewsEl = document.getElementById('active-reviews');
|
|
809
845
|
const activeReviewsCount = document.getElementById('active-reviews-count');
|
|
@@ -2392,7 +2428,11 @@
|
|
|
2392
2428
|
} else {
|
|
2393
2429
|
const repository = availableRepositories.find((r) => r.localPath === activeTabId);
|
|
2394
2430
|
const projectName = repository?.name ?? activeTabId.split('/').filter(Boolean).pop() ?? activeTabId;
|
|
2395
|
-
|
|
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 };
|
|
2396
2436
|
}
|
|
2397
2437
|
const counters = computeCardCounters({
|
|
2398
2438
|
activeReviews: currentData.activeReviews,
|
|
@@ -2428,6 +2468,7 @@
|
|
|
2428
2468
|
handleTabClick(tabId);
|
|
2429
2469
|
});
|
|
2430
2470
|
});
|
|
2471
|
+
setupTabUnderline();
|
|
2431
2472
|
}
|
|
2432
2473
|
|
|
2433
2474
|
function handleTabClick(tabId) {
|
|
@@ -2499,6 +2540,11 @@
|
|
|
2499
2540
|
});
|
|
2500
2541
|
dialog.innerHTML = renderSettingsModalHtml(viewModel);
|
|
2501
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
|
+
}
|
|
2502
2548
|
dialog.showModal();
|
|
2503
2549
|
} catch (error) {
|
|
2504
2550
|
showToast(t('toast.error') || 'Erreur', 'error');
|
|
@@ -2627,6 +2673,12 @@
|
|
|
2627
2673
|
isManagePanelOpen = !isManagePanelOpen;
|
|
2628
2674
|
toggle.setAttribute('aria-expanded', isManagePanelOpen ? 'true' : 'false');
|
|
2629
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
|
+
}
|
|
2630
2682
|
});
|
|
2631
2683
|
}
|
|
2632
2684
|
|
|
@@ -3390,7 +3442,169 @@
|
|
|
3390
3442
|
}
|
|
3391
3443
|
|
|
3392
3444
|
refreshWorktreeSection({ animate: true });
|
|
3393
|
-
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
|
+
}
|
|
3394
3608
|
</script>
|
|
3395
3609
|
</body>
|
|
3396
3610
|
</html>
|
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* animations.js — Anime.js v4 animation helpers for the Operator's Console dashboard.
|
|
3
|
+
*
|
|
4
|
+
* Pure functions: take DOM elements + options, delegate to anime.js, return nothing
|
|
5
|
+
* (or a stop handle for loops). No DOM access at module level, no global state.
|
|
6
|
+
*
|
|
7
|
+
* Every function guards on reducedMotion() and returns immediately (or applies end-state)
|
|
8
|
+
* when the user prefers reduced motion.
|
|
9
|
+
*
|
|
10
|
+
* @module animations
|
|
11
|
+
*/
|
|
12
|
+
/**
|
|
13
|
+
* Returns true when the user has opted-in to reduced motion.
|
|
14
|
+
* @returns {boolean}
|
|
15
|
+
*/
|
|
16
|
+
export function reducedMotion(): boolean;
|
|
17
|
+
/**
|
|
18
|
+
* Fade-up stagger mount animation for a NodeList or Array of elements.
|
|
19
|
+
* @param {Element[]|NodeList} elements
|
|
20
|
+
* @param {{ staggerMs?: number, durationMs?: number, yOffset?: number, animeApi: object }} options
|
|
21
|
+
*/
|
|
22
|
+
export function animateMount(elements: Element[] | NodeList, options: {
|
|
23
|
+
staggerMs?: number;
|
|
24
|
+
durationMs?: number;
|
|
25
|
+
yOffset?: number;
|
|
26
|
+
animeApi: object;
|
|
27
|
+
}): void;
|
|
28
|
+
/**
|
|
29
|
+
* Animated number counter — morphs text content from oldValue to newValue.
|
|
30
|
+
* Adds a brief scale pulse on the element.
|
|
31
|
+
* @param {Element} element
|
|
32
|
+
* @param {number} from
|
|
33
|
+
* @param {number} to
|
|
34
|
+
* @param {{ animeApi: object, durationMs?: number, onComplete?: () => void }} options
|
|
35
|
+
*/
|
|
36
|
+
export function animateCounter(element: Element, from: number, to: number, options: {
|
|
37
|
+
animeApi: object;
|
|
38
|
+
durationMs?: number;
|
|
39
|
+
onComplete?: () => void;
|
|
40
|
+
}): void;
|
|
41
|
+
/**
|
|
42
|
+
* Slide the shared tab underline element to match the target tab's
|
|
43
|
+
* position and width. Pass the underline element and the active tab element.
|
|
44
|
+
* @param {Element} underline
|
|
45
|
+
* @param {Element} targetTab
|
|
46
|
+
* @param {{ animeApi: object }} options
|
|
47
|
+
*/
|
|
48
|
+
export function slideTabUnderline(underline: Element, targetTab: Element, options: {
|
|
49
|
+
animeApi: object;
|
|
50
|
+
}): void;
|
|
51
|
+
/**
|
|
52
|
+
* Heartbeat — a 1px amber line traversing left-to-right infinitely
|
|
53
|
+
* inside the given container element.
|
|
54
|
+
* @param {Element} container - The 240px-wide container element.
|
|
55
|
+
* @param {{ animeApi: object }} options
|
|
56
|
+
* @returns {{ stop: () => void }}
|
|
57
|
+
*/
|
|
58
|
+
export function heartbeat(container: Element, options: {
|
|
59
|
+
animeApi: object;
|
|
60
|
+
}): {
|
|
61
|
+
stop: () => void;
|
|
62
|
+
};
|
|
63
|
+
/**
|
|
64
|
+
* Breathing amber glow pulse on a live/active review card.
|
|
65
|
+
* @param {Element} element
|
|
66
|
+
* @param {{ animeApi: object }} options
|
|
67
|
+
* @returns {{ stop: () => void }}
|
|
68
|
+
*/
|
|
69
|
+
export function pulseLive(element: Element, options: {
|
|
70
|
+
animeApi: object;
|
|
71
|
+
}): {
|
|
72
|
+
stop: () => void;
|
|
73
|
+
};
|
|
74
|
+
/**
|
|
75
|
+
* Spring-in entry for the settings modal (scale + opacity).
|
|
76
|
+
* @param {Element} modal
|
|
77
|
+
* @param {{ animeApi: object }} options
|
|
78
|
+
*/
|
|
79
|
+
export function springIn(modal: Element, options: {
|
|
80
|
+
animeApi: object;
|
|
81
|
+
}): void;
|
|
82
|
+
/**
|
|
83
|
+
* Card lift on hover — translateY -2px + scale 1.005.
|
|
84
|
+
* @param {Element} card
|
|
85
|
+
* @param {{ animeApi: object }} options
|
|
86
|
+
*/
|
|
87
|
+
export function liftCard(card: Element, options: {
|
|
88
|
+
animeApi: object;
|
|
89
|
+
}): void;
|
|
90
|
+
/**
|
|
91
|
+
* Return card to resting position.
|
|
92
|
+
* @param {Element} card
|
|
93
|
+
* @param {{ animeApi: object }} options
|
|
94
|
+
*/
|
|
95
|
+
export function unliftCard(card: Element, options: {
|
|
96
|
+
animeApi: object;
|
|
97
|
+
}): void;
|
|
98
|
+
/**
|
|
99
|
+
* Subtle breathing pulse for the online status dot.
|
|
100
|
+
* @param {Element} dot
|
|
101
|
+
* @param {{ animeApi: object }} options
|
|
102
|
+
* @returns {{ stop: () => void }}
|
|
103
|
+
*/
|
|
104
|
+
export function pulseStatusDot(dot: Element, options: {
|
|
105
|
+
animeApi: object;
|
|
106
|
+
}): {
|
|
107
|
+
stop: () => void;
|
|
108
|
+
};
|
|
109
|
+
/**
|
|
110
|
+
* Very subtle logo breathing pulse (daemon-alive signal).
|
|
111
|
+
* @param {Element} logo
|
|
112
|
+
* @param {{ animeApi: object }} options
|
|
113
|
+
* @returns {{ stop: () => void }}
|
|
114
|
+
*/
|
|
115
|
+
export function breatheLogo(logo: Element, options: {
|
|
116
|
+
animeApi: object;
|
|
117
|
+
}): {
|
|
118
|
+
stop: () => void;
|
|
119
|
+
};
|
|
120
|
+
/**
|
|
121
|
+
* Cross-fade between tab content panels (fade out old, fade in new).
|
|
122
|
+
* @param {Element} outgoing - Element being hidden.
|
|
123
|
+
* @param {Element} incoming - Element being shown.
|
|
124
|
+
* @param {{ animeApi: object }} options
|
|
125
|
+
*/
|
|
126
|
+
export function crossFadeTab(outgoing: Element, incoming: Element, options: {
|
|
127
|
+
animeApi: object;
|
|
128
|
+
}): void;
|
|
129
|
+
/**
|
|
130
|
+
* Expand an element from height 0 to its natural height using anime.js.
|
|
131
|
+
* Uses a snapshot of the natural height before collapsing to 0.
|
|
132
|
+
* @param {Element} element
|
|
133
|
+
* @param {{ animeApi: object }} options
|
|
134
|
+
*/
|
|
135
|
+
export function expandHeight(element: Element, options: {
|
|
136
|
+
animeApi: object;
|
|
137
|
+
}): void;
|
|
138
|
+
/**
|
|
139
|
+
* Collapse an element from its natural height to 0 using anime.js.
|
|
140
|
+
* @param {Element} element
|
|
141
|
+
* @param {{ animeApi: object }} options
|
|
142
|
+
*/
|
|
143
|
+
export function collapseHeight(element: Element, options: {
|
|
144
|
+
animeApi: object;
|
|
145
|
+
}): void;
|
|
146
|
+
/**
|
|
147
|
+
* Toggle expand/collapse of an element and stagger-animate its children on open.
|
|
148
|
+
* @param {Element} element
|
|
149
|
+
* @param {boolean} isOpen - true to expand, false to collapse
|
|
150
|
+
* @param {{ animeApi: object, childSelector?: string }} options
|
|
151
|
+
*/
|
|
152
|
+
export function toggleHeight(element: Element, isOpen: boolean, options: {
|
|
153
|
+
animeApi: object;
|
|
154
|
+
childSelector?: string;
|
|
155
|
+
}): void;
|
|
156
|
+
/**
|
|
157
|
+
* Review status change: amber border fades → green glow pulse → green border.
|
|
158
|
+
* @param {Element} element
|
|
159
|
+
* @param {{ animeApi: object }} options
|
|
160
|
+
*/
|
|
161
|
+
export function reviewCompleted(element: Element, options: {
|
|
162
|
+
animeApi: object;
|
|
163
|
+
}): void;
|
|
164
|
+
//# sourceMappingURL=animations.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"animations.d.ts","sourceRoot":"","sources":["../../../src/dashboard/modules/animations.js"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAEH;;;GAGG;AACH,iCAFa,OAAO,CAInB;AAED;;;;GAIG;AACH,uCAHW,OAAO,EAAE,GAAC,QAAQ,WAClB;IAAE,SAAS,CAAC,EAAE,MAAM,CAAC;IAAC,UAAU,CAAC,EAAE,MAAM,CAAC;IAAC,OAAO,CAAC,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAA;CAAE,QAkBzF;AAED;;;;;;;GAOG;AACH,wCALW,OAAO,QACP,MAAM,MACN,MAAM,WACN;IAAE,QAAQ,EAAE,MAAM,CAAC;IAAC,UAAU,CAAC,EAAE,MAAM,CAAC;IAAC,UAAU,CAAC,EAAE,MAAM,IAAI,CAAA;CAAE,QA0B5E;AAED;;;;;;GAMG;AACH,6CAJW,OAAO,aACP,OAAO,WACP;IAAE,QAAQ,EAAE,MAAM,CAAA;CAAE,QAsB9B;AAED;;;;;;GAMG;AACH,qCAJW,OAAO,WACP;IAAE,QAAQ,EAAE,MAAM,CAAA;CAAE,GAClB;IAAE,IAAI,EAAE,MAAM,IAAI,CAAA;CAAE,CA6ChC;AAED;;;;;GAKG;AACH,mCAJW,OAAO,WACP;IAAE,QAAQ,EAAE,MAAM,CAAA;CAAE,GAClB;IAAE,IAAI,EAAE,MAAM,IAAI,CAAA;CAAE,CAiBhC;AAED;;;;GAIG;AACH,gCAHW,OAAO,WACP;IAAE,QAAQ,EAAE,MAAM,CAAA;CAAE,QAe9B;AAED;;;;GAIG;AACH,+BAHW,OAAO,WACP;IAAE,QAAQ,EAAE,MAAM,CAAA;CAAE,QAW9B;AAED;;;;GAIG;AACH,iCAHW,OAAO,WACP;IAAE,QAAQ,EAAE,MAAM,CAAA;CAAE,QAW9B;AAED;;;;;GAKG;AACH,oCAJW,OAAO,WACP;IAAE,QAAQ,EAAE,MAAM,CAAA;CAAE,GAClB;IAAE,IAAI,EAAE,MAAM,IAAI,CAAA;CAAE,CAYhC;AAED;;;;;GAKG;AACH,kCAJW,OAAO,WACP;IAAE,QAAQ,EAAE,MAAM,CAAA;CAAE,GAClB;IAAE,IAAI,EAAE,MAAM,IAAI,CAAA;CAAE,CAYhC;AAED;;;;;GAKG;AACH,uCAJW,OAAO,YACP,OAAO,WACP;IAAE,QAAQ,EAAE,MAAM,CAAA;CAAE,QAwB9B;AAED;;;;;GAKG;AACH,sCAHW,OAAO,WACP;IAAE,QAAQ,EAAE,MAAM,CAAA;CAAE,QA0B9B;AAED;;;;GAIG;AACH,wCAHW,OAAO,WACP;IAAE,QAAQ,EAAE,MAAM,CAAA;CAAE,QAoB9B;AAED;;;;;GAKG;AACH,sCAJW,OAAO,UACP,OAAO,WACP;IAAE,QAAQ,EAAE,MAAM,CAAC;IAAC,aAAa,CAAC,EAAE,MAAM,CAAA;CAAE,QAqBtD;AAED;;;;GAIG;AACH,yCAHW,OAAO,WACP;IAAE,QAAQ,EAAE,MAAM,CAAA;CAAE,QAkB9B"}
|