Replace the redundant footer AI session control with progress and action visibility in the session notification banner. - Delete the BackgroundTasksIndicator component, styles, footer wiring, and obsolete translations. - Surface non-planning generating and retained error sessions through the notification banner while leaving Planning in its dedicated view. - Update session lifecycle, localization, documentation, and regression coverage. Files changed: .../fn-8229-remove-ai-background-tasks-pill.md | 7 + docs/dashboard-guide.md | 4 +- packages/dashboard/README.md | 1 - packages/dashboard/app/App.tsx | 28 +-- .../app/__tests__/css-hygiene-scan.test.ts | 2 - .../dashboard-component-color-tokenization.test.ts | 1 - packages/dashboard/app/api/legacy.ts | 2 +- .../app/components/BackgroundTasksIndicator.css | 130 ------------- .../app/components/BackgroundTasksIndicator.tsx | 210 --------------------- .../dashboard/app/components/ExecutorStatusBar.css | 3 - .../dashboard/app/components/ExecutorStatusBar.tsx | 30 +-- .../app/components/SessionNotificationBanner.css | 10 + .../app/components/SessionNotificationBanner.tsx | 52 ++++- .../app/components/SubtaskBreakdownModal.tsx | 7 +- .../app/components/__tests__/App.test.tsx | 11 +- .../__tests__/ExecutorStatusBar.test.tsx | 1 - .../__tests__/SessionNotificationBanner.test.tsx | 88 ++++----- .../components/__tests__/utility-mobile.test.tsx | 191 +------------------ .../app/components/dashboard/DashboardBanners.tsx | 8 +- .../dashboard/__tests__/DashboardBanners.test.tsx | 30 ++- .../hooks/__tests__/useBackgroundSessions.test.ts | 21 ++- .../dashboard/app/hooks/useBackgroundSessions.ts | 21 ++- .../app/utils/__tests__/appLifecycle.test.ts | 61 ++---- packages/dashboard/app/utils/appLifecycle.ts | 16 +- packages/dashboard/src/routes.ts | 2 +- packages/i18n/locales/en/app.json | 33 +--- packages/i18n/locales/es/app.json | 44 ++--- packages/i18n/locales/fr/app.json | 44 ++--- packages/i18n/locales/ko/app.json | 44 ++--- packages/i18n/locales/zh-CN/app.json | 44 ++--- packages/i18n/locales/zh-TW/app.json | 44 ++--- packages/i18n/src/resources.d.ts | 48 ++--- 32 files changed, 316 insertions(+), 922 deletions(-) Fusion-Task-Id: FN-8229 Fusion-Task-Lineage: a232fb04-08ff-4f0a-a440-e94545243e61 Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
481 lines
12 KiB
CSS
481 lines
12 KiB
CSS
/* === Executor Status Bar === */
|
|
|
|
/**
|
|
* Footer status bar that displays real-time executor statistics.
|
|
* Fixed at bottom of viewport, only visible in project view.
|
|
*
|
|
* FNXC:FooterChrome 2026-06-22-18:00:
|
|
* The bottom executor footer must sit flush against main content without visible divider chrome; keep the draggable/layout affordances elsewhere, but do not draw a top border between content and footer or internal separator lines between footer groups.
|
|
*/
|
|
.executor-status-bar {
|
|
position: fixed;
|
|
/* See MobileNavBar.css — ICB compensation for Android Chrome's inflated
|
|
initial containing block. Defaults to 0px on healthy browsers. */
|
|
bottom: var(--icb-bottom-offset, 0px);
|
|
left: 0;
|
|
right: var(--icb-right-offset, 0px);
|
|
z-index: 50;
|
|
display: flex;
|
|
align-items: center;
|
|
gap: var(--space-sm);
|
|
padding: var(--space-xs) var(--space-lg);
|
|
background: var(--surface);
|
|
border-top: none;
|
|
font-size: 12px;
|
|
color: var(--text-muted);
|
|
height: 36px;
|
|
box-sizing: border-box;
|
|
}
|
|
|
|
/* State variants */
|
|
.executor-status-bar--error {
|
|
background: var(--executor-status-error-bg);
|
|
}
|
|
|
|
.executor-status-bar--connecting,
|
|
.executor-status-bar--loading {
|
|
background: var(--surface);
|
|
}
|
|
|
|
/* Individual stat segment */
|
|
.executor-status-bar__segment {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: var(--space-xs);
|
|
white-space: nowrap;
|
|
}
|
|
|
|
.executor-status-bar__segment--time {
|
|
color: var(--text-dim);
|
|
flex: 0 1 auto;
|
|
min-width: 0;
|
|
}
|
|
|
|
/*
|
|
FNXC:Terminal 2026-06-21-22:13:
|
|
FN-6887 makes the footer status bar the canonical desktop/tablet terminal launcher surface. Mobile continues to use MobileNavBar's More sheet, so this segment is hidden at the mobile breakpoint.
|
|
*/
|
|
.executor-status-bar__segment--terminal-launcher {
|
|
flex-shrink: 0;
|
|
}
|
|
|
|
.executor-status-bar__segment--quick-chat-launcher {
|
|
flex-shrink: 0;
|
|
}
|
|
|
|
.executor-status-bar__footer-launcher {
|
|
display: inline-flex;
|
|
align-items: center;
|
|
gap: var(--space-xs);
|
|
min-height: calc(var(--space-lg) + var(--space-xs));
|
|
padding: 0 var(--space-xs);
|
|
border: none;
|
|
background: transparent;
|
|
/*
|
|
* FNXC:FooterLaunchers 2026-06-24-00:00:
|
|
* Quick Chat and Terminal are peer footer launchers. Pin both to the footer's compact UI font and color token so switching the launcher location does not make one control read heavier or dimmer than the other.
|
|
*/
|
|
color: inherit;
|
|
font-family: var(--font-primary);
|
|
font-size: inherit;
|
|
font-weight: 500;
|
|
line-height: 1;
|
|
white-space: nowrap;
|
|
cursor: pointer;
|
|
}
|
|
|
|
.executor-status-bar__footer-launcher:hover {
|
|
color: var(--text);
|
|
}
|
|
|
|
.executor-status-bar__footer-launcher:hover span {
|
|
text-decoration: underline;
|
|
text-underline-offset: calc(var(--space-xs) / 2);
|
|
}
|
|
|
|
.executor-status-bar__footer-launcher:focus-visible {
|
|
outline: none;
|
|
box-shadow: var(--focus-ring-strong);
|
|
border-radius: var(--radius-sm);
|
|
}
|
|
|
|
.executor-status-bar__segment--stuck {
|
|
color: var(--color-error);
|
|
}
|
|
|
|
.executor-status-bar__segment--fanout {
|
|
color: var(--color-error);
|
|
min-width: 0;
|
|
}
|
|
|
|
/* Colored dot indicator */
|
|
/* Colored dot indicator */
|
|
.executor-status-bar__indicator {
|
|
width: 6px;
|
|
height: 6px;
|
|
border-radius: 50%;
|
|
flex-shrink: 0;
|
|
/* Force GPU compositing to keep indicator in sync with parent during scroll */
|
|
transform: translateZ(0);
|
|
}
|
|
|
|
.executor-status-bar__indicator--running {
|
|
background: var(--in-progress);
|
|
}
|
|
|
|
.executor-status-bar__indicator--running.executor-status-bar__indicator--active {
|
|
background: var(--in-progress);
|
|
animation: executor-pulse 1.5s ease-in-out infinite;
|
|
}
|
|
|
|
.executor-status-bar__indicator--blocked {
|
|
background: var(--triage);
|
|
}
|
|
|
|
.executor-status-bar__indicator--blocked.executor-status-bar__indicator--active {
|
|
background: var(--triage);
|
|
}
|
|
|
|
.executor-status-bar__indicator--stuck {
|
|
background: var(--color-error);
|
|
}
|
|
|
|
.executor-status-bar__indicator--stuck.executor-status-bar__indicator--active {
|
|
animation: executor-pulse 1s ease-in-out infinite;
|
|
}
|
|
|
|
.executor-status-bar__indicator--queued {
|
|
background: var(--todo);
|
|
}
|
|
|
|
.executor-status-bar__indicator--review {
|
|
background: var(--in-review);
|
|
}
|
|
|
|
.executor-status-bar__indicator--fanout {
|
|
background: var(--color-error);
|
|
}
|
|
|
|
.executor-status-bar__indicator--connecting {
|
|
background: var(--color-warning);
|
|
}
|
|
|
|
.executor-status-bar__indicator--connecting.executor-status-bar__indicator--active {
|
|
animation: executor-pulse 1.5s ease-in-out infinite;
|
|
}
|
|
|
|
/* Numeric count display */
|
|
.executor-status-bar__count {
|
|
font-family: var(--font-mono);
|
|
font-weight: 600;
|
|
color: var(--text);
|
|
line-height: 1;
|
|
}
|
|
|
|
.executor-status-bar__count--warning {
|
|
color: var(--triage);
|
|
}
|
|
|
|
.executor-status-bar__count--error {
|
|
color: var(--color-error);
|
|
}
|
|
|
|
/* Label text */
|
|
.executor-status-bar__label {
|
|
color: var(--text-muted);
|
|
line-height: 1;
|
|
}
|
|
|
|
/* Mobile mode is component-owned so short landscape phones hide labels and
|
|
expose tap targets under the same viewport contract. */
|
|
.executor-status-bar--mobile .executor-status-bar__label {
|
|
display: none;
|
|
}
|
|
|
|
.executor-status-bar__stat-tooltip {
|
|
position: fixed;
|
|
z-index: var(--z-popover, 60);
|
|
transform: translate(-50%, calc(-100% - var(--space-sm)));
|
|
padding: var(--space-xs) var(--space-sm);
|
|
border-radius: var(--radius-sm);
|
|
background: var(--surface);
|
|
box-shadow: var(--shadow-lg);
|
|
color: var(--text);
|
|
font-size: inherit;
|
|
line-height: 1;
|
|
pointer-events: none;
|
|
white-space: nowrap;
|
|
}
|
|
|
|
.executor-status-bar__segment--stat {
|
|
border: none;
|
|
background: transparent;
|
|
color: inherit;
|
|
font: inherit;
|
|
padding: 0;
|
|
text-align: inherit;
|
|
}
|
|
|
|
.executor-status-bar--mobile .executor-status-bar__segment--stat {
|
|
min-width: var(--executor-status-touch-size, calc(var(--space-lg) * 2 + var(--space-xs)));
|
|
min-height: var(--executor-status-touch-size, calc(var(--space-lg) * 2 + var(--space-xs)));
|
|
justify-content: center;
|
|
cursor: pointer;
|
|
}
|
|
|
|
.executor-status-bar__segment--stat:focus-visible {
|
|
outline: none;
|
|
box-shadow: var(--focus-ring-strong);
|
|
border-radius: var(--radius-sm);
|
|
}
|
|
|
|
/* Max concurrent display */
|
|
.executor-status-bar__max {
|
|
font-family: var(--font-mono);
|
|
font-weight: 600;
|
|
color: var(--text-dim);
|
|
line-height: 1;
|
|
}
|
|
|
|
.executor-status-bar__fanout-summary {
|
|
color: var(--text);
|
|
font-family: var(--font-mono);
|
|
font-weight: 600;
|
|
max-width: 20ch;
|
|
overflow: hidden;
|
|
text-overflow: ellipsis;
|
|
}
|
|
|
|
/* Separator between count and max */
|
|
.executor-status-bar__separator {
|
|
font-family: var(--font-mono);
|
|
font-weight: 600;
|
|
color: var(--text-dim);
|
|
margin: 0 2px;
|
|
line-height: 1;
|
|
}
|
|
|
|
/* Divider between segments */
|
|
.executor-status-bar__divider {
|
|
display: none;
|
|
}
|
|
|
|
/* Project directory toggle/link */
|
|
.executor-status-bar__segment--project-directory {
|
|
min-width: 0;
|
|
flex-shrink: 1;
|
|
}
|
|
|
|
.executor-status-bar__folder-toggle {
|
|
--executor-status-touch-size: calc(var(--space-lg) * 2 + var(--space-xs));
|
|
display: inline-flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
padding: 0;
|
|
border: none;
|
|
background: transparent;
|
|
color: var(--text-muted);
|
|
cursor: pointer;
|
|
line-height: 1;
|
|
transform: translate(-2px, -1px);
|
|
transition: color var(--transition-fast);
|
|
}
|
|
|
|
/* Mobile keeps a tappable 40-ish px target. Desktop collapses to the icon
|
|
so it aligns on the same baseline as the surrounding status-bar text. */
|
|
@media (pointer: coarse), (max-width: 768px) {
|
|
.executor-status-bar__folder-toggle {
|
|
min-width: var(--executor-status-touch-size);
|
|
min-height: var(--executor-status-touch-size);
|
|
}
|
|
}
|
|
|
|
.executor-status-bar__folder-toggle:hover {
|
|
color: var(--text);
|
|
}
|
|
|
|
.executor-status-bar__folder-toggle:focus-visible,
|
|
.executor-status-bar__project-path:focus-visible {
|
|
outline: none;
|
|
box-shadow: var(--focus-ring-strong);
|
|
border-radius: var(--radius-sm);
|
|
}
|
|
|
|
.executor-status-bar__folder-toggle--active {
|
|
color: var(--todo);
|
|
}
|
|
|
|
.executor-status-bar__project-path {
|
|
min-width: 0;
|
|
max-width: min(44ch, 34vw);
|
|
min-height: var(--executor-status-touch-size, calc(var(--space-lg) * 2 + var(--space-xs)));
|
|
display: inline-flex;
|
|
align-items: center;
|
|
border: none;
|
|
background: transparent;
|
|
color: var(--text);
|
|
cursor: pointer;
|
|
font-size: inherit;
|
|
line-height: 1;
|
|
white-space: nowrap;
|
|
overflow: hidden;
|
|
text-overflow: ellipsis;
|
|
text-align: left;
|
|
text-decoration: underline;
|
|
text-decoration-color: color-mix(in srgb, var(--text-muted) 70%, transparent);
|
|
text-underline-offset: calc(var(--space-xs) / 2);
|
|
padding: 0 var(--space-xs);
|
|
}
|
|
|
|
.executor-status-bar__project-path:hover {
|
|
color: var(--todo);
|
|
text-decoration-color: var(--todo);
|
|
}
|
|
|
|
/* Spacer to push right-aligned elements */
|
|
.executor-status-bar__spacer {
|
|
flex: 1;
|
|
}
|
|
|
|
/* Time display */
|
|
.executor-status-bar__time {
|
|
color: var(--text-dim);
|
|
overflow: hidden;
|
|
text-overflow: ellipsis;
|
|
white-space: nowrap;
|
|
}
|
|
|
|
.executor-status-bar__icon {
|
|
color: var(--text-dim);
|
|
flex-shrink: 0;
|
|
}
|
|
|
|
/* Executor state badge */
|
|
.executor-status-bar__state {
|
|
font-weight: 600;
|
|
}
|
|
|
|
.executor-status-bar__segment--engine-controls {
|
|
position: relative;
|
|
}
|
|
|
|
.executor-status-bar__state-trigger {
|
|
display: inline-flex;
|
|
align-items: center;
|
|
gap: var(--space-xs);
|
|
padding: 0;
|
|
border: none;
|
|
background: transparent;
|
|
color: inherit;
|
|
cursor: pointer;
|
|
font: inherit;
|
|
line-height: 1;
|
|
}
|
|
|
|
.executor-status-bar__state-trigger:hover .executor-status-bar__state {
|
|
text-decoration: underline;
|
|
text-underline-offset: calc(var(--space-xs) / 2);
|
|
}
|
|
|
|
.executor-status-bar__state-trigger:focus-visible {
|
|
outline: none;
|
|
box-shadow: var(--focus-ring-strong);
|
|
border-radius: var(--radius-sm);
|
|
}
|
|
|
|
/* Error message */
|
|
.executor-status-bar__error,
|
|
.executor-status-bar__connecting {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 6px;
|
|
}
|
|
|
|
.executor-status-bar__error {
|
|
color: var(--color-error);
|
|
}
|
|
|
|
.executor-status-bar__connecting {
|
|
color: var(--color-warning);
|
|
}
|
|
|
|
/* Loading message */
|
|
.executor-status-bar__loading-text {
|
|
color: var(--text-muted);
|
|
}
|
|
|
|
/* Pulsing animation for active indicators */
|
|
@keyframes executor-pulse {
|
|
0%, 100% {
|
|
opacity: 1;
|
|
transform: scale(1);
|
|
}
|
|
50% {
|
|
opacity: 0.6;
|
|
transform: scale(1.2);
|
|
}
|
|
}
|
|
|
|
/* Responsive: collapse on small screens */
|
|
@media (max-width: 768px) {
|
|
/*
|
|
FNXC:MobileFooter 2026-06-23-22:10:
|
|
The footer's "last updated" freshness text is useful but lower priority than terminal/script access, chat launch, and status counters on narrow mobile. Hide the time segment first when mobile footer space is tight instead of letting it squeeze controls or counts.
|
|
*/
|
|
.executor-status-bar__segment--time {
|
|
display: none;
|
|
}
|
|
|
|
.executor-status-bar__fanout-summary {
|
|
max-width: 14ch;
|
|
}
|
|
/* Update the footer height token for mobile - must be on the wrapper, not on :root */
|
|
.project-content--with-footer {
|
|
--executor-footer-height: calc(var(--space-lg) * 2 + var(--space-xs));
|
|
}
|
|
|
|
.executor-status-bar {
|
|
padding: var(--space-xs) var(--space-md);
|
|
gap: 4px;
|
|
font-size: 11px;
|
|
height: calc(var(--space-lg) * 2 + var(--space-xs));
|
|
overflow: hidden;
|
|
/* Must match the mobile nav bar's effective height. MobileNavBar floors
|
|
the bottom inset to 12px (Android Chrome under-reports the inset while
|
|
its address bar is collapsing), so we have to use the same floor here
|
|
or the footer ends up drawn inside the nav's padding area. */
|
|
bottom: calc(var(--icb-bottom-offset, 0px) + var(--mobile-nav-height) + max(env(safe-area-inset-bottom, 0px), 12px) + var(--standalone-bottom-gap));
|
|
}
|
|
|
|
/* Keyboard-open override: ignore ICB offset so the keyboard can cover the
|
|
footer instead of displacing it upward by keyboard height. */
|
|
.executor-status-bar.executor-status-bar--keyboard-open {
|
|
bottom: 0;
|
|
}
|
|
|
|
.executor-status-bar__segment {
|
|
min-width: 0;
|
|
gap: 4px;
|
|
overflow: hidden;
|
|
}
|
|
|
|
.executor-status-bar__project-path {
|
|
max-width: min(26ch, 30vw);
|
|
}
|
|
|
|
}
|
|
|
|
/* Light theme support */
|
|
[data-theme="light"] .executor-status-bar {
|
|
background: var(--surface);
|
|
border-top: none;
|
|
}
|
|
|
|
[data-theme="light"] .executor-status-bar__count {
|
|
color: var(--text);
|
|
}
|
|
|
|
[data-theme="light"] .executor-status-bar__state {
|
|
color: var(--color-success);
|
|
}
|