Skip to content

Generator: Kanata decides what is a key, so key names are never typed as letters - #1329

Merged
malpern merged 1 commit into
masterfrom
fix/generator-unknown-keys
Oct 20, 2026
Merged

malpern merged 1 commit into
masterfrom
fix/generator-unknown-keys

Conversation

@malpern

@malpern malpern commented Oct 20, 2026

Copy link
Copy Markdown
Owner

The generator decided "key or word to type" from a hand-kept list in KanataKeyConverter.shouldConvertToMacro that missed real Kanata keys, so .keystroke("f21") became (macro f 2 1) — a rule that silently typed three characters instead of being refused (found while fixing test fixtures for phase 6).

What

  • BackendCapabilityProfile.knowsKey(_:): true/false from the bridge (a nil answer for the name while the bridge answers for a means "not a Kanata key"), nil when there is no bridge.
  • shouldConvertToMacro asks it first: a name Kanata knows is emitted as that key, so admission refuses it with a reason when the session can't post it; only a word Kanata doesn't know is typed. Typing words (hello → (macro h e l l o)) is an existing, tested feature and is unchanged. The hand list stays as the no-bridge fallback.

Scope note

The card proposed refusing every unknown name, but that would break the intended text-typing feature; the actual bug was real key names falling into the word path, which this fixes at the source.

Tests

GeneratorKeyNameTests: keys vs words through the converter; knowsKey answers and its no-bridge nil; an f9 → f21 rule is refused by reviewAdmission with the presenter sentence and by the save gate, never written as a macro. KEYPATH_TEST_ENFORCE_CLEAN_SUMMARY=1 Scripts/run-tests-safe.sh: 5,796 passed, 0 app warnings/errors, 0 build warnings. Doc note in rules-admission-review.md §4.5.

🤖 Generated with Claude Code

… as letters

The generator decided "key or word to type" from a hand-kept list that
missed real Kanata keys, so `.keystroke("f21")` became `(macro f 2 1)` and a
rule silently typed three characters instead of being refused. It now asks
the bridge (BackendCapabilityProfile.knowsKey): a name Kanata knows is
emitted as that key and goes through admission like any output, refused
with "sends f21, which the current backend can't send" when the session
cannot post it; only a word Kanata does not know ("hello") is typed. The
list remains the fallback when no bridge is available.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Oct 20, 2026 •

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review ✅ Completed 2026-10-20T15:13:06.698994Z 548b6ac PR opened
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@claude

claude Bot commented Oct 20, 2026

Copy link
Copy Markdown

Review of #1329

One behavior risk and two minor points.

  1. Ordinary words that are also Kanata key names will now be sent as a key press, not typed. knowsKey answers true for any name the bridge recognizes, and that answer now outranks the fallback list in shouldTreatAsMacro. Kanata has many names that are also plain English: help, menu, copy, paste, cut, find, open, undo, select, stop, play, mute, sleep, power, pause, home, end, up, down. A keystroke action whose text is one of these used to become (macro h e l p). If the bridge knows the name, it now becomes the single key. That fixes f21, but it silently changes the output for real words. Please either confirm this is intended and add a test, or restrict the bridge path to names that aren't alphabetic-only words, for example names containing digits or on an explicit allowlist of non-text keys. Tests for a couple of these words (help, end) would pin the intended behavior either way.

  2. nil is overloaded in knowsKey. The init docs say query returns nil for "no answer", but knowsKey reads nil as "not a Kanata key" once query("a") answers. If the bridge fails or times out for one name but not for a, a real key is cached as false permanently in knownCache and is typed as text. It would be safer to cache only the positive answers, or to document and enforce that the bridge returns nil only for unknown names.

  3. Minor: the no-bridge result isn't cached. Every call re-runs two query calls (key plus the a probe) in the no-bridge case. That is probably cheap but sits on the generator path.

The new tests all call SessionBridgeTestFixture.requireAvailable(), so they skip where there is no bridge. The fallback-list path through knowsKey == nil has no coverage in this diff.

🤖 Generated with Claude Code

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 548b6accc9

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +1831 to +1832
if let known = BackendCapabilityProfile.shared.knowsKey(token) {
return !known

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Canonicalize KeyPath aliases before querying Kanata

When the live bridge is available, KeyPath aliases that Kanata does not recognize verbatim—such as escape, space, return, and command—produce known == false, so this early return bypasses the fallback alias set and the later convertToKanataKey mappings. Existing mappings then generate (macro e s c a p e), (macro s p a c e), etc. instead of esc, spc, ret, or lmet; canonicalize the token before querying the bridge or preserve the alias check ahead of this return.

Useful? React with 👍 / 👎.

@malpern
malpern merged commit 39024e3 into master Oct 20, 2026
8 checks passed
@malpern
malpern deleted the fix/generator-unknown-keys branch October 20, 2026 15:16
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.

1 participant