Question
Should odu move from node+tsx to bun, the way srid/drishti did — primarily to unlock OpenTUI for the attach face?
This issue records the fact-checked research (2026-06-12) so the decision has a paper trail.
Where the runtime choice actually lives
odu on node is packaging path-of-least-resistance, not a considered runtime decision:
default.nix fetches deps with nixpkgs-native fetchPnpmDeps + pnpmConfigHook and ships odu / odu-runner as makeWrapper-ed tsx scripts — no bundle step, zero flake inputs.
- drishti had to break the zero-input convention to use bun: its flake pulls
juspay/bun2nix (rawflake) as the documented exception, because nixpkgs has no fetchBunDeps / buildBunPackage. That pattern is directly copyable.
The roadmap already gates this — kolu#1294
The merged juspay/kolu#1294 atlas notes evaluated OpenTUI empirically against 0.4.0:
- Packaging is a non-issue:
@opentui/core ships prebuilt natives as optionalDependencies for all four kolu systems; fetchPnpmDeps ingests them like esbuild's binary.
- The real gate is the runtime: under node 24,
createCliRenderer throws "OpenTUI native FFI is not available for this runtime yet" — the package ships libopentui.so for bun:ffi, no napi addon.
- Plan of record: prototype
odu attach on OpenTUI when upstream's node support lands — "or earlier under bun for just the attach face." The roadmap sanctions a bun attach face, not a whole-runner port.
- The pull beyond widgets:
@kolu/surface's reactive hooks (useCell/useStream) are Solid-only — today's hand-rolled TUI can't use them. On @opentui/solid the terminal becomes a Solid tree over those hooks, and odu-web Phase 2 plans the browser observer over the same hooks: faces over one surface becomes shared view code.
Fact-check findings
OpenTUI is no longer strictly bun-only. The project moved to anomalyco/opentui; per the official docs, the native renderer now also runs on Node 26.3.0 with --experimental-ffi (the long-open Node/Deno support issue is being answered via Node's experimental FFI rather than napi). But that's current-line Node behind an experimental flag, while nixpkgs' pkgs.nodejs is LTS — so bun is the production-viable path today; node may be the path in 6–12 months.
Bun's child-process layer is the real port risk, but smaller than feared. odu's runner does detached: true + process.kill(-pid) group kills (src/runner/runner.ts); bun now supports detached properly (setsid(), child becomes group leader — oven-sh/bun#1442 resolved). odu's stdio framing uses only fd 0–2, so the long-standing extra-fds gap #4670 doesn't bite. Remaining known issues are edge-case: missing close event on fast exits, intermittent CI hangs. The EPIPE-on-teardown class of bug (#25) is exactly where runtime semantics differ — re-litigation risk, not a known blocker.
Tests: vitest can run under bun via bun run --bun vitest (unofficial); realistic choices are migrating to bun test or keeping vitest on node — with the caveat that node-hosted unit tests wouldn't exercise bun runtime behavior, leaving the e2e suite (#20, driving the nix-built binary) as the only bun-fidelity coverage.
Pros
- Unlocks OpenTUI now on the proven
bun:ffi path, with @opentui/solid slotting onto odu's existing solid-js (^1.9.0 satisfies the 1.9.12 peer-pin) and the surface hooks.
- Simpler runtime stack: drops
tsx and the node wrapper; bun runs the .ts entrypoints natively, with faster startup — relevant since the runner is spawned fresh over ssh per attach.
- In-house precedent: drishti's bun2nix flake pattern is directly copyable; the Nix cost is already paid once.
Cons
- Runtime risk lands on odu's core competency — process supervision and stdio teardown — where bun's node-compat history is weakest. Unit tests on node won't catch it; only e2e will.
- Cross-repo runtime divergence: kolu is pnpm+node (node-pty in kaval), and the surface-daemon plan has
odu serve consuming a spine born in kaval under node. A bun odu makes bun a second, upstream-untested runtime for shared @kolu/* code — and that coupling tightens over time.
- The motivation may have a shelf life: with Node's experimental FFI already running OpenTUI's renderer, a full-runner bun port could be obsolete as a means to OpenTUI once that stabilizes into an LTS.
- Mechanical churn: bun2nix flake input,
bun.lock, adapting scripts/hydrate-kolu-packages.sh to bun's node_modules layout, and a test-runner decision.
Recommendation
Follow the roadmap's own middle path rather than a whole-repo port:
- Prototype the attach face on bun + OpenTUI — read-only, smallest blast radius, no process supervision in that code path, so bun's weak spots don't apply.
default.nix already builds odu and odu-runner as separate wrappers; a bun-wrapped attach entrypoint is a contained change.
- Decide on the full-runner port only after (a) the prototype proves bun out under the e2e suite, or (b) OpenTUI-on-node stabilizes and makes it unnecessary.
- Cheap first step either way: re-run the kolu#1294 renderer probe under Node 26.3
--experimental-ffi to see how real that path is today.
Research conducted with Claude Code (claude-fable-5); sources linked inline.
Question
Should odu move from node+tsx to bun, the way srid/drishti did — primarily to unlock OpenTUI for the attach face?
This issue records the fact-checked research (2026-06-12) so the decision has a paper trail.
Where the runtime choice actually lives
odu on node is packaging path-of-least-resistance, not a considered runtime decision:
default.nixfetches deps with nixpkgs-nativefetchPnpmDeps+pnpmConfigHookand shipsodu/odu-runnerasmakeWrapper-ed tsx scripts — no bundle step, zero flake inputs.juspay/bun2nix(rawflake) as the documented exception, because nixpkgs has nofetchBunDeps/buildBunPackage. That pattern is directly copyable.The roadmap already gates this — kolu#1294
The merged juspay/kolu#1294 atlas notes evaluated OpenTUI empirically against 0.4.0:
@opentui/coreships prebuilt natives asoptionalDependenciesfor all four kolu systems;fetchPnpmDepsingests them like esbuild's binary.createCliRendererthrows "OpenTUI native FFI is not available for this runtime yet" — the package shipslibopentui.soforbun:ffi, no napi addon.odu attachon OpenTUI when upstream's node support lands — "or earlier under bun for just the attach face." The roadmap sanctions a bun attach face, not a whole-runner port.@kolu/surface's reactive hooks (useCell/useStream) are Solid-only — today's hand-rolled TUI can't use them. On@opentui/solidthe terminal becomes a Solid tree over those hooks, and odu-web Phase 2 plans the browser observer over the same hooks: faces over one surface becomes shared view code.Fact-check findings
OpenTUI is no longer strictly bun-only. The project moved to anomalyco/opentui; per the official docs, the native renderer now also runs on Node 26.3.0 with
--experimental-ffi(the long-open Node/Deno support issue is being answered via Node's experimental FFI rather than napi). But that's current-line Node behind an experimental flag, while nixpkgs'pkgs.nodejsis LTS — so bun is the production-viable path today; node may be the path in 6–12 months.Bun's child-process layer is the real port risk, but smaller than feared. odu's runner does
detached: true+process.kill(-pid)group kills (src/runner/runner.ts); bun now supportsdetachedproperly (setsid(), child becomes group leader — oven-sh/bun#1442 resolved). odu's stdio framing uses only fd 0–2, so the long-standing extra-fds gap #4670 doesn't bite. Remaining known issues are edge-case: missingcloseevent on fast exits, intermittent CI hangs. The EPIPE-on-teardown class of bug (#25) is exactly where runtime semantics differ — re-litigation risk, not a known blocker.Tests: vitest can run under bun via
bun run --bun vitest(unofficial); realistic choices are migrating tobun testor keeping vitest on node — with the caveat that node-hosted unit tests wouldn't exercise bun runtime behavior, leaving the e2e suite (#20, driving the nix-built binary) as the only bun-fidelity coverage.Pros
bun:ffipath, with@opentui/solidslotting onto odu's existing solid-js (^1.9.0satisfies the1.9.12peer-pin) and the surface hooks.tsxand the node wrapper; bun runs the.tsentrypoints natively, with faster startup — relevant since the runner is spawned fresh over ssh per attach.Cons
odu serveconsuming a spine born in kaval under node. A bun odu makes bun a second, upstream-untested runtime for shared@kolu/*code — and that coupling tightens over time.bun.lock, adaptingscripts/hydrate-kolu-packages.shto bun'snode_moduleslayout, and a test-runner decision.Recommendation
Follow the roadmap's own middle path rather than a whole-repo port:
default.nixalready buildsoduandodu-runneras separate wrappers; a bun-wrapped attach entrypoint is a contained change.--experimental-ffito see how real that path is today.Research conducted with Claude Code (
claude-fable-5); sources linked inline.