M1b: mobile app-bar clock — Design's resting clock + STALE reaction (Hybrid)
Design's mobile app bar shows a wall clock, no SYNC/STALE readout. Building it literally would drop the Ship-A staleness signal on the device most users are on. Kev's ruling: Hybrid — the wall clock is the RESTING state (the stillness), STALE/amber is the REACTION (the punctuation), driven by the SAME real signal as desktop (refreshed_at vs expected_interval_s, thresholds 1.5×/3×). Never silently stale on mobile. Design drew the happy path; we keep the failure state. - web/src/lib/freshness.js — extracted the freshness tier as ONE shared source (CommonJS, unit-tested); desktop HeartbeatBar + mobile clock both key off it, so mobile can't silently disagree with desktop about staleness. - LiveLayer: <768px hides SIGNAL LIVE + EKG + graded + the SYNC label and shows a single right-aligned clock — ticking wall clock when calm, amber SYNC / red STALE when the tier reacts. Resting dot is static (the clock is the pulse). - Test-lock: freshness.test.js (tier thresholds, absent≠false-stale, no negative age) + vyndrParityQA (mobile clock wired, one shared freshness source). Built to Design's mobile spec, VISUALLY UNVERIFIED at 390px. NEXT shell increments (still M1b): merge the clock into the logo row (Design's single-row app bar), the breadth strip (EDGES/AVG CLV/GAMES — replaces the graded count mobile lost here), and relocate the wire to the bottom of the board. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -148,12 +148,18 @@ describe('QA.22 — honest waiting copy everywhere (no passive mystery)', () =>
|
||||
describe('M1 — mobile foundation is wired (structural, not visual)', () => {
|
||||
const css = read('app/globals.css');
|
||||
|
||||
it('header collapses on phones: the decorative EKG is hidden <768px', () => {
|
||||
// The audit flagged ticker + SIGNAL LIVE + STALE stacking in ~110px.
|
||||
expect(css).toMatch(/\.hb-ekg\s*\{[^}]*display:\s*none/);
|
||||
// and the hook actually exists on the component
|
||||
expect(read('components/vyndr/LiveLayer.tsx')).toMatch(/hb-ekg/);
|
||||
expect(read('components/vyndr/LiveLayer.tsx')).toMatch(/heartbeat-bar/);
|
||||
it('mobile app-bar clock: desktop heartbeat pieces hidden, clock shown <768px', () => {
|
||||
// Design's mobile drops SIGNAL LIVE + EKG + graded + SYNC-label for a single
|
||||
// right-aligned clock (Hybrid: wall clock rests, STALE reacts).
|
||||
expect(css).toMatch(/\.hb-ekg,\s*\.hb-desktop\s*\{[^}]*display:\s*none/);
|
||||
expect(css).toMatch(/\.hb-mobile-clock\s*\{\s*display:\s*inline-flex/);
|
||||
const live = read('components/vyndr/LiveLayer.tsx');
|
||||
expect(live).toMatch(/hb-mobile-clock/);
|
||||
expect(live).toMatch(/hb-desktop/);
|
||||
// freshness is the ONE shared tier, not a re-implementation
|
||||
expect(live).toMatch(/from '@\/lib\/freshness'/);
|
||||
// never silently stale: the mobile clock reacts to the stale tier
|
||||
expect(live).toMatch(/STALE/);
|
||||
});
|
||||
|
||||
it('primary CTA meets a 44px touch target on mobile', () => {
|
||||
|
||||
Reference in New Issue
Block a user