feat(ui): active-tasks strip bottom-left — active first, pending dimmed (#44)

This commit is contained in:
2026-10-02 21:02:37 +00:00
parent 1cabea7972
commit f1b5645e9d
2 changed files with 61 additions and 0 deletions
+7
View File
@@ -16,6 +16,7 @@ import { MissionPanel } from '@/components/missions'
import { NextTurnButton } from '@/components/next-turn-button'
import { ShipSystemsPanel } from '@/components/ship-systems'
import { SkillTreePanel } from '@/components/skill-tree'
import { TaskStrip } from '@/components/task-strip'
import { WorldMapPanel } from '@/components/world-map'
import { CREW_CAPACITY } from '@/lib/game/ship-costs'
import type { CrewState, SystemState } from '@/lib/game/save'
@@ -562,6 +563,12 @@ export function MissionControl() {
stays visible on every tab (Radix unmounts inactive tabs — the button only refreshes
the snapshots this shell owns) */}
<NextTurnButton turn={ship?.turn ?? null} onTicked={refreshAll} />
{/* #44 — the active-tasks strip: fixed bottom-left (same mount convention as the button —
outside the tab grid so it stays visible on every tab). Data: the shell's system
snapshot (JC-15) — refreshAll re-runs /api/system on every turn, so it updates on
every shell refresh with no fetch of its own. */}
<TaskStrip tasks={system?.tasks ?? []} />
</div>
)
}
+54
View File
@@ -0,0 +1,54 @@
'use client'
import type { QueueDto } from '@/lib/game/save'
// TASK STRIP — the save's task queue, fixed bottom-left next to the NEXT TURN button (#44).
// The array arrives pre-ordered from the snapshot builder (Task 6: ACTIVE block, then PENDING
// block, each by (queuedAtTurn, id)) — the strip renders it as-is, dimming the PENDING rows,
// and never fetches or re-sorts. Empty queue → null, no box at all. The shell re-renders it on
// every refreshAll (Task 7) — a pure display of the array it is handed.
export function TaskStrip({ tasks }: { tasks: QueueDto[] }) {
if (tasks.length === 0) return null
return (
<div
style={{
position: 'fixed',
left: '1rem',
bottom: '1rem',
zIndex: 60, // above the header/footer's 50
background: 'var(--surface)',
border: '1px solid var(--border)',
padding: '0.5rem 0.75rem',
maxWidth: '26rem',
}}
>
<span style={{ display: 'block', fontSize: '0.6rem', color: 'var(--text-muted)', letterSpacing: '0.12em', marginBottom: '0.3rem' }}>
ACTIVE TASKS
</span>
{tasks.map((t) => (
<div
key={t.id}
style={{
display: 'flex',
justifyContent: 'space-between',
gap: '0.75rem',
padding: '0.2rem 0',
opacity: t.status === 'PENDING' ? 0.45 : 1, // dim, don't reorder — the array is the order
}}
>
<span style={{ fontSize: '0.62rem', color: 'var(--text-secondary)', letterSpacing: '0.06em' }}>
{t.type} // {t.targetLabel}
</span>
<span style={{ fontSize: '0.62rem', color: 'var(--text-muted)', letterSpacing: '0.04em', whiteSpace: 'nowrap' }}>
{t.leadName}
{t.helperNames.length > 0 ? ` +${t.helperNames.join(' +')}` : ''}
{' — '}
{t.status === 'ACTIVE' ? `${t.remaining} turn(s) left` : 'queued'}
</span>
</div>
))}
</div>
)
}