Files
NODEDC_MISSION_CORE/docs/18_APPLICATION_COMPONENT_ARCHITECTURE.md
T

12 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 SpatialView contract.

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:

  1. Its responsibility can be stated in one sentence.
  2. Its inputs are typed domain data or callbacks, not hidden global state.
  3. It does not duplicate a Design Guideline entity.
  4. It has explicit loading, empty, unavailable, and error behavior where relevant.
  5. It has no knowledge of unrelated workspaces or vendor implementations.
  6. Its styles are feature-scoped.
  7. 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:

  1. productModel.ts for application navigation and capability declarations;
  2. versioned TypeScript/Pydantic/JSON contracts for executable data;
  3. docs/domain-model/mission-core-experimental-vocabulary-v0alpha2.md for meanings that are not yet stable enough for Platform Ontology.

These mechanisms are sufficient before A3. A new RDF/graph store, ontology service, or parallel entity catalog would introduce duplicated identity and migration work without a current query or integration consumer.

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.

Until those conditions hold, new stable meanings extend the versioned local vocabulary and executable contracts. They do not create a second runtime model.

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;
  • central composition files cannot silently return to their previous size.

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.

The line limits are ratchets, not quality targets. When a file reaches a limit, split a feature; do not raise the limit to accommodate unrelated behavior.

From apps/control-station run:

npm run test:unit
npm run typecheck
npm run build

Known bounded debt

  • App.tsx remains a large shell orchestrator. Its current size is frozen by a ratchet; future shell behavior must extract a controller/hook or panel module.
  • Workspaces.tsx still contains several established generic workspaces. New domains must be separate modules, and existing ones may be extracted when their behavior changes.
  • LaboratoryArchiveWorkspace.tsx is now physically isolated but at its ratchet. A3 receives its own component/module instead of growing that file.
  • 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.