Skip to content

Releases: tackish/pigeoneye

v1.4.8

Choose a tag to compare

@github-actions github-actions released this 20 Oct 09:48

What changed in v1.4.8:

  • fix: open the default StorageClass, whose printed name carries a "(default)" suffix
    With includeObject=None the row's name falls back to the printer's first cell,
    which the code notes "holds everywhere except Event". It also fails wherever a
    printer DECORATES that cell: kubectl marks the default StorageClass by printing
    "aws-ebs-gp3 (default)" in the NAME column. Taken literally that became the
    request path, which then could not be built as a URI:

    Failed to build request: invalid uri character

Only the default class was affected — every other StorageClass opened fine,
which is what made it look arbitrary.

An object name can never contain a space (RFC 1123), so the fallback now cuts
the cell at the first one. Names that legitimately carry punctuation are left
alone: system:controller:deployment-controller, kube-root-ca.crt. This
covers any printer that decorates the same way, not just StorageClass, on every
kind that takes the cells-only path — cluster-scoped kinds and namespaced ones
viewed inside a namespace.

Unit tests cover all three cases: the decorated cell, the object's own name
winning when metadata is present, and punctuated names surviving.

Also brings the READMEs (both languages) up to date with what has shipped since
they were written: the rollout state a workload shows when you open it, the
Issues list staying fast however many issues there are, and a cluster whose
credentials expired being parked out of background polling so a browser-opening
credential helper is not re-run on a timer.

v1.4.7

Choose a tag to compare

@github-actions github-actions released this 06 Oct 07:02

What changed in v1.4.7:

  • fix: window the Issues list so it stops slowing down with the issue count
    The Issues view rendered a DOM row per issue and, for each of them, looked that
    issue up with issues().indexOf(i) — a linear scan per row, so quadratic over
    the view. It is the one list in the app with no bound: the table is windowed,
    search results are capped, and every other list is the size of the cluster's
    config. Measured on this list, opening it blocked the main thread for 83ms at
    1,500 issues and 219ms at 4,000.

That is exactly backwards. The Issues list is longest during an incident — a
registry outage, a dead node pool, a bad rollout — which is when the tool has
to be at its fastest.

Now only the rows near the viewport exist, with spacer rows carrying the height
so the scrollbar and the cluster cards keep their real size. Rows ride the same
--row-h the table uses, so ⌥ +/- still resizes them and the window follows. The
cursored row is always rendered whatever the scroll says, and the view scrolls
itself to follow the cursor — the old scrollIntoView could not, since the row it
wanted may not be in the DOM.

4,000 issues: 4,000 rows / 219ms -> 32 rows / 10ms, and flat from there.

smoke:issues-nav locks the behaviour this could have broken, and passed before
and after: the cursor starts at 0 and clamps at both ends, j/k step, PageDown
pages, the cursor stays in the DOM deep in the list, click and Enter both open
exactly the cursored resource, grouping/badges/summary survive, and ⌥ +/- still
resizes the rows.

Also checked in: perf:split, a broad-query phase for perf:hotpaths, and a
regression guard on perf:issues so a future change cannot quietly go back to
rendering a row per issue. The rest of the audit found the hot paths sound at
20k rows — listing 64ms, metrics 63ms, a 500-row watch burst 10-46ms, search
15-22ms, split 2x on bursts with no idle cost, idle 1ms with no background
fetches.

v1.4.6

Choose a tag to compare

@github-actions github-actions released this 06 Oct 06:32

What changed in v1.4.6:

  • fix: sort the AGE column by the age it shows, not the one it arrived with
    The AGE cell holds whatever the server printed when the row arrived, while the
    column displays a live age that ticks up from there. Sorting compared the cell,
    so rows were ordered by how old they were when we first saw them — on a list
    that keeps taking in new rows (a rollout, say) that drifts away from what is on
    screen and the column visibly stops being sorted: 1m sitting above 10s.

The display and the sort now derive from one anchor per row, and an AGE sort
compares the age the column is actually showing. One reference instant per
sort is enough — every row ages at the same rate, so the order settled there
stays right and nothing re-sorts on the per-second tick. The key is computed
once per row rather than inside the comparator, which runs O(n log n) times.
Both panes carry their own sort, so both were fixed.

Measured on 20k rows: an AGE sort costs 31ms against 23ms for a plain string
sort, and the main thread sits idle (1ms) between ticks. Benchmarks are
checked in — perf:sort, perf:idle, perf:hotpaths, perf:issues — along with
smoke:age, which reproduces the drift by letting seeded rows age past rows
that arrive later over the watch, and fails on the old behaviour.

v1.4.5

Choose a tag to compare

@github-actions github-actions released this 06 Oct 04:17

What changed in v1.4.5:

  • fix: pass the AppDir to the repack as an absolute path
    The repack ran from a temp dir but handed appimagetool a path relative to the
    checkout, so it never found the AppDir:

    Error: no such file or directory: src-tauri/target/.../PigeonEye.AppDir

The step then took its "repack produced nothing" branch and shipped the
bundler's untouched AppImage — the permission fix was never in any release,
which is why the catalog test still saw AppRun.wrapped as 0770.

With the path absolute the repack actually runs, and the directory a+rx pass
added alongside it lands too: a parent directory without world-execute denies
traversal to the files inside no matter how the files themselves are set, which
is the likelier cause of the run where AppRun itself was refused.

v1.4.4

Choose a tag to compare

@github-actions github-actions released this 06 Oct 04:04

What changed in v1.4.4:

  • fix: repack the AppImage with the bundler's own tool, not a different one
    The previous attempt fixed the permissions but broke launching outright. It
    repacked the AppDir with the standalone appimagetool, which embeds a different
    AppImage runtime (the catalog's log reported X-AppImage-Runtime=static). The
    file modes were then correct — that same log shows AppRun as 0755 root:root —
    yet the app failed earlier than before: AppRun itself would not execute, where
    previously it ran and only AppRun.wrapped was denied.

Repack with the linuxdeploy-plugin-appimage that Tauri itself downloads and
uses, so the runtime that ships is exactly the one the bundler produced, and
only the permissions differ:

  • chmod a+rx the AppDir's launchers, directories and every ELF, fixing the
    0770 AppRun.wrapped that started this.
  • Repack with the bundler's plugin, found next to the AppDir or in the Tauri
    cache.
  • Verify the result unpacks AND that both launchers come out executable before
    swapping it in; otherwise ship the bundler's own AppImage untouched.

Still Linux-only and continue-on-error, so none of this can skip the release
or stall the Homebrew cask.

v1.4.3

Choose a tag to compare

@github-actions github-actions released this 06 Oct 03:44

What changed in v1.4.3:

  • fix: give the AppImage's executables 0755, not whatever umask leaves
    The repair step used chmod +x, which the runner's umask trims to --x for
    "other" — so AppRun.wrapped shipped as 0771. That is enough to execute, but
    AppImages are expected to be 0755, and the original bug was precisely that the
    "other" bits were missing (0770: fine for the build user, "Permission denied"
    for anyone who downloaded it). a+rx makes the intent explicit and leaves no
    margin.

v1.4.2

Choose a tag to compare

@github-actions github-actions released this 06 Oct 03:36

What changed in v1.4.2:

  • fix: restore the LICENSE notice an earlier commit overwrote
    The AppImage-packaging commit replaced LICENSE with the upstream PolyForm
    template, which dropped the Required Notice line PolyForm itself mandates:

    Required Notice: Copyright (c) 2026 tackish (…/pigeoneye)

The file was already the correct licence; it did not need replacing. GitHub
reports NOASSERTION for PolyForm because licensee does not recognise it, not
because the file was missing, so this never affected the catalog entry.

v1.4.1

Choose a tag to compare

@github-actions github-actions released this 22 Sep 09:26

What changed in v1.4.1:

  • docs: Windows is supported
    The platform table said "not supported" because it wasn't. v1.4.0 ships a
    Windows installer, so it now says so, and the install section points at
    the -setup.exe (with the .msi for policy deployment) and warns about
    the SmartScreen prompt an unsigned installer earns.

  • ci: require the Windows build now that it has come back green
    v1.4.0 built windows-x86_64 into an NSIS installer and an MSI on the first
    run, so the leg no longer needs to be allowed to fail — a Windows break
    should stop a release like any other platform.

v1.4.0

Choose a tag to compare

@github-actions github-actions released this 22 Sep 07:01

What changed in v1.4.0:

  • feat: run on Windows
    Everything that reached outside the app assumed a unix host. The pieces
    that talk to a cluster were already portable — kube-rs finds the
    kubeconfig per platform, TLS is rustls so there is no OpenSSL to source,
    portable-pty speaks ConPTY, and an exec into a pod runs /bin/sh in a
    Linux container whatever the host is. What was unix-only was the local
    machine:

  • the in-app terminal ran $SHELL with -il. Windows has no $SHELL, so
    pick PowerShell — pwsh where the user installed 7, else the one the OS
    ships. Its profile loads for the same reason the unix side insists on
    an interactive login shell: that is where PATH edits and AWS_PROFILE
    live, and a kubeconfig exec login has to run in the same environment
    the token refresh will.

  • PATH was split and joined on ":" and seeded with /opt/homebrew and
    friends. Use the platform separator and seed the places Windows tools
    actually land (scoop shims, chocolatey, krew, the gcloud SDK). The
    login-shell probe is skipped there: a Windows GUI process already
    inherits the user+machine PATH every installer writes to.

  • ~/ expanded through $HOME alone. Fall back to %USERPROFILE%, and
    accept ~\ as well.

  • the in-app upgrade shells out to brew, which is how macOS gets the app
    and nowhere else does. It now says where to download instead of failing
    with "could not run brew" — that was already the wrong answer on Linux.

  • the menu existed only to take ⌘W back from macOS; elsewhere it was
    just a menu bar this app has no use for. macOS-only now, as is the top
    bar's inset for the traffic lights, which left a gap on a platform that
    draws its own title bar.

peye:// links need the single-instance plugin on Windows, since a link
arrives as a second launch rather than to the running app. Added for that
target only, so macOS and Linux startup is untouched.

The release matrix builds windows-x86_64 into an NSIS installer and an
MSI. It is marked soft for now: this has never been built on a Windows
runner and a failure there must not hold back the macOS and Linux
release. Drop soft once a run comes back green — and only then is the
README's platform table worth changing.

v1.3.3

Choose a tag to compare

@github-actions github-actions released this 22 Sep 06:45

What changed in v1.3.3:

  • docs: catch the READMEs up to 1.0
    Both files last changed on 2026-08-05, before the 1.0 release — so the
    features that release was named for were missing from the page describing
    the product:

  • the two-pane split view

  • cross-cluster Issues

  • the rollout/sync dot on workloads, Argo Rollouts included

  • peye:// links to a view or a single resource

  • context groups, where only kind pinning was mentioned

Also a steady-state number in "why it's fast". That section only covered
connecting (first screen, discovery) and typing, and said nothing about
staying live, which is where the time actually goes. The ~0.5ms per
keystroke already there still measures true.

  • perf: fold a watch batch without re-indexing the list or splicing per delete
    Both flush paths built a key->index Map over every row to find the handful
    that changed, which meant a namespace/name string per row on every flush.
    On a 24k-pod list that was the most expensive thing a live view did, and
    nearly all of it was immediately garbage. Deletes then came out one
    splice at a time — O(deleted x rows), so a rollout retiring a few hundred
    pods walked the array a few hundred times.

Match rows on the two fields the key was built from instead, and drop
deleted rows in one pass. Same two fields, so any event the old code could
place, this places identically; a name would have to contain "/" to tell
them apart, which Kubernetes forbids. Isolated, on 24k rows:

one pod changes 2.29ms -> 0.18ms
idle flush 2.28ms -> 0.28ms
800 deleted 6.40ms -> 1.27ms

The two copies are now one function, which also settles a difference
between them: the secondary pane rebuilt its table object on every flush
even when nothing landed, re-running that pane's whole pipeline for it.
The primary already returned the previous table untouched.

Verified equivalent on 4000 randomised batches plus the edge cases
(empty table, every row deleted, first/last deleted, add+delete mixed,
delete of an unlisted row, null vs "" namespace) — no divergence, and
neither version mutates the previous table.

Nothing drove watch events before, so the whole apply-a-batch path had no
coverage. npm run smoke:watch streams real ADDED/MODIFIED/DELETED
through the Tauri channel and asserts the exact row set after each batch,
including a delete burst, cluster-scoped (null-namespace) rows and the
secondary pane's own watch. It passes unchanged on the previous code, so
it pins behaviour rather than this implementation; breaking the delta five
different ways all get caught.

  • fix: document the five shortcuts the ? overlay was missing
    Extracted every key branch in the app and diffed it against the overlay.
    These are bound and working but had nothing telling you so:

  • / and p in the sidebar — jump to the kind filter, pin/unpin the
    kind under the cursor

  • ⌘↵ inside the New editor — create without reaching for the button

  • ⌃d / ⌃u in the detail panel — half-page scroll, next to ⇞/⇟

  • p on an event — jump to the object the event reports on

  • ⌃] and ⌘↑ — leave a focused terminal, which listed only esc
    even though TerminalPanel takes all three

Aliases that only duplicate a documented key (⌘R for rollout restart,
⌃n/⌃p for the arrows on the launcher list) are deliberately left out.