From a79ee909c651d0279b103972acab35dec8824d32 Mon Sep 17 00:00:00 2001 From: Andras Bacsai <5845193+andrasbacsai@users.noreply.github.com> Date: Mon, 28 Sep 2026 00:18:16 +0200 Subject: [PATCH] chore: remove unused components and markup-only tests - Delete Blade components, component classes, Livewire components and views that nothing uses after the redesign. - Delete tests that only compare Blade/CSS source text; keep the source checks for authorization gates, stable wire:key values and escaping. - Update stale tests to the current UI and behaviour. - Update DESIGN.md to the current navigation and layout. - Add a lesson: test behaviour, not source markup. Co-Authored-By: Claude Opus 5.5 --- .ai/lessons.md | 5 + DESIGN.md | 220 ++++--- resources/css/app.css | 40 +- resources/views/livewire/dashboard.blade.php | 2 +- .../project/application/heading.blade.php | 25 - .../project/service/heading.blade.php | 42 -- tests/Feature/AccessControlRegressionTest.php | 42 -- .../Feature/ActivityMonitorCrossTeamTest.php | 2 +- tests/Feature/AddServerTimingHeadersTest.php | 50 -- tests/Feature/AdminSubscriptionStatusTest.php | 5 +- .../Feature/AllResourceRestartLimitsTest.php | 80 --- .../Api/ApplicationHealthCheckApiTest.php | 3 + .../Api/DatabaseBackupCreationApiTest.php | 4 +- tests/Feature/Api/DomainsByServerApiTest.php | 9 +- tests/Feature/Api/GithubAppsListApiTest.php | 7 + tests/Feature/Api/ScheduledTaskApiTest.php | 2 +- tests/Feature/Api/ServerValidationApiTest.php | 2 +- tests/Feature/ApiTokenExpirationTest.php | 28 +- .../ApplicationBuildpackCleanupTest.php | 8 +- .../BuildpackSwitchCleanupTest.php | 60 +- .../Application/GithubSourceChangeTest.php | 12 +- tests/Feature/ApplicationClearDomainsTest.php | 5 +- .../ApplicationContainerPresenceTest.php | 30 - ...onDockerRegistryImageValidationApiTest.php | 3 + tests/Feature/ApplicationDomainsTest.php | 136 +--- ...pplicationGeneralBuildpackSelectorTest.php | 5 +- .../ApplicationGitSourceButtonLayoutTest.php | 12 - .../ApplicationHeadingActiveTabTest.php | 94 +-- .../ApplicationPreviewSettingsTest.php | 22 - .../ApplicationSourceLocalhostKeyTest.php | 7 +- ...pplicationStoppedAfterRestartLimitTest.php | 12 +- .../Auth/TwoFactorChallengeAccessTest.php | 12 - .../Authorization/ApiTokenPermissionTest.php | 2 +- .../ApplicationConfigAuthorizationTest.php | 16 +- .../DashboardAuthorizationTest.php | 37 +- .../DestinationOwnershipTest.php | 6 +- .../Authorization/LegacyMemberTokenTest.php | 79 +-- .../ResourceHeadingAuthorizationTest.php | 37 +- .../SecurityPageAuthorizationTest.php | 15 +- .../Authorization/ServerAuthorizationTest.php | 55 +- .../ServiceConfigAuthorizationTest.php | 23 +- .../SharedResourceAuthorizationTest.php | 2 +- tests/Feature/BackupEditValidationTest.php | 98 +-- tests/Feature/BackupNowAvailabilityTest.php | 3 - tests/Feature/BackupSearchTest.php | 2 +- tests/Feature/BreadcrumbsVisibilityTest.php | 32 +- .../CleanupUnsubscribedServersTest.php | 3 + .../Feature/CloudProviderLinkDropdownTest.php | 8 +- tests/Feature/ComposeEditorLayoutTest.php | 68 -- tests/Feature/CopyButtonComponentTest.php | 19 - tests/Feature/CrossTeamIdorLogsTest.php | 12 +- .../CrossTeamIdorServerProjectTest.php | 10 +- tests/Feature/DashboardDeploymentsTest.php | 11 - tests/Feature/DashboardHttpsRedirectTest.php | 16 - .../DashboardServerMetricsChartTest.php | 41 -- .../DashboardServersEmptyStateTest.php | 30 - tests/Feature/DatabaseHealthCheckTest.php | 98 ++- .../DatabasePersistentStorageLayoutTest.php | 10 +- .../DatabasePublicPortTimeoutApiTest.php | 4 +- .../Feature/DatabaseSslStatusRefreshTest.php | 32 +- tests/Feature/DeploymentByUuidApiTest.php | 3 + .../Feature/DeploymentHistoryFiltersTest.php | 41 -- tests/Feature/DeploymentLogsLayoutTest.php | 231 +------ tests/Feature/DevScriptExampleNginxTest.php | 22 +- .../DigitalOceanServerCreationTest.php | 12 +- tests/Feature/DnsProviderManagementTest.php | 7 - .../DockerImagePreviewDeploymentApiTest.php | 8 + tests/Feature/DomainChipsComponentTest.php | 70 --- tests/Feature/EmptyStateComponentTest.php | 93 --- tests/Feature/EnableActionButtonsTest.php | 51 +- tests/Feature/EnvVarInputDesignTest.php | 55 +- .../EnvironmentVariableSharedSpacingTest.php | 48 +- .../EnvironmentVariableBulkCommentApiTest.php | 2 +- .../EnvironmentVariableCommentTest.php | 42 +- .../EnvironmentVariableMassAssignmentTest.php | 28 +- ...ronmentVariableMultilineToggleViewTest.php | 63 +- .../EnvironmentVariableTableLayoutTest.php | 142 ----- tests/Feature/FormsButtonLoadingTest.php | 48 -- tests/Feature/GithubAppSettingsLayoutTest.php | 32 - tests/Feature/GithubWebhookTest.php | 4 + tests/Feature/GitlabAppAuthorizationTest.php | 7 - tests/Feature/GitlabRepositoryListingTest.php | 13 +- tests/Feature/GitlabSourceChangeViewTest.php | 22 +- tests/Feature/GitlabSourceCreateModalTest.php | 2 +- .../Feature/HeaderVersionReleaseLinkTest.php | 24 - tests/Feature/HelperInfoButtonTest.php | 143 ----- .../Feature/Helpers/ConvertingGitUrlsTest.php | 20 +- .../Jobs/CheckAndStartSentinelJobTest.php | 2 +- .../Jobs/CheckTraefikVersionJobTest.php | 13 +- tests/Feature/Jobs/CleanupRedisTest.php | 6 +- .../Jobs/ScheduledJobManagerStaleLockTest.php | 41 +- .../Jobs/StartupExecutionCleanupTest.php | 61 +- .../LayoutPopupsBladeDirectiveTest.php | 10 + .../Livewire/ConfigurationCheckerTest.php | 45 -- tests/Feature/Livewire/SentinelLogsTest.php | 10 +- .../Livewire/SentinelRestoreDefaultsTest.php | 13 +- .../LivewireRequestFailurePreviewTest.php | 10 - .../Feature/MassAssignmentProtectionTest.php | 31 +- .../MigrateResourceToDestinationTest.php | 34 +- .../ModalConfirmationStep1NoCancelTest.php | 14 - tests/Feature/ModalScrollLockTest.php | 35 -- ...bleLivewireComponentsAuthorizationTest.php | 34 +- .../NotificationRestartLimitSettingTest.php | 16 - .../PasswordVisibilityComponentTest.php | 68 -- .../PersistentStoragePerformanceTest.php | 21 +- .../PersistentStorageVolumesLayoutTest.php | 271 +------- .../PreviewDomainPortOverridesTest.php | 10 - tests/Feature/PreviewStatusSummaryTest.php | 78 --- tests/Feature/ProfileAvatarTest.php | 35 +- ...rojectAndEnvironmentSettingsButtonTest.php | 18 - .../Feature/ProjectViewControlsStyleTest.php | 15 - tests/Feature/Proxy/RestartProxyTest.php | 34 - tests/Feature/ProxyAcmeCertificateUiTest.php | 13 +- .../Feature/RealtimeTerminalPackagingTest.php | 587 ------------------ tests/Feature/ResetPasswordUrlTest.php | 45 +- .../Feature/ResourceBreadcrumbStatusTest.php | 4 +- tests/Feature/ResourceHostingServerTest.php | 9 +- tests/Feature/ResourcePageTitleTest.php | 80 +-- .../Feature/ResourcePermissionCalloutTest.php | 29 +- .../Feature/ResponsiveCheckboxLayoutTest.php | 49 +- .../ReverbAndTerminalPackagingTest.php | 7 +- .../SearchableListboxComponentTest.php | 47 +- .../Feature/SecureCookieAutoDetectionTest.php | 6 +- .../CaCertificateCommandInjectionTest.php | 6 + .../Feature/Security/CloudInitScriptsTest.php | 8 +- .../Security/CloudProviderTokenFormTest.php | 15 - .../Security/IntegrationTokenFormTest.php | 20 - .../Security/PrivateKeyDropdownTest.php | 22 +- .../Security/TrustHostsMiddlewareTest.php | 143 +++-- .../SecurityResourceModalEditorsTest.php | 66 -- .../Server/HetznerServerCreationTest.php | 28 +- .../Server/NewServerProviderTokenUrlTest.php | 48 +- .../SentinelUpdateCheckIndependenceTest.php | 3 + .../Feature/Server/ServerIpUniquenessTest.php | 46 +- .../Server/ServerMetricsSettingsTest.php | 39 -- .../ServerPatchCheckNotificationTest.php | 2 +- .../Server/ServerSettingWasChangedTest.php | 4 + .../ServerStorageCheckIndependenceTest.php | 2 + tests/Feature/ServerBuildRoleHelperTest.php | 14 - .../ServerDangerMenuVisibilityTest.php | 3 +- tests/Feature/ServerIndexIpExposureTest.php | 11 + tests/Feature/ServerMetadataTest.php | 18 +- .../ServerNavbarStatusVisibilityTest.php | 34 - .../Feature/ServerPrivateKeyDropdownTest.php | 22 +- .../Feature/ServerResourcesPaginationTest.php | 30 +- .../Service/EditDomainPortValidationTest.php | 145 ----- .../Service/StartDatabaseProxyTest.php | 19 +- .../ServiceDatabaseVerticalNavigationTest.php | 324 ---------- tests/Feature/ServiceDomainsTest.php | 133 ---- tests/Feature/ServiceExtraFieldsTest.php | 3 + tests/Feature/ServiceResourceRoutingTest.php | 32 +- .../ServiceResourceSettingsPageTest.php | 6 - .../ServiceTemplatesLastUpdatedHintTest.php | 66 +- tests/Feature/SettingsAccessListboxTest.php | 24 - .../Feature/SettingsBackupEmptyStateTest.php | 6 +- tests/Feature/SettingsDropdownTest.php | 10 - .../SettingsEmailSendTestPlacementTest.php | 13 +- tests/Feature/SettingsEmailSmtpSetupTest.php | 8 - .../SettingsOauthProviderControlsTest.php | 87 +-- tests/Feature/SettingsOauthTest.php | 48 +- .../SidebarNavigationPreferencesTest.php | 50 -- tests/Feature/StandardTableComponentsTest.php | 76 --- tests/Feature/StorageSettingsLayoutTest.php | 41 -- .../SubscriptionPricingPageTest.php | 51 -- tests/Feature/TablePaginationLoadingTest.php | 115 ---- tests/Feature/TagsShowPageTest.php | 29 - .../Team/ActiveTeamPersistenceTest.php | 18 + .../TeamInvitationCsrfProtectionTest.php | 37 +- tests/Feature/TeamInvitationUiTest.php | 47 -- tests/Feature/TeamScopedDestinationTest.php | 6 +- tests/Feature/TerminalPageHeaderTest.php | 351 ----------- tests/Feature/ToastPositionTest.php | 40 -- .../ApplicationAnalyticsTest.php | 7 - .../DashboardTrafficAnalyticsTest.php | 40 -- .../TrafficAnalytics/GlobalAnalyticsTest.php | 15 - .../ServerAnalyticsPageTest.php | 64 +- .../ToggleTrafficAnalyticsTest.php | 9 - tests/Feature/UpgradeComponentTest.php | 60 -- .../Feature/UserDeletionWithGithubAppTest.php | 16 +- tests/Feature/VolumeBackupTest.php | 149 ++--- tests/Feature/VultrServerCreationTest.php | 24 +- tests/Feature/VultrServerLifecycleTest.php | 7 + .../Unit/Actions/Server/CleanupDockerTest.php | 3 +- .../Api/ApplicationAutogenerateDomainTest.php | 21 +- .../Unit/ApplicationComposeEditorLoadTest.php | 132 ++-- ...plicationDeploymentNixpacksNullEnvTest.php | 80 +-- tests/Unit/ApplicationGitSecurityTest.php | 38 +- tests/Unit/ApplicationPortDetectionTest.php | 5 - tests/Unit/CheckForUpdatesJobTest.php | 29 +- .../ClickhouseOfficialImageMigrationTest.php | 3 + tests/Unit/CloudProviderTokenFormViewTest.php | 61 +- tests/Unit/ComposerAuthEnvEscapingTest.php | 22 +- tests/Unit/ContainerHealthStatusTest.php | 35 +- tests/Unit/CoolifyUpgradeStatusTest.php | 54 -- ...atabaseCredentialValidationPatternTest.php | 3 + .../ApplicationConfigurationSnapshotTest.php | 28 +- tests/Unit/DigitalOceanDeleteServerTest.php | 3 +- tests/Unit/DockerComposeLabelParsingTest.php | 9 +- tests/Unit/DockerComposeRawSeparationTest.php | 76 ++- tests/Unit/ExcludeFromHealthCheckTest.php | 20 - ...ContainersStatusServiceAggregationTest.php | 53 +- tests/Unit/GitRefValidationTest.php | 25 +- tests/Unit/GitSubmoduleCredentialTest.php | 7 +- .../GlobalSearchNewImageQuickActionTest.php | 157 +---- tests/Unit/HetznerServiceTest.php | 3 + .../Unit/InstallScriptRockyDockerRepoTest.php | 2 +- .../ApplicationGeneralPreviewTest.php | 42 +- .../EnvironmentVariableAutocompleteTest.php | 5 +- tests/Unit/LogViewerXssSecurityTest.php | 13 - .../Channels/EmailChannelTest.php | 217 +++---- .../Channels/WebhookChannelTest.php | 2 +- .../NotifyOutdatedTraefikServersJobTest.php | 14 +- tests/Unit/PersistentVolumeSecurityTest.php | 17 +- tests/Unit/ProductionImageWorkflowTest.php | 6 +- .../S3StorageEndpointNormalizationTest.php | 12 +- tests/Unit/ScheduledJobsRetryConfigTest.php | 39 +- .../ServerManagerJobSentinelCheckTest.php | 96 ++- .../Unit/ServiceConfigurationRefreshTest.php | 8 - tests/Unit/ServiceExcludedStatusTest.php | 4 +- tests/Unit/VultrDeleteServerTest.php | 3 +- .../Browser/ApplicationConfigurationTest.php | 9 - tests/v4/Browser/AuthorizationTest.php | 39 +- .../Browser/LivewireToggleEdgeCasesTest.php | 28 +- tests/v4/Browser/ProjectAndServerTest.php | 6 +- tests/v4/Browser/TrafficAnalyticsTest.php | 13 +- 225 files changed, 2000 insertions(+), 6991 deletions(-) create mode 100644 tests/Feature/LayoutPopupsBladeDirectiveTest.php create mode 100644 tests/Feature/ServerIndexIpExposureTest.php diff --git a/.ai/lessons.md b/.ai/lessons.md index e596a86c16..e78d70720c 100644 --- a/.ai/lessons.md +++ b/.ai/lessons.md @@ -17,6 +17,11 @@ - Do not add billing restrictions, live reconciliation, or fallback behavior unless the request includes them. - Treat implementation constraints as details. Do not expand a requested team-level control into a more complex policy model. +## Test behavior, not source markup +- Do not write tests that read Blade or CSS files as text to assert layout, classes, or copy; they break on every redesign and miss real failures. +- Render the component (`Livewire::test()`, an HTTP request) and assert the result, including authorization; use a few `tests/v4/Browser` tests with screenshots for visual flows. +- A source-text check is acceptable only for a correctness or security rule that rendering cannot prove easily, such as stable `wire:key` values or no raw user output. + ## Keep dynamic Livewire identities stable - In dynamic lists, key components and actions with immutable record identities, not counts, indexes, or array positions. - Use targeted refresh events. Do not refresh a parent and a child that the parent can remove or hide during the same operation. diff --git a/DESIGN.md b/DESIGN.md index 53b058ddef..1226dd0587 100644 --- a/DESIGN.md +++ b/DESIGN.md @@ -20,10 +20,9 @@ compact divided list, not legacy green check SVGs or fixed-width status rows. > interactions while changing layout and presentation. > - Add or update tests when a UI change affects behavior. Follow the testing > requirements in `AGENTS.md`. -> - Validate Blade with `docker exec coolify php artisan view:cache`, then clear -> it with `docker exec coolify php artisan view:clear`. -> - Build frontend assets in the Vite container with -> `docker exec coolify-vite npm run build`. +> - Validate Blade with `./scripts/dev exec php artisan view:cache`, then clear +> it with `./scripts/dev exec php artisan view:clear`. +> - Build frontend assets with `npm run build`. > - Use existing components before adding another styling abstraction. --- @@ -42,7 +41,7 @@ The interface is compact and product-focused: - the Coolify purple brand accent in light mode; - the readable Coolify yellow accent in dark mode; - solid active-item fills (neutral black/white opacity), not accent gradients; - active state is the left accent rail plus a flat selected surface; + active state is a flat selected surface with no accent rail; - sentence-case labels and headings; - never use the em dash (`—`) in UI copy. Prefer a period, colon, comma, or ASCII hyphen (`-`) for empty cells and separators. @@ -59,17 +58,18 @@ and focus-visible controls retain the accent ring alongside the depth. Movement and depth-shadow changes transition over 80ms; color transitions keep the shared 120ms duration. Standard button labels use `capitalize`, giving each word an initial capital. -Highlighted buttons mix the accent equally with black for a pronounced bottom -edge, so custom theme colors produce a matching edge instead of a generic one. -Dark mode matches the depth edge of neutral buttons to their regular border -color. Highlighted buttons keep their dark, color-matched accent edge. +Highlighted buttons use `--color-coollabs-300` for their bottom edge; the +custom theme derives it from its bright color mixed with black, so custom +colors keep a matching edge. Dark mode matches the depth edge of neutral +buttons to their regular border color (`rgb(255 255 255 / 0.08)`). --- ## 2. Development and cascade notes -PHP runs in the `coolify` container. The development app is normally available -at `http://localhost:8000`, with Vite on port `5173`. +PHP runs in this branch's Coolify container (`./scripts/dev exec …`). The main +checkout serves the app at `http://localhost:8000` with Vite on `5173`; +worktrees use the port block printed by `./scripts/dev urls`. `resources/css/app.css` still contains unlayered global element rules for headings, labels, and tables. Tailwind utilities are layered, so the @@ -89,8 +89,8 @@ Important consequences: - add shared surface overrides to the unlayered block instead of stacking `!important` utilities; - listbox panels require ancestors with `overflow: visible`; -- anchored cards use `scroll-margin-top: 7rem` to clear both fixed navigation - layers; +- anchored cards use `scroll-margin-top: 7rem` to clear the fixed topbar and + the page's top padding; - modal shells reuse the layer-card classes but keep content-width sizing on desktop; - Alpine code inside quoted Blade attributes must not introduce conflicting @@ -104,11 +104,11 @@ The surface ladder is defined in `resources/css/app.css`. | Token | Light | Dark | Use | |---|---|---|---| -| `--coollabs-canvas` | 97% off-white | 10% neutral | page canvas (kept below card fills so cards lift) | -| `--coollabs-elevated` | 98% neutral | 15% neutral | shells and card headers | -| `--coollabs-base` | white | 17% neutral | nested card bodies | -| `--coollabs-recessed` | 96% neutral | 20% neutral | inputs and listboxes | -| `--coollabs-fill` | 92.2% neutral | 26.9% neutral | dividers and passive fills | +| `--coollabs-canvas` | 97% off-white | 14.48% neutral | page canvas (kept below card fills so cards lift) | +| `--coollabs-elevated` | 98% neutral | 20.02% neutral | shells and card headers | +| `--coollabs-base` | white | 22.64% neutral | nested card bodies | +| `--coollabs-recessed` | 96% neutral | 25.2% neutral | inputs and listboxes | +| `--coollabs-fill` | 92.2% neutral | 29.31% neutral | dividers and passive fills | | `--coollabs-line` | translucent dark | 32% neutral | control borders | | `--coollabs-hairline` | 85.5% neutral | 32% neutral | shell rings (crisp enough to read as a card edge, ~1.5:1) | | `--coollabs-subtle` | 50% neutral | 70.8% neutral | labels and muted titles (light darkened for WCAG AA 4.5:1) | @@ -137,17 +137,17 @@ The app shell is three distinct surface layers, not one flat color. Chrome lifts, content is the base, cards lift off the content: - **Content canvas** is the base layer: `bg-app` in dark (deepest, - `--color-app` `#0a0a0b`), `bg-gray-50` in light. The `
` content area - and page body use it. + `--color-app` `oklch(14.48% 0 0)`, sRGB 10), `bg-neutral-50` in light. The + `
` content area and page body use it. - **Sidebar and topbar chrome** use `bg-panel` in dark (`--color-panel` - `#141418`, a clear step lighter than the content canvas) and `bg-white` in - light, so the chrome reads as a separate panel from the content. + `oklch(19.13% 0 0)`, sRGB 20, a clear step lighter than the content canvas) + and `bg-white` in light, so the chrome reads as a separate panel from the content. -Dark surface tokens are hex, not oklch. oklch lightness compresses toward pure -black below ~15% (oklch(10%) renders as sRGB 3, oklch(15%) as sRGB 11), so oklch -values there give no visible step between layers. The dark ladder is -`--color-app` 10, `--coollabs-elevated` 22, `--coollabs-base` 28, -`--coollabs-recessed` 34 (sRGB), which reads as distinct surfaces. +Dark surface tokens are exact oklch equivalents of chosen sRGB steps. oklch +lightness compresses toward pure black below ~15%, so do not pick dark values by +round oklch percentages. The dark ladder is `--color-app` 10, +`--coollabs-elevated` 22, `--coollabs-base` 28, `--coollabs-recessed` 34 (sRGB), +which reads as distinct surfaces. Temperature: every panel is **pure neutral gray** (r=g=b), one consistent temperature across the sidebar, tables, cards, inputs, dividers, borders, and @@ -171,60 +171,54 @@ color. Row-hover states keep the lighter `dark:hover:bg-white/[0.025]`. - Main sidebar groups are compact, use outline Reicons, and keep a 32px row height. -- Active sidebar rows are rounded pills (`rounded-md`) with an accent rail on - the left plus a solid neutral selected fill (`bg-black/5` light, - `bg-white/6` dark). Hover rows use the same radius. Do not use accent-tinted - gradients on nav rows; yellow washes look muddy on dark UI. -- Nested items use a thin guide line with a visible active segment, not a thick - box border. +- Active sidebar rows are rounded pills (`rounded-md`) with a solid neutral + selected fill (`bg-black/5` light, `bg-white/6` dark) and no accent rail. + Hover rows use the same radius. Do not use accent-tinted gradients on nav + rows; yellow washes look muddy on dark UI. +- Nested items sit behind a thin 1px guide line (`.nav-children`) and use the + same selected pill, not a thick box border. - The update badge sits on the version row and uses a tiny fully rounded primary-action pill. -### Layer-2 navigation +### Resource navigation -Application and server pages use the same fixed second navigation layer -directly below the global topbar. Do not keep a large in-flow resource heading -or legacy `.navbar-main` tabs on one resource type while using the compact -layer-2 bar on another. Active tabs are a light brand fill: - -- purple tint in light mode; -- yellow tint in dark mode; -- no fully saturated tab background. +Application, service, database, and server pages have no second navigation +bar; content starts directly below the 48px global topbar. Every route in a +resource family (settings pages, backups, logs, terminal, metrics, danger zone) +is an entry in its grouped settings sidebar. Do not add a tab row, a large +in-flow resource heading, or legacy `.navbar-main` tabs to one resource type. Keep route-derived active state in Blade/Livewire. Do not rely only on Alpine state because it can disappear after polling or a Livewire morph. -The global topbar owns the current resource identity and its compact status -badges. Layer 2 owns route tabs, resource links, and contextual action buttons -only. If a resource is missing from `x-top-breadcrumb`, extend the global -topbar instead of repeating its name or status summary in layer 2. Mobile -resource navigation may repeat this context because the desktop global topbar -is hidden there. +The global topbar owns the current resource identity, its compact status +badges, configuration warnings (`#configuration-warning-hud-slot`), and the +resource actions (`#resource-action-hud-slot`). If a resource is missing from +`x-top-breadcrumb`, extend the global topbar instead of repeating its name or +status summary in the page. Below `xl` the resource heading repeats the name, +status, and links in-flow because the desktop HUD is hidden there. -Desktop resource lifecycle actions dock in `#resource-action-hud-slot` and -use ``. Show primary actions (Deploy, Redeploy, -Restart, Stop) as sibling header buttons. Collapse that group into an Actions -dropdown only when the remaining top-bar width cannot fit them (breadcrumb -keeps a 200px floor). Infrequent operations live in a separate Advanced -dropdown with the grid icon: force restart / force deploy / force cleanup -on services, and Traefik dashboard / refresh proxy status on servers. Place -Advanced immediately after Links, or first in the action cluster when there -is no Links control. Application Deploy is a dropdown with Deploy and -Deploy (without cache). A running service Restart control is a dropdown with -Restart current version and Pull latest and restart. Mobile -headings keep a full-width Actions dropdown because the desktop HUD is hidden -below `xl`. Do not hide primary actions behind a menu on a wide desktop. Links -stay a separate dropdown because the URL list is unbounded. +Desktop resource actions dock in `#resource-action-hud-slot` (visible from +`xl`) as one ``. The main button is the primary action for the +current state (Deploy, Restart, Start, Restart Proxy); the caret opens a menu +with the secondary actions: Deploy (without cache) and Restart on applications, +Pull latest and restart / Force Restart / Force Deploy / Force Cleanup +Containers on services, Refresh Proxy Status on servers. Stop is the last menu +item in the error color. Stop, restart, and removal items open the existing +confirmation modals. Do not add a separate Advanced dropdown or collapse actions +into an overflow menu. Place Links (`x-applications.links`, `x-services.links`) +or the server Traefik Dashboard link immediately before the split action; Links +stay a separate dropdown because the URL list is unbounded. Below `xl` the same +split action renders full width under the in-flow resource name. A resource +that cannot deploy yet shows a single Actions dropdown that explains why. -Only add layer-2 tabs when they represent real sibling routes inside one -context. Never repeat main-sidebar destinations such as Dashboard, Projects, -Terminal, Servers, Sources, Destinations, or Storage as a second tab row. A -single collection page does not need a tab just to fill the bar; keep its -primary action in the page header instead. When tabs are useful, their left edge -uses the same compact `pl-2` alignment as application navigation rather than -the content container's wide horizontal padding. +The only fixed tab strip left is `x-dashboard.navbar`, and it renders only when +a page has at least two real sibling routes or header actions. Never repeat +main-sidebar destinations such as Dashboard, Projects, Terminal, Servers, +Sources, Destinations, or Storage as a tab row. A single collection page does +not need a tab just to fill the bar; keep its primary action in the page header. -A layer-2 tab must be active on the page that renders it. A bar whose only tab +A tab must be active on the page that renders it. A bar whose only tab points at a different route reads as broken navigation, so project and environment pages (`project.show`, `project.edit`, `project.environment.edit`, `project.clone-me`) carry a plain page header with a 24px title and a 13px @@ -232,11 +226,11 @@ muted summary instead of a bar. The environment identity and the way back to its resources already live in `x-top-breadcrumb`; do not restate them in a sub-header. -The dashboard is a compact overview, not a metrics wall. Use two full-width -sections that follow the projects-page grid pattern: projects first, then -servers. Keep one `New` action in the page header and let its modal choose the -resource type. Place active deployments above the resource grids as a compact, -live-updating table rather than a metric card. Communicate server health with +The dashboard is a compact overview, not a metrics wall. It has no page header: +live active deployments come first as a compact table, followed by traffic +analytics when a server has it enabled, then two full-width sections (Projects, +then Servers) that follow the projects-page grid pattern. Each section uses +`x-section-heading` linking to its full index. Communicate server health with the shared status badge. ### Top-level dashboard destinations @@ -258,9 +252,9 @@ cards. Sources, destinations, S3 storage, private keys, and shared-variable scopes use this pattern. Top-level settings families such as Team, Notifications, Keys & Tokens, and -instance Settings use a compact header followed by a small route-derived tab -strip. The active tab uses the same purple-light/yellow-dark tint as resource -tabs. Do not nest `