Fix the broken input focus outline, unify input styling, tokenize scales

The root cause of 'some inputs show a focus outline, most don't': the
global input:focus/textarea:focus/select:focus rule (outline:none,
border-color: accent) has specificity (0,0,1,1). Every per-component
rule shaped '.wrapper input { border: 1px solid var(--border) }' (.form
input, .field-row input, .kanban__new input, .sidebar__select select,
.modal__field select) ties it exactly, and -- being unconditional --
silently won that tie by simply appearing later in the file, so the
input's border stayed var(--border) forever regardless of focus. Only
two controls escaped: .project-card__name-input (a class applied
directly to the element, (0,0,1,0), too low to ever win the tie) and
.card-row__text (has its own .card-row__text:focus, (0,0,2,0),
genuinely higher). Confirmed with a live computed-style test against
the real stylesheet before and after.

Fix: one canonical .field class (plus .field--compact for inline 'add'
rows and the sidebar, .field--autosize for the dashboard's JS-grown
textarea), applied directly to the <input>/<textarea>/<select> itself
in every view -- structurally immune to the same tie, since a bare
class can never out-specificity input:focus. This also collapses five
near-duplicate rules (each with its own padding/radius/font-size) into
one definition, and fixes .kanban__new input's stray
background: var(--surface) (every other field uses var(--bg); this one
nearly matched its own var(--surface) sidebar panel).

Also: .btn-danger hardcoded #b3261e/#fff instead of the already-
existing var(--error)/var(--accent-text) tokens, so danger buttons
didn't adapt in dark mode like every other error-coloured element;
now they do. Added .field:disabled styling (opacity/cursor), matching
buttons -- latent until now since no input bound :disabled yet.

Second pass, promoting repeated-but-consistent raw values to tokens:
- border-radius: 6/7/8/10/12/999px -- the stray 7px (.sidebar__link)
  folded into the 6px tier -- become --radius-sm/md/lg/xl/pill.
- disabled/ghost opacity: 0.6 was used for every disabled button
  except .status-row__delete's 0.4 (now unified) and drag-ghost states'
  0.5 (semantically different, kept separate) -- --opacity-disabled/
  --opacity-ghost.
Font-size and spacing values also repeat but don't reduce to a clean
scale without arbitrary judgement calls either way -- left alone.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
2026-09-04 22:07:35 +01:00
co-authored by Claude Sonnet 5
parent a85bb182c7
commit a3462af005
8 changed files with 145 additions and 113 deletions
+2 -2
View File
@@ -141,7 +141,7 @@ async function onRemovePasskey(passkey: Passkey) {
<form class="form" @submit.prevent="onChangeEmail">
<label>
<span>New email</span>
<input v-model="newEmail" type="email" maxlength="255" required />
<input v-model="newEmail" class="field" type="email" maxlength="255" required />
<small v-if="changeError?.fieldError('email')" class="field-error">
{{ changeError.fieldError('email') }}
</small>
@@ -197,7 +197,7 @@ async function onRemovePasskey(passkey: Passkey) {
<form class="form form--new-card" @submit.prevent="onAddPasskey">
<label>
<span>Label</span>
<input v-model="newLabel" type="text" maxlength="100" />
<input v-model="newLabel" class="field" type="text" maxlength="100" />
</label>
<p v-if="addError" class="form-error">{{ addError }}</p>
<button type="submit" :disabled="adding">