PURPOSE
React screen rendering the job board / mercenary postings interface. Presents two randomly picked mission postings as cards, lets the player accept one, assembles a runDef, and routes into the play scene. Uses the unified white-panel <MedPanel> medical-UI aesthetic (Tick 77 PR 4/5), replacing the prior green-terminal monospace look. Per directive decisions/20260513-001.
OWNS
MissionBoardScreen— top-level exported screen component. Owns the two-posting selection (stable across re-renders), the mount-time UTC timestamp, and the accept handler.MissionCard— local component rendering one posting inside a<MedPanel variant="card">. Owns its own layout (header row, location line, objective headline, flavor blurb, payout line, posted-by attribution, accept button).DifficultyPill— local component rendering a colored, uppercase, bordered pill for a difficulty label.DIFFICULTY_VAR— module-local mapping from difficulty string to a medical CSS variable color token. Tiers:Routine,Standard,Hazardous,Critical,Black Flag. Falls back to--med-text-muted.
READS FROM
useSessionStore(@starship-survivors/stores/sessionStore) — selectsselectedShipIdand thesetRunDefaction.pickTwoPostings,MissionPostingtype (@starship-survivors/data/mission-postings) — sources the two postings shown.PLANETS(@starship-survivors/data/planet-config) — looked up byposting.planetIdto display the human-readable planet name; falls back to the rawplanetIdif absent.assembleRunDef(@starship-survivors/services/assembleRunService) — invoked on accept to produce therunDeffor the run.MedPanel(../components/MedPanel) — wrapping card surface.useNavigate,Link(react-router-dom) — routing primitives.- React hooks
useState,useMemo— for stable posting list and stable timestamp.
PUSHES TO
setRunDefon the session store — writes the assembled run definition before navigation.navigate('/games/starship-survivors/play')— react-router push into the play route after accept.- DOM render only otherwise; no other store mutations, no network calls, no telemetry.
DOES NOT
- Does not fetch mission data over the network; postings come from a static data module.
- Does not persist posting selection across navigation; postings are picked once on mount via
useStateinitializer. - Does not validate or guard against a missing
selectedShipId— passes whatever the store holds intoassembleRunDef. - Does not render the legacy green-terminal scanline overlay; it was removed with the medical-UI rollout.
- Does not mutate postings or filter by difficulty / faction / payout.
- Does not handle accept failure paths —
assembleRunDefand navigation are fire-and-forget.
Signals
- User taps
ACCEPTon a card →onAccept(posting)→handleAcceptrunsassembleRunDef, callssetRunDef, thennavigate('/games/starship-survivors/play'). - User taps
← HOMElink → react-routerLinkto/. - No keyboard handlers, no focus management beyond default button behavior.
aria-labelon the accept button identifies the mission and planet.
Entry points
- Exported as named export
MissionBoardScreen— mounted by the metagame router. No default export. - No props.
- Mount-time side effects:
pickTwoPostings()runs once via lazyuseStateinitializer; ISO UTC timestamp is computed once viauseMemo.
Pattern notes
- Postings are frozen at mount in a tuple-typed
useState<[MissionPosting, MissionPosting]>; the setter is intentionally discarded, so re-rolling requires re-mounting the screen. - Timestamp is
new Date().toISOString().replace('T', ' ').slice(0, 19) + ' UTC', computed viauseMemowith empty deps for stability across re-renders. - Cards are laid out via a flex-wrap row in a
<main>; each card hasflex: 1 1 0,minWidth: 280px,maxWidth: 520px. - Card key is composite:
${planetId}-${objectiveLabel}-${idx}— index is included to disambiguate hypothetical duplicate postings. - Styling is inline-style heavy and leans on the medical CSS variable set (
--med-*tokens), with class hooksmed-h1,med-h2,med-text,med-text-muted,med-btn,med-btn-primary,med-btn-ghost. - Difficulty color is resolved at render via
DIFFICULTY_VAR[difficulty] ?? 'var(--med-text-muted)'— unknown difficulties degrade gracefully rather than crashing. - File contains two TODO markers: (1) on the import of
mission-postingsnoting the data module may need to be created, (2) onPLANETSlookup noting the index signature may need adjustment forPlanetId, and (3) on theassembleRunDefcall noting the arg name may differ fromHubScreen’s usage. TheassembleRunDefcall uses aParameters<typeof assembleRunDef>[0]cast to bypass strict typing on the argument shape. - Background uses
var(--med-panel-recessed); header and footer usevar(--med-panel-raised)withvar(--med-border)separators and avar(--med-shadow-card)drop shadow on the header. - Header title literal:
JOB BOARD // CYGNUS NET v3.2. Preamble literal:INCOMING POSTINGS — 2 OF MANY. SELECT ASSIGNMENT.. Footer literal referencesCYGNUS NET MERCENARY EXCHANGEandOUTER-RIM COMPACT §7.