Per-container metadata showed healthcheck passing/failing instead of Docker's healthy/unhealthy/starting, which disagreed with the stack pill. Render the normalized healthStatus token directly and document the same terms.
Closes#1677
Service chips only differentiate multi-service or multi-container log streams. Gate rendering with the same layout criterion already used in stack details, while keeping parsed prefixes and download attribution intact.
* feat: add service-scoped stack alert rules
Stack alerts can target one Compose service or all services. Breach timers
are per container and cooldowns are per service so a healthy sibling no
longer clears another container's timer or silences a different service.
* fix: gate remote scoped alert creates without losing the body
Remote hops skip JSON parsing so the proxy stream stays pipeable, which
left service_name invisible to the capability gate. Buffer POST /alerts
bodies for inspection, fail closed when the remote lacks the capability,
and rewrite the buffered bytes on forward. Restore alert-panel alt text
to match the unchanged screenshot.
* fix: bound remote alert body buffer and reject encoded JSON
Cap proxied POST /alerts buffering at the local 100KB JSON limit with
structured 413 cleanup, reject non-identity Content-Encoding with 415 so
compressed scoped bodies cannot bypass the mixed-version gate, and cover
oversized, chunked, and gzip regressions.
* fix: harden service-scoped alert delete, cooldown, and proxy gates
Reject non-digit alert ids, dual-write last_fired_at for rollback safety,
gate cooldown on persisted notification history, fail-fast oversized proxy
bodies with 413, and clarify Not in compose UI semantics.
* test: expect dispatchAlert persisted result in crash-safety cases
Update notification-routing assertions for the new { persisted } return
shape so CI matches the cooldown-gating contract.
* feat: graduate Host Console to Community admins
Make Host Console available to Community and Admiral admins (system:console), add host-console-community for mixed fleets, and keep opaque API tokens off the host shell.
* docs: document Host Console deep links
Cover root and stack-scoped Console URLs, correct the phone treatment note, and pin parse/build round-trips in senchoRoute tests.
* fix: bind Host Console socket to the resolved node
Treat unresolved activeNode as loading, target the WebSocket with an explicit nodeId, and wait for stack deep-link hydration so the shell cannot open on the wrong node or compose root. Add regression coverage for node/stack retargeting and fail-closed directory resolution.
* fix: harden Host Console node binding, audit acting_as, and console_session tokens
Reject unknown or malformed nodeIds before spawning a PTY. Record hub operators in audit_log.acting_as for remote console_session bridges. Path-scope and one-time-consume console_session JWTs so Host Console mints cannot open container exec or be replayed.
* test: expect acting_as in audit CSV export header
Align the CSV export assertion with the P0-2B acting_as column added to audit log exports.
* fix(fleet): refresh prune reclaimable estimate after successful run
The live estimate only re-ran when targets or scope changed, so the
toolbar and per-node reclaimable figures stayed stale after a real prune.
Invalidate via estimateEpoch when any non-dry-run target succeeds.
* fix(fleet): toast partial prune success honestly
A reachable node with mixed per-target outcomes made okNodes zero and
showed a total-failure toast even when some targets mutated Docker.
Gate the failure toast on zero successful targets anywhere.
* fix: purge deleted-stack notifications from panel and ticker
Stack delete already cascaded scans and drift but left notification_history rows, so the bell and Activity ticker kept showing the deleted stack. Purge those rows in the shared deletion lifecycle, invalidate connected clients, and drop node-scoped in-memory rows immediately.
* fix: target remote notification purge by hub node id
Remote stack-deleted invalidations always reconcile with hub rn.id, and
notification refetch preserves failed node slices instead of wiping them.
* fix: prevent false empty states during stack hydration
Only show confirmed-empty UI after successful stack, status, and container
fetches. Distinguish loading and recoverable error states in the sidebar,
dashboard, and container health panel.
* fix: arbitrate overlapping stack status and container fetches
Prevent older dashboard status and same-owner container responses from overwriting newer load state after concurrent poll, invalidation, retry, or lifecycle refresh.
* fix: do not let soft status polls starve slow foreground loads
Skip soft /stacks/statuses poll and invalidation while a statuses request is already in flight so a deferred foreground hydration can still commit after the ten-second cadence.
* fix(stacks): surface recoverable errors for confirmed-empty soft failures
Sidebar and dashboard soft (background) refresh failures after a
confirmed-empty state silently kept showing the empty/adopt prompt
instead of a recoverable error, since only the error message was set
without flipping the load status. Also reject malformed non-array
/stacks responses instead of coercing them into a confirmed-empty list,
and drop malformed per-stack status entries before they reach the
dashboard table, which previously crashed the entire app on a null
entry.
* fix(stacks): close two review-found gaps in the load-failure fix
A non-empty stack-statuses map where every entry failed validation was
still committed as a confirmed-empty success; it now surfaces as a
recoverable error instead, and dropped entries are logged. The sidebar's
background-failure helper also checked a stale closure snapshot of the
file list, which could wipe a list that had just loaded non-empty in the
same attempt if the follow-up statuses fetch then failed; it now tracks
the freshest committed list for that decision. Also collapses two refs
tracking dashboard status-map emptiness into one.
* 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.
* 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.
* fix: require explicit repair before clearing a corrupt mute schedule
The suppression engine already fails closed on an unreadable stored
schedule (scheduleInvalid), but the frontend never surfaced that flag:
a corrupt rule looked identical to an ordinary unscheduled one, and
opening Edit then clicking Update sent an explicit schedule: null,
silently turning the corruption into a valid all-day mute. Add the
flag to the rule type, show an Invalid schedule badge on the card, and
block saving in the edit form until the operator explicitly touches
the weekly window (configures a new one, or toggles it to confirm the
clear).
* fix: correct contradictory toggle-sequence copy in schedule-repair toast
The blocking toast told operators to toggle the weekly window "off then
on" to confirm clearing a corrupt schedule, but the toggle starts off
for a corrupt rule, so that sequence leaves it on and trips the
no-selected-day validation instead. The correct, tested sequence is on
then off, matching the inline hint below the toggle. Also add a
regression test confirming the invalid-schedule save gate resets
cleanly across edit sessions on different rules.
* fix: enforce replica node_id and guard fleet sync against stale writes
Two hardenings to the suppression-rule fleet sync path found during
review: the /replica endpoint trusted the payload's node_id instead of
forcing it to null server-side, so a direct proxy-authenticated caller
could persist a scoped replica; and upsertNotificationSuppressionRuleReplica
overwrote unconditionally with no ordering check, so a delayed older
POST arriving after a newer one could downgrade the stored rule. Force
node_id to null on every replica write, and skip (with a warning log)
any incoming write whose updated_at is not newer than what's stored.
* test: assert the exact-tie updated_at case in the fleet sync stale-write guard
The staleness guard added in c31458a1 uses >= (ties are ignored, not
just strictly older writes); add the missing assertion for that
boundary and make the comment explicit about it.
* fix: bump vulnerable transitive backend dependencies
npm audit flagged body-parser, fast-uri, and protobufjs (one high
severity: fast-uri host confusion via failed IDN canonicalization).
All three have patch/minor fixes within existing semver ranges;
npm audit fix resolves all three with no package.json changes.
* fix: sanitize suppression replica fields before logging
Log entries built from fleet-sync replica payloads embedded rule id
and timestamp values directly, allowing a compromised peer to forge
log lines via control characters.
* fix: prevent delayed replica writes from resurrecting deleted mute rules
A network-reordered replica POST arriving after a DELETE fell into the
insert-when-absent branch with no protection, since the staleness guard
only compares against a row that still exists. Add a permanent
per-id tombstone (safe because rule ids are AUTOINCREMENT and never
reused): every delete records one, and the replica upsert refuses to
recreate a tombstoned id regardless of the incoming updated_at.
* fix(blueprints): gate confirmed apply on live intent fingerprint
reconcileConfirmedPlan only checked approval_status, so a concurrent compose
edit during Apply could deploy unapproved content under a stale fingerprint.
Match the tick-path fingerprint gate, refuse Apply when live intent drifts,
and surface reconciler refusal as PREVIEW_STALE instead of a false success.
* test(blueprints): cover matching-fingerprint reconcileConfirmedPlan path
Prove the production allow branch still deploys authorized actions when the
approval fingerprint matches, and does not execute unauthorized blast nodes.
* fix(blueprints): report live approval after confirmed snapshot apply
A concurrent edit can clear approval while multi-node snapshot deploy is
still running. Keep the in-flight snapshot contract, but re-read live
effectiveApproval before responding and warn in the rollout dialog when
approval is no longer current.
* fix: leave editor after deleting the open stack
Clear selection on delete-key identity match, navigate to dashboard only when the editor is visible, and clear mobile detail so the URL writer leaves the deleted stack route instead of a permanent skeleton.
* fix: remove duplicate setIsFileLoading in stack actions test fixture
* fix: keep running containers until stack pull/build succeeds
Acquire images before reconcile, capture a recovery generation for
compensation, and only remove classified orphans after handoff.
* fix: address recovery audit blockers for safe stack updates
Retire abandoned and expired recovery artifacts, probe compensated
runtimes before reporting rollback success, preserve local Docker when
deleting a node, validate the exact Compose invocation before capture,
and repair updateStack return-contract fixtures.
* fix: resolve ESLint errors blocking CI on this branch
Unused-import and unused-variable errors left over from the stack
deletion refactor: MeshService in stacks.ts (its opt-out cascade moved
into DeployedStackDeletionService), a redundant pruneVolumes
destructure in deleteDeployedStack (the real one is re-derived from
the same input object inside runDeletionBody), and an unused beforeAll
import in a Docker-integration test stub. Also scopes the webhook
pull-action case body in a block to satisfy no-case-declarations;
purely syntactic, no behavior change.
* fix: harden recovery probe, cleanup retry, and failed-pull Docker test
Reject absent or unhealthy expected replicas before reporting rollback
success, keep cleanup records until artifacts are actually removed, fail
closed when a mesh override cannot be generated, and assert a real
failed pull leaves the original container running.
* fix: verify recovery probe image identity and stack-scoped override paths
Reject recovered runtimes that use the wrong image or leave scale-zero
services running, and confine tombstone override deletion to the intent
stack directory so forged cross-stack paths cannot be swept.
* test: batch notification cap fixtures in a SQLite transaction
Unbatched 1200-row inserts were timing out at the default 30s under
CI load even though the same assertions pass in under 2s when green.
* 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.
* 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.
* 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.
* 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.
* 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.
* 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.
* 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.
* 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.
* 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.
* 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.
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.
* 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.
* 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.
* 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
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.
* 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
* 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.
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.
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.
* 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.
* 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.
* 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.
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(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.
* 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.
* 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.
* 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.
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.
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.
* 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.
* 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
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.
* 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
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.