Commit Graph

727 Commits

Author SHA1 Message Date
SaelixCode 1d1fba0e33 fix: fail closed on corrupt mute schedules and clean invalid replicas
Empty or whitespace stored schedules no longer act as all-day mutes. Invalid schedules trigger remote DELETE cleanup, and the weekly-window form gains accessibility attributes plus component coverage.
2026-07-21 11:47:56 -04:00
SaelixCode 48a2662f5e feat: add weekly UTC maintenance windows to mute rules
Let mute rules suppress only during recurring UTC windows, normalize
replica node identity, and fail-open when remotes lack schedule support
so older nodes never keep an all-day scheduled mute after a successful cleanup DELETE.
2026-07-20 23:46:15 -04:00
Anso ad00517a0e fix(settings): toast and block save when node settings fail to load (#1654)
* fix(settings): toast and block save when node settings fail to load

Failed GET /settings no longer seeds defaults as the dirty baseline. Shared
loader pins node identity, shows an error callout until an authoritative load
succeeds, and keeps Save disabled across all eight node-scoped sections.

* test(settings): cover load-failure toast and App Store refresh pin

Remove the incorrect settings-load-failure docs image. Assert toast ownership for current vs stale/aborted loads, and pin App Store cache refresh to the PATCH node across a mid-save switch.

* fix(settings): reject malformed settings bodies and skip pre-node bootstrap load

Treat non-object 200 /settings payloads as load failures so null or array bodies cannot seed editable defaults. Do not fetch or toast while the active node id is still undefined, so hard refresh settles to a single failure toast.
2026-07-20 21:40:35 -04:00
Anso 090a0d73ac feat: add configurable notification dispatch retries (#1655)
* feat: add configurable notification dispatch retries

Allow each node to set 0-3 extra in-process delivery attempts with a fixed
one-second delay for routes, agents, and Test dispatch.

* fix: harden notification retry settings load/save and channel tests

Guard Delivery retries against failed and out-of-order same-node settings responses, and cover Slack/webhook retry classification alongside Discord.

* fix: clear Delivery retries saving state and correct screenshot alt

Separate save-request ownership from value-generation invalidation so a successful PATCH cannot leave Save retries stuck on Saving, reset saving on node switch, and align the Channels screenshot alt with the committed image.

* fix: surface invalid notification retry settings instead of false saved clamp

Align Channels GET handling with the backend strict 0-3 parser so stored values like 9 or 1.5 show as error needing repair, matching runtime fallback to 0 instead of displaying a clamped saved policy.
2026-07-20 20:29:17 -04:00
Anso 859839082c fix: pin brace-expansion to a patched version in both packages (#1658)
npm audit was failing CI with a high-severity DoS advisory
(GHSA-3jxr-9vmj-r5cp, plus GHSA-jxxr-4gwj-5jf2 on the frontend) for
brace-expansion, pulled in transitively through eslint -> minimatch.
It is a lint-only dev dependency, never bundled into the runtime
image, but the audit gate blocks on it regardless.

Pins it via overrides instead of accepting the wider dependency bump
npm audit fix proposes, since minimatch already declares a compatible
range for the patched version.
2026-07-20 19:16:32 -04:00
Anso 3b027957c4 fix(fleet): isolate corrupt snapshot file decrypt failures (#1650)
* fix(fleet): isolate corrupt snapshot file decrypt failures

A single damaged encrypted fleet-snapshot row no longer fails detail, restore, or off-site upload for the whole snapshot. Unavailable members are marked, restore is blocked before mutation, and cloud upload fails closed with no PutObject.

* fix(fleet): fail closed on damaged enc snapshot envelopes

Unrecognized enc: payloads no longer fall through as usable plaintext. Only clear legacy prose stays readable; delimiter-byte and similar envelope damage stays unavailable through restore and cloud upload.

* fix(fleet): subordinate legacy enc prose to envelope shape

Legacy exceptions no longer trigger from = or whitespace alone. Encryption-shaped payloads (length and hex density) stay unavailable through restore and cloud upload, while short genuine prose such as enc:hello remains usable.

* fix(fleet): preserve non-envelope enc legacy plaintext

Any non-empty enc: payload that is not encryption-shaped is kept verbatim, including punctuation forms such as enc:hello-world, while envelope-shaped damage remains unavailable.
2026-07-19 20:08:31 -04:00
Anso d94e586af3 feat(blueprints): require confirmed rollout preview before reconcile (#1649)
* feat(blueprints): require confirmed rollout preview before reconcile

Persist place/remove approval with an intent fingerprint and transition matrix so Apply, Retry, ticks, and pin cannot mutate the fleet until the operator confirms the current blast radius. Preview surfaces requirements, health, and informational in-flight rows without executing them.

* fix(blueprints): silence unused retry nodeId lint error

* test(blueprints): harden approval gate coverage and preview clarity

Add real reconcileOne place/remove fan-out and STALE_GUARD regressions, surface reachability in the rollout dialog, align warning totals, and document the fail-closed upgrade pause.

* test(blueprints): cover legacy approval schema migration

Seed a pre-approval database with an enabled Blueprint and live deployment, run production DatabaseService startup, and assert pending null auth columns plus a fail-closed reconcile gate.

* test(blueprints): clarify legacy approval migration fixture

Extract seed/boot helpers so the migration regression reads as a linear upgrade path without changing assertions.

* fix(blueprints): report apply outcomes and gate manual withdraw

Return per-node reconcile outcomes from Confirm Apply, block create preview on unmanaged same-name stacks, and require an approved remove outcome for every manual withdraw or evict.

* fix(blueprints): scope withdraw approval to destructive eviction

Require remove approval only for snapshot/evict confirms and evict_blocked rows. Keep plain stateless standard withdraw as an immediate stop, and update withdraw-route tests to seed remove approval when needed.
2026-07-19 15:30:26 -04:00
Anso 972f2b9483 feat: stack glob patterns and route severity levels (#1651)
* feat: add stack glob patterns and route severity levels

Operators can filter notification routes and mute rules with anchored * globs, and routes can target info, warning, or error. Matching is fail-closed for unsafe stored patterns; write paths keep partial-PUT semantics and ReDoS caps.

* test: split mute and routing chip tests to avoid dialog race

* fix: move stack pattern client validator out of PatternChips

* fix: bound stack glob matching and atomic pattern chip saves
2026-07-19 14:54:48 -04:00
Anso 63213c0960 feat: add service-scoped Compose update and restore (#1648)
* feat: add service-scoped Compose update and restore

Allow updating or rebuilding one declared Compose service on multi-service
stacks without recreating siblings, with recovery snapshots, health-gate
observation, and prune holds for rollback images. Full-stack update paths
and single-service UX stay unchanged.

* fix: sanitize service-scoped update log messages for CodeQL

* fix: address service-scoped update audit findings B-01 through B-07

* fix: complete service-scoped update audit metadata and surfaces

* test: wrap Updates readiness tests for deploy-feedback context

* fix: keep service recovery reachable without Deploy Progress

Make failed service-gate recovery discoverable when Deploy Progress is
disabled or dismissed, suppress stale image-scan notification side
effects, normalize ComposeService line endings, and add focused
regression coverage.

* fix: resurface ContainersHealth density and expand on multi-service stacks

Service grouping hid the summary strip and Compact/Detailed/Expand controls that still applied to multi-container stacks.
2026-07-19 02:42:29 -04:00
Anso 83b3d932e5 feat: add Apprise as a fourth notification channel (#1644)
* feat: add Apprise as a fourth notification channel

Support keyed and stateless Apprise endpoints with secret-safe public DTOs, fail-closed malformed config, and mode-specific Settings UI. Docs and screenshots updated for four-channel Channels and routing.

* fix: harden Apprise secrets at rest and preserve-on-write saves

Encrypt Apprise endpoint and config with CryptoService so a downgrade cannot leak via SELECT *. Align channel and routing saves so blank destination fields omit config on same-mode URL edits, enforce keyed notify IDs, and keep secrets_redacted truthful.

* fix: harden Apprise route type changes and mixed-version config UI

Require a raw channel_url when switching notification-route types so ciphertext cannot strand under Discord/Slack/webhook. Default missing remote apprise status, replace Channels state on node switch, and exercise the production config-column migrator.

* fix: tolerate stub fleet configuration payloads without agents

Normalize remote Apprise agent status only when notifications.agents is present so successful Pilot/stub fetches stay online instead of throwing into the offline catch path.

* fix: correct TypeScript in configuration normalize tests

* fix: ignore stale Channels agent bodies after node switch

Compare the active node after response JSON parsing so a slow body
cannot overwrite the newly selected node's channel state.

* fix: isolate corrupt Apprise crypto and keep keyed Tags visible

Decrypt failures on one Apprise row no longer 500 agent/route lists or
suppress sibling channel dispatch. Treat public /notify/<redacted> as keyed
so Tags remain editable after reload.
2026-07-18 16:32:58 -04:00
Anso 674220b9de fix(fleet): expose Community cordon on NodeCard (#1646)
Drop the stale isPaid conjunct so desktop cordon matches backend and mobile permission-only gating.
2026-07-17 20:23:34 -04:00
Anso 35bb74425b feat: guide missing external network creation during deploy (#1645)
* feat: guide missing external network creation during deploy

Detect missing external networks before Compose runs, prompt or auto-create safe bridge networks, and keep unsupported declarations blocked with trusted deploy provenance.

* test: align deploy context and settings fixtures with missing-network gate

Update caller spies, EffResource expectations, StacksSection save keys, and git-source spy cleanup so CI matches the new deployStack context and auto-create setting.

* fix: drop unused renderError binding in missing-network resolver

Satisfies no-unused-vars so backend ESLint CI passes; callers already key only on model presence.

* fix: use HTTP-safe clipboard helper in missing-network dialog

navigator.clipboard fails on plain HTTP LAN hosts; route copy actions through copyToClipboard so Docker and Compose copy buttons work on self-hosted instances.

* fix: simplify missing-network dialog actions and copy label

Drop the Compose snippet escape hatch, move secondary actions under More, and rename the terminal copy action to Copy create command so the footer is a clear Cancel / Create decision.
2026-07-17 14:51:00 -04:00
Anso 25586fc8ab feat(ui): add Classic, Smart, and Compact desktop navigation styles (#1642)
* feat(ui): add Classic, Smart, and Compact desktop navigation styles

Introduce a shared app-nav registry and reachable model so TopBar, the
command palette, and mobile menus share one destination source. Smart bar
is the default; Appearance gains a Navigation subsection with quick links.

* fix(e2e): stop clearing top-nav prefs on every reload

The desktop-navigation suite used addInitScript to wipe mode storage,
which re-ran on reload and undid the classic/compact values under test.

* fix(ui): harden nav PR docs and Compact quick-link coverage

Drop the partial docs-refresh import that left missing image assets and a stale Display screenshot, keep navigation-scoped operator docs against main, and add an E2E path that proves Compact quick-link add, persist, and render after reload.

* fix(ui): polish Compact quick links and menu mastheads

Align Smart/Compact menus with Theme chrome, keep pin labels always visible, and drive add capacity from persisted pins (max five) with a trailing + picker and per-pin remove.

* test(e2e): exact-match Compact Networking pin locator

Avoid Playwright strict-mode clash with Actions for Networking.

* fix(ui): simplify Compact quick link removal and fix + button trailing

Remove the (...) dropdown per quick link in Compact mode. Right-click
context menu remains as the sole on-bar removal affordance. Fix the +
add-button to trail quick links rather than pinning to the far right
by removing flex-1 from the quick-link rail.
2026-07-16 21:48:03 -04:00
Anso d8e4ede94f fix(notifications): neutralize satellite-local node names in alert bodies (#1640)
* fix(notifications): neutralize satellite-local node names in alert bodies

Fleet-aggregated alerts embedded each instance seed name (often Local)
while the hub badge already named the remote. Drop identity prefixes and
use type-aware local wording so attribution stays on the badge.

* fix(docs): correct image-update default check cadence

Operator docs still said six-hour polling; the seeded default is two
hours in interval mode, and the cadence is configurable or cron-based.

* test(notifications): assert hub stamps roster name on neutral remote bodies

Cover the fan-in path that attaches hub roster identity while leaving the
satellite message body unchanged.
2026-07-16 15:51:52 -04:00
dependabot[bot] 4bdeb9d55a chore(deps): bump the all-npm-frontend group in /frontend with 5 updates (#1635)
Bumps the all-npm-frontend group in /frontend with 5 updates:

| Package | From | To |
| --- | --- | --- |
| [lucide-react](https://github.com/lucide-icons/lucide/tree/HEAD/packages/lucide-react) | `1.23.0` | `1.24.0` |
| [eslint](https://github.com/eslint/eslint) | `10.6.0` | `10.7.0` |
| [typescript](https://github.com/microsoft/TypeScript) | `6.0.3` | `7.0.2` |
| [typescript-eslint](https://github.com/typescript-eslint/typescript-eslint/tree/HEAD/packages/typescript-eslint) | `8.63.0` | `8.64.0` |
| [vite](https://github.com/vitejs/vite/tree/HEAD/packages/vite) | `8.1.3` | `8.1.4` |

Updates `lucide-react` from 1.23.0 to 1.24.0
- [Release notes](https://github.com/lucide-icons/lucide/releases)
- [Commits](https://github.com/lucide-icons/lucide/commits/1.24.0/packages/lucide-react)

Updates `eslint` from 10.6.0 to 10.7.0
- [Release notes](https://github.com/eslint/eslint/releases)
- [Commits](https://github.com/eslint/eslint/compare/v10.6.0...v10.7.0)

Updates `typescript` from 6.0.3 to 7.0.2
- [Release notes](https://github.com/microsoft/TypeScript/releases)
- [Commits](https://github.com/microsoft/TypeScript/commits)

Updates `typescript-eslint` from 8.63.0 to 8.64.0
- [Release notes](https://github.com/typescript-eslint/typescript-eslint/releases)
- [Changelog](https://github.com/typescript-eslint/typescript-eslint/blob/main/packages/typescript-eslint/CHANGELOG.md)
- [Commits](https://github.com/typescript-eslint/typescript-eslint/commits/v8.64.0/packages/typescript-eslint)

Updates `vite` from 8.1.3 to 8.1.4
- [Release notes](https://github.com/vitejs/vite/releases)
- [Changelog](https://github.com/vitejs/vite/blob/main/packages/vite/CHANGELOG.md)
- [Commits](https://github.com/vitejs/vite/commits/v8.1.4/packages/vite)

---
updated-dependencies:
- dependency-name: lucide-react
  dependency-version: 1.24.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: all-npm-frontend
- dependency-name: eslint
  dependency-version: 10.7.0
  dependency-type: direct:development
  update-type: version-update:semver-minor
  dependency-group: all-npm-frontend
- dependency-name: typescript
  dependency-version: 7.0.2
  dependency-type: direct:development
  update-type: version-update:semver-major
  dependency-group: all-npm-frontend
- dependency-name: typescript-eslint
  dependency-version: 8.64.0
  dependency-type: direct:development
  update-type: version-update:semver-minor
  dependency-group: all-npm-frontend
- dependency-name: vite
  dependency-version: 8.1.4
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: all-npm-frontend
...

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Anso <dev@anso.codes>
2026-07-15 21:18:34 -04:00
Anso 8980910153 feat: node-scoped Networking operator page (#1603)
* feat: add node-scoped Networking operator page

Adds a Networking view with overview, topology, inventory, and findings.

Shared aggregate reads back the page; Resources keeps prune and redirects here.

Includes fail-closed network delete guards, operator docs, and /nodes/:slug/networking routing.

* fix: rename unused variable n to _n to satisfy no-unused-vars lint

* fix: keep top bar search clickable when nav grows

* feat: complete Compose-first Networking Phase 2 operator assistant

* fix: move networking action visibility helper out of component module

* feat(networking): complete Compose-first Networking operator page

Finish the node-scoped Networking page (Overview, Networks, Topology,
Findings) with design-system parity and correct finding semantics.

- Rebuild detail sheets on SystemSheet/SheetSection; align the tab band,
  masthead, and mobile tone with Fleet and Security.
- Encode the host-mode and exposure severity matrix; fix collision counts
  so intentional shared externals are not flagged; add one typed drift
  predicate shared by inventory, topology, badges, and overview counts.
- Preserve per-container attachments and IPs on topology node clicks;
  drawer-only click with an explicit logs action; ownership and boolean
  filters; bound large graphs before layout.
- Aggregate cached Compose Doctor findings into the Findings tab with
  honest source labels, structural merge and dedupe, staleness
  reconciliation, and a shared exposure-context helper both engines use.
- Networks tab: privacy-safe service search, precise ownership counts,
  schema v3 with version-2 adapters on every endpoint, pre-confirm delete
  reasons, and the shared sortable table with an internal scroll region.
- Interop: Fleet node-card networking signal with pending-intent
  navigation, stack-to-node backlink, and Dossier/Drift deep links.
- Enrich sanitized inspect with an allowlisted connected-container list;
  fetch topology once and filter client-side.
- Docs and tests across every new finding kind, adapter, and flow.

* fix(networking): correct drift count, exposure fail-soft, and inspect crash paths

Address code-review findings on the Networking page implementation:
- Fix the Overview drift count to use the shared drift-kind predicate instead
  of a hardcoded list that omitted external-network-missing.
- Gate Compose Doctor's unclassified-exposure and reverse-proxy-undocumented
  rules on exposure-context availability, so a DB read failure no longer
  fabricates findings (mirrors the live engine's existing fail-soft behavior).
- Guard the per-stack exposure-intent read in topology aggregation so a
  transient DB failure degrades to unknown intent instead of failing the
  whole response.
- Harden the network detail drawer against a partial inspect payload from an
  older remote node, and log the real error instead of a bare catch.
- Remove now-duplicated severity-rank and drift-kind helpers in favor of the
  shared modules; drop dead backend-only exports; widen the frontend schema
  version type to a plain number instead of casting past a literal type.
- Add coverage for the delete-guard precedence, the full host-mode severity
  matrix, the schema-2 compatibility adapter, and the sanitized connected-
  container allowlist; tighten two tests that were not exercising the
  behavior they claimed to.

* fix: add missing onOpenNodeNetworking prop to FleetView experimental test

The added required prop on FleetViewProps broke the merge-build when the
test file (on main but not on this branch) was compiled against the
updated FleetView interface.
2026-07-14 20:18:10 -04:00
Anso 8096799e49 docs: align Community and Admiral assurance positioning (#1632)
Reframe docs, OpenAPI registries copy, and License trial/expiry helpers
so Community is the full AGPLv3 product and Admiral leads with current
assurance (Hardened Build, Recovery Vault, support, governance).
Mesh, Secrets, and Host Console stay limited-availability without
Admiral sales framing.
2026-07-14 19:44:28 -04:00
Anso c170c3f30c fix(ui): default Reduced Motion on Calm and quiet decorative rails (#1622)
* fix(ui): default Reduced Motion on Calm and quiet decorative rails

Calm (and Reset to default) enables Reduced Motion; Signature clears it. Manual Motion stays independent of the visual-style card. Reduced Effects also stops decorative masthead rail animations. Related to #1614; does not close that issue.

* test(e2e): fix Calm Motion specs racing empty-create compose edit

Stop appearance init-script reseeding after mid-test mutations, and wait for Save and Deploy / mobile compose editor after empty create instead of assuming Anatomy Edit compose.
2026-07-14 17:58:38 -04:00
Anso b70a529656 feat: add developer-mode startup and stack hydration timing (#1619)
* feat: add developer-mode startup and stack hydration timing

Instrument boot-to-list and detail hydration with commit-aligned milestones, truthful request stages, and destination/gateway debug duration logs so performance work is guided by measurements.

* fix: redact stack names and complete hydration request stages

Stop logging stack identifiers in containers debug timing, and record state_dispatch (plus detail fetch spans) so copied reports match the advertised stage breakdown.
2026-07-14 17:24:25 -04:00
Anso 381ed2a91f feat: add Admiral Hardened Build channel and business assurance surfaces (#1629)
* feat: add Admiral Hardened Build channel and business assurance surfaces

Introduce Studio Saelix entitlement-backed Hardened Build switching, a
single-flight image operation coordinator, Recovery Vault naming, Admiral
Account settings, and typed Fleet update failures while preserving Community
custom-repo and targetless pull-current updates.

* fix: harden image-op paths and clear CI CodeQL/pilot flake

Validate operation IDs before filesystem use, use hostname checks in Fleet
fetch mocks, sanitize registry probe logs, and swallow expected TCP teardown
errors in the pilot reverse-route post-handshake test.

* fix: sanitize image-op docker config write and probe logs

Allowlist-copy registry host keys and base64 auth before writing the
temp DOCKER_CONFIG, and log registry probe failures with a fixed message
so CodeQL no longer flags network-to-file and log-injection mediums.

* fix: address Admiral Hardened Build audit blockers

Expose imageChannel so hardened Fleet peers still POST for typed rejection, claim community updates before 202, terminalize helper failures, gate Hardened on paid, and align support/docs/e2e wording.

* fix: terminalize image ops on helper survival and aborted claims

* fix: prevent recreating persist from overwriting helper-exit failure

* test: assert helper-exit failure lands before recreating persist

* fix: keep current pointer when acknowledging a stale image operation
2026-07-14 10:47:54 -04:00
Anso 0cd03c6f87 chore(ui): hide Mesh, Fleet Secrets, and Host Console behind experimental discovery (#1624)
Gate Routing, Secrets, Host Console, and Mesh dashboard/settings surfaces on the existing useExperimental readiness flag so immature operator surfaces stay out of the default UI while paid and admin backend gates remain unchanged.
2026-07-13 11:55:50 -04:00
Anso 9b3f5c5a90 docs: relicense Sencho to AGPLv3 and reframe Community positioning (#1623)
* docs: relicense Sencho to AGPLv3 and reframe Community positioning

Replace BSL with AGPLv3 for the public Community product, update contributor
and licensing copy for Community-focused contributions, and surface source
and license links in Settings About.

* docs: clarify CLA scope and Community contribution framing

* fix(ui): split About link constants and harden AboutSection test selectors
2026-07-13 03:49:29 -04:00
Anso c01f2479b9 fix(ui): lower-overhead Reduced effects for constrained GPUs (#1616)
* fix(ui): lower-overhead Reduced effects path for constrained GPUs

Disable backdrop-filter glass and solidify chrome fills when data-effects is reduced (Calm default), with Appearance warning and docs for constrained rendering devices. Related to #1614.

* test(ui): harden Reduced effects glass e2e and docs wording

Parse Color Level 4 slash alpha so opacity asserts are not vacuously true, cover dialog/toast/overlay both ways with stable data-sn hooks, and soften troubleshooting copy that overstated the unmeasured diagnosis.

* docs(ui): lead constrained-GPU guidance with Reduced Motion

Rename the troubleshooting section to neutral GPU framing, point Appearance and docs at Reduced Motion first, and show the callout whenever Motion is off so Calm or Reduced effects alone do not hide the validated workaround.
2026-07-12 22:45:50 -04:00
Anso 78fbc151e7 fix(scheduler): show every Timeline lane dot and rename the Maintenance lane to Upkeep (#1618)
The Timeline lane marker lacked shrink-0, so inside the fixed 80px label
column the widest labels flex-shrank their color dot to zero width. The
Maintenance and Lifecycle dots collapsed and rendered as no dot at all,
while the shorter labels kept theirs. Add shrink-0 so every lane dot
renders at a consistent size.

Maintenance was the only label wide enough to fill the 80px column on its
own, which is what left its dot no room. Rename it to Upkeep so all five
labels fit and align. A registry test now pins each lane's key, order,
and label, and asserts every lane carries a color, so a future rewrite of
the action registry cannot silently rename a lane or drop a dot color.
2026-07-11 22:14:14 -04:00
Anso 2da87c8c5d feat: add mobile row layout to Security History tab (#1608)
Replace the desktop 8-column table with card-like mobile rows on phones,
following the ImagesTab pattern. Each row shows a 44px checkbox for compare
selection, a severity dot, truncated image ref with trigger and timestamp
meta, count tags (total vulns + fixable, at most 2), and a chevron.

Secret/config-only scans use the FINDINGS dot and suppress the clean tag,
matching the getSeverityKey classification from severityStyles.

Extract a shared HistoryStateMessage component to deduplicate the
loading/error/empty state blocks between the mobile and desktop branches.
2026-07-11 20:57:06 -04:00
Anso 3fed67a3f1 fix: assorted UI polish across Home, Security, and Resources pages (#1590)
* fix: polish stack health table, security card heights, and scanner-gate copy

- Remove state dot column from Stack Health rows; state still signaled
  via row tinting and sparkline color
- Reposition Update available badge adjacent to stack name and track
  the theme accent (--brand) instead of static fuchsia
- Equalize Top exploit-risk findings and Severity x exploitability
  card heights via h-full in a stretching grid
- Add resolution prop to CapabilityGate so scanner-gated tabs show
  "Install a scanner from the Scanner setup tab." instead of the
  misleading "Upgrade the node" message

* fix: scope Resources scroll area to table contents only

- Remove bg-background from the outer wrapper so search/filter bars
  no longer sit on a dark strip
- Replace outer ScrollArea with a plain div so search and filter
  controls stay fixed while only table rows scroll
- Add ScrollArea (max-h-[62vh]) inside each table card for Images,
  Volumes, and Networks tabs

* fix: remove static bg-background from Security table scroll areas

Images and History tab tables used bg-background on their ScrollArea,
forcing a near-black fill that didn't adapt to the theme. Removing it
lets the parent card's bg-card show through, matching the Resources
table pattern that correctly respects DIM, OLED, and Light modes.

* fix: collapsible search icon and transparent Combobox across Resources and Security

- Replace always-visible search inputs on Resources tabs (Images,
  Volumes, Networks) and Security Images (desktop + mobile) with
  Fleet-style collapsible search: an icon button that expands to
  a full input on click, collapses on blur when empty
- Add [&>button]:!bg-background to Security Images severity
  Combobox, matching Fleet's transparent dropdown design

* fix: remove redundant orange badge from Unmanaged tab label

The count was shown twice: once as inline text in the tab and again
as an orange bg-warning pill badge. The inline text remains; the
duplicate badge is removed.

* fix: outline buttons invisible in Light mode

- Changed outline variant border from border-input to border so it
  uses --card-border, which maintains a visible 0.035 lightness gap
  from --background in Light mode (was 0.005, essentially invisible)
- Boosted Light-mode --button-inner-glow from a 4% white inset to
  a 10% inset plus a subtle drop shadow for the physical-key feel

* fix: bump table cell horizontal padding from 8px to density-aware token

Changed TableHead and TableCell px-2 (8px) to px-[var(--density-row-x)]
which resolves to 20px comfortable / 16px compact. Matches the density
system used by StackHealthTable and gives every shadcn-based table
proper breathing room on both edges.

* fix: expand collapsible search before typing in ImagesTab test

The test was written for the old always-visible search input. Now that
the search bar collapses to an icon, click it first to expand before
interacting with the input.

* fix: reorder Settings Infrastructure to Nodes, Stacks, Fleet

Move the Stacks section directly below Nodes so the Infrastructure
sidebar reads Nodes, Stacks, Fleet. Pure array-element relocation in
the settings registry; the sidebar renders in array order.

* fix: simplify pilot enroll command and add Step 2 copy button

Step 2 of the pilot-agent enrollment modal now shows "docker compose up
-d" instead of the redundant "-f compose.yaml" form, since Step 1
already has the operator save the file as the default compose.yaml. Add
an inline copy button next to the command so it can be copied without
selecting the text by hand.

* fix: stack Fleet/Security tab heading actions on mobile

The shared FleetTabHeading kept its title and action buttons on one row
at every width, so on phones the Security Suppressions heading and its
Export VEX / Add suppression buttons were crammed together. Stack the
heading and actions vertically below the md breakpoint and restore the
original row layout at md and up, so desktop is unchanged.

* feat: add node selector to mobile Home masthead

The mobile Home page showed the active node as static text, so switching
nodes was only possible from the Stacks page. Render the same compact
NodeSwitcher in the Home masthead kicker slot, giving a second place to
view and switch the active node with the identical popover and Manage
nodes action.
2026-07-11 20:55:37 -04:00
Anso e7ac496009 feat(security): add triage status and OpenVEX justification parity (#1615)
* feat(security): add triage status and OpenVEX justification parity

Share triage options across SuppressionsPanel and the scan-sheet suppress dialog, require justification for not_affected and false_positive, and document the fields.

* fix(security): use design-system Select for triage dropdowns

Replace native selects so OpenVEX justification options use themed popover content instead of unreadable OS option lists in dark mode.

* fix(security): keep triage justification Select controlled

Pass an empty string instead of undefined so Radix Select does not flip between uncontrolled and controlled when the placeholder is shown.
2026-07-11 19:26:26 -04:00
Anso ce699864c1 feat(registries): add exact-ID tag browser with non-401 failures (#1613)
* feat(resources): show multi-stack usedByStacks on images

Classify images with a deduped sorted stack reverse index, surface chips in the Images table and inspect sheet, and clear node-bound sheet selection on active-node change.

* feat(registries): add exact-ID tag browser with non-401 failures

Add GET /api/registries/:id/tags using credentials for that registry row only, map upstream auth failures to 424, and surface a Registry tags section on the image inspect sheet.

* fix(registries): distinguish unreachable hosts from auth failures

Map auth transport errors to REGISTRY_UPSTREAM (502), surface registry list-load failures in the tag panel, document Used by and Registry tags, and add parser coverage.

* fix(registries): drop unused RegistryTagsPanel __test export

The non-component export tripped react-refresh/only-export-components and failed Frontend lint in CI.
2026-07-11 13:21:38 -04:00
Anso 362a18e91a feat(resources): show multi-stack usedByStacks on images (#1612)
Classify images with a deduped sorted stack reverse index, surface chips in the Images table and inspect sheet, and clear node-bound sheet selection on active-node change.
2026-07-11 02:23:28 -04:00
Anso 213d3d5d3a feat(resources): bind prune to fingerprinted itemized plans (#1611)
* feat(resources): bind prune to fingerprinted itemized plans

* fix(resources): repair prune plan volume usage and preview list

Source volume RefCount from docker df, keep preview rows from flex-shrinking, tighten managed image attribution and becomesFree, and stop audit summaries from claiming success on rejected prunes.
2026-07-11 00:11:37 -04:00
Anso 4834e2e51d feat(security): prefer digest identity in scan history (#1610)
Retain scans and History search by digest when available, keep imageRefLike for compatibility, and surface digests in compare.
2026-07-10 22:10:09 -04:00
Anso ba2e7bded9 feat: first-boot compose discovery and adopt-first sidebar (#1600)
* feat: add compose discovery for setup preflight and sidebar empty state

Expose read-only compose discovery via GET /api/stacks/discovery and setup

diagnostics. Replace the blank sidebar with path-aware discovery and move

adopt into a dedicated dialog with a three-tab Create Stack flow.

* test: assert post-setup handoff via sessionStorage read-back

The Setup preflight test spied on Storage.prototype.setItem to check the
post-setup adopt handoff. When the jsdom storage probe fails and the test
harness swaps in its in-memory storage stub (which does not extend Storage),
that stub's setItem never touches Storage.prototype, so the spy records zero
calls and the assertion fails even though the component wrote the value.

Read the value back with sessionStorage.getItem instead, matching how every
other storage test in the suite asserts. This is robust to both the native
jsdom storage and the in-memory fallback.

* fix(setup): surface compose discovery as a preflight check row

Drop the Setup discovery banner and non-working Review button. Show

counts as a pass row in EnvironmentChecks (Setup only) and keep

Enter Sencho as the handoff that opens adopt when candidates exist.

* test(setup): cover zero-count discovery row omission

* fix(stacks): widen adopt scan to any yaml and rename into place

Homelab layouts often use nginx.yml or plex.yml. Surface those for
adopt (except overrides), rename to compose.yaml on move so stacks
register, and reset the confirm UI when a move fails.
2026-07-10 08:42:52 -04:00
Anso e2001fed72 feat: open compose editor in edit mode with one click (#1592)
* feat: open compose editor in edit mode with one click

Remove the desktop double edit gate so Anatomy "Edit compose" and empty-stack
create land in an immediately editable workspace. Close discards both compose
and env buffers; deferred loads keep startInComposeEdit through unsaved confirm.

* docs: align compose editor close behavior and Edit compose labels

Update stack-management and editor docs for one-click edit mode.

Close discards unsaved compose/env edits. Anatomy shortcuts use Edit compose.

* fix(e2e): update routing test to use anatomy-edit-compose-btn test id

The compose editor env tab routing test was written against the old
'edit' button name. The single-compose-edit-gate branch renamed that
button to 'Edit compose' with test id 'anatomy-edit-compose-btn'.
Update the selector to match.
2026-07-09 13:53:26 -04:00
Anso d113004359 feat: add confirmed Take down stack action with optional volume removal (#1599)
* feat: add confirmed Take down stack action with optional volume removal

Expose Take down in the stack header and sidebar with a confirmation dialog
that runs compose down while keeping the stack definition on disk. Optional
volume removal is gated by node capability and stack:deploy permission, with
remote gateway preflight before proxying removeVolumes requests.

Closes #1582

* fix: reset take-down volume checkbox when dialog closes

* test: align getStackMenuVisibility assertions with showTakeDown key

getStackMenuVisibility now returns a fifth lifecycle flag, showTakeDown,
but three exhaustive toEqual assertions still listed only the prior four
keys and failed. Add the expected showTakeDown value to each: true for
the partial and exited running-stack cases, false for the self stack.

* test: cover Take down visibility for running non-self stacks

The getStackMenuVisibility assertions exercised the partial and exited
branches and the self-stack guard, but not the raw === 'running' literal
that drives showTakeDown for a normal running stack. Add a case so a
regression dropping 'running' from that check is caught.

* fix: drop Take down from header overflow and wire activity shortcut

Remove duplicate Take down from More actions.

Keep inline button when running, sidebar menu, and Cmd+ArrowDown.

Record stack_taken_down in activity on successful POST /down.
2026-07-09 12:20:13 -04:00
Anso 296ddff2a0 fix(routing): split stack detail URL from compose editor URL (#1605)
Sidebar opens /stacks/:name (anatomy). Monaco uses /compose|/env|/files.
Refresh of a detail URL no longer opens the editor, and editor deep links
keep a hydration shell instead of flashing the dashboard.
2026-07-09 12:05:36 -04:00
Anso 7517a4f49c fix(routing): guard remote stack hydration and settle empty env routes (#1602)
Gate stack resolution until the active node matches a pending remote deep link.
Return env inventory from loadFileForRoute so empty stacks settle env tab routes.
Reject backslashes in buildPath env query tokens.
2026-07-08 23:09:05 -04:00
Anso 1de9f5e4b2 fix(routing): basename env URLs and defer writes during node hydration (#1598)
* fix(routing): basename env URLs and defer writes during node hydration

Encode only env file basenames in ?env= and omit the default file.

Resolve legacy absolute-path bookmarks on load.

Block history writes until the active node matches a cold-loaded remote deep link.

* fix(routing): restore env deep links after stack load hydrates file list

Defer env selection until envFiles is populated after loadFileForRoute.

Apply default env when the URL omits ?env= (Back/popstate).

Document env URL rules and legacy basename resolution in deep-links.mdx.
2026-07-08 21:02:13 -04:00
dependabot[bot] ad5e3f38c8 chore(deps): bump the all-npm-frontend group in /frontend with 20 updates (#1596)
* chore(deps): bump the all-npm-frontend group

Bumps the all-npm-frontend group in /frontend with 20 updates:

| Package | From | To |
| --- | --- | --- |
| [@radix-ui/react-alert-dialog](https://github.com/radix-ui/primitives/tree/HEAD/packages/react/alert-dialog) | `1.1.18` | `1.1.19` |
| [@radix-ui/react-checkbox](https://github.com/radix-ui/primitives/tree/HEAD/packages/react/checkbox) | `1.3.6` | `1.3.7` |
| [@radix-ui/react-context-menu](https://github.com/radix-ui/primitives/tree/HEAD/packages/react/context-menu) | `2.3.2` | `2.3.3` |
| [@radix-ui/react-dialog](https://github.com/radix-ui/primitives/tree/HEAD/packages/react/dialog) | `1.1.18` | `1.1.19` |
| [@radix-ui/react-dropdown-menu](https://github.com/radix-ui/primitives/tree/HEAD/packages/react/dropdown-menu) | `2.1.19` | `2.1.20` |
| [@radix-ui/react-hover-card](https://github.com/radix-ui/primitives/tree/HEAD/packages/react/hover-card) | `1.1.18` | `1.1.19` |
| [@radix-ui/react-popover](https://github.com/radix-ui/primitives/tree/HEAD/packages/react/popover) | `1.1.18` | `1.1.19` |
| [@radix-ui/react-scroll-area](https://github.com/radix-ui/primitives/tree/HEAD/packages/react/scroll-area) | `1.2.13` | `1.2.14` |
| [@radix-ui/react-select](https://github.com/radix-ui/primitives/tree/HEAD/packages/react/select) | `2.3.2` | `2.3.3` |
| [@radix-ui/react-slider](https://github.com/radix-ui/primitives/tree/HEAD/packages/react/slider) | `1.4.2` | `1.4.3` |
| [@radix-ui/react-tabs](https://github.com/radix-ui/primitives/tree/HEAD/packages/react/tabs) | `1.1.16` | `1.1.17` |
| [@radix-ui/react-tooltip](https://github.com/radix-ui/primitives/tree/HEAD/packages/react/tooltip) | `1.2.11` | `1.2.12` |
| [@xyflow/react](https://github.com/xyflow/xyflow/tree/HEAD/packages/react) | `12.11.1` | `12.11.2` |
| [radix-ui](https://github.com/radix-ui/primitives/tree/HEAD/packages/react/radix-ui) | `1.6.1` | `1.6.2` |
| [recharts](https://github.com/recharts/recharts) | `3.9.1` | `3.9.2` |
| [@types/node](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/node) | `26.1.0` | `26.1.1` |
| [typescript](https://github.com/microsoft/TypeScript) | `6.0.3` | `7.0.2` |
| [typescript-eslint](https://github.com/typescript-eslint/typescript-eslint/tree/HEAD/packages/typescript-eslint) | `8.62.1` | `8.63.0` |
| [vite](https://github.com/vitejs/vite/tree/HEAD/packages/vite) | `8.1.2` | `8.1.3` |
| [vitest](https://github.com/vitest-dev/vitest/tree/HEAD/packages/vitest) | `4.1.9` | `4.1.10` |


Updates `@radix-ui/react-alert-dialog` from 1.1.18 to 1.1.19
- [Changelog](https://github.com/radix-ui/primitives/blob/main/packages/react/alert-dialog/CHANGELOG.md)
- [Commits](https://github.com/radix-ui/primitives/commits/HEAD/packages/react/alert-dialog)

Updates `@radix-ui/react-checkbox` from 1.3.6 to 1.3.7
- [Changelog](https://github.com/radix-ui/primitives/blob/main/packages/react/checkbox/CHANGELOG.md)
- [Commits](https://github.com/radix-ui/primitives/commits/HEAD/packages/react/checkbox)

Updates `@radix-ui/react-context-menu` from 2.3.2 to 2.3.3
- [Changelog](https://github.com/radix-ui/primitives/blob/main/packages/react/context-menu/CHANGELOG.md)
- [Commits](https://github.com/radix-ui/primitives/commits/HEAD/packages/react/context-menu)

Updates `@radix-ui/react-dialog` from 1.1.18 to 1.1.19
- [Changelog](https://github.com/radix-ui/primitives/blob/main/packages/react/dialog/CHANGELOG.md)
- [Commits](https://github.com/radix-ui/primitives/commits/HEAD/packages/react/dialog)

Updates `@radix-ui/react-dropdown-menu` from 2.1.19 to 2.1.20
- [Changelog](https://github.com/radix-ui/primitives/blob/main/packages/react/dropdown-menu/CHANGELOG.md)
- [Commits](https://github.com/radix-ui/primitives/commits/HEAD/packages/react/dropdown-menu)

Updates `@radix-ui/react-hover-card` from 1.1.18 to 1.1.19
- [Changelog](https://github.com/radix-ui/primitives/blob/main/packages/react/hover-card/CHANGELOG.md)
- [Commits](https://github.com/radix-ui/primitives/commits/HEAD/packages/react/hover-card)

Updates `@radix-ui/react-popover` from 1.1.18 to 1.1.19
- [Changelog](https://github.com/radix-ui/primitives/blob/main/packages/react/popover/CHANGELOG.md)
- [Commits](https://github.com/radix-ui/primitives/commits/HEAD/packages/react/popover)

Updates `@radix-ui/react-scroll-area` from 1.2.13 to 1.2.14
- [Changelog](https://github.com/radix-ui/primitives/blob/main/packages/react/scroll-area/CHANGELOG.md)
- [Commits](https://github.com/radix-ui/primitives/commits/HEAD/packages/react/scroll-area)

Updates `@radix-ui/react-select` from 2.3.2 to 2.3.3
- [Changelog](https://github.com/radix-ui/primitives/blob/main/packages/react/select/CHANGELOG.md)
- [Commits](https://github.com/radix-ui/primitives/commits/HEAD/packages/react/select)

Updates `@radix-ui/react-slider` from 1.4.2 to 1.4.3
- [Changelog](https://github.com/radix-ui/primitives/blob/main/packages/react/slider/CHANGELOG.md)
- [Commits](https://github.com/radix-ui/primitives/commits/HEAD/packages/react/slider)

Updates `@radix-ui/react-tabs` from 1.1.16 to 1.1.17
- [Changelog](https://github.com/radix-ui/primitives/blob/main/packages/react/tabs/CHANGELOG.md)
- [Commits](https://github.com/radix-ui/primitives/commits/HEAD/packages/react/tabs)

Updates `@radix-ui/react-tooltip` from 1.2.11 to 1.2.12
- [Changelog](https://github.com/radix-ui/primitives/blob/main/packages/react/tooltip/CHANGELOG.md)
- [Commits](https://github.com/radix-ui/primitives/commits/HEAD/packages/react/tooltip)

Updates `@xyflow/react` from 12.11.1 to 12.11.2
- [Release notes](https://github.com/xyflow/xyflow/releases)
- [Changelog](https://github.com/xyflow/xyflow/blob/main/packages/react/CHANGELOG.md)
- [Commits](https://github.com/xyflow/xyflow/commits/@xyflow/react@12.11.2/packages/react)

Updates `radix-ui` from 1.6.1 to 1.6.2
- [Changelog](https://github.com/radix-ui/primitives/blob/main/packages/react/radix-ui/CHANGELOG.md)
- [Commits](https://github.com/radix-ui/primitives/commits/HEAD/packages/react/radix-ui)

Updates `recharts` from 3.9.1 to 3.9.2
- [Release notes](https://github.com/recharts/recharts/releases)
- [Changelog](https://github.com/recharts/recharts/blob/main/CHANGELOG.md)
- [Commits](https://github.com/recharts/recharts/compare/v3.9.1...v3.9.2)

Updates `@types/node` from 26.1.0 to 26.1.1
- [Release notes](https://github.com/DefinitelyTyped/DefinitelyTyped/releases)
- [Commits](https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/node)

Updates `typescript` from 6.0.3 to 7.0.2
- [Release notes](https://github.com/microsoft/TypeScript/releases)
- [Commits](https://github.com/microsoft/TypeScript/commits)

Updates `typescript-eslint` from 8.62.1 to 8.63.0
- [Release notes](https://github.com/typescript-eslint/typescript-eslint/releases)
- [Changelog](https://github.com/typescript-eslint/typescript-eslint/blob/main/packages/typescript-eslint/CHANGELOG.md)
- [Commits](https://github.com/typescript-eslint/typescript-eslint/commits/v8.63.0/packages/typescript-eslint)

Updates `vite` from 8.1.2 to 8.1.3
- [Release notes](https://github.com/vitejs/vite/releases)
- [Changelog](https://github.com/vitejs/vite/blob/main/packages/vite/CHANGELOG.md)
- [Commits](https://github.com/vitejs/vite/commits/v8.1.3/packages/vite)

Updates `vitest` from 4.1.9 to 4.1.10
- [Release notes](https://github.com/vitest-dev/vitest/releases)
- [Changelog](https://github.com/vitest-dev/vitest/blob/main/docs/releases.md)
- [Commits](https://github.com/vitest-dev/vitest/commits/v4.1.10/packages/vitest)

---
updated-dependencies:
- dependency-name: "@radix-ui/react-alert-dialog"
  dependency-version: 1.1.19
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: all-npm-frontend
- dependency-name: "@radix-ui/react-checkbox"
  dependency-version: 1.3.7
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: all-npm-frontend
- dependency-name: "@radix-ui/react-context-menu"
  dependency-version: 2.3.3
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: all-npm-frontend
- dependency-name: "@radix-ui/react-dialog"
  dependency-version: 1.1.19
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: all-npm-frontend
- dependency-name: "@radix-ui/react-dropdown-menu"
  dependency-version: 2.1.20
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: all-npm-frontend
- dependency-name: "@radix-ui/react-hover-card"
  dependency-version: 1.1.19
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: all-npm-frontend
- dependency-name: "@radix-ui/react-popover"
  dependency-version: 1.1.19
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: all-npm-frontend
- dependency-name: "@radix-ui/react-scroll-area"
  dependency-version: 1.2.14
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: all-npm-frontend
- dependency-name: "@radix-ui/react-select"
  dependency-version: 2.3.3
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: all-npm-frontend
- dependency-name: "@radix-ui/react-slider"
  dependency-version: 1.4.3
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: all-npm-frontend
- dependency-name: "@radix-ui/react-tabs"
  dependency-version: 1.1.17
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: all-npm-frontend
- dependency-name: "@radix-ui/react-tooltip"
  dependency-version: 1.2.12
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: all-npm-frontend
- dependency-name: "@xyflow/react"
  dependency-version: 12.11.2
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: all-npm-frontend
- dependency-name: radix-ui
  dependency-version: 1.6.2
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: all-npm-frontend
- dependency-name: recharts
  dependency-version: 3.9.2
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: all-npm-frontend
- dependency-name: "@types/node"
  dependency-version: 26.1.1
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: all-npm-frontend
- dependency-name: typescript
  dependency-version: 7.0.2
  dependency-type: direct:development
  update-type: version-update:semver-major
  dependency-group: all-npm-frontend
- dependency-name: typescript-eslint
  dependency-version: 8.63.0
  dependency-type: direct:development
  update-type: version-update:semver-minor
  dependency-group: all-npm-frontend
- dependency-name: vite
  dependency-version: 8.1.3
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: all-npm-frontend
- dependency-name: vitest
  dependency-version: 4.1.10
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: all-npm-frontend
...

Signed-off-by: dependabot[bot] <support@github.com>

* chore(deps): hold typescript on the 6.x line in frontend group

typescript-eslint 8.63.0 supports typescript <6.1.0, so bumping to 7.0.2
crashes eslint (typescript-estree cannot read the TS7 native compiler API).
Keep typescript at ^6.0.3 until typescript-eslint supports TS7; the rest of
the group bump is unaffected.

---------

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: SaelixCode <dev@saelix.com>
2026-07-08 14:57:03 -04:00
Anso 5fe0843eb2 feat: add routable browser URLs for stacks and shell views (#1586)
* feat: add routable browser URLs for stacks and shell views

Sync in-memory navigation to the address bar via a History API hook so
deep links, refresh, Back/Forward, and bookmarks work across nodes,
views, stack editor tabs, and mobile surfaces. Gate role/tier URL
normalization on permissions and license readiness, preserve URLs on
metadata fetch failure, and surface retryable stack-list errors without
rewriting pending stack paths.

* fix: preserve deep-link views on cold load and refresh

Stop the node-switch effect from resetting to dashboard on initial mount.

Defer URL writer settlement until hydrated activeView matches the route.

Adds E2E coverage for shell cold loads, stack refresh, and compose env tab.

* fix: keep mobile dashboard on list surface so sidebar renders

On mobile, the URL sync hook was routing /nodes/<slug>/dashboard to the
content surface, hiding the stack list sidebar. This prevented the
data-stacks-loaded sentinel from appearing, causing sidebar truncation
E2E tests to time out after reload on a mobile viewport.

Mobile dashboard now stays on the list surface; other non-editor views
still render on the content surface.

* fix: complete mobile URL routing follow-ups for stack deep links

Restore mobile /dashboard vs /stacks, list surface always writes /stacks.

Hydrate pendingDetailStack, freeze compose failures with routeDetailError,

and add unit plus E2E coverage.

* fix: hydrate shell views from URL and sync in-app navigation

Bootstrap activeView and tab state from the pathname on cold load.

Settle route phase when state already matches, normalize unknown segments,

and open Monaco editor tabs from stack deep links via applyEditorRouteState.

* fix: prevent mobile stack deep links from hanging on cold load

The resolvePendingStack effect did not re-fire when the pending stack ref
was populated during URL hydration, because the urlHydratingStack state
set in the same callback was not listed in the effect's dependency array.
Adding it causes the effect to retry once hydration has committed.

A resolvingRef mutex prevents concurrent invocations. When the target file
is already loaded, route state is applied directly without calling
loadFileForRoute, which avoids unmounting the editor (and hiding the
recovery chip) if a background refresh triggers route resolution during
a deploy operation.

* test: adapt stack, deploy, and sidebar e2e specs to routable stack URLs

* ci: raise E2E Playwright job timeout to 20 minutes
2026-07-08 13:07:16 -04:00
Anso 0c37d18586 fix: normalize dashboard network sparkline to bytes per second (#1589)
Historical net_rx_mb/net_tx_mb values are MB/s rates, not cumulative bytes.
Replace delta bucketing with per-bucket aggregate averaging converted to
bytes/s so the NETWORK sparkline matches the live headline units.
2026-07-07 20:32:11 -04:00
Anso e12602091a fix: repin semver compose tags during fleet self-update (#1587)
* fix: repin semver compose tags during fleet self-update

Fleet updates failed when docker-compose.yml pinned a semver tag because recreate reused the on-disk pin. Pull the target image first, rewrite semver pins via the update helper, and block digest or unresolved pins with fast 409s.

* fix: update OFFLINE_META shape in capability and node-registry meta tests
2026-07-07 14:40:50 -04:00
Anso dbe230eef3 fix(editor): remove misleading image line above stack actions (#1584)
Closes #1580. The Command Center header showed the first container's
image directly above stack-wide Start/Stop/Update controls, which implied
those buttons targeted one image. Remove the header image/digest row;
per-container ImageSourceMenu on each row remains.
2026-07-07 14:16:32 -04:00
Anso 383d2248a2 fix: keep confirm modal actions visible for long titles (#1578) 2026-07-06 17:13:12 -04:00
Anso 30f1ca0a5f fix: default to compact density only for multi-container stacks (#1576)
* fix: only default to compact density for multi-container stacks

* fix: update density reset test for single-container detailed default
2026-07-06 05:17:46 -04:00
Anso bbcc4b59e4 fix: UI polish - tooltips, masthead, tabs, settings, and layout consistency (#1563)
* fix: increase masthead rail shimmer visibility

Bumped shimmer highlight opacity from 25% to 50% and reduced
animation duration from 11s to 5s, matching the login page
AuthCanvas shimmer precedent (via-white/60 at 4.5s).

* fix: reduce masthead rail opacity to match login page pattern

Changed all MastheadRail background classes from 100% to 70% opacity
(bg-brand → bg-brand/70, etc.) so the shimmer highlight reads as a surface
reflection rather than a detached floating bar. Matches the AuthCanvas
login card precedent.

* fix: improve masthead rail glow visibility

Replaced the invisible inset box-shadow glow (10px blur inside a 3px
element) with a white overlay that pulses opacity 0.15-0.55 over 4s.
Sped up glow animation from 5.5s to 4s for a more noticeable breath.

* fix: replace cursor-follow tooltip with standard Radix tooltip in sidebar rows

Replaced the Cursor/CursorFollow animate-ui primitives in StackRow
with the standard Radix Tooltip/TooltipTrigger/TooltipContent already
used throughout the app. The custom cursor dot that followed the mouse
is gone; tooltips now appear as static popovers on hover/touch.

* fix: wrap tooltip icon triggers in span for Radix compatibility

TooltipTrigger asChild requires a native element to forward props.
Lucide icon components did not reliably receive data-state and event
handlers, so tooltips on the check-failed and git-pending indicators
did not open. Wrapped each icon in a span.

* fix: rename Notification Routing to Routing in settings sidebar

* fix: replace Timeline/All tasks buttons with SegmentedControl

The two loose Button elements had indistinguishable selected/unselected
states (secondary vs ghost). Replaced with the SegmentedControl component
already used elsewhere on the same page, which has a clear active
highlight indicator.

* fix: standardize Blueprint empty state headings to font-heading

Replaced manual font-serif [font-style:var(--heading-style)] with the
font-heading class on the main heading and three step titles so the
Deployments empty state follows the same heading convention as the
rest of the app and respects the Calm/Signature theme toggle.

* fix: remove custom tooltip styling, use standard TooltipContent defaults

The RowTooltip wrapped its label in a font-mono text-stat-value span
instead of using the TooltipContent base styling (text-xs
text-popover-foreground) like every other tooltip in the app.

* Revert "fix: remove custom tooltip styling, use standard TooltipContent defaults"

This reverts commit 209fbd2742.

* fix: standardize all tooltips to match sidebar RowTooltip design

Wrapped every TooltipContent child in a font-mono text-xs
tabular-nums text-stat-value span so all tooltips across the app
share a consistent look. Previously some used plain text, others
used <p> elements with different fonts and sizes.

* Revert "fix: standardize all tooltips to match sidebar RowTooltip design"

This reverts commit 9e1d97d39e.

* fix: bake standard tooltip styling into TooltipContent component

Added font-mono tabular-nums text-stat-value directly to the
TooltipContent base styles so every tooltip in the app inherits
the same look without per-instance wrappers. Removed the now-
redundant span from StackRow's RowTooltip.

* fix: replace custom tooltip with shadcn/ui Radix tooltip

Replaced the glass/blur-styled tooltip with the standard shadcn/ui
pattern: bg-foreground/text-background, arrow pointer, proper
slide/fade animations, and w-fit max-w-xs sizing. This gives all
tooltips a consistent dark-solid look across the entire app.

* fix: apply glass/blur design to shadcn tooltip component

Replaced the solid bg-foreground/text-background style with the app's
glass design: bg-popover, border-glass-border, backdrop-blur, and
text-popover-foreground. Kept the shadcn structure (Portal, Arrow,
slide/fade animations).

* fix: replace native title tooltips with Radix tooltips on Fleet toolbar

Converted Refresh, Export Dossier, Search, Sort direction, Node Update,
and Manage Nodes buttons from native HTML title attributes to the shared
Radix Tooltip component so they match the app-wide glass tooltip design.

* fix: replace native title tooltip on Fleet Snapshots upload button

* fix: replace native title tooltips with Radix tooltips on Resources page

Converted all actionable button tooltips (Open stack, Inspect image,
Scan, Browse volume, and delete buttons) plus the SenchoBadge
informational tooltip. Wrapped disabled protected-resource delete
buttons in span triggers so tooltips fire on disabled elements.

* fix: replace native title tooltips with Radix tooltips on Security page

Converted scanner info icon, exploitability n/a badge, and scan
image button from native HTML title attributes to the shared Radix
Tooltip component.

* fix: replace native title tooltips with Radix tooltips on Schedules and Settings pages

Converted Run now, Execution history, Edit, Delete buttons on Schedules;
Download/Delete on Cloud Backup; Send test/Edit/Delete on Notification
Routing; Reset 2FA on Users; and Edit/Remove suppression on
Suppressions panel. Skipped SettingsSection/Modal title props which
are React component props, not native HTML attributes.

* feat: add copy button to execution history details column

Replaced the plain truncated Details cell with a flex layout containing
a copy button. Users can now copy the full error/output text instead
of relying on the native title tooltip to read long messages.

* fix: change tab highlight from neutral to brand color

Replaced bg-glass-highlight with bg-brand/20 on all TabsHighlight
instances and updated TabsTrigger active state to text-brand,
matching the SegmentedControl selected style. Works on both
dark and light themes.

* fix: restructure Resources page tabs to match Fleet/Security pattern

Moved tab band out of the outer card into a standalone full-width
element. Replaced custom FilterToggle with Fleet-style Button
toggles. Added search inputs per tab. Wrapped each resource table
in a card container matching the Security Images table design.

* fix: match Resources tab font size to Fleet and Security

Removed explicit text-xs from Resources TabsTrigger elements so they
use the default text-sm, matching Fleet and Security tab typography.

* fix: restore missing closing bracket on TabsTrigger tag

* fix: add aria-labels to schedule action buttons, update test selectors

Replaced native title attributes with aria-labels on Run now,
Execution history, Edit, and Delete buttons so the Radix tooltip
conversion does not break test selectors that relied on title.
Updated tests from findByTitle to findByRole.

* fix: add fingerprint-based dismiss to post-deploy scan banner

Created useScanBannerDismiss hook following the usePreflightDismiss
pattern. The banner now persists dismissal in localStorage keyed to a
fingerprint of scan status + attemptedAt. Dismissal survives page
reloads for the same scan outcome, and the banner automatically
reappears when a new scan runs or the status changes.

* fix: update Configuration Status card labels and add new rows

Notifications section: renamed Notification agents to Channels,
Notification routing to Routing, added Mute Rules row linking to
notification-suppression settings.

Security section: added Trivy installed Yes/No row, renamed
Vulnerability scanning to Scan policies.

Backend: added trivyInstalled and suppressionRules fields to
the /dashboard/configuration payload.

* fix: style Trivy row as badge and link to Security scanner setup

* fix: remove Mute button from stack anatomy panel header

* Revert "fix: remove Mute button from stack anatomy panel header"

This reverts commit 16843a81b5.

* fix: remove Mute button from stack anatomy panel header

* fix: remove unused stackMuteActions imports and props from StackAnatomyPanel

The removal of the ActivityMuteKebab rendering left orphaned imports
and props that cause TS6133 errors under strict mode. Clean up the
imports, prop type, destructuring, and caller prop passes.

* fix: refine anatomy tab sizing and add pulsing scroll chevron

Changed anatomy tabs from 12px fixed height to 11px with py-1
padding, matching the Fleet tab approach. Bumped Files/Edit buttons
from 10px to xs for better visibility. Added subtle animate-pulse
to the right scroll chevron to hint at overflow tabs, gated behind
the Reduced effects appearance setting.

* fix: link reclaim hero number to heading style

* fix: replace native title tooltips with Radix tooltips on stack detail page

Converted Expand/Collapse logs, Stats unavailable, Copy service URL,
Dismiss findings, Likely secret badge, Move up/down, and Remove
buttons from native HTML title attributes to the shared Radix
glass tooltip component.

* fix: replace native title tooltip on image source link button

* fix: add tooltips to container logs and bash session buttons

* fix: add tooltip to download logs button

* fix: add tooltip to copy digest button

* fix: replace cursor-follow tooltip with Radix tooltip on severity badge

* fix: add tooltips to density toggle buttons

* feat: add expand button to multi-container health panel

Added an expand/collapse toggle next to the density controls that
expands the containers panel to fill the column and hides the logs
section. The expand and logs-expand are mutually exclusive. Only
appears for stacks with more than one container.

* fix: reduce Structured/Raw terminal toggle font to 10px

* fix: set compact density as default for multi-container stacks

* fix: change Feedback to Open New Issue with GitHub issues link

* fix: replace ExternalLink with GitHub icon for Open New Issue menu item

* fix: restore ExternalLink import needed by other menu items

* fix: default host threshold alerts to off for new installations

* fix: resolve CI typecheck and test failures

Broaden MenuRowProps.icon type to accept custom SVG components alongside
lucide icons, fixing TS2741 on GitHubIcon import.

Update host_alerts_enabled seed test assertion to match the new opt-in
default of off (0).

* fix: update ContainersHealth tests for compact default density

Adjust three tests to reflect the new default of compact mode:
- Rename 'detailed mode is the default' to 'compact mode is the default'
- Update sparkline visibility checks to switch to detailed first
- Rename remount test and verify reset to compact (new default)
2026-07-06 04:25:01 -04:00
Anso 0f9925e04f feat: block self-stack lifecycle ops with UI and preflight guardrails (#1569)
* feat: block self-stack lifecycle ops with UI and preflight guardrails

Refuse update, deploy, down, stop, and delete when the stack matches Sencho's compose project.

Return 409 self_stack_protected. Expose isSelf on /statuses and disable guarded UI actions.

Add SelfStackProtectedDialog and self-managed-stack preflight warning.

Closes #1564

* fix: add missing stackSelfFlags mock to useSidebarContextMenu test

The production hook now reads stackListState.stackSelfFlags[file], but the
test mock did not include it, causing 6 tests to fail with TypeError:
Cannot read properties of undefined (reading 'web.yml').

* fix: harden self-stack protection during startup

Add a global environment preflight warning when Sencho is managed inside COMPOSE_DIR.

Align status decoration and route guards on Docker label fallback detection.

Block rollback and service-level stop on the protected self stack.

* fix: add self_stack_location to diagnostics-route expected check IDs
2026-07-06 02:08:16 -04:00
Anso f30a65ee08 fix(anatomy): name each image with a pending update in the update banner (#1575)
Show each updated image and tag transition in the Anatomy banner.

Uses the per-image data the update-preview API already returns.
2026-07-06 02:08:02 -04:00
Anso b3bc223c45 fix(deploy): selective compose recreate on Save and Deploy (#1565) (#1568)
* feat: move core Blueprint orchestration to Community tier

Blueprints CRUD, reconciliation, and drift modes are now available on
Community. Pin remains Admiral-only via Federation placement controls.

* test: update NodeCard cordon tests for Admiral-only tier gate

Cordon now requires both isPaid and node:manage permission, matching the
backend requirePaid + requirePermission guard. Three tests still used
isPaid:false but expected the menu to be visible.
2026-07-05 21:55:32 -04:00
Anso adcc27e2cf fix: update NodeCard cordon tests for paid-tier gate parity (#1571) 2026-07-05 06:05:05 -04:00
Anso a7e856f447 feat: enforce singleton local node per instance (#1567)
Only one local node is allowed. Creating a second local returns 409,
and the last local node cannot be deleted or converted to a remote type.
Existing duplicate local nodes from older versions are preserved and can
be cleaned up individually. Zero-local recovery auto-assigns the default
flag. Frontend delete surfaces and the Add Node form respect the new
invariant.

Enforced in DatabaseService (addNode/updateNode/deleteNode guards) and
routes (error translations). Legacy test fixtures use raw SQL helpers.
2026-07-05 05:30:56 -04:00