Skip to content

fix(tui): marshal off-loop Channels label refresh onto the loop (#1426)#1439

Merged
Aaronontheweb merged 2 commits into
netclaw-dev:devfrom
Aaronontheweb:fix/1426-channels-offloop-renderframeprovider
Jun 18, 2026
Merged

fix(tui): marshal off-loop Channels label refresh onto the loop (#1426)#1439
Aaronontheweb merged 2 commits into
netclaw-dev:devfrom
Aaronontheweb:fix/1426-channels-offloop-renderframeprovider

Conversation

@Aaronontheweb

Copy link
Copy Markdown
Collaborator

Summary

The background channel-label refresh in ChannelsConfigViewModel reconciled in its probe continuation — mutating _channelAudiences, the Step channel IDs, Status/IsSaved, and the persisted config — on a thread-pool thread, racing the Termina loop thread that reads that state for render and mutates it in the audience/remove handlers. Audiences are security-relevant ACL trust tiers, so the race could tear the Dictionary or lose an audience-lowering edit (fail open).

Fix

  • Bump Termina 0.12.10.14.0-beta.1 to use the new ReactiveViewModel loop-marshal primitive (Post/InvokeAsync).
  • The background path now runs only the pure probe off-loop and fire-and-forgets the reconcile onto the loop via InvokeAsync, serialized with render/input. This replaces the hand-rolled publish-cell + drain + page-chokepoint approach.
  • The marshal is fire-and-forget (not awaited) on purpose: awaiting it would tie the tracked task's completion to a loop turn, so CancelAndAwaitLabelRefreshAsync (which SaveAsync awaits at its top) would defer the save's own disk write behind a loop turn — a save-then-immediate-quit would drop the write. Cancelling neuters any queued stale apply via the ct re-check.
  • Updated the termina-tui-patterns skill to document the new marshal primitive and mark ChannelsConfigViewModel as the reference.

⚠️ Dependency note

This introduces a dependency on a prerelease Termina (0.14.0-beta.1).

Validation

  • Netclaw.Cli.Tests: 1096/1096
  • Native smoke: config-channels tape (real Termina loop) ✓
  • dotnet slopwatch analyze (no new violations) + copyright headers ✓
  • Canonical Docker build (scripts/docker/build-image.sh) + in-container CLI smoke (test-nonroot-cli.sh) + interactive config smoke (added channel persists with correct canonical ID + audience) ✓
  • Manual interactive testing: audience-lowering integrity + save-then-quit durability ✓

Follow-on

#1418 (retry-with-feedback paradigm) is sequenced to build on this marshal foundation.

Closes #1426

…law-dev#1426)

The background channel-label refresh reconciled in its probe continuation —
mutating _channelAudiences, the Step channel ids, Status/IsSaved, and the
persisted config — on a thread-pool thread, racing the loop thread that reads
that state for render and mutates it in the audience/remove handlers. The
audience is a security-relevant ACL trust tier, so the race could tear the
Dictionary or lose a lowering edit.

Bump Termina 0.12.1 -> 0.14.0-beta.1 to use the new ReactiveViewModel loop
marshal primitive. The background path now runs only the pure probe off-loop and
fire-and-forgets the reconcile onto the loop via InvokeAsync, serialized with
render/input. This replaces the hand-rolled publish-cell + drain + page-chokepoint
approach.

The marshal is fire-and-forget (not awaited) on purpose: awaiting it would tie the
tracked task's completion to a loop turn, so CancelAndAwaitLabelRefreshAsync (which
SaveAsync awaits at its top) would defer the save's own disk write behind a loop
turn — a save-then-immediate-quit would drop the write. Cancelling neuters any
queued stale apply via the ct re-check.

Also update the termina-tui-patterns skill to document the new marshal primitive
and mark ChannelsConfigViewModel as the reference, and drive the InvokeAsync path
in tests via a probe-entered/release handshake (no Thread.Sleep/Task.Delay).
@Aaronontheweb
Aaronontheweb enabled auto-merge (squash) June 18, 2026 20:20
@Aaronontheweb
Aaronontheweb merged commit 0c13f4f into netclaw-dev:dev Jun 18, 2026
15 checks passed
@Aaronontheweb
Aaronontheweb deleted the fix/1426-channels-offloop-renderframeprovider branch June 18, 2026 21:47
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.

Config TUI: marshal background label-refresh / save-reload state mutations back onto the Termina loop

1 participant