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.
* 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.
Non-admin users no longer see Update buttons in Fleet View; the UI shows
read-only "Available" badges instead. The accordion describing a 403-click
path was orphaned by the frontend role-gating that shipped in an earlier PR.
The Note above the troubleshooting section already states the admin-role
requirement in plain language.
* 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.
Update docs.json logo paths from PNG to SVG, matching the frontend
convention (sencho-logo-dark.svg / sencho-logo-light.svg). Remove old
PNG assets from docs/images/logo/.
* 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
* 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.
* 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
Drift compared raw compose YAML to runtime, so ${VAR:-default} image
expressions false-positive as image-mismatch. Source the declared side
from docker compose config instead, matching deploy-time resolution.
Fixes#1572
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.
Detect services with build: in the update preview and run compose build --pull
plus pull --ignore-buildable when Update is triggered on those stacks, while
keeping the existing pull-only path for image-only stacks.
* feat: acknowledge Compose Doctor preflight findings per stack
Add node-scoped preflight acknowledgements with read-time filtering.
Supports four expiry modes and activeStatus for banner, tab dot, and readiness.
* fix: align preflight acknowledge UI with design system
Use Combobox, modal chrome, mono fields, and non-destructive clear confirm.
* fix: update test mocks to match new preflight field names
The preflight-acknowledgements feature renamed status-\>activeStatus and
highestSeverity-\>activeHighestSeverity in the preflight report shape. The
corresponding test mocks in three files still used the old field names,
causing 6 test failures across backend and frontend.
- backend: update-guard-service mock now passes activeStatus
- frontend PreflightPanel: Report interface and report() helper now include
activeStatus, activeHighestSeverity, activeCount, acknowledgedCount
- frontend StackAnatomyPanel doctor: mock API response now includes
activeHighestSeverity and activeStatus
* feat: bound container health pane so logs stay visible on large stacks
On the stack overview page, multi-container stacks (8+ services) pushed the
logs pane off-screen because the Command Center card had shrink-0 and no
scroll mechanism. This reworks the desktop layout for stacks with more than
one container:
- The Command Center card is bounded at max-h-[42%] so it never consumes
the full left column. The container list scrolls internally via Sencho's
existing Radix ScrollArea.
- A summary strip shows total / running / paused / unhealthy counts above
the list, with a Compact / Detailed density toggle. Detailed (default)
shows CPU / Mem / Net sparklines; Compact hides them for a denser list.
- The logs pane has a guaranteed min-h-[180px] so it is always reachable.
- Single-container stacks are untouched: the card keeps its original
shrink-0 behaviour, and no summary strip or density toggle appears.
Mobile is unaffected (it already uses segmented Health / Logs / Compose
panes).
* fix: key ContainersHealth by node+stack so density resets on navigation
The density toggle state (compact/detailed) is local to ContainersHealth.
Without a key, selecting Compact on one multi-container stack would persist
when navigating to a single-container stack, hiding sparklines while also
hiding the toggle itself. Keying by node+stack ensures a clean remount.
Also added test coverage for zero containers and density reset on remount.
* fix: remove unused rerender variable from ContainersHealth test
* feat: add sidebar update indicator toggle and Stack Health badge
- Add image_update_sidebar_indicators setting (default off, node-scoped)
- Gate the Updates filter chip and sidebar status indicators on the setting
- Add "Update available" badge to Stack Health table (always visible)
- Extend ImageUpdateStatus with sidebarIndicators boolean
- Poll /api/image-updates/status alongside /detail in useImageUpdates
- React to SENCHO_SETTINGS_CHANGED for instant toggle propagation
- Reset sidebar state on node switch; generation-guard stale responses
- Disable toggle when status is null (loading) or field is absent (old node)
- Wire stackUpdates through ViewRouter → HomeDashboard → StackHealthTable
- Update settings registry, operator docs, and sidebar/dashboard docs
* fix: guard against stale node renders, memo drift, and cross-node error toasts
- Track owning node ID in useImageUpdates state so React never renders
node B with node A's data before the passive effect resets (P2)
- Replace incorrect stackUpdates dependency with sidebarStackUpdates in
chipFilteredFiles useMemo (P3)
- Guard the error toast in handleSidebarIndicatorsChange so a stale PATCH
failure from node A does not surface while viewing node B (P3)
* fix: default sidebar update indicators to on (opt-out)
The sidebar indicators are a safe convenience that most users want.
Switching the default from off to on matches the opt-out convention
used by prune_on_update, reclaim_hero, and health_gate_enabled.
* fix: fall back to enroll token when pilot tunnel JWT is rejected
On HTTP 401/404 upgrade rejection, delete stale pilot.jwt and retry with SENCHO_ENROLL_TOKEN.
* test: import pilot agent module after DATA_DIR is set in fallback test
The auth-fallback test statically imported pilot/agent, which freezes its
pilot.jwt path from DATA_DIR at module load, before setupTestDb redirects
DATA_DIR to a writable temp dir. On the Linux CI runner the path resolved
to a non-writable /app/data, so persistToken silently failed and the
round-trip assertion read null. Import the module dynamically in beforeAll
after setupTestDb, matching the sibling unit test.
* fix: remove unexpected-response listener that blocked pilot reconnect
The ws library skips abortHandshake when an unexpected-response listener
exists, so error and close never fire and the agent hangs in CONNECTING.
Detect auth rejection via the abortHandshake error message instead; the
close handler already performs enroll-token fallback and reconnect.
* 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.
* feat: move Federation placement controls to Community tier
Remove requirePaid from cordon, uncordon, and blueprint pin routes. Ungate the Federation tab and gate cordon UI on node:manage only. Update licensing and fleet docs for the new tier split.
* feat(scheduler): schedule container restart, stop, and start
Add container as a scheduled-task target type so operators can automate lifecycle actions against standalone containers by node and name, with matching UI pickers, validation, execution on local and remote nodes, and tests.
* fix(scheduler): stack service matching and container picker hygiene
Backfill Service on smartFallback containers so per-service stack restarts work when container_name is set. Match services by compose label and container name in stack routes and scheduled restarts. Exclude Sencho from GET /api/containers lists. Hide the Restart Stack service picker when a stack has only one service.
* test(scheduler): scope service checkbox assertion to Services block
The create dialog also has a Delete after run checkbox. Count checkboxes only inside the Services section so CI does not include unrelated form controls.
* fix(scheduler): narrow closest() result to HTMLElement in schedule test
The service-checkbox assertion passed an Element from closest() into
within(), which requires an HTMLElement, failing tsc -b in the frontend
build and Docker build stages. Use the closest<HTMLElement>() type
argument so the value type-checks without an unsafe cast.
* fix(scheduler): hide Sencho container on remote node picker lists
Remote container lists are proxied from peer Sencho instances, so id-only self filtering missed peers on older builds. Await SelfIdentity init, match ImageID, and drop official saelix/sencho images. Apply the same heuristic in the scheduled-operations UI and when the hub fetches remote containers for scheduled runs.
* test(monitor): add missing DatabaseService mocks for scan history cleanup
* test(scheduler): add missing markStaleScansAsFailed mock
SchedulerService.tick() calls db.markStaleScansAsFailed() to sweep stale
vulnerability scans. The scheduler-service test was missing this method in
its DatabaseService mock, causing TypeError failures during test initialization.
Added mockMarkStaleScansAsFailed to hoisted mocks and DatabaseService mock
object, returning safe default of 0 scans marked as failed.
* test(compose): add missing FileSystemService mocks for getStackContent/getEnvContent
* test(containers-route): mock SelfIdentityService to prevent initialize() crash
The excludeSelfContainers() helper calls SelfIdentityService.initialize(), which tries to access DockerController. Without a proper SelfIdentityService mock, the initialize() call fails silently, causing a 500 error on GET /api/containers.
Added SelfIdentityService mock with initialize(), isOwnContainer(), and isOwnImage() methods to prevent the crash.
* feat: add notification suppression rules
* fix: restore label routing and routing test mocks for suppression
* fix: allow bell mute shortcuts for history-only notification categories
Suppression rule validation used the routable category whitelist, which rejected history-only categories such as update_started that appear in the bell during stack updates.
* feat: expand Mute Rules UX with compose-first entry points and activity badges
* fix: add missing NodeContext mocks for notification suppression tests
GitHub Releases appear before docker-publish.yml finishes pushing images.
Probe Docker Hub and GHCR manifests before advertising a version as available.
Sanitize registry probe debug logs for CodeQL log-injection.
Rename "Janitor threshold" to "Reclaimable Docker data threshold" in Settings
and update the unused-Docker-data alert to point users to the Resources view
and the Prune Node Resources scheduled action, which are the real UI surfaces
for reclaiming disk space. Remove "janitor" from settings search keywords.
The internal docker_janitor_gb database key and JANITOR_* constants are
unchanged.
* fix: enforce 1:1 compose path mapping for Pilot agent mounts
Pilot enrollment now generates validated 1:1 bind mounts so every
agent path maps to a unique compose directory. Persisted agent paths
reconcile during startup to catch drift. Unsafe relative-bind redeploys
are blocked before container removal to prevent path escapes.
- Add composePathMapping utility with strict path validation
- Generate COMPOSE_DIR and validated mounts during Pilot enrollment
- Reconcile persisted agent paths during startup bootstrap
- Block redeploy when a relative-bind mount would escape the compose root
- Default Pilot UI path to /opt/docker/sencho
- Update multi-node and pilot-agent documentation
- Add regression tests for enrollment, bootstrap, compose-service,
and environment-check paths
* fix: update E2E enrollment regexes for YAML-quoted token values
* docs(introduction): refresh for the redesigned UI and replace screenshots
Bring the Getting Started Introduction page in line with the current
product:
- Add the Security top-level view to the navigation list and a
dedicated Security section with a new screenshot.
- Correct the Fleet tab names (Snapshots, Status, Map, Deployments,
Routing, Federation, Actions, Secrets).
- Split Settings out from security and list the current nine setting
groups (Security graduated to its own view).
- Refine the navigation paragraph so role, tier, and local-vs-remote
context read accurately.
Replace all four existing screenshots (Home, stack workspace, Fleet,
Resources) with fresh captures of the redesigned UI and add a Security
overview screenshot.
* docs(configuration): document advanced env vars and clarify deployment vs runtime config
Add an Advanced environment variables section (TRIVY_BIN, SENCHO_MESH_SUBNET,
GITSOURCE_MAX_CLONE_BYTES, SENCHO_PUBLIC_URL, SENCHO_COMPOSE_STALL_TIMEOUT_MS) and
reframe the intro to separate deployment-time configuration from the runtime
settings that live in the in-app Settings Hub. Cross-link the pilot-agent
variables to the Pilot Agent page instead of duplicating them.
* docs(sso): refresh SSO Setup Guide and SSO & LDAP reference for the redesigned UI
Refresh both SSO documentation pages against the current product and the
redesigned settings UI.
- Correct the navigation path to Settings -> Access -> SSO on both pages.
- Fix the "Require 2FA on SSO sign-in" toggle location to
Settings -> Personal -> Account.
- Describe the login-page experience (the Local / LDAP toggle and the
branded OIDC buttons under the "Or continue with" divider) and the SSO
panel masthead (SCOPE, PROVIDERS, ENABLED).
- Replace all six SSO screenshots with fresh captures of the redesigned UI.
* docs(features): refresh the Features Overview page for the redesigned UI
Rewrite docs/features/overview.mdx to mirror the current Features
navigation grouping (Stacks, Deployment, Resources, Observability,
Fleet, Automation, Security & Identity) and add the recently shipped
capabilities surfaced in the redesign: Stack Dossier, Drift Detection,
Compose Doctor, Compose Networking, Environment & secrets guardrails,
Storage portability, Health-Gated Updates, Fleet Dossier, and the
dedicated Security page.
Correct stale claims (the file explorer now gates writes on stack edit
permission, not an admin role; downloads are a read action; bulk label
assign now spans nodes) and standardize the tier callouts so partly
paid features read as "Admiral adds X". Replace the three pre-redesign
screenshots and add a Security overview banner, all captured from a
populated fleet.
* docs(features): refresh the Appearance page for the redesigned UI
Add fresh screenshots and a troubleshooting section to the Appearance
page, verified against the live product.
- Add four screenshots: the Theme card (live preview, mode, accent, and
fine-tune sliders), the top-bar quick switcher, the Typography card,
and the Display card.
- Refresh the Density screenshot used by the Settings reference page.
- State that the quick switcher also covers text size, and that the
contrast, border, and glow sliders stay in Settings.
- Add a Troubleshooting accordion covering per-browser persistence,
resets to defaults, cross-operator scope, and the quick-switcher
versus full-Settings split.
* docs(introduction): refresh screenshots and correct stale content
* docs(reference): refresh the Settings Reference page for the redesigned UI
Replace all seven stale screenshots with fresh 1920x1080 captures.
Add five new screenshots for the sections that previously had none.
Content changes:
- Sidebar table: rename Infrastructure "Fleet Mesh" entry to "Fleet";
add "Image update checks" to the Automation group list
- Fleet section: rename heading to match registry label; add the
Documentation snapshots subsection (snapshot_documentation toggle)
- Container Alerts: add screenshot
- Image update checks: add the full section (Registry checks table,
scheduling mode, interval presets, cron expression support)
- Stacks / Deploy Guardrails: add screenshot
- Recovery: add the full section (System health snapshot, Environment
preflight checks, Safe actions, Command-line recovery table)
* docs(sso): refresh screenshots for SSO quickstart and feature pages
* docs: refresh Features Overview screenshots and content
Replace all 4 hero screenshots with fresh 1920x1080 production captures.
Correct security posture state names (Action needed / Monitoring / Secure),
add the Policies tab to the Security section tab list, mention the Simple
mode in Scheduled operations, and update all alt text to match the new
screenshots.
* docs: refresh Appearance page screenshots and correct quick-switcher scope
Replace all four Appearance screenshots with fresh production captures.
Fix the quick-switcher control list: remove fonts (not present in the
popover), add visual style and readability which are. Add Log chip color
to the Display section. Update all screenshot alt text to match new
captures.
* docs: refresh stack management page with current UI and anatomy tabs
* docs: fix convert-tab-error screenshot with fully visible error toast
* docs: convert troubleshooting section to AccordionGroup format
* docs(quickstart): refresh screenshots and align dashboard description
Replace all three first-boot and dashboard screenshots with current UI.
Add Security to the top navigation list, update gauge and Stack health
descriptions to reflect sparklines and column detail, and align
Configuration Status wording with the Introduction page.
* docs(editor): rewrite anatomy panel, replace all screenshots
- Correct the anatomy panel tab inventory: the panel has eight tabs
(Anatomy, Activity, Dossier, Drift always; Environment, Networking,
Doctor, Storage when the node advertises the matching capability), not
three as previously documented
- Add table describing all eight tabs with capability gates and links to
dedicated feature pages
- Add anatomy-tabs.png screenshot showing the scrollable tab row
- Note the Doctor severity dot (red for blocker, amber for high-risk)
- Remove the stale Markdown-export subsection; Dossier and Activity are
now covered in the tab table
- Replace all six stale screenshots with fresh 1920x1080 captures
- Replace the compose diff preview screenshot
* docs(files): refresh Files & Volumes screenshots and fix context-menu alt text
Replace all 9 stale screenshots on the Files & Volumes page with fresh
captures from the production node. Fix three alt-text strings that did
not match the live UI: removed hardcoded octal value 644, and added the
Duplicate, Copy to, and Move to entries missing from the context-menu
alt text.
* docs: rewrite Stack Activity page with full event categories and fresh screenshots
Expands the event category table from 5 to 10 entries to cover drift
detected, drift resolved, update started, health gate passed, and health
gate failed. Adds a live-disconnected-state section, a background-actor
attribution table, and a corrected troubleshooting accordion covering the
WebSocket reconnect case. Replaces both stale screenshots with fresh
1920x1080 captures from the production node.
* docs(drift): rewrite drift detection page with screenshots and full coverage
Full rewrite of the Drift Detection feature page. Adds two previously
undocumented finding types (network-undeclared, network-missing), expands
the temporal section to distinguish the raw-file hash from the parsed-model
hash, documents the two-layer spatial-engine and ledger architecture,
explains when the ledger is reconciled (post-deploy vs manual re-check vs
tab open), adds Activity timeline integration note, introduces a Limitations
section (no background scanner, port-range caveat, history cap, advisory-only
enforcement), expands Troubleshooting from five entries to seven using the
AccordionGroup convention, and adds four production screenshots.
* docs(drift): use CardGroup for Related section
* docs(dossier): rewrite Stack Dossier page with full feature coverage
* docs(networking): rewrite Compose Networking page with full feature coverage
* docs(doctor): rewrite Compose Doctor with full 30-rule reference, screenshots, and cross-links
* docs(networking): add production screenshots and correct alt text
Adds 7 production screenshots for all sections of the Compose Networking
page and updates the four placeholder alt texts written before screenshots
were taken to match what the actual images show (arr-net external badge,
swag service with 443/tcp and 80/tcp, single-service exposure intent row).
Also adds the full-panel overview image at the top of the page.
* docs(environment-guardrails): rewrite with project env file, env file status, and screenshots
* docs(storage): rewrite Storage Portability page with screenshots and full coverage
Rewrites compose-storage.mdx from a 61-line sketch into a complete
reference page. Key additions: Where to find it section with screenshot,
full storage inventory section documenting all mount type/access/status
chips and the Linux owner display, expanded portability verdict section
with per-reason detail and edge-case caveats (read-only binds, symlink
escapes, anonymous volume risks), snapshot coverage section with admin
scope and remote-node behavior, Findings in Doctor cross-reference, and
six troubleshooting accordions covering tab visibility, bind status,
external named volumes, render errors, and snapshot coverage states.
Adds two production screenshots: storage-tab.png and storage-node-bound.png.
* docs(stack-labels): rewrite with accurate permissions, capability gate, dry run, live preview, and color conflict docs
* docs: rewrite Stack Sidebar page with accurate feature coverage
Rewrites the Stack Sidebar documentation page to match the current UI.
Key changes:
- Fix branding header description (shows logo + version, not just version)
- Fix bulk mode icon description (stacked-rows, not square)
- Add cross-node search section (fan-out behavior, Other nodes section,
unreachable-node warnings, click-to-switch navigation)
- Update Labels submenu description (inline New label creation, Manage labels link)
- Note that Delete only appears when the user has delete permission
- Remove the auto-update implication from Schedule task description
- Rewrite the Activity ticker section with the full 6-state priority cascade
table; remove the non-existent IDLE state; correct pulsing-dot behavior
- Replace all 7 stale screenshots with fresh production screenshots
- Add new sidebar-cross-node-search.png screenshot
* docs(atomic-deployments): refresh screenshot and document project env files, rollback readiness, and recovery actions
* docs(atomic-deployments): fix rollback permission visibility and banner string accuracy
The Rollback menu entry is hidden by the frontend when the user lacks
stack:deploy; it never appears and does not 403. Fixed the step-4
narrative and troubleshooting accordion to match.
The rollback-failure banner emitted by ComposeService is
'=== Rollback failed. Manual intervention may be required ==='
(period, capital M). Fixed both occurrences in the page.
Updated the Settings navigation path from the nonexistent
'Roles & Access' to the real 'Access'.
* docs(deploy-progress): rewrite with health gate, inline style, and 9 fresh screenshots
Add health gate section covering all four states (observing, passed, failed,
unknown) with exact UI banner text and the configurable observation window.
Expand the inline style section with full band content, 4s auto-dismiss, and
pill handoff. Add Scanning as a supported entry point. Replace all 6 existing
screenshots and add 3 new ones (modal-health-gate, inline-banner, setting-style).
Add two health gate troubleshooting accordions. Add Related CardGroup linking to
health-gated-updates, stack-activity, deploy-enforcement, and atomic-deployments.
* docs(health-gated-updates): refresh screenshots and correct signal row order and label
* docs(deploy-enforcement): rewrite with fleet replication, honor suppressions location, scan-failed dialog state, and fresh screenshots
Adds the Fleet policy replication section covering control/replica behavior,
Managed by control node banner, and Demote to control. Documents the exact
location of the Honor suppressions toggle (bottom of Policies tab). Expands
the block dialog section with the scan-failed row state. Updates all three
screenshots to the current visual design. Restores the Admiral license note
and corrects the policy-card scope description.
* docs(app-store): rewrite with mobile layout, fresh screenshots, and registry admin note
- Replace all 5 stale screenshots with 1920x1080 production captures
- Add app-store-mobile.png showing the status masthead layout
- Document mobile single-column layout in a new Mobile subsection
- Note that the featured hero has its own Deploy button
- Mark the category rail as desktop only with a cross-link to Mobile
- Add admin-account requirement to the custom registry section
- Add Related CardGroup linking vulnerability scanning, deploy progress, deploy enforcement, and resources
Scan policies, deploy enforcement, the suppression-aware deploy-block
toggle, SARIF export, and OpenVEX export now work on Community, matching
the rest of the vulnerability-scanning surface that was already free.
Backend: drop the tier gate from the seven security routes and from the
dashboard configuration-status scan-policies row, so the Dashboard and
Fleet config cards stop hiding the Vulnerability scanning row. Reading
policies stays auth-only; mutations and exports stay admin-only.
Frontend: always show the Policies tab and panel, the SARIF and VEX
export actions, and the honor-suppressions toggle for admins.
Docs: move scan policies, SARIF, and OpenVEX to every tier across the
feature and API-reference pages; clarify that Fleet Sync's cross-node
replication remains the paid part.
The schedule editor exposed only a raw 5-field cron input, which is
unfriendly for the common "daily at 3am" or "one-time next week" cases.
Add a Simple mode (now the default) that builds the cron from a
frequency and time: Once, Hourly, Daily, Weekly, and Monthly. Advanced
mode keeps the raw cron input as an escape hatch.
- Simple mode compiles to the existing cron_expression on save; no
schema or scheduler changes.
- One-time schedules reuse the existing delete-after-run flag: selecting
Once turns it on and locks it so the task runs a single time (cron has
no year field, so without it the task would repeat yearly).
- Editing a task opens in Simple mode when its cron maps to one of the
simple shapes, otherwise in Advanced; switching a custom cron to Simple
warns that it will be replaced.
- Day-of-week uses frosted toggle chips and the time uses hour/minute
selects so the controls match the rest of the editor.
Timeline pills and the mobile schedule list now identify what each
scheduled run acts on instead of repeating the task name. Pills stay
compact (firing time plus a category-aware target) and carry the full
detail on hover:
- Stack actions show the stack name.
- Fleet snapshots show "Entire fleet".
- Fleet auto-updates and node-scoped prune/scan show the selected node.
- The hover tooltip adds the action label, task name, and node.
The mobile list resolves node names too, so prune and scan rows name the
node rather than the literal "system". A shared scheduleTargetDescriptor
helper removes the target-label logic that was duplicated across the
desktop and mobile views. The lifecycle lane is renamed "Stack lifecycle"
to match the action-picker category wording.
* fix: request registry tokens with the target repository scope
The image-update detector authenticated to registries by reusing the scope
echoed in the registry's GET /v2/ ping. That ping carries no repository
context, and ghcr.io answers it with a placeholder scope
(repository:user/image:pull), so the token was requested for the wrong
repository and rejected. Every ghcr.io-backed image (including lscr.io, which
delegates auth to ghcr.io) then failed its manifest lookup and was reported as
"Registry unreachable", while Docker Hub and quay.io kept working. Always
request a pull scope for the repository being checked rather than the echoed
placeholder.
Also report the actual failure cause: getRemoteDigestResult now distinguishes
an authentication failure, a rate limit (with retry-after), a missing image, a
registry error, and a genuinely unreachable registry, instead of collapsing
every failure into "Registry unreachable". getRemoteDigest stays a
digest-or-null wrapper so the update-preview path is unchanged, and
listRegistryTags shares the same token path so it now resolves on
ghcr.io/lscr.io too.
* fix: neutralize control characters in the registry digest error log
The error-path console.error in getRemoteDigestResult interpolated the image
ref and the caught error message, both of which originate from compose-authored
input. Route them through sanitizeForLog so a crafted image string or upstream
error text cannot forge multi-line log entries (log injection). The returned
reason and the digest logic are unchanged.
* fix: distinguish failed image-update checks from "up to date"
The image-update detector collapsed every failure (registry unreachable,
missing auth, rate limit, unresolved local digest) into hasUpdate:false and
dropped the captured reason, so a failed check was indistinguishable from a
current image and never raised a notification, even while a manual stack
update still pulled a newer image.
Detection now records a tri-state per stack (ok / partial / failed) with the
failure reason, exposed via a new GET /api/image-updates/detail (the boolean
GET / is unchanged so fleet aggregation is unaffected). A fully-failed check
preserves the last known has_update, so a transient outage neither erases a
real update nor flaps the notification state. The sidebar shows a muted
"couldn't check" indicator with the reason on hover, and the Update board
lists stacks whose check failed in a "could not be checked" advisory.
Detector hardening: the manifest digest lookup issues HEAD first (falling back
to GET) so it no longer draws down Docker Hub's anonymous pull-rate budget, and
local RepoDigest matching is normalized so official library/* images resolve
their digest instead of falling through to a silent "no update".
* fix: preserve confirmed updates through partial checks; tighten failure surfacing
Address review findings on the tri-state image-update detection:
- A partial check (some images errored) no longer erases a previously
confirmed update; only a fully-ok check can lower has_update, so a single
image's registry blip cannot drop the stack's update and re-fire the
notification on recovery. Adds a regression test.
- The image-level catch stores getErrorMessage(e) rather than raw String(e),
since that value surfaces verbatim in the sidebar tooltip and readiness
advisory.
- useImageUpdates and the readiness detail fetch now log unexpected non-ok
responses instead of silently leaving stale state.
- Remove an unused checkFailedCount derivation (the row indicator is driven by
the checkStatus prop).
- Reword the recordStackCheckFailure docstring and the HEAD-first comment.
Vulnerability scan rows were never cleaned up when their image was removed
from Docker or their stack was deleted, so the Security Overview (including
the Top exploit-risk findings card) kept surfacing findings for artifacts that
no longer exist.
Scan results now reflect what is still on the host:
- Deleting a stack immediately purges its stack:<name> compose-config scan.
- A background reconciliation in the monitor janitor removes scans whose image
is gone from the node, or whose stack folder no longer exists. It is
fail-safe: a scan is only removed when its artifact is positively known to be
gone, the Docker image list is read with a timeout (skipped on failure), and
stack scans are reconciled only when the stack list is non-empty.
- An opt-out "Remove scans for deleted images and stacks" setting (on by
default, per-node) lets operators retain scan history for removed artifacts.
Scan deletes remove child findings explicitly, since SQLite foreign-key cascade
is not enabled on the connection.
* feat: add node update alerts with changelog tab and skip-version handling
- Add node_update_available notification category with blue/brand bell dot
- Route node_update_available notifications to Fleet -> Node updates sheet
- Add Changelog tab to NodeUpdatesSheet with GitHub release notes
- Add per-node skip-version persistence (node_update_skips table)
- Skip hides update CTA on node card and sheet; re-surfaces on newer version
- Skipped nodes excluded from Update all backend filter
- Add pulsating dot indicator on Changelog tab when updates available
- Always-visible View changelog action in notification row bottom
- Admin-only for all mutating controls (skip, unskip, update)
- Backend tests for skip-version semantics (15 tests)
- Update fleet-view.mdx, remote-updates.mdx, and OpenAPI spec
* fix: address audit findings - nested button, stale changelog, semver normalization, mobile intent
- Move View changelog button outside routable button (sibling element)
- Fix aria-label for node_update_available notification rows
- Support ?recheck=true on release-notes endpoint
- Invalidate release notes cache on forced recheck
- Store normalized semver (semver.valid strips v prefix)
- Skip fleetUpdatesIntent on mobile (desktop only)
- Add v-prefix normalization test
* fix: restore View changelog on same line as timestamp, opposite sides
The button is always visible at the bottom right of the notification card,
on the same row as the timestamp (just now), using justify-between layout.
* fix: update tests for node_update_available category and release-notes fetch
- Backend: monitor-service tests now expect node_update_available instead of system
- Frontend: NodeUpdatesSheet tests mock release-notes API call to prevent undefined then()
* fix: resolve ci lint failures
Add structured posture reasons derived alongside the posture verb in
securityPosture.ts so the masthead and Overview tab can answer why the
page is red, what to do first, and what clears it.
Backend:
- derivePostureReasons() returns blocker, review, and info reasons from
the same SecurityPostureFacts used by deriveSecurityPosture()
- deriveSecurityPosture() depends on derivePostureReasons() internally
- Exposure split: public exposure with KEV, fixable, or EPSS >= 0.1 is a
blocker; exposure without any of those is a review item
- Fully dismissed exposed images produce no posture reason
- postureReasons and primaryAction returned by the overview endpoint
Frontend:
- ReviewQueueCard on the Overview tab with per-row CTAs for blockers
- Action summary in masthead subtitle and desktop primary CTA button
- Card gated on posture not being Unknown
- Backward compatible with older remote nodes
* feat: split Host Alerts into Host Alerts, Container Alerts, and Stacks guardrails
Move global_crash from Host Alerts to new Monitoring > Container Alerts section.
Move health gate and env deploy guardrails from Host Alerts to
Infrastructure > Stacks > Deploy Guardrails subsection.
Host Alerts now contains only host threshold settings (CPU, RAM, disk,
alert suppression, and the master host_alerts_enabled toggle).
Stacks gains a Deploy Guardrails subsection (node-scoped, admin-gated)
alongside the existing Workflow controls (browser-local).
Dashboard Crash detection row now routes to Container Alerts.
* docs: update crash detection toggle description to match new Container Alerts section