Skip to content

fix(chart): keep the live edge free of unread spans - #114

Merged
mrcsin merged 1 commit into
masterfrom
live-gap-probes
Oct 8, 2026
Merged

mrcsin merged 1 commit into
masterfrom
live-gap-probes

Conversation

@mrcsin

@mrcsin mrcsin commented Oct 8, 2026

Copy link
Copy Markdown
Member

Problem

A pen in follow mode lost samples the viewer had received or the archive held, and the line drew one
straight segment across the hole until a gesture read the window again. Three defects caused it:

  • A history apply replaced every column, so live samples that arrived between the read's snapshot and its
    apply were lost; the realtime poll never delivers a sample twice.
  • At a coarse layer a live sample folds into the last column. When the coarse read did not apply, the
    columns were still Raw, the whole coarse phase folded into one Raw column, and a zoom back to the same
    width found the Raw fetch covering the window and read nothing.
  • The fresh tail clamped four point spacings back from the requested right edge, which the prefetch margin
    puts one window past the visible end, so a coarse read never reached the newest raw rows.

Change

  • Live tail across an apply. EnvelopeLine.ReplaceColumns(columns, requestedEndX) keeps the columns
    appended past the last history X that lie after the new last column and at or before the requested edge,
    then trims to the cap. A pan into the past drops the follow tail, so the band just read keeps the buffer.
    ClearColumns is gone: a pen absent from a result keeps its live tail. CurrentValue reads the merged
    columns.
  • Fold over Raw. A fold while the fetched range is Raw clears it, so the return to Raw reads Raw. A fold
    that meets a gap column opens a column past it instead of dropping the sample and freezing the reading.
  • Fresh tail. The clamp is the earlier of the requested edge and the present; PostgresDataProvider
    takes an injected TimeProvider (TryAddSingleton(TimeProvider.System)).
  • History read log. Every read logs one Debug entry (layer, range, column target, pen count, outcome,
    envelope count, duration) beside the realtime poll's per-tick entry.
  • Docs in docs/architecture/charting.md and data-integration.md; the plan moves to
    docs/plans/completed/.

Verification

  • Acceptance tests, each red with its fix reverted and green at HEAD:
    AReturnToRawAfterAHeldCoarseReadReadsRaw, AnApplyKeepsTheLiveColumnsPastItsSnapshot,
    TheRangeTheChartAsksForReachesTheRawLayersNewest (09:04:50 UTC against 09:04:00),
    AValueAfterACoarseEnvelopeEndingOnAGapTakesTheReading; AHistoryReadLogsItsRequestOutcomeAndDuration
    pins the log.
  • dotnet test SemiPlot.slnx: unit 1822/1822, integration 147/147. dotnet format --verify-no-changes
    clean.
  • Manual test on the demo stand in follow mode: no straight segment at the live edge.
Per-task commits before collapse
13a15e2 docs(plans): state the gap fold rule plainly
9309ad7 docs(plans): record the live tail gaps run
1241a42 fix(chart): resume the fold after a gap column
36b95f3 refactor(chart): trim comments the docs already hold
d8f34d8 refactor(chart): address smells review findings
38dfc5b fix(chart): address review findings
a5e16f8 docs(plans): close the live tail gaps tasks
eb857c2 docs(plans): record the live tail gaps checks
732789b fix(postgres): clamp the fresh tail at the present
6809d62 fix(chart): keep the live tail across a history apply
f3e1077 fix(chart): read Raw again after a fold over Raw
29b0e7b feat(postgres): log every history read
0d39406 docs(plans): add live tail gaps plan

A pen in follow mode lost samples the viewer had received or the archive
held, and the line drew one straight segment across the hole until a
gesture read the window again. Three defects caused it.

A history apply replaced every column, so the live samples that arrived
between the read's snapshot and its apply were lost: the realtime poll
never delivers a sample twice. ReplaceColumns keeps the columns appended
past the last history X that lie after the new last column and at or
before the right edge the read asked for, then trims to the cap. A pan
into the past asks for a range that ends before the follow tail, so the
band just read keeps the whole buffer.

At a coarse layer a live sample folds into the last column. When the
coarse read did not apply, the columns were still Raw, every sample of
the coarse phase folded into one Raw column, and a zoom back to the same
width found the Raw fetch covering the window and read nothing. A fold
over a Raw fetch clears it, so the return to Raw reads Raw. A fold that
meets a gap column opens a column past it instead of dropping the sample
and freezing the reading.

The fresh tail clamped four point spacings back from the requested right
edge, which the prefetch margin puts one window past the visible end, so
a coarse read never reached the newest raw rows. It clamps at the earlier
of that edge and the present; the provider takes the clock as an injected
TimeProvider.

Every history read logs one Debug entry with its layer, range, outcome,
envelope count and duration, beside the realtime poll's per-tick entry,
so a straight segment on the bench can be matched against the reads
around it.
@mrcsin
mrcsin marked this pull request as ready for review October 8, 2026 08:57
@mrcsin
mrcsin merged commit 44bba75 into master Oct 8, 2026
2 checks passed
@mrcsin
mrcsin deleted the live-gap-probes branch October 8, 2026 09:02
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant