Compare commits

..
2 Commits
Author SHA1 Message Date
Alexander Heldt 122897c732 Remove the walking goal
It was mine, not the user's: added unprompted alongside the Walks panel because
sleepTargetFor existed and it seemed to fit. The number came from the
"five-minute rule" — five minutes per month of age, twice a day — written from
general knowledge with no source consulted and none cited in the code.

That rule is widely repeated but is folk guidance, not a veterinary standard.
Its origin is unclear, it is contested by what evidence exists on early
exercise and joint development, and it ignores breed, terrain, lead or off-lead
and the individual dog. Drawing it as a line labelled "Goal", with a ✓ for
clearing it, stated it far more confidently than a rule of thumb earns —
particularly in an app whose entire job is otherwise to record what happened
rather than to advise.

So the target line goes, along with its legend chip, the ✓, and walkTargetFor
itself, which had no other caller. The trend keeps today against yesterday and
the average: a record, with the judgement left to whoever knows the dog.
2026-09-01 10:07:20 +00:00
Alexander Heldt 31e04fb9a1 Lift the chart window out of the Sleep panel
It governs nearly every panel on the page — the training grid, the timing
charts, both sleep patterns, the counts panel, all three walk views — but it
sat inside Sleep, so it read as a sleep setting. That was already the reason
its old home carried a comment explaining it was not one; a control needing a
comment to deny what its position says is in the wrong position.

It now has its own row under the quick actions, above the first panel that
reads it, with a "Charts cover" label to say what it does. The row skips the
card chrome: it is a control, not content, and giving it a section's surface
and shadow would have made it look like a panel with nothing in it.

No JS change — both the click wiring and renderChartWindow address the buttons
by class, so they never cared where the picker lived.

Also removes that old comment, which had stopped being wrong about the picker
and started being wrong about the panel: the walks move carried it along by
accident and left it sitting above Weight, explaining a control three panels
away.
2026-09-01 07:58:14 +00:00
4 changed files with 38 additions and 66 deletions
+4 -48
View File
@@ -664,19 +664,6 @@
.map(w => ({ start: w.start, end: w.end, ongoing: w.ongoing && today }));
}
// Rough age-based walking guideline (the widely used "five-minute rule"):
// about 5 minutes per month of age per walk, twice a day, until the puppy is
// grown. Returns null without a birthday or once it's a year old, the same
// way sleepTargetFor bows out.
function walkTargetFor(birthday) {
const a = ageParts(birthday);
if (!a || a.months >= 12) return null;
// Under a month of counted age the rule has nothing to say yet; treat it
// as one "month" so the advice stays a short outing rather than zero.
const months = Math.max(1, a.months);
return { perWalk: months * 5, walks: 2, total: months * 10 };
}
// ---------- rendering ----------
const dayPicker = document.getElementById("day-picker");
const eventList = document.getElementById("event-list");
@@ -1762,7 +1749,7 @@
};
}
function drawWalkTrendChart(curves, target) {
function drawWalkTrendChart(curves) {
const svg = document.getElementById("chart-walk-trend");
if (!svg) return;
const W = 320, H = 180;
@@ -1777,12 +1764,7 @@
{ pts: curves.today, cls: "wtrend-today", label: curves.dayLabel },
].filter(s => s.pts && s.pts.length > 1);
// The axis has to reach the goal even on a day that fell well short of it,
// or the line it is measured against would sit off the top of the chart.
const rawMax = Math.max(
target ? target.total : 0,
...series.flatMap(s => s.pts.map(p => p.y)),
);
const rawMax = Math.max(...series.flatMap(s => s.pts.map(p => p.y)));
const { yMax, steps } = niceAxisLinear(rawMax);
// Minutes are linear all the way down, so no split axis: the sleep trend
// stretches its top because the interesting hours cluster near a 16h goal,
@@ -1804,16 +1786,6 @@
parts.push(`<text x="${x.toFixed(1)}" y="${H - MB + 14}" text-anchor="${anchor}">${pad2(hr)}</text>`);
}
// The five-minute rule as one line rather than a band: it is a target to
// reach, with no upper bound a band would imply.
if (target) {
const y = yOf(Math.min(target.total, yMax));
parts.push(
`<line class="wtrend-goal" x1="${ML}" y1="${y.toFixed(1)}" x2="${W - MR}" y2="${y.toFixed(1)}">` +
`<title>${escapeText(`Goal ~${target.total} min (${target.perWalk} min × ${target.walks})`)}</title></line>`
);
}
for (const s of series) {
const d = s.pts
.map((p, i) => `${i === 0 ? "M" : "L"}${xOf(p.x).toFixed(1)} ${yOf(p.y).toFixed(1)}`)
@@ -1830,22 +1802,11 @@
function renderWalkTrend(events) {
const curves = walkTrendCurves(events);
const target = walkTargetFor(loadConfig().birthday);
drawWalkTrendChart(curves, target);
drawWalkTrendChart(curves);
const chip = (id) => document.getElementById(id);
const mins = (pts) => `${Math.round(pts[pts.length - 1].y)} min`;
// With no projection to carry it, the ✓ goes on today's own chip: the goal
// is a total for the day, so today's line is what meets it or doesn't.
let mark = "";
if (target) {
const done = curves.today[curves.today.length - 1].y >= target.total;
mark = done ? " ✓" : "";
chip("legend-wtrend-today-text").parentElement.title = done
? "Past the walking goal for this age"
: `Goal is about ${target.total} min a day at this age`;
}
chip("legend-wtrend-today-text").textContent = `${curves.dayLabel} ${mins(curves.today)}${mark}`;
chip("legend-wtrend-today-text").textContent = `${curves.dayLabel} ${mins(curves.today)}`;
const yLegend = chip("legend-wtrend-yesterday");
yLegend.hidden = !curves.yesterday;
@@ -1857,11 +1818,6 @@
if (curves.avg) {
chip("legend-wtrend-avg-text").textContent = `${curves.avgDays}-day avg ${mins(curves.avg)}`;
}
const gLegend = chip("legend-wtrend-goal");
gLegend.hidden = !target;
if (target) {
chip("legend-wtrend-goal-text").textContent = `Goal ~${target.total} min`;
}
}
// Which heatmap block is focused, remembered across re-renders so a background
+2
View File
@@ -1,4 +1,6 @@
[
{ "date": "2026-09-01", "text": "Removed the walking goal from the Walk trend — the dashed target line, its legend chip and the ✓ that marked a day as met. It came from the “five-minute rule” (five minutes per month of age, twice a day), which is a widely repeated rule of thumb rather than veterinary guidance, and the app was stating it more confidently than it deserved. The chart is now just a record of what you walked, against yesterday and the average" },
{ "date": "2026-09-01", "text": "The 7d / 14d / 30d buttons have moved out of the Sleep panel onto their own “Charts cover” row, just under the Log event buttons. They always set the window for every chart on the page — training, timing, sleep, walks, pees/poos/meals — but sitting inside the Sleep panel made them look like a sleep setting" },
{ "date": "2026-09-01", "text": "Dropped the “Darker = more sessions that day” caption under the training consistency grid. Tapping a cell still opens that day" },
{ "date": "2026-09-01", "text": "Dropped two hint lines: the “Based on N pee gaps…” sentence under the Timing charts, and the “Rule of thumb at this age…” one in the Walks panel. The charts above them already say it. The age-based walking goal is still there, drawn as the goal line on the Walk trend" },
{ "date": "2026-09-01", "text": "On the dark theme the awake timers are a warm near-white now — both “Awake for” at the top and the pill in the day bar — instead of yellow text on a yellow-tinted pill. The light theme keeps its dark gold, where white would disappear into a near-white pill" },
+14 -11
View File
@@ -134,6 +134,19 @@
</div>
</section>
<!-- Page-level, not a panel's: every panel below that covers more than
one day reads this, so it sits on its own row above them all rather
than inside one of them, where it read as that panel's own control
(see renderChartWindow). -->
<div class="chart-window">
<span class="chart-window-label">Charts cover</span>
<div class="chart-days-picker" role="group" aria-label="How many days the charts cover">
<button type="button" class="ghost" data-days="7">7d</button>
<button type="button" class="ghost" data-days="14">14d</button>
<button type="button" class="ghost" data-days="30">30d</button>
</div>
</div>
<section class="training" data-panel="training">
<h2>Training</h2>
<ul id="training-list" class="training-list"></ul>
@@ -229,11 +242,6 @@
<section class="patterns" data-panel="sleep-daily">
<h2>Sleep <span class="muted-note" data-chart-days-label>(last 7 days)</span></h2>
<div class="chart-days-picker" role="group" aria-label="How many days the charts cover">
<button type="button" class="ghost" data-days="7">7d</button>
<button type="button" class="ghost" data-days="14">14d</button>
<button type="button" class="ghost" data-days="30">30d</button>
</div>
<div class="chart">
<div class="chart-title">Hours per day</div>
<svg id="chart-sleep" class="chart-svg" viewBox="0 0 320 160" role="img" aria-label="Sleep hours per day"></svg>
@@ -305,20 +313,15 @@
<section class="patterns" data-panel="walk-trend" hidden>
<h2>Walk trend</h2>
<svg id="chart-walk-trend" class="chart-svg" viewBox="0 0 320 180" role="img" aria-label="Cumulative minutes walked through the selected day, the day before it, the recent average, and the age-based daily goal"></svg>
<svg id="chart-walk-trend" class="chart-svg" viewBox="0 0 320 180" role="img" aria-label="Cumulative minutes walked through the selected day, the day before it, and the recent average"></svg>
<div class="legend">
<span class="lg wtrend-today"><span class="sw"></span><span id="legend-wtrend-today-text">Today</span></span>
<span class="lg wtrend-yesterday" id="legend-wtrend-yesterday"><span class="sw"></span><span id="legend-wtrend-yesterday-text">Yesterday</span></span>
<span class="lg wtrend-avg" id="legend-wtrend-avg"><span class="sw"></span><span id="legend-wtrend-avg-text">7-day avg</span></span>
<span class="lg wtrend-goal" id="legend-wtrend-goal" hidden><span class="sw"></span><span id="legend-wtrend-goal-text">Goal</span></span>
</div>
<p class="muted-note">Minutes walked so far at each point of the day, against yesterday and the average over the picked window. The line climbs only while a walk is on, so every step is one walk.</p>
</section>
<!-- The day-window picker lives in this panel but governs every
day-window chart on the page — the training grid above, both sleep
patterns below, the counts panel and the walk chart — so changing it
here changes all of them (see renderChartWindow). -->
<section class="weight" data-panel="weight">
<h2>Weight</h2>
<div class="weight-summary">
+18 -7
View File
@@ -721,11 +721,29 @@ dialog menu {
.time-row input { flex: 1 1 120px; min-width: 0; }
.time-row button { padding: 8px 12px; }
/* The chart window is a page-level control rather than a panel's, so its row
skips the card chrome the sections have: no surface, no shadow, just a label
and the buttons sitting on the page background between two cards. */
.chart-window {
display: flex;
align-items: center;
gap: 10px;
padding: 0 4px;
}
.chart-window-label {
font-size: 0.75rem;
text-transform: uppercase;
letter-spacing: 0.06em;
color: var(--muted);
}
.chart-days-picker {
display: flex;
gap: 6px;
margin: -4px 0 14px;
}
/* No heading above it out here, so nothing to pull up under or clear beneath. */
.chart-window .chart-days-picker { margin: 0; }
.chart-days-picker button {
padding: 5px 12px;
font-size: 0.8rem;
@@ -1155,16 +1173,9 @@ input.switch:checked::after { transform: translateX(18px); }
fill: none;
opacity: 0.85;
}
.chart-svg .wtrend-goal {
stroke: var(--walk);
stroke-width: 1.5;
stroke-dasharray: 2 3;
opacity: 0.55;
}
.lg.wtrend-today .sw { background: var(--walk); }
.lg.wtrend-yesterday .sw { background: var(--eat); }
.lg.wtrend-avg .sw { background: var(--weight); }
.lg.wtrend-goal .sw { background: color-mix(in srgb, var(--walk) 45%, var(--surface)); }
.lg[hidden] { display: none; }
.chart-svg .hm-cell { stroke: none; }