Skip to content

feat(ui): Follow switched by scrolling; a deleted namespace noticed from its own events - #82

Merged
hans-fischer merged 16 commits into
mainfrom
feat/follow-by-scroll-ns-events
Oct 22, 2026
Merged

hans-fischer merged 16 commits into
mainfrom
feat/follow-by-scroll-ns-events

Conversation

@hans-fischer

Copy link
Copy Markdown
Contributor

Two findings of the owner's alpha walk of 2026-10-22 (AW11, AW14):

  • A namespace the filter names that is deleted now turns the filter red at once.
  • Follow in a logs tab is switched by scrolling.

Cause

  • AW11: the window read the namespaces again only when the view's rows changed (recheckNamespaces) or when the filter opened. A Pods view filtered to a namespace without pods never changes. So walk-gw9 going Terminating, then gone, showed red only much later.
  • AW14: the log view had no notion of the user's scroll. Follow stayed on until it was unticked by hand, and the next lines snapped the view back.

Fix

  • AW11: a new engine watch (nswatch.go) holds the namespaces while the shown cluster's filter names some and the user may list them.
    • It emits EventNamespaces when a namespace comes, goes, or starts or stops terminating, coalesced to one per 500 ms. The window reads the namespaces again on it.
    • The view's rows no longer trigger a read.
    • A namespace-limited user gets nothing listed, and a refused list ends the watch until the access is refreshed.
  • AW14:
    • The list's scroller reports every offset change, and the view's own scrolling runs quietly.
    • A user's scroll five rows up switches Follow off; scrolling back down to the newest line switches it on.
    • A smaller scroll up keeps following, that far behind the newest line.
    • New lines from the stream never switch Follow on. The scroll bar counts as the user's scroll, and the checkbox works as before.
  • The lead's details are in D-106 (AW11) and D-99 (AW14, with the owner's spec verbatim), open to revision.

Tests

  • Engine: TestNamespaceEventsWhileFiltered, TestNamespaceEventsCoalesced, TestNamespaceWatchOnlyWhileFiltered, TestNamespaceWatchOfANamespaceLimitedUser, TestNamespaceWatchWaitsForTheAccessAnswer, TestNamespaceWatchEndsAtARefusal.
  • Window: TestNamespaceDeletedWhileFiltered, rewritten.
  • termview: TestLogsFollowOffByScrollingUp, TestLogsFollowByTheScrollBar, and TestLogsFollowCheckboxAsBefore as a guard.
  • All but the guard failed before the change.
  • P5 (log batches) is unchanged: the slowest batch took 5.0 ms against a 16 ms budget.
  • -race was clean on the engine namespace tests and the termview tests. Lint, make cyclo and vet with the kind, envtest and standby tags are clean.
  • Not tried: a real window, trackpad momentum, a real cluster. Docker was down, so the kind test did not run locally; CI's kind jobs cover it.

…outlined

The owner's alpha walk of 2026-10-22 on v0.38.0 (G16):

- AW3, AW19: the window's Escape knew the palette, the notifications, a
  settings page and the drawer only, so over a dialog it closed the drawer
  behind it or did nothing. Every overlay now registers what Escape does
  over it as it shows (escape.go: onEscape, onShown); the window's escape
  runs the top one's, an overlay without one takes the key. Every Fyne
  dialog is shown through sh.showDialog (Escape is its Dismiss), a framed
  dialog hides (its SetOnClosed answers no), the pop-ups, the palette, the
  notifications, the MCP details and the top bar's menus close.
- Fyne hands a key to the focused widget only: the fields of Cogpit's
  dialogs are types that pass an Escape on (fieldEntry, fieldSelectEntry,
  fieldCheck, fieldSlider, secretEntry), so Escape cancels a dialog
  whatever field has the keyboard.
- AW4: a list's search is cleared and the table gets the keys; the find bar
  closes from its fields and from the editor (yamlview's OnEscape). D-57's
  "any other search" had been the navigator's only.
- AW2: Escape on an open dropdown's list takes the keyboard off the field,
  and a dropdown with the keyboard passes Escape on.
- AW1: a closed dropdown draws an entry's outline; the field outline token
  is raised to 2:1 on every surface in both themes, the wells' and cards'
  edges keep the old value as colorHairline.

TestEveryDialogTakesEscape reads the sources for a dialog or pop-up shown
past the mechanism. D-116 and D-104 record the lead's decisions; G16,
ui-parity, usage, architecture, conventions and the design language say so.
The owner's alpha walk (AW12): a click below a list's rows closed the
drawer about a second later, × and Escape at once. Fyne's driver holds
every tap on a fyne.DoubleTappable for the double-click interval before
it calls Tapped (internal/driver/glfw/window.go,
mouseClickedHandleTapDoubleTap; NSEvent.doubleClickInterval on macOS,
0.5 s by default), and the list's table was one for its header
separators' double click, which fits a column.

The table now counts that double click itself, as the tab bar and the
navigator do (doubleClickTime and the tab bar's clock), and is no
DoubleTappable: every click on it acts at its release. Rows and headers
were not held: their cells take their own taps.

TestListClicksAreNotHeld clicks a row, a header, a header separator and
the area below the rows as the driver finds the object under the pointer
and fails on a DoubleTappable target; it and the reworked
TestDoubleClickSeparatorFitsColumn fail before the change. D-106's GW1
table records the cause and the lead's choice; G16, ui-parity and
architecture follow.
The welcome page's cluster rows were a fyne.DoubleTappable, their double
click doing what a click does, so Fyne's driver held every click on them
for the double-click interval (AW12's cause, found on the table first).
The row takes clicks only now.

Without the hold, a double click's second click reaches the row again,
which the page has rebuilt by then for the cluster connecting: a second
cluster.connect would join the run under way but end the first one's
wait with errConnectInBackground, an error toast naming another cluster,
and a login program not allowed yet would be reviewed twice. The page
therefore does nothing for a cluster whose connect it began and that has
not returned (pending, from before the review or the dispatch to the
engine's answer) or that the window shows connecting.

TestWelcomeRowConnectsOnce fails on the old row (held) and on a row
without the page's guard (connected twice). D-106 records the choice
and the clicks left held (the sync picker's rows, the log tab's head
row) with why; G16's AW12 entry follows.
…114)

The owner's AW17, AW13 and AW18 of the alpha walk (2026-10-22, D-114).

A chosen cluster's failed connect, the restore's or a click's, now shows
that cluster's error page over the workspace whether or not a tab is
open; the welcome page shows only without a chosen cluster. The page is
the welcome page's look: the logo, that it is an error page, the cause
in a sentence under the stage it failed in, the connect stage by stage,
Connect Again and Show Connection Updates.

The cluster layer measures every connect in stages: the kubeconfig, a
credential plugin's login, DNS with its addresses, TCP to the address
and port (or the proxy's), TLS and the API server's answer. DNS, TCP
and TLS come from client-go's own first request through
net/http/httptrace, so Cogpit opens no connection of its own. The
stages reach the connection log (an attempt's stages, a record each in
its file), UIState.ConnectFailure and the ConnectError; the
connection's error is the failed stage's sentence, the chain stays in
the log. Connection Updates shows each connect's stages.

The welcome page lists the hotbar and Recent only, the kubeconfigs'
clusters as "Your clusters" on a first start alone (AW13). The view's
placeholder no longer says the failure and fits the view with an
ellipsis: its long line drew a clipped "Could not" over the navigator
(AW18). The window tests' fake engine derives the welcome page where
the connection changes, as the engine does.
TestWelcomeRowConnectsOnce failed on the stacked tree for two reasons of
D-114's commit, not of the guard. The window tests' fake engine now
derives the welcome page as the engine does, which hides it while the
shown cluster connects, so the row it waited to see rebuilt never was.
And the page has no "Other clusters" any more (AW13): once a cluster is
recent, the others' rows went, and the later steps found none.

The scene puts every cluster on the hotbar, so each keeps its row, and
keeps the page up after each connect the test shows, so that a click on
the connecting cluster's row is still checked; a last step checks that
the page hides while the shown cluster connects. The error page's test
checks that Connect Again, which goes through the welcome page's
connect, connects once for a double press.
…o failures

The owner's alpha walk of 2026-10-22 (AW15, AW16): his own Cancel
Connecting raised the error toast "connect <context>: connecting
cancelled", and a click on another cluster while a login waited raised
"connect <context>: another cluster was shown; the connect goes on in the
background". cluster.connect answered the background case with
{state: connecting}, but hotbar.openCluster, ui.tab.select and
ui.setNavigatorScope passed the Go error on, and the window toasted every
error but a declined confirmation. A cancel that ended the run at the
moment the window's connect saw it said "superseded by a newer connect".

The engine now types the outcomes (ErrConnectCancelled,
ErrConnectSuperseded beside ErrConnectInBackground, ConnectNotFailed),
awaitRun answers cancelled or in the background on every race instead
of "superseded", every connecting command answers a background connect
without an error, and the window's showError and the --context answer
skip the three. MCP and the built-in agent read a cancel as cancelled,
not failed. A second connect of the cluster the window's connect waits
for joins that wait instead of ending it, so the first no longer says
another cluster was shown. A failed connect keeps its toast.

D-97 records the lead's decisions; D-66, G16, ui-parity and usage follow.
The owner's decision of 2026-10-22 reverses D-25's "copyable only once
revealed": a masked value in the drawer has the copy icon on hover, its
tooltip "Copy the value without showing it". A tap runs the GUI-only
secret.reveal with whole and puts the whole value on the clipboard for
Secret values (concealed, this Mac only, cleared after 60 seconds); the
value enters no widget and not the drawer's revealed map, and the toast
names the key only. An answer for a drawer that moved on copies nothing.
In a Secret's editable drawer (D-59) a masked value copies the same way
and opens no editor; an edited or added value hidden again copies what
was typed. Empty and cut answers copy nothing; binary values have no
such icon. No command copies.

D-25, H-25 and the Secrets pages record the decision verbatim and the
lead's details.
The lead's decision (D-25, open to revision): a revealed value that Show
cut at 4 KiB or 40 lines copies the whole value through its copy icon, by
the masked value's path (secret.reveal with whole, the clipboard for
Secret values, the toast naming the key), its tooltip "Copy the whole
value"; the drawer keeps the cut text. A value Show did not cut copies
what it shows, and a selection with Cmd+C exactly the selection, as
before. The fetch hook is shared (fetchCopyFor, copyWhole, wholeRead).
The owner's alpha walk (AW7): the second line of a dialog's Context row,
the context's source label "[kubernetes_configs/<file>]", read as a
relative path. His choice: keep the label as it is, in grey. placeRows
now draws the context id as before, wrapped the same way, with the part
from "[" on in the kind word's muted colour (contextValue,
paragraph.mutedFrom), in every dialog that names a place.
The owner's alpha walk (AW9): with the dock minimised, Edit on an object
whose Edit tab was open sometimes did nothing. Every command that reuses
a dock tab set the active tab to the id it already had when that tab was
the active one; SyncDock opens the dock only for a tab added or another
tab made active, so the dock stayed collapsed. It worked when another tab
was active, hence sometimes.

UIState.ShowDockTab makes a tab active and opens the dock on it (a hidden
cluster's tab opens that cluster's dock for later, never the shown one);
every command that may reuse a tab uses it: Edit, a pod's and a
workload's logs, Helm Upgrade and Install, the Audit Log, the
Diagnostics, a draft restored into its tab, dock.activate and
dock.reconnect.
The owner's alpha walk (AW10): ⌘⌥⇧← and ⌘⌥⇧→ did nothing in the dock;
ui.tab.moveLeft and ui.tab.moveRight moved only the tab bar's active
tab. Both take an optional dock now, which steps the dock's active tab
among the tabs the dock shows (Session.StepDockTab, greyed with the
reason at the dock's ends), and the window sends it while the keyboard
works in the dock: a focused widget in the dock, or with nothing focused
a dock tab clicked after the tab bar's last click or key. The keys reach
the menu item from any focus, a terminal's included.
With D-115 a click on a dock tab gives its editor, terminal or log the
keys, so after that click the keys are the dock's by the focus, and
switching the tab bar's tabs by the keys leaves them there.
TestMoveTabKeysMoveTheDocksTab assumed the click left nothing focused and
expected the tab bar's tab to move after ⌘⌥→ with the keys still in the
dock's editor. The test's scenes now follow the focus as a click moves
it, and check the case the click's memory is still for: a dock tab that
takes no keys (the Audit Log). The rule's texts in tabmovekeys.go and
D-106 say so.
…spaces' own changes

The owner's alpha walk, AW11: the Pods view filtered to walk-gw9, a namespace
without pods held Terminating by a finalizer, turned the filter's button red
only much later. The window read the namespaces again only when the view's
rows changed, which a namespace without objects of the view's kind never does.

The engine now watches the namespaces while the shown cluster's namespace
filter names some and the user may list them (one handle, shared through the
cache), and emits EventNamespaces when a namespace comes, goes or begins or
ends terminating, coalesced to one per 500 ms. The window reads the namespaces
again on it; the rows no longer do and recheckNamespaces' timer is gone. A
user the access reviews do not let list namespaces gets nothing listed; a
refused list ends the watch until the access is refreshed.
@hans-fischer
hans-fischer force-pushed the feat/follow-by-scroll-ns-events branch from 9c39082 to ffc886e Compare October 22, 2026 16:06
The owner's request in his alpha walk, AW14: he forgets to untick Follow when
he scrolls up. A scroll of five rows or more up from the newest line (the
wheel, the trackpad, the scroll bar's drag or track) now switches Follow off
and the log stands where he scrolled; scrolling back down to the newest line
switches it on again. A smaller scroll up keeps following, that far behind the
newest line, so a fast stream does not snap the view back under the wheel.
The stream's own lines never switch Follow on; the checkbox works as before.

The list's own scroller reports every offset change; the view's own
scrolling and layout (flushes, jumps, a selection's auto-scroll, dropped
rows, resizes) run quietly and do not count as the user's.
@hans-fischer
hans-fischer force-pushed the feat/follow-by-scroll-ns-events branch from ffc886e to 0485ba0 Compare October 22, 2026 16:09
@hans-fischer
hans-fischer merged commit e2cf936 into main Oct 22, 2026
19 of 23 checks passed
@hans-fischer
hans-fischer deleted the feat/follow-by-scroll-ns-events branch October 22, 2026 16:37
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant