168 lines
18 KiB
Markdown
168 lines
18 KiB
Markdown
# Handoff: macOS Launchpad/Spotlight integration via stub .app wrapper added to brew tap
|
|
|
|
## Session Metadata
|
|
- Created: 2026-04-29 12:15:29 UTC
|
|
- Project: /home/claude/bin/sethLabels
|
|
- Branch: main
|
|
- Session duration: continuation of the same session that produced the first-release handoff (2026-04-29-155439); this addition is ~1 hour of follow-up work
|
|
|
|
### Recent Commits (for context)
|
|
- f6c30f2 test: move bats scratch dirs to repo-local .test-scratch/ (per global no-/tmp/ rule)
|
|
- 76a3cb7 docs: README.sethlabels.md — include upstream remote setup in build-from-source
|
|
- 891cc7c docs: add session handoff (first-release)
|
|
- 2d04943 docs: refresh CLAUDE.md to post-first-release phase
|
|
- 2108e2c docs: changelog for 3.99-master618-seth1
|
|
|
|
Plus pending uncommitted edits this session (will be committed by the wrap-up step):
|
|
- CLAUDE.md (Conventions section: removed stale "to be implemented" wording for check-no-upstream-edits.sh; added tests-impl/ to dirs list)
|
|
- DECISIONS.md (appended .app launcher decision)
|
|
|
|
Brew tap repo (`git.sethpc.xyz/Seth/homebrew-tap`) has TWO new commits this session:
|
|
- ef4d6c7 feat: generate stub .app bundle for Launchpad/Spotlight integration on macOS
|
|
- 3542762 fix: use upstream SVG (not nonexistent PNG) for .app icon conversion
|
|
|
|
## Handoff Chain
|
|
|
|
- **Continues from**: [2026-04-29-155439-first-release.md](./2026-04-29-155439-first-release.md)
|
|
- Previous title: sethLabels packaging pipeline live — first release published
|
|
- **Supersedes**: None.
|
|
|
|
> The predecessor captures the 12-task implementation + first-release publication. This handoff extends that work with a single follow-up feature: macOS Launchpad/Spotlight integration. No regressions in the predecessor's deliverables; everything from the first release is unchanged and still live.
|
|
|
|
## Current State Summary
|
|
|
|
This session continued from "first release published" state. Seth asked whether the brew install would put a glabels-qt launcher icon in the Mac menu/Launchpad. Answer: no, because upstream's `glabels/CMakeLists.txt:125` declares `add_executable(glabels-qt WIN32 ...)` with no `MACOSX_BUNDLE` keyword, producing a CLI-only Mach-O on macOS. Strict-zero (I1) forbids patching upstream to fix this. Seth picked option 1 (stub `.app` wrapper synthesized in the brew formula) over option 2 (upstream a PR) and option 3 (switch to a Cask). Implemented as two commits on the brew tap repo. The sethLabels repo itself was NOT modified — the entire feature lives in the tap's `def install` block.
|
|
|
|
The `.app` launcher works by: (1) cmake installs binaries to `bin/` per upstream rules, (2) brew formula's `def install` then synthesizes `<prefix>/glabels-qt.app/Contents/{Info.plist, MacOS/glabels-qt, Resources/glabels-qt.icns}` where the launcher script is a 2-line shell that `exec`s the real CLI binary, (3) the icon is converted from upstream's installed SVG via macOS-built-in `sips`, (4) the formula's `caveats` block tells the user to run `cp -R "$(brew --prefix glabels-qt)/glabels-qt.app" /Applications/` once.
|
|
|
|
NOT YET VALIDATED: needs first install on a real Mac. Two unknowns: whether macOS 13+ `sips` actually accepts SVG input (begin/rescue catches the failure with an `opoo` if not — falls back to generic icon), and whether Gatekeeper requires a right-click→Open on first launch (likely yes since the .app isn't signed, but acceptable for the project's audience).
|
|
|
|
## Codebase Understanding
|
|
|
|
### Architecture Overview
|
|
|
|
The brew tap pattern: a Homebrew formula's `def install` runs on the user's Mac during `brew install`. It can do anything the user's shell can do, including writing files outside the source tree (within the Cellar prefix). This is the legitimate sethLabels-side hook for filling gaps that strict-zero forbids fixing in upstream code. The .app wrapper is one such gap; others (e.g., a future macOS-specific `Info.plist` content type registration for `.glabels` files) could plug in here too.
|
|
|
|
The launcher script approach (`exec "#{bin}/glabels-qt" "$@"`) is a thin wrapper, not a copy. It avoids:
|
|
- Having to `MACOSX_BUNDLE` the cmake target (needs upstream patch — strict-zero forbids)
|
|
- Running `macdeployqt` on the binary (needs Mac to build — defeats decision D2's no-Mac-CI goal)
|
|
- Maintaining a separate Cask with a pre-built artifact (same problem)
|
|
|
|
The wrapper has one downside: launching from Launchpad doesn't inherit a shell PATH, so the wrapper's `exec` path is hardcoded at install time using brew's `#{bin}` interpolation. That `#{bin}` resolves to `/opt/homebrew/Cellar/glabels-qt/<version>/bin/` on Apple Silicon, which has rpath set up correctly for Qt6 lookup. Should "just work" without `DYLD_LIBRARY_PATH` or `QT_PLUGIN_PATH` exports.
|
|
|
|
### Critical Files
|
|
|
|
| File | Purpose | Relevance |
|
|
|------|---------|-----------|
|
|
| `~/bin/homebrew-tap/Formula/glabels-qt.rb` | The brew formula. Contains `def install` (cmake build + .app synthesis), `def caveats` (user-facing post-install message), and `test do` (assert `--version` works). | This is the ONLY file the user changes per release — bump `tag:` and `revision:` (and the SHA needs `# pragma: allowlist secret` to bypass the tap repo's detect-secrets pre-commit hook). |
|
|
| `~/bin/homebrew-tap/README.md` | Tap install instructions. | Has a "Launchpad / Spotlight integration (macOS)" section documenting the one-time `cp -R` step. |
|
|
| `glabels/CMakeLists.txt` (upstream — DO NOT EDIT) | Lines 125 (`add_executable(glabels-qt WIN32 ...)` — no MACOSX_BUNDLE) and 152-156 (icon install rules) explain why we need the wrapper and where the SVG icon comes from. | Read-only — strict-zero forbids edits. The reason we need the .app wrapper. |
|
|
| `~/bin/sethLabels/sethlabels-docs/specs/2026-04-29-packaging-design.md` | Design spec. §D2 covers the brew tap decision; the .app wrapper is a sub-decision under it. | Reference for why brew tap was chosen over .dmg/Cask. |
|
|
| `~/bin/sethLabels/DECISIONS.md` | Project decision log. Has a new entry for the .app launcher choice. | Quick scan to see what was already decided/rejected. |
|
|
|
|
### Key Patterns Discovered
|
|
|
|
- **`on_macos do ... end`** — Homebrew DSL block for macOS-only formula logic. Wraps both the .app generation (in `def install`) and the user message (in `def caveats`). On Linux brew (rare for this formula), the .app code is skipped entirely.
|
|
- **Path interpolation in heredocs** — the launcher script uses `#{bin}/glabels-qt` interpolation. At formula evaluation time `#{bin}` becomes the absolute Cellar path. So the launcher script written to disk has a hardcoded full path, not an env-dependent reference.
|
|
- **`sips` for SVG → icns** — macOS-built-in tool. Wrapped in `begin/rescue` so a sips failure on older macOS (which can't read SVG) just `opoo`s and the .app gets a generic icon — no error path.
|
|
- **detect-secrets in tap repo** — the tap has a detect-secrets pre-commit hook that flags 40-char hex strings as secrets. The `revision:` field (a git SHA) gets a `# pragma: allowlist secret` inline comment. Every future tap bump must preserve that pragma. Future improvement: add the SHA pattern to `.secrets.baseline` so the pragma isn't needed.
|
|
|
|
## Work Completed
|
|
|
|
### Tasks Finished
|
|
|
|
- [x] Diagnosed the menu-launcher question by reading upstream `glabels/CMakeLists.txt:125,150` (no `MACOSX_BUNDLE`) and `docs/BUILD-INSTRUCTIONS-MACOS.md` (CLI-only `make install`)
|
|
- [x] Presented three implementation options to Seth; he picked option 1 (stub .app via brew formula)
|
|
- [x] Implementer subagent generated the .app wrapper block in `def install` + `caveats` method + README.md "Launchpad / Spotlight" section. Committed as `ef4d6c7` on the tap.
|
|
- [x] Caught a bug via context check: the initial implementation globbed for `glabels.png` but upstream installs only SVGs (`glabels/CMakeLists.txt:152-156`). The `opoo` fallback would have fired on every install, leaving every .app with a generic icon.
|
|
- [x] Fix subagent updated the glob to `glabels.svg`, prefer the scalable variant, fall back to sized variants, wrap `sips` in `begin/rescue`. Committed as `3542762` on the tap.
|
|
- [x] CLAUDE.md fixed: removed stale "(to be implemented per spec §5.5)" wording for the guardrail; added `tests-impl/` to the dirs-list (uncommitted at handoff time).
|
|
- [x] DECISIONS.md appended: macOS Launchpad/Spotlight integration via stub .app wrapper, with rationale (uncommitted at handoff time).
|
|
|
|
### Files Modified
|
|
|
|
| File | Changes | Rationale |
|
|
|------|---------|-----------|
|
|
| `CLAUDE.md` (sethLabels) | Removed stale "(to be implemented per spec §5.5)" parenthetical; expanded the "Enforced by..." line to mention working-tree drift + ref-existence check; added `tests-impl/` to dirs list. | Stale wording flagged by cumulative review last session; tests-impl/ omission was a small accuracy gap. |
|
|
| `DECISIONS.md` (sethLabels) | Appended decision entry for the .app launcher approach. | Project's Decision-Log convention: every non-obvious settled choice gets logged. |
|
|
| `~/bin/homebrew-tap/Formula/glabels-qt.rb` | Added `on_macos do` block in `def install` to synthesize the .app; added `def caveats`; later fixed the icon glob from PNG to SVG. | Two commits — ef4d6c7 (initial) + 3542762 (icon fix). |
|
|
| `~/bin/homebrew-tap/README.md` | New "Launchpad / Spotlight integration (macOS)" subsection under `## Install`. | User-facing docs for the one-time `cp -R` step. |
|
|
|
|
### Decisions Made
|
|
|
|
| Decision | Options Considered | Rationale |
|
|
|----------|-------------------|-----------|
|
|
| .app launcher via brew formula `def install` synthesis | (1) Brew formula synthesis, (2) Upstream PR adding MACOSX_BUNDLE, (3) Switch from Formula to Cask with pre-built .app | Option 1 lives entirely sethLabels-side, no upstream dep, no Mac CI required. Option 2 indefinite timeline (upstream review). Option 3 needs a Mac to build the .app (defeats D2's no-macOS-CI simplification). |
|
|
| Icon source = upstream SVG, converted to icns via `sips` | (a) Skip icon (generic), (b) Convert SVG via sips, (c) Convert PNG via sips, (d) Ship pre-built .icns in tap repo, (e) Fetch .icns as a brew resource | (b) chosen. Upstream installs only SVGs — verified at `glabels/CMakeLists.txt:152-156`. (c) was the initial implementation's bug. (a) is the rescue-block fallback. (d)/(e) add tap-repo maintenance. |
|
|
| Don't auto-link .app to /Applications/ | (i) Manual `cp -R` (user runs once), (ii) Symlink during install, (iii) Use `brew linkapps` | (i) chosen. (ii) requires writing to `/Applications/` which brew formulas can't do without sudo. (iii) is deprecated. The caveats block reminds the user. |
|
|
|
|
## Immediate Next Steps
|
|
|
|
1. **First Mac install validation.** When you (or Seth) get to a Mac with brew, run `brew tap seth/tap https://git.sethpc.xyz/Seth/homebrew-tap.git && brew install seth/tap/glabels-qt`. Expected: build succeeds (~5-10 min first time), `which glabels-qt` finds the binary, `cp -R "$(brew --prefix glabels-qt)/glabels-qt.app" /Applications/` succeeds, the app appears in Launchpad and is searchable in Spotlight. If `sips` failed silently (older macOS), the icon will be generic — check Launchpad first.
|
|
2. **First-launch Gatekeeper handling.** macOS will likely block first launch with "cannot be opened because it is from an unidentified developer." Right-click → Open is the standard bypass. Document this in the tap README if it turns out to be a notable friction point.
|
|
3. **T5 fresh-Debian-13-VM smoke test for the .deb** (still deferred from the first-release handoff — also not yet done). Spin up a clean Debian 13 VM, download the .deb from the release page, install with `apt install ./...`, run `glabels-qt --version`. If unmet deps surface, override via `CPACK_DEBIAN_PACKAGE_DEPENDS` in `scripts/build-deb.sh` and re-tag as -seth2.
|
|
|
|
## Blockers / Open Questions
|
|
|
|
- **Does `sips` on the user's macOS version handle SVG input?** macOS 13+ should; older versions may fail. The `rescue` block falls back gracefully — the only signal will be a generic Mac icon in Launchpad.
|
|
- **Will Gatekeeper friction be acceptable to Seth's eventual users?** First-launch right-click→Open is standard for unsigned apps. If it becomes a nuisance, options are: (a) self-sign with a free Apple ID (no notarization, only works for personal use), (b) properly sign + notarize ($99/year — explicitly rejected in §D2), (c) document the right-click→Open step prominently.
|
|
|
|
## Deferred Items
|
|
|
|
- **Adding the SHA pattern to `.secrets.baseline`** in the tap repo — would eliminate the need for `# pragma: allowlist secret` on every future tap bump. Trivial change but I left it for a future session since it's not blocking.
|
|
- **Verifying the `.app` actually contains all needed Qt6 plugins on user-side** — brew's Qt6 ships with cocoa platform plugin, image format plugins, and SVG support. The wrapper's `exec` of the real binary picks them up via the binary's rpath. Should "just work" but unconfirmed without a Mac test.
|
|
- **Cumulative-review minor follow-ups** still deferred from previous handoff: build-appimages.sh step counter `[1/6]` reused 3x, `packaging/appimage-recipe.env` is currently unwired, `compute-version.sh` opaque error if upstream has no annotated tags, changelog.md not discoverable from README.
|
|
|
|
## Important Context
|
|
|
|
**The .app wrapper is the FIRST piece of sethLabels-side macOS-specific build logic.** Up to this point, sethLabels was fully cross-platform-by-virtue-of-strict-zero — every script was Linux-only because the targets were Linux-only. This session adds the first piece of mac-conditional code, but ONLY on the brew-tap side (the sethLabels repo itself remains Linux-targeted). Future macOS-specific gaps (e.g., `.glabels` UTI registration, dock badge support, etc.) should follow the same pattern: handled in the brew formula's `def install` and `def caveats`, never via patches to upstream code.
|
|
|
|
**The tap's detect-secrets hook is a known papercut.** Every release-flow run will require manually preserving the `# pragma: allowlist secret` comment on the `revision:` line after `sed`-replacing the SHA. The cleanest fix is to add the SHA pattern to `.secrets.baseline` (a `pre-commit run --all-files` regenerate after a `pre-commit autoupdate`). Until then, the release flow's tap-bump step should warn about this.
|
|
|
|
**Don't conflate the .app wrapper with a "real" Mac app.** It's a thin shell script in `Contents/MacOS/`. Apps that need to listen for system events, register URL schemes, or claim file types properly need a real bundle with proper Info.plist UTI declarations. The current Info.plist has only the bare-minimum keys for Launchpad/Spotlight; if Seth ever wants drag-a-`.glabels`-onto-the-icon behavior, that's a separate Info.plist change.
|
|
|
|
## Assumptions Made
|
|
|
|
- The user has macOS 13+ (so `sips` handles SVG). Older macOS gets the rescue path with a generic icon — works, just ugly. The project audience (technical users with brew) skews to recent macOS, so this is acceptable.
|
|
- Brew's installed Qt6 binary correctly resolves Qt plugins via rpath when launched from a non-shell context (Launchpad). Standard for brew-installed Qt apps; unverified here.
|
|
- The `/Applications/` copy is not a deal-breaker for the user (vs. an automatic symlink). Users running brew already accept similar manual steps for `brew linkapps`-style integrations.
|
|
- Upstream's scalable SVG icon will continue to be installed at the spec'd path on macOS. If upstream restructures the icon install rules, the formula's glob may miss — `opoo` then falls through to generic icon (not a fail-build).
|
|
|
|
## Potential Gotchas
|
|
|
|
- **`brew --prefix glabels-qt`** in the caveats text resolves the formula's installed prefix. If the user runs the `cp -R` BEFORE actually installing, it'll error with "no formula found." The caveats text shows this command in the post-install message, which is where it makes sense.
|
|
- **Re-running `cp -R` after `brew upgrade`** is required because Launchpad caches the .app. The README documents this. Future improvement could be a small post-install hint via `brew services`-style integration, but that's out of scope.
|
|
- **Editing the formula requires care around heredoc delimiters.** The current file has three: `SH` (launcher), `PLIST` (Info.plist), `CAVEATS`. Each must open and close correctly; an unclosed heredoc would silently consume the rest of the file as content. Visual inspection on the live tap confirmed all three are balanced.
|
|
- **The .app wrapper's launcher script is shell, not a Mach-O binary.** This may cause Gatekeeper to flag it more aggressively on first launch than a signed Mach-O would. If it becomes a problem, alternatives are: (a) hardlink/symlink the real binary into the launcher path, (b) compile a tiny C program that `exec`s the real binary.
|
|
|
|
## Environment State
|
|
|
|
### Tools/Services Used
|
|
|
|
- **Homebrew tap** at `git.sethpc.xyz/Seth/homebrew-tap` — separate Gitea repo (NOT inside sethLabels). Has its own per-release commit history (one commit per release, bumping `tag:` and `revision:`).
|
|
- **gitea CLI** (`~/bin/gitea`) — not directly used this session (the tap was already created in Task 11 of the previous session).
|
|
- **detect-secrets** (pre-commit hook on the tap repo) — flagged the revision SHA; resolved with inline pragma comment.
|
|
|
|
### Active Processes
|
|
|
|
- None.
|
|
|
|
### Environment Variables
|
|
|
|
- `HOMEBREW_PREFIX` — referenced in the formula's `caveats` text, resolves on the user's Mac.
|
|
- No new env vars set or required this session.
|
|
|
|
## Related Resources
|
|
|
|
- **First-release handoff (predecessor):** [`2026-04-29-155439-first-release.md`](./2026-04-29-155439-first-release.md) — predecessor capturing the 12-task implementation + first published release
|
|
- **Spec-approved-pre-implementation handoff (grandfather):** [`2026-04-29-095534-spec-approved-pre-implementation.md`](./2026-04-29-095534-spec-approved-pre-implementation.md) — the design state we resumed from
|
|
- **Design spec:** [`../sethlabels-docs/specs/2026-04-29-packaging-design.md`](../../sethlabels-docs/specs/2026-04-29-packaging-design.md) §D2 (brew tap decision)
|
|
- **Implementation plan:** [`../sethlabels-docs/plans/2026-04-29-packaging-implementation.md`](../../sethlabels-docs/plans/2026-04-29-packaging-implementation.md) — the 12-task plan that built the pipeline
|
|
- **Brew tap (live):** https://git.sethpc.xyz/Seth/homebrew-tap
|
|
- **First sethLabels release:** https://git.sethpc.xyz/Seth/sethLabels/releases/tag/3.99-master618-seth1
|
|
- **Upstream glabels-qt cmake target (read-only reference):** `glabels/CMakeLists.txt:125` (the `WIN32` keyword without `MACOSX_BUNDLE`) and `glabels/CMakeLists.txt:152-156` (icon install rules)
|
|
|
|
---
|
|
|
|
**Security Reminder**: No secrets present. Validated post-write.
|