Repository navigation
Dashboard "Oldest untouched task" tile renders a raw ISO timestamp — replace the measure with a number a manager can read #122
Description
Activity
Claiming this issue.
- Branch:
claude/issue-122-oldest-touch-tile - Session: https://claude.ai/code/session_01SqkTcrxUFci7nqXdbBSe2p
Plan: verify the issue's premise against current
mainfirst (the measure, the widget rendering, and what@objectstack/spec17.3.0 lets a derived measure express), then take the preferred route (days-since integer) if the analytics door can actually aggregate it on a real boot — fallback to dropping the tile and wideningnot_moving_30donly if it cannot.
Generated by Claude Code
- Branch:
- added a commit that references this issue
on Sep 5, 2026 { "issue": 122, "status": "done", "branch": "claude/issue-122-oldest-touch-tile", "pr": "https://github.com/objectstack-ai/duly/pull/126", "pr_url": "https://github.com/objectstack-ai/duly/pull/126", "pr_number": 126, "head_sha": "c22d68b0613a5dbd61333e2b19a255576315dd61", "premise_still_valid": false, "gates": { "validate": "green — '✓ Validation passed (538ms)', plus the hierarchy-security warning AGENTS.md documents as this repo's expected state", "typecheck": "green — tsc --noEmit, silent", "test": "green — 'Test Files 35 passed (35) / Tests 897 passed (897)'", "build": "green — '✓ Build complete (879ms)'" }, "route_taken": { "preferred_or_fallback": "fallback", "why": "The preferred route (days-since as an integer) is not expressible, measured on a real boot rather than assumed: AggregationFunction has no date-difference member; a derived measure's `of` takes other MEASURE NAMES only (no literal, no `now`); and a temporary { op: 'difference', of: [max_ts, min_ts] } queried through POST /api/v1/analytics/dataset/query returned null, because computeDerived coerces operands with Number() and an ISO string is NaN. A stored days_stalled is AGENTS.md rule 5's banned shape and the card refuses it by name. A third route the card did not know about — format: 'relative', which objectstack#14933 records as rendering '3 days ago' — was also probed on the real tile and does NOT reach the dataset metric path on cli 17.3.0 (unchanged output in both locales, though the door does carry the style). So the card's own fallback shipped: tile retired, not_moving_30d takes the row." }, "browser_evidence": [ "1440x2300 — docs/i18n/01-dashboard-zh.png (committed). pnpm demo:zh on a clean .objectstack/data, Chromium locale zh-CN, signed in as admin@objectos.ai. Top row is two numbers (停滞 / 停滞超过 30 天); no ISO timestamp in the page text; document.scrollWidth == clientWidth == 1440, no overflow. Re-captured over the existing file, which showed the retired tile.", "390x844 — docs/i18n/04-dashboard-zh-390.png (committed, new). Same boot and locale; tiles stack two per row, all numbers; no ISO timestamp; scrollWidth == clientWidth == 390, no overflow." ], "files_changed": [ "src/dashboards/duty-health.dashboard.ts — oldest_touch widget removed (replaced by a removal note carrying the measurements); not_moving_30d 3->5 cols; headline 6->7 cols; header docblock updated (three tiles -> two)", "src/datasets/stagnation.dataset.ts — measure kept, documented: never bind it to a metric tile, and why days-since is not derivable", "src/translations/zh-CN.ts — widgets.oldest_touch keys removed with the widget; not_moving_30d description updated; the measure's label key stays with the measure", "test/dashboard.test.ts — new guard: no metric/kpi tile may bind min/max/sum/avg over a date/datetime/time field, resolved through the objects barrel, with 9 synthetic self-tests; stale 'three tiles' comment fixed", "docs/i18n/01-dashboard-zh.png — re-captured (it showed the retired tile)", "docs/i18n/04-dashboard-zh-390.png — new 390px capture" ], "decisions": [ "The MEASURE stays in the dataset; only the tile goes. Precedent inside the same dataset: open_tasks and untouched_over_7d are already declared with no widget binding, so the semantic layer is not required to be 1:1 with widgets. Dropping a declared measure is a contract change (the datasets barrel warns about it) that the card did not ask for.", "The row is 7/5, not the card's literal w:6. Six columns at h:4 makes the subset tile EQUAL the headline's area, which fails the pin the card also required to stay green. The first fix — w:6,h:3 — passed all four gates and broke the screen: the console's grid compacts vertically, so the on-time rate jumped into the right column beside the headline and the documented reading order silently stopped being what the screen showed. Caught only by screenshotting. A row must be uniform in height, so width was the only free dimension.", "The >30d tile's description now names where the worst single case lives (the stalled list, already sorted by last touch ascending), in English and zh-CN — the card's fallback promises that reachability and nothing on the screen said it.", "Filed objectstack-ai/objectstack#15768 (unassigned, unlabelled for triage): the analytics door types a min() over a datetime as `number` while returning an ISO string, and the metric tile ignores the `format` style it is handed. AGENTS.md rule 9 — the platform gap is reported, not written around. It also contradicts a premise of open objectstack#14933, which is about to document `format` as a reachable date style." ], "concerns": [ "PREMISE: the card's measured symptom does not reproduce. On 17.2.0 the tile printed a raw ISO string and overflowed both viewports; on 17.3.0 (main today) it renders 2026年7月4日 07:00 / Jul 4, 2026, 07:00 AM with no overflow — objectui#7178 was fixed and shipped between the card and now. What survives is the half the card's 'done' section is written against (an instant among counts and rates, and no way to say 'days since'), which is what this PR acts on. If the maintainer now prefers keeping a formatted-date tile, this is one revert — the PR says so in its first section.", "The card's statement 'measures carry only a numeral format pattern, so this cannot be fixed by formatting' is also no longer strictly true — the schema key IS read as a display style on some paths (objectstack#14933). It is true on the path this tile uses, which is what the probe established.", "Retiring the tile removes the one place the dashboard answered 'the single worst case'. The stalled list answers it better (task, owner and date together, first row), but it is one navigation away rather than on the leadership screen." ], "summary": "Verified the premise first and found half of it dead: on 17.3.0 the tile no longer prints a raw ISO string and does not overflow (objectui#7178 shipped). The half that survives — an instant on a KPI tile, with no way to express days-since — is what shipped a fix for. Measured all three routes on a real boot before choosing: no date-difference aggregate, no literal or `now` operand for a derived measure (a probe difference over two instants returns null), and format: 'relative' does not reach the dataset metric path on this console. So the card's own fallback: the oldest_touch tile is retired, not_moving_30d widens into the row (7/5, not 6/6 — 6/6 fails the area pin, and a shorter tile reorders the whole screen through vertical compaction, caught by screenshot), the measure stays as a semantic-layer value, and test/dashboard.test.ts now bans binding any instant to a single-value tile with nine self-tests behind it.", "tests": "All four gates run as one chain through the shared verify lock at the final commit c22d68b (git rev-parse --short HEAD before the run): 'VERDICT command-exit 0 · held the lock 43s'. pnpm validate -> '✓ Validation passed (538ms)'; pnpm typecheck -> tsc --noEmit silent; pnpm test -> 'Test Files 35 passed (35) / Tests 897 passed (897)'; pnpm build -> '✓ Build complete (879ms)'. Reverse-verification of the new guard, against the REAL dashboard rather than a fixture: the retired tile was reinstated, the mutation confirmed on disk by grep -c on the injected text (1 hit) and on `id: 'oldest_touch'` (1 hit) — not by the editor's exit code — restored by a trap on EXIT/INT/TERM and the restore confirmed by git diff --quiet. Direction expected and observed: red, `2 failed | 50 passed`, vitest exit 1, the failures being the new instant-tile walk and the 'tile is gone' pin. No build/dist step is involved: vitest imports the TypeScript sources directly, so no rebuild leg applies.", "open_questions": [], "out_of_scope_findings": [ "filed as objectstack-ai/objectstack#15768: a dataset measure over a datetime is typed `number` in the analytics response while returning an ISO string, and the dashboard metric tile ignores the `format` display style the door does carry — measured on cli 17.3.0, with the version-skew caveat stated; it makes open objectstack#14933's proposed documentation wording wrong for the tile path." ] }
Generated by Claude Code
Resolved upstream — closing as completed, no app change.
Measured on
main74342a1 (@objectstack/*17.3.0, freshpnpm demo:zh): the tile now renders 2026年7月4日 07:00 in a zh-CN session, no overflow at 1440 or 390 — the raw-ISO rendering this card was filed against was fixed by objectui#7178 between filing and today. The dashboard's design brief asks for exactly this (the worst single case as a date, not a magnitude), so the tile stays as authored.PR #126 explored the card's "days since" route and established on a real boot that it is not expressible (no date-difference aggregate; derived measures take measure names only; a difference over two instants returns null;
format: 'relative'does not reach the dataset metric path on cli 17.3.0). That is recorded there and as objectstack#15768; the PR is closed unmerged because its fallback (retiring the tile) is no longer needed.
Generated by Claude Code
Measured in the browser on 2026-09-02 (main 5c5a8a0, Console from
@objectstack/cli17.2.0), desktop and 390px:The
oldest_touchmetric tile (src/dashboards/duty-health.dashboard.ts) readsduly_stagnation.oldest_last_update_at— aminover a timestamp — and the metric widget prints the value verbatim:2026-07-04T07:00:00.000Z, overflowing the tile on both viewports. The widget has no date formatting (measures carry only a numeralformatpattern, seeon_time_rate's comment), so this cannot be fixed by formatting.For the leadership report the tile was hidden locally in a screenshot-only patch (and
not_moving_30dwidened into its slot). That patch is not onmain; this card is the real fix.What "done" is
Replace the measure with a number, keeping the tile's purpose (the single worst case on the screen, ranking nobody):
today − min(last_update_at)in days, else a dataset column over an existing stored field that the analytics door can aggregate. Do not add a storeddays_stalled(AGENTS.md rule 5: a maintained flag); if no aggregate can yield days, fall back to the second option.not_moving_30dtow: 6(exactly the screenshot patch), and keep the worst case reachable through thestalledlist, which is sorted by last touch already.Either way: title/description in
src/dashboards/duty-health.dashboard.tsupdated to match, zh-CN keys insrc/translations/zh-CN.ts,test/dashboard.test.ts's area pin (no tile may out-areanot_moving_14d) still green, four gates green, and a screenshot of the tile at 1440 and 390 in the PR.Related, not this card: rates print as
0.95— #101 owns the percent formatting; the tile sub-labels and legend English is #106.