Repository navigation
Releases: tackish/pigeoneye
Release list
v1.4.8
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
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
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
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
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
What changed in v1.4.3:
- fix: give the AppImage's executables 0755, not whatever umask leaves
The repair step usedchmod +x, which the runner's umask trims to--xfor
"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+rxmakes the intent explicit and leaves no
margin.
v1.4.2
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
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.msifor 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
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 kubeconfigexeclogin 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
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 anamespace/namestring 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
spliceat 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: -
/andpin 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/⌃uin the detail panel — half-page scroll, next to ⇞/⇟ -
pon an event — jump to the object the event reports on -
⌃]and⌘↑— leave a focused terminal, which listed onlyesc
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.