From 4aadef350295f09f291abb86e07a0fd0990b99fc Mon Sep 17 00:00:00 2001 From: Phundahl <89451493+Phundahl@users.noreply.github.com> Date: Fri, 21 Aug 2026 11:28:17 +0200 Subject: [PATCH 1/3] chore(release): v1.3.0 Live theme switching and tailTUI's own theme schema (#4). Also fixes a packaging gap the release surfaced: the README told users to copy the template from contrib/, which does not exist for anyone who installed the AUR, .deb or .rpm package. The template is now installed to /usr/share/tailtui/tailtui.toml.tpl and the README documents both paths. Any future user-facing file under contrib/ needs the same treatment or the docs silently break for package users. version_test.go no longer asserts on the real version string, so a release bump does not look like it requires a test edit -- the test covers SetVersion's normalization, not the current version. Co-Authored-By: Claude Opus 5 (1M context) --- CLAUDE.md | 1 + README.md | 23 +++++++++++++++++++++++ internal/tui/version_test.go | 10 +++++----- internal/tui/view.go | 2 +- 4 files changed, 30 insertions(+), 6 deletions(-) diff --git a/CLAUDE.md b/CLAUDE.md index dd952ce..cc301c4 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -170,6 +170,7 @@ Before changing any interaction, consult the keybinding matrix and overlay specs - **Phase 28 — CI workflow & release-config repair** — `.github/workflows/release.yml` was the **only** workflow and it fires solely on `v*` tags, so nothing verified a commit until a release was already being cut, and a broken `.goreleaser.yaml` could only surface as a failed release job *after* the tag existed. Added `.github/workflows/ci.yml`: runs on pushes to `main`, on every pull request, and via `workflow_dispatch`; `concurrency` cancels superseded in-flight runs; `contents` permission is read-only. Two jobs — **`test`** (a `gofmt -l` gate, a `go mod tidy` drift gate, `go build ./...`, `go vet ./...`, and **`go test -race ./...`**; the View layer is TTY-free so the suite runs headless, and `-race` covers the async `tea.Cmd` status/ping polling paths) and **`goreleaser-config`** (`goreleaser check`, so config errors fail on a PR instead of on a tag). Both resolve Go via `go-version-file: go.mod` rather than `stable`, so CI tracks the pinned toolchain instead of drifting on release day. **The new check immediately caught three real defects** in `.goreleaser.yaml`, every one of which would have failed the release job only after a tag was pushed: `nfpms.files` → **`nfpms.contents`** (`files` is a v1 key and is rejected outright by the v2 schema — the hard failure); `archives.format` → **`archives.formats`** (deprecated singular); and `snapshot.name_template` → **`snapshot.version_template`** (deprecated — note `goreleaser check` exits non-zero on deprecations, not just schema errors). Also dropped `-extldflags "{{.Env.LDFLAGS}}"` from the build ldflags — it required an `LDFLAGS` env var to be set at release time and is a no-op under `CGO_ENABLED=0` (no external linker to pass it to) — and translated the one non-English comment. **Test hermeticity is a requirement for CI to stay green**: every styles test uses `t.Setenv` for `HOME`/`TAILTUI_THEME`, and `TestInstalledOmarchyThemesMapCompletely` (which globs the machine's real Omarchy theme dir) **skips** when none is installed. Don't add a test that assumes Omarchy, a TTY, or a live `tailscale` daemon without an equivalent skip guard. - **Phase 29 — Release v1.2.0 & version plumbing** — cut the release covering everything since the (never-published) `v1.1.0` tag: the 24.x sudo account flows, Phase 26 mock mode + VHS demo, the 26.2 privacy pass, Phase 27's Omarchy 4 theme support, and Phase 28's CI. **Version plumbing fix**: `.goreleaser.yaml` stamps `-X main.version={{.Version}}`, but `main.go` had **no `version` var** and `appVersion` was a `const` in `internal/tui/view.go` — so the ldflag silently did nothing (Go ignores `-X` on a missing symbol) and a tagged build would have shipped whatever was hardcoded. Now `main.go` declares `var version string` (empty for `go build`/`go run`) and calls **`tui.SetVersion(version)`** first thing in `main`; `appVersion` became a **var** (dev-build literal `v1.2.0`) and `SetVersion` ignores an empty value and normalizes a bare `1.2.0` → `v1.2.0`, since goreleaser's `{{.Version}}` drops the prefix. `appVersion` remains the single definition — set it there, never duplicate it. Added `internal/tui/version_test.go` (normalization table incl. prerelease + empty, plus a render test asserting the stamped version actually reaches the footer while staying flush). `.gitignore` was rewritten to cover the compiled `/tailtui` binary (previously **committed**, ~6.5 MB, removed from history in the same cycle), test/coverage output, and editor/OS noise, while preserving the existing `_designs/` allow-list. README gained a `ci` status badge under the tagline and a "What's New in v1.2.0" section above the v1.1.0 one. No layout / state / keybinding changes. - **Phase 30 — tailTUI theme schema, Omarchy template & live theme reload** — closed the one gap Phase 27 could not: some mappings are genuinely undecidable in code. `osaka-jade` defines `yellow = #459451` (a green), so mapping `yellow`→Warning makes exit-node markers nearly indistinguishable from the online green — but preferring `orange` breaks `retro-82`, where orange *is* the accent. **(1) tailTUI's own schema**: a third wire struct `tailtuiTheme` with one key per `Theme` field, tried **before** both Omarchy schemas, and probed as `tailtui.toml` **ahead of** `colors.toml` inside each theme directory. Keys are disjoint from the Omarchy vocabularies so detection stays key-based; `mode`/`background` are shared and therefore excluded from `hasMarkers()`. **(2) Omarchy template**: `contrib/tailtui.toml.tpl`, installed by the user into `~/.config/omarchy/themed/`. Omarchy's `omarchy-theme-set-templates` renders every `*.tpl` there into the **active theme directory** (`$NEXT_THEME_DIR/`, which becomes `~/.local/state/omarchy/current/theme/`) on every theme switch — i.e. straight into the directory the loader already probes, so no new path was needed. Templates get the full palette (semantic slots, `color0`–`color15`, `mode`, `bright_`/`dark_`/`light_` variants) plus `_strip`/`_rgb` modifiers and a `{{ mix a b 30% }}` blend. **(3) Live reload**: `styles.ThemeStamp()` (path+mtime) plus `checkThemeCmd`, batched onto the **existing** `refreshInterval` tick — no fsnotify, no new dependency — so switching the desktop theme re-colors a running tailTUI within one tick. The critical constraint: the command **returns** the parsed `Theme` and never calls `styles.Apply` itself, because `Apply` rewrites package-level vars `View` reads; applying happens in `Update`, on the Elm goroutine. The handler records the stamp even on no-change (so a file appearing/vanishing is caught) and rebuilds viewport-backed overlays via `resizeOverlay()`, whose content would otherwise keep stale ANSI colors. Zero-config behavior is untouched: with no template installed, `colors.toml` is read exactly as before. **Tests**: own-schema mapping, light mode, three-way detection non-ambiguity, `tailtui.toml`-over-`colors.toml` precedence, fallback when no template exists, `ThemeStamp` change tracking and empty state (`theme_test.go`); palette applied + stamp recorded + logged, unchanged-is-quiet, overlay rebuilt (forcing TrueColor, since a TTY-less test strips every SGR and both renders would otherwise compare equal), and `New()` stamp seeding (`theme_reload_test.go`). +- **Phase 31 — Release v1.3.0** — bumped `appVersion` `v1.2.0` → `v1.3.0` (still the single definition in `view.go`) for Phase 30's live theme switching + tailTUI theme schema. **Packaging gap closed**: the README told users to `cp contrib/tailtui.toml.tpl …`, which is wrong for anyone who installed from the AUR / `.deb` / `.rpm` — they have a binary package, not a checkout. The template is now installed to **`/usr/share/tailtui/tailtui.toml.tpl`** by the packaging, and the README gives both paths. Any future user-facing file added under `contrib/` needs the same treatment, or the docs silently break for package users. Also decoupled `version_test.go` from the real version string (it now asserts on an obviously-arbitrary literal), so a release bump no longer looks like it requires a test edit — the test exercises `SetVersion`'s normalization, not the current version. README gained a "What's New in v1.3.0" section; the theming docs and the Phase 30 entry shipped with the feature itself, which is the rule: **docs ride in the feature PR, and the release PR carries only version, changelog, and phase log**. - **Upcoming (next major cycle — see the README Roadmap)** — - **Tailscale Serve & Funnel management**: visual port forwarding to securely expose local services to the tailnet (`tailscale serve`) or the public internet (`tailscale funnel`), driven from keyboard overlays in the existing modal style. - **Connection diagnostics**: deep-dive into peer connection health — DERP-relay vs. direct routing and the signals to debug a flaky link (likely from `tailscale status --json` endpoints + `tailscale ping`/`netcheck`). diff --git a/README.md b/README.md index c2917a4..8df99dc 100644 --- a/README.md +++ b/README.md @@ -29,6 +29,24 @@ for the opposite workflow: drop you to the shell only when *they* need to (to paste an auth URL), then restore the UI automatically. +## What's New in v1.3.0 + +- **Live theme switching.** Switch your desktop theme and a running `tailTUI` + re-colors itself within a few seconds — no restart, and it works with a modal + open. Implemented on the existing refresh tick, so there is no file-watcher + dependency and no extra background work. +- **A theme format of tailTUI's own.** A `tailtui.toml` with one key per UI role + (`primary`, `surface`, `warning`, …) now takes precedence over the raw Omarchy + palette. Write it by hand on any distro, or generate it from the bundled + Omarchy template. +- **You decide the ambiguous mappings.** Some palettes cannot be mapped + automatically — `osaka-jade` defines its `yellow` as a green, which leaves + exit-node markers nearly indistinguishable from the online color. A template + settles it in one line, per theme, instead of `tailTUI` guessing. + +None of this is required: with no template installed, `tailTUI` reads the +theme's `colors.toml` and maps it itself, exactly as before. + ## What's New in v1.2.0 - **Omarchy 4 ("Quattro") theme support.** Omarchy 4 moved the current-theme @@ -230,6 +248,11 @@ running**, within a few seconds of you switching themes — no restart: ```bash mkdir -p ~/.config/omarchy/themed + +# installed from a package (AUR, .deb, .rpm) +cp /usr/share/tailtui/tailtui.toml.tpl ~/.config/omarchy/themed/ + +# or from a clone of this repository cp contrib/tailtui.toml.tpl ~/.config/omarchy/themed/ ``` diff --git a/internal/tui/version_test.go b/internal/tui/version_test.go index 50619cd..6d165d5 100644 --- a/internal/tui/version_test.go +++ b/internal/tui/version_test.go @@ -20,15 +20,15 @@ func TestSetVersion(t *testing.T) { cases := []struct { name, in, want string }{ - {"bare goreleaser version", "1.2.0", "v1.2.0"}, - {"already prefixed", "v1.2.0", "v1.2.0"}, - {"prerelease", "1.3.0-rc1", "v1.3.0-rc1"}, - {"unstamped build keeps the literal", "", "v1.2.0"}, + {"bare goreleaser version", "9.9.9", "v9.9.9"}, + {"already prefixed", "v9.9.9", "v9.9.9"}, + {"prerelease", "9.9.9-rc1", "v9.9.9-rc1"}, + {"unstamped build keeps the literal", "", "v0.0.0-devbuild"}, } for _, c := range cases { t.Run(c.name, func(t *testing.T) { restoreVersion(t) - appVersion = "v1.2.0" // the dev-build literal + appVersion = "v0.0.0-devbuild" // stands in for the dev-build literal SetVersion(c.in) if appVersion != c.want { t.Errorf("SetVersion(%q) → %q, want %q", c.in, appVersion, c.want) diff --git a/internal/tui/view.go b/internal/tui/view.go index 8d9a66d..5a7b610 100644 --- a/internal/tui/view.go +++ b/internal/tui/view.go @@ -16,7 +16,7 @@ const appName = "tailTUI" // appVersion is the version string shown in the footer. It is a var, not a // const, so a release build can override it from the git tag via SetVersion // (see main.go / .goreleaser.yaml); the literal here is the dev-build value. -var appVersion = "v1.2.0" +var appVersion = "v1.3.0" // SetVersion overrides the footer version string, for builds that stamp the // real tag in at link time. An empty version is ignored (keeping the dev-build From 4fb295bc23367c099d30a39a580b12e94a96b478 Mon Sep 17 00:00:00 2001 From: Phundahl <89451493+Phundahl@users.noreply.github.com> Date: Fri, 21 Aug 2026 11:31:27 +0200 Subject: [PATCH 2/3] fix(release): ship the Omarchy template in the packages The README now points users at /usr/share/tailtui/tailtui.toml.tpl, but nothing installed it there -- the docs promised a path that did not exist in any artifact. - nfpm installs the template to /usr/share/tailtui/, plus README.md to /usr/share/doc/tailtui/. - The tar.gz carries contrib/tailtui.toml.tpl. Specifying `files` replaces goreleaser's defaults, so LICENSE and README are restated. Verified by building a snapshot release and listing the payloads. Co-Authored-By: Claude Opus 5 (1M context) --- .goreleaser.yaml | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/.goreleaser.yaml b/.goreleaser.yaml index 40fe4da..a9dab7d 100644 --- a/.goreleaser.yaml +++ b/.goreleaser.yaml @@ -18,6 +18,12 @@ builds: archives: - formats: [tar.gz] name_template: "{{ .ProjectName }}_{{ .Version }}_{{ .Os }}_{{ .Arch }}" + # Listing `files` replaces goreleaser's defaults, so LICENSE and README + # have to be restated alongside the template. + files: + - LICENSE + - README.md + - contrib/tailtui.toml.tpl # nfpm builds official .deb (Debian/Ubuntu) and .rpm (Fedora/RedHat) packages nfpms: @@ -35,6 +41,10 @@ nfpms: contents: - src: LICENSE dst: /usr/share/licenses/tailtui/LICENSE + - src: contrib/tailtui.toml.tpl + dst: /usr/share/tailtui/tailtui.toml.tpl + - src: README.md + dst: /usr/share/doc/tailtui/README.md checksum: name_template: 'checksums.txt' From 9f0ec08556e82ce842e87b49243d84db894eda40 Mon Sep 17 00:00:00 2001 From: Phundahl <89451493+Phundahl@users.noreply.github.com> Date: Fri, 21 Aug 2026 11:31:27 +0200 Subject: [PATCH 3/3] chore: ignore goreleaser dist/ output Co-Authored-By: Claude Opus 5 (1M context) --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index 1e07077..a4589b0 100644 --- a/.gitignore +++ b/.gitignore @@ -20,3 +20,4 @@ coverage.* _designs/* !_designs/00_STYLE_GUIDE.md !_designs/00_WIREFRAME_SPEC.md +/dist/