The TUI phases established that kodr wants channels - thin adapters over one shared request flow - rather than one-off code paths per surface. Phases 49 and 50 protect that idea and then stretch it to a third channel.
Phase 49: contract tests against drift
The whole channel concept only holds if the channels actually stay aligned. The moment there’s more than one way to talk to kodr, it gets easy for the CLI, the TUI, and a future web UI to each grow their own slightly-different run/session behaviour. The channel handler exists to stop that; phase 49 adds tests that hold it to its word.
There’s almost no new product surface here, which is the point. The contract tests assert that:
- CLI and channel run-turns produce the same artifact shape.
- Session list/show work without any presentation code in the loop.
- Unknown channel requests fail loudly, not silently.
- TUI slash commands never reach the model channel.
- A TUI turn doesn’t mutate its base option template.
That last one matters more than it looks - shared mutable option templates are exactly how “the web channel behaves weirdly after you’ve used the TUI” bugs are born. These tests are the guardrail that makes the next channel cheap and low-risk to add.
Phase 50: a web channel that isn’t a harness
Then the smallest useful proof of a web UI: kodr serve, a local-only JSON HTTP server. No frontend bundle, no dependencies. The constraint I held to is that HTTP is not a second harness - every route adapts into the same central channel handler the CLI and TUI use:
GET /sessions → session-list
GET /sessions/:id → session-show
POST /turn → run-turn
So routes can evolve without bypassing session browsing, run artifacts, dry-run defaults, model settings, or any later channel policy. They get all of it for free, because they aren’t reimplementing it.
Writing the tests surfaced a security detail worth stating plainly: web turns must not inherit ambient apply/session state from the process. If you start kodr serve with broad options, you do not want a stray POST /turn quietly writing files because the server happened to launch with apply on. So /turn resets to dry-run and no session by default, then honours only explicit yes, sessionId, or continue fields from the request body. That keeps the web channel aligned with kodr’s “model output is untrusted” rule and avoids surprise writes.
The route tests use a fake channel rather than LM Studio - fast and deterministic, asserting each endpoint hits the right request shape, rejects malformed input, maps missing sessions to 404, and refuses non-local bind hosts. The live LM Studio check stayed as a smoke test: start the server, post a real turn through /turn, watch it flow all the way through the model-backed run path. Sketch, not product - but a sketch that proves the boundary holds at a third surface.
Links:
- Phase docs: 49-channel-contract-tests, 50-web-channel-sketch
- The local server: src/server.mjs