Skip to main content

max / goingson

Give progress bars one name each, and take their depth from the generated sheet Adopting progress meant first untangling it. There were four independent trough-and-fill pairs using three fill colours and two trough surfaces, no two agreeing, and two live bugs falling out of the overlap. Bug 1: .progress-bar was defined twice with contradictory meanings. At the subtask section it was a FILL (height 100%, background --success); in the estimate section it was a TROUGH (height 6px, --surface-overlay, bordered, overflow hidden). Both bare, identical specificity, and the minifier keeps them separate, so the later trough rule won everywhere. Three call sites put class="progress-bar" inside a container expecting the green full-height fill and instead rendered a 6px bordered sliver of the wrong colour: task-list subtask progress, project subtask progress, and milestone progress. Bug 2: .milestone-progress was styled as a trough -- 6px, bordered, overflow: hidden -- while the markup used it as a wrapper holding a full 10px trough AND the completed/total count. The inner trough was clipped to 6px and the count was clipped away entirely. Both are the same failure, and it is the one a shared vocabulary exists to prevent: a name meaning two things, with nothing to catch it. Now one name each. .progress is the trough and takes its surface and inset edge from the generated layout.css, so this file keeps only geometry: the base plus --slim, --mini and --focus for the four sizes that actually differ. .progress-fill is the fill, and says what it means with data-tone rather than through a parent class, so the .over-estimate override on the estimate bar becomes data-tone="danger" at the call site. .over-estimate stays in use on .task-time-badge, which is a different thing. Eleven call sites moved across six files, plus a runtime querySelector in focus-timer.js that updated .focus-progress-fill by name, and a :has(.progress-bar-container) selector in the mobile rules. Visible changes to eyeball: the three call sites from bug 1 render correctly for the first time, milestone progress gets its count back, and the mini and focus bars move from --surface-sunken to --surface-well and gain an inset bevel, which is the same trade button, card and tab already took.
Co-Authored-By
Claude Opus 5 (1M context) <noreply@anthropic.com>
Author: Max Johnson <me@maxj.phd> · 2026-07-29 23:30 UTC
Signed with PGP, not checked
Commit: 881476adb034629557691c3fdeb49d4ae8e3572d
Parent: 914eec7
7 files changed, +82 insertions, -59 deletions
M Cargo.lock +2 -2
@@ -3396,9 +3396,9 @@
3396 3396
3397 3397 [[package]]
3398 3398 name = "makeover-webview"
3399 - version = "0.3.0"
3399 + version = "0.4.0"
3400 3400 source = "registry+https://github.com/rust-lang/crates.io-index"
3401 - checksum = "d6c4dfda8103ba1abaeebd41c3fbb44678f3ab51a8e54a4002d3a76bc744e44c"
3401 + checksum = "f9b3d9f5b5e7f6bc4ded9b6c14185ce5348d02882ec3260642ce400a5ad0a89f"
3402 3402 dependencies = [
3403 3403 "makeover-layout",
3404 3404 ]
@@ -4114,19 +4114,48 @@
4114 4114 }
4115 4115
4116 4116 /* A trough, so it reads inset. The fill inside stays flat: at 10px tall a
4117 - bevel on the bar itself would eat most of its height. */
4118 - .progress-bar-container {
4117 + bevel on the bar itself would eat most of its height.
4118 +
4119 + The surface and the inset edge come from the generated layout.css, which
4120 + makes this a Depth::Well. Everything below is geometry.
4121 +
4122 + One name, one meaning, which is the whole reason this section was rewritten:
4123 + `.progress-bar` used to be defined twice, as a fill here and as a trough in
4124 + the estimate section, so the later rule silently won and three call sites
4125 + rendered their fill as a 6px bordered sliver of the wrong colour. */
4126 + .progress {
4119 4127 width: 100%;
4120 4128 height: 10px;
4121 - background: var(--surface-overlay);
4122 4129 border: var(--border-width-sm) solid var(--border);
4123 - box-shadow: var(--bevel-inset);
4124 4130 overflow: hidden;
4125 4131 }
4126 4132
4127 - .progress-bar {
4133 + /* Bordered, and shorter. The task-overview estimate bar. */
4134 + .progress--slim {
4135 + height: 6px;
4136 + margin-bottom: var(--gap-section);
4137 + }
4138 +
4139 + /* Bare: no border, because at 3px a border is most of the height. */
4140 + .progress--mini {
4141 + height: 3px;
4142 + border: none;
4143 + border-radius: var(--radius-xs);
4144 + margin-top: var(--gap-peer);
4145 + }
4146 +
4147 + /* Bare, and the focus timer's own spacing below it. */
4148 + .progress--focus {
4149 + height: 6px;
4150 + border: none;
4151 + border-radius: var(--radius-xs);
4152 + margin-bottom: var(--gap-pane);
4153 + }
4154 +
4155 + /* The fill. Colour comes from the generated rules: untoned is --action, and
4156 + data-tone carries success or danger where the bar means something. */
4157 + .progress-fill {
4128 4158 height: 100%;
4129 - background: var(--success);
4130 4159 }
4131 4160
4132 4161 .no-subtasks {
@@ -6998,12 +7027,25 @@
6998 7027 background: var(--surface-overlay);
6999 7028 }
7000 7029
7030 + /* A row holding a trough and its count, not a trough itself. It was styled as
7031 + one -- 6px, bordered, overflow: hidden -- while the markup put a full
7032 + .progress-bar-container AND a .milestone-progress-text span inside it, so the
7033 + inner trough was clipped to 6px and the count was clipped away entirely.
7034 + Wrapper only now; the trough inside it is .progress.progress--slim. */
7001 7035 .milestone-progress {
7002 - height: 6px;
7003 - background: var(--surface-overlay);
7004 - border-radius: var(--radius-full);
7005 - overflow: hidden;
7006 - border: var(--border-width-sm) solid var(--border);
7036 + display: flex;
7037 + align-items: center;
7038 + gap: var(--gap-bound);
7039 + }
7040 +
7041 + .milestone-progress > .progress {
7042 + flex: 1 1 auto;
7043 + }
7044 +
7045 + .milestone-progress-text {
7046 + flex: 0 0 auto;
7047 + color: var(--content-secondary);
7048 + font-size: var(--font-size-md);
7007 7049 }
7008 7050
7009 7051 .milestone-actions {
@@ -7651,7 +7693,7 @@
7651 7693 }
7652 7694
7653 7695 /* Show subtask progress inline if present */
7654 - .ui-mode-mobile .task-cell.task-progress:has(.progress-bar-container) {
7696 + .ui-mode-mobile .task-cell.task-progress:has(.progress) {
7655 7697 display: flex !important;
7656 7698 order: 5;
7657 7699 }
@@ -8111,8 +8153,9 @@
8111 8153 .kanban-card-meta { font-size: var(--font-size-sm); color: var(--content-secondary); display: flex; gap: var(--gap-peer); flex-wrap: wrap; }
8112 8154 .kanban-card-due.overdue { color: var(--danger); font-weight: 600; }
8113 8155
8114 - .progress-bar-mini { height: 3px; background: var(--surface-sunken); border-radius: var(--radius-xs); margin-top: var(--gap-peer); }
8115 - .progress-bar-mini .progress-fill { height: 100%; background: var(--success); border-radius: var(--radius-xs); }
8156 + /* Was .progress-bar-mini + its own fill. Now .progress.progress--mini with a
8157 + data-tone="success" fill, so only the corner rounding is left to say. */
8158 + .progress--mini > .progress-fill { border-radius: var(--radius-xs); }
8116 8159
8117 8160 /* Kanban Responsive */
8118 8161 .ui-mode-mobile .kanban-board { grid-template-columns: 1fr; }
@@ -8249,19 +8292,9 @@
8249 8292 color: var(--content);
8250 8293 }
8251 8294
8252 - .focus-progress-bar {
8253 - height: 6px;
8254 - background: var(--surface-sunken);
8255 - border-radius: var(--radius-xs);
8256 - margin-bottom: var(--gap-pane);
8257 - overflow: hidden;
8258 - }
8259 -
8260 - .focus-progress-fill {
8261 - height: 100%;
8262 - background: var(--action);
8263 - border-radius: var(--radius-xs);
8264 - }
8295 + /* Was .focus-progress-bar + .focus-progress-fill. Now
8296 + .progress.progress--focus with an untoned fill, which is --action already. */
8297 + .progress--focus > .progress-fill { border-radius: var(--radius-xs); }
8265 8298
8266 8299 .focus-task-name {
8267 8300 color: var(--content-secondary);
@@ -8914,22 +8947,12 @@
8914 8947 border-bottom: var(--border-width-sm) solid var(--border);
8915 8948 }
8916 8949
8917 - .progress-bar {
8918 - height: 6px;
8919 - background: var(--surface-overlay);
8920 - border: var(--border-width-sm) solid var(--border);
8921 - margin-bottom: var(--gap-section);
8922 - overflow: hidden;
8923 - }
8924 -
8925 - .progress-fill {
8926 - height: 100%;
8927 - background: var(--success);
8928 - }
8929 -
8930 - .progress-bar.over-estimate .progress-fill {
8931 - background: var(--danger);
8932 - }
8950 + /* Was a second, conflicting `.progress-bar` (a trough) plus its own fill and an
8951 + .over-estimate override. All three are gone: the trough is
8952 + .progress.progress--slim, and the fill says what it means with
8953 + data-tone="success" or data-tone="danger" rather than through a parent class.
8954 + The .over-estimate class stays in use on .task-time-badge, which is a
8955 + different thing entirely. */
8933 8956
8934 8957
8935 8958 /* Task overview stats in mobile UI, 2 columns instead of 4. */
@@ -38,7 +38,7 @@
38 38 </div>
39 39 </div>
40 40 <div class="focus-countdown"></div>
41 - <div class="focus-progress-bar"><div class="focus-progress-fill"></div></div>
41 + <div class="progress progress--focus"><div class="progress-fill"></div></div>
42 42 <div class="focus-task-name"></div>
43 43 <div class="focus-actions">
44 44 <button class="button button--primary focus-stop-btn">Stop</button>
@@ -97,7 +97,7 @@
97 97 const progress = totalSeconds > 0
98 98 ? ((totalSeconds - remainingSeconds) / totalSeconds) * 100
99 99 : 0;
100 - overlay.querySelector('.focus-progress-fill').style.width = `${progress}%`;
100 + overlay.querySelector('.progress-fill').style.width = `${progress}%`;
101 101 }
102 102
103 103 // Timer Logic
@@ -73,9 +73,9 @@
73 73 ${GoingsOn.tasks.renderTaskBadges(t)}
74 74 </div>
75 75 ${t.subtaskCount > 0 ? `
76 - <div class="progress-bar-container" style="margin: var(--gap-peer) 0;" title="${t.subtaskCompleted}/${t.subtaskCount} subtasks"
76 + <div class="progress" style="margin: var(--gap-peer) 0;" title="${t.subtaskCompleted}/${t.subtaskCount} subtasks"
77 77 role="progressbar" aria-valuenow="${progress}" aria-valuemin="0" aria-valuemax="100">
78 - <div class="progress-bar" style="width: ${progress}%"></div>
78 + <div class="progress-fill" data-tone="success" style="width: ${progress}%"></div>
79 79 </div>
80 80 ` : ''}
81 81 <div class="text-sm text-secondary">
@@ -188,8 +188,8 @@
188 188 ${m.targetDate ? `<span class="milestone-date">${esc(m.targetDate)}</span>` : ''}
189 189 </div>
190 190 <div class="milestone-progress">
191 - <div class="progress-bar-container" title="${m.completedCount}/${m.taskCount} tasks">
192 - <div class="progress-bar" style="width: ${m.progress}%"></div>
191 + <div class="progress progress--slim" title="${m.completedCount}/${m.taskCount} tasks">
192 + <div class="progress-fill" data-tone="success" style="width: ${m.progress}%"></div>
193 193 </div>
194 194 <span class="milestone-progress-text">${m.completedCount}/${m.taskCount}</span>
195 195 </div>
@@ -357,7 +357,7 @@
357 357
358 358 if (total > 0) {
359 359 const pct = t.subtaskProgress ?? 0; // pre-computed in Rust (TaskResponse.subtaskProgress)
360 - html += `<div class="progress-bar"><div class="progress-fill" style="width: ${pct}%"></div></div>`;
360 + html += `<div class="progress progress--slim"><div class="progress-fill" data-tone="success" style="width: ${pct}%"></div></div>`;
361 361 }
362 362
363 363 html += '<div class="task-overview-subtask-list">';
@@ -417,8 +417,8 @@
417 417
418 418 if (t.estimatedMinutes && t.estimatedMinutes > 0) {
419 419 const pct = t.timeProgress ?? 0; // pre-computed in Rust (TaskResponse.timeProgress)
420 - const overClass = t.isOverEstimate ? ' over-estimate' : '';
421 - html += `<div class="progress-bar${overClass}"><div class="progress-fill" style="width: ${pct}%"></div></div>`;
420 + const estimateTone = t.isOverEstimate ? 'danger' : 'success';
421 + html += `<div class="progress progress--slim"><div class="progress-fill" data-tone="${estimateTone}" style="width: ${pct}%"></div></div>`;
422 422 }
423 423
424 424 if (sessions.length > 0) {
@@ -66,7 +66,7 @@
66 66 ${task.projectName ? `<span class="kanban-card-project">${esc(task.projectName)}</span>` : ''}
67 67 ${task.dueFormatted ? `<span class="kanban-card-due ${task.isOverdue ? 'overdue' : ''}">${esc(task.dueFormatted)}</span>` : ''}
68 68 </div>
69 - ${task.subtaskCount > 0 ? `<div class="progress-bar-mini"><div class="progress-fill" style="width:${progress}%"></div></div>` : ''}
69 + ${task.subtaskCount > 0 ? `<div class="progress progress--mini"><div class="progress-fill" data-tone="success" style="width:${progress}%"></div></div>` : ''}
70 70 </div>`;
71 71 }
72 72
@@ -163,10 +163,10 @@
163 163 <div class="task-cell task-recurrence">${formatRecurrence(t)}</div>
164 164 <div class="task-cell task-progress">
165 165 ${t.subtaskCount > 0 ? `
166 - <div class="progress-bar-container" title="${t.subtaskCompleted}/${t.subtaskCount} subtasks"
166 + <div class="progress" title="${t.subtaskCompleted}/${t.subtaskCount} subtasks"
167 167 role="progressbar" aria-valuenow="${progress}" aria-valuemin="0" aria-valuemax="100"
168 168 aria-label="${t.subtaskCompleted} of ${t.subtaskCount} subtasks completed">
169 - <div class="progress-bar" style="width: ${progress}%"></div>
169 + <div class="progress-fill" data-tone="success" style="width: ${progress}%"></div>
170 170 </div>
171 171 ` : '<span class="no-subtasks">-</span>'}
172 172 </div>
@@ -199,8 +199,8 @@
199 199 <span>Progress</span>
200 200 <span>${completedCount}/${totalCount} (${progress}%)</span>
201 201 </div>
202 - <div class="progress-bar-container" style="height: 12px;">
203 - <div class="progress-bar" style="width: ${progress}%"></div>
202 + <div class="progress" style="height: 12px;">
203 + <div class="progress-fill" data-tone="success" style="width: ${progress}%"></div>
204 204 </div>
205 205 </div>
206 206 ` : '';