feat(tui): generate TuiCore via fluessig single_threaded handle#345
feat(tui): generate TuiCore via fluessig single_threaded handle#345zmaril wants to merge 1 commit into
Conversation
Swap the hand-written `TuiCore` napi class (src/tui.rs, the native half of pi's differential renderer, packages/tui/src/tui.ts) for a fluessig-generated one — the largest UI core yet (ctor + 12 sync ops), extending the #80 single_threaded (thread-confined `!Send`) handle path proven by InputCore (#341) and SelectListCore: - Author the ctor + twelve sync ops in schema/api.json, marking the interface `single_threaded: true`. fluessig lowers it to a THREAD-CONFINED handle: `pub struct TuiCore { core: RefCell<TuiCoreImpl> }` — no `Arc`, no `Send`/`Sync` — and the `TuiCoreCore` trait sheds its `Send + Sync` supertraits (`Sized + 'static`) with `&mut self` ops reached through `borrow_mut()`. - This is what lets a `!Send` core compile: `TuiCoreImpl` (core_impl.rs) owns pi's `Tui<LoggingTerminal>`, whose differential renderer holds `Rc<RefCell<dyn Component>>` children and non-`Send` closures. Every op is sync — no streaming/async — so nothing is carved `@manual`. The `tick` fallibility (`Result<()>`) rides the trait's `anyhow::Result` seam; the `to_usize` dimension clamp moves with it. - regen src/generated.rs (fluessig e056bb9); delete src/tui.rs and its `mod tui;` in lib.rs; keep `pub mod generated;`. Self-mutability is invisible to the TS type, so the RefCell-vs-`&mut self` handle is byte-transparent: the generated `TuiCore` class `.d.ts` block, the `.js` export, and the nativeBinding entry are all byte-identical to the pre-swap build. pi's own test/tui-render.test.ts (23/23) and test/tui-shrink.test.ts (1/1) oracles pass against the locally-built addon. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017xL2W7dkhPsTSRuXQNCDeY
Conformance smoke: agent + ai + tui
Headline is rust-backed: passing cases in files whose module-under-test is a native (Rust addon) module. Raw all-pass is shown secondary — it is inflated by unflipped TypeScript that passes without touching any Rust.
agent: 55/180 (30.6%) · ai: 74/1296 (5.7%) · tui: 375/678 (55.3%) rust-backed = passing / total tests run, per the manifest's per-native-row AttributionA pi test file is rust-backed iff the module it primarily exercises — its module-under-test — is Per-file decisions (from each native manifest row's
CLI conformance (black-box, against the pidgin binary)
The four repointed coding-agent CLI test files spawn the compiled pidgin binary via |
Swap the hand-written
TuiCorenapi class (src/tui.rs, the native half of pi's differential renderer) for a fluessig-generated one — the largest UI core yet (ctor + 12 sync ops), extending the #80single_threaded(thread-confined!Send) handle path proven by InputCore (#341) and SelectListCore (#344).What changed
schema/api.json, marking the interfacesingle_threaded: true. fluessig lowers it to a THREAD-CONFINED handlepub struct TuiCore { core: RefCell<TuiCoreImpl> }— noArc, noSend/Sync— with theTuiCoreCoretrait sheddingSend + Sync(Sized + 'static),&mut selfops reached throughborrow_mut().TuiCoreImpl(core_impl.rs) owns pi's!SendTui<LoggingTerminal>, whose differential renderer holdsRc<RefCell<dyn Component>>children and non-Sendclosures. Every op is sync — no streaming/async — so nothing is carved@manual; thetickfallibility (Result<()>) rides the trait'sanyhow::Resultseam.src/generated.rs(fluessig e056bb9); deletesrc/tui.rsand itsmod tui;in lib.rs; keeppub mod generated;.Gates
TuiCore.d.tsblock,.jsexport, and nativeBinding entry are byte-identical to the pre-swap build.cargo build/fmt/clippy -D warningsclean; codespell 0; straitjacket v0.2.3 0 errors.test/tui-render.test.ts(23/23) andtest/tui-shrink.test.ts(1/1) pass against the locally-built addon.Stacked on
napi-fluessig/select-list-core(#344, SelectListCore) →napi-fluessig/single-threaded-tui(#341, InputCore).🤖 Generated with Claude Code
https://claude.ai/code/session_017xL2W7dkhPsTSRuXQNCDeY
Generated by Claude Code