Skip to main content

max / goingson

Adopt .field, fixing a raised fill under a recessed edge on every input The headline is a bug, not a rename. This file said background-color: var(--surface-raised); box-shadow: var(--bevel-inset); on the shared rule behind every input, select and textarea in the app, with :focus re-asserting the raised fill so focusing did not fix it either. A raised fill beneath a recessed edge is the exact disagreement Depth exists to make unrepresentable, and it is the failure makeover-layout's own doc comment cites from audiofiles and Balanced Breakfast. Fields are filled with --surface-well now, because the generated layout.css names a field a Depth::Well and supplies both halves from one place. .form-input, .form-select and .form-textarea collapse into .field, which is what their shared rule always described. Kind rides on the ELEMENT rather than on a second class: all 55 call sites had .form-input on an <input>, .form-select on a <select> and .form-textarea on a <textarea>, no exceptions, so the class was repeating what the tag already said. textarea.field carries min-height and resize; select.field carries the two layout rules that need to size a dropdown differently from a text box. An element selector cannot be forgotten at a call site the way a modifier class can. The invalid state is now the crate's, keyed on [aria-invalid="true"] rather than on a class so the visual and accessible states read one fact, and composing the danger ring after the bevel so the well stays under it. Both were corrections to makeover-webview, which had a .invalid class and a ring that replaced the bevel; GO was right and the crate moved. Three selector groups collapsed to single selectors, and the stylesheet's own index and section map were updated to match. To eyeball: every form field in the app changes fill, from --surface-raised to --surface-well. That is the bug fix, not a restyle.
Co-Authored-By
Claude Opus 5 (1M context) <noreply@anthropic.com>
Author: Max Johnson <me@maxj.phd> · 2026-07-29 23:38 UTC
Signed with PGP, not checked
Commit: 8e388844985fb2fd9e6df204f5879f183eb983e5
Parent: 881476a
23 files changed, +110 insertions, -107 deletions
M Cargo.lock +2 -2
@@ -3396,9 +3396,9 @@
3396 3396
3397 3397 [[package]]
3398 3398 name = "makeover-webview"
3399 - version = "0.4.0"
3399 + version = "0.5.0"
3400 3400 source = "registry+https://github.com/rust-lang/crates.io-index"
3401 - checksum = "f9b3d9f5b5e7f6bc4ded9b6c14185ce5348d02882ec3260642ce400a5ad0a89f"
3401 + checksum = "c14aeac61c38f8413d049a1e2bb7ebc3541a116e9a2a730b1ae02b33bd72eb93"
3402 3402 dependencies = [
3403 3403 "makeover-layout",
3404 3404 ]
@@ -197,10 +197,10 @@
197 197 <div class="page-header">
198 198 <h2 class="page-title">Problems</h2>
199 199 <div class="row-flex row-flex-peer">
200 - <select id="problems-source-filter" class="form-select form-select--compact" aria-label="Filter by source" data-change="problems.setSourceFilter">
200 + <select id="problems-source-filter" class="field field--compact" aria-label="Filter by source" data-change="problems.setSourceFilter">
201 201 <option value="">All sources</option>
202 202 </select>
203 - <select id="problems-status-filter" class="form-select form-select--compact" aria-label="Filter by status" data-change="problems.setStatusFilter">
203 + <select id="problems-status-filter" class="field field--compact" aria-label="Filter by status" data-change="problems.setStatusFilter">
204 204 <option value="Open" selected>Open</option>
205 205 <option value="Promoted">Promoted</option>
206 206 <option value="Dismissed">Dismissed</option>
@@ -478,11 +478,11 @@
478 478 </div>
479 479 </div>
480 480 <div class="email-filter-row mb-peer">
481 - <input type="text" class="form-input" id="email-search" placeholder="Search emails..." data-input="emails.search">
482 - <select class="form-select" id="email-folder-filter" data-change="emails.filterByFolder">
481 + <input type="text" class="field" id="email-search" placeholder="Search emails..." data-input="emails.search">
482 + <select class="field" id="email-folder-filter" data-change="emails.filterByFolder">
483 483 <option value="">All folders</option>
484 484 </select>
485 - <select class="form-select" id="email-label-filter" data-change="emails.filterByLabel">
485 + <select class="field" id="email-label-filter" data-change="emails.filterByLabel">
486 486 <option value="">All labels</option>
487 487 </select>
488 488 <span id="email-count" class="filter-count" aria-live="polite"></span>
@@ -518,7 +518,7 @@
518 518 <div class="page-header">
519 519 <h2 class="page-title">Contacts</h2>
520 520 <div class="contacts-filter-row">
521 - <input type="text" class="form-input" id="contacts-search" placeholder="Search contacts..." data-input="contacts.filterBySearch">
521 + <input type="text" class="field" id="contacts-search" placeholder="Search contacts..." data-input="contacts.filterBySearch">
522 522 <select class="filter-select" id="contacts-tag-filter" data-change="contacts.filterByTag">
523 523 <option value="">All Tags</option>
524 524 </select>
@@ -8,11 +8,11 @@
8 8 2. Use a LAYOUT PRIMITIVE to position content (.row-flex + .row-flex-{peer,
9 9 group,section}, .row,
10 10 .stack-{peer,group,section})
11 - 3. Use a COMPONENT PRIMITIVE for a UI element (.card, .button, .form-input,
12 - .form-select, .modal, .badge,
11 + 3. Use a COMPONENT PRIMITIVE for a UI element (.card, .button, .field,
12 + .modal, .badge,
13 13 .tag, .toggle-switch)
14 14 4. Extend a primitive with a modifier (.button--primary, .button--sm,
15 - .form-select--compact,
15 + .field--compact,
16 16 .subtask-item--linked)
17 17 5. ONLY THEN consider a new class | and add it to the right
18 18 BAND below
@@ -29,7 +29,7 @@
29 29 encodes intent in browser flex-shrink math rather than in CSS that reads
30 30 as intent, especially fragile when the row contains a native `<select>`,
31 31 whose intrinsic-min-content sizing fights `flex: 1`. Smell test: if you
32 - find yourself reaching for `.form-select--compact`, `style="width: auto;"`,
32 + find yourself reaching for `.field--compact`, `style="width: auto;"`,
33 33 or `min-width: 0 !important` to make a row lay out right, the layout
34 34 itself wants a name. See `.email-filter-row`, `.contacts-filter-row`, and
35 35 `.work-hours-row` (all 2026-05-24) for the canonical form.
@@ -66,8 +66,8 @@
66 66 17. Email List
67 67 18. Toast Notifications
68 68 19. Modals
69 - 20. Form Elements | .form-input,
70 - .form-select, +
69 + 20. Form Elements | .field + kind by
70 + element, +
71 71 JS-rendered layout
72 72 utilities tail
73 73 21. Footer
@@ -151,8 +151,8 @@
151 151 COMPONENT PRIMITIVES
152 152 .button + .button--primary / --secondary / --danger / --sm
153 153 .card + .card--list-item / --shell / --muted (compact / container / info-panel)
154 - .form-input + .form-input--ghost
155 - .form-select + .form-select--compact / --ghost
154 + .field + .field--ghost / --compact (kind rides on the element:
155 + input / select / textarea)
156 156 .modal + .modal-overlay
157 157 .badge + variants ([data-color], --xs / --sm / --filled)
158 158 .tag + variants ([data-color])
@@ -167,7 +167,7 @@
167 167 CONTROL MODIFIERS
168 168 Sizes: .button--sm
169 169 Intents: .button--primary / --secondary / --danger
170 - Shapes: .form-select--compact / --ghost, .form-input--ghost
170 + Shapes: .field--compact / --ghost
171 171
172 172 UI MODE STRATEGY (do not casually merge)
173 173
@@ -367,7 +367,7 @@
367 367 .mb-peer { margin-bottom: var(--gap-peer); }
368 368
369 369 /* --- FORM SIZING UTILITIES */
370 - .form-select--compact { width: auto; min-width: 120px; }
370 + .field--compact { width: auto; min-width: 120px; }
371 371 .settings-heading { margin-bottom: var(--gap-section); font-family: var(--font-heading); }
372 372 .settings-desc { font-size: var(--font-size-base); color: var(--content-secondary); margin-bottom: var(--gap-section); }
373 373
@@ -731,7 +731,7 @@
731 731 }
732 732
733 733 /* 10. (vacated: Quick Add Input was deleted 2026-05-24; the modal at
734 - keyboard.js:openQuickAddModal uses .form-input, not a custom
734 + keyboard.js:openQuickAddModal uses .field, not a custom
735 735 primitive. Section number kept for stable navigation.) */
736 736
737 737 /* 11. Cards */
@@ -1829,8 +1829,7 @@
1829 1829 display: none;
1830 1830 }
1831 1831
1832 - .recurrence-config .form-input,
1833 - .recurrence-config .form-select {
1832 + .recurrence-config .field {
1834 1833 font-size: var(--font-size-base);
1835 1834 padding: var(--gap-bound) var(--gap-peer);
1836 1835 }
@@ -1843,47 +1842,55 @@
1843 1842 margin-bottom: var(--gap-bound);
1844 1843 }
1845 1844
1846 - .form-input,
1847 - .form-select,
1848 - .form-textarea {
1845 + /* One class for all three kinds, which is what the shared rule always was.
1846 + Surface and inset edge come from the generated layout.css: a field is a
1847 + Depth::Well, so it is filled with --surface-well.
1848 +
1849 + This file used to say `background-color: var(--surface-raised)` under
1850 + `box-shadow: var(--bevel-inset)` -- a raised fill beneath a recessed edge, on
1851 + every input, select and textarea in the app, with :focus re-asserting it. That
1852 + is the exact disagreement Depth exists to make unrepresentable, and taking the
1853 + depth from one place is what fixes it. */
1854 + .field {
1849 1855 width: 100%;
1850 1856 padding: var(--gap-peer) var(--gap-group);
1851 1857 border: var(--border-width-sm) solid var(--border);
1852 1858 border-radius: var(--radius-sm);
1853 - background-color: var(--surface-raised);
1854 1859 color: var(--content);
1855 1860 font-size: var(--font-size-lg);
1856 - box-shadow: var(--bevel-inset);
1857 1861 }
1858 1862
1859 - /* The focus ring sits outside the well, so the inset bevel stays under it. */
1860 - .form-input:focus,
1861 - .form-select:focus,
1862 - .form-textarea:focus {
1863 + /* The focus ring sits outside the well, so the inset bevel stays under it.
1864 + Composed rather than replacing, because box-shadow is not additive. */
1865 + .field:focus {
1863 1866 outline: none;
1864 - background-color: var(--surface-raised);
1865 1867 box-shadow: var(--bevel-inset), 0 0 0 2px var(--action);
1866 1868 }
1867 1869
1868 - .form-textarea {
1870 + /* Kind rides on the element, not on a second class. Every one of the 55 call
1871 + sites had the old .form-input class on an <input>, .form-select on a
1872 + <select> and .form-textarea on a <textarea>, with no exceptions, so the class
1873 + was saying what the tag already said. An element selector cannot be forgotten at a call
1874 + site the way a modifier class can. */
1875 + textarea.field {
1869 1876 min-height: 100px;
1870 1877 resize: vertical;
1871 1878 }
1872 1879
1873 1880 /* Ghost variants, borderless / transparent inputs used in toolbar
1874 1881 contexts (compose header rows). Strips chrome and reduces padding. */
1875 - .form-input--ghost {
1882 + .field--ghost {
1876 1883 background: transparent;
1877 1884 border: none;
1878 1885 box-shadow: none;
1879 1886 padding: var(--gap-peer);
1880 1887 font-size: var(--font-size-base);
1881 1888 }
1882 - .form-input--ghost:focus { background: transparent; box-shadow: none; }
1883 - .form-select--ghost {
1889 + .field--ghost:focus { background: transparent; box-shadow: none; }
1890 + /* A ghost select still needs a surface: it keeps its native dropdown arrow,
1891 + which needs something behind it. */
1892 + select.field--ghost {
1884 1893 background: var(--surface-page);
1885 - padding: var(--gap-peer);
1886 - font-size: var(--font-size-base);
1887 1894 }
1888 1895
1889 1896 .form-actions {
@@ -1894,16 +1901,15 @@
1894 1901 }
1895 1902
1896 1903 /* Form Validation */
1897 - .form-input[aria-invalid="true"],
1898 - .form-select[aria-invalid="true"],
1899 - .form-textarea[aria-invalid="true"] {
1904 + /* The generated layout.css carries .field[aria-invalid="true"], keyed on the
1905 + ARIA attribute so the visual and accessible states cannot drift, and composing
1906 + the danger ring after the bevel so the well stays under it. Left here: the
1907 + border colour, and the stronger ring while focused. */
1908 + .field[aria-invalid="true"] {
1900 1909 border-color: var(--danger);
1901 - box-shadow: var(--bevel-inset), 0 0 0 2px color-mix(in srgb, var(--danger) 30%, transparent);
1902 1910 }
1903 1911
1904 - .form-input[aria-invalid="true"]:focus,
1905 - .form-select[aria-invalid="true"]:focus,
1906 - .form-textarea[aria-invalid="true"]:focus {
1912 + .field[aria-invalid="true"]:focus {
1907 1913 box-shadow: var(--bevel-inset), 0 0 0 2px var(--danger);
1908 1914 }
1909 1915
@@ -2771,7 +2777,7 @@
2771 2777 NOTE on naming: .header-row / .header-label refer to the compose
2772 2778 form's "header fields" (To, Cc, Subject), NOT the app header. Don't
2773 2779 rename without grepping composeForm.buildFieldsHtml. The input/select
2774 - inside each row use .form-input--ghost / .form-select--ghost. */
2780 + inside each row use .field--ghost / .field--ghost. */
2775 2781 .header-row {
2776 2782 display: flex;
2777 2783 align-items: center;
@@ -3458,9 +3464,7 @@
3458 3464 /* 28. Focus & Keyboard Accessibility */
3459 3465 .tab:focus-visible,
3460 3466 .button:focus-visible,
3461 - .form-input:focus-visible,
3462 - .form-select:focus-visible,
3463 - .form-textarea:focus-visible,
3467 + .field:focus-visible,
3464 3468 .card:focus-visible,
3465 3469 .email-item:focus-visible,
3466 3470 .task-row:focus-visible,
@@ -5194,18 +5198,17 @@
5194 5198 gap: var(--gap-peer);
5195 5199 align-items: center;
5196 5200 }
5197 - .email-filter-row > .form-input,
5198 - .email-filter-row > .form-select {
5201 + .email-filter-row > .field {
5199 5202 width: 100%;
5200 5203 min-width: 0;
5201 5204 }
5202 - .email-filter-row > .form-select {
5205 + .email-filter-row > select.field {
5203 5206 width: 9rem;
5204 5207 }
5205 5208
5206 5209 /* Contacts filter row: same shape as .email-filter-row (search fills,
5207 5210 filter sizes to content, action button sizes to content). Tag filter
5208 - uses .filter-select rather than .form-select, but the layout rule is
5211 + uses .filter-select rather than .field, but the layout rule is
5209 5212 identical. */
5210 5213 .contacts-filter-row {
5211 5214 display: grid;
@@ -5213,7 +5216,7 @@
5213 5216 gap: var(--gap-peer);
5214 5217 align-items: center;
5215 5218 }
5216 - .contacts-filter-row > .form-input {
5219 + .contacts-filter-row > .field {
5217 5220 width: 100%;
5218 5221 min-width: 0;
5219 5222 }
@@ -5231,7 +5234,7 @@
5231 5234 gap: var(--gap-bound);
5232 5235 align-items: center;
5233 5236 }
5234 - .work-hours-row > .form-select {
5237 + .work-hours-row > select.field {
5235 5238 width: auto;
5236 5239 }
5237 5240
@@ -7570,12 +7573,12 @@
7570 7573 }
7571 7574
7572 7575 /* Email filter row: let selects shrink and wrap instead of forcing 120px. */
7573 - .ui-mode-mobile #emails-view .form-select[id^="email-"] {
7576 + .ui-mode-mobile #emails-view select.field[id^="email-"] {
7574 7577 min-width: 0 !important;
7575 7578 flex: 1;
7576 7579 }
7577 7580
7578 - .ui-mode-mobile .form-textarea {
7581 + .ui-mode-mobile textarea.field {
7579 7582 min-height: 72px;
7580 7583 }
7581 7584 .ui-mode-mobile .modal-header h2, .ui-mode-mobile .modal-title {
@@ -8900,7 +8903,7 @@
8900 8903 margin-top: var(--gap-peer);
8901 8904 }
8902 8905
8903 - .task-overview-add-form .form-input {
8906 + .task-overview-add-form .field {
8904 8907 flex: 1;
8905 8908 }
8906 8909
@@ -9434,7 +9437,7 @@
9434 9437
9435 9438 /* Saved Views modal (Run #27 UX) */
9436 9439 .saved-views-save { display: flex; gap: var(--gap-peer); align-items: center; flex-wrap: wrap; margin-bottom: var(--gap-section); }
9437 - .saved-views-save .form-input { flex: 1 1 auto; min-width: 160px; }
9440 + .saved-views-save .field { flex: 1 1 auto; min-width: 160px; }
9438 9441 .saved-views-row { display: flex; gap: var(--gap-peer); align-items: center; }
9439 9442 .saved-views-row .saved-views-apply { flex: 1 1 auto; text-align: left; }
9440 9443 .saved-views-empty { color: var(--content-muted); }
@@ -433,7 +433,7 @@
433 433 <p class="confirm-message">${esc(message)}</p>
434 434 </div>
435 435 <div class="form-group">
436 - <input type="text" class="form-input" id="${inputId}"
436 + <input type="text" class="field" id="${inputId}"
437 437 value="${escAttr(defaultValue)}"
438 438 placeholder="${escAttr(placeholder)}"
439 439 ${suggestions.length ? `list="${listId}" autocomplete="off"` : ''}
@@ -184,7 +184,7 @@
184 184
185 185 switch (kind) {
186 186 case 'textarea':
187 - inputHtml = `<textarea class="form-textarea" id="${inputId}" name="${field.name}" ${required} ${placeholder} ${extraAttrs}>${esc(value)}</textarea>`;
187 + inputHtml = `<textarea class="field" id="${inputId}" name="${field.name}" ${required} ${placeholder} ${extraAttrs}>${esc(value)}</textarea>`;
188 188 break;
189 189 case 'select': {
190 190 const options = field.options || [];
@@ -206,7 +206,7 @@
206 206 const selected = (opt.selected || opt.value === value) ? 'selected' : '';
207 207 return `<option value="${escAttr(opt.value)}" ${selected}>${esc(opt.label)}</option>`;
208 208 }).join('');
209 - inputHtml = `<select class="form-select" id="${inputId}" name="${field.name}" ${required} ${extraAttrs}>${strayHtml}${optionsHtml}</select>`;
209 + inputHtml = `<select class="field" id="${inputId}" name="${field.name}" ${required} ${extraAttrs}>${strayHtml}${optionsHtml}</select>`;
210 210 break;
211 211 }
212 212 case 'checkbox':
@@ -214,7 +214,7 @@
214 214 inputHtml = `<label class="form-checkbox-label"><input type="checkbox" id="${inputId}" name="${field.name}" ${value ? 'checked' : ''} ${extraAttrs}><span>${esc(field.label || '')}</span></label>`;
215 215 break;
216 216 default:
217 - inputHtml = `<input type="${kind}" class="form-input" id="${inputId}" name="${field.name}" ${required} ${placeholder} value="${escAttr(value)}" ${extraAttrs}>`;
217 + inputHtml = `<input type="${kind}" class="field" id="${inputId}" name="${field.name}" ${required} ${placeholder} value="${escAttr(value)}" ${extraAttrs}>`;
218 218 }
219 219
220 220 const hintText = field.hint ? `<div class="form-hint">${esc(field.hint)}</div>` : '';
@@ -187,26 +187,26 @@
187 187 return `
188 188 <div class="header-row">
189 189 <label class="header-label" for="from-account">From:</label>
190 - <select class="form-select form-select--ghost flex-1" id="from-account" name="from-account" required>
190 + <select class="field field--ghost flex-1" id="from-account" name="from-account" required>
191 191 ${accountOptionsHtml}
192 192 </select>
193 193 </div>
194 194 <div class="header-row">
195 195 <label class="header-label" for="to-address">To:</label>
196 196 <div class="autocomplete-wrapper">
197 - <input type="text" class="form-input form-input--ghost flex-1" id="to-address" name="to-address" placeholder="recipient@example.com (comma-separated)" required autocomplete="off" value="${escAttr(prefill.to || '')}">
197 + <input type="text" class="field field--ghost flex-1" id="to-address" name="to-address" placeholder="recipient@example.com (comma-separated)" required autocomplete="off" value="${escAttr(prefill.to || '')}">
198 198 </div>
199 199 </div>
200 200 <div class="header-row${ccRowHidden}" id="cc-row">
201 201 <label class="header-label" for="cc-address">CC:</label>
202 202 <div class="autocomplete-wrapper">
203 - <input type="text" class="form-input form-input--ghost flex-1" id="cc-address" name="cc-address" placeholder="cc@example.com (comma-separated)" autocomplete="off" value="${escAttr(prefill.cc || '')}">
203 + <input type="text" class="field field--ghost flex-1" id="cc-address" name="cc-address" placeholder="cc@example.com (comma-separated)" autocomplete="off" value="${escAttr(prefill.cc || '')}">
204 204 </div>
205 205 </div>
206 206 <div class="header-row${ccRowHidden}" id="bcc-row">
207 207 <label class="header-label" for="bcc-address">BCC:</label>
208 208 <div class="autocomplete-wrapper">
209 - <input type="text" class="form-input form-input--ghost flex-1" id="bcc-address" name="bcc-address" placeholder="bcc@example.com (comma-separated)" autocomplete="off" value="${escAttr(prefill.bcc || '')}">
209 + <input type="text" class="field field--ghost flex-1" id="bcc-address" name="bcc-address" placeholder="bcc@example.com (comma-separated)" autocomplete="off" value="${escAttr(prefill.bcc || '')}">
210 210 </div>
211 211 </div>
212 212 <div class="header-row header-row--tight">
@@ -215,7 +215,7 @@
215 215 </div>
216 216 <div class="header-row">
217 217 <label class="header-label" for="subject">Subject:</label>
218 - <input type="text" class="form-input form-input--ghost flex-1" id="subject" name="subject" placeholder="Subject" value="${escAttr(prefill.subject || '')}">
218 + <input type="text" class="field field--ghost flex-1" id="subject" name="subject" placeholder="Subject" value="${escAttr(prefill.subject || '')}">
219 219 </div>
220 220 <div class="body-container">
221 221 <textarea class="body-textarea" id="body" name="body"${bodyRowsAttr} placeholder="Write your message...">${esc(prefill.body || '')}</textarea>
@@ -289,7 +289,7 @@
289 289 return `
290 290 <div class="form-group">
291 291 <label class="form-label" for="${escAttr(f.id)}">${esc(f.label)}</label>
292 - <input type="${escAttr(f.type)}" class="form-input" id="${escAttr(f.id)}" name="${escAttr(f.name)}"${f.required ? ' required' : ''}${f.placeholder ? ` placeholder="${escAttr(f.placeholder)}"` : ''}>
292 + <input type="${escAttr(f.type)}" class="field" id="${escAttr(f.id)}" name="${escAttr(f.name)}"${f.required ? ' required' : ''}${f.placeholder ? ` placeholder="${escAttr(f.placeholder)}"` : ''}>
293 293 </div>`;
294 294 }).join('');
295 295
@@ -119,7 +119,7 @@
119 119 <form id="painted-event-form">
120 120 <div class="form-group">
121 121 <label class="form-label">What to create</label>
122 - <select class="form-select" name="item_mode" data-change="dayPlan.togglePaintMode" data-a1="@el">
122 + <select class="field" name="item_mode" data-change="dayPlan.togglePaintMode" data-a1="@el">
123 123 <option value="event">Event</option>
124 124 <option value="block">Time Block</option>
125 125 <option value="task">Link to Task</option>
@@ -129,7 +129,7 @@
129 129 <div id="paint-task-fields" class="hidden">
130 130 <div class="form-group">
131 131 <label class="form-label">Task</label>
132 - <select class="form-select" name="linked_task_id">
132 + <select class="field" name="linked_task_id">
133 133 <option value="">-- Select a task --</option>
134 134 ${taskOptions}
135 135 </select>
@@ -139,7 +139,7 @@
139 139 <div id="paint-block-fields" class="hidden">
140 140 <div class="form-group">
141 141 <label class="form-label">Block Type</label>
142 - <select class="form-select" name="block_type">
142 + <select class="field" name="block_type">
143 143 <option value="free_time">Free Time</option>
144 144 <option value="personal">Personal</option>
145 145 <option value="vacation">Vacation</option>
@@ -151,11 +151,11 @@
151 151 <div id="standalone-event-fields">
152 152 <div class="form-group">
153 153 <label class="form-label">Title</label>
154 - <input type="text" class="form-input" name="title" placeholder="Event title">
154 + <input type="text" class="field" name="title" placeholder="Event title">
155 155 </div>
156 156 <div class="form-group" id="paint-description-group">
157 157 <label class="form-label">Description</label>
158 - <textarea class="form-textarea" name="description" placeholder="Details..."></textarea>
158 + <textarea class="field" name="description" placeholder="Details..."></textarea>
159 159 </div>
160 160 </div>
161 161
@@ -163,19 +163,19 @@
163 163 <div class="form-row" style="display: grid; grid-template-columns: 1fr 1fr; gap: var(--gap-group);">
164 164 <div class="form-group">
165 165 <label class="form-label">Start</label>
166 - <input type="datetime-local" class="form-input" name="start_time" value="${startISO}"
166 + <input type="datetime-local" class="field" name="start_time" value="${startISO}"
167 167 data-change="dayPlan.updatePaintTimePreview">
168 168 </div>
169 169 <div class="form-group">
170 170 <label class="form-label">End</label>
171 - <input type="datetime-local" class="form-input" name="end_time" value="${endISO}"
171 + <input type="datetime-local" class="field" name="end_time" value="${endISO}"
172 172 data-change="dayPlan.updatePaintTimePreview">
173 173 </div>
174 174 </div>
175 175
176 176 <div class="form-group" id="paint-location-group">
177 177 <label class="form-label">Location</label>
178 - <input type="text" class="form-input" name="location" placeholder="Zoom / Office / etc.">
178 + <input type="text" class="field" name="location" placeholder="Zoom / Office / etc.">
179 179 </div>
180 180
181 181 <div class="form-actions">
@@ -49,7 +49,7 @@
49 49
50 50 <div class="form-group">
51 51 <label class="form-label" for="schedule-datetime">Or Choose Custom</label>
52 - <input type="datetime-local" id="schedule-datetime" class="form-input"
52 + <input type="datetime-local" id="schedule-datetime" class="field"
53 53 min="${now.toISOString().slice(0, 16)}"
54 54 value="${now.toISOString().slice(0, 16)}">
55 55 </div>
@@ -81,7 +81,7 @@
81 81 ${ff({ kind: 'text', name: 'account_name', id: `${idPrefix}-name`, label: 'Account Name', value: values.accountName || '', placeholder: 'Personal, Work, etc.', required: true })}
82 82 <div class="form-group">
83 83 <label class="form-label" for="${idPrefix}-email">Email Address</label>
84 - <input type="email" class="form-input" id="${idPrefix}-email" name="email_address" required placeholder="you@example.com" value="${escAttr(values.emailAddress || '')}">
84 + <input type="email" class="field" id="${idPrefix}-email" name="email_address" required placeholder="you@example.com" value="${escAttr(values.emailAddress || '')}">
85 85 <div id="${idPrefix}-detect-status" class="email-detect-status"></div>
86 86 <div id="${idPrefix}-detect-note" class="email-detect-note hidden"></div>
87 87 </div>
@@ -104,7 +104,7 @@
104 104 <form id="label-form" data-submit="emails._saveLabels" data-a1="${escAttr(emailId)}">
105 105 <div class="form-group">
106 106 <label class="form-label">Labels (comma-separated)</label>
107 - <input type="text" class="form-input" id="label-input" value="${escAttr((currentLabels || []).join(', '))}"
107 + <input type="text" class="field" id="label-input" value="${escAttr((currentLabels || []).join(', '))}"
108 108 placeholder="work, important, follow-up" autofocus>
109 109 ${existing.length > 0 ? `<div class="label-existing-line">Existing: ${existing.map(l => esc(l)).join(', ')}</div>` : ''}
110 110 </div>
@@ -146,7 +146,7 @@
146 146 <form data-submit="emails._doMoveToFolder" data-a1="${escAttr(emailId)}">
147 147 <div class="form-group">
148 148 <label class="form-label">Move to folder</label>
149 - <input type="text" class="form-input" id="move-folder-input" placeholder="INBOX, Archive, Sent, ..."
149 + <input type="text" class="field" id="move-folder-input" placeholder="INBOX, Archive, Sent, ..."
150 150 list="folder-suggestions" autofocus>
151 151 <datalist id="folder-suggestions">
152 152 ${folders.map(f => `<option value="${escAttr(f)}">`).join('')}
@@ -627,7 +627,7 @@
627 627 extraContent: (project ? `
628 628 <div class="form-group">
629 629 <label class="form-label">Project</label>
630 - <input type="text" class="form-input" value="${escAttr(project.name)}" disabled>
630 + <input type="text" class="field" value="${escAttr(project.name)}" disabled>
631 631 </div>
632 632 ` : '') + buildRemindersHtml(null),
633 633 });
@@ -305,7 +305,7 @@
305 305 <form id="quick-add-form" data-submit="keyboard.submitQuickAdd" data-a1="@event">
306 306 <div class="form-group">
307 307 <label class="form-label">Quick Add Task</label>
308 - <input type="text" class="form-input" name="text" required
308 + <input type="text" class="field" name="text" required
309 309 placeholder="Fix login bug @backend #urgent +H due:friday"
310 310 autofocus
311 311 data-input="keyboard._onQuickAddInput" data-a1="@el">
@@ -68,7 +68,7 @@
68 68 const fields = prompts.map(p => `
69 69 <div class="reflection-prompt">
70 70 <label class="form-label" for="${idPrefix}-${p.key}">${esc(p.label)}</label>
71 - <textarea id="${idPrefix}-${p.key}" class="reflection-textarea form-textarea" rows="3"
71 + <textarea id="${idPrefix}-${p.key}" class="reflection-textarea field" rows="3"
72 72 placeholder="${escAttr(p.placeholder || '')}">${esc(p.value || '')}</textarea>
73 73 </div>
74 74 `).join('');
@@ -401,7 +401,7 @@
401 401 <form id="link-email-form" data-submit="projects.submitLinkEmail" data-a1="@event">
402 402 <div class="form-group">
403 403 <label class="form-label">Select Email to Link</label>
404 - <select class="form-select" name="email_id" required>
404 + <select class="field" name="email_id" required>
405 405 <option value="">Choose an email...</option>
406 406 ${emailOptions}
407 407 </select>
@@ -478,7 +478,7 @@
478 478 <form id="share-project-form" data-submit="projects.submitShare" data-a1="@event" data-a2="${escAttr(id)}">
479 479 <div class="form-group">
480 480 <label class="form-label">Share with group</label>
481 - <select class="form-select" name="group_id" required>${options}</select>
481 + <select class="field" name="group_id" required>${options}</select>
482 482 <p class="form-hint">The whole project (tasks, events, milestones, and attachments) becomes visible to that group's members.</p>
483 483 </div>
484 484 <div class="form-actions">
@@ -57,7 +57,7 @@
57 57 let html = `
58 58 <div class="saved-views-modal">
59 59 <div class="saved-views-save">
60 - <input type="text" id="saved-view-name" class="form-input" placeholder="Name these filters" maxlength="100">
60 + <input type="text" id="saved-view-name" class="field" placeholder="Name these filters" maxlength="100">
61 61 <label class="form-checkbox-label"><input type="checkbox" id="saved-view-pin"> <span>Pin</span></label>
62 62 <button type="button" class="button button--primary" data-act="savedViews._save">Save current filters</button>
63 63 </div>`;