13 KiB
Mission Core application component architecture
Status: accepted, 2026-07-26
Decision
Mission Core adopts the same dependency discipline as the NODE.DC Design Guideline without copying its application code.
The Design Guideline remains the visual platform:
@nodedc/tokens
→ @nodedc/ui-core
→ @nodedc/ui-react
→ Mission Core application
Inside Mission Core, dependencies flow in one direction:
typed contracts and data adapters
→ reusable domain components and renderers
→ feature workspaces
→ product registry and reviewed composition roots
→ App shell
A lower layer does not import a higher layer. The application owns domain meaning and data flow; the Design Guideline owns general visual controls, surfaces, interaction states, icons, and shell geometry.
This dependency direction constrains ownership, not product composition. New
workspaces may have different information architectures when their operator
jobs differ. Their placement and form follow
docs/19_PRODUCT_SURFACE_EXTENSION_PROTOCOL.md.
Why Design Guideline code is not copied
The reusable architecture in NODEDC_DESIGN_GUIDELINE is its published package
boundary, registries, validation, and one-way imports. Its living catalog is an
application and is not a source template to paste into Mission Core.
Mission Core consumes @nodedc/tokens, @nodedc/ui-core, and
@nodedc/ui-react. A visual primitive missing from those packages is proposed
and approved in the Design Guideline first. Mission Core does not fork it.
The current file: package dependencies are suitable for the local standalone
development contour, but they are not an immutable distribution contract.
Before portable CI or deployment, the packages must be published, vendored with
content verification, or pinned to a verified donor revision.
Internal layers
src/core
Owns typed domain contracts, parsers, API adapters, state machines, and domain-specific hooks. It may depend on React for a hook or context boundary, but it does not import visual adapters, workspaces, or the App shell.
src/components
Owns reusable Mission Core domain components such as observation sources, timelines, media adapters, and laboratory presentation/evidence frames. Components may consume core contracts and canonical Design Guideline exports. They do not select routes, own product navigation, or import workspaces.
src/workspaces
Owns domain composition. A workspace connects core data to reusable components and renderers. A new domain gets its own module or feature directory; it is not appended to the central workspace hub.
workspaces/contracts.ts is the host contract shared by workspace
implementations. The central Workspaces.tsx is a composition hub for existing
generic workspaces, not a home for LAB-specific adapters or evidence logic.
src/workspaces/laboratory
Owns the laboratory catalog feature. It may compose:
- the reusable laboratory presentation components;
- immutable LAB adapters from
core/laboratory; - domain evidence renderers;
- the generic spatial workspace through the explicit
SpatialViewcontract.
Every new LAB adds a bounded result component and typed data adapter here. It
does not add LAB imports, selectors, styles, or conditional evidence code to
Workspaces.tsx or App.tsx.
src/composition
Owns reviewed integration imports and registries. Vendor/plugin contributions enter Core only through this layer and the public plugin SDK. Composition does not become a second business-logic layer.
src/productModel.ts
Is the typed application registry for roots, workspaces, labels, icons, and declared capabilities. Navigation and routing project from it rather than duplicating lists across the App shell.
It is a product registry, not a runtime ontology and not a substitute for evidence or domain contracts.
New workspaces and roots enter this registry only after their user job and placement are resolved. The registry must not accumulate placeholder pages for unapproved future ideas.
src/App.tsx
Owns shell-level orchestration: selected root/workspace, global panels, runtime providers, and passing typed controllers to a workspace. It must not absorb domain API calls, per-LAB renderers, or new visual primitives.
CSS ownership
CSS follows the same feature boundary:
- general workspace composition lives in
styles/workspaces.css; - laboratory presentation and E30 evidence layout live in
styles/laboratory.css; - spatial, observation, device, and shell rules remain in their named files;
- plugin CSS is plugin-local and scoped below its contribution root.
Application CSS may arrange domain content. Canonical control geometry and states remain in the Design Guideline packages. A feature must not add selectors to a generic CSS bucket merely because that file is already imported.
Component admission
A Mission Core component is admitted only when all of the following are true:
- Its responsibility can be stated in one sentence.
- Its inputs are typed domain data or callbacks, not hidden global state.
- It does not duplicate a Design Guideline entity.
- It has explicit loading, empty, unavailable, and error behavior where relevant.
- It has no knowledge of unrelated workspaces or vendor implementations.
- Its styles are feature-scoped.
- Its contract or behavior is covered by a unit or architecture test.
If the entity is visually generic rather than domain-specific, it belongs in the Design Guideline and requires product-owner approval before implementation.
Laboratory feature template
The code shape of a new laboratory increment is:
core/laboratory/eNNContract.ts
immutable API/schema adapter
workspaces/laboratory/ENNResult.tsx
typed values for the versioned summary + admitted evidence + result projection
domain renderer module
only when an existing renderer cannot express the evidence
LaboratoryArchiveWorkspace.tsx
registry/selection composition only
styles/laboratory.css
shared LAB layout; no per-run visual language
The complete engineering narrative remains in Ops. The product component receives a concise typed projection.
components/laboratory/LaboratoryPresentation.tsx owns the executable
missioncore.laboratory-report/v1 anatomy. LaboratorySummary requires
question, approach, principalResult, and limitation;
LaboratoryResultSummary requires metrics plus proved, notProved, and
decision. A bounded LAB result module cannot own the canonical summary/result
DOM or CSS classes. The product UI test discovers every ENNResult.tsx
automatically and rejects such a fork.
components/laboratory/LaboratoryRecordedClipPlayer.tsx exclusively owns the
versioned missioncore.laboratory-recorded-clip-viewer/v1 camera transport and
clock. Feature renderers may add typed overlays or a synchronized spatial scene
through its slots. Source-paced spatial evidence follows exact media sequences
through a bounded reusable look-ahead cache; frame-step remains an explicit
capability only when the source cannot keep pace. Experiment-named players must not fetch a JPEG
per playback frame, instantiate MediaSource, schedule frame timers, or declare a
new Rerun receiver/blueprint. Forward buffered progression and backward
loop/seek are separate shared-player transitions; a backward target cannot be
treated as an ordinary rolling-buffer advance.
The shared player owns the admitted primary camera and companion camera +
spatial presentations. A feature mode may not disable playback, unmount the
camera, or create a second viewer configuration merely to show 3D/PLAN.
LaboratoryEvidenceViewer owns the reusable stacked chrome layout: one header,
one visual stage and one transport/timeline rail joined into a seamless surface.
LaboratoryRecordedClipPlayer reuses the canonical SplitPane for resizable
spatial + companion-camera evidence while keeping the recorded camera owner
mounted across CAMERA/3D/PLAN transitions. Feature code supplies typed actions
and modes; it does not declare a feature-local splitter. M4.8 reuses one
M48EvidenceModeRail projection in the report, candidate-assisted correction,
independent review and adjudication surfaces. The rail is the admitted
viewport overlay: a canonical GlassSurface containing three default-size
IconButton controls and no local control geometry. CAMERA/3D/PLAN therefore
do not return to the stacked header or fork per workflow:
CAMERA visibility is independent from the nullable 3D/PLAN spatial mode, and
their state transition cannot hide both channels. Candidate seed data belongs
to the correction contract only; it cannot enter the blind source decoder or
upgrade assisted evidence to independent truth.
Legacy/integrated diagnostic viewers may keep a result interaction inside the evidence slot only where that viewer is already the admitted result instrument. This exception does not apply automatically to a new LAB.
ADR 0039 adds a lifecycle distinction to this code shape. A canonical LAB
enters through the reusable registry and must not add another experiment-named
branch to App.tsx, the generic workspace or the advanced dispatcher. An
experimental LAB may have a bounded adapter or renderer when the current graph
cannot express its evidence, but the adapter remains feature-local, declares its
classification and cannot change generic product behavior. A legacy LAB keeps
its existing read-only projector and is not rewritten solely to satisfy the
latest code shape.
Local ontology decision
Mission Core already has three semantic mechanisms:
productModel.tsfor application navigation and capability declarations;- versioned TypeScript/Pydantic/JSON contracts for executable data;
docs/domain-model/mission-core-experimental-vocabulary-v0alpha2.mdfor meanings that are not yet stable enough for Platform Ontology.
These mechanisms were sufficient before A3. Observatory modular compositions
crossed the gate on 2026-09-04: planning, Worker dispatch, LAB publication and
replay presentation require the same module/composition/result/layer relations,
and typed single-result traversal lost the TGS member of a composed run. The
bounded local package is defined by
docs/domain-model/observatory-domain-ontology-v1.md. It keeps identity in the
existing stores and has no Platform Ontology runtime dependency.
A formal local runtime ontology is introduced only when:
- at least two independent runtime consumers require the same relationships;
- typed contract traversal is demonstrably insufficient;
- ownership, identity, lifecycle, versioning, and migration are agreed;
- the graph answers named queries used by the product or automation;
- promotion or synchronization with NODE.DC Platform Ontology is defined.
Other domains continue to extend the versioned local vocabulary and executable contracts until they independently satisfy these conditions. The admitted Observatory package projects existing store identities and cannot become a second authority for them.
Automated boundary gate
test/applicationArchitecture.test.mjs enforces:
- no upward imports from core/components into workspaces or App;
- no visual adapter imports from core;
- no local vendor icon library or direct Design Guideline source imports;
- LAB code and CSS remain outside the central workspace buckets.
test/laboratoryProductUi.test.mjs additionally enforces the versioned LAB
report fields and shared result component across bounded LAB modules.
The next executable laboratory architecture gate extends these tests to reject new unclassified experiment branches while allowing a declared bounded experimental adapter. The gate protects core composition; it does not forbid a novel research stack.
File length is not an architectural boundary and is not enforced. Refactoring is justified by ownership, cohesion, dependency direction, lifecycle or test isolation, not by a line-count threshold.
From apps/control-station run:
npm run test:unit
npm run typecheck
npm run build
Known bounded debt
App.tsxremains a large shell orchestrator. Future shell behavior should preserve its orchestration ownership and extract modules only where they acquire an independent responsibility or lifecycle.Workspaces.tsxstill contains several established generic workspaces. New domains should respect the existing dependency direction; extraction is a design decision rather than a response to file length.LaboratoryArchiveWorkspace.tsxis physically isolated. Further LAB work must preserve the feature boundary without imposing a size quota on the implementation.- Design Guideline dependencies are mutable local
file:links until a portable package/distribution decision is implemented.
None of these debts require a new ontology or a copy of the Design Guideline application.