Make plant details reachable without hover and mark a failed poll stale - #118
Conversation
Draw each plot cell as a button opening a detail card under the plot, carrying the facts that previously lived only in a title tooltip plus last_run/last_outcome, which no view rendered. Complete the garden tablist with aria-controls, tabpanel back-references and a roving tabindex with arrow-key navigation. Check res.ok before parsing the poll's body, and mark the whole page stale with the snapshot's age when a poll fails. Closes #114 Closes #116 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Rewrite the detail card only when its content changed, restore focus to a plant after the plot is rebuilt, drop the aria-live region that would have re-announced the card on every poll, look tab keys up in a Map rather than an object literal, and mark the snapshot fresh only once every panel has actually rendered it. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Name a bad body and a throwing render as their own staleness reasons rather than folding both into "fetch failed", guard against overlapping polls re-marking a live page stale, restore focus to the detail card's close button when the card's own content changes, restore plant focus without scrolling the reader back to the plot, put the rendered age in the plot signature so a plant's accessible name cannot freeze, and desaturate rather than dim the stale page so the last known data stays readable. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
ReviewThe diff was reviewed inline this session (no
Conventions checked and clean. Stdlib-only (no dependency added; the One gap this review cannot close. This review was drafted during a Gardener session (https://github.com/Stephenson-Software/gardener). |
Describe TestPageHtmlInvariants in TESTING.md, retitle DASHBOARD.md now that it covers more than the garden view, point README at both halves, and add the reason-string check to CLAUDE.md's doc source-of-truth row. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Review — second pass (continuing an interrupted dispatch)This PR was found open and carrying gardener's own dispatch marker, meaning The review already posted above covers the diff up to Verification performed this session
On
|
Summary
Two dashboard defects are addressed, both concerning facts the page holds
and does not surface.
A plant's detail is reachable without a hover (#114). Every per-repo
fact except the leaf name and a tend/error tally used to live in a
titleattribute — a hover tooltip, on a stylesheet written phone-first, in the
view the page defaults to. Each plot cell is now a
<button>that opens adetail card beneath the plot: full
owner/name, health, tends out of totalruns, errors, last successful tend, last attempt and its outcome, cost,
merge eligibility, and whether the repo is actually planted. The
titleiskept unchanged for pointer users, since it costs nothing.
last_runandlast_outcomewere emitted bybuild_garden_rowsand rendered by no viewat all; they are on the card, because "the most recent attempt errored" is a
different fact from "the last success was three days ago", and the more
urgent one.
The garden tablist implements what it declares (#116).
aria-controlsis added to each tab,
role="tabpanel"and anaria-labelledbyback-reference to each panel, and a roving
tabindexwithLeft/Right/Home/End handling is wired into the tab buttons.
setTabStatesets
tabIndexfrom the sameselectedflag it setsaria-selectedfrom,so the two cannot disagree.
A failed poll no longer renders as a healthy dashboard (#116). Every
panel keeps showing the last good snapshot after a failed poll, so changing
one header caption was not enough of a signal.
res.okis now checkedbefore the body is parsed — a 500 was previously detected only by way of
res.json()throwing on its error body, and a 2xx with a bad body wasindistinguishable from a dead server. Either failure marks
<body class="stale">: the content below the header is dimmed anddesaturated, and the heartbeat is replaced by the age of the displayed
snapshot (built from the payload's own
generated_at, atseconds/minutes granularity rather than
fmtAge's day buckets) plus thereason and a consecutive-failure count. The first successful poll clears
all of it; the existing
visibilitychangefast path is untouched.dashboard.py's module docstring anddocs/DASHBOARD.mdare updated tomatch.
Test plan
python3 -m compileall -q gardener testspython3 -m unittest discover -s tests -v— 645 tests, all passing(14 new
TestPageHtmlInvariantscases covering the button markup, thetwo newly-rendered row fields, the delegated listeners, the detail
card's change-only rewrite, focus surviving a plot rebuild, the
tablist wiring, the roving tabindex, the staleness class, the
res.okcheck, each of the four poll-failure reasons, thenon-overlapping poll guard, the age string in the plot signature, and
the age-based caption)
PAGE_HTMLparsed withhtml.parserto confirm the markup isbalanced after the tab/panel/detail-card edits, and
build_statusre-checked against a temporary state dir
second review pass:
.plant's existingbackground/border/text-aligncorrectly override the UA button defaults,generated_atis offset-aware so
fmtSince'sDate.parseis unambiguous,body.stale mainandbody.stale #updatedboth have real targetswith
#updatedoutside<main>as intended, and every row field andCSS class the detail card references exists.
CLAUDE.mdrecords thatanything touching the plot is verified by rendering it against the
real state db and looking at it, since stdlib-only Python leaves no JS
test runner. This dispatch ran headlessly with no browser and no JS
runtime available, so the in-page JavaScript here has never been
executed — only its emitted source text was asserted against. Because
that code shares one
<script>block with the whole page, a syntaxerror in it would blank every panel rather than just the new card,
which the Python-side assertions cannot detect. Review should include
opening the dashboard and confirming: a plant taps open and closed,
the detail card shows the right repo, arrow keys move between the Plot
and Table tabs, and stopping the server dims the page with an aging
caption.
dispatch.py,dev_loop.pyand the prompt templates are untouched, so themanual dispatch verification those require does not apply to this change.
Closes #114
Closes #116
This PR description was drafted during a Gardener session (https://github.com/Stephenson-Software/gardener).