fix(ui): reserve the weekly percent column at its semibold weight

The tail percent is drawn at weight 600 but its column was measured at
400, so "100%" was clipped at larger bubble sizes. The layout now asks
the measure callback for the weight the text is drawn in.
This commit is contained in:
tiennm99 committed 2026-10-23 03:25:28 +07:00
1 parent 452ba60ea5
commit c4ad23236e
4 files changed
+27 -5

No files matched your search

+16
View File
@@ -15,6 +15,7 @@ import {
remainingFraction,
ringDash,
scale,
TAIL_PCT_WEIGHT,
tailLayout,
timeFillWidth,
usageFillWidth,
@@ -130,6 +131,21 @@ describe('tailLayout', () => {
assert.equal(tail.usageBarRect.r - tail.usageBarRect.l, 8);
});
it('reserves the percent column at the weight it is drawn in', () => {
const regular = 40;
const semibold = 61.3;
/** @type {Array<number | undefined>} */
const pctWeights = [];
const tail = tailLayout({ canvasW: 360, canvasH: 138, dpi: 96 }, (text, _px, weight) => {
if (text === '100%') {
pctWeights.push(weight);
}
return weight === TAIL_PCT_WEIGHT ? semibold : regular;
});
assert.deepEqual(pctWeights, [600]);
assert.ok(tail.usagePctRect.r - tail.usagePctRect.l >= Math.ceil(semibold));
});
it('rounds fractional measurements up', () => {
const exact = tailLayout({ canvasW: 360, canvasH: 138, dpi: 96 }, () => 40);
const fractional = tailLayout({ canvasW: 360, canvasH: 138, dpi: 96 }, () => 39.2);
+1
View File
@@ -101,6 +101,7 @@ body {
}
.tail-pct {
/* The layout reserves this column measured at TAIL_PCT_WEIGHT in layout.js; change both together. */
font-weight: 600;
}
+2 -2
View File
@@ -53,7 +53,7 @@ const DRAG_THRESHOLD_PX = 3;
const PULSE_INTERVAL_MS = 80;
const TIME_PROGRESS_INTERVAL_MS = 60_000;
const FONT_STACK = '"Segoe UI", system-ui, sans-serif';
/** Weight the layout's reserved text widths are measured at. */
/** Weight text is measured at when the caller names none (regular). */
const MEASURE_WEIGHT = 400;
const HEAD_LABEL_WEIGHT = 600;
@@ -212,7 +212,7 @@ function relayout(p) {
}
const canvas = { canvasW: p.canvasW, canvasH: p.canvasH, dpi: p.dpi };
head = headMetrics(canvas);
tail = tailLayout(canvas, (text, px) => measure(text, px));
tail = tailLayout(canvas, (text, px, weight) => measure(text, px, weight));
layoutKey = key;
rings.setAttribute('viewBox', `0 0 ${p.canvasW} ${p.canvasH}`);
+8 -3
View File
@@ -15,6 +15,9 @@ const COUNTDOWN_TEMPLATE = '999d';
/** Widest percent text the tail reserves room for. */
const PCT_TEMPLATE = '100%';
/** Font weight of the tail percent; keep in step with `.tail-pct` in bubble.css. */
export const TAIL_PCT_WEIGHT = 600;
/** Usage at or above this percent pulses. */
export const PULSE_THRESHOLD_PCT = 95;
@@ -67,10 +70,11 @@ const PULSE_STRENGTH = 0.3;
*/
/**
* Measures `text` set at `fontPx` (regular weight) in physical px.
* Measures `text` set at `fontPx` and `weight` in physical px.
* @callback MeasureText
* @param {string} text Text to measure.
* @param {number} fontPx Font size in physical px.
* @param {number} [weight] CSS font weight; regular (400) when omitted.
* @returns {number} Width in physical px.
*/
@@ -174,7 +178,7 @@ export function headMetrics(canvas) {
* their right-aligned texts. When the tail is too narrow, the text column
* shrinks (possibly to zero) before the bars drop below their minimum.
* @param {Canvas} canvas Window canvas.
* @param {MeasureText} measure Regular-weight text measurement.
* @param {MeasureText} measure Text measurement.
* @returns {TailLayout} Tail geometry in physical px.
*/
export function tailLayout(canvas, measure) {
@@ -186,7 +190,7 @@ export function tailLayout(canvas, measure) {
const barTextGap = scale(8, dpi);
const countdownW = Math.ceil(measure(COUNTDOWN_TEMPLATE, fonts.main));
const pctReserveW = Math.ceil(measure(PCT_TEMPLATE, fonts.small)) + scale(2, dpi);
const pctReserveW = Math.ceil(measure(PCT_TEMPLATE, fonts.small, TAIL_PCT_WEIGHT)) + scale(2, dpi);
const usageBarH = clamp(idiv(h * 10, 100), scale(6, dpi), scale(12, dpi));
const timeBarH = clamp(idiv(h * 4, 100), scale(3, dpi), scale(6, dpi));
@@ -341,3 +345,4 @@ export function timeFillWidth(barW, fraction) {
export function headLabelText(label, labelW, rectW) {
return label === '' || labelW > rectW ? HEAD_LABEL_FALLBACK : label;
}