Commit Graph

13 Commits

Author SHA1 Message Date
Anso c0a252026d feat(appearance): add theme, accent, contrast, and typography personalization (#1307)
* feat(appearance): add theme, accent, contrast, and typography personalization

Expand Settings to Appearance into a full personalization surface and add a
quick switcher to the top bar (the palette button between search and
notifications). Choices are saved to the browser, sync across tabs, and apply
before first paint so there is no flash on reload.

- Themes: Dim (the default raised charcoal), OLED true black, Light, and Auto
  (follows the OS and re-resolves live when it flips).
- Accent: an eight-hue wheel (cyan default) that drives the one data color
  across charts, rails, focus rings, active states, and the ambient glow.
- Fine-tune sliders: a master Contrast that spreads page, ink, and borders
  together, plus Border brightness and Ambient glow.
- Typography: swappable interface (Geist / IBM Plex Sans / Hanken Grotesk) and
  data (Geist Mono / IBM Plex Mono / Fira Code) faces, and a text-size control
  (continuous slider in Settings, S/M/L/XL presets in the popover, kept in
  sync). The display serif stays locked as the signature face.

Surfaces, borders, and ink derive from per-theme lightness values so the live
knobs scale the whole UI through CSS, and the opaque directional borders read on
every panel including true black. A live preview reflects changes in real time.
Adds a documentation page under Features.

* fix(appearance): keep contrast-driven tokens in gamut and add picker keyboard nav

Clamp the contrast and knob driven surface, border, and ink lightness so the
full slider range stays a valid color. The page now always sits below the card
tone, so page/card separation no longer collapses at high contrast in Light; the
lit border edge never reaches white in Light at the low end of the knobs; and the
OLED and Dim extremes resolve to valid black/white instead of out-of-range values.

Add the radiogroup keyboard model (roving tabindex plus Arrow / Home / End) to the
accent and type pickers through a shared hook, matching the segmented control. One
item is tabbable and arrow keys move focus and selection together.
2026-06-04 01:50:41 -04:00
Anso 13e4edc1e2 fix(ui): close user dropdown after clicking menu actions (#1058)
Clicking Settings, Billing, Documentation, Feedback, or Log Out now
dismisses the popover, matching standard menu UX. The Appearance theme
toggle stays open so users can preview themes without reopening the menu.
2026-05-15 09:00:56 -04:00
Anso f62716f557 refactor(design): align typography, colors, and card surfaces to DESIGN.md (#859)
* refactor(design): align surface tokens to DESIGN.md §2

* refactor(design): canonicalize tracked-mono kickers and display rungs

* refactor(design): collapse to five-slot palette and align card surfaces
2026-05-01 03:01:00 -04:00
Anso eead195529 feat(settings): dress the page to match the audit (#849)
* feat(settings): dress the page to match the audit (cyan rail, italic serif, two-column rows)

Brings the full-page Settings route into the Sencho voice. The page now
opens with a full-width PageMasthead (cyan rail, mono crumb, italic
serif title, contextual stat strip) above a sidebar and main-content
panel, each as a rounded-xl card inset on the dark background.

Sidebar drops the duplicate "Settings" header and the candy tier badges.
Group headers carry mono labels with visible/total counts; gated rows
get a neutral uppercase lock chip and dim. Active rows keep the cyan
2px rail.

Five new primitives (SettingsSection, SettingsField, SettingsCallout,
SettingsActions / SettingsPrimaryButton, TierLockChip) replace the
stacked label-input-help shadcn defaults and the per-section ad-hoc
chrome. AccountSection, AppearanceSection, LicenseSection, SystemSection,
NotificationsSection, DeveloperSection, AppStoreSection, AboutSection,
and SupportSection are migrated to the new layout. The list-driven
sections (Webhooks, Routing, Users, Labels, Security, CloudBackup,
ApiTokens, Registries, NodeManager, SSO) keep their list cards but get
the new chrome and primary CTAs.

Each section can publish contextual stats to the masthead via a small
context channel: 2FA state on Account, plan/trial/renews on License,
edited count on System, channel counts on Notifications, etc.

* refactor(settings): drop react-router-dom and align with DESIGN.md

The Settings page was the only surface using react-router-dom for sub-section
navigation. Every other primary view (Home, Fleet, Resources, App Store,
Schedules, etc.) drives view switching through a single activeView useState in
EditorLayout. This change removes the dependency end-to-end:

- App.tsx drops BrowserRouter
- EditorLayout adds 'settings' to the activeView union; SettingsPage renders
  inside the same flex-1 overflow-y-auto p-6 wrapper as siblings
- UserProfileDropdown receives an onOpenSettings callback instead of
  useNavigate. SettingsPage owns currentSection via props lifted to
  EditorLayout, so cross-component navigation (openLabelManager,
  onManageNodes, ConfigurationStatus rows) can route to a sub-section
- SettingsSidebar items become buttons (no more NavLink); SectionGate's
  redirect-on-invisible falls back through SettingsPage's safeSection memo
- e2e/nodes.spec.ts updates the Nodes selector from link to button role
- react-router-dom removed from package.json + package-lock.json

The visual treatment is brought into alignment with frontend/DESIGN.md,
which was rewritten this week to be the normative extract of the audit:

- PageMasthead: title text-3xl → text-[22px] Section rung italic; kicker
  11px → 10px Label rung; stat label tracking 0.22em → 0.18em; stat value
  font-medium for mono Stat-rung family discipline
- SettingsField helper: mono → sans Body rung 14/22; success tone now uses
  --success green (was incorrectly mapped to brand cyan)
- SettingsCallout: title tracking 0.18em; subtitle Body rung 14px; success
  tone now genuinely uses --success green; new brand tone for promotional
  callouts (Trial CTA, Admiral upgrade) that should read cyan
- SettingsActions: SettingsPrimaryButton renders mono uppercase tracked,
  size sm by default. DESIGN §9.10 requires "small mono uppercase, cyan-
  filled" for every Settings primary CTA
- TierLockChip: 9px → 10px Label rung floor
- SettingsSidebar: group header tracking 0.18em; ⌘K kbd 9px → 10px;
  aside gains text-card-foreground transition-colors per §10 canonical
  card class
- SettingsPage main panel: text-card-foreground transition-colors added;
  uses h-full overflow-auto p-6 to mirror FleetView's wrapper rhythm
- Field rows, section headers, action rows now consume var(--density-*)
  tokens with literal fallbacks so Settings respects the comfortable/
  compact toggle

* fix(e2e): update mfa openAccountSettings to match settings redesign

Settings now opens to the Account section by default when accessed from
the profile dropdown, and the Account section no longer renders an h2
heading element. Update the openAccountSettings helper to open the
correct section and assert on the Password h3 heading that SettingsSection
renders instead.

* test(e2e): fix MFA enrolment assertion after settings redesign

The 2FA enrolment badge was replaced with a kicker/field pattern.
Assert on the 'enrolled' text that the new design renders instead of
the removed Enabled badge.

* test(e2e): fix low-backup-codes warning assertions after settings redesign

Update two assertions in the 'low backup codes warning' test that
referenced UI text removed in the settings redesign:
- '1 backup code remaining' -> '1 remaining' (SettingsField body text)
- 'Regenerate now' button -> callout subtitle text, which uniquely
  identifies the zero-codes error card without hitting strict-mode
  from two identically-labelled Regenerate buttons on the page

* test(e2e): navigate to root before re-opening settings for mock refresh

The settings redesign uses a nested full-page route. Navigating to the
same URL a second time does not remount the component, so AccountSection
retains cached MFA state and the 0-codes branch never fetches. A
page.goto('/') ensures full unmount before the second openAccountSettings
call, so the refreshed mock is actually hit.

* test(e2e): scroll zero-codes callout into view before asserting visibility

The callout sits below the Disable 2FA section in the MFA settings page
and is scrolled out of the clipped content area on initial render.
scrollIntoViewIfNeeded() brings it into the visible viewport before the
toBeVisible assertion.

* test(e2e): scroll Radix ScrollArea viewport for zero-codes callout assertion

The settings page wraps content in a Radix ScrollArea whose Root has
overflow:hidden, so the browser's native scrollIntoView cannot scroll
the inner viewport. Wait for the callout to attach (confirms mock data
loaded), then programmatically set scrollTop on the Radix viewport
element before asserting visibility.

* test(e2e): use toBeAttached for zero-codes callout to avoid Radix clip issue

The callout renders below the Disable 2FA section, outside the visible
clip area of the Radix ScrollArea Root (overflow:hidden) on a standard
viewport. Playwright's visibility check uses the clip intersection, so
toBeVisible() fails even after programmatic scroll. toBeAttached()
confirms the component rendered the warning card for backupCodesRemaining:0
without depending on the element's scroll position.
2026-04-30 19:37:38 -04:00
Anso 9a1c043189 refactor(settings): replace modal with nested full-page route (#848)
* refactor(settings): replace modal with nested full-page route

Settings sections are now URL-addressable at /settings/:sectionId, rendered
nested inside EditorLayout alongside the stack sidebar. Browser back/forward
navigates between sections. Deep links (e.g. /settings/cloud-backup) load
the section directly on hard reload.

- Add react-router-dom v7; BrowserRouter wraps the full app tree
- New SettingsPage (scroll memory, Cmd+K palette), SettingsSidebar (NavLink
  active styling, back-arrow), SectionGate (visibility + tier lock card)
- Rename SectionId 'appstore' to 'app-store' so slug === SectionId
- Decouple SystemSection, DeveloperSection, AppStoreSection from modal-
  passed props; each fetches its own data on mount
- Replace onLabelsChanged prop chain with SENCHO_LABELS_CHANGED window event
- Drop onOpenSettings prop from UserProfileDropdown, HomeDashboard,
  ConfigurationStatus; each calls useNavigate directly
- Delete SettingsModal.tsx

* fix(settings): validate sectionId against registry before property write

Prevents prototype pollution (CodeQL js/remote-property-injection #243).
URL param sectionId is checked against SETTINGS_ITEMS before being used
as a property key on scrollPositionsRef.

* fix(settings): eliminate remote property injection via Map and registry-sourced key

Two-part fix for CodeQL js/remote-property-injection:

1. currentSection is now derived from SETTINGS_ITEMS.find().id (trusted
   registry data) instead of the raw sectionId URL param. The tainted
   string never flows into any property access.

2. scrollPositionsRef uses Map<SectionId, number> with .get()/.set()
   instead of a plain object. Map operations do not write to the prototype
   chain, removing the prototype pollution vector entirely.

* test(e2e): align settings selectors with full-page route

The settings refactor (4475afd) replaced the modal with a nested route.
The new sidebar renders sub-sections as NavLinks (role link, not button)
and adds a "Filter settings" button that collides with the loose
/settings/i regex used in mfa and nodes specs.

- Use exact 'Settings' match for the profile-dropdown menu row
- Switch the Nodes sub-section selector from button to link role
2026-04-30 12:57:02 -04:00
SaelixCode 3da0aa6036 chore: migrate repository URLs from AnsoCode/Sencho to studio-saelix/sencho
Updates all hardcoded GitHub repository references across 21 files:
- package.json: repository URL, bugs URL, homepage, description, author
- CONTRIBUTING.md: bug report template URL
- SECURITY.md: advisory URL, cosign cert-identity regexp
- .github/CODEOWNERS: @AnsoCode -> @studio-saelix/maintainers
- .github/workflows/ci.yml: repositories scope (Sencho -> sencho), docs-sync git URL
- .github/workflows/cla.yml: path-to-document URL
- .github/workflows/docker-publish.yml: cosign verify comment
- frontend/**/*.tsx: issues and changelog links (3 components)
- frontend/public/.well-known/security.txt: Contact and Policy URLs
- security/vex/sencho.openvex.json: @id field
- docs/openapi.yaml: license URL
- docs/docs.json: navbar and footer GitHub links (5 instances)
- docs/security.mdx: advisory and SECURITY.md links
- docs/reference/verifying-images.mdx: repo link + cosign regexp + legacy identity note
- docs/reference/contact.mdx: issues, LICENSE, advisory, policy, CoC links
- docs/reference/security-advisories.mdx: releases link
- docs/operations/verifying-images.mdx: cosign regexps and VEX download URL (6 instances)
- docs/operations/upgrade.mdx: releases links (2 instances)
- backend/src/utils/version-check.ts: GitHub Releases API endpoint

CHANGELOG.md intentionally excluded (release-please managed).
Legacy cosign identity note added for pre-migration image verification.
2026-04-29 09:24:20 -04:00
Anso 72a1ecd192 feat(ui): wire overlay popovers to density tokens (#695)
Swap hardcoded Tailwind padding on NodeSwitcher, UserProfileDropdown,
and NotificationPanel for var(--density-row-x) / --density-row-y /
--density-cell-y / --density-tile-y custom properties so the three
sidebar/header overlays now tighten when the Appearance setting is
switched to Compact, matching the reference pattern in ui/table.tsx.
2026-04-19 12:36:59 -04:00
Anso 7c01906e70 feat(ui): redesign user menu and notification panel (#691)
* feat(ui): redesign user menu and notification panel per design audit

Align both floating surfaces with the stack-view design language: cyan
signal rails, identity headers, strip containers, day-banded streams,
severity rails, Instrument Serif for hero words, and tracked-mono for
metadata. Extract the notifications popover out of EditorLayout into its
own component and introduce a reusable SegmentedControl primitive for
3-way radiogroups (Auto/Light/Dark theme, All/Unread/Alerts filter).

* fix(ui): pin accessible names on avatar and bell buttons

After the redesign the avatar button's text content became the user's
initials and the bell button's text became the unread count, which
displaced the title-based accessible names and broke
getByRole('button', { name: /profile/i }). Set aria-label explicitly on
both triggers and mark the bell badge aria-hidden so screen readers and
role-based selectors see stable names regardless of dynamic content.
2026-04-19 03:10:10 -04:00
Anso be7eda85f1 fix(billing): hide billing portal for lifetime licenses (#427)
Lifetime licenses have no recurring subscription, so the Lemon Squeezy
customer portal cannot generate a URL. The Manage Subscription button in
Settings already had the isLifetime guard, but the Billing button in the
profile dropdown did not, causing a confusing "No billing portal
available" error.

- Add !license.isLifetime guard to UserProfileDropdown (matches
  LicenseSection pattern)
- Move lifetime detection into getBillingPortalUrl() so the service owns
  all billing eligibility logic
- Change return type to { url } | { error } discriminated union for
  clear error propagation
2026-04-08 09:51:27 -04:00
Anso f9ebd1d77c feat: UI polish sprint — 7 items + logs toolbar redesign (#365)
* feat: UI polish sprint — tag filters, toast tokens, logs toolbar, billing portal, editor UX

- Fleet: replace inline tag pills with multi-select combobox dropdown
- Toast: remap all notification colors to oklch design tokens
- Logs: convert floating hover toolbar to permanent pinned toolbar,
  replace all hardcoded colors with design tokens for theme support
- Audit: fix dropdown scroll-lock (modal=false), light theme button contrast
- Resources: remove redundant inner border/bg on tab wrapper
- Editor: add ⌘K/Ctrl+K shortcut hint and handler, standardize button heights
- Billing: signed Lemon Squeezy portal URLs via sencho.io proxy for all tiers
- New MultiSelectCombobox UI component

* feat(editor): replace button row with split-button dropdown

Replace three separate editor buttons (Discard, Save Only, Save & Deploy)
with a compact split-button dropdown. Primary action is "Save & Deploy";
chevron opens dropdown with "Save Only" and "Discard Changes" options.
2026-04-03 20:33:44 -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 db73d7671a feat: RBAC, atomic deployments, and fleet-wide backups (Pro) (#181)
* 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
2026-03-26 12:51:30 -04:00
Anso 4f26f22cce feat: add Community/Pro licensing, fleet view, and UI reorganization (#145)
* feat: add license gating system with Lemon Squeezy integration

Add Community/Pro tier infrastructure:
- LicenseService singleton with Lemon Squeezy license API integration
- /api/license endpoints (GET info, POST activate/deactivate/validate)
- 14-day Pro trial activated automatically on first boot
- 72-hour periodic validation with 30-day offline grace period
- LicenseContext provider for frontend tier awareness
- License settings tab with activation UI and status display
- ProBadge and ProGate reusable components for feature gating
- requirePro per-route guard for backend Pro-only endpoints
- Proxy bypass for /api/license routes (local-only, never proxied)

* feat: add user profile dropdown and reorganize top navigation

- Create UserProfileDropdown component with settings, billing, theme
  toggle (System/Light/Dark), documentation links, and logout button
- Remove logout button from sidebar header
- Remove standalone settings button from top bar
- Move theme toggle from Settings modal to profile dropdown
- Inject app version via Vite define from root package.json
- Add globals.d.ts for __APP_VERSION__ type declaration

* refactor(settings): remove appearance tab from settings modal

Theme toggle was moved to the User Profile Dropdown in the previous
commit. Remove the now-redundant Appearance section, its nav button,
and the unused theme/setTheme props from SettingsModal.

* feat: add fleet view dashboard and about settings section

Fleet Overview: aggregates all nodes into a card grid showing status,
container counts, CPU/RAM/disk usage bars. Pro tier unlocks stack
drill-down with auto-refresh (30s). Backend endpoints /api/fleet/overview
and /api/fleet/node/:nodeId/stacks query nodes in parallel.

About section in Settings: displays version, license tier, status,
instance ID, and links to docs/changelog/issues.

Sidebar perf fix: stack status fetches now run in parallel via
Promise.allSettled instead of sequential for-loop, significantly
reducing load time for nodes with many stacks.

Also removes version number from User Profile Dropdown (now in About).

* fix(ci): resolve Docker build and E2E test failures

- Copy root package.json into frontend build stage so vite.config.ts
  can read the app version during Docker multi-stage build.
- Update auth E2E test: logout button moved into User Profile Dropdown.
- Update nodes E2E test: Settings button moved into User Profile Dropdown.
2026-03-25 08:32:07 -04:00