Add a "Global Max Concurrent" slider to the footer engine menu and the Command Center Concurrency card, and group the Scheduling settings by Global vs Project scope so the global cap isn't mistaken for a per-project setting (clearer on mobile). Both sliders are backed by a single shared `useGlobalConcurrency` hook (module-level store) so they read/write one source of truth and revalidate after every PUT /api/global-concurrency — fixing the last-writer-wins and stale-clobber races a per-component cache would cause. The hook treats a fetch error as non-interactive (slider disabled, not stuck at 1), surfaces a save-state indicator, and flushes a pending edit on menu close / unmount so a quick drag-then-dismiss is never silently dropped. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
133 lines
3.1 KiB
CSS
133 lines
3.1 KiB
CSS
.engine-control-menu {
|
|
position: relative;
|
|
display: inline-flex;
|
|
align-items: center;
|
|
}
|
|
|
|
.engine-control-menu__trigger {
|
|
color: var(--text-muted);
|
|
}
|
|
|
|
.engine-control-menu__trigger:hover {
|
|
color: var(--text);
|
|
}
|
|
|
|
.engine-control-menu__popover {
|
|
position: absolute;
|
|
right: 0;
|
|
bottom: calc(100% + var(--space-xs));
|
|
z-index: 70;
|
|
width: min(24rem, calc(100vw - (var(--space-lg) * 2)));
|
|
max-height: min(32rem, calc(100vh - var(--space-2xl)));
|
|
overflow: auto;
|
|
padding: var(--space-md);
|
|
display: flex;
|
|
flex-direction: column;
|
|
gap: var(--space-md);
|
|
background: var(--card);
|
|
border: 1px solid var(--border);
|
|
border-radius: var(--radius-lg);
|
|
box-shadow: var(--shadow-lg);
|
|
color: var(--text);
|
|
}
|
|
|
|
.engine-control-menu__section {
|
|
display: flex;
|
|
flex-direction: column;
|
|
gap: var(--space-sm);
|
|
}
|
|
|
|
.engine-control-menu__section--actions {
|
|
gap: var(--space-xs);
|
|
}
|
|
|
|
.engine-control-menu__action {
|
|
width: 100%;
|
|
justify-content: flex-start;
|
|
gap: var(--space-sm);
|
|
}
|
|
|
|
.engine-control-menu__action:disabled {
|
|
opacity: 0.5;
|
|
cursor: not-allowed;
|
|
}
|
|
|
|
.engine-control-menu__section-header,
|
|
.engine-control-menu__slider-label {
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: space-between;
|
|
gap: var(--space-md);
|
|
}
|
|
|
|
.engine-control-menu__section-header {
|
|
color: var(--text);
|
|
font-weight: 600;
|
|
}
|
|
|
|
.engine-control-menu__save-state {
|
|
color: var(--text-muted);
|
|
font-size: var(--font-size-xs);
|
|
font-weight: 500;
|
|
}
|
|
|
|
/* FNXC:GlobalConcurrencyControls 2026-06-25-22:45: "All projects" scope caption sits between the global-cap title and its save-state indicator; muted so the save-state remains the emphasized signal. */
|
|
.engine-control-menu__scope-caption {
|
|
margin-inline-start: auto;
|
|
color: var(--text-muted);
|
|
font-size: var(--font-size-xs);
|
|
font-weight: 500;
|
|
}
|
|
|
|
.engine-control-menu__save-state--saving {
|
|
color: var(--color-warning);
|
|
}
|
|
|
|
.engine-control-menu__save-state--saved {
|
|
color: var(--color-success);
|
|
}
|
|
|
|
.engine-control-menu__save-state--error,
|
|
.engine-control-menu__error {
|
|
color: var(--color-error);
|
|
}
|
|
|
|
/* FNXC:GlobalConcurrencyControls 2026-06-25-14:10: The global cap is a cross-project setting; separate it visually from the per-project sliders below with a divider and an "All projects" caption. */
|
|
.engine-control-menu__section--global {
|
|
padding-bottom: var(--space-sm);
|
|
border-bottom: 1px solid var(--border);
|
|
}
|
|
|
|
.engine-control-menu__slider {
|
|
display: flex;
|
|
flex-direction: column;
|
|
gap: var(--space-xs);
|
|
color: var(--text-muted);
|
|
}
|
|
|
|
.engine-control-menu__slider-label strong {
|
|
color: var(--text);
|
|
font-family: var(--font-mono);
|
|
}
|
|
|
|
.engine-control-menu__range {
|
|
width: 100%;
|
|
accent-color: var(--accent);
|
|
}
|
|
|
|
.engine-control-menu__error {
|
|
margin: 0;
|
|
font-size: var(--font-size-xs);
|
|
}
|
|
|
|
@media (max-width: 768px) {
|
|
.engine-control-menu__popover {
|
|
position: fixed;
|
|
left: var(--space-sm);
|
|
right: var(--space-sm);
|
|
bottom: calc(var(--mobile-nav-height) + max(env(safe-area-inset-bottom, 0px), var(--space-md)) + var(--space-2xl));
|
|
width: auto;
|
|
max-height: min(28rem, calc(100vh - var(--mobile-nav-height) - var(--space-2xl) - var(--space-lg)));
|
|
}
|
|
}
|