PRIZM 4.0DSTA
Viewing C3 design language
RC3 · Components

Platform detail

Vertical card showing the deep state of a single platform — the master-detail companion to platform roster. Composes naturally to the right of a roster: operator scans the list, selects a row, this panel surfaces everything else.

Live

Six fixtures across UXV domains (UAV / UGV / UUV / USV), then a lost-link state and a strike platform demonstrating the indicator alphabet. Each section renders only when its props are passed.

Full aerial fixture — every section populated. The master-detail companion to the fleet-overview roster when this platform is selected.
UAV-01UAVDELEGATED
Comms
LinkLINK
Signal4/4
Battery88%
Telemetry
Position01°20'58"N 103°49'13"E
Heading120°
Speed22 kn
ALT4000 ft AGL
Mission
StepWPT-04 · 4/12
OperatorMAJ A. LOH
Last contact0.3 s ago
Ground platform — ELEV reads not ALT; minimal telemetry; no mission step (operator is manually driving).
UGV-04UGVSUPERVISED
Comms
LinkLINK
Signal4/4
Battery78%
Telemetry
Heading048°
Speed3 m/s
Mission
OperatorWO2 S. TAN
Last contact0.2 s ago
Underwater platform — DEPTH reads instead of ALT; speed in m/s; operator is SYSTEM AI (autonomous mode).
UUV-11UUVAUTONOMOUS
Comms
LinkLINK
Signal3/4
Battery64%
Telemetry
Position01°15'30"N 103°52'40"E
Heading092°
Speed2 m/s
DEPTH85 m
Mission
StepTRANSECT C · 7/9
OperatorSYSTEM AI
Last contact1.1 s ago
Surface platform — DEPTH reads water-under-keel; degraded link surfaces semantic colour on LINK. Endurance uses `CapacityBar` for the diesel reserve, Sensors uses `StateDot` — two indicator primitives in one card.
USV-02USVSUPERVISED
Comms
LinkDEGRADED
Signal2/4
Battery54%
Telemetry
Position01°16'42"N 103°47'10"E
Heading305°
Speed6 kn
DEPTH18 m BLW
Mission
StepPATROL B · 3/6
OperatorLTA H. WONG
Endurance
Fuel62%
Sensors
SonarActive
RadarStandby
Last contact0.6 s ago
Lost-link state — sparse data, lastContact stretches, low battery flagged. The detail panel still anchors the platform identity even when most fields are absent.
UAV-04UAVMANUAL
Comms
LinkLOST
Signal0/4
Battery18%
Last contact47 s ago
Strike platform with `extras` — Payload and Sensors sections compose `PipCount`, `StateText`, and `StateDot` into row values so payload and sensor state read as instruments, not flat text.
UAV-07UAVDELEGATED
Comms
LinkLINK
Signal4/4
Battery72%
Telemetry
Position01°22'14"N 103°50'40"E
Heading215°
Speed28 kn
ALT3500 ft AGL
Mission
StepSTRIKE A · 2/5
OperatorCPT NG
Payload
Munition4/4AGM-114
StatusARMED
Sensors
EO/IRTracking
RadarStandby
Last contact0.3 s ago

Anatomy

Header + three labelled sections (Comms, Telemetry, Mission), plus an optional last- contact footer. Each row is a label + value pair in mono. The header carries the Ember platform marker; rows stay neutral except for semantic-coded link / battery values.

Header

Ember-dotted platform identifier, optional class tag, optional current-autonomy summary on the right. Always present.

Comms section

LINK label (semantic colour), signal bars next to the count, battery gauge next to the percent. Surfaces only if at least one is passed.

Telemetry section

POSITION, HEADING (with a small rotating dial alongside the bearing), SPEED, vertical (ALT/ELEV/DEPTH from domain). Each row optional.

Mission section

Mission step progress (e.g. WPT-04 · 4/12) and active operator. Together they answer 'what is this platform doing and who's responsible.'

Extras

Domain-specific rows surfaced between Mission and Last contact — payload, sensor health, fuel, munitions. Consecutive entries with the same section group under one header.

Indicator alphabet

PipCount (discrete inventory), CapacityBar (continuous percentage), StateDot (active / standby / off), StateText (binary safety-critical state) compose into extras[].value so payload, endurance, and sensor rows read as instruments rather than flat text. See the Indicators section below.

Fit-and-scroll

Pass fillHeight to opt into internal scrolling. The header and last-contact footer stay anchored; the body (Comms / Telemetry / Mission / extras) scrolls within the frame. Use when composing inside a height-constrained container so a card with many extras doesn't push the parent layout. Without it, the card stays intrinsic-height.

Last contact footer

Time since last heartbeat. Anchors invariant 5 — when contact ages out, the operator sees it before drawing conclusions from stale data.

Master-detail compositing

Designed to sit next to platform roster — roster has the active-platform state, detail receives the same id and renders the deep state.

Indicators

Four pack-internal primitives extend the indicator alphabet beyond the built-in signal / battery / heading. Pass them through extras[].value so payload, endurance, sensor, and safety-critical rows read as instruments rather than flat text. Imported from @/components/rc3/indicators.

PipCount

Discrete inventory — munition rounds, smoke cartridges, comms-relay drops. Pips stay countable; a smooth gauge would round away the operationally-meaningful unit.

3/4AGM-114
<PipCount filled={3} total={4} suffix="AGM-114" />
CapacityBar

Continuous percentage — fuel reserve, comms-relay buffer, mission completion, tank level. Pair with text suffix carrying the value or remaining-time. Tone is explicit since 'low = bad' is not universal.

62%34%12%
<CapacityBar pct={62} suffix="62%" />
StateDot

Three-state operational status — active / standby / off. Colour-coded dot paired with the state text. Use for sensor channels, sub-system health, secondary modes.

TrackingStandbyOffline
<StateDot state="active">Tracking</StateDot>
StateText

Binary safety-critical state — ARMED / SAFE, WEAPONS HOT / COLD. Colour-coded mono text, no border or pill. Matches the LINK / LOST family already in Comms.

ARMEDSAFE
<StateText tone="danger">ARMED</StateText>

Pick the indicator that matches the data's shape — pips for discrete countable quantities, capacity bar for continuous percentages, dots for ternary state, semantic text for binary safety-critical state. Don't reach for chrome decoration (corner brackets, bordered pills, glow) to signal “tactical”; the instrument reads tactical because the encoding is honest.

Props

PropTypeDefaultDescription
platformstringPlatform identifier — e.g. "UAV-01". Ember-dotted header.
klassstringClass tag — UGV / UAV / USV / UUV.
domain"aerial" | "ground" | "surface" | "underwater"Drives the vertical concept label (ALT / ELEV / DEPTH).
link"good" | "degraded" | "lost"Link state. Surfaces in the Comms section as a semantic-coloured LINK label.
signal0 | 1 | 2 | 3 | 4Signal bars count.
batterynumberBattery percent (0–100). Colour-coded — success > 50, warning 20–50, danger < 20.
autonomystringCurrent autonomy rung label — e.g. "DELEGATED". Surfaces in the header.
positionstringConsumer-formatted coordinates — e.g. 01°20'58"N 103°49'13"E.
headingnumberHeading in degrees (0–359). Three-digit padded.
speednumberSpeed value.
speedUnit"m/s" | "km/h" | "kn""m/s"Speed unit.
verticalnumberVertical position. Label resolves from domain.
verticalUnit"m" | "ft""m"Vertical unit.
verticalRefstringReference marker — e.g. "AGL", "MSL", "BLW".
missionMissionStepMission step progress — { current, total, label? }.
operatorstringActive operator — who is currently driving / supervising.
lastContactstringTime since last heartbeat — e.g. "0.3 s ago". Surfaces in the footer.
extrasPlatformDetailExtra[]Domain-specific rows surfaced between Mission and Last contact. Consecutive entries with the same section group under one header; an entry without a section merges into the immediately-preceding section.
fillHeightbooleanfalseOpt into fit-and-scroll. Root takes parent's full height; header and last-contact footer stay anchored; the body section scrolls within the frame. Use inside height-constrained containers.
classNamestringForwarded to the root container.

Types

MissionStep

Mission progress. The detail renders as `{label} · {current}/{total}` when label is provided, otherwise just the count.

ts
interface MissionStep {
  current: number;   // current step, 1-indexed
  total: number;     // total steps
  label?: string;    // optional step label, e.g. "WPT-04"
}
PlatformDetailExtra

Shape of one entry in `extras`. Omit `section` to merge a row into the preceding section.

ts
interface PlatformDetailExtra {
  section?: string;     // section header (consecutive same-section entries group)
  label: string;        // row label
  value: ReactNode;     // row value
}

Wiring

Read-only. Pass the active platform's deep state. Compose to the right of platform roster — roster passes the selected id up to the consumer's state machine, the consumer looks up the platform's full record and passes the fields to this organism. Pass `extras` for domain-specific rows (payload, sensors, fuel) that the base props don't cover.

tsx
import {
  CapacityBar,
  PipCount,
  StateDot,
  StateText,
} from "@/components/rc3/indicators";

const active = fleet.find((p) => p.id === activeId);

<div className="flex gap-4">
  <PlatformRoster
    platforms={fleet.map(toRosterEntry)}
    activeId={activeId}
    onSelect={setActiveId}
  />
  {active && (
    <PlatformDetail
      platform={active.id}
      klass={active.klass}
      domain={active.domain}
      link={active.link}
      signal={active.signal}
      battery={active.battery}
      autonomy={active.autonomy}
      position={active.coords}
      heading={active.heading}
      speed={active.speed}
      speedUnit={active.speedUnit}
      vertical={active.altitude}
      verticalUnit={active.altitudeUnit}
      verticalRef={active.altitudeRef}
      mission={active.mission}
      operator={active.operator}
      lastContact={active.lastContact}
      extras={[
        {
          section: "Payload",
          label: "Munition",
          value: <PipCount filled={4} total={4} suffix="AGM-114" />,
        },
        {
          section: "Payload",
          label: "Status",
          value: <StateText tone="danger">ARMED</StateText>,
        },
        {
          section: "Endurance",
          label: "Fuel",
          value: <CapacityBar pct={62} suffix="62%" />,
        },
        {
          section: "Sensors",
          label: "EO/IR",
          value: <StateDot state="active">Tracking</StateDot>,
        },
      ]}
      fillHeight
    />
  )}
</div>

Signal bars, battery gauge, and heading dial appear automatically next to their text values when signal, battery, and heading are passed — no separate prop. Text stays as the primary read; the indicators are a glanceable secondary read.

Set fillHeight when composing inside a height-constrained container (e.g. a sidebar column with flex-1 min-h-0): the card grows to fill the available height and the body section scrolls within the frame, so a card with many extras doesn't push the parent layout. Without it the card stays intrinsic-height.

Behavioural rule

Invariant Three

Active context unambiguous

The Ember-dotted header marks which platform this detail is for. When the operator selects a different row in the roster, this card swaps wholesale — never a partial update that could leave fields from the previous platform showing.

Read invariant

Accessibility

Region roleThe root carries `role="region"` with an `aria-label` of the form `Platform detail for {platform}`.
Decorative partsThe Ember dot in the header is `aria-hidden`. Row labels and values carry the meaning.
Section labelsComms / Telemetry / Mission are visible mono section headers, not just heading roles — they read consistently across operator surfaces.
Colour and meaningLink and battery state colour is paired with text. Operators with colour-vision differences still read state.
Last contactSurfaces honestly when contact ages — pair with consumer logic that classifies the value (e.g. show in danger after a threshold). The organism doesn't classify the age; that's domain-specific.

JavaFX

Ships in the PRIZM JavaFX library for thick-client C3 apps as Rc3PlatformDetail (extends VBox). Run the gallery to see it natively.

java
import design.prizm.fx.rc3.Rc3PlatformDetail;

Rc3PlatformDetail()
Rc3PlatformDetail(String platform)
MemberTypeDefaultDescription
LinkStatus / UxvDomainenumComms status + vertical label (ALT / ELEV / DEPTH).
MissionSteprecord(int current, int total, String label)label optional.
Extrarecord(String section, String label, Node value)Domain-specific row; consecutive same-section entries group. value is any Node — pass an Rc3Indicators primitive (or a String for plain text).
setLink / setSignal / setBattery→ voidComms section — signal bars + battery gauge.
setPosition / setHeading / setSpeed / setVertical→ voidTelemetry section — heading dial.
setMission / setOperator→ voidMission section.
setExtras / setLastContact→ voidExtra rows + last-contact footer.
setFillHeight(boolean) → voidfalsePins header + footer, scrolls the body.

Master-detail companion to Rc3PlatformRoster; honours invariant 3. The Rc3Indicators alphabet (pipCount / capacityBar / stateDot / stateText) composes into extra values so payload / sensor state reads as instruments. Mirrors components/rc3/platform-detail.tsx.

Usage

Compose as the master-detail companion to platform roster. Pass only the fields the platform actually surfaces — the card stays useful even when most fields are absent (a lost-link platform shows the identifier, lastContact age, and whatever final state was captured before contact dropped). Don't carry stale values forward when contact is lost: omit fields whose freshness can't be guaranteed.