Skip to content

CliOrb — LumosRenderer<LumosTerminalFrame> terminal renderer #44

@wow-miley

Description

@wow-miley

Context

This is the integration ticket — the actual JVM-side terminal renderer that takes a stream of LumosTerminalFrames and writes ANSI escape sequences to stdout (or any provided PrintStream). It implements LumosRenderer<LumosTerminalFrame>, completing the Wave 2 pipeline:

CognitiveSceneRuntime → SceneSnapshot
  → VoxelFrameBuilder.build(snapshot) → VoxelFrame
  → CliLattice.project(frame) → LumosTerminalFrame
  → CliGlyph.overlay(frame) → LumosTerminalFrame (with glyph if active)
  → CliOrb.render(frame) → stdout ANSI bytes

After this ticket, a JVM consumer can run a full loop and see the Lumos orb in their terminal. Wave 3's AMPERE bridge ticket plugs EventSerializerBus into the upstream end of this pipeline.

Objective

A production-quality terminal renderer with cursor management, frame loop, and resize handling, but without stdin (deferred to Wave 5).

Expected Outcomes

  • CliOrb class at phosphor-lumos-cli/src/jvmMain/kotlin/link/socket/phosphor/lumos/cli/renderer/CliOrb.kt.
  • Implements LumosRenderer<LumosTerminalFrame> (from :phosphor-lumos).
  • Constructor parameters:
    • out: PrintStream = System.out
    • colorMode: AnsiColorMode = AnsiColorMode.TRUECOLOR
    • targetFps: Int = 30
    • clearMode: ClearMode = ClearMode.DIFFERENTIALFULL clears the entire orb region each frame, DIFFERENTIAL writes only changed cells.
  • render(frame: LumosTerminalFrame):
    • On first render, emit cursor-hide and clear-screen escapes.
    • Position cursor at orb origin (top-left of orb region).
    • For each cell, emit cursor position + color escape + character, with run-length optimization (consecutive cells with the same foreground/background skip redundant escapes).
    • In DIFFERENTIAL mode, compare against the previous frame and skip cells that haven't changed.
    • Flush at the end of each frame.
  • start() and stop() lifecycle methods:
    • start() registers a shutdown hook to call stop().
    • stop() emits cursor-show, clears the orb region, flushes.
  • Resize handling via polling: every N frames (configurable, default every 30), re-query terminal dimensions. If changed, clear and re-render fully on next frame. Cross-platform — works on Unix-like and Windows JVMs without signal handlers.
  • Terminal dimension query: use system properties / JLine / native call, abstracted behind a TerminalSize provider interface so tests can inject fake dimensions.
  • Unit tests for the rendering logic that capture stdout to a buffer and assert escape sequence output for known frame inputs.

Technical Constraints

  • JVM-only — lives in jvmMain, not commonMain.
  • Does not handle stdin. No raw mode. No key bindings.
  • Does not manage a frame timer or scheduler. Callers drive render() at their preferred cadence. (The targetFps parameter is informational metadata for downstream throttling decisions, not enforced here.)
  • Resize handling polls; never blocks waiting on signals.
  • All ANSI escapes use the standard CSI prefix (\u001B[). No vendor-specific extensions.
  • Color quantization delegates to AnsiColorMap from AnsiColorMap — OKLab to terminal color escape sequences #40.

API Surface Verification (before starting)

Confirm in socket-link/phosphor:

  • LumosRenderer<T> interface signature from Wave 1's Add LumosRenderer interface and VoxelFrame data class #30 — specifically the render method name and any lifecycle methods (start/stop/flush).
  • Whether :phosphor-lumos's common surface includes a LumosRenderTarget.VOXEL_TERMINAL enum value that should be reported by CliOrb.
  • Available terminal-size detection options on JVM — the project may already have a dependency on JLine or similar; if not, use System.console()-derived heuristics + a fallback to 80×24.

Tasks

  1. Implement TerminalSize provider interface + default JVM implementation that queries terminal dimensions, with 80×24 fallback. Validate: calling the provider returns plausible dimensions on a real terminal; injection of a fake provider works for tests.
  2. Implement render() for FULL clear mode — no diffing, write every cell each frame. Validate: capture stdout, render a known 5×3 frame with one red cell at (2, 1), assert the captured bytes contain the expected escape sequence prefix, cursor position, and character.
  3. Implement run-length optimization (consecutive cells with same color skip redundant color escapes). Validate: capture stdout for a frame of all-red cells; assert only one color escape is emitted, not one per cell.
  4. Implement DIFFERENTIAL clear mode — diff against previous frame, skip unchanged cells. Validate: render frame A then frame A again; second render produces zero cell writes (only cursor parking + flush).
  5. Implement start() and stop() with shutdown hook. Validate: integration test that calls start(), renders, and exits the JVM; shutdown hook runs (verify cursor-show escape was emitted on exit).
  6. Implement resize polling — every N frames, re-query size; if changed, force-clear on next frame. Validate: inject a fake provider that changes size mid-stream; assert next render emits full clear + redraw.
  7. End-to-end smoke test: construct CognitiveSceneRuntime with enableAtmosphere = true, drive it through choreographer transitions IDLE → LISTENING → THINKING → UNCERTAIN → READY, render each settled state via the full pipeline, capture stdout to a file. Manually eyeball the output in a real terminal to confirm visual quality. (This is a developer ergonomics gate, not an automated assertion — but commit a script that produces this output so future regressions are easy to spot.)

Out of Scope

  • stdin handling, raw mode, keypress capture, interactive demos. Wave 5.
  • Multiple orbs in the same terminal (split-pane / dashboard layouts). Future ticket.
  • Inline rendering above other terminal output (e.g., Lumos orb above an agent log stream). Wave 3 (AMPR ticket for ampere-cli integration) handles layout; CliOrb just renders into a region.
  • Mouse events.
  • Performance benchmarking under sustained load — Wave 5 polish.
  • Frame timing / scheduler enforcement.

Metadata

Metadata

Assignees

Type

No type
No fields configured for issues without a type.

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions