* feat(scheduler): add auto_backup, auto_stop, auto_down, auto_start actions and delete_after_run one-shot mode
Extends the scheduler with four new stack-targeted actions:
- auto_backup: backs up stack compose files and .env using the existing
FileSystemService.backupStackFiles primitive
- auto_stop: runs compose stop (containers preserved)
- auto_down: runs compose down (containers removed)
- auto_start: runs compose up -d via deployStack (universal start for both
stopped and down stacks)
Adds delete_after_run boolean column to scheduled_tasks. When enabled,
the task self-deletes after its first successful execution; failures keep
the task so the user can debug and retry.
All four new actions gate at Admiral tier, consistent with restart/snapshot/prune.
Migration is idempotent (maybeAddCol).
* docs(scheduler): update scheduled-operations doc with new lifecycle actions and delete-after-run
Adds the four new actions (Backup Stack Files, Stop Stack, Take Stack Down,
Start Stack) to the action table. Documents the delete-after-run one-shot mode
with its success-only deletion semantics. Adds the Stack Lifecycle Scheduling
section explaining stop-vs-down semantics and the local-execution boundary.
Adds three troubleshooting entries: auto-start on a missing compose folder,
auto-backup single-slot overwrite by design, and one-shot task disappearing
after successful run.
Updates the timeline description from four to five lanes. Refreshes
screenshots to show the new dialog layout with the Lifecycle lane visible.
Allow a scheduled task with action='update' and target_type='fleet'
to update every eligible stack on a node in a single schedule entry.
The executor respects each stack's per-stack auto-update policy via a
single batch query, skipping stacks that have opted out. For remote
nodes the request proxies to the remote Sencho instance, which already
enforces the same policy in its /api/auto-update/execute endpoint.
Backend route validation now accepts update+fleet as a valid combo
(previously only update+stack was allowed) and requires node_id.
Frontend adds an "Auto-update All Stacks" option to the scheduled-task
creation form with a node selector and descriptive helper text.
Right-clicking a stack or opening its 3-dot menu now shows a
'Schedule task' entry in the lifecycle group (visible to paid tiers).
Clicking it navigates to Scheduled Operations and opens the New
Schedule dialog pre-filled with the stack name and active node,
removing the need to navigate there manually and re-enter the target.
- Added openScheduleTask to StackMenuCtx; wired in buildMenuCtx using
the active node from NodeContext
- Extended ScheduledOperationsView with optional prefill/onPrefillConsumed
props; a ref-guarded effect calls openCreate() with the prefill data
- openCreate refactored to accept an optional prefill arg, removing the
duplication between the effect and the existing 'New Schedule' button
* chore: ignore local design-system references
Adds frontend/DESIGN.md and .design-bundle/ to .gitignore so the
design-system reference and the audit handoff bundle stay local
to each contributor's machine.
* feat(ui): replace Switch with TogglePill per design audit
Adds a mono-uppercase ON/OFF pill that replaces the sliding Switch
across every call site. The pill uses role="switch" plus aria-checked,
tints success-green when on and neutral card-tone when off, and holds
a stable 60px min-width so the label flip does not shift layout. It
reinforces the tracked-mono uppercase pattern already used for kicker
labels and table column headers, and echoes the UP/DN status language
of the sidebar stack list.
- Add TogglePill component (frontend/src/components/ui/toggle-pill.tsx)
- Replace 17 Switch call sites across 10 files (settings sections,
SSO providers, scheduled operations, network topology, resources,
stack auto-heal)
- Remove unused Switch wrapper and radix primitive
- Drop the @radix-ui/react-switch dependency
* feat(backend): add stack update-preview endpoint for readiness board
Adds GET /api/stacks/:stackName/update-preview that returns per-image
semver diff, bump classification, and a stack-level summary powering
the Auto-Update readiness board.
- New UpdatePreviewService parses compose images, inspects local
digests, fetches remote digests and tag lists, and finds the
highest compatible semver tag.
- Major bumps are flagged blocked until human review; unknown bumps
rank below real semver so they cannot mask a major.
- Rollback target is reconstructed through parseImageRef to preserve
registry ports and drop the Docker Hub library/ prefix.
- Registry helpers (httpGet, auth token, digest, tag list, ref parse)
are extracted into registry-api.ts and shared with ImageUpdateService.
- 28 Vitest cases cover parse, selection, bump math, digest rebuilds,
blocked policy, and rollback target construction.
* feat(schedules): next-24h timeline, merge auto-update crud, add readiness board
Replace the flat task table with a Timeline view as the default, showing the
next 24 hours of scheduled work across four lanes (Restart, Update, Scan,
Prune) with a live now rail and per-firing pills. The All tasks tab preserves
the existing CRUD surface.
Merge Auto-update Stack into Schedules as a first-class action and replace the
standalone Auto-Update Policies view with a per-stack Readiness board that
surfaces version diffs, risk tags, changelog previews, and rollback targets
sourced from the stack update-preview endpoint.
Enrich scheduled vulnerability scan completion notifications with
per-severity CVE counts so recipients can triage from the message body
alone. Expose the scan action in the schedule creation UI, require an
explicit node_id, and harden fire-and-forget alert dispatches so a
failing webhook cannot crash the scheduler.
Notification body now reports scanned/skipped/failed counts plus
critical/high/medium totals aggregated across fresh and cached scans,
reflecting the current node posture rather than only what was newly
scanned on this run.
* fix(scheduler): clean up stale runs on startup and auto-disable invalid cron tasks
- Add markStaleRunsAsFailed() bulk DB method with status index
- Clean up orphaned 'running' records on scheduler startup
- Auto-disable tasks when cron expression becomes invalid at execution time
- Promote CRUD debug logs to standard logs for scheduled task admin actions
- Add diagnostic logging for task pre-checks, action timing, and prune fallback
* refactor(scheduling): extract shared types and fix design system violations
- Extract ScheduledTask, TaskRun, NodeOption to shared types file
- Extract getCronDescription and formatTimestamp to shared utilities
- Fix formatTimestamp falsy-zero null check
- Tighten last_status type to 'success' | 'failure' | null
- Add strokeWidth={1.5} to all action icons per design system
- Add sr-only DialogDescription for accessibility
- Wrap Sheet run history in ScrollArea
- Fix delete button styling to match design system pattern
- Change manual trigger toast from "executed" to "triggered"
* test(scheduler): add tests for snapshot, remote update, stale cleanup, and cron invalidation
- Add stale run cleanup tests (bulk markStaleRunsAsFailed, logging)
- Add cron invalidation test (auto-disable, error message)
- Add executeSnapshot tests (fleet capture, empty stacks)
- Add executeUpdateRemote tests (proxy success, remote error)
- Document stale run cleanup and cron auto-disable in troubleshooting docs
* fix(schedules): filter auto-update policies from Scheduled Operations view
Auto-update policies (action=update) were appearing in both the
Auto-Update tab and the Scheduled Operations tab. Added server-side
action/exclude_action query params to GET /api/scheduled-tasks.
ScheduledOperationsView now requests ?exclude_action=update and
AutoUpdatePoliciesView requests ?action=update, so each view only
shows its relevant tasks.
* fix(schedules): use typeof guard for query param type safety
Express can parse ?action[]=x as an array. Use typeof === 'string'
guard instead of unsafe type assertion.
* feat(labels): add stack_labels schema and DatabaseService CRUD methods
* feat(labels): add label CRUD, assignment, and bulk action API routes
* feat(labels): add oklch label color palette for light and dark themes
* feat(labels): add LabelPill and LabelDot reusable components
* feat(labels): add LabelAssignPopover component for inline label management
* feat(labels): add label pill bar, label dots, and label assignment to sidebar
* feat(labels): add label filtering and label dots to fleet view
* feat(labels): add label-scoped bulk actions (deploy/stop/restart all)
* docs: add Stack Labels feature documentation
* fix(labels): use context menu sub-menu for label assignment and add settings integration
Replace broken Popover-inside-ContextMenu pattern with native Radix
ContextMenuSub for reliable label toggling on right-click. Wrap
ContextMenuSubContent in a Portal to prevent overflow clipping. Add
"Manage labels..." item that opens Settings directly to Labels section.
Fix close button overlap in LabelsSection header. Add LabelsSection
settings component with full CRUD, assignment counts, and ProGate.
Add initialSection prop to SettingsModal for deep-linking. Include
screenshots for documentation.
* docs: update stack labels documentation with screenshots and corrected instructions
* fix(labels): address security and quality issues from code review
- Add NaN validation on parseInt(req.params.id) in label routes
- Scope updateLabel/deleteLabel by nodeId to prevent cross-node IDOR
- Validate labelIds belong to correct node in setStackLabels
- Add requireAdmin check on bulk action endpoint
- Replace error: any with error: unknown and proper narrowing
- Remove unused Label import from index.ts
- Remove unused isPro prop from LabelsSection
- Add strokeWidth={1.5} to Check icons per design system
* chore: update CHANGELOG with stack labels feature
* feat(nodes): add per-node scheduling and update visibility
Add Schedules and Updates columns to the Nodes table showing active
task counts, next run times, and auto-update status per node. A calendar
action button navigates to filtered schedule/auto-update views.
Backend changes:
- Add node_id to stack_update_status table (migration + unique index)
- Cascade cleanup on node deletion (scheduled_tasks + update status)
- Pre-check target node existence/status before executing scheduled tasks
- New GET /api/nodes/scheduling-summary endpoint
- New GET /api/image-updates/fleet endpoint with 2-minute cache
- Parallelize remote node fetches with Promise.allSettled
- Wrap deleteNode cascade in a transaction
Frontend changes:
- NodeManager: Schedules/Updates columns with summary data fetch
- EditorLayout: sencho-navigate event listener for cross-component nav
- ScheduledOperationsView/AutoUpdatePoliciesView: filterNodeId prop,
filter bar UI, pre-selected node in create dialog
* feat(auto-update): add auto-update policies and fix image update detection
Auto-Update Policies (Skipper+ tier):
- New scheduled task action type 'update' for check-then-update flow
- Dedicated AutoUpdatePoliciesView with CRUD, cron presets, and run history
- Conditional tier gating: Skipper gets auto-update, Admiral gets full scheduled ops
- Backend executeUpdate: checks digests, pulls only if newer, atomic redeploy
Image Update Detection fixes (all tiers):
- Fix stack name key mismatch: use working_dir label instead of project label
- Add 5-minute periodic frontend polling for background check results
- Replace fixed 3s timeout with polling-based manual refresh via /api/image-updates/status
- Clear update status after successful stack update
* fix(ui): remove Skipper tier badge from Auto-Update Policies header
* fix(ui): remove auto-update action from Scheduled Operations view
Admiral users have a dedicated Auto-Update view — showing update tasks
in Scheduled Operations too was confusing duplication. Each view now
owns a distinct, non-overlapping set of action types.
* fix(auto-update): fix node-stack linking and add All Stacks option
- Stack dropdown now re-fetches when node selection changes using
fetchForNode, and resets the selected stack
- Node selector moved above stack selector with stack disabled until
a node is picked
- Added "All Stacks" wildcard option that checks and updates every
stack on the selected node
- Backend executeUpdate refactored to iterate over all stacks when
target_id is "*", with per-stack error isolation
* refactor(ui): replace Select dropdowns with searchable Combobox component
Add a reusable Combobox component with inline search and use it for
Node/Stack selectors in both Auto-Update Policies and Scheduled
Operations dialogs. Also fixes node-stack linking bug where changing
node didn't update the stack list.
* fix(ui): resolve CI TypeScript errors in Combobox and ScheduledOperationsView
Add missing searchPlaceholder prop to ComboboxProps interface and remove
dead 'update' action filter that conflicted with the narrowed type union.
* fix(ui): use Geist Sans font in toast component
The toast renders via React portal on document.body, bypassing the app's
font inheritance. Add explicit font-family declaration using var(--font-sans)
to match Sencho's design system.
* refactor(ui): replace Sonner with custom Sera UI-inspired toast system
Replace the Sonner toast library with a custom implementation inspired by
Sera UI's glassmorphism design. The new system uses an external store pattern
with useSyncExternalStore for React integration, keeping the same
toast.success()/error()/warning() API so all 172 call sites required only
an import path change.
Key changes:
- New toast-store.ts: singleton store with identical API to Sonner
- New toast.tsx: Sera UI-faithful Notification component with Framer Motion
animations, frosted glass (backdrop-blur-xl), type gradient overlays,
animated progress bar (green→blue→sky gradient), and hover:scale-105
- Removed sonner and next-themes dependencies
- Rewired all 19 consumer files to import from the new store
* fix(ui): resolve ESLint errors in toast system
- Use const for listeners Set (prefer-const)
- Initialize startRef with 0 instead of Date.now() to satisfy
react-hooks/purity rule, set actual value inside useEffect
- Dispatch error alerts via NotificationService when scheduled tasks fail,
with info-level recovery notifications when a previously-failing task succeeds
- Per-service restart targeting: scheduled stack restarts can target specific
services instead of restarting the entire stack
- Prune label filter: scheduled prune operations can be scoped to resources
matching a specific Docker label
- CSV export button in the execution history panel for one-click download
- Fix: prune_targets was silently dropped on task creation (missing in INSERT)
* feat(ui): add glassmorphism design tokens and utility classes
Introduce glass design system foundation: translucent oklch color
variables for both light and dark themes, glass/glass-border/glass-highlight
tokens, semantic status colors (success/warning/info), .glass and
.glass-strong utility classes with backdrop-filter, reduced shadow
values, and standardized spring animation presets in lib/motion.ts.
* feat(ui): apply glass treatment to core components
Update card, dialog, input, button, popover, sheet, tooltip,
dropdown-menu, context-menu, select, alert-dialog, and tabs
components with glassmorphism styling: translucent backgrounds via
new CSS variables, backdrop-blur layers, glass-border luminous
edges, and glass-highlight hover states.
* refactor(settings): decompose Settings Modal into section components
Extract 10 inline sections from the 1,987-line SettingsModal into
dedicated files under components/settings/. Introduce section
registry pattern replacing 14 conditional blocks. Add shared types,
sidebar navigation grouping with separators, glass treatment on
sidebar and nav buttons, and responsive modal height. SettingsModal
shell shrinks to ~380 lines.
* refactor(ui): unify all tabs to animate-ui TabsHighlight with glass styling
Migrate 4 tab instances (EditorLayout, FleetView, ResourcesView,
NotificationsSection) from inconsistent patterns (manual layoutId,
underline border-b-2, default fade) to the shared TabsHighlight
primitive with glass-highlight indicator and springs.snappy
transition. Standardize EditorLayout nav highlight spring config,
apply glass-highlight to sidebar stack list hover/active states,
and update mobile nav styling.
* refactor(ui): migrate hardcoded colors to semantic CSS variables
Replace hardcoded Tailwind color classes across ~19 component files
with semantic CSS variable classes: emerald/green to success,
orange/amber to warning, blue to info. Preserves brand/decorative
colors (Crown amber, Admiral blue). Enables consistent theming of
status indicators across the entire application.
* refactor(ui): Linear dark precision aesthetic — solid surfaces, depth cues, text hierarchy
Replace glassmorphism with Linear.app-inspired design: solid surface tokens
(card #111111, sidebar #0d0d0d, root #0a0a0a), backdrop-blur restricted to
floating overlays only (blur(10px) saturate(1.15)), desaturated teal accent,
font-weight 500 everywhere, monochrome chart palette, and three depth cues:
root ambient glow, luminous card top-edge, steep text brightness ramp.
* refactor(ui): precision polish — fix muddy dark, snowblind light, add design anchors
- Replace 34 hardcoded rgba values with theme-aware stat-* CSS tokens
- Fix light theme: solid white cards, off-white background, readable text
- Add card-border tokens with sharper directional lighting (top edge 2x)
- Add chart-grid/chart-tick tokens for theme-aware axis rendering
- Upgrade body glow: teal-tinted (dark), warm amber (light)
- Terminal-inspired sidebar: monospaced UP/DN status codes, Geist Mono
- Add tabular-nums to stat values to prevent layout jitter
- Light mode cards get shadow-sm for depth against off-white background
* refactor(ui): Linear materiality pass — ghosted nav, translucent sidebar, font unity
- De-escalate Delete button from solid destructive to ghost with hover fill
- Make sidebar translucent (80% opacity + backdrop-blur) so body glow bleeds through
- Bump dark nav accent to 0.07 for ghosted backlit selection
- Unify all terminal/editor fonts to Geist Mono (was JetBrains/Consolas mix)
- Add Monaco editor fontFamily for YAML/env editing consistency
- Add threshold-based color to Host RAM and Host Disk stat values (warn/crit)
* refactor(ui): material simulation — inherent depth, layer separation, recessed terminal
- Bump dark background 0.065→0.08, card surfaces 0.10→0.12 for 4% layer separation
- Add card-bevel token (inset top shimmer) for permanent structural depth
- Add button-inner-glow token for physical key feel on outline buttons
- Recess terminal with inset shadow and dimmed label
- Reduce action icon strokeWidth to 1.5 for refined industrial feel
- Add teal LED backlight bar on active nav item via blur pseudo-element
* fix(ui): parse usagePercent string to number for getValueColor
usagePercent is typed as string in SystemStats but getValueColor
expects number, causing TS2345 in CI builds.
- Fix "Run Now" audit log showing "Created scheduled task" instead of "Triggered scheduled task" by adding wildcard-based route matching with specificity sorting
- Add triggered_by column to track whether runs were started by the scheduler or manually via Run Now
- Add configurable prune targets (containers, images, networks, volumes) with checkbox UI
- Add pagination to execution history with offset-based navigation
- Document Run Now behavior on disabled tasks and add screenshots
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.