From 0485e7f4badd26f9bc8f26ffe0d201da926ac912 Mon Sep 17 00:00:00 2001 From: neilcawse <8085959+neilcawse@users.noreply.github.com> Date: Sun, 11 Oct 2026 20:53:10 -0400 Subject: [PATCH] Packaging: offer GridCraft for .xlsb and .ods files on macOS GridCraft imports XLSB (#101) and ODS (#100) files, and #321 added both to the Linux and Windows "Open with" lists, but the macOS Info.plist still declared only .xlsx, .xlsm, .csv and .tsv, so Finder's Open With didn't offer GridCraft for them. Add two CFBundleDocumentTypes entries (Editor, rank Alternate) with the UTIs macOS reports for these files, plus an imported UTI for .xlsb like the .xlsm one. A test now checks that the macOS and Windows packaging offer the same file types. Co-Authored-By: Claude Opus 5.5 --- apps/gridcraft/src/main.rs | 29 ++++++++++++++++++++ docs/file-format-parity.md | 3 ++- docs/releasing.md | 8 +++--- packaging/macos/Info.plist.in | 51 +++++++++++++++++++++++++++++++++++ 4 files changed, 86 insertions(+), 5 deletions(-) diff --git a/apps/gridcraft/src/main.rs b/apps/gridcraft/src/main.rs index 8093205..cf3a371 100644 --- a/apps/gridcraft/src/main.rs +++ b/apps/gridcraft/src/main.rs @@ -369,3 +369,32 @@ fn main() -> eframe::Result<()> { } result } + +#[cfg(test)] +mod packaging_tests { + use gridcraft_engine::io::FileKind; + + /// The text between `start` and `end` that follows each `marker` in `text`, sorted (e.g. the + /// `xlsb` in `Classes\.xlsb\OpenWithProgids`). + fn extensions(text: &str, marker: &str, start: &str, end: char) -> Vec { + let mut v: Vec = text.split(marker).skip(1).filter_map(|s| s.split_once(start)?.1.split(end).next()).map(str::to_string).collect(); + v.sort(); + v + } + + /// macOS Open With (`CFBundleDocumentTypes`) offers GridCraft for the same files as Windows + /// Open with, and GridCraft opens each of them. #321 added `.xlsb` and `.ods` on Windows and + /// Linux only. + #[test] + fn macos_and_windows_offer_the_same_file_types() { + let plist = include_str!("../../../packaging/macos/Info.plist.in"); + let wxs = include_str!("../../../packaging/windows/gridcraft.wxs"); + let mac = extensions(plist, "CFBundleTypeExtensions", "", '<'); + let win = extensions(wxs, "Key=\"Software\\Classes\\.", "", '\\'); + assert!(mac.iter().any(|e| e == "xlsx"), "Info.plist.in extensions not found: {mac:?}"); + assert_eq!(mac, win); + for ext in &mac { + assert!(FileKind::from_path(&format!("book.{ext}")).is_some(), "GridCraft doesn't open .{ext}"); + } + } +} diff --git a/docs/file-format-parity.md b/docs/file-format-parity.md index 8510659..83c1849 100644 --- a/docs/file-format-parity.md +++ b/docs/file-format-parity.md @@ -1,6 +1,6 @@ # File-format parity -> **Last reviewed:** 2026-10-11 · **Last updated:** 2026-10-12 · **Change:** minor (CSV save keeps the separator it was opened with) · **Target:** Microsoft Excel (Microsoft 365), Excel for Mac 16.113.4 +> **Last reviewed:** 2026-10-11 · **Last updated:** 2026-10-12 · **Change:** minor (macOS Open With offers GridCraft for `.xlsb` and `.ods`) · **Target:** Microsoft Excel (Microsoft 365), Excel for Mac 16.113.4 Every format Excel opens or saves, with GridCraft's read and write support, fidelity and tests. Part of [target-app-parity.md](target-app-parity.md). **File formats: ~50% ready, 80–120 h** @@ -78,6 +78,7 @@ refresh. Counted under features (Get & Transform, ~10% ready), not here. | Date | Change | Summary | |---|---|---| +| 2026-10-12 | minor | macOS: the app bundle declares `.xlsb` and `.ods`, so Finder's Open With offers GridCraft for them, as Linux and Windows already did (#321) | | 2026-10-12 | minor | CSV: saving a file opened as CSV keeps its detected separator (`;` files were rewritten with `,`) | | 2026-10-11 | minor | XLSX: cell/shared/array formulas read through the file→model conversion (`_xlpm.` LET/LAMBDA parameter prefixes stripped); table column names keep line breaks; table names may start with `\` | | 2026-10-11 | minor | CSV/TSV export writes displayed values (number formats applied, no `#####`); `.xltx`, `.xltm` and `.xlsm` saved with their own workbook content types | diff --git a/docs/releasing.md b/docs/releasing.md index 7f7ecce..479c2c0 100644 --- a/docs/releasing.md +++ b/docs/releasing.md @@ -15,7 +15,7 @@ This is GridCraft's implementation of the shared [release playbook](release-play | Display name `GridCraft`, binaries `gridcraft` + `gridcraft-cli` | every script and workflow | | Bundle / app id `ai.storyteller.gridcraft` | `packaging/macos/Info.plist.in`, `packaging/linux/*`, flatpak manifest | | WiX UpgradeCode `5511327B-F305-41BE-92D0-EB84944E7C4D` (fixed forever) | `packaging/windows/gridcraft.wxs` | -| File types `.xlsx` `.xlsm` `.csv` `.tsv` | Info.plist (`CFBundleDocumentTypes`), WiX (ProgIds/OpenWithProgids), `.desktop` `MimeType=`, `ai.storyteller.gridcraft.mime.xml`, metainfo | +| File types `.xlsx` `.xlsm` `.xlsb` `.ods` `.csv` `.tsv` | Info.plist (`CFBundleDocumentTypes`), WiX (ProgIds/OpenWithProgids), `.desktop` `MimeType=`, `ai.storyteller.gridcraft.mime.xml`, metainfo | | Web app dir `apps/gridcraft-web` | `packaging/web/package.sh` | | App colour `#1f9d55` (ink `#147a40`), **proposed** | `assets/app-icon/README.md`, metainfo `` | @@ -94,9 +94,9 @@ and upload their artifacts, the environment refuses macOS and Windows, and no re - `Info.plist` comes from `Info.plist.in`: bundle id `ai.storyteller.gridcraft`, `LSMinimumSystemVersion` 11.0, `NSHighResolutionCapable`, category productivity, and - `CFBundleDocumentTypes` for `.xlsx`, `.xlsm`, `.csv` and `.tsv` with rank **Alternate**, so - GridCraft is offered in *Open With* without taking over Numbers' (or another app's) - defaults. `.xlsm` gets an imported UTI in case macOS doesn't know it. + `CFBundleDocumentTypes` for `.xlsx`, `.xlsm`, `.xlsb`, `.ods`, `.csv` and `.tsv` with rank + **Alternate**, so GridCraft is offered in *Open With* without taking over Numbers' (or another + app's) defaults. `.xlsm` and `.xlsb` get imported UTIs in case macOS doesn't know them. - **Signing** goes inside-out with the hardened runtime and a secure timestamp; no `--deep` on the final signature; `entitlements.plist` is deliberately empty. - **Notarization:** the app is zipped and sent with `xcrun notarytool submit --wait`, then diff --git a/packaging/macos/Info.plist.in b/packaging/macos/Info.plist.in index 9379279..4ed6d97 100644 --- a/packaging/macos/Info.plist.in +++ b/packaging/macos/Info.plist.in @@ -111,6 +111,39 @@ CFBundleTypeMIMETypes text/tab-separated-values + + + CFBundleTypeName + Excel Binary Workbook + CFBundleTypeRole + Editor + LSHandlerRank + Alternate + LSItemContentTypes + + com.microsoft.excel.sheet.binary.macroenabled + + CFBundleTypeExtensions + xlsb + CFBundleTypeMIMETypes + application/vnd.ms-excel.sheet.binary.macroEnabled.12 + + + CFBundleTypeName + OpenDocument Spreadsheet + CFBundleTypeRole + Editor + LSHandlerRank + Alternate + LSItemContentTypes + + org.oasis-open.opendocument.spreadsheet + + CFBundleTypeExtensions + ods + CFBundleTypeMIMETypes + application/vnd.oasis.opendocument.spreadsheet + @@ -134,6 +167,24 @@ application/vnd.ms-excel.sheet.macroEnabled.12 + + UTTypeIdentifier + com.microsoft.excel.sheet.binary.macroenabled + UTTypeDescription + Excel Binary Workbook + UTTypeConformsTo + + public.data + public.zip-archive + + UTTypeTagSpecification + + public.filename-extension + xlsb + public.mime-type + application/vnd.ms-excel.sheet.binary.macroEnabled.12 + +