authorgravatar for git@paperclover.netclover caruso <git@paperclover.net> 2026-10-02 12:16:27-07:00
committergravatar for git@paperclover.netclover caruso <git@paperclover.net> 2026-10-02 12:53:12-07:00
log5df173732775356dc2a04025c303d4e6c23890f9
tree62d42afc20dc0fd6da58c369013df57a740b7e68
parentf837389ba02268ce5ee180e09bba96a337be406b
signature Signed by SSH key SHA256:52mNGHRsVFBDED9IAX5pe+LRWUefqTbxEReunq21QvU

fix: New Page and New Section have chords the browser lets through (#65)

Chrome, Firefox and Safari keep Ctrl/⌘ with N, T and W for their own windows and tabs whatever a page asks, so on the web New Page is Ctrl+Alt+N (OneNote 2010's other New Page chord) and New Section Ctrl+Alt+T, ⌘⌥ on a Mac, in the menus, the palette and Keyboard options. The desktop keeps Ctrl+N and Ctrl+T. A Mac reports ⌘⌥ with a letter as the dead key Option makes, so the glue names it by its key. Assisted-by: claude-opus-5.5

4 files changed, 26 insertions(+), 6 deletions(-)

arc/platforms.md+2-1
...@@ -174,7 +174,8 @@ keyboard, the toolbar and the macOS menu bar all run commands from it....@@ -174,7 +174,8 @@ keyboard, the toolbar and the macOS menu bar all run commands from it.
174 it is uncertain, as one whose answer was lost, and it goes again on top of another app's174 it is uncertain, as one whose answer was lost, and it goes again on top of another app's
175 write. The sync popup says the folder isn't locked.175 write. The sync popup says the folder isn't locked.
176- Menus are the kit's own, as on Linux, with the PC's chords and ⌘ for Ctrl on a Mac; the176- Menus are the kit's own, as on Linux, with the PC's chords and ⌘ for Ctrl on a Mac; the
177 browser keeps its own window and tab chords. Dialogs are the browser's; Insert, and Open177 browser keeps its own window and tab chords, so New Page and New Section add Alt to
178 theirs. Dialogs are the browser's; Insert, and Open
178 without a folder to give, ask for files to copy in; printing downloads the PDF. Servers and179 without a folder to give, ask for files to copy in; printing downloads the PDF. Servers and
179 recording are still to come.180 recording are still to come.
180- Spelling is Hunspell's dictionaries through `spellbook`, each fetched beside the module the181- Spelling is Hunspell's dictionaries through `spellbook`, each fetched beside the module the
crates/snowbound/src/commands.rs+13
...@@ -622,9 +622,22 @@ pub fn command(id: Id) -> &'static Command {...@@ -622,9 +622,22 @@ pub fn command(id: Id) -> &'static Command {
622 .expect("Every command has a row")622 .expect("Every command has a row")
623}623}
624624
625/// Commands whose chords the browser keeps for itself (Chrome, Firefox and Safari never pass
626/// a page Ctrl or ⌘ with N, T or W), and theirs there: Alt joins them, as Ctrl+Alt+N is
627/// OneNote 2010's other New Page.
628#[cfg(target_arch = "wasm32")]
629const BROWSER: [(Id, &[Chord]); 2] = [
630 (Id::NewSection, &[cmd('t').option()]),
631 (Id::NewPage, &[cmd('n').option()]),
632];
633
625impl Command {634impl Command {
626 /// The table's chords on `platform`, which the user's may replace.635 /// The table's chords on `platform`, which the user's may replace.
627 pub fn chords(&self, platform: Platform) -> &'static [Chord] {636 pub fn chords(&self, platform: Platform) -> &'static [Chord] {
637 #[cfg(target_arch = "wasm32")]
638 if let Some((_, chords)) = BROWSER.iter().find(|(id, _)| *id == self.id) {
639 return chords;
640 }
628 match platform {641 match platform {
629 Platform::MacOs => self.mac,642 Platform::MacOs => self.mac,
630 Platform::Gtk | Platform::Windows => self.pc,643 Platform::Gtk | Platform::Windows => self.pc,
crates/snowbound/src/web.rs+2-1
...@@ -1050,7 +1050,8 @@ fn queue(input: Input) {...@@ -1050,7 +1050,8 @@ fn queue(input: Input) {
1050#[wasm_bindgen]1050#[wasm_bindgen]
1051pub fn takes(key: &str, held: u8) -> bool {1051pub fn takes(key: &str, held: u8) -> bool {
1052 let command = held & if MAC.get() { 8 } else { 2 } != 0;1052 let command = held & if MAC.get() { 8 } else { 2 } != 0;
1053 if !command {1053 // The browser's own chords take Ctrl or ⌘ without Alt (see `commands::BROWSER`).
1054 if !command || held & 4 != 0 {
1054 return true;1055 return true;
1055 }1056 }
1056 !matches!(1057 !matches!(
crates/snowbound/web/glue.js+9-4
...@@ -597,13 +597,18 @@ export function attach(module) {...@@ -597,13 +597,18 @@ export function attach(module) {
597 );597 );
598598
599 input.addEventListener("keydown", (event) => {599 input.addEventListener("keydown", (event) => {
600 if (event.isComposing || ["Process", "Dead", "Unidentified"].includes(event.key)) return;600 // A Mac reports ⌘⌥N as Option makes N (a dead key), where chords name the key itself.
601 const key =
602 event.metaKey && event.altKey && /^(Key[A-Z]|Digit\d)$/.test(event.code)
603 ? event.code.slice(-1).toLowerCase()
604 : event.key;
605 if (event.isComposing || ["Process", "Dead", "Unidentified"].includes(key)) return;
601 const bits = modifiers(event);606 const bits = modifiers(event);
602 if (!wasm.takes(event.key, bits)) return;607 if (!wasm.takes(key, bits)) return;
603 event.preventDefault();608 event.preventDefault();
604 const shortcut = event.ctrlKey || event.metaKey;609 const shortcut = event.ctrlKey || event.metaKey;
605 const text = [...event.key].length === 1 && !shortcut ? event.key : undefined;610 const text = [...key].length === 1 && !shortcut ? key : undefined;
606 wasm.key(event.key, text, bits);611 wasm.key(key, text, bits);
607 });612 });
608 input.addEventListener("keyup", (event) => wasm.modifiers(modifiers(event)));613 input.addEventListener("keyup", (event) => wasm.modifiers(modifiers(event)));
609 input.addEventListener("compositionupdate", (event) => wasm.compose(event.data));614 input.addEventListener("compositionupdate", (event) => wasm.compose(event.data));