From 881624d5e31d61398f48c2794b2378a9d91c32b4 Mon Sep 17 00:00:00 2001 From: Xuepoo Date: Sun, 25 Oct 2026 19:44:41 +0800 Subject: [PATCH 1/2] [CTX-0060] [CTX-1559] docs(ref): document plugin keymap reads --- sdk/reference/keymaps.md | 285 ++++++++++++++++++++++++++------------- 1 file changed, 188 insertions(+), 97 deletions(-) diff --git a/sdk/reference/keymaps.md b/sdk/reference/keymaps.md index e58c953..5ba64ff 100644 --- a/sdk/reference/keymaps.md +++ b/sdk/reference/keymaps.md @@ -1,6 +1,6 @@ --- title: bitty.keymaps Reference -description: L1 key-binding suggestion capture model with admission caps and typed denials +description: Live keymap reads and key-binding suggestion capture with bounded paging and typed denials category: reference audience: plugin-author document_type: reference @@ -11,141 +11,232 @@ sidebar_order: 90 # bitty.keymaps Reference -> Status: **draft**. Normative surface detail lives in the accepted -> [Plugin API v1 Lua Surface RFC](../plugin-api-v1-lua-surface-rfc.md); -> executable behavior lives in the `bitty` repository -> (`crates/bitty-lua/src/host.rs`). This page states no behavior beyond what -> those sources pin. +> Status: **draft**. The accepted [Plugin API v1 Lua Surface RFC](../plugin-api-v1-lua-surface-rfc.md) defines the `suggest` spelling and its precedence model. The live `list` read is documented here from Core `bitty@7d98f53a` and the `plugin_keymaps` runtime tests. This page states implementation evidence, not a compatibility promise for a future API revision. ## Purpose and scope -`bitty.keymaps` is the L1 Control surface for suggesting key bindings that -invoke the plugin's own commands. Suggestions never override user or -workspace mappings: the bridge captures each suggestion during `init.lua` -activation, and chord-grammar validation, precedence, and conflict -diagnostics are applied host-side after activation. This page covers -`bitty.keymaps.suggest` (WIRED bridge capture, LUA-OQ-5, CTX-0707), the -only v1 function in this namespace. +`bitty.keymaps` has two different surfaces: + +- `bitty.keymaps.list(options)` is a bounded, read-only view of the live + resolved keymap registry. It reads user and shipped-default bindings through + the `runtime.keymaps.read` capability. +- `bitty.keymaps.suggest(def)` is the separate registration-time surface for + capturing a plugin's proposed binding. A suggestion is not a row in + `list`, and capture is not publication or application of a binding. + +The list read contains registry data only. It never exposes configuration +paths, raw configuration text, arguments, environment values, URLs, terminal +content, clipboard content, plugin-store values, or host diagnostics. Rows are +read from the live source at call time; a reload or republished table is +visible on the next call rather than through a cached page. ## Signature ```lua +bitty.keymaps.list(options?) -> page bitty.keymaps.suggest(def) -> handle ``` -The `keymaps` table in the bridge (`crates/bitty-lua/src/host.rs`, -`bitty@7da6d6f`) exposes exactly one function, `suggest`. A successful -call pushes a `KeymapSuggestion { chord, command, when }` onto the -generation-scoped `RegistrationCapture` in suggestion order and returns -the dense 1-based capture index as the handle. There is no removal -surface, so the index is stable within the generation. +`list` accepts no argument, `nil`, or an options table. `suggest` accepts the +definition described in [Suggestion capture](#suggestion-capture). The two +functions have separate capability and lifecycle behavior; do not use one as +evidence that the other is available. ## Params -`def` is a table with the following fields (RFC vocabulary; the bridge -checks shape only — the shipped chord grammar itself is validated -host-side at application time, LUA-OQ-5): +### `list` options + +The only recognized options are integer `offset` and integer `limit`. +Unknown fields are ignored. The options table is validated by the Lua bridge +before the host source is called. + +| Option | Default | Accepted range | Meaning | +| -------- | ------- | -------------- | ----------------------------------------------- | +| `offset` | `0` | `0..=1024` | Zero-based start in resolved-table order. | +| `limit` | `256` | `1..=256` | Maximum number of rows requested for this page. | -| `def` field | Type | Required | Rule | -| ----------- | ------ | -------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `chord` | string | yes | Non-empty bounded chord string (bridge: `required_string`, at most `REGISTRATION_MAX_KEYMAP_CHORD_BYTES`, 128 bytes, else `E_DEF_INVALID`). | -| `command` | string | yes | Non-empty bounded command name for a command registered by the same generation (bridge: `required_string`, at most `REGISTRATION_MAX_KEYMAP_COMMAND_BYTES`, 128 bytes, else `E_DEF_INVALID`). | -| `when` | string | no | Absent normalizes to `"global"`; an explicit `"global"` is accepted; any other value is refused at the bridge with `E_DEF_INVALID` (RFC: a registration error naming the supported context). | +`offset` and `limit` are paging bounds, not filters. The API has no path, +command-name, URL, pattern, or free-form string option. -A non-table `def` is refused with `E_DEF_INVALID`. +### Suggestion capture + +The accepted RFC defines: + +```lua +def = { + chord = string, + command = string, + when? = string, +} +``` + +`command` names a command registered by the same plugin generation. In v1, +`when` is absent or `"global"`; the shipped configuration chord grammar and +the `(when, normalized chord)` identity apply when suggestions are evaluated. +The current bridge validates the shape and captures the definition during +`init.lua` activation. Host-side grammar validation and binding application +are separate stages. ## Returns -The integer handle: the 1-based position of the suggestion in the -generation capture (`capture.keymaps.len()` after pushing). Host-side -application (precedence, conflict diagnostics) is follow-up work; the -handle carries no host meaning yet. +### `list` + +`page` is a table with these fields: + +| Field | Type | Meaning | +| ----------- | ------- | ---------------------------------------------------------------------------------------------------- | +| `entries` | array | Canonical rows in resolved-table order. | +| `total` | integer | Number of addressable rows, capped at `1024`; this is not the number returned in `entries`. | +| `offset` | integer | The validated offset echoed by the response. | +| `truncated` | boolean | Whether an unreturned tail exists because of the page, addressable-row, or serialized-payload bound. | + +An offset at or past `total` returns an empty `entries` array and +`truncated = false`. A page is also bounded to a 64 KiB canonical serialized +payload; whole rows are dropped from the tail to stay within that bound. + +Each entry has the following canonical fields: + +| Field | Presence | Meaning | +| --------- | -------- | -------------------------------------------------------------------------------------------------- | +| `chord` | required | Canonical chord spelling, such as `alt+h` or `ctrl+shift+v`. | +| `action` | required | Canonical Core action spelling, or `command:owner:command` for a plugin command binding. | +| `source` | required | `"default"` for a shipped default or `"user"` for a user override. | +| `context` | required | `"global"` in the current implementation. | +| `title` | optional | Bounded static Core action description; present for a Core action and absent for a `command:` row. | +| `command` | optional | The validated qualified `owner:command` target; present only when `action` is `command:`. | + +`title` and `command` are mutually exclusive. A malformed canonical row is +dropped rather than echoed; an optional title may be sanitized to one bounded +line, while the binding identity fields remain strict. + +### `suggest` + +The current bridge returns a positive integer handle: the 1-based position of +the captured suggestion within that plugin generation. It has no host-side +binding meaning and there is no removal function. Capture is generation-scoped +and is disposed with the generation. ## Errors -Denials arrive as catchable Lua error tables (`class` / `code` / `message`, -per `BridgeError::to_error`); match on `code`. +Denials arrive as catchable Lua error tables with `class`, `code`, and +`message`; match on the stable `code`, not message text. + +### `list` read errors + +| `code` | `class` | When | +| --------------------- | ------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `E_CAPABILITY_DENIED` | `runtime` | The plugin lacks `runtime.keymaps.read`, or safe policy denies the VM. The read grant is independent of `runtime.inspect`, `runtime.configure`, and UI grants. | +| `E_NOT_IMPLEMENTED` | `runtime` | The host has no keymap source. The namespace remains callable, but the default host fails closed instead of returning an empty registry. | +| `E_VALUE_TYPE` | `validation` | `options` is not a table, or `offset`/`limit` is not an integer. Validation happens before the host call. | +| `E_DEF_LIMIT` | `validation` | `offset` is outside `0..=1024`, `limit` is outside `1..=256`, the serialized page exceeds 64 KiB, or the per-generation call budget is exhausted. | + +An `E_CAPABILITY_DENIED` result does not reveal whether a keymap source exists. +Likewise, a host without a source reports `E_NOT_IMPLEMENTED` rather than +silently returning an empty page. -| `code` | `class` | When | -| ------------------ | ------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `E_DEF_INVALID` | `validation` | Malformed suggestion at the bridge: non-table `def`, missing or empty `chord` / `command`, a field exceeding its 128-byte cap, or a `when` other than absent / `"global"` (`host.rs`). | -| `E_DEF_LIMIT` | `validation` | The 129th suggestion: `capture.keymaps.len() >= REGISTRATION_MAX_KEYMAP_SUGGESTIONS` (128, mirroring `REGISTRATION_MAX_COMMANDS`) (`host.rs`). | -| Registration error | `validation` | Suggestion after `init.lua` returns; a `command` not registered by the same generation; a non-`"global"` context (RFC, LUA-OQ-5 / LUA-OQ-12 — the bridge implements the `when` case as `E_DEF_INVALID`). | +### `suggest` capture errors + +| `code` | `class` | When | +| ------------------ | ------------ | ------------------------------------------------------------------------------------------------------------------------------ | +| `E_DEF_INVALID` | `validation` | `def` is not a table, a required `chord` or `command` is missing/empty/over-bound, or `when` is neither absent nor `"global"`. | +| `E_DEF_LIMIT` | `validation` | The 129th suggestion exceeds the 128-suggestion generation capture bound. | +| Registration error | `validation` | Capture is attempted after activation, or `command` does not name a command registered by the same generation. | ## Capability -None. The extension-level split of the accepted Lua surface RFC lists -`bitty.keymaps.suggest` as L1 Control gated by `none (suggestion only)`. -The accepted precedence (`user > workspace > first-party/default > plugin -suggestion`) applies host-side, and chord conflicts produce diagnostics -for user resolution rather than following load order (RFC, LUA-OQ-5). +`bitty.keymaps.list` requires the manifest capability: + +```toml +[capabilities] +runtime.keymaps.read = true +``` + +This is a narrow read head. It does not grant `runtime.inspect`, +`runtime.configure`, UI capabilities, or access to any other runtime data. +Conversely, `runtime.inspect` does not imply `runtime.keymaps.read`. + +`bitty.keymaps.suggest` is the accepted RFC's L1 Control suggestion surface +and is described as `none (suggestion only)` rather than as a registry-read +capability. Its capture is still constrained by activation lifecycle and +generation ownership. ## Example -Verified against `crates/bitty-lua/tests/lua_parity.rs` -(`keymaps_suggest_captures_with_global_default`; absent `when` -normalizes to `"global"`): +The following read and paging shapes are covered by +`crates/bitty-runtime/tests/plugin_keymaps.rs` in Core `7d98f53a`: ```lua -local h1 = bitty.keymaps.suggest({ chord = "ctrl+k", command = "palette:open" }) -local h2 = bitty.keymaps.suggest({ chord = "ctrl+s", command = "palette:save", when = "global" }) --- host observes capture.keymaps[1] == { chord = "ctrl+k", command = "palette:open", when = "global" } --- and capture.keymaps[2].when == "global" +local ok, page = pcall(bitty.keymaps.list, { offset = 2, limit = 2 }) +if not ok then + return page.code +end + +for _, row in ipairs(page.entries) do + -- Required: chord, action, source, context. + -- Optional: title for a Core action, command for a plugin command row. + print(row.chord, row.action, row.source, row.context) +end + +-- page.total is the bounded addressable total, not #page.entries. +-- page.offset is 2; page.truncated reports an unreturned tail. ``` -Malformed shapes (`keymaps_suggest_rejects_malformed`) and the 129th -suggestion (`keymaps_suggest_enforces_cap_at_bridge`, `E_DEF_LIMIT`) are -pinned by the same file. Applying suggestions (precedence, conflict -diagnostics) has no covering `bitty-lua` test and remains follow-up work; -any such example would be illustrative, never verified. +The tests also prove that the source is read live: replacing the published +table is visible on the next call. They cover grant denial, canonical row +fields, offset/limit paging, malformed options, the addressable-row cap, and +the per-generation call budget. The bridge tests cover the over-64 KiB payload +failure and the default `E_NOT_IMPLEMENTED` host seam. + +Suggestion capture is separately covered by the Lua parity tests +`keymaps_suggest_captures_with_global_default`, +`keymaps_suggest_rejects_malformed`, and +`keymaps_suggest_enforces_cap_at_bridge`. ## Limits -- At most `REGISTRATION_MAX_KEYMAP_SUGGESTIONS` (128) captured suggestions - per `init.lua`; the 129th fails closed with `E_DEF_LIMIT` (`host.rs`, - CTX-0707 admission bound). -- Field byte caps at the bridge: `chord` 128, `command` 128 - (`REGISTRATION_MAX_KEYMAP_CHORD_BYTES`, - `REGISTRATION_MAX_KEYMAP_COMMAND_BYTES`). -- `when` must be absent or `"global"` in v1; any other value is refused - (RFC, LUA-OQ-5). -- The shipped chord grammar (trimmed case-insensitive modifiers joined - with `+`, named keys from the shipped vocabulary, single-character keys - requiring a modifier) is validated host-side at application, never at - the bridge (RFC, LUA-OQ-5). -- Identity for precedence and conflict detection is `(when, normalized -chord)` (RFC). -- Suggestion calls are valid only while the plugin generation is - activating; after `init.lua` returns, any later suggestion attempt is a - registration error (RFC, LUA-OQ-12). -- Suggestions are generation-scoped captures owned by `(PluginId, -generation)` and disposed with it. +- A single page requests at most `256` rows. +- At most `1024` rows are addressable through the read API. Rows beyond that + cap are not reachable; `total` is capped and `truncated` reports the tail. +- The canonical serialized page is at most `64 KiB`; tail rows are removed as + whole rows to preserve canonical entries. +- `offset` is `0..=1024`; `limit` is `1..=256`. +- A plugin generation may make at most `16` keymap reads per second. Further + calls fail with `E_DEF_LIMIT` rather than being silently accepted. +- Rows are converted from typed Core bindings at call time. The read API + intentionally omits plugin suggestions; it is not a combined view of every + possible dispatch source. +- Suggestion capture allows at most `128` suggestions per generation. The + 129th call fails with `E_DEF_LIMIT`. +- Suggestions are valid only during activation, are owned by the plugin + generation, and have no removal surface. ## Compatibility and versioning -Draft reference: this page states the candidate v1 spellings and carries no -compatibility promise while its status is draft. The target contract is the -[Compatibility policy](../plugin-api-v1-lua-surface-rfc.md#compatibility-policy) -in the accepted Plugin API v1 Lua Surface RFC: the surface is stable within -`1.x`, additions ship as minor versions, and removals or narrowings require a -major version, gated by the manifest `compat.plugin-api` range against the -runtime `bitty.api_version`. +This page is draft and carries no compatibility promise. The accepted RFC's +compatibility policy applies to the accepted `suggest` surface: additions are +minor-version changes within `1.x`, while removals or narrowings require a +major version and manifest compatibility-range review. The `list` read and +`runtime.keymaps.read` capability are current Core implementation evidence at +`7d98f53a`; their presence here does not turn the separate candidate or +follow-up publication behavior for suggestions into an accepted stable +contract. ## References - [Plugin API v1 Lua Surface RFC](../plugin-api-v1-lua-surface-rfc.md) - (Accepted; `bitty.keymaps.suggest` spelling, `def` schema, chord - grammar, precedence, LUA-OQ-5, LUA-OQ-12) -- [Lua Reference Overview](overview.md) (error contract, capability gating, - executability rule, L1/L2 split) -- [Lua API Reference](README.md) (route table; `keymaps.md` row; capture - versus host-side apply) -- `bitty` `crates/bitty-lua/src/host.rs` (`keymaps` `suggest` capture, - `KeymapSuggestion`, `RegistrationCapture`, - `REGISTRATION_MAX_KEYMAP_SUGGESTIONS`, - `REGISTRATION_MAX_KEYMAP_CHORD_BYTES`, - `REGISTRATION_MAX_KEYMAP_COMMAND_BYTES`, `E_DEF_INVALID` / `E_DEF_LIMIT`) -- `bitty` `crates/bitty-lua/tests/lua_parity.rs` - (`keymaps_suggest_captures_with_global_default`, - `keymaps_suggest_rejects_malformed`, - `keymaps_suggest_enforces_cap_at_bridge`) + (accepted `suggest` spelling, definition shape, chord grammar, precedence, + and `none (suggestion only)` capability classification) +- [Lua Reference Overview](overview.md) (error table, capability gating, + executability rule, and L1/L2 split) +- [Lua API Reference](README.md) (namespace route table and reference status) +- `bitty@7d98f53a` `crates/bitty-lua/src/keymaps.rs` (query bounds, canonical + row fields, page fields, payload and call bounds) +- `bitty@7d98f53a` `crates/bitty-lua/src/host.rs` (options validation, grant + bridge, typed failures, and `suggest` capture) +- `bitty@7d98f53a` `crates/bitty-runtime/src/plugin_runtime/services.rs` + (live source conversion, `runtime.keymaps.read` gate, and exclusion of + suggestions from the read registry) +- `bitty@7d98f53a` `crates/bitty-runtime/tests/plugin_keymaps.rs` (real-plugin + read, paging, grants, canonical rows, bounds, and budget evidence) +- `bitty` `crates/bitty-lua/tests/lua_parity.rs` (suggestion capture and bridge + error evidence) From 86d00390ffb37591607063b08a76912c98a52df0 Mon Sep 17 00:00:00 2001 From: Xuepoo Date: Sun, 25 Oct 2026 19:53:27 +0800 Subject: [PATCH 2/2] [CTX-0060] docs(ref): correct keymap payload evidence --- sdk/reference/keymaps.md | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/sdk/reference/keymaps.md b/sdk/reference/keymaps.md index 5ba64ff..bbc2a9e 100644 --- a/sdk/reference/keymaps.md +++ b/sdk/reference/keymaps.md @@ -184,8 +184,10 @@ end The tests also prove that the source is read live: replacing the published table is visible on the next call. They cover grant denial, canonical row fields, offset/limit paging, malformed options, the addressable-row cap, and -the per-generation call budget. The bridge tests cover the over-64 KiB payload -failure and the default `E_NOT_IMPLEMENTED` host seam. +the per-generation call budget. The bridge implementation bounds the serialized +payload by dropping whole rows from the tail to stay within `64 KiB`, and the +host `fits_budget()` guard checks the payload budget. The bridge tests cover the +default `E_NOT_IMPLEMENTED` host seam. Suggestion capture is separately covered by the Lua parity tests `keymaps_suggest_captures_with_global_default`,