The portfolio filter row was five flat dropdowns that wrapped across the
toolbar. It is now one Filters popover with labelled sections (Attention,
Target, Node, Drift, Evidence) and a count badge, the same shape as the
Fleet toolbar. Stack and Blueprint scopes set by other surfaces still show
as removable chips, and Clear filters still resets everything, including
filters only a deep link can set.
Option groups expose their section label and pressed state to assistive
technology, and the Node and Drift dropdowns are tied to their labels.
The Fleet filters popover now lays out its Critical Only button in the same
row as the other sections.
* fix(gitops): keep failing applications inside the portfolio merge cap
* fix(gitops): repair phone search and paging, bound the attention queue, add clear filters
* feat(gitops): open the portfolio scoped to a stack from its Git indicators
* fix(gitops): list hub-local legacy Git sources and reach hub-only views from a remote node
* feat(gitops): add the hub-owned GitOps portfolio read API
Adds the canonical aggregate read surface behind the GitOps workplace:
- GET /api/gitops/applications: one bounded response covering Direct
applications from the hub and every reachable node, plus hub-owned
Blueprint applications. Remote legs probe each node's git-sources with
a bounded timeout, rewrite node identity into hub numbering, and
re-authorize every row against the calling user; unreachable or
unsupported nodes become named coverage entries rather than vanishing
rows.
- GET /api/gitops/applications/🆔 the row plus the canonical
projection, the shared single-application contract (remote ids resolve
through a single-node probe; legacy ids resolve by stack name).
- Server-owned attention classification (services/gitops/attention.ts):
a closed reason union with failure/pending tone, computed once from
canonical facets, so no client re-derives "needs attention".
- Per-application posture (failed/attention/in_progress/converged/
converged_qualified/unknown) from the same facets; qualified
convergence never claims exactness, and an unrecognized status from a
newer node is reported as unknown evidence instead of being
reinterpreted.
- The surface is Community and hub-only: the prefix is listed in
HUB_ONLY_PREFIXES and node names are withheld from callers without
node:read.
* feat(gitops): add the GitOps workplace view and phone screen
A first-class GitOps destination next to Fleet (launcher, command
palette, direct URL /nodes/<hub>/gitops, hub-only via HUB_ONLY_VIEWS):
- Portfolio masthead: verdict word, application/attention/drifted/
pending/converged counts, and an honesty line that names unreachable
or too-old nodes and says when a failed refresh left last-known data
on screen.
- Attention queue: one entry per canonical reason with the explanation
inline, failures sorted ahead of pending decisions, independent of the
list filters so narrowing the table cannot hide an exception.
- Application table with server-side search, filters (attention, target
mode, node involvement, drift class, evidence), cursor pagination, and
drill-down hand-offs: Direct rows open the stack's Git source panel on
the owning node, Blueprint rows land on Fleet deployments.
- Bespoke phone screen on the shared data hook: masthead-led, chip
filters, inline attention reasons, >=44px targets.
- Refresh is event-driven off the gitops invalidate channel (debounced),
with URL-synced filters that survive reloads; the router no longer
strips query parameters owned by the view.
- Dashboard badges and the sidebar pending indicator link into the
workplace through one shared navigate helper.
* docs(gitops): document the portfolio workplace
Adds the operator page for the GitOps portfolio (what the page answers,
how to read the masthead and queue, filters, freshness honesty) with a
screenshot, and registers it in the docs navigation.
* fix(preferences): accept the gitops destination as a quick-link id
The GitOps nav entry is quick-link eligible, but the backend enum that
validates saved quick-link preferences still listed only the previous
ids, so the preference-parity pretest rejected the value-set drift.
* chore(gitops): drop imports orphaned by the review pass
Three dead imports left behind when the aggregator and route code moved
during review: an unused test import, and two unused type imports.
* refactor(gitops): rebuild the workplace on the shared page patterns
The portfolio page was visually drifting from the rest of the app. It now
uses the same building blocks as the Dashboard, Fleet, and Security pages:
- Masthead rebuilt on the Dashboard/Fleet recipe: beveled card with the
accent tint wash, a rail that shimmers while the data is live, the state
word with a mono meta line, stat tiles (applications, pending, drifted,
converged with a qualified sub-line), the attention count in the alerts
slot, and a live ticker so "updated Xs ago" keeps moving. A failed
refresh reuses the Dashboard's stale chip.
- Filter row matches the Security Images and Fleet tabs: the collapsible
search affordance (square outline button expanding into a focused input)
plus flat combobox filters on one wrapping row, replacing the bespoke
chip set.
- Table rebuilt on the shared table shell: beveled card, ScrollArea so the
scrolling stays inside the table, tracked-mono header cells, hover-tinted
rows with the application name as the drill-down target, and the
pagination footer in the standard ghost-button style.
- The e2e spec drives the new control shapes.
* test(nav): poll for the settled Navigate panel size
The panel-size assertion read scrollHeight/clientHeight once, immediately
after the panel opened. The popper's available-height variable is applied
after opening, so a single read can observe the pre-positioning clamp and
report a settled panel as clipped; on CI this failed intermittently while
the same check passes consistently once the layout settles. Poll until
the metrics stabilize, which is what the assertion actually means.