fix(gpu): F-004 AltGr strip → Ctrl+Alt only; run pmacs-protocol tests in CI

Two follow-ups on this session's audit work, both on freshly-merged code.

F-004 hardening (regression fix). The AltGr text-input strip
(`is_layout_text`) gated on *any* command modifier, so it fired on
Alt-alone. On macOS the Option key is reported as Alt and emits printable
text for most letters (Option+x → "≈"), so every GUI Meta binding (M-x,
M-f, M-b, …) was stripped to a self-insert. Tighten the gate to the true
AltGr signature — both Ctrl and Alt (the LCtrl+RAlt the OS synthesizes on
Windows) — so Alt-alone forwards as a Meta chord again while Windows AltGr
still inserts. Strict narrowing of when we strip: no-op on Linux/Windows,
unblocks macOS Option-as-Meta. Test flips the Alt-alone € assertion and
adds the macOS Option+x case.

CI coverage (F-001 residue). `workspace_default_members` is only the root
`pmacs` package, so `cargo test` skipped pmacs-protocol — the shared wire
format the daemon, TUI, and GPU all depend on. Add
`cargo test -p pmacs-protocol --all-targets` to the test job (its ~12
encode/decode + transport-framing tests). All three first-party crates now
run in CI (root pmacs, pmacs-gpu via the render job, now pmacs-protocol).

Validated: fmt clean; clippy -p pmacs-gpu --all-targets clean; pmacs-gpu
tests 42 pass (render tests on the local adapter, PMACS_REQUIRE_GPU=1);
pmacs-protocol 12 pass.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014TXbAwk27agwhrNNrhLi2U
This commit is contained in:
Levi Neuwirth 2026-07-03 14:38:19 -04:00
parent 6acbb98e38
commit 623bd884af
3 changed files with 62 additions and 17 deletions

View File

@ -92,6 +92,13 @@ jobs:
# to the behavior under test.
- run: cargo test --all-targets --no-default-features --features ${{ matrix.lua }} -- --test-threads=1
- run: cargo test --doc --no-default-features --features ${{ matrix.lua }}
# The workspace default member is only the root `pmacs` package, so
# the runs above never execute pmacs-protocol's own tests — the
# shared wire format (encode/decode, transport framing) the daemon,
# TUI, and GPU all depend on. Run them explicitly (Lua-flavor
# independent, so once is enough; runs on every matrix leg cheaply
# off the shared cache).
- run: cargo test -p pmacs-protocol --all-targets
acceptance:
name: M1 Acceptance Gates

View File

@ -78,6 +78,15 @@ commands, e.g. `cargo clippy --workspace --all-targets -- -D warnings` and
`default-members` if the intent is for plain `cargo test` and `cargo clippy` to
cover all first-party crates.
Resolution (PR #75, then extended): the three `pmacs-gpu` lints were fixed and
CI now runs `cargo clippy -p pmacs-gpu --all-targets` (README uses `--workspace`).
That closed the *clippy* blind spot but not the symmetric *test* one — the
default-member scope also means `cargo test` skips `pmacs-protocol`. F-014's
GPU-render job later ran `pmacs-gpu`'s tests; this change adds
`cargo test -p pmacs-protocol --all-targets` to the test job so the shared wire
format (its ~12 encode/decode + transport-framing tests) is finally exercised in
CI. All three first-party crates now run in CI.
### F-002 - Medium - `--all-features` is not buildable because Lua features are mutually exclusive
Evidence:
@ -153,6 +162,19 @@ text input from command chords. Add tests for AltGr-style printable input. If th
wire protocol needs it, add an AltGraph modifier bit rather than folding it into
Ctrl/Alt.
Resolution (PR #76, then hardened): a keypress that produced printable
`key.text` while a command modifier was held is reclassified as text input and
the modifiers are stripped (`is_layout_text`). The first cut gated on *any*
command modifier, which over-reached: on macOS the `Option` key is reported as
`Alt` and emits printable text for most letters, so `Option+x` was stripped to a
plain insert — swallowing every GUI Meta binding (`M-x`, `M-f`, …) on that
platform. The gate is now the true AltGr signature — **both `Ctrl` and `Alt`**
(the LCtrl+RAlt the OS synthesizes for AltGr on Windows) — so `Alt`-alone forwards
as a Meta chord again while Windows AltGr still inserts. A protocol AltGraph bit
was *not* added; the text-presence heuristic is sufficient and layout-agnostic.
Not locally validatable on macOS (no box) — a narrowing of when we strip, so
low-risk on Linux/Windows.
### F-005 - Medium - Package install paths collide for namespaced packages with the same basename
Evidence:

View File

@ -800,13 +800,14 @@ impl ApplicationHandler<AppEvent> for App {
// AltGr / international text (audit F-004). winit reports
// the text a keypress produces; when a keypress yields
// printable text *while* Ctrl/Alt is held — AltGr is
// Ctrl+Alt on Windows and some layouts — it's text input,
// not a command chord. Strip the Ctrl/Alt (keep Shift) so
// it inserts (through the plain-text path, or the daemon's
// SelfInsert while a prompt is open) instead of being
// routed to the keymap. On platforms where AltGr isn't
// Ctrl/Alt this is a no-op (the chord was already plain).
// printable text *while both Ctrl and Alt* are held — the
// AltGr signature on Windows (LCtrl+RAlt) — it's text
// input, not a command chord. Strip those modifiers (keep
// Shift) so it inserts (through the plain-text path, or the
// daemon's SelfInsert while a prompt is open) instead of
// being routed to the keymap. Alt alone is left intact so
// macOS Option-as-Meta still reaches the keymap; on layouts
// where AltGr isn't Ctrl+Alt this is a no-op.
if matches!(pkey, ProtocolKey::Char(_))
&& is_layout_text(key.text.as_deref(), pmods)
{
@ -4982,16 +4983,25 @@ fn is_command_chord(key: ProtocolKey, mods: Modifiers) -> bool {
) && (mods.contains(Modifiers::CTRL) || mods.contains(Modifiers::ALT))
}
/// Whether a keypress is **layout text input carried under a command
/// modifier** — the `AltGr` case (audit F-004): winit produced printable
/// `text` while `Ctrl`/`Alt` is held. On layouts/platforms where `AltGr`
/// is `Ctrl+Alt` (Windows), the produced character would otherwise be
/// misclassified as a command chord; when this is true the caller strips
/// the command modifiers so it inserts. Returns `false` for genuine
/// command chords (which produce no text, or a control char) and for
/// plain text (no command modifier — already handled).
/// Whether a keypress is **`AltGr` layout text** (audit F-004): winit
/// produced printable `text` while **both `Ctrl` and `Alt`** are held.
/// `AltGr` is `Ctrl+Alt` on Windows (the OS synthesizes LCtrl+RAlt), and
/// on such layouts the produced character (`@`, `€`, `{`, …) would
/// otherwise be misclassified as a command chord; when this is true the
/// caller strips the command modifiers so it inserts.
///
/// The gate is deliberately `Ctrl+Alt`, **not** "any command modifier":
/// `Alt` alone is *not* `AltGr`. On macOS the `Option` key is reported as
/// `Alt` and produces printable text for most letters (`Option+x` → "≈"),
/// but Option-as-Meta is exactly how the GUI reaches `M-x` / `M-f` / … —
/// stripping `Alt`-alone would swallow every macOS Meta chord. Genuine
/// `AltGr` needs both modifiers, so requiring both leaves `Alt`-alone
/// (macOS `Option`, plain `Meta`) to forward as command chords. Returns
/// `false` for genuine command chords (no text, or a control char) and
/// for plain text (no command modifier — already handled).
fn is_layout_text(text: Option<&str>, mods: Modifiers) -> bool {
!is_plain_text_modifiers(mods)
mods.contains(Modifiers::CTRL)
&& mods.contains(Modifiers::ALT)
&& text.is_some_and(|t| !t.is_empty() && t.chars().all(|c| !c.is_control()))
}
@ -6074,8 +6084,14 @@ mod tests {
// text; that's text input, so the caller strips the modifiers.
let ctrl_alt = Modifiers::CTRL | Modifiers::ALT;
assert!(is_layout_text(Some("@"), ctrl_alt));
assert!(is_layout_text(Some(""), Modifiers::ALT));
assert!(is_layout_text(Some("{"), ctrl_alt));
assert!(is_layout_text(Some(""), ctrl_alt)); // AltGr+e on many layouts
// Alt ALONE is not AltGr. On macOS the Option key is Alt and emits
// printable text (Option+x → "≈"), but Option-as-Meta is how the
// GUI reaches M-x / M-f — leave it intact so it forwards as a
// command chord instead of self-inserting the symbol.
assert!(!is_layout_text(Some(""), Modifiers::ALT)); // macOS Option+x → M-x
assert!(!is_layout_text(Some(""), Modifiers::ALT));
// Genuine command chords produce no text (or a control char) —
// not layout text, so they still route to the keymap.
assert!(!is_layout_text(None, Modifiers::CTRL)); // C-a etc.