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) — selects selectedShipId and the setRunDef action.
  • pickTwoPostings, MissionPosting type (@starship-survivors/data/mission-postings) — sources the two postings shown.
  • PLANETS (@starship-survivors/data/planet-config) — looked up by posting.planetId to display the human-readable planet name; falls back to the raw planetId if absent.
  • assembleRunDef (@starship-survivors/services/assembleRunService) — invoked on accept to produce the runDef for 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

  • setRunDef on 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 useState initializer.
  • Does not validate or guard against a missing selectedShipId — passes whatever the store holds into assembleRunDef.
  • 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 — assembleRunDef and navigation are fire-and-forget.

Signals

  • User taps ACCEPT on a card → onAccept(posting)handleAccept runs assembleRunDef, calls setRunDef, then navigate('/games/starship-survivors/play').
  • User taps ← HOME link → react-router Link to /.
  • No keyboard handlers, no focus management beyond default button behavior. aria-label on 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 lazy useState initializer; ISO UTC timestamp is computed once via useMemo.

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 via useMemo with empty deps for stability across re-renders.
  • Cards are laid out via a flex-wrap row in a <main>; each card has flex: 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 hooks med-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-postings noting the data module may need to be created, (2) on PLANETS lookup noting the index signature may need adjustment for PlanetId, and (3) on the assembleRunDef call noting the arg name may differ from HubScreen’s usage. The assembleRunDef call uses a Parameters<typeof assembleRunDef>[0] cast to bypass strict typing on the argument shape.
  • Background uses var(--med-panel-recessed); header and footer use var(--med-panel-raised) with var(--med-border) separators and a var(--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 references CYGNUS NET MERCENARY EXCHANGE and OUTER-RIM COMPACT §7.