Skip to content

feat(tui): generate KeybindingsManagerCore handle class via fluessig#311

Merged
zmaril merged 1 commit into
mainfrom
napi-fluessig/keybindings-manager
Jul 21, 2026
Merged

feat(tui): generate KeybindingsManagerCore handle class via fluessig#311
zmaril merged 1 commit into
mainfrom
napi-fluessig/keybindings-manager

Conversation

@zmaril

@zmaril zmaril commented Jul 21, 2026

Copy link
Copy Markdown
Owner

What

First opaque-handle class swap in the pidgin-napi → fluessig campaign: moves the hand-written #[napi] pub struct KeybindingsManagerCore (backing packages/tui/src/keybindings.ts) onto the fluessig-generated napi surface. This is the strategic prove that fluessig's Shape::Ctor handle-lowering path works — the biggest remaining bucket (~79 of ~166 exports are handle-class methods).

Stacked on napi-fluessig/tui-utils (the current stack tip; the task's "module5" name maps to it).

Before / After

Before — hand-written in lib.rs:

#[napi(js_name = "KeybindingsManagerCore")]
pub struct KeybindingsManagerCore { inner: pidgin_tui::KeybindingsManager }
#[napi]
impl KeybindingsManagerCore {
    #[napi(constructor)] pub fn new(definitions_json: String, user_bindings_json: String) -> napi::Result<Self> {}
    #[napi(js_name = "matches")] pub fn matches(&self, data: String, keybinding: String) -> bool {}
    #[napi(js_name = "getKeys")] pub fn get_keys(&self, keybinding: String) -> Vec<String> {}
    #[napi(js_name = "getConflictsJson")] pub fn get_conflicts_json(&self) -> napi::Result<String> {}
    #[napi(js_name = "getResolvedBindingsJson")] pub fn get_resolved_bindings_json(&self) -> napi::Result<String> {}
}

After — authored as a ctor interface in schema/api.json (ctor op + 4 unary method ops), regenerated by regen.sh into src/generated.rs as #[napi] pub struct KeybindingsManagerCore { core: Arc<crate::core_impl::KeybindingsManagerCoreImpl> } with the ctor building the core via the generated KeybindingsManagerCoreCore trait and each method delegating through self.core. The engine seam (KeybindingsManagerCoreImpl) lives in src/core_impl.rs, wrapping one immutable pidgin_tui::KeybindingsManager and routing to the same resolution logic — reproducing the hand-written JSON parse-error messages via anyhow. The hand-written struct/impl + its two input DTOs are deleted from lib.rs.

How

  • Class picked: KeybindingsManagerCore — the simplest immutable Bucket-D handle: all-primitive carriers (string / bool / Vec<string>), a plain concrete inner (no generic host provider, no fs/exec side-effects), truly &self-immutable, dedicated status=native oracle.
  • Shape::Ctor path worked end-to-end — no fluessig codegen gap. fluessig (pinned 1fed77a) lowered the ctor interface to the Arc<…Impl> handle class + …Core trait exactly as the reference backend prescribes; ctor via <Impl as Trait>::new(...).map_err(err)?, method arms honoring infallible (-> T) vs fallible (-> Result<T>) and the js-name pins.

Gates

  • .d.ts: whole-file byte-identical to the pre-swap baseline. The class block (JSDoc + all 5 members) matches exactly.
  • .js: class entries byte-identical. module.exports.KeybindingsManagerCore = KeybindingsManagerCore is byte-identical text; the only delta is the symbol relocating from the lib.rs tail into the generated export block (every prior swap did the same — napi orders index.js by crate registration order).
  • cargo build -p pidgin-napi, cargo fmt --all --check, cargo clippy -p pidgin-napi --all-targets -- -D warnings — clean. codespell 0.
  • straitjacket v0.2.3 over git archive HEAD: 877 files, 0 errors, 1 conformance/STEWARD.md slop-prose WARN (pre-existing, allowed).
  • Oracle packages/tui/test/keybindings.test.ts against the locally built addon: 4/4 pass.

🤖 Generated with Claude Code

https://claude.ai/code/session_017xL2W7dkhPsTSRuXQNCDeY


Generated by Claude Code

@github-actions

github-actions Bot commented Jul 21, 2026

Copy link
Copy Markdown

Conformance smoke: agent + ai + tui

  • pi_sha: 3da591ab74ab9ab407e72ed882600b2c851fae21
  • manifest_native_modules: 54

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.

Package rust-backed Native modules raw pass (secondary) Failing Skipped
agent 73/180 (40.6%) 7 180/180 0 0
ai 81/1296 (6.3%) 11 553/1296 3 740
tui 375/678 (55.3%) 14 678/678 0 0
smoke total 529/2154 (24.6%) 32 1411/2154 3 740

agent: 73/180 (40.6%) · ai: 81/1296 (6.3%) · tui: 375/678 (55.3%)

rust-backed = passing / total tests run, per the manifest's per-native-row tests lists. The Native modules column counts modules served by the Rust addon. This is the agent+ai+tui smoke subset; the full baseline lives in committed conformance.json.

Attribution

A pi test file is rust-backed iff the module it primarily exercises — its module-under-test — is status=native in conformance/manifest.json. A package's rust-backed count is the passing cases in its rust-backed files. Transitive or infrastructure use does not count (e.g. ~30 ai files construct the native faux provider but test other things — only faux-provider.test.ts, whose subject is faux, counts). A file that substantially tests both a native and an original module is excluded rather than counted, so the number under-reports rather than over-claims.

Per-file decisions (from each native manifest row's tests list):

Native module Test file Decision
ai/api/anthropic-messages.ts test/anthropic-sse-parsing.test.ts counted
ai/providers/faux.ts test/faux-provider.test.ts counted
coding-agent/utils/ansi.ts test/ansi-utils.test.ts counted
coding-agent/utils/changelog.ts test/changelog.test.ts counted
coding-agent/utils/git.ts test/git-ssh-url.test.ts counted
coding-agent/core/tools/path-utils.ts test/path-utils.test.ts counted
tui/keys.ts test/keys.test.ts counted
tui/utils.ts test/truncate-to-width.test.ts, test/regression-regional-indicator-width.test.ts counted
coding-agent/utils/mime.ts test/image-process.test.ts mixed — excluded (2 of 3 cases test processImage/original)
coding-agent/utils/version-check.ts test/version-check.test.ts mixed — excluded (fetch cases mock the original; not separable per-file)
coding-agent/core/export-html/ansi-to-html.ts test/export-html-whitespace.test.ts mixed — excluded (asset-grep + tool-renderer/original dominate)
coding-agent/core/tools/truncate.ts no dedicated test (exercised via tools.test.ts, subject is the read/edit factory/original)
coding-agent/core/tools/edit-diff.ts no dedicated test (edit-tool tests' subject is the edit tool/original)

CLI conformance (black-box, against the pidgin binary)

  • CLI conformance: 15/15 pass against target/release/pidgin (pass delta ±0, fail delta ±0).
File Passing Failing Skipped
packages/coding-agent/test/session-file-invalid.test.ts 1 0 0
packages/coding-agent/test/session-id-readonly.test.ts 7 0 0
packages/coding-agent/test/startup-session-name.test.ts 2 0 0
packages/coding-agent/test/stdout-cleanliness.test.ts 5 0 0

The four repointed coding-agent CLI test files spawn the compiled pidgin binary via $PIDGIN_BIN instead of pi's own cli.ts. This is a separate signal from the module smoke table and is never folded into the per-package Native count.

@zmaril
zmaril force-pushed the napi-fluessig/keybindings-manager branch from 6b9ecfe to 396d767 Compare July 21, 2026 08:32
@zmaril
zmaril changed the base branch from napi-fluessig/tui-utils to napi-fluessig/module5 July 21, 2026 08:32
@zmaril
zmaril marked this pull request as ready for review July 21, 2026 09:06
@zmaril
zmaril force-pushed the napi-fluessig/module5 branch from 18158ba to e549210 Compare July 21, 2026 22:31
Base automatically changed from napi-fluessig/module5 to main July 21, 2026 22:38
Swap the hand-written `#[napi] pub struct KeybindingsManagerCore` (backing
`packages/tui/src/keybindings.ts`) for a fluessig-generated handle class —
the first opaque-handle (`Shape::Ctor`) swap in the campaign. The class is
authored as a ctor interface in `schema/api.json`: a `ctor` op plus four
`unary` methods (`matches`, `getKeys` infallible; `getConflictsJson`,
`getResolvedBindingsJson` fallible). `regen.sh` (fluessig pinned at 1fed77a)
lowers it to `#[napi] pub struct { core: Arc<KeybindingsManagerCoreImpl> }`
with the ctor building the core via the generated `KeybindingsManagerCoreCore`
trait and each method delegating through `self.core`.

The engine seam lives in `src/core_impl.rs`: `KeybindingsManagerCoreImpl`
wraps one immutable `pidgin_tui::KeybindingsManager` and routes to the SAME
resolution logic the hand-written class called, reproducing its exact JSON
parse-error messages via `anyhow`. The hand-written struct/impl and its two
input DTOs are deleted from `tui_components.rs`.

This proves the `Shape::Ctor` handle path end-to-end — no fluessig codegen
gap. The generated `.d.ts` is whole-file byte-identical to the pre-swap
baseline; the class's `.js`/`nativeBinding` entries are byte-identical (the
symbol relocates from the lib.rs tail into the generated export block, as
every prior swap did). The `test/keybindings.test.ts` oracle passes 4/4
against the locally built addon.
@zmaril
zmaril force-pushed the napi-fluessig/keybindings-manager branch from 396d767 to 318fdb9 Compare July 21, 2026 22:59
@zmaril
zmaril merged commit 1c8a16e into main Jul 21, 2026
13 checks passed
@zmaril
zmaril deleted the napi-fluessig/keybindings-manager branch July 21, 2026 23:18
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants