authorgravatar for git@paperclover.netclover caruso <git@paperclover.net> 2026-10-02 13:00:12-07:00
committergravatar for git@paperclover.netclover caruso <git@paperclover.net> 2026-10-02 13:35:43-07:00
logda750b15d3ea1dddf92d129bfd17e7c4c9f1712d
tree1a6f337be91ece1166df56ed29d59a6ef54517e7
parent5a7c56cdaa6c4de435f0f0c28d29c72b980348bd
signature Signed by SSH key SHA256:52mNGHRsVFBDED9IAX5pe+LRWUefqTbxEReunq21QvU

fix: the web build on a Mac shows chords as ⌘ and ⌥, the keys that run them

A Mac's browser takes the PC's chords with Command for Ctrl, but menus, the palette, tag lists and Keyboard options labelled them Ctrl+. They now read as a Mac's (⌥⌘N), and the symbol face that draws ⌘⌥⇧ is fetched on a Mac at start. Assisted-by: claude-opus-5.5

5 files changed, 32 insertions(+), 11 deletions(-)

arc/platforms.md+1-1
...@@ -173,7 +173,7 @@ keyboard, the toolbar and the macOS menu bar all run commands from it....@@ -173,7 +173,7 @@ keyboard, the toolbar and the macOS menu bar all run commands from it.
173 page has written the file and found nothing else wrote it since it was read; until then173 page has written the file and found nothing else wrote it since it was read; until then
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, which a Mac takes and shows with ⌘ for Ctrl and ⌥ for Alt; the
177 browser keeps its own window and tab chords, so New Page and New Section add Alt to177 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 Open178 theirs. Dialogs are the browser's; Insert, and Open
179 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
crates/snowbound/src/commands.rs+11-1
...@@ -1038,13 +1038,23 @@ impl Chord {...@@ -1038,13 +1038,23 @@ impl Chord {
1038 }1038 }
1039}1039}
10401040
1041/// The platform whose way chords read in here: a Mac's in a browser on one, where ⌘ takes
1042/// the PC's Ctrl chords.
1043pub fn shown() -> Platform {
1044 #[cfg(target_arch = "wasm32")]
1045 if crate::platform::mac() {
1046 return Platform::MacOs;
1047 }
1048 Platform::CURRENT
1049}
1050
1041/// The chord a menu or hint shows for `id` here, empty where it has none.1051/// The chord a menu or hint shows for `id` here, empty where it has none.
1042pub fn shortcut(id: Id) -> String {1052pub fn shortcut(id: Id) -> String {
1043 let chord = match id {1053 let chord = match id {
1044 Id::Tag(place) => tag_chord(place),1054 Id::Tag(place) => tag_chord(place),
1045 id => chords(id).first().copied(),1055 id => chords(id).first().copied(),
1046 };1056 };
1047 chord.map_or_else(String::new, |chord| chord.label(Platform::CURRENT))1057 chord.map_or_else(String::new, |chord| chord.label(shown()))
1048}1058}
10491059
1050impl State {1060impl State {
crates/snowbound/src/keys.rs+10-7
...@@ -72,7 +72,7 @@ fn group(id: Command) -> &'static str {...@@ -72,7 +72,7 @@ fn group(id: Command) -> &'static str {
72}72}
7373
74fn label(chord: Chord) -> String {74fn label(chord: Chord) -> String {
75 chord.label(Platform::CURRENT)75 chord.label(commands::shown())
76}76}
7777
78fn title(id: Command) -> String {78fn title(id: Command) -> String {
...@@ -334,16 +334,19 @@ impl Keyboard {...@@ -334,16 +334,19 @@ impl Keyboard {
334 fn status(&mut self, ui: &mut Ui) {334 fn status(&mut self, ui: &mut Ui) {
335 let theme = ui.theme.clone();335 let theme = ui.theme.clone();
336 let row = theme.font_size * 2.0;336 let row = theme.font_size * 2.0;
337 let mac = Platform::CURRENT == Platform::MacOs;337 // A Mac's browser takes the PC's chords, Option for Alt.
338 let modifiers = match (Platform::CURRENT, commands::shown()) {
339 (Platform::MacOs, _) => "⌘ or ⌃",
340 (_, Platform::MacOs) => "⌘ or ⌥",
341 _ => "Ctrl or Alt",
342 };
338 let text = match &self.recording {343 let text = match &self.recording {
339 None => "Click a shortcut to change it".to_owned(),344 None => "Click a shortcut to change it".to_owned(),
340 Some(recording) => match recording.refused {345 Some(recording) => match recording.refused {
341 None => format!("Type a shortcut for {}", title(recording.command)),346 None => format!("Type a shortcut for {}", title(recording.command)),
342 Some(Refusal::Unusable(chord, Unusable::Typing)) => format!(347 Some(Refusal::Unusable(chord, Unusable::Typing)) => {
343 "{} types text. Include {}",348 format!("{} types text. Include {}", label(chord), modifiers)
344 label(chord),349 }
345 if mac { "⌘ or ⌃" } else { "Ctrl or Alt" }
346 ),
347 Some(Refusal::Unusable(chord, Unusable::Editing)) => {350 Some(Refusal::Unusable(chord, Unusable::Editing)) => {
348 format!("{} moves through text. Type another", label(chord))351 format!("{} moves through text. Type another", label(chord))
349 }352 }
crates/snowbound/src/tags.rs+1-2
...@@ -6,7 +6,6 @@ use accesskit::Role;...@@ -6,7 +6,6 @@ use accesskit::Role;
6use canvas::editor::NoteTag;6use canvas::editor::NoteTag;
7use canvas::gpu::{colorref, tag_sources};7use canvas::gpu::{colorref, tag_sources};
8use canvas::outline::{TagIcon, symbol_name};8use canvas::outline::{TagIcon, symbol_name};
9use draw::edit::Platform;
10use notebook::sidecar::art_name;9use notebook::sidecar::art_name;
11use ui::{Anchor, Axis, Flags, Id, Spec, Ui, children, fill, fit, px};10use ui::{Anchor, Axis, Flags, Id, Spec, Ui, children, fill, fit, px};
12use winit::keyboard::NamedKey;11use winit::keyboard::NamedKey;
...@@ -458,7 +457,7 @@ impl State {...@@ -458,7 +457,7 @@ impl State {
458 None => icon(ui, "icon", Some(crate::art::FONT_COLOR), theme.text),457 None => icon(ui, "icon", Some(crate::art::FONT_COLOR), theme.text),
459 }458 }
460 let text = match commands::tag_chord(place) {459 let text = match commands::tag_chord(place) {
461 Some(chord) => format!("{} ({})", tag.label, chord.label(Platform::CURRENT)),460 Some(chord) => format!("{} ({})", tag.label, chord.label(commands::shown())),
462 None => tag.label.clone(),461 None => tag.label.clone(),
463 };462 };
464 label(ui, "label", &text, tag);463 label(ui, "label", &text, tag);
crates/snowbound/src/web.rs+9
...@@ -203,6 +203,11 @@ thread_local! {...@@ -203,6 +203,11 @@ thread_local! {
203 static LANGUAGE: RefCell<String> = const { RefCell::new(String::new()) };203 static LANGUAGE: RefCell<String> = const { RefCell::new(String::new()) };
204}204}
205205
206/// Whether the browser runs on a Mac, where Command takes the editing chords.
207pub fn mac() -> bool {
208 MAC.get()
209}
210
206/// The page's canvas, which frames are drawn into.211/// The page's canvas, which frames are drawn into.
207pub fn canvas() -> web_sys::HtmlCanvasElement {212pub fn canvas() -> web_sys::HtmlCanvasElement {
208 use wasm_bindgen::JsCast;213 use wasm_bindgen::JsCast;
...@@ -920,6 +925,10 @@ pub async fn start(...@@ -920,6 +925,10 @@ pub async fn start(
920 }925 }
921 STATE.with_borrow_mut(|slot| *slot = Some(state));926 STATE.with_borrow_mut(|slot| *slot = Some(state));
922 attach(module);927 attach(module);
928 // A Mac's chords read ⌘⌥⇧, which only a fallback face draws.
929 if MAC.get() {
930 fetch_fallbacks("⌘⌥⇧");
931 }
923 request_frame();932 request_frame();
924 Ok(())933 Ok(())
925}934}