From 2baa8e1bfc3f4c27845176158103db0ffc86bd04 Mon Sep 17 00:00:00 2001 From: Jean-Pierre Martineau Date: Tue, 1 Sep 2026 23:11:30 -0400 Subject: [PATCH] The live feed's depth is a packet count, not a clock MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Restores the WIP parked on 2026-08-31 before the PicoCalc sync. The feed seeded itself from `utcnow() - OBSERVATION_WINDOW`, borrowing the two-hour window the dashboard's rolling stats are bounded by. Two bounds for one list, and the wrong one usually won: on a quiet mesh the screen opened nearly empty, not because little had ever been heard but because little had been heard *lately* — which is the one thing the reader can already see from the ages in the rows. So the cap is the only bound, and it goes from 100 to 500. A busy hour runs to some hundreds of packets, so a hundred rows forgot the last ten minutes at exactly the moment the feed was worth watching. Depth is close to free here: the paint is windowed, so the cap costs a pointer per entry and a few milliseconds at open, never anything per frame. Widening the seed costs nothing either — `observed_at` is indexed, so the query walks the index backwards and stops after _FEED_CAP rows whatever the floor. Measured on a 700-packet history spread over a year: the unbounded seed fills all 500 rows and reaches 249 days back, where the two-hour window seeded one. OBSERVATION_WINDOW keeps its name and its meaning, now solely the dashboard's: it is what the RF health card means by "reception over 2 h", and its docstring says so rather than leaving the next reader to find two screens sharing it. Co-Authored-By: Claude Opus 5 (1M context) --- meshterm/persistence/repository.py | 11 ++++++---- meshterm/ui/livefeed_screen.py | 35 +++++++++++++++++++++++------- 2 files changed, 34 insertions(+), 12 deletions(-) diff --git a/meshterm/persistence/repository.py b/meshterm/persistence/repository.py index 5e2edc4..47a761d 100644 --- a/meshterm/persistence/repository.py +++ b/meshterm/persistence/repository.py @@ -33,10 +33,13 @@ if TYPE_CHECKING: from ..core.contact_score import ContactSignals -#: The trailing window the dashboard's live observation stats and packet feed prune to -#: — "what's happening right now" territory, labelled "reception over 2 h" on the RF -#: health card. Distinct from the deeper channel-activity window below: this bounds a -#: live view, that one only feeds a scaling peak. +#: The trailing window the dashboard's live observation stats prune to — "what's +#: happening right now" territory, labelled "reception over 2 h" on the RF health card. +#: Distinct from the deeper channel-activity window below: this bounds a live view, that +#: one only feeds a scaling peak. The Live feed screen once shared it and no longer does: +#: its depth is a packet count, not a duration (see +#: :data:`~meshterm.ui.livefeed_screen._FEED_SEED_FLOOR`), so this number means the +#: dashboard's two hours and nothing else. OBSERVATION_WINDOW = timedelta(hours=2) #: The trailing window a channel's activity histogram is computed over. Six hours is diff --git a/meshterm/ui/livefeed_screen.py b/meshterm/ui/livefeed_screen.py index 988cadb..c1d05f2 100644 --- a/meshterm/ui/livefeed_screen.py +++ b/meshterm/ui/livefeed_screen.py @@ -47,6 +47,7 @@ from __future__ import annotations import asyncio from collections import deque +from datetime import datetime, timezone from typing import TYPE_CHECKING, Any, Mapping, Optional, Sequence from rich.text import Text @@ -55,7 +56,6 @@ from ..core.channels import identify_channel, split_channel_sender from ..core.events import EventKind, MeshEvent from ..core.frames import CHANNEL_CLASSES, ENDPOINT_HASH_BYTES from ..core.models import Observation, utcnow -from ..persistence.repository import OBSERVATION_WINDOW from .menus import fit_cells from .packet_viewer import ( KIND_STYLES, @@ -78,8 +78,29 @@ if TYPE_CHECKING: _REFRESH_S = 1.0 #: How many feed rows are kept (the feed windows within the screen, so this is -#: history depth, not layout). -_FEED_CAP = 100 +#: history depth, not layout) — and, being the feed's *only* bound, the one number that +#: decides how far back the screen remembers. A busy hour on a real mesh runs to some +#: hundreds of packets, so a hundred rows forgot the last ten minutes exactly when the +#: feed was worth watching. Depth is nearly free here: the paint is windowed +#: (:meth:`LiveFeedScreen._feed_lines` formats only the rows on screen), so the cap costs +#: a pointer per entry and a few milliseconds of hydration at open, never anything per +#: frame. +_FEED_CAP = 500 + +#: How far back the opening seed may reach: **no limit** — the cap alone says how much +#: history the feed holds, and a clock has no business overruling it. A quiet mesh would +#: otherwise open a half-empty screen not because nothing was ever heard but because +#: nothing was heard *lately*, which is the one thing the reader can already see. +#: +#: The mechanics are unchanged — :meth:`~meshterm.persistence.repository.Repository +#: .recent_observations` still takes a ``since``, and this is simply a floor no stored +#: row predates. It stays cheap because ``observed_at`` is indexed: the query walks the +#: index backwards and stops after :data:`_FEED_CAP` rows, so widening the window reads +#: no more rows than narrowing it did. Deliberately *not* +#: :data:`~meshterm.persistence.repository.OBSERVATION_WINDOW`, which the feed used to +#: borrow: that one bounds the dashboard's rolling stats and is what its RF health card +#: means by "reception over 2 h". +_FEED_SEED_FLOOR = datetime.min.replace(tzinfo=timezone.utc) #: The feed's fixed subject-lane width; anything longer ellipsizes so the columns hold. #: Sized for a node name, which is what most classes put here — and wide enough that the @@ -826,11 +847,9 @@ async def open_livefeed(ctx: "AppContext") -> None: key_of = trace_runner.make_name_key_resolver(contacts, stored_names) prefix_bytes = await _routing_prefix_bytes(ctx) - # Only the newest screenful is kept (the deque caps at _FEED_CAP), so only that - # many are worth hydrating. - seed = ctx.repo.recent_observations( - since=utcnow() - OBSERVATION_WINDOW, limit=_FEED_CAP - ) + # The cap is the only bound: the newest _FEED_CAP packets ever recorded, however + # long ago the quiet stretch before them began. + seed = ctx.repo.recent_observations(since=_FEED_SEED_FLOOR, limit=_FEED_CAP) screen = LiveFeedScreen( session=session, resolve=resolve,