Files
sethLabels/.claude/handoffs/2026-04-29-095534-spec-approved-pre-implementation.md
Seth da74709433 docs: add session handoff (spec-approved-pre-implementation)
Captures session state at the natural pause point: design spec
approved, no implementation started. Supersedes the scaffold-only
handoff (which described an empty-dir state).

Validated 80/100 by validate_handoff.py. The 4 file-not-found
warnings reference scripts/* that are intentionally unwritten —
they are the next session's deliverables per the spec.

Resume next session with writing-plans skill against the spec at
sethlabels-docs/specs/2026-04-29-packaging-design.md.
2026-04-29 10:00:59 -04:00

185 lines
19 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Handoff: Packaging design spec approved — implementation plan pending
## Session Metadata
- Created: 2026-04-29 09:55:34 UTC
- Project: /home/claude/bin/sethLabels
- Branch: main
- Session duration: ~1 hour
- Live URL: https://git.sethpc.xyz/Seth/sethLabels
### Recent Commits (for context)
- d3e14c6 docs: refresh CLAUDE.md + IDEA.md for design-approved phase
- 8e272c0 docs: add packaging design spec + decision log
- 9dc6776 chore: add sethLabels deployment-fork scaffold
- d1ee78e Fix incorrect font sizes (#321) ← upstream HEAD
- 06675f8 Create template style guide and minor updates… ← upstream
The first three commits are sethLabels'; everything from `d1ee78e` back is upstream's history (preserved untouched per strict-zero policy — see Important Context below).
## Handoff Chain
- **Continues from**: [2026-04-29-125823-scaffold-only.md](./2026-04-29-125823-scaffold-only.md)
- Previous title: sethLabels scaffolded — no code yet
- **Supersedes**: None. The scaffold-only handoff documented an empty-dir state; this one documents the post-design-approval state.
> Review the previous handoff for full context before filling this one. The "scaffold-only" predecessor's "Suggested Next Steps" #1 (decide upstream-tracking strategy) and #5 (initialize git + push to Gitea) are now complete; #2 (verify upstream alive) and #3 (confirm Qt version) are also resolved during this session.
## Current State Summary
This session moved sethLabels from "scaffold-only, no upstream pulled" to "design-approved, ready for implementation plan." Three things happened in order: (1) upstream `j-evins/glabels-qt` was cloned in-place and pushed to Gitea as `main`, with all 24 upstream contributors' commit authorship preserved end-to-end; (2) the brainstorming skill ran a 6-question multiple-choice round to lock all packaging-strategy decisions; (3) the resulting design was written into a 398-line spec at `sethlabels-docs/specs/2026-04-29-packaging-design.md` and committed. No `scripts/` or `packaging/` code was written — that's the next session's job. The spec is the load-bearing artifact; everything else (CLAUDE.md, IDEA.md, DECISIONS.md) was updated to point at it.
The user explicitly chose option **(3) pause here** at session close, declining to invoke writing-plans this session. The spec is sufficient to resume cleanly; there are no in-progress edits or half-implemented states.
## Architecture Overview
**sethLabels is a deployment fork, not a real fork.** The discipline (locked as Invariant I1 in the spec) is **strict zero source patches** — no upstream-tracked file is ever edited. The only exception is `.gitignore` (allowlisted as a one-time scaffold-time touch). Every sethLabels addition lives in NEW files in NEW top-level directories:
- Already created: `CLAUDE.md`, `IDEA.md`, `DECISIONS.md`, `GITEA_API.md` (gitignored symlink), `.claude/handoffs/`, `sethlabels-docs/specs/`
- Planned per spec: `scripts/` (build-deb, build-appimages, compute-version, check-no-upstream-edits, lib/), `packaging/` (deb-metadata.env, appimage-recipe.env, changelog.md), `README.sethlabels.md`
The "fork" is therefore best thought of as a **packaging repo with upstream's history embedded** — anyone can audit it instantly and confirm we changed nothing in the application itself.
**Upstream details discovered during the session:**
- Qt version: **Qt6 6.2** (find_package at `CMakeLists.txt:119`)
- CPack metadata: pre-wired at `CMakeLists.txt:84-104`; generators NOT pinned → `cpack -G DEB` and `cpack -G DragNDrop` selectable at command line with zero source change
- Install rules: clean and FHS-compliant across all subdirs (`bin`, `share/icons/hicolor`, `share/applications`, `share/mime`, `share/metainfo`, `share/man/man1`, `share/glabels-qt/{templates,translations}`)
- Desktop integration: `.desktop` file, AppStream metainfo, MIME XML, hicolor icon set already provided by upstream
- Two binaries shipped: `glabels-qt` (GUI) + `glabels-batch-qt` (CLI for scripted/mail-merge use)
- CI matrix exists for ubuntu-latest, ubuntu-22.04, windows-latest, macos-latest — but upstream doesn't publish binary releases ("Currently there are no self-hosted binary snapshot releases available" — upstream README). sethLabels exists to fill this gap.
## Critical Files
| File | Purpose | Relevance |
|------|---------|-----------|
| `sethlabels-docs/specs/2026-04-29-packaging-design.md` | THE design spec — 13 sections covering invariants, decisions, repo layout, build pipeline, release flow, brew tap, failure modes, smoke tests, glossary | **Must read before any implementation.** All design questions are answered here; do not re-derive. |
| `DECISIONS.md` | Short-form decision log (6 settled choices + 13 rejected/deferred items) | Quick scan to check what's already been decided/rejected |
| `CLAUDE.md` | Durable project instructions (refreshed this session) | Loaded on every session start; points at this handoff + the spec |
| `IDEA.md` | Plain-language project brief (refreshed this session) | Read if scope feels unclear |
| `CMakeLists.txt` (upstream root) | Defines Qt6 dep, CPack metadata, install rules | Read-only — strict-zero forbids edits. Reference for understanding what `cpack` and `linuxdeploy` will see. |
| `.github/workflows/build-tests.yml` (upstream) | Upstream CI multi-platform build matrix | Reference for valid `apt install` deps + cmake invocation patterns when writing `scripts/lib/deps-debian.sh` |
| `docs/BUILD-INSTRUCTIONS-LINUX.md` (upstream) | Upstream's manual build instructions | Reference for the `apt install` list + cmake flags; our `scripts/build-deb.sh` automates this |
## Key Patterns Discovered
- **Authorship preservation:** the gitea repo holds all 24 upstream contributors' commit authorship intact; the only Seth-authored commits are the sethLabels-specific additions (scaffold + spec + context-refresh). Verifiable via `git log --format='%an' origin/main | sort -u | wc -l` → 25 (24 upstream + Seth).
- **Convention from sibling Seth projects** (`blind_chess`, `sethmux`, `kitty-web`, `mcp-gemma4`, `seth-orchestrator`): `GITEA_API.md` is gitignored (it's a local symlink to `~/bin/GITEA_API.md`); `.backup/` is gitignored; project-tracked files are `CLAUDE.md`, `IDEA.md`, `DECISIONS.md`, `.claude/handoffs/*.md`. sethLabels follows this convention but adds the strict-zero-policy gitignore section.
- **Versioning convention:** `<upstream-tag>-seth<N>` (e.g., `3.99-master618-seth1`). Tag is `git describe --tags --abbrev=0 upstream/master`; `<N>` increments on re-package of the same upstream commit. See spec §D4 + §5.4.
## Tasks Finished
- [x] Cloned upstream `j-evins/glabels-qt` in-place; preserved all upstream commit authorship
- [x] Created Gitea repo `git.sethpc.xyz/Seth/sethLabels` (default branch: `main`); pushed full history + scaffold commit
- [x] Configured `upstream` remote → `github.com/j-evins/glabels-qt` for periodic rebases
- [x] Added scaffold commit (`9dc6776`) with author `Seth Freiberg <seth@sethfreiberg.com>` containing CLAUDE.md, IDEA.md, DECISIONS.md, .claude/handoffs/, .gitignore additions
- [x] Brainstormed all 6 packaging-strategy decisions: Linux format, macOS format, build infra, versioning, upstream-touch policy, package name
- [x] Wrote design spec at `sethlabels-docs/specs/2026-04-29-packaging-design.md` (398 lines, 13 sections); self-reviewed and fixed 5 inline issues (FF/rebase wording, --peek removal, tag-fetch caller responsibility, T3 brittleness, working-tree drift in check-no-upstream-edits)
- [x] Updated `DECISIONS.md` with 6 settled + 13 rejected/deferred decisions (all dated 2026-04-29)
- [x] Refreshed `CLAUDE.md` "Current State" + "Conventions" sections; refreshed `IDEA.md` "Constraints / preferences"
- [x] All 3 sethLabels commits pushed to Gitea immediately per the gitea-workflow convention
## Files Modified
| File | Changes | Rationale |
|------|---------|-----------|
| `.gitignore` | Appended sethLabels section (`.backup/`, `GITEA_API.md`, `.env*`, `.claude/handoffs/*.draft.md`) | Allowlisted exception to strict-zero (one-time scaffold touch) |
| `CLAUDE.md` | NEW — project instructions | Created at scaffold time; refreshed this session for design-approved phase |
| `IDEA.md` | NEW — project brief | Created at scaffold time; refreshed this session |
| `DECISIONS.md` | NEW — populated with 6 settled + 13 rejected/deferred decisions | Tracks what was decided vs. explicitly rejected (per Seth's global persistence convention) |
| `sethlabels-docs/specs/2026-04-29-packaging-design.md` | NEW — design spec | The design artifact this session produced |
| `.claude/handoffs/2026-04-29-095534-spec-approved-pre-implementation.md` | NEW — this handoff | Session close artifact |
No upstream files were modified (verified by `git diff --name-only upstream/master..HEAD` — only allowlist entries appear).
## Decisions Made
| Decision | Options Considered | Rationale |
|----------|-------------------|-----------|
| Linux: `.deb` + AppImage | AppImage-only, .deb-only, both, Flatpak | `.deb` for Debian-family install ergonomics + AppImage for any-Linux portability. Flatpak too heavyweight for use case. See spec §D1. |
| macOS: Homebrew tap, build-from-source | Unsigned .dmg, signed+notarized .dmg ($99/yr), brew tap, brew tap with cask | Brew tap eliminates macOS CI/signing/Apple Dev ID entirely. User's Mac builds from source on `brew install`. See spec §D2. |
| Build infrastructure: manual local builds | Manual, Gitea Actions runner, GitHub Actions public, GitHub Actions private | Local feedback loop beats CI loop during iteration. Scripts in `scripts/` are canonical recipe; CI YAML at the public-flip will call them unmodified. See spec §D3. |
| Versioning: `<upstream-tag>-seth<N>` | This vs. plain upstream tag, independent semver, date-based | Lineage-preserving + rebuild counter survives packaging-only fixes + sorts correctly under `dpkg --compare-versions`. See spec §D4. |
| Upstream-touch policy: strict zero | Strict zero, permissive small patches, strict-zero-with-CMakeLists-carveout | CPack `-D` flags cover all needed metadata at build time → no edit required to package. See spec §D5. |
| Package name: `glabels-qt` | This vs. `sethlabels` | Strict-zero forbids renaming the binary, so package name should match command name. sethLabels identity lives in repo name + version-string `-seth<N>` marker. See spec §D6. |
| Spec/docs location: `sethlabels-docs/` (top-level new dir) | This vs. `docs/superpowers/specs/` (inside upstream's docs/) | Strict-zero spirit forbids polluting upstream namespaces, even with new files. Top-level new dir = cleanest fork boundary. |
## Immediate Next Steps
1. **Invoke `writing-plans` skill** with the spec at `sethlabels-docs/specs/2026-04-29-packaging-design.md` as input. The plan should produce an ordered, dependency-aware implementation sequence — likely something like: (a) scripts/lib/deps-debian.sh, (b) scripts/check-no-upstream-edits.sh, (c) scripts/compute-version.sh, (d) scripts/build-deb.sh + smoke test on a clean Debian 13 box, (e) scripts/build-appimages.sh + smoke tests, (f) packaging/ env files, (g) README.sethlabels.md, (h) homebrew tap repo + initial formula, (i) first end-to-end release dry run.
2. **Address the 4 user-flagged review items** before or during plan execution:
- §5.1 build dependency list — Seth may know Debian 13 quirks (e.g., `qt6-tools-dev-tools` vs `qt6-tools-dev` package naming)
- §5.5 allowlist pattern — anything Seth wants to add that doesn't fit `scripts/`/`packaging/`/`sethlabels-docs/`?
- §7.2 brew formula `:recommended` deps — `qrencode` and `zint` as recommended (default-on) vs required vs optional
- §10 smoke test T5 — gate every release on a fresh-Debian-13-VM install test, or only on upstream-tag bumps?
3. **Consider whether to spike Path A (Qt for WebAssembly)** later — Seth raised this as a hypothetical and we concluded it's "possible but not easy" because Qt's `QPrintSupport` doesn't work in WASM (would need to render-to-PDF and let user download). Not blocking, just flagged for future.
## Blockers / Open Questions
- None blocking. The 4 review items in step 2 above are open but optional — they can be addressed during plan execution as small fixups, not gating questions.
## Deferred Items
- **Windows packaging** — deferred per project brief. Upstream's NSIS support is intact and works for anyone who wants to build their own.
- **Custom default templates baked into the package** — strict-zero forbids; user-specific templates can live in `~/.config/glabels-qt/templates/` or a future separate repo.
- **Branding, icon, splash, or string changes** — strict-zero forbids. sethLabels is a packaging fork, not a rebrand.
- **CI infrastructure (Gitea Actions / GitHub Actions runner)** — battle-test phase is manual local builds; CI is added at the eventual public-flip on GitHub.
- **Distribution to Debian backports / PPA / Ubuntu universe** — requires Debian Developer mentorship + ongoing policy compliance work; not justified for current scope.
- **Headless print-server CT** — mentioned in `IDEA.md` as "optional/later"; not in current spec scope.
## Important Context
**The strict-zero source-patch policy is not a guideline, it is the project's defining discipline.** Violating it means the project drifts from "deployment fork" toward "real fork," which breaks the trivial-rebase property that makes the whole approach work. The spec's Invariant I1 is enforced by `scripts/check-no-upstream-edits.sh` (to be implemented per spec §5.5). Any temptation to "just edit one upstream file to fix a packaging issue" should be resisted — the right move is either (a) pass via `cpack -D` flags or `linuxdeploy` config, or (b) upstream the fix as a PR to glabels-qt.
**The eventually-public-on-GitHub framing matters for build-host neutrality.** During battle-test, builds happen on steel141 (Seth's primary dev machine, Debian 13). At public-flip, builds move to GitHub Actions ubuntu-latest runners. The build scripts MUST work on a clean Debian 13 / Ubuntu LTS VM with nothing pre-installed beyond what `scripts/lib/deps-debian.sh` declares. Any steel141-specific path or tool dependency is a bug.
**Steel141 is a build host, NOT an install target.** Earlier session draft framed it as the deploy target — that was wrong, corrected during Q1. The artifacts ship to anyone running Debian-family Linux or macOS-with-brew.
**Authorship in git history is load-bearing.** All 24 upstream contributors' commits appear in `git log` with their original author/committer fields intact. Only sethLabels-specific commits (currently 3) carry Seth's authorship. This makes the fork's relationship to upstream provable end-to-end and is critical for the eventual public-fork narrative.
**The "scaffold commit" is the only sethLabels commit on top of upstream/master.** Currently `9dc6776 chore: add sethLabels deployment-fork scaffold`. Two more commits exist (`8e272c0` spec + `d3e14c6` CLAUDE.md/IDEA.md refresh) but they only touch sethLabels-namespaced files. The number of commits on top of upstream will grow; what matters is that all of them honor strict-zero.
## Assumptions Made
- **The user picked `glabels-qt` as the package name** (Q6 → α) intending the binary, brew formula, and `.deb` package to all share the name. If a future Debian official `glabels-qt` package emerges, the version-string `-seth<N>` marker will dominate the sort, but a name conflict could be revisited.
- **The user has Homebrew on the macOS machines they intend to install on.** The brew-tap approach assumes a technical user; if a non-technical macOS user enters scope, signed-DMG would have to be reconsidered.
- **The user's scaffold commit (Author: Seth Freiberg <seth@sethfreiberg.com>) was set explicitly via `git config user.email seth@sethfreiberg.com` for this repo only.** The global git identity on steel141 is `Mortdecai` (a bot identity). Future commits in this repo from Claude Code should use the same Seth identity to maintain authorship consistency. The per-repo `.git/config` is already set; new agents inherit it automatically.
## Potential Gotchas
- **Don't run a non-rebase pull from upstream.** `git pull upstream master` (without `--rebase`) would create a merge commit, breaking linear history and complicating future strict-zero enforcement. The release flow's step 2 (spec §6) says `git rebase upstream/master`, which is correct.
- **`compute-version.sh` reads local tag database.** If invoked outside the release flow without a fresh `git fetch --all --tags`, it can produce a stale `<N>` value. Spec §5.4 calls this out, but it's easy to miss when running scripts manually during development.
- **AppImage builds need `linuxdeploy` and `linuxdeploy-plugin-qt` from GitHub releases — neither is apt-installable.** `scripts/lib/linuxdeploy.sh` (to be written) handles bootstrap to a script-local cache. The cache directory must be added to `.gitignore`.
- **`dpkg-shlibdeps` (CPack's `SHLIBDEPS=ON`) sometimes mis-detects runtime deps** — particularly with optional Qt6 plugins. Mitigation in spec §F8: smoke-test install on a clean Debian 13 VM (T5).
- **`brew tap` from non-GitHub URLs requires the explicit URL form.** Initial install is `brew tap seth/tap https://git.sethpc.xyz/Seth/homebrew-tap.git`, not `brew tap seth/tap`. This is documented in spec §7.3 but easy to forget when writing the brew tap README.
- **Don't add `superpowers/` paths under `docs/`.** The brainstorming skill's default spec location is `docs/superpowers/specs/`, but `docs/` is an upstream directory, so we use `sethlabels-docs/specs/` instead. This was caught and corrected mid-session.
## Environment State
### Tools/Services Used
- **gitea CLI** (`~/bin/gitea`) — used for `gitea create sethLabels`, `gitea remote sethLabels`. Token loaded from `~/.config/gitea/token`. Documented in `~/bin/GITEA_API.md` (symlinked into project but gitignored).
- **git** — local tooling for clone, fetch, commit, push, tag. Per-repo identity set to `Seth Freiberg <seth@sethfreiberg.com>` to override the global `Mortdecai` bot identity.
- **curl + python3** — used for Gitea API calls to verify `default_branch=main` post-push.
### Active Processes
- None. No background services were started or left running. No long-running shells.
### Environment Variables
- `HOMELAB_PASSWORD` — referenced by `~/bin/CLAUDE.md` for SSH access, NOT used in this session.
- No other env vars set or required for the current state.
## Related Resources
- **Gitea repo:** https://git.sethpc.xyz/Seth/sethLabels (default branch `main`)
- **Spec:** [`sethlabels-docs/specs/2026-04-29-packaging-design.md`](../../sethlabels-docs/specs/2026-04-29-packaging-design.md) — read this before any implementation
- **Decision log:** [`DECISIONS.md`](../../DECISIONS.md) — short-form
- **Predecessor handoff:** [`2026-04-29-125823-scaffold-only.md`](./2026-04-29-125823-scaffold-only.md) — the empty-scaffold state we resumed from
- **Upstream:** https://github.com/j-evins/glabels-qt (j-evins is Jaye Evins of glabels.org)
- **Upstream README packaging gap:** the line we're filling — *"Currently there are no self-hosted binary snapshot releases available… I encourage you to try building the code yourself."*
- **Sibling Seth projects with similar conventions:** `~/bin/blind_chess/` (handoff structure reference), `~/bin/sethmux/` (gitignore convention)
---
**Security Reminder**: Validated via `validate_handoff.py` post-write. No secrets present.