Commit Graph

34 Commits

Author SHA1 Message Date
Anso 63213c0960 feat: add service-scoped Compose update and restore (#1648)
* feat: add service-scoped Compose update and restore

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

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

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

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

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

* fix: keep service recovery reachable without Deploy Progress

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

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

Service grouping hid the summary strip and Compact/Detailed/Expand controls that still applied to multi-container stacks.
2026-07-19 02:42:29 -04:00
Anso ba2e7bded9 feat: first-boot compose discovery and adopt-first sidebar (#1600)
* feat: add compose discovery for setup preflight and sidebar empty state

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

Closes #1582

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

* test: align getStackMenuVisibility assertions with showTakeDown key

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

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

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

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

Remove duplicate Take down from More actions.

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

Record stack_taken_down in activity on successful POST /down.
2026-07-09 12:20:13 -04:00
Anso dbe230eef3 fix(editor): remove misleading image line above stack actions (#1584)
Closes #1580. The Command Center header showed the first container's
image directly above stack-wide Start/Stop/Update controls, which implied
those buttons targeted one image. Remove the header image/digest row;
per-container ImageSourceMenu on each row remains.
2026-07-07 14:16:32 -04:00
Anso 0f9925e04f feat: block self-stack lifecycle ops with UI and preflight guardrails (#1569)
* feat: block self-stack lifecycle ops with UI and preflight guardrails

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

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

Add SelfStackProtectedDialog and self-managed-stack preflight warning.

Closes #1564

* fix: add missing stackSelfFlags mock to useSidebarContextMenu test

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

* fix: harden self-stack protection during startup

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

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

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

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

Uses the per-image data the update-preview API already returns.
2026-07-06 02:08:02 -04:00
Anso f2b5c68d84 feat: add build-aware compose stack updates (#1561)
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.
2026-07-05 04:45:45 -04:00
Anso fbe98676cb feat: bound container health pane so logs stay visible on large stacks (#1556)
* 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
2026-07-05 02:53:26 -04:00
Anso 9ff678a7bb docs: v1 docs refresh (batch 5) (#1395)
* 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
2026-06-29 01:29:03 -04:00
Anso f23b7e1bac feat: ordered multi-file Compose for Git sources (#1380)
* feat: ordered multi-file Compose for Git sources

Extend Git sources to deploy an ordered list of compose files merged with
docker compose -f base.yaml -f override.yaml ..., plus an optional project
directory.

- Pick and reorder compose files from the repository tree (drag to reorder on
  desktop, up/down arrows on phones); manual path entry is also supported.
- The ordered set drives every stack-scoped compose command (deploy, update,
  start/stop/restart/down, image scans, Compose Doctor) and the container
  lookup, so a service or image declared only in an override is handled too.
- Runtime keys off the materialized set, not the saved configuration: saving a
  source does not change deploy args until the pull is applied, and apply
  materializes from the pending snapshot rather than live config.
- The project directory is passed as --project-directory, with -p <stack>
  pinning the Compose project so container labels stay stable.
- The Mesh override is layered last; single-file sources are byte-identical to
  before, and existing rows keep working via the single-path fallback.

Docs cover the picker, ordering, project directory, and the new troubleshooting
and limitations (referenced files are not materialized; the dependency graph,
drift, and networking views read the primary file).

* fix: harden multi-file Git source (hash, unlink, collisions, node id)

- hashContent folds ordered file CONTENTS (not paths) so a clean multi-file
  stack is not flagged as locally edited: create/apply hash the fetched files
  (repo paths) while pull hashes the on-disk files (materialized paths), which
  previously disagreed and showed a false "local edits detected".
- Block unlinking a multi-file or project-directory Git source (409): the deploy
  spec lives on the source row, so removing it would silently revert deploys to
  root compose.yaml. Single-file sources still unlink.
- Reject materialized-path collisions in the selection validator: an additional
  file equal to or nested under compose.yaml, an ancestor/descendant overlap
  between selected files, and a project directory nested under a compose file
  (previously a 500 at materialization).
- DockerController.getContainersByStack uses the controller's node compose dir
  and passes its node id to the authored prefix, instead of the process default.

* fix: CI failures on multi-file Git source (test crash, aria query, path barrier)

- GitSourceFields no longer crashes when repoUrl/branch are falsy: the canBrowse
  trim() is optional-chained, so a reusable field component tolerates partial
  props. Fixes the apply-binding panel test, which feeds a minimal source object.
- GitSourcePanel tests query the footer Remove button by its exact name, so the
  picker's per-file "Remove <path>" buttons no longer collide with the broad
  /remove/i match (the test intent, footer Remove present/absent, is unchanged).
- validateCompose uses an inline resolve + startsWith barrier at the context-dir
  mkdir sink (CodeQL does not credit the wrapped isPathWithinBase helper),
  clearing the js/path-injection alert. The containment check is equivalent and
  contextDir is also validated upstream.

* test: update Git source E2E spec for the multi-file compose picker

The compose-file picker replaced the single #git-source-path input and added
per-file Remove buttons, so the E2E spec drove selectors that no longer exist:

- Drop the redundant compose.yaml fills (the picker defaults to compose.yaml).
- Select the footer Remove button by exact name so the picker's per-file
  "Remove <path>" buttons no longer make the locator ambiguous.
- Set a custom compose path through the picker (add via the manual input, press
  Enter, then remove the default compose.yaml).

* test: match the footer Remove button with an exact Playwright name

Playwright's getByRole name option is a substring match by default, so
{ name: 'Remove' } also matched the picker's "Remove <path>" buttons. Require an
exact match so only the footer Remove button is selected.
2026-06-17 13:24:55 -04:00
Anso f253276303 fix: make published port links open reliably (#1359)
* fix: make published port links open reliably

Container published-port links now render as real anchors that open on
desktop and mobile, replacing ad hoc window.open calls. A shared service
URL builder centralizes host resolution (configured host, remote node
API host, or the browser host, with no browser fallback for unreachable
remote nodes), protocol selection (HTTPS for port 443), and known app
sub-paths (Plex opens its web path). The container port mapping itself is
the link, with a Copy URL action beside it. The stack Open App menu and
the anatomy panel footer use the same builder, and the menu only offers
Open App when a reachable URL can be built.

* fix: skip UDP ports and scope known-app paths to the container port

Two follow-ups to the published-port links:

- The known-app path (Plex web sub-path) was borrowed from the published
  host port even when the container port was known and unregistered, so a
  non-Plex service published on host port 32400 wrongly inherited it. The
  container-port lookup now wins when known; the published-port lookup stays
  a fallback for the menu and anatomy footer, which only have the host port.

- UDP ports could surface as HTTP links. The backend now carries the port
  protocol through both container-mapping paths and skips UDP when choosing
  the main web port (extracted as selectMainWebPort), and the container card
  filters UDP before selecting a port to link.
2026-06-11 16:37:53 -04:00
Anso e3944295f2 feat: link container images to registry and source metadata (#1358)
* feat: link container images to registry and source metadata

Turn image references on the stack surfaces into actionable links so an
operator can review what an image ships before approving an update.

A new pure helper maps a reference to its registry page (Docker Hub for
official and namespace images, the owner profile for GitHub Container
Registry) and never guesses a link for unknown or private registries. A
shared dropdown adds copy-image-reference plus source, homepage,
documentation, and revision links read from the image OCI labels via a
local inspect, so they need no internet access and only appear when the
image ships them. Version and non-commit revisions render as plain text.

The menu is wired into the stack header image row, each per-container
health card, and the update-readiness cards.

* fix: invalidate image-source inspect token synchronously on image change

The request token that drops a stale image inspect response was bumped
only in a passive effect cleanup, which runs after paint. A response for a
superseded image could resolve in the gap after the image-id change
committed and still pass the token check, writing stale source labels into
the new menu. Move the invalidation to a layout effect so the token is
bumped during commit, before any network response can interleave.

Add a regression test that holds the first inspect open, changes the image
id, then resolves the stale response and asserts it is discarded.
2026-06-11 14:37:04 -04:00
Anso f7f3afe05a feat(stacks): one-click import for stray compose files (#1320)
* feat(stacks): move discovered import candidates into place

The guided import flow previewed loose and nested compose files but could
not act on them, so it only told the user where to move files by hand. Add
an opt-in "Move into place" action: relocate a loose-root file into its own
<name>/ subfolder, or promote a nested stack directory one level up, so
Sencho's filesystem discovery lists it as a stack. The file stays a plain
compose file on disk; nothing is captured into a store. The move re-derives
the candidate from a fresh scan and matches by location, validates the
destination name and containment, resolves symlinks before the rename, and
never overwrites an existing stack. Backend and frontend both gate the
action on stack:create.

Also fix the rescan flicker: scan results now stay on screen while a rescan
runs (only the Rescan button shows progress) instead of the whole panel
collapsing to a spinner, and an empty rescan surfaces a toast.

* fix(stacks): make import-move destination creation atomic

The loose-root branch created the destination directory with mkdir
recursive after an access() existence precheck. If the destination
appeared between the check and the create, recursive accepted the
existing directory and the following rename could overwrite a
same-named compose file inside it, so the intended conflict response
never fired. Use a non-recursive mkdir so a destination that already
exists raises a conflict instead of being merged into. Add a regression
test that forces the precheck to miss and asserts the existing file is
left intact.

* fix(stacks): only offer not-yet-imported compose files in the import tab

The import tab listed every compose file in the compose directory,
including ones that are already stacks (a top-level subfolder with a
compose file), which just duplicated the sidebar. The scan now skips
those and surfaces only files that still need importing: a compose file
loose at the compose-dir root, or one nested a folder too deep.

Also harden the move-into-place write path that turns a stray file into
a stack: a failed rename after the destination folder is created now
rolls back the empty folder, so a retry is not blocked by a false
"already exists" conflict, and the move switches on an exhaustive set of
placements so a new one cannot silently take the wrong branch. The
sidebar refreshes after a move so the imported stack appears right away,
and the docs describe import as relocating a file, not capturing running
containers.

* fix(stacks): reject a nested import whose compose file escapes the base

The move-into-place path for a nested compose file validated only the
parent directory's real path, not the compose file itself. A directory
that is real and inside the compose base but holds a compose file
symlinked outside the base would survive the directory move and become a
stack whose compose file still points outside the base, which the editor
read path would then follow. The move now resolves the compose file too
and refuses it unless it stays inside the resolved source directory,
matching the loose-root check and the scan's preview reader.

* fix(stacks): satisfy CodeQL path and log analysis in import-move

The import-move write path built its destination directory from the
user-provided stack name through resolveStackDir, whose containment
barrier is wrapped in a helper that static analysis does not credit, so
every filesystem sink on the destination was flagged as path injection.
Re-establish the resolve-against-the-safe-base plus startsWith barrier
inline at the sinks, matching the read and backup paths in the same file,
and route the relocated file path through the same check. The name is
already restricted to an alphanumeric, hyphen, and underscore allowlist,
so the containment can never actually fail; this only makes the existing
safety visible to the analyzer.

Also log the move route's error as a sanitized message rather than the
raw error object, so a name embedded in an error message cannot forge log
lines.
2026-06-05 22:35:04 -04:00
Anso 865d792874 feat(pricing): collapse to two tiers (#1309)
* feat(pricing): collapse to two tiers (Community + Admiral)

Collapse Sencho's pricing from three tiers (Community / Skipper / Admiral)
to two: a generous free Community tier and a single paid Admiral tier. The
Skipper tier is removed.

Now free in Community: auto-heal, auto-update, scheduled operations,
webhooks, notification routing, Fleet Actions and bulk operations, SSO
preset providers (Google / GitHub / Okta), unlimited users with admin and
viewer roles, and deploy safety (atomic deploys, auto-rollback, and
one-click rollback).

Admiral (paid) is focused on running and governing a fleet: blueprints,
Fleet Secrets, deploy enforcement, vulnerability report export, audit log,
host console, private registries, mesh networking, node cordon, managed
cloud backup, LDAP / Active Directory SSO, and the advanced RBAC roles
(deployer, node-admin, auditor) with per-resource scoped assignments.

Internally the license variant distinction is removed so tier is binary
(community / paid). License validation still verifies the Lemon Squeezy
store and product before granting paid status.

Docs and the contributor guide are updated to the two-tier model.

* docs(pricing): correct licensing page to two-tier pricing and tidy stale tier wording

The licensing docs page kept the old Admiral pricing plus a Founder
Lifetime column and an Enterprise paragraph after the two-tier collapse.
Update it to $12/month or $99/year, drop the lifetime and Enterprise
content, and link to the pricing page for current pricing.

Also fix stale "Skipper" wording in CLA.md, SUPPORT.md, one test title,
and three test comments. Historical CHANGELOG entries and the
retired-Skipper license-guard test are intentionally left as-is.

* docs: align licensing and SSO pages with the two-tier model

Correct the SSO overview so the Google, GitHub, and Okta presets read as
available on every tier, matching the provider table; only LDAP and Active
Directory require Sencho Admiral. Remove the lifetime-plan references from the
licensing, settings, and troubleshooting pages so they reflect subscription-only
Admiral pricing.

* fix(rbac): omit scoped permissions from /me on the Community tier

Scoped role assignments only take effect on the paid tier, but GET /api/permissions/me returned them unconditionally, so a downgraded instance with leftover assignments rendered per-resource affordances the API then rejected with 403. The endpoint now mirrors the permission middleware and includes scoped permissions only on the paid tier. Adds a regression test covering the downgrade case.

* docs: use custom-pricing wording on the contact page

The two-tier model has no Enterprise tier; reword the contact page's enterprise pricing/deals to custom pricing/deals so it does not imply a tier that no longer exists.
2026-06-04 17:45:53 -04:00
Anso 06b25262cc feat(stacks): guided first stack import flow (#1285)
* feat(stacks): add guided first stack import flow

Add an Import mode to the Create Stack dialog and a zero-stacks empty
state so a new user who already has compose files on disk can land their
first stack without reading the docs first.

A read-only scan of the compose directory (GET /api/stacks/import/scan)
lists the compose files it finds with a dry preview of each file's
services, ports, volumes, and env files. Each result is labelled by
placement: already a stack, loose at the root of the compose directory,
or one folder too deep, with the exact path to move misplaced files to.
The scan never writes, moves, or changes any files.

Manual stack creation (Empty, From Git, From Docker Run) is unchanged.

* fix(stacks): read import-scan candidates via a single file handle

Open the compose file once and stat plus read on the same descriptor so
the size check and the read observe the same inode, instead of resolving
the path twice (stat then readFile), which is a time-of-check/time-of-use
race. Mirrors the existing handle-based readers in FileSystemService.

* fix(stacks): confine import scan to the compose dir and refine the empty state

Harden the read-only import scan:
- Resolve symlinks and confirm the real target stays inside the compose
  directory before reading a candidate, and reject non-regular files, so a
  symlinked compose file or parent cannot expose a file outside the compose
  directory through the preview (matches resolveSafeStackPath).
- Read at most the stat-reported size (bounded by the 1 MiB cap) from the open
  handle, so a file that grows after the size check cannot exceed the cap.
- Log when the compose directory or a subdirectory cannot be read, so an access
  failure is not silently reported as "no compose files found".

Only show the first-run "No stacks yet" prompt when no filter chip is active, so
a filter that matches nothing is not mistaken for an empty fleet.
2026-06-02 16:10:05 -04:00
Anso adcd04b01a refactor(auto-update): retire per-stack gate, drive auto-update from schedules only (#1233)
* refactor(auto-update): retire per-stack gate, drive auto-update from schedules only

The per-stack Auto-update toggle in the stack sidebar context menu wrote a
gate row to `stack_auto_update_settings`, but actual updates only ran when a
`scheduled_tasks` row with `action='update'` fired. On a fresh install the
toggle was inert: detection ran every 6h, nothing was applied.

The same context menu already exposes `Schedule task`, which opens
ScheduledOperationsView pre-filled for the stack where the user can pick
`Auto-update Stack` and any cron. Keeping the toggle alongside that flow
duplicated the same action and turned the gate table into a parallel store
of "is a covering schedule active" derivable from `scheduled_tasks` itself.

Drop the gate model entirely:
- Backend: remove the `stack_auto_update_settings` table and its four
  accessors, the three routes under /api/stacks/*/auto-update, the per-stack
  skip in /api/auto-update/execute and SchedulerService.executeUpdate's
  fleet branch, and the clearStackAutoUpdateSetting call on stack delete.
  Dashboard `autoUpdate` count derives from scheduled_tasks (action='update'
  rows pinned to the node, total/enabled split).
- Frontend: drop the Auto-update entry from the sidebar context menu and its
  optimistic toggle plumbing. Drop autoUpdateSettings state, the
  /stacks/auto-update-settings fetch, and the auto-update-settings-changed
  WebSocket branch. Slim useSidebarActivitySummary (just nextRunAt; no
  enabled/total counts). AutoUpdateReadinessView's per-card autoUpdateEnabled
  now means "a covering enabled action='update' schedule exists" (per-stack
  row or fleet row on this node, earliest next_run_at wins, per-stack row
  wins on ties), with the gate-fetch removed.
- New: scheduledTasksRouter broadcasts scope: 'scheduled-tasks' on POST,
  PUT, PATCH /toggle, and DELETE so useConfigurationStatus and
  useNextAutoUpdateRun refetch under the 250ms debounce instead of waiting
  for the 60s poll. The broadcast is wrapped so a broken subscriber socket
  cannot turn a successful mutation into a 500.
- Docs: rewrite the "Per-stack control" section of auto-update-policies.mdx
  to describe the schedule-based model; update the matching troubleshooting
  entry. The misleading fleet-update help text in ScheduledOperationsView
  is corrected to reflect that every stack on the node is covered.

Tier parity: the surviving auto-update path (Schedule task -> Auto-update
Stack / All Stacks) is gated `requirePaid + requireAdmin` backend and
`isPaid + isAdmin` frontend, matching the gate the deleted routes carried.
The pre-commit grep returns no tier-related diff outside this PR's scope.

No data migration is provided: greenfield rules apply, and the leftover
table on already-shipped instances is harmless because no code reads or
writes it after this PR.

* docs: sweep remaining references to the per-stack auto-update toggle

The previous commit retired the per-stack Auto-update gate in favor of
configuring auto-update purely through scheduled tasks. This commit
removes the now-stale mentions of that toggle across the operator docs:

- docs/features/sidebar.mdx: drop the Auto-update entry from the Inspect
  group description, the matching screenshot alt-text, and the Skipper
  Note that listed it. Schedule task now carries the cross-link to
  Auto-Update Policies.
- docs/features/stack-management.mdx: drop the Auto-update list item;
  refresh the Schedule task entry to mention the Auto-update Stack action.
- docs/features/dashboard.mdx: rename the Configuration Status row from
  "Auto-update stacks" to "Auto-update schedules" with the new value
  shape, and rewrite the troubleshooting accordion to describe the
  scheduled-tasks invalidation path.
- docs/features/scheduled-operations.mdx: rewrite the Auto-update All
  Stacks row and helper text to reflect that every stack on the node is
  covered (no per-stack opt-out from this surface anymore).
- docs/features/multi-node.mdx: rewrite the Updates column definition to
  derive the Auto/Off flag from enabled Auto-update Stack / Auto-update
  All Stacks schedules instead of the removed per-stack policy.

The auto-update-policies.mdx rewrite in the previous commit already
covered the main reference page. The sidebar-context-menu.png screenshot
will be refreshed on release once the new menu is live in production;
the alt text is updated in this commit so it accurately describes the
shipping state.

No website edits needed: the Auto-Update Policies feature card description
("Schedule automatic image pulls and redeployments per stack on your own
cadence") and the feature matrix labels ("Auto-update stack schedule",
"Auto-update all stacks schedule") remain accurate under the new model.

* fix(stacks): drop orphaned requireAdmin import after auto-update route removal

CI's backend lint step flagged this PR's earlier deletion of the three
/api/stacks/*/auto-update routes: those handlers were the only callers of
`requireAdmin` inside routes/stacks.ts, leaving the named import on line 15
unreferenced. `requirePaid` and `effectiveTier` from the same line are still
in use elsewhere in the file and stay.

tsc --noEmit does not flag unused named imports; ESLint's no-unused-vars
does. Local backend lint reproduces and now reports 0 errors against the
existing 334-warning baseline.
2026-05-26 11:08:33 -04:00
Anso 8ba88755b1 fix(stacks): default Empty template ships ports block commented out (#1189)
The Empty branch of the Create Stack flow wrote a compose.yaml whose
first service bound the host's port 8080 by default. On any workstation
already running something on 8080 (traefik, caddy, librespeed, another
nginx, etc.) the very first deploy failed at the docker compose
networking step with "Bind for 0.0.0.0:8080 failed: port is already
allocated", which made the day-one experience feel broken right after
F-2 (PR #1168) tightened the dialog itself.

The boilerplate in FileSystemService.createStack now emits the ports
block commented out plus a one-line hint above it. A fresh deploy
binds no host port, so the container starts cleanly on any host; the
user uncomments the two-line block when they're ready to expose the
container. The deterministic shape (no probe-and-write, no random port,
no preflight scan) avoids the TOCTOU race that a free-port probe would
have left between template creation and the actual compose up.

Adds backend/src/__tests__/file-system-service-create-stack.test.ts
(8 cases): directory + file creation, structural YAML assertions via
yaml.parse to lock in the no-live-ports invariant, raw-text regex to
lock in the commented hint, and the already-exists rejection path.
FileSystemService.createStack had no coverage before this change.

Adds e2e/default-stack-template-no-fixed-port.spec.ts (1 case): drives
the dialog through Create, reads the resulting compose via the
in-browser apiFetch, and asserts the live + commented invariants
end-to-end.

docs/features/stack-management.mdx Empty bullet rewritten to describe
the minimal skeleton and the commented ports block instead of calling
it "blank".

Resolves: F-3 in the v1.0 audit tracker.
2026-05-24 01:06:22 -04:00
Anso 0be479100c docs: v1 docs refresh (#966)
* docs(introduction): rewrite intro page and refresh screenshots

Rewrite the Getting Started introduction to reflect the current product:
adds the Mesh, Blueprints, Pilot, Fleet, Resources concepts up front;
restructures capability sections around Stacks, Fleet (now including
Fleet Actions), Mesh, Blueprints, Monitoring, Resources, Security,
Automation, and Pilot/Remote ops; cross-links every claim to the
matching feature page.

Replaces the dashboard hero shot with a fresh capture against the
redesigned cockpit chrome and adds three inline shots (running stack
with anatomy and logs, fleet command deck, resources hub with
reclaim header). All screenshots taken at 1920x900, dark theme,
with node names, usernames, IPs, and home paths neutralized.

Drops outdated claims: stale "190+ templates" count, the
"viewer accounts" RBAC summary, and the "atomic" deployment label
that did not match the actual rollback mechanism.

* docs(introduction): add Federation and Fleet Secrets

Federation has shipped (Admiral) with cordon and pin policy as
operator overrides on the blueprint reconciler. Fleet Secrets is
landing as a Skipper+ tab for versioned env-var bundles encrypted
at rest, with diff preview and target push.

Mirror those in the Run-one-machine-or-many bullet list and in the
Tiers paragraph so the introduction matches the current product.

* docs(introduction): re-shoot screenshots against v0.72.0 production

Re-capture all four introduction screenshots from the upgraded
production node so the fleet view shows the current full tab strip
(Overview / Snapshots / Status / Deployments / Traffic / Federation
/ Fleet Actions / Secrets) instead of the older Overview / Snapshots
/ Status only. Same 1920x900 dark-theme capture and the same PII
scrub applied (node names, usernames, IPs, home paths neutralized).

* docs(quickstart): rewrite around v0.72.0 cockpit and add screenshots

Replaces the bare install snippet with a five-minute walkthrough that
matches the redesigned UI. Leads with a docker-compose.yml block (the
bare docker run command is collapsed in an Accordion), keeps the 1:1
path rule, and adds two new sections that show the user what happens
on first boot.

Adds two screenshots at 1920x900 dark theme:
- setup-cold-start.png: the Cold start card with Username, Password,
  Confirm password fields, and the Initialize console button.
- dashboard.png: the post-sign-in dashboard captured against v0.72.0
  with the full top nav (Home, Fleet, Resources, App Store, Logs,
  Auto-Update, Console, Audit, Schedules) and the populated Stack
  health table sorted by load.

Where-to-next now uses CardGroup cols=2 to match the introduction
page's pattern.

* docs(configuration): align env var reference with current backend

Bring docs/getting-started/configuration.mdx up to date with the
v0.72.0 backend:

- Remove PORT from the optional env vars table. The listen port is
  hardcoded to 1852 in backend/src/helpers/constants.ts and is never
  read from the environment. Replace it with a Listen port section
  that explains the fixed port and host-port remapping.
- Document API_RATE_LIMIT (default 200) and API_POLLING_RATE_LIMIT
  (default 300), both applied in production only.
- Note the /app/compose fallback default for COMPOSE_DIR while still
  pointing readers at the 1:1 path rule.
- Point the SSO env var section at the new SSO Quickstart page and
  keep the SSO feature reference as the deeper dive.
- Tighten First boot and cross-link to Quickstart so the screenshot
  is not duplicated.
- Add a Where to next CardGroup matching the refreshed Introduction
  and Quickstart pages.

Drop stale PORT=1852 and JWT_SECRET=your-secure-jwt-secret-here
lines from .env.example so the example no longer contradicts the
docs (PORT is hardcoded, JWT_SECRET is auto-generated and persisted
to the database during initial setup).

* docs(configuration): replace em-dash-substitute hyphens in prose

Three sentences used ` - ` (space-hyphen-space) as an em-dash
substitute. Replaced with the punctuation that fits each case:

- "How Sencho organizes your compose directory": semicolon between
  the two related clauses.
- Data directory Warning: parentheses around the parenthetical
  insertion.
- 1:1 path rule explanation: comma before the contrastive clause.

Heading slugs and YAML code-block comments are unchanged.

* docs(sso): refresh setup guide and drop misleading "one-click" wording

Brings the SSO Setup Guide in line with how SSO actually works in the
current build, and corrects misleading copy across both SSO docs pages.

- Setup Guide: explains the env-var-seeds-once / DB-is-authoritative
  config model up front, replacing per-section "Restart Sencho"
  wording that implied a restart was always required.
- Setup Guide: promotes the per-provider Test Connection button out
  of the LDAP-only paragraph into a generic intro callout, and adds
  a self-signed LDAPS tip.
- Setup Guide: notes that all OIDC providers accept a *_DISPLAY_NAME
  override for the login button label, and adds a commented LDAP TLS
  toggle to the full compose example.
- Setup Guide: adds two screenshots of the redesigned Settings > SSO
  panel (overview + LDAP card expanded with form).
- Both pages: replaces "one-click presets" / "one-click configuration"
  with "preset providers". The Skipper-tier presets still require an
  OAuth app provisioned in the provider's console; what they actually
  buy is provider-aware defaults and a branded login button. The old
  wording overpromised.

* docs(features-overview): regroup catalog and add 17 missing features

Restructure the Features Overview into the same six groups the docs
sidebar uses (Stacks & Deployments, Observability, Fleet & Multi-Node,
Security & Identity, Automation, Platform) plus a short Reference tail.

Add catalog entries for 17 shipped features that the previous overview
never mentioned: Stack Activity, Stack File Explorer, Deploy Progress,
Deploy Enforcement, Blueprints, Git Sources, Global Search, Pilot Agent,
Sencho Mesh, Fleet Federation, Fleet Actions, Fleet Sync, Fleet Secrets,
Two-Factor Authentication, CVE Suppressions, Auto-Heal Policies, Stack
Sidebar.

Fix two factual inaccuracies:
- Fleet View blurb wrongly gated search, sort, filter, and stack
  drill-down behind Skipper. The deep-dive is explicit that those are
  available on every tier; only the bulk Update All action inside the
  Node Updates modal is paid.
- Auto-update entry was titled and described as a scheduling system.
  The deep-dive page is the Auto-Update Readiness board (risk tags,
  changelog previews, rollback targets); the scheduler lives under
  Scheduled Operations.

Add three hero screenshots captured from production at 1920x900,
illustrating the redesigned cockpit visual language: Home dashboard,
stack anatomy, and fleet topology.

* docs(stack-management): refresh page around v0.72.0 cockpit and add screenshots

Updates the Stack Management page to match the current UI: sidebar with
filter chips and label groups, bulk mode, restructured kebab menu,
two-tab anatomy panel, and three-source New stack dialog (Empty, From
Git, From Docker Run).

Adds sections for Filter chips, Pinned and label groups, and Bulk mode.
Restructures the Stack context menu around the inspect, organize,
lifecycle, and destructive groups with their keyboard shortcuts.
Documents the From Git tab and cross-links Git Sources for the full
sync flow.

Replaces every existing screenshot with fresh captures from the current
UI and adds eight new captures for the new sections. Cross-links Stack
Activity, Stack Labels, Stack File Explorer, Compose Editor, Atomic
Deployments, Scheduled Operations, Auto-Heal Policies, Auto-Update
Policies, and Alerts and Notifications for features documented on
their own pages.

* docs(stack-activity): refresh page around v1 cockpit and recapture screenshots

Realign the page with the current Anatomy panel tab strip and the
StackActivityTimeline component:

- Frame the Activity tab as a sibling of Anatomy under the right-hand
  panel, with files/edit actions belonging to the strip.
- Expand the category guidance: list the five iconized categories and
  call out that other stack-scoped notifications (deploy failure,
  available image updates, auto-heal triggers, monitor alerts, scan
  findings) flow into the timeline with a generic icon.
- Tighten the actor-attribution rule to match the component: omitted
  for events without an actor and for system-driven events.
- Add the day-format example to the relative-time row.
- Recapture both screenshots from a populated stack (Today + Yesterday
  + Earlier with three distinct icons) and an empty stack.
- Convert troubleshooting blurbs to H3 for anchor links and consistency
  with the v1-refresh sibling pages.

* docs(stack-activity): wrap troubleshooting entries in Accordion blocks

Match the foldable troubleshooting pattern established by
docs/features/deploy-progress.mdx so the page stays compact and
readers can scan to their issue. This is the canonical formatting
for the /features section's troubleshooting blurbs going forward.

* docs(editor): refresh page around v1 cockpit and recapture screenshots

Rewrites the page around the dual-mode right panel (Anatomy by default,
Monaco when the user clicks edit), the redesigned Command Center action
bar, the new container row layout with status badges and live stats, the
Structured / Raw terminal logs toggle, the Git Source toolbar button, and
the opt-in diff preview. Drops the obsolete persistent embedded terminal
section. Preserves the #diff-preview-before-save and #log-viewer anchors
referenced from settings.mdx and global-observability.mdx. Replaces
editor-overview.png and container-exec-modal.png with fresh captures
against v0.72.0 production at 1920x900 dark theme; renames container
-actions.png to containers-list.png; adds command-center.png and
editor-edit-mode.png. PII scrubbed (host paths normalized) and the
compose-diff-preview/diff-modal.png shared asset is left untouched after
visual diff against the live UI showed no chrome change.

* docs(features): wrap troubleshooting accordions in AccordionGroup

Wraps the loose <Accordion> blocks in editor.mdx and stack-activity.mdx
in a single <AccordionGroup> to match the troubleshooting design used
by fleet-federation.mdx. Structural only; no content changes.

* docs(stack-file-explorer): refresh page around v1 explorer and add screenshots

Full rewrite to match the live two-pane Files tab. Replaces the false
'Edit' toolbar flow with the read-only chip + always-on Save model,
fixes the protected-files list (5 names) and dedicated-tab redirect
list (3 names), drops the fabricated 100 MB download cap, and corrects
the upload claim to single file at a time.

Adds sections for New File, Rename, Permissions, the type-to-confirm
protected-file delete flow, the non-empty folder delete confirmation,
the 500-entry tree display cap, and the symlink rendering. Restructures
troubleshooting around <AccordionGroup> + <Accordion> to match
fleet-federation.mdx, and adds two new entries (display cap, 403 on
Community admin write).

Ships nine screenshots captured against v0.72.0 production at 1920x900
in dark theme: overview, two-pane layout, protected-tree-marker,
viewer-edit-mode, new-file-dialog, context-menu-folder, context-menu-file,
permissions-dialog, delete-protected-confirm.

* docs(deploy-progress): rewrite around current modal + capture v1 screenshots

Aligns the page with the v0.72.0 implementation and standardizes the
troubleshooting layout with the rest of the docs refresh.

Setting and gating
- Renames the Settings field to "Deploy progress modal" and quotes the
  current helper text verbatim. Documents that the toggle is off by
  default, lives under Settings > Appearance > Display, is stored in
  localStorage, and syncs across tabs in the same browser.

Modal anatomy
- Names every visible UI string: header verbs, status indicator (with
  the "closes in <n>s" countdown that was previously undocumented),
  empty-body strings, footer toggle that flips between "Raw output" and
  "Hide raw", and the destructive border on ERR rows vs the softer warn
  tint on WARN rows.
- Replaces the vague "after a few seconds" with the actual 4-second
  auto-close timer; documents hover-to-pause and the
  leave-hover-restarts-the-countdown behavior.
- Documents the truncated error message in the failed-state header and
  the manual close-only requirement.
- Notes that the pill is portal-mounted and survives navigation.

Stage badges
- Keeps the 9-badge table but adds an honest note that most lines render
  as LOG because the badges are gated on Compose's "[+]" progress
  prefix, which Compose only emits in TTY mode and Sencho spawns it
  without one.

Entry points
- Splits the supported actions into the four that produce a populated
  structured-log body (Deploy, Update, Install, Git Apply) and the two
  that bypass compose and finish with 0 lines (Restart, Stop). Drops the
  "Down" claim from the user-facing list since no UI control currently
  triggers it; mentions the down route as an automation surface only.

Troubleshooting
- Wraps the existing accordions in an <AccordionGroup> matching the
  pattern used by the editor and stack-activity refreshes. Adds two new
  entries: one explaining the Restart/Stop "0 lines" outcome, one
  explaining the LOG-everywhere case for non-TTY compose output.

Screenshots (six PNGs in docs/images/deploy-progress/, 1920x900, dark
theme, captured against the upgraded production node)
- setting-toggle.png: the Display section with the toggle enabled.
- modal-streaming.png: a real update in flight at 19s, 554 lines.
- modal-succeeded.png: succeeded state with the live closes-in
  countdown visible.
- modal-raw-output.png: structured rows with the Raw output panel
  expanded beneath.
- pill.png: minimized pill anchored bottom-center on a stack editor
  view.
- modal-failed.png: failed state with the truncated error in the
  header and ERR rows highlighted.

* docs(resources): refresh Resources Hub page for v1 redesign and feature additions

Rewrite the page to match the shipping UI and replace stale screenshots
with fresh captures of the redesigned chrome.

- Document the admin-only Reclaim hero and clarify the per-tile Sencho-only
  vs. All Docker (includes external) split in Quick Clean.
- Add coverage of the Scan history toolbar button and the per-row severity
  badge plus shield-icon scan dropdown in the Images tab; cross-link to the
  vulnerability scanning page.
- Correct the Volumes column list (no Size column; size lives on the Largest
  5 landing card) and call out admin gates on browse and delete.
- Spell out the List/Topology view-mode toggle and that Create Network is
  admin-only and List-mode only.
- Rewrite the Unmanaged tab section around the project-grouped layout, the
  Select all + Purge Selected (N) admin-only multi-select, and the empty
  state copy.
- Replace screenshots: resources-reclaim, networks-list, create-network,
  network-inspect, network-topology, network-topology-toggle. Add fresh
  resources-volumes-tab and resources-unmanaged-tab captures.

* docs(app-store): refresh page around v1 deploy sheet, scan integration, and registry settings

Rewrites the App Store reference to match the current cockpit:

- Documents the weekly-rotated featured banner picked from the top-5 by GitHub stars and the star-descending grid sort.
- Adds the deploy-sheet structure (breadcrumb, meta line, About panel with Read more) and splits the Advanced tab into Ports, Volumes, Environment variables, Custom variables, and Security subsections.
- Documents the Trivy-gated Security checkbox, atomic vs non-atomic deploys by tier, and the rollback semantics driven by error class.
- Adds a Watching the deploy section linking to the deploy-progress modal.
- Rewrites the Custom registry section against the new two-panel settings layout (Default + Custom) with the URL validation rule and the using default / using custom hint.
- Adds a four-entry Accordion troubleshooting block in the house style.
- Replaces three screenshots and adds two (Advanced tab, Settings registry panel) captured against the production node.

Permissions wording aligns with current backend (admin only); the broader stack:create gate will land in a follow-up fix branch.

* docs(app-store): describe inline port-conflict messaging on deploy sheet

Update the deploy-sheet section to match the visible port-conflict
behavior: the Essentials tab surfaces a Port-conflict warning that
replaces the defaults hint when any default port is already bound, and
the Advanced tab shows an inline "in use by {stack}" message next to
the container port instead of a hover-only tooltip. Refresh the
screenshot alt-text and the troubleshooting Accordion to match.

* docs(app-store): align permissions note with stack:create gate

Pairs with the backend gate swap in fix/templates-deploy-rbac (#986)
which moves POST /api/templates/deploy from requireAdmin to
requirePermission('stack:create'). Updates the Note block under
'Watching the deploy' so the docs match the new behavior: admin and
node-admin can deploy templates from the App Store; viewer, deployer,
and auditor cannot.
2026-05-07 23:44:13 -04:00
Anso 945259f048 refactor(frontend): migrate CreateStackDialog to Modal chrome system (D-4) (#908)
* refactor(frontend): migrate CreateStackDialog to Modal chrome system (D-4)

Swap raw shadcn Dialog/DialogContent/DialogHeader/DialogFooter for the
Modal/ModalHeader/ModalBody/ModalFooter primitives shipped in D-1 and
introduce an inline ModeRail to replace the TabsHighlight chip. The new
chrome carries the cyan rail, mono kicker (STACKS · NEW), italic serif
title, and contextual footer hints per mode (ALPHANUMERIC · HYPHENS,
HTTPS REPOS ONLY, CONVERT FIRST / YAML READY + line-count accent).

ModeRail implements the full WAI-ARIA tabs pattern: aria-selected on the
active tab, aria-controls/role=tabpanel/aria-labelledby linkage to each
mode panel, roving tabIndex (active=0, inactive=-1), and ArrowLeft /
ArrowRight / Home / End keyboard navigation matching the contract that
the prior shadcn Tabs primitive provided.

Each mode also gains an explicit Cancel button alongside the existing
primary action, the empty-mode primary button is now disabled until the
stack name is non-empty, and the ModeRail disables itself while a Git
or docker-run create is in flight.

The async handlers (handleCreateStack, handleCreateStackFromGit,
handleConvertDockerRun, handleCreateStackFromDockerRun) and form-reset
helpers are unchanged; this PR is structural plus the segmented-control
redesign called out in the migration tracker as the D-4 risk note.

* test(e2e): align git-sources spec with new CreateStackDialog title

D-4 renamed the dialog title from "Create New Stack" to "New stack".
The shared openCreateStackDialog helper in git-sources.spec.ts was
still asserting the old literal, so three tests in the "Create stack
from Git" group failed at the helper's first assertion.

Update the assertion to match the shipped title and refresh the two
stale references in docs/features/stack-management.mdx so the docs
stay in sync with the UI copy.

* test(e2e): use dialog accessible name in openCreateStackDialog helper

The previous helper used getByText('New stack') which matched two
elements: the dialog title h2 and the sr-only description (which
starts "Create a new stack: empty, ..." and contains the substring).
Playwright fails with a strict mode violation.

Switch to getByRole('dialog', { name: 'New stack' }), which asserts
the dialog by its accessible name (provided by DialogTitle via the
Radix aria-labelledby wiring). One match, more precise, and
independent of any future description copy.
2026-05-04 11:17:29 -04:00
Anso 6986b927e3 feat(stacks): per-service start/stop/restart lifecycle actions (#778)
* feat(stacks): add per-service start/stop/restart lifecycle routes

Adds POST /:stackName/services/:serviceName/{start,stop,restart} routes
that operate on containers belonging to a single Compose service, using
the same Engine API pattern as the existing stack-level lifecycle routes.
Includes isValidServiceName validator and audit-summary entries for the
three new paths.

* test(stacks): add per-service action route tests

* test(stacks): fix test quality issues in service action tests

* feat(stacks): add per-service lifecycle menu to container cards

* fix(stacks): handle paused container state in service action menu

* docs(stacks): add per-service lifecycle actions documentation

* docs(stacks): add validation screenshots for per-service lifecycle actions
2026-04-25 17:26:04 -04:00
Anso 9e41d5e6b8 feat(stack-view): anatomy panel replaces always-open yaml (#690)
* feat(stack-view): anatomy panel replaces always-open yaml

Introduce StackAnatomyPanel as the default right-column surface of the stack
view, replacing the always-visible Monaco YAML editor. The panel parses
compose.yaml client-side and surfaces services, ports, volumes, restart
policy, env file with missing-variable detection, network, and git/local
source in a compact, read-only format. An inline banner surfaces pending
image updates with risk classification (patch/minor/major) and an apply
button gated on edit permissions.

Compose editing remains one click away: the anatomy header exposes an
"edit compose.yaml" toggle that slides the Monaco editor into the same
right-column slot with the full tabs, Git Source, Save, and Save & Deploy
toolbar. Closing the editor discards unsaved changes and restores the
anatomy view.

The overall stack view is now a two-column grid: left column stacks the
identity header, per-container health strip, and logs viewer; right
column holds the anatomy panel or the Monaco editor. Missing-variable
detection covers the full compose interpolation grammar, including
defaulted (:- / -) and required (:? / ?) modifiers.

* fix(stack-view): restore Git Source accessible name on anatomy source row

The anatomy panel's source row is the primary Git Source control on the
default stack view. Without an explicit accessible name, screen readers
announced only the raw git ref or 'local', and role-based locators could
not target it.
2026-04-19 01:13:33 -04:00
Anso a65a1c0e86 feat(stack-view): per-container health strip and structured logs viewer (#689)
* feat(stack-view): per-container health strip and structured logs viewer

Replaces the flat container list with a per-container health strip showing
healthcheck state, uptime, port mapping with an open-app link, and live
cpu/memory/network sparklines fed by a 60-sample ring buffer on the stats
WebSocket.

Adds a structured logs viewer that parses docker timestamps (emitted by the
-t flag on the logs stream) and classifies each line by level. Rows render
as a DOM grid with filter pills (all / info / warn / err with count),
following indicator, and plain-text download. A segmented toggle switches
between the structured viewer and the original xterm view; the choice is
persisted in localStorage.

* fix(stack-view): disable no-control-regex for ANSI escape pattern

ANSI escape sequences start with ESC (0x1B), which is a control character.
The regex is intentional and cannot be rewritten without it.
2026-04-19 00:17:51 -04:00
Anso 82aabfe64c feat(stack-view): identity header with health state and action hierarchy (#688)
* feat(stack-view): identity header with health state and action hierarchy

Redesign the stack view header around three questions: what is this, is it
healthy, what does it do. Surface Docker healthcheck state, primary image
tag, and image digest; group actions by frequency.

- Backend: extend /api/stacks/:name/containers with healthStatus (healthy /
  unhealthy / starting / none), Image, and ImageID by inspecting each
  container in parallel.
- Frontend: replace flat CardHeader with breadcrumb, italic serif title,
  colored state pill with pulse, and a mono image/digest line with a
  one-click copy button for the full digest.
- Frontend: action hierarchy - primary cyan Restart/Start, outline Stop and
  Update, and an overflow menu for Rollback, Scan config, and Delete.
- Docs: new Stack header section and updated controlling-a-running-stack
  tables showing primary/secondary/overflow grouping.

* test(e2e): open overflow menu to reach stack Delete action

Destructive actions now live under the stack toolbar overflow menu rather
than as a flat top-level button, so the delete flow must click More actions
before selecting the Delete menu item.
2026-04-18 23:38:56 -04:00
Anso b2f341b43d feat: docker run to compose converter (#623)
* feat(convert): harden /api/convert endpoint with auth, validation, and tests

Applies authMiddleware to the docker run to compose endpoint, validates
that the payload is a non-empty string within an 8192 character budget,
rejects inputs containing null bytes, and wraps composerize in a
try/catch that surfaces a 422 with a clear message when the library
cannot produce a services block. Adds a Vitest suite covering the auth
gate, happy path, common flag coverage, boundary and null byte
placement variants, and malformed command handling.

* feat(editor): add From Docker Run tab to create stack dialog

Introduces a third tab in the Create New Stack dialog that accepts a
docker run command, calls the converter endpoint, and previews the
returned compose YAML before writing it to a new stack directory. Uses
the defensive toast pattern, clears the stale preview when the input
changes, and rolls back the empty stack directory if saving the
converted YAML fails so the user never ends up with an orphan stack.

* docs(stack-management): document docker run to compose converter

Adds a Convert from a docker run command section to the stack
management page covering how to use the new tab, the list of supported
flags, and troubleshooting for unparseable inputs. Screenshots show the
empty tab, a successful conversion with the compose preview, the
resulting stack in the editor, and the error toast surfaced when the
input cannot be converted. Appends a matching entry to the
troubleshooting page.
2026-04-15 20:23:12 -04:00
Anso f516275834 refactor(licensing): replace Pro branding with Community/Skipper/Admiral tiers (#375)
Eliminate all references to "Pro" across backend, frontend, and docs.
Internal tier value renamed from 'pro' to 'paid'; user-facing text now
uses the thematic tier names (Community, Skipper, Admiral).

- Rename LicenseTier 'pro' to 'paid' in backend and frontend types
- Rename requirePro guard to requirePaid, error code PRO_REQUIRED to PAID_REQUIRED
- Rename ProGate.tsx to PaidGate.tsx with updated copy
- Fix: trial users can now see upgrade/purchase cards in Settings
- Update all docs and openapi.yaml to use correct tier names
2026-04-05 05:59:36 -04:00
Anso a1804c8fbe docs: comprehensive review and refresh of all documentation (#374)
* docs: comprehensive review and refresh of all documentation pages

Reviewed every doc page against the current app state after the v0.38
dashboard redesign. Updated content, fixed inaccuracies, and refreshed
all screenshots at 1920x1080.

Pages updated:
- introduction: expanded feature list to 25 items across 6 subsections
- quickstart: fixed docker run command (Docker Hub, auto JWT, COMPOSE_DIR)
- configuration: replaced personal paths with generic /home/user/docker
- sso-quickstart: fixed Settings navigation reference
- sso: added SSO_LDAP_DISPLAY_NAME env var
- overview: added 8 missing feature sections (labels, API tokens, schedules, etc.)
- dashboard: complete rewrite for new health bar, gauges, stack health table
- stack-management: updated for UP/DN indicators, rollback button, split actions
- editor: rewritten for two-column layout, inline stats, embedded terminal
- resources: updated Quick Clean docs, added network topology and inspect
- app-store: updated categories, deploy sheet details, permission gate, settings
- openapi.yaml: fixed YAML parsing error on line 1831

Screenshots refreshed: 14 images across 6 feature areas.

* docs: review and update observability, console, multi-node, and compatibility pages

- Global Observability: fix log format fields, add download button docs,
  split display limits into memory buffer vs rendered rows, correct
  settings labels
- Host Console: remove internal implementation details per security
  docs policy, add stack directory behavior, expand header bar docs,
  remove unverified scrollback claim
- Multi-Node: add Compose Directory field, document connection test
  details panel, fix edit/delete node behavior, simplify token security
  section, remove internal details
- Node Compatibility: add missing self-update capability, remove
  internal endpoint paths and cache TTL, move from Features to
  Reference group in navigation
- Refresh all screenshots for the redesigned UI (7 images)

* docs: review and refresh fleet, remote updates, labels, alerts, routing, and webhooks pages

- Fleet View: added node updates modal, container detail, version/update/critical badges, Tags filter
- Remote Updates: removed internal details, added capability cross-link, fast polling
- Stack Labels: three creation methods, two assignment methods, 10 colors, bulk actions screenshot
- Alerts & Notifications: fixed metric labels, added notification popover detail, status banner
- Notification Routing: HTTPS requirement, rule card layout, channel terminology fix
- Webhooks: corrected license tier to Admiral, matched action labels to UI, removed internal security details, added local-only note
- Troubleshooting: centralized entries from remote-updates, stack-labels, notification-routing
- Refreshed all screenshots at 1920x1080, removed 11 orphaned images

* docs: review and refresh RBAC, user management, and atomic deployments pages

- RBAC: added missing Auditor role (5th role), updated permission matrix, fixed license tier references, documented username/password validation rules, self-deletion protection
- Atomic Deployments: added "Which operations are protected" section covering webhooks/schedules/app store, removed internal backup path, fixed license tier to Skipper/Admiral
- Screenshots: cropped to dialog element per updated strategic cropping guideline, removed 2 orphaned images

* docs: review and refresh fleet-wide backups and audit log pages

Update fleet-backups page to reflect current inline create form, add
scheduled snapshots section, document the detail view and restore dialog,
expand RBAC table to all five roles. Update audit log page to document
expanded row detail fields, pagination, refresh button, and data retention
screenshot. Replace all screenshots with fresh captures at 1920x720.

* docs: review and refresh API tokens and private registries pages

- API Tokens: clarify Full Admin scope, add Managing tokens section
  with card details, document revocation confirmation dialog, add
  usage tracking to security model, refresh screenshot
- Private Registries: add Managing registries section with card details
  and action buttons, document edit behavior, fix URL auto-fill
  description, remove encryption algorithm name per security policy,
  fix grammar, refresh both screenshots

* docs: review and refresh auto-update policies, scheduled operations, and SSO pages

- Auto-Update Policies: document all 8 table columns, expand action buttons,
  add "All Stacks" wildcard option, fix field labels, add CSV export and
  pagination details, refresh screenshots
- Scheduled Operations: fix System Prune target description, add Task List
  table columns, restructure create dialog fields with action-specific
  annotations, rewrite execution history with column table, refresh screenshots
- SSO: remove encryption algorithm name per security policy, add LDAP and
  OIDC configuration field tables, document provider card controls (Save,
  Test Connection, Remove, Active badge), refresh screenshots
- Move SSO troubleshooting entries to centralized troubleshooting page

* docs: review and refresh licensing & billing page

Update upgrade card feature lists to match actual tier gating (Skipper:
fleet view, webhooks, labels, atomic deployments, backups, auto-update
policies; Admiral: scoped RBAC, SSO, audit log, host console, API
tokens, private registries, scheduled operations). Add flex layout to
align upgrade card buttons at the bottom. Replace stale screenshot with
fresh community and active license captures. Add feature breakdown
subsection and profile menu billing shortcut to docs.

* docs: review and refresh settings reference and security advisories pages

Settings Reference: add 5 missing sections (SSO, API Tokens, Registries,
Labels, Routing), expand Users from 2 to 5 roles, fix System Limits and
Developer field labels to match UI, restructure Developer into Streaming
and Data Retention sub-tables, update App Store and Support sections,
refresh overview screenshot.

Security Advisories: restructure into versioned sections (v0.25.x
hardening and v0.19-v0.24 CVE remediation), expand from 3 bullet points
to 10 specific improvements, fix GitHub URL from SaelixCode to AnsoCode,
redact internal details per security docs policy.

Remove "Sencho Pro" product name from all three pages, replaced with
tier names (Community, Skipper, Admiral).

* docs: review and refresh troubleshooting page, remove architecture and development guides

- Rewrote forgotten password section to remove exposed SQL and table names
- Updated all Settings navigation paths to Profile > Settings > X
- Fixed network topology from "tab" to "view mode", added Pro license note
- Updated Prune Networks to current "Prune Dead Networks" label
- Corrected update check cooldown from vague to 2 minutes
- Consolidated two network creation error sections into one
- Removed hardcoded version reference (v0.34.0)
- Replaced em dashes throughout
- Deleted architecture.mdx (exposes internal implementation details)
- Deleted development.mdx (contributor guide belongs in repo, not public docs)
- Removed both pages from docs.json navigation

* docs: review and refresh operations pages (backup, upgrade, self-hosting, troubleshooting)

Backup & Restore:
- Added missing encryption.key to all backup/restore procedures
- Added Warning about restoring db without matching encryption key
- Added cross-reference to Fleet-Wide Backups for paid tiers
- Removed false claim about no built-in backup scheduler
- Updated cron example to include encryption key copy

Upgrading Sencho:
- Removed internal migration details (table names, column specs, encryption algorithm)
- Replaced with high-level migration summary per security docs policy
- Added encryption.key to pre-upgrade backup command
- Updated version pinning example from 0.25.3 to 0.38.0
- Added Remote Updates cross-reference for Skipper/Admiral users

Self-Hosting Best Practices:
- Removed JWT_SECRET from env var table (auto-generated, not an env var)
- Removed PORT from env var table (hardcoded to 3000, not configurable)
- Added API_RATE_LIMIT to env var table (actually exists in code)
- Fixed listen port description from "configurable" to "fixed"
- Updated resource recommendations based on measured footprint audit
- Removed su-exec reference (internal implementation detail)
- Upgraded data directory Note to Warning with file names

Troubleshooting:
- Fixed "Pro features" heading to "Paid features" with correct tier names
2026-04-04 21:32:51 -04:00
Anso 55d3b8ca1d feat(stacks): state-aware sidebar context menu and Open App action (#368)
* feat(stacks): state-aware sidebar context menu and Open App action

- Context menu now adapts to stack state: running stacks show
  Stop/Restart/Update, stopped stacks show Deploy only
- Added "Open App" shortcut to open a stack's web UI directly
  from the sidebar (visible when running with a published port)
- Backend bulk status endpoint enriched with mainPort detection
- Reduced manual image update check cooldown from 10 to 2 minutes
- Rate limit error message now derives from the configured constant

* fix(stacks): use const for bulkPorts (prefer-const lint)
2026-04-03 21:41:01 -04:00
Anso 6f7415351f feat(stack-management): add scan stacks folder button (#332)
* feat(stack-management): add scan stacks folder button to detect manually-placed compose files

Users who place Docker Compose files directly into the stacks directory
(via SCP, file manager, etc.) can now click the folder-search icon next
to "Create Stack" to immediately discover and surface those stacks in
the sidebar without a full page reload.

* docs(troubleshooting): add scan stacks folder troubleshooting section

Covers common issues: compose file not in subdirectory, unrecognized
filenames, empty directories, and already-tracked stacks.
2026-04-02 12:17:19 -04:00
Anso 116f15dae9 fix(stacks): resolve permission denied error on stack deletion (#261)
* fix(stacks): resolve permission denied error when deleting stacks with root-owned files

When Docker Compose creates files as root inside a stack directory, the
non-root Sencho process cannot remove them. This adds a Docker-based
fallback: if fsPromises.rm fails with EACCES/EPERM, Sencho spawns a
short-lived Alpine container to clean up the root-owned files.

Also enhances docker compose down with --volumes --remove-orphans to let
Docker clean up its own resources before filesystem deletion.

* docs: clarify that pre-existing root-owned stacks can be deleted

* fix(stacks): include Docker stderr in fallback deletion error message

Fixes CI lint failure: 'stderr' was assigned but never read in
forceDeleteViaDocker(). Now surfaces Docker stderr output in the error
message when the fallback cleanup fails.
2026-03-29 21:32:05 -04:00
Anso 31e1795af0 feat(scheduled-ops): add scheduled operations for Team Pro users (#231)
Adds the ability to schedule recurring Docker operations (stack restarts,
fleet snapshots, system prunes) via cron expressions with full execution
history logging. Includes Run Now for on-demand execution.
2026-03-28 23:56:37 -04:00
Anso dda1671e5a feat: stack context menu, tier icons, centered logo & support (#194)
* chore: remove CLAUDE.md and MANUAL_STEPS.md from git tracking

These files contain internal project instructions that should not be
public. Both are now in .gitignore to prevent re-tracking.

* chore: remove build output and generated files from git tracking

Remove backend/public/ (frontend build artifacts) and backend/cookies.txt
from tracking. Add demo-video/ and website/ to .gitignore.

* docs: align mintlify theme with website and app branding

Update colors to brand cyan, add Geist font, dark mode default,
gradient background, navbar with GitHub link and CTA, and footer
matching the website structure.

* chore: add shadcn MCP server configuration

* feat: add stack context menu, tier icons, centered logo, and support section

- Add right-click context menu on sidebar stacks with full actions
  (alerts, check updates, deploy, stop, restart, update, delete)
- Mirror same actions in the three-dot dropdown menu
- Create TierBadge component with Globe/Crown/Users icons for
  Community/Pro/Team tiers, replacing ProBadge across the app
- Center logo and app name in sidebar header
- Add tiered Support section in Settings with docs, GitHub issues,
  and tier-appropriate email support channels
- Remove duplicate links from About section

* docs: update changelog and stack management docs for context menu
2026-03-27 17:10:47 -04:00
Anso 32a7d53b2b feat: RBAC, atomic deployments, fleet backups, and licensing (Pro) (#185)
* feat: add RBAC viewer accounts, atomic deployments, and fleet-wide backups (Pro)

Introduces three Pro-tier features:

- RBAC: Multi-user system with admin/viewer roles, user management UI,
  automatic migration from single-admin credentials, viewer restrictions
  across the entire UI (read-only editor, hidden action buttons)

- Atomic Deployments: Pre-deploy file backup to .sencho-backup/, automatic
  rollback on health probe failure, manual rollback button, health probes
  added to stack updates, webhook-triggered deploys use atomic rollback

- Fleet-Wide Backups: Point-in-time snapshots of compose files across all
  nodes (local + remote), stored centrally in SQLite, per-stack restore
  with optional redeploy, graceful handling of offline nodes

* fix(settings): use correct ProGate prop name in UsersSection

* fix(settings): remove unused isPro prop from UsersSection

* fix(auth): fetch user info after login and setup so isAdmin is set correctly

* feat(pricing): revise pricing strategy and enforce variant-based seat limits

Raise Personal Pro from $49/yr to $69/yr with 3 viewer seats (up from 1).
Add $15/mo billing option for Team Pro. Mark lifetime pricing as a
90-day early-adopter offer. Store Lemon Squeezy variant_name on
activation/validation and enforce seat limits server-side per variant.

* feat(licensing): add Lemon Squeezy checkout, webhook, and billing portal integration

Server-side checkout URL generation (POST /api/checkout) with admin email
pre-fill and instance_id custom data. HMAC-SHA256 verified webhook endpoint
(POST /api/webhooks/lemonsqueezy) handling order, subscription, and payment
lifecycle events for automatic license activation. Customer billing portal
link stored from webhook events and exposed via GET /api/billing/portal.
In-app checkout buttons in Settings with manual license key fallback.

* fix(licensing): exempt Lemon Squeezy webhook from auth middleware

The catch-all auth middleware on /api/* was blocking the public webhook
endpoint. Added /webhooks/lemonsqueezy to the exemption list alongside
/auth/* and /webhooks/:id/trigger.

* feat(pricing): update pricing to final live rates

Personal Pro: $7.99/month, $69.99/year, $249 lifetime.
Team Pro: $49.99/month, $499.99/year, $1,499 lifetime.
Added personal_monthly checkout variant across backend, frontend, and website.

* refactor(licensing): remove server-side checkout/webhook for self-hosted model

Sencho is self-hosted — each user runs their own instance, so there is
no central server to receive webhooks or hold the store API key. Replaced
in-app checkout buttons with a "View Pricing" redirect to sencho.io and
kept manual license key activation as the primary flow.

- Delete LemonSqueezyService (checkout, webhook, HMAC verification)
- Remove POST /api/checkout, GET /api/billing/portal, POST /api/webhooks/lemonsqueezy
- Remove raw body parser and auth exemption for webhook route
- Remove all LEMONSQUEEZY_* env vars from .env.example
- Replace checkout buttons in SettingsModal with single "View Pricing" button
- Simplify LicenseContext checkout to open sencho.io pricing page
- Update licensing docs to reflect website-based purchase flow

* chore: normalize em-dashes to hyphens across codebase (linter)

* chore: remove accidentally tracked directories from index
2026-03-26 21:58:24 -04:00
SaelixCode 98910c4117 docs: bootstrap user-facing documentation from codebase audit
- Add 5 new Tier 1 doc pages: configuration, stack-management, editor,
  multi-node, and alerts-notifications
- Update introduction, quickstart, and features/overview to reflect
  current feature set and link to new pages
- Restructure mint.json with Getting Started / Features / Reference /
  Operations navigation groups
- Add Playwright-captured screenshots for all major UI screens
2026-03-22 22:39:06 -04:00