Skip to main content

max / goingson

Take row text colour from the generated row parts makeover-webview emits .row-primary, .row-secondary and .row-meta alongside the .row-actions that 3d24498 adopted. They are only the three content colours, and unlike .row-actions they are not scoped to a .row ancestor, so any list-row text can wear one -- the email search result, which has no hidden actions and so is not a .row, wears them too. Ten hand-written colour declarations go, one per row part: the task row's project, due and recurrence cells and its no-subtasks dash; the email item's from, subject, date and preview; the event row's date number, time and location. Parts are worn only where the colour is the rank and nothing else. The overdue description and due date, the recurring event's pattern label and the badges keep their own rules and out-specify the part class beside them. Row text with no colour declaration of its own gains no class: it already inherits --content, so the class would add a declaration rather than replace one. BUG FIXED, the same duplicate-name shape as the .progress-bar pair in 881476a: .task-project and .task-due were each defined twice at equal specificity, once for the tasks table and once for the review-card pill, and the pill won everywhere by sitting later in the file. The tasks table's project cell has been rendering as a small raised pill at --content-muted, and its due cell at font-size-sm, neither of which the table's own rules asked for -- the table rule set --content-secondary and font-size-base and was simply never in effect. The pill is now scoped to its two containers, .task-item and .event-item, so the table gets what it declared. Mobile UI mode already re-declared both cells at higher specificity and is unchanged. Two visible changes to eyeball, both on the tasks table: the project cell loses its pill background and goes up to base font size and --content-secondary, and the due cell goes back to the row's font size. Its colour is unchanged, muted, now said out loud by .row-meta instead of inherited from a rule for another component. MEASURES: minified 159629 -> 159405 bytes, ten declarations deleted for one added selector. 86 frontend tests pass, lint clean. Docs: design-system.md gains the row text parts and the rule for when an element does not get one.
Co-Authored-By
Claude Opus 5 (1M context) <noreply@anthropic.com>
Author: Max Johnson <me@maxj.phd> · 2026-07-30 02:51 UTC
Signed with PGP, not checked
Commit: f08b4bad96328e3425c70d7ee3a8228a079ea1e4
Parent: 3d24498
6 files changed, +47 insertions, -27 deletions
@@ -119,6 +119,24 @@
119 119 there is no hover, so `.row-actions` is shown unconditionally; any such override must
120 120 restore `pointer-events` as well as `opacity`.
121 121
122 + ### Row text parts: `.row-primary` / `.row-secondary` / `.row-meta`
123 + Generated too, and they are only the three content colours (`--content`,
124 + `--content-secondary`, `--content-muted`). Unlike `.row-actions` they are not scoped
125 + to a `.row` ancestor, so a list row that carries no hidden actions can still wear
126 + them: the email search result does.
127 +
128 + Put one on any row text whose colour is the hierarchy and nothing more, and delete
129 + the declaration it replaces. Worn today by the task row's project, due and
130 + recurrence cells and its no-subtasks dash; the email item's from, subject, date and
131 + preview; the event row's date number, time and location.
132 +
133 + A part is not worn where the colour means something other than rank. The overdue
134 + task description and due date, the recurring event's pattern label and every badge
135 + keep their own rule, and each of those out-specifies the part class it sits next to.
136 + A row element with no colour declaration of its own does not gain a part class
137 + either: it already inherits `--content`, so the class would add a declaration
138 + without replacing one.
139 +
122 140 **Canonical helper (to be added):** `GoingsOn.ui.renderRow(kind, model, opts)`. Each `renderXxx` above becomes a thin adapter that maps the model to the shared "icon · primary · secondary · meta · actions" slot layout. Surface audits in Phase 1+ assume this exists.
123 141
124 142 ### Task row state classes (composed onto `.task-row`)
@@ -153,6 +153,10 @@
153 153 DO write it on a list row that hides actions until hover: .row is the
154 154 ancestor .row-actions reveals from. Worn today by the task row, the email
155 155 item, the event row and the filled month goal.
156 + The three text parts are NOT scoped to that ancestor -- they are just the
157 + content, secondary and muted colours -- so wear .row-primary /
158 + -secondary / -meta on any list-row text whose colour is its rank, .row
159 + above it or not, and delete the declaration they replace.
156 160
157 161 COMPONENT PRIMITIVES
158 162 .button + .button--primary / --secondary / --danger / --sm
@@ -1164,9 +1168,11 @@
1164 1168 max-width: 100%;
1165 1169 }
1166 1170
1171 + /* The tasks-table project cell. Colour comes from the generated .row-secondary,
1172 + which the cell wears. Scoped away from the review-card pill of the same name
1173 + further down this file, which used to win here by source order alone. */
1167 1174 .task-project {
1168 1175 font-size: var(--font-size-base);
1169 - color: var(--content-secondary);
1170 1176 white-space: nowrap;
1171 1177 }
1172 1178
@@ -1357,7 +1363,6 @@
1357 1363 .event-cell-date .event-date-num {
1358 1364 font-weight: 700;
1359 1365 font-size: var(--font-size-base);
1360 - color: var(--content);
1361 1366 margin-right: var(--gap-bound);
1362 1367 }
1363 1368
@@ -1377,7 +1382,6 @@
1377 1382 .event-cell-time {
1378 1383 font-family: var(--font-mono);
1379 1384 font-size: var(--font-size-base);
1380 - color: var(--content-secondary);
1381 1385 }
1382 1386
1383 1387 .event-cell-title {
@@ -1385,7 +1389,6 @@
1385 1389 }
1386 1390
1387 1391 .event-cell-location {
1388 - color: var(--content-secondary);
1389 1392 font-size: var(--font-size-base);
1390 1393 }
1391 1394
@@ -1601,20 +1604,17 @@
1601 1604 }
1602 1605
1603 1606 .email-from {
1604 - color: var(--content);
1605 1607 font-size: var(--font-size-base);
1606 1608 font-weight: 600;
1607 1609 }
1608 1610
1609 1611 .email-date {
1610 - color: var(--content-muted);
1611 1612 font-size: var(--font-size-md);
1612 1613 flex-shrink: 0;
1613 1614 font-weight: 600;
1614 1615 }
1615 1616
1616 1617 .email-subject {
1617 - color: var(--content);
1618 1618 font-size: var(--font-size-lg);
1619 1619 margin-bottom: var(--gap-bound);
1620 1620 white-space: nowrap;
@@ -1623,7 +1623,6 @@
1623 1623 }
1624 1624
1625 1625 .email-preview {
1626 - color: var(--content-muted);
1627 1626 font-size: var(--font-size-base);
1628 1627 white-space: nowrap;
1629 1628 overflow: hidden;
@@ -4186,7 +4185,6 @@
4186 4185 }
4187 4186
4188 4187 .no-subtasks {
4189 - color: var(--content-secondary);
4190 4188 font-size: var(--font-size-base);
4191 4189 }
4192 4190
@@ -4992,7 +4990,6 @@
4992 4990
4993 4991 .task-recurrence {
4994 4992 font-size: var(--font-size-base);
4995 - color: var(--content-secondary);
4996 4993 }
4997 4994
4998 4995 .task-due {
@@ -6125,7 +6122,12 @@
6125 6122 font-size: var(--font-size-base);
6126 6123 }
6127 6124
6128 - .task-project {
6125 + /* Review-card pills. Scoped to their two containers: bare, these two rules also
6126 + painted the tasks-table cells of the same name -- same specificity, later in
6127 + the file -- so the table's project cell rendered as a small raised pill and
6128 + its due cell went muted, neither of which the table's own rules asked for. */
6129 + .task-item .task-project,
6130 + .event-item .task-project {
6129 6131 font-size: var(--font-size-sm);
6130 6132 padding: var(--step-tight) var(--step-base);
6131 6133 background: var(--surface-raised);
@@ -6133,7 +6135,7 @@
6133 6135 color: var(--content-muted);
6134 6136 }
6135 6137
6136 - .task-due {
6138 + .task-item .task-due {
6137 6139 font-size: var(--font-size-sm);
6138 6140 color: var(--content-muted);
6139 6141 }
@@ -45,13 +45,13 @@
45 45 </div>
46 46 <div class="email-content" data-act="emails.open" data-a1="${escAttr(e.id)}" role="button">
47 47 <div class="email-header">
48 - <span class="email-from">${esc(e.from)}</span>
48 + <span class="row-primary email-from">${esc(e.from)}</span>
49 49 ${threadBadge}
50 50 ${isSnoozed ? `<span class="snooze-badge" title="Snoozed until ${escAttr(e.snoozedUntilFormatted || '')}" aria-label="Snoozed until ${escAttr(e.snoozedUntilFormatted || '')}">Snoozed</span>` : ''}
51 - <span class="email-date">${e.receivedFormatted}</span>
51 + <span class="row-meta email-date">${e.receivedFormatted}</span>
52 52 </div>
53 - <div class="email-subject">${esc(e.subject)}${labelBadges ? ' ' + labelBadges : ''}</div>
54 - <div class="email-preview">${esc(e.bodyPreview)}...</div>
53 + <div class="row-primary email-subject">${esc(e.subject)}${labelBadges ? ' ' + labelBadges : ''}</div>
54 + <div class="row-meta email-preview">${esc(e.bodyPreview)}...</div>
55 55 </div>
56 56 <button class="button--icon row-actions kebab-btn" data-act="contextMenus.showEmail" data-a1="@event" data-a2="${escAttr(e.id)}" title="Actions" aria-label="Email actions">&#x22EE;</button>
57 57 </div>
@@ -455,9 +455,9 @@
455 455 tabindex="0" role="listitem">
456 456 <div class="email-content">
457 457 <div class="email-header">
458 - <span class="email-subject">${esc(r.title)}</span>
458 + <span class="row-primary email-subject">${esc(r.title)}</span>
459 459 </div>
460 - ${r.snippet ? `<div class="email-preview">${esc(r.snippet)}</div>` : ''}
460 + ${r.snippet ? `<div class="row-meta email-preview">${esc(r.snippet)}</div>` : ''}
461 461 </div>
462 462 </div>
463 463 `).join('');
@@ -543,9 +543,9 @@
543 543 data-contextmenu="contextMenus.showEvent" data-a1="@event" data-a2="${escAttr(e.id)}"
544 544 tabindex="0" role="row">
545 545 <div class="event-cell event-cell-date"><span class="event-recurrence-pattern">${esc(patternLabel)}</span></div>
546 - <div class="event-cell event-cell-time">${e.timeFormatted}</div>
546 + <div class="event-cell row-secondary event-cell-time">${e.timeFormatted}</div>
547 547 <div class="event-cell event-cell-title">${esc(displayTitle)}</div>
548 - <div class="event-cell event-cell-location">${e.location ? esc(e.location) : '-'}</div>
548 + <div class="event-cell row-secondary event-cell-location">${e.location ? esc(e.location) : '-'}</div>
549 549 <div class="event-cell" style="text-align: right;" data-act="ui.noop">
550 550 <button class="button--icon row-actions kebab-btn" data-act="contextMenus.showEvent" data-a1="@event" data-a2="${escAttr(e.id)}" title="Actions" aria-label="Event actions">&#x22EE;</button>
551 551 </div>
@@ -584,12 +584,12 @@
584 584 aria-label="Select event">
585 585 </div>
586 586 <div class="event-cell event-cell-date">
587 - <span class="event-date-num">${startDate.getDate()} ${monthName}</span>
587 + <span class="row-primary event-date-num">${startDate.getDate()} ${monthName}</span>
588 588 <span class="event-date-badge event-proximity-${e.proximityClass || 'default'}">${e.proximityLabel || ''}</span>
589 589 </div>
590 - <div class="event-cell event-cell-time">${e.timeFormatted}</div>
590 + <div class="event-cell row-secondary event-cell-time">${e.timeFormatted}</div>
591 591 <div class="event-cell event-cell-title">${esc(displayTitle)}</div>
592 - <div class="event-cell event-cell-location">${e.location ? esc(e.location) : '-'}</div>
592 + <div class="event-cell row-secondary event-cell-location">${e.location ? esc(e.location) : '-'}</div>
593 593 <div class="event-cell" style="text-align: right;" data-act="ui.noop">
594 594 <button class="button--icon row-actions kebab-btn" data-act="contextMenus.showEvent" data-a1="@event" data-a2="${escAttr(e.id)}" title="Actions" aria-label="Event actions">&#x22EE;</button>
595 595 </div>
@@ -157,10 +157,10 @@
157 157 ${t.contactName ? `<span class="contact-badge" title="${escAttrVal(t.contactName)}">${esc(t.contactName)}</span>` : ''}
158 158 ${t.isSnoozed ? `<span class="snooze-badge" title="Snoozed until ${escAttr(t.snoozedUntilFormatted || '')}" aria-label="Snoozed until ${escAttr(t.snoozedUntilFormatted || '')}">Snoozed</span>` : ''}
159 159 </div>
160 - <div class="task-cell task-project">${esc(t.projectName) || '-'}${GoingsOn.groups.taskSharedBadge(t.projectId)}</div>
160 + <div class="task-cell row-secondary task-project">${esc(t.projectName) || '-'}${GoingsOn.groups.taskSharedBadge(t.projectId)}</div>
161 161 <div class="task-cell priority-${t.priority.toLowerCase()}" aria-label="Priority ${t.priority}">${t.priority.charAt(0)}</div>
162 - <div class="task-cell task-due">${t.dueFormatted || '-'}</div>
163 - <div class="task-cell task-recurrence">${formatRecurrence(t)}</div>
162 + <div class="task-cell row-meta task-due">${t.dueFormatted || '-'}</div>
163 + <div class="task-cell row-secondary task-recurrence">${formatRecurrence(t)}</div>
164 164 <div class="task-cell task-progress">
165 165 ${t.subtaskCount > 0 ? `
166 166 <div class="progress" title="${t.subtaskCompleted}/${t.subtaskCount} subtasks"
@@ -168,7 +168,7 @@
168 168 aria-label="${t.subtaskCompleted} of ${t.subtaskCount} subtasks completed">
169 169 <div class="progress-fill" data-tone="success" style="width: ${progress}%"></div>
170 170 </div>
171 - ` : '<span class="no-subtasks">-</span>'}
171 + ` : '<span class="row-secondary no-subtasks">-</span>'}
172 172 </div>
173 173 <div class="task-cell task-actions-cell">
174 174 ${renderRowActions(t, isStarted)}