From 3fc693443e6d784e58e67bde12a46e1db9e566ba Mon Sep 17 00:00:00 2001
From: Bryan Van Deusen
Date: Sat, 1 Aug 2026 22:48:35 -0400
Subject: [PATCH] refactor(ui): the settings family migrates; two colour bugs
and a dead class
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
SettingsView and UserManagementView had near-identical button vocabularies —
btn-delete, btn-cancel-delete, btn-confirm-delete, btn-toggle/-open/-close —
defined separately in each. Parallel duplication (#2278's shape), and it had
already diverged twice:
- .btn-confirm-delete used --color-danger in UserManagement and
--color-action-destructive in Settings. Those are different colours on
purpose: the house style keeps error (something went wrong) distinct from
destructive (something is about to). A delete confirmation is destructive.
UserManagement was showing the error colour for a button nothing had failed
in yet.
- .btn-remove-slot's hover reached for --color-danger for the same reason, and
is the same correction. It turned out to be dead anyway — style rules only,
no template reference anywhere in the app — so it is gone.
.btn-danger-outline was defined TWICE inside SettingsView, at 0.4rem 0.9rem and
0.45rem 1rem. One file, one class, two geometries, ~1200 lines apart. That is
the clearest single argument for this whole task that I have found: the drift
does not need two files, only enough distance that nobody sees both at once.
The registration toggle keeps .btn-toggle-close, and only that. It is bound
dynamically (:class="registrationOpen ? … : …"), so a name-based scan reads it
as unused — checked before deleting. .btn-toggle-open went, because btn-primary
now says the same thing; the close state stays because it must NOT read as the
primary action it sits on.
Co-Authored-By: Claude Opus 5 (1M context)
Claude-Session: https://claude.ai/code/session_01UaYUaouG9jjhATyuxCKrQs
---
frontend/src/views/SettingsView.vue | 223 +++-------------------
frontend/src/views/UserManagementView.vue | 100 +---------
2 files changed, 34 insertions(+), 289 deletions(-)
diff --git a/frontend/src/views/SettingsView.vue b/frontend/src/views/SettingsView.vue
index dc7b975..813e83c 100644
--- a/frontend/src/views/SettingsView.vue
+++ b/frontend/src/views/SettingsView.vue
@@ -1165,7 +1165,7 @@ function formatUserDate(iso: string): string {
Click Detect to auto-fill from your browser.
-
+
{{ savingTimezone ? 'Saving…' : 'Save' }}
Saved!
@@ -1190,7 +1190,7 @@ function formatUserDate(iso: string): string {
Set to 0 to keep deleted items forever (never auto-purge).
-
+
{{ savingRetention ? 'Saving…' : 'Save' }}
Saved!
@@ -1313,7 +1313,7 @@ function formatUserDate(iso: string): string {
-
+
{{ savingKbInject ? 'Saving…' : 'Save' }}
Saved!
@@ -1361,7 +1361,7 @@ function formatUserDate(iso: string): string {
@@ -1409,7 +1409,7 @@ function formatUserDate(iso: string): string {
@@ -1465,7 +1465,7 @@ function formatUserDate(iso: string): string {
- {{ profileSaving ? 'Saving…' : 'Save' }}
+ {{ profileSaving ? 'Saving…' : 'Save' }}
Saved!
@@ -1491,7 +1491,7 @@ function formatUserDate(iso: string): string {
- {{ profileSaving ? 'Saving…' : 'Save' }}
+ {{ profileSaving ? 'Saving…' : 'Save' }}
Saved!
@@ -1501,7 +1501,7 @@ function formatUserDate(iso: string): string {
Topics you care about — used to personalise the journal's daily prep and chat responses.
- {{ profileSaving ? 'Saving…' : 'Save' }}
+ {{ profileSaving ? 'Saving…' : 'Save' }}
Saved!
@@ -1533,7 +1533,7 @@ function formatUserDate(iso: string): string {
- {{ profileSaving ? 'Saving…' : 'Save' }}
+ {{ profileSaving ? 'Saving…' : 'Save' }}
Saved!
@@ -1562,7 +1562,7 @@ function formatUserDate(iso: string): string {
Emails for logins, logouts, and password changes.
-
+
{{ savingNotifications ? "Saving..." : "Save" }}
Saved!
@@ -1589,7 +1589,7 @@ function formatUserDate(iso: string): string {
placeholder="Enter a search query..."
@keydown="onSearchKeydown"
/>
-
+
{{ searchLoading ? "Searching..." : "Search" }}
@@ -1891,7 +1891,7 @@ function formatUserDate(iso: string): string {
/>
-
+
{{ savingBaseUrl ? "Saving..." : "Save" }}
Saved!
@@ -1916,7 +1916,7 @@ function formatUserDate(iso: string): string {
/>
-
+
{{ savingMarketplaceUrl ? "Saving..." : "Save" }}
Saved!
@@ -1946,10 +1946,10 @@ function formatUserDate(iso: string): string {
-
+
{{ savingDbMaint ? "Saving..." : "Save" }}
-
+
{{ runningDbMaint ? "Running..." : "Run now" }}
Saved!
@@ -2040,7 +2040,7 @@ function formatUserDate(iso: string): string {
Recommended for port 587. Implicit TLS is used automatically for port 465.
-
+
{{ savingSmtp ? "Saving..." : "Save SMTP Settings" }}
Saved!
@@ -2054,7 +2054,7 @@ function formatUserDate(iso: string): string {
placeholder="test@example.com"
class="input"
/>
-
+
{{ sendingTest ? "Sending..." : "Send Test" }}
@@ -2079,7 +2079,7 @@ function formatUserDate(iso: string): string {
When closed, new users can only be added by an administrator.
-
+
{{ sendingInvite ? "Sending..." : "Send Invite" }}
@@ -2122,7 +2122,7 @@ function formatUserDate(iso: string): string {
{{ formatUserDate(inv.created_at) }}
{{ formatUserDate(inv.expires_at) }}
-
+
{{ revokingId === inv.id ? "Revoking..." : "Revoke" }}
@@ -2161,13 +2161,13 @@ function formatUserDate(iso: string): string {
You
-
+
{{ deleting === u.id ? "Deleting..." : "Confirm" }}
- Cancel
+ Cancel
- Delete
+ Delete
@@ -2305,10 +2305,10 @@ function formatUserDate(iso: string): string {
{{ g.description }}
-
+
{{ expandedGroupId === g.id ? 'Collapse' : 'Manage' }}
- Delete
+ Delete
@@ -2338,7 +2338,7 @@ function formatUserDate(iso: string): string {
{{ m.username }}
{{ m.role }}
- Remove
+ Remove
No members yet.
@@ -2564,54 +2564,6 @@ function formatUserDate(iso: string): string {
flex-wrap: wrap;
}
/* Save: Moss action-primary per Hybrid */
-.btn-save {
- padding: 0.4rem 0.9rem;
- background: var(--color-action-primary);
- color: var(--fs-text-on-action);
- border: none;
- border-radius: var(--radius-sm);
- cursor: pointer;
- font-size: 0.875rem;
- font-family: inherit;
- white-space: nowrap;
- transition: background 0.15s;
-}
-.btn-save:disabled { opacity: 0.6; cursor: default; }
-.btn-save:hover:not(:disabled) { background: var(--color-action-primary-hover); }
-
-/* Danger outline (Invalidate sessions, Clear observations, etc.):
- Oxblood action-destructive ghost — fills on hover */
-.btn-danger-outline {
- padding: 0.4rem 0.9rem;
- background: none;
- color: var(--color-action-destructive);
- border: 1px solid var(--color-action-destructive);
- border-radius: var(--radius-sm);
- cursor: pointer;
- font-size: 0.875rem;
- font-family: inherit;
- white-space: nowrap;
- transition: background 0.15s, color 0.15s;
-}
-.btn-danger-outline:hover:not(:disabled) {
- background: var(--color-action-destructive);
- color: var(--fs-text-on-action);
-}
-.btn-danger-outline:disabled { opacity: 0.5; cursor: default; }
-
-/* Filled destructive: Oxblood action-destructive */
-
-/* Secondary: Bronze action-secondary — alternate paths (Detect, Test,
- Refresh, Add slot, etc.). Outline form for visual lightness. */
-
-/* DB maintenance last-run summary */
-.db-maint-last { margin-top: 1rem; }
-.db-maint-last-label {
- display: block;
- font-size: 0.8rem;
- color: var(--color-text-muted);
- margin-bottom: 0.4rem;
-}
.db-maint-table-list {
list-style: none;
margin: 0;
@@ -2957,68 +2909,6 @@ function formatUserDate(iso: string): string {
}
.you-label { font-size: 0.8rem; color: var(--color-text-muted); }
/* Per-row delete (users / invitations / etc.): ghost → Oxblood on hover */
-.btn-delete {
- padding: 0.25rem 0.6rem;
- background: transparent;
- color: var(--color-text-muted);
- border: 1px solid var(--color-border);
- border-radius: var(--radius-sm);
- cursor: pointer;
- font-size: 0.8rem;
-}
-.btn-delete:hover:not(:disabled) { border-color: var(--color-action-destructive); color: var(--color-action-destructive); }
-.btn-delete:disabled { opacity: 0.4; cursor: default; }
-/* Two-stage destructive: Confirm = Oxblood filled, Cancel = Bronze ghost */
-.btn-confirm-delete {
- padding: 0.25rem 0.6rem;
- background: var(--color-action-destructive);
- color: var(--fs-text-on-action);
- border: none;
- border-radius: var(--radius-sm);
- cursor: pointer;
- font-size: 0.8rem;
- font-weight: 500;
- margin-right: 0.25rem;
- transition: background 0.15s;
-}
-.btn-confirm-delete:hover:not(:disabled) { background: var(--color-action-destructive-hover); }
-.btn-confirm-delete:disabled { opacity: 0.6; cursor: default; }
-.btn-cancel-delete {
- padding: 0.25rem 0.6rem;
- background: transparent;
- color: var(--color-text-muted);
- border: 1px solid var(--color-border);
- border-radius: var(--radius-sm);
- cursor: pointer;
- font-size: 0.8rem;
-}
-.btn-cancel-delete:hover { color: var(--color-text); border-color: var(--color-text-muted); }
-/* Toggle (Open/Close registration, etc.): Open = Moss, Close = Pewter ghost */
-.btn-toggle {
- padding: 0.45rem 1rem;
- border: none;
- border-radius: var(--radius-sm);
- cursor: pointer;
- font-size: 0.9rem;
- font-weight: 500;
- white-space: nowrap;
- transition: background 0.15s;
-}
-.btn-toggle:disabled { opacity: 0.6; cursor: default; }
-.btn-toggle-open { background: var(--color-action-primary); color: var(--fs-text-on-action); }
-.btn-toggle-open:hover:not(:disabled) { background: var(--color-action-primary-hover); }
-.btn-toggle-close {
- background: var(--color-bg-secondary);
- color: var(--color-text);
- border: 1px solid var(--color-border);
-}
-.btn-toggle-close:hover:not(:disabled) { border-color: var(--color-warning); color: var(--color-warning); }
-.loading-msg, .empty-msg {
- text-align: center;
- color: var(--color-text-muted);
- font-size: 0.9rem;
- padding: 1rem 0;
-}
/* Logs panel */
.stats-section { padding: 1rem 1.25rem; }
@@ -3202,32 +3092,6 @@ function formatUserDate(iso: string): string {
flex-shrink: 0;
}
-.btn-sm {
- padding: 0.25rem 0.6rem;
- background: none;
- border: 1px solid var(--color-border);
- border-radius: 4px;
- font-size: 0.78rem;
- color: var(--color-text-secondary);
- cursor: pointer;
- font-family: inherit;
- transition: border-color 0.15s, color 0.15s;
-}
-.btn-sm:hover { border-color: var(--color-primary); color: var(--color-primary); }
-.btn-danger-sm:hover { border-color: var(--color-action-destructive); color: var(--color-action-destructive); }
-
-.group-members-panel {
- padding: 0.75rem 1rem 1rem;
- border-top: 1px solid var(--color-border);
- background: var(--color-surface);
-}
-
-.members-search {
- display: flex;
- gap: 0.5rem;
- align-items: flex-start;
- margin-bottom: 0.75rem;
-}
.member-search-wrap {
flex: 1;
@@ -3740,21 +3604,6 @@ function formatUserDate(iso: string): string {
text-align: right;
color: var(--color-text-secondary);
}
-.btn-remove-slot {
- background: none;
- border: 1px solid var(--color-border);
- border-radius: var(--radius-sm);
- color: var(--color-text-muted);
- cursor: pointer;
- font-size: 0.75rem;
- padding: 0.2rem 0.45rem;
- line-height: 1;
- transition: color 0.15s, border-color 0.15s;
-}
-.btn-remove-slot:hover {
- color: var(--color-danger, #e05555);
- border-color: var(--color-danger, #e05555);
-}
.blend-actions {
margin-top: 0.25rem;
}
@@ -3808,24 +3657,4 @@ function formatUserDate(iso: string): string {
color: var(--color-text-muted);
margin-bottom: 0.75rem;
}
-.btn-danger-outline {
- padding: 0.45rem 1rem;
- background: none;
- border: 1px solid var(--color-action-destructive);
- border-radius: var(--radius-sm);
- color: var(--color-action-destructive);
- font-size: 0.9rem;
- cursor: pointer;
- font-family: inherit;
- transition: background 0.15s, color 0.15s;
-}
-.btn-danger-outline:hover:not(:disabled) {
- background: var(--color-action-destructive);
- color: var(--fs-text-on-action);
-}
-.btn-danger-outline:disabled { opacity: 0.5; cursor: default; }
-@keyframes va-dot-bounce {
- 0%, 80%, 100% { transform: scale(0.6); opacity: 0.4; }
- 40% { transform: scale(1); opacity: 1; }
-}
diff --git a/frontend/src/views/UserManagementView.vue b/frontend/src/views/UserManagementView.vue
index 62bc563..ce0918f 100644
--- a/frontend/src/views/UserManagementView.vue
+++ b/frontend/src/views/UserManagementView.vue
@@ -167,7 +167,7 @@ function formatDate(iso: string): string {
{{ sendingInvite ? "Sending..." : "Send Invite" }}
@@ -216,7 +216,7 @@ function formatDate(iso: string): string {
{{ formatDate(inv.expires_at) }}
@@ -262,17 +262,17 @@ function formatDate(iso: string): string {
{{ deleting === u.id ? "Deleting..." : "Confirm" }}
- Cancel
+ Cancel
@@ -328,24 +328,6 @@ function formatDate(iso: string): string {
outline: none;
border-color: var(--color-primary);
}
-.btn-invite {
- padding: 0.45rem 1rem;
- background: var(--color-action-primary);
- color: var(--fs-text-on-action);
- border: none;
- border-radius: var(--radius-sm);
- cursor: pointer;
- font-size: 0.9rem;
- font-weight: var(--fs-weight-medium);
- white-space: nowrap;
-}
-.btn-invite:disabled {
- opacity: 0.6;
- cursor: default;
-}
-.btn-invite:hover:not(:disabled) {
- opacity: 0.9;
-}
.invite-list {
margin-top: 1rem;
}
@@ -380,26 +362,8 @@ function formatDate(iso: string): string {
font-size: 0.8rem;
color: var(--color-text-muted);
}
-.btn-toggle {
- padding: 0.45rem 1rem;
- border: none;
- border-radius: var(--radius-sm);
- cursor: pointer;
- font-size: 0.9rem;
- font-weight: var(--fs-weight-medium);
- white-space: nowrap;
-}
-.btn-toggle:disabled {
- opacity: 0.6;
- cursor: default;
-}
-.btn-toggle-open {
- background: var(--color-action-primary);
- color: var(--fs-text-on-action);
-}
-.btn-toggle-open:hover:not(:disabled) {
- opacity: 0.9;
-}
+/* The one genuine override: 'close registration' must NOT read as the
+ primary action it sits on. Scoped, so it beats the shared variant. */
.btn-toggle-close {
background: var(--color-bg-secondary);
color: var(--color-text);
@@ -478,54 +442,6 @@ function formatDate(iso: string): string {
font-size: 0.8rem;
color: var(--color-text-muted);
}
-.btn-delete {
- padding: 0.25rem 0.6rem;
- background: transparent;
- color: var(--color-text-muted);
- border: 1px solid var(--color-border);
- border-radius: var(--radius-sm);
- cursor: pointer;
- font-size: 0.8rem;
-}
-.btn-delete:hover:not(:disabled) {
- border-color: var(--color-danger);
- color: var(--color-danger);
-}
-.btn-delete:disabled {
- opacity: 0.4;
- cursor: default;
-}
-.btn-confirm-delete {
- padding: 0.25rem 0.6rem;
- background: var(--color-danger);
- color: var(--fs-text-on-action);
- border: none;
- border-radius: var(--radius-sm);
- cursor: pointer;
- font-size: 0.8rem;
- font-weight: var(--fs-weight-medium);
- margin-right: 0.25rem;
-}
-.btn-confirm-delete:hover:not(:disabled) {
- filter: brightness(0.9);
-}
-.btn-confirm-delete:disabled {
- opacity: 0.6;
- cursor: default;
-}
-.btn-cancel-delete {
- padding: 0.25rem 0.6rem;
- background: transparent;
- color: var(--color-text-muted);
- border: 1px solid var(--color-border);
- border-radius: var(--radius-sm);
- cursor: pointer;
- font-size: 0.8rem;
-}
-.btn-cancel-delete:hover {
- color: var(--color-text);
- border-color: var(--color-text-muted);
-}
@media (max-width: 768px) {
.registration-row {