- Layout: status bar (borderless), horizontal top row (sessions 30% / active 30% / tools 40%), router fill, input bar fixed
- Components: rewrote all 5 + added ToolsPanel; all accept TuiState directly
- InputBar: 4 modes (ROUTER/NAVIGATE/FILTER/STEER) with context-sensitive keybind hints
- RouterPanel: inline approval/event overlays as last-child text panels
- StatusBar: borderless row with model, providerType, session count, approval badge
- SessionList: manual column/row rendering, no list widget, overflow truncation
- tamboui bumped to 0.3.0
- TambouiKeyMapper: ctrl+key required for all keybinds, bare chars always
go to input buffer.
- KeyResolver: Quit is ROUTER-only (not global); NavUp/NavDown handled in
ROUTER as well as NAVIGATE; Filter in ROUTER maps to OpenFilter; CharInput
in ROUTER returns null (semantic chars handled upstream by mapper).
Sync all TUI reducer and input tests with the state model refactor:
InputMode.None/WorkflowId/SteeringNote/Filter → ROUTER/NAVIGATE/STEER/FILTER;
InputState(mode, text) → TuiState(inputMode, inputBuffer); fix SessionStarted
effect assertion to expect ConnectSession.
Lifts inputMode/inputBuffer out of InputState into top-level TuiState,
renames InputMode variants to ROUTER/NAVIGATE/FILTER/STEER, adds
TuiToolRecord and TuiEventEntry for per-session tool/event history,
expands SessionsReducer to track tool lifecycle and recent events, and
adds ToggleApprovalOverlay/ToggleEventOverlay/EnterSteer actions.
Two issues causing "no transition condition matched":
1. emitToolArtifacts only emitted ArtifactCreatedEvent; the artifact_validated
transition condition requires VALIDATED phase, which needs the full lifecycle
(Created → Validating → Validated). Tool success is sufficient grounds for
auto-validation, so emit all three events.
2. DefaultSessionOrchestrator was building stageArtifacts from LiveArtifactRepository
which uses a hot async subscription — cache was always empty at lookup time.
Now reads ArtifactValidatedEvents directly from the synchronous EventStore.read().
LiveArtifactRepository uses a hot subscription that does not replay history,
so its cache was always empty when verifyProduces ran immediately after emit.
Fix by reading directly from EventStore (synchronous) instead of the repository.
Also emit ArtifactCreatedEvent for non-llmEmitted produces slots after stage
validation passes, and fix tool parameter decoding to use JsonPrimitive.content
so escape sequences like \n are resolved rather than passed as literal backslash-n.
Deletes 21 Module.kt scaffolding objects that were never wired into any DI
registry. Removes unused imports across 8 production and 3 test files.
Restores StageExecutor, CyclePolicyResolver, PolicyValidation, and
ReplayContractTest — these are deferred features, not dead code.
Removes 6 files audited as fully unused: StageExecutor interface (shadowed
by kernel), CyclePolicyResolver and PolicyValidation (deferred cycle policy),
ApprovalRoutes (never registered), TuiNavigation (superseded by reducer),
and ReplayContractTest abstract class (no concrete subclasses).
ArtifactRelationshipType and CyclePolicy were audited but retained —
both are actively referenced by production code.
- SessionOrchestrator: wire T2/T3/T4 approval gate before tool execution;
emit OrchestrationPausedEvent/ApprovalRequestedEvent, await decision,
return rejection as ERROR context entry so LLM sees the denial;
propagate tool ERROR entries as StageExecutionResult.Failure
- SandboxedToolExecutor: remove dead code and simplify
- InfrastructureModule: minor wiring cleanup
- LlamaCppInferenceProvider / build.gradle: related build fixes
- SessionEventBridge: replaySnapshot() sends all historical events on connect;
streamLive(sessionId) subscribes to live event flow per session
- DomainEventMapper: maps 14 domain event types to ServerMessage; reads
InferenceCompleted response text from ArtifactStore with empty-string fallback
- GlobalStreamHandler: wires bridge on connect for replay, launches per-session
live-stream job on StartSession, cancels all jobs on disconnect; removes
ProtocolError("ping") heartbeat hack
- SessionStreamHandler: fixes replay to use DomainEventMapper instead of
emitting SessionStarted for every event; injects real artifactStore;
removes ProtocolError("ping") heartbeat; adds structured logging throughout
- Main: startup log block showing port, model config, sandbox, stores, tools,
providers
- SessionsReducer: StageCompleted/StageFailed now clears currentStage; adds
5 missing tests (InferenceCompleted, StageStarted, ToolCompleted, SessionPaused)
- RouterPanel: new TUI widget rendering active session's lastResponseText
- TuiWsClient: remove println that was polluting the TUI on WS connect failure
New core:artifacts-store interface + infrastructure/artifacts-cas
implementation: segment files + SQLite index, Blake3 hashing,
group-commit fsync via flushBefore, recovery tail-scan, manual
compactor, and oldest-first disk-cap evictor.
Inference events now carry promptArtifactId / responseArtifactId;
orchestrators put bytes before emitting. SqliteEventStore wraps
its txn in artifactStore.flushBefore so segment data is fsynced
before the event commit, making the crash window non-corrupting
(TailScanner re-indexes orphan tail records on reopen).
Compactor and evictor are mutually exclusive via maintenanceMutex.
Step 9 (cloud sync) deferred to a later epic.
See docs/reviews/2026-05-18-cas-steps-1-8-review.md for the final
review.
Add log4j2/SLF4J deps and log4j2.xml, install Ktor CallLogging, replace
manual System.err.println sites with leveled loggers, and wrap the
EventStore with a LoggingEventStore decorator so every append/appendAll
is logged in one place.
Replaces the Mosaic 0.18 string-frame renderer with tamboui 0.2.1-SNAPSHOT
to unlock a cell buffer, widget catalog, and real layout engine. Domain
core (state/reducer/ws/input) is unchanged; only the renderer and the key
mapper are rewritten. Fixes a latent Mosaic-mapper bug where q/n/c/a/r/s//
were swallowed inside input modes by making the new mapper mode-aware.
Tasks 4.2–4.3 of docs/plans/2026-05-17-tui-refactor.md:
- SessionList filters by workflowId (case-insensitive) using
state.sessions.filter, populated by SubmitInput while in
InputMode.Filter and cleared on CancelInput.
- TuiWsClient tracks reconnect attempt count and emits
ConnectionEvent.RetryScheduled(attempt, nextRetryAtMs) before each
backoff delay. Attempt resets on successful connect.
- StatusBar renders "reconnecting (attempt N, retry in Xs)" with
recompose-driven countdown.
Also captures a renderer-evaluation note in the progress doc: Mosaic's
ceiling vs Textual, candidate JVM alternatives (tamboui, Kotter,
Lanterna, Mordant), and confirmation that the reducer/Effect core is
renderer-agnostic if a swap is ever pursued.
Pending: 4.1 (scroll log, deferred), 5.1 (coverage gate), 5.2 (cleanup).