A view that reads one node can outlive it. The owner deletes a document in one tab while another
tab still shows it, or a saved view area still points at it. Core's node-stream cache then ends the
read with MeshNodeReadEndedWithDeleteException. A cold read of a path that no longer exists gets
the router's typed NotFound for that path instead.
Either way the node the view reads is gone. That is a state of the data, not a fault of the view,
so BlazorView handles it differently from other faults:
| The fault says… | BlazorView does |
|---|---|
| the node this subscription reads is gone (exact path, ordinal compare) | SurfaceReadError(ex, context, readPath): Information log, sets GoneReadPath, re-renders, no error modal |
| anything else, including the delete of a different node | SurfaceError(ex, context): Error log, the error modal, inline surface |
Paths compare ordinally because mesh paths are case-sensitive: a path that differs only by case is another node.
The classifier is BlazorView.IsReadNodeGone(error, readPath). The layout-area binding uses the same
classifier through IsStreamOwnerGone (MeshWeaver#6074): a /data pointer bound to the layout
stream of a deleted node draws its default.
Use it in any view whose subscription reads one known node: pass the onError to
SurfaceReadError with that node's path, and render the gone state. SurfaceReadError only
records it in GoneReadPath; a view that never reads that property keeps showing the stale copy
of a document that no longer exists. A view does not clear it: every re-bind resets it. The update
is queued to the renderer's dispatcher, so it carries the bind generation it was observed under and
is dropped when a re-bind ran first: a delete of the node the view used to read never hides the
node it reads now behind the deleted banner.
A view that attaches JavaScript to the content element must undo that while the gone state is
drawn and attach it again when content returns. CollaborativeMarkdownView disposes its module's
listeners on entering the gone state and runs init and enableCommentSelection against the new
content element after a re-bind; otherwise inline annotation clicks and selection-to-comment stay
bound to an element that is no longer in the page.
CollaborativeMarkdownView does this for BOTH of its reads of the document (MeshWeaver#6767): the
document read every render binds, and, in a live comparison (CompareFromVersion set,
CompareToVersion absent), the second read the tracked-change projection subscribes to. While
GoneReadPath is set it draws a FluentMessageBar reading "This document was deleted" (German:
"Dieses Dokument wurde gelöscht") in place of the content, toolbar, sidebar and comment surface.
The sentences are the module text table CollaborativeMarkdownText ([Description] plus
[Translation("de", …)]), resolved for the viewer. Before this, an owner deleting an open document produced an Error line
for each of its parallel reads, and each line minted a log incident.
Regression tests: StreamOwnerGoneBindingTest.ADocumentDeletedWhileAViewReadsIt_RaisesNoErrorModal
and its control, AReadThatEndsWithTheDeleteOfAnotherNode_StillRaisesTheErrorModal. Both create a
real node, read it through the cache, and delete it.
ADeleteQueuedBeforeARebind_DoesNotMarkTheReboundView drives the queued update after a real
re-bind on the dispatcher, with a control that the same update for the current binding does mark
the view.
DeletedDocumentViewTest renders the real CollaborativeMarkdownView over a real node, deletes
it, and asserts the deleted state replaces the text with no modal, once for the plain view and
once under a live comparison.