Problem
The factory visualization needs a stable API layer that derives workflow progress from run snapshots — what each agent is doing, what state each artifact is in, what the orchestrator is carrying — independent of any specific visualization's spatial layout. Currently, each visualization (catwalk, factory-floor) interprets CanonicalRunStatus directly with duplicated logic. Branch 295's tilemap implementation couples logical state derivation to a 5-room spatial layout that has since been superseded.
Context
Branch 295 implemented a complete 4-layer tilemap pipeline (state mapper, position resolver, differ, transition planner) but embedded logical interpretation (agent statuses, orchestrator behavior, artifact lifecycle) inside a tilemap-specific mapper that assigns entities to rooms and slots. When the layout changed from 5 rooms to 3 zones (#293), the logical interpretation remained sound but the spatial coupling made it fragile.
The architectural insight: the interpretation of CanonicalRunStatus into present-tense workflow state ("the architect is done, the planner is working, the orchestrator is carrying findings") is shared across all visualizations. Only the spatial mapping (rooms, slots, positions) is visualization-specific.
Salvageable code exists on branch 295-salvage. The shared utilities (agent-state-resolver.ts, artifact-utils.ts, orchestrator-utils.ts) and the logical derivation logic in run-to-tilemap.ts can be extracted and adapted.
Related: #301 (structured review findings — deferred), #302 (thought bubble cycling — deferred), #303 (tilemap 3-zone adapter — depends on this ticket)
See packages/factory/docs/visions/facility-architecture.md for the full spatial and behavioral architecture.
Solution
1. Extract shared utilities
Move visualization-agnostic utilities from branch 295-salvage into packages/factory/src/client/visualizations/shared/:
agent-state-resolver.ts — resolves AgentAnimationState from phase and run status
artifact-utils.ts — artifact color lookup, type guards, reviewer name extraction
orchestrator-utils.ts — carried artifacts, code badge derivation
2. Define LogicalSceneState
A visualization-agnostic snapshot of workflow progress. No rooms, no slots, no spatial concepts.
interface LogicalSceneState {
runStatus: string;
currentPhase: PhaseName | undefined;
agents: LogicalAgentState[];
orchestrator: LogicalOrchestratorState;
artifacts: LogicalArtifactState[];
}
- Agents: id, role, roleType, phase, status (idle/working/done/blocked/concerned)
- Orchestrator: status (idle/dispatching/monitoring/delivering/done), carriedArtifacts, codeBadge, waiting
- Artifacts: id, label, color, status (created/in_transit/delivered), producerPhase, iteration
3. Implement the mapper
mapRunToLogicalScene(status: CanonicalRunStatus): LogicalSceneState
Pure function. Reuses shared utilities for the non-trivial derivations. This is the extraction and generalization of the logical parts of branch 295's mapRunToTilemap(), minus room/slot assignment.
4. Artifact lifecycle rename
ArtifactStatus = 'created' | 'in_transit' | 'delivered' (was 'on_desk' | 'delivered' in branch 295).
Out of scope
Acceptance criteria
Problem
The factory visualization needs a stable API layer that derives workflow progress from run snapshots — what each agent is doing, what state each artifact is in, what the orchestrator is carrying — independent of any specific visualization's spatial layout. Currently, each visualization (catwalk, factory-floor) interprets
CanonicalRunStatusdirectly with duplicated logic. Branch 295's tilemap implementation couples logical state derivation to a 5-room spatial layout that has since been superseded.Context
Branch 295 implemented a complete 4-layer tilemap pipeline (state mapper, position resolver, differ, transition planner) but embedded logical interpretation (agent statuses, orchestrator behavior, artifact lifecycle) inside a tilemap-specific mapper that assigns entities to rooms and slots. When the layout changed from 5 rooms to 3 zones (#293), the logical interpretation remained sound but the spatial coupling made it fragile.
The architectural insight: the interpretation of
CanonicalRunStatusinto present-tense workflow state ("the architect is done, the planner is working, the orchestrator is carrying findings") is shared across all visualizations. Only the spatial mapping (rooms, slots, positions) is visualization-specific.Salvageable code exists on branch
295-salvage. The shared utilities (agent-state-resolver.ts,artifact-utils.ts,orchestrator-utils.ts) and the logical derivation logic inrun-to-tilemap.tscan be extracted and adapted.Related: #301 (structured review findings — deferred), #302 (thought bubble cycling — deferred), #303 (tilemap 3-zone adapter — depends on this ticket)
See
packages/factory/docs/visions/facility-architecture.mdfor the full spatial and behavioral architecture.Solution
1. Extract shared utilities
Move visualization-agnostic utilities from branch 295-salvage into
packages/factory/src/client/visualizations/shared/:agent-state-resolver.ts— resolvesAgentAnimationStatefrom phase and run statusartifact-utils.ts— artifact color lookup, type guards, reviewer name extractionorchestrator-utils.ts— carried artifacts, code badge derivation2. Define
LogicalSceneStateA visualization-agnostic snapshot of workflow progress. No rooms, no slots, no spatial concepts.
3. Implement the mapper
mapRunToLogicalScene(status: CanonicalRunStatus): LogicalSceneStatePure function. Reuses shared utilities for the non-trivial derivations. This is the extraction and generalization of the logical parts of branch 295's
mapRunToTilemap(), minus room/slot assignment.4. Artifact lifecycle rename
ArtifactStatus = 'created' | 'in_transit' | 'delivered'(was'on_desk' | 'delivered'in branch 295).Out of scope
LogicalSceneStatetype must be extensible to accommodate them later, but no thought bubble code is included.Acceptance criteria
visualizations/shared/and importable by any visualizationLogicalSceneStatetype defined with agent, orchestrator, and artifact statemapRunToLogicalScene()correctly derives present-tense workflow state fromCanonicalRunStatuscreated | in_transit | delivered