Summary
Follow-up from the review of #440. The compact Dashboard freshness control is acceptable for the toolbar-density work in #437, but its remaining outcome semantics should be completed separately rather than blocking that PR.
The current implementation introduces:
lastSuccessWallMs, a stable wall-clock timestamp for the last successful full refresh;
lastRefreshOutcome, currently success | failure | null;
- a visible and accessible refresh-failure state.
Three edge cases remain:
- a failed refresh replaces the visible timestamp instead of preserving it;
- filter-source/helper failures can still be classified as a successful refresh;
- a superseded partial refresh can be recorded as successful even though it did not complete its full intended wave.
Current behavior
Failure hides the visible last-good time
When lastRefreshOutcome === 'failure', the renderer currently sets:
updated.textContent = 'Refresh failed';
The prior successful timestamp remains only in the refresh button's tooltip and accessible label.
That conflicts with the compact control's intended visual contract:
A failure should preserve the visible last-known-good time when one exists and layer an error state onto the same compact control.
Refresh success is derived only from tile errors
The current outcome classifier checks only the tiles passed to it:
const failed = ranTiles.some((runtime) => runtime.state.status === 'error');
A full Dashboard refresh also runs and merges Filter sources. Those paths can terminate with:
- source-query/preparation failure;
source-error;
helper-error;
missing-helper;
- error diagnostics from the helper merge.
The affected panel query may still complete successfully, so tile status alone can produce a false success while the Dashboard visibly reports a broken Filter source or helper.
Superseded refreshes can claim success
When a full refresh's Filter wave is superseded by a concurrent selective update, the refresh delegates its affected tiles to the newer wave. The current branch still calls:
recordRefreshOutcome(unaffected, waveMs);
If unaffected is empty, the error check is vacuously false and the incomplete full refresh is recorded as successful. With a non-empty unaffected set, it can still advance the global Dashboard success timestamp even though the refresh did not complete the affected portion it intended to run.
A superseded full refresh is incomplete, not successful or failed.
Required behavior
Visible failure treatment
After a failed refresh:
- preserve
lastSuccessWallMs;
- when a prior successful timestamp exists, keep that timestamp visible in
.dash-updated;
- expose failure through the compact control without replacing the visible time;
- tint or otherwise mark the time and refresh action as an error using the existing semantic error vocabulary;
- keep a descriptive tooltip and accessible label, for example:
Refresh failed. Last successfully updated at 16:31
- when no refresh has ever succeeded, visible
Refresh failed text is acceptable because there is no timestamp to preserve;
- a later successful refresh clears the failure state and advances the visible timestamp.
Do not permanently widen the ordinary compact control merely to show an inline error sentence.
Whole-wave outcome classification
A full refresh() may record success only after every part owned by that refresh reaches a clean terminal state.
At minimum, classify the wave as failed when any of the following belongs to that completed refresh:
- a tile run by the wave ends in
error;
- a Filter source run by the wave ends in
error;
- a Filter consumer ends in
source-error, helper-error, or missing-helper;
- the terminal helper merge produces an error diagnostic.
waiting, unfilled, and ordinary warning/info diagnostics should retain their existing semantics unless the implementation can show they represent a broken refresh rather than a valid incomplete-input state.
Do not infer the outcome solely from the final global tile array: concurrent selective waves may own some of those statuses by the time the older refresh settles. Track the records/generations actually owned by the full refresh.
Superseded outcome
A full refresh whose source wave is superseded must not update either:
lastSuccessWallMs;
- the most recent completed success/failure outcome shown by the freshness control.
Represent this explicitly if useful, for example:
type RefreshOutcome = 'success' | 'failure' | 'superseded' | null;
Alternatively, return an internal terminal result and skip publishing a new user-visible outcome for superseded. The exact representation is implementation-defined; the required behavior is that incomplete work never claims a new successful refresh.
The newer selective wave may update tile/filter data normally, but it must not retroactively make the superseded full refresh a clean full-Dashboard refresh unless the application deliberately tracks and awaits all delegated work under one combined operation.
Suggested design
Introduce a per-full-refresh outcome accumulator rather than deriving success from a tile array at the end.
An illustrative internal shape:
interface FullRefreshResult {
status: 'success' | 'failure' | 'superseded';
tileFailures: string[];
sourceFailures: string[];
helperFailures: string[];
}
Suggested flow:
- capture the full refresh generation and
waveMs;
- run the unaffected tile batch and Filter-source wave;
- if the source wave is superseded, return
superseded without updating freshness state;
- run the affected tile batch;
- classify only work owned by this generation;
- on clean success, set
lastSuccessWallMs = waveMs and outcome success;
- on failure, preserve
lastSuccessWallMs and set outcome failure;
- publish the terminal state once with the correct outcome.
Avoid coupling the freshness model to updatedAt; that field uses the monotonic performance clock and is not suitable for display as wall time.
Unit tests: viewer session
Extend tests/unit/dashboard-viewer-session.test.ts.
Required coverage:
-
Filter-source transport failure
- the panel query succeeds;
- the Filter source query fails;
- the full refresh outcome is
failure;
- the prior
lastSuccessWallMs is preserved.
-
Helper failure
- source transport succeeds but produces
helper-error or missing-helper;
- the full refresh is not recorded as a clean success.
-
Superseded with no unaffected tiles
- every tile is Filter-affected;
- a selective wave supersedes the full source wave;
- the full refresh does not advance the timestamp or outcome.
-
Superseded with unaffected tiles
- some unaffected work completes;
- the affected side is delegated to a newer wave;
- completing only the unaffected subset does not count as a successful full refresh.
-
Clean recovery
- a later complete clean refresh advances the timestamp and clears failure.
-
Destroyed/stale generation
- destroyed or stale work never updates freshness state.
Unit tests: Dashboard renderer
Extend tests/unit/dashboard.test.ts.
Required coverage:
- after a successful refresh, record the visible time;
- fail a later refresh;
- assert the same time remains visible;
- assert
.dash-freshness exposes the error state;
- assert the refresh button label/title says:
Refresh failed. Last successfully updated at <time>
- cover a first-ever failure with no prior successful timestamp;
- cover successful recovery;
- preserve the existing stability assertions for Search, layout, and document-only publications.
Browser coverage
Add or extend focused Playwright coverage for the compact freshness control:
- successful refresh shows a compact visible time;
- failed refresh retains that visible time and exposes the error treatment;
- the tooltip/accessible name identifies both failure and the last successful time;
- recovery clears the failure styling;
- View/Edit remains immediately after the compact control at desktop and mobile widths.
Regression boundaries
The change must not alter:
- Dashboard query/filter execution ordering;
- stale-generation and cancellation guards;
- selective filter-wave ownership;
- tile/filter error rendering;
updatedAt's existing internal completion semantics;
waveWallNowMs time-range resolution;
- Search/layout/document-only timestamp stability;
- refresh spinner and
aria-busy behavior;
- toolbar width, ordering, and mobile horizontal scrolling;
- View/Edit placement;
- ordinary tile-level refresh behavior.
Acceptance criteria
Related work
Summary
Follow-up from the review of #440. The compact Dashboard freshness control is acceptable for the toolbar-density work in #437, but its remaining outcome semantics should be completed separately rather than blocking that PR.
The current implementation introduces:
lastSuccessWallMs, a stable wall-clock timestamp for the last successful full refresh;lastRefreshOutcome, currentlysuccess | failure | null;Three edge cases remain:
Current behavior
Failure hides the visible last-good time
When
lastRefreshOutcome === 'failure', the renderer currently sets:The prior successful timestamp remains only in the refresh button's tooltip and accessible label.
That conflicts with the compact control's intended visual contract:
A failure should preserve the visible last-known-good time when one exists and layer an error state onto the same compact control.
Refresh success is derived only from tile errors
The current outcome classifier checks only the tiles passed to it:
A full Dashboard refresh also runs and merges Filter sources. Those paths can terminate with:
source-error;helper-error;missing-helper;The affected panel query may still complete successfully, so tile status alone can produce a false
successwhile the Dashboard visibly reports a broken Filter source or helper.Superseded refreshes can claim success
When a full refresh's Filter wave is superseded by a concurrent selective update, the refresh delegates its affected tiles to the newer wave. The current branch still calls:
If
unaffectedis empty, the error check is vacuously false and the incomplete full refresh is recorded as successful. With a non-empty unaffected set, it can still advance the global Dashboard success timestamp even though the refresh did not complete the affected portion it intended to run.A superseded full refresh is incomplete, not successful or failed.
Required behavior
Visible failure treatment
After a failed refresh:
lastSuccessWallMs;.dash-updated;Refresh failedtext is acceptable because there is no timestamp to preserve;Do not permanently widen the ordinary compact control merely to show an inline error sentence.
Whole-wave outcome classification
A full
refresh()may recordsuccessonly after every part owned by that refresh reaches a clean terminal state.At minimum, classify the wave as failed when any of the following belongs to that completed refresh:
error;error;source-error,helper-error, ormissing-helper;waiting,unfilled, and ordinary warning/info diagnostics should retain their existing semantics unless the implementation can show they represent a broken refresh rather than a valid incomplete-input state.Do not infer the outcome solely from the final global tile array: concurrent selective waves may own some of those statuses by the time the older refresh settles. Track the records/generations actually owned by the full refresh.
Superseded outcome
A full refresh whose source wave is superseded must not update either:
lastSuccessWallMs;Represent this explicitly if useful, for example:
Alternatively, return an internal terminal result and skip publishing a new user-visible outcome for
superseded. The exact representation is implementation-defined; the required behavior is that incomplete work never claims a new successful refresh.The newer selective wave may update tile/filter data normally, but it must not retroactively make the superseded full refresh a clean full-Dashboard refresh unless the application deliberately tracks and awaits all delegated work under one combined operation.
Suggested design
Introduce a per-full-refresh outcome accumulator rather than deriving success from a tile array at the end.
An illustrative internal shape:
Suggested flow:
waveMs;supersededwithout updating freshness state;lastSuccessWallMs = waveMsand outcomesuccess;lastSuccessWallMsand set outcomefailure;Avoid coupling the freshness model to
updatedAt; that field uses the monotonic performance clock and is not suitable for display as wall time.Unit tests: viewer session
Extend
tests/unit/dashboard-viewer-session.test.ts.Required coverage:
Filter-source transport failure
failure;lastSuccessWallMsis preserved.Helper failure
helper-errorormissing-helper;Superseded with no unaffected tiles
Superseded with unaffected tiles
Clean recovery
Destroyed/stale generation
Unit tests: Dashboard renderer
Extend
tests/unit/dashboard.test.ts.Required coverage:
.dash-freshnessexposes the error state;Browser coverage
Add or extend focused Playwright coverage for the compact freshness control:
Regression boundaries
The change must not alter:
updatedAt's existing internal completion semantics;waveWallNowMstime-range resolution;aria-busybehavior;Acceptance criteria
Related work