A relative JsonPointerReferenceanswers/mesh-space, name, "" — means nothing on its own. It is resolved against a data context, and the context decides whether the value is read from a mesh node, from the area's /data replica, or from nowhere useful at all.

The rule

A view resolves its pointers against BlazorView.BindingDataContext, never against the raw DataContext cascading parameter.

protected string? BindingDataContext =>
    ViewModel?.DataContext is { } own && !string.IsNullOrWhiteSpace(own)
        ? WorkspaceReference.Decode(own).ToString()
        : DataContext;

The control's OWN declaration wins; the cascade is the fallback for a control that declares none.

Why the order is not a preference

DataContext is a [CascadingParameter], and its one producer in the whole product is DispatchView:

<CascadingValue Value=@ViewModelDataContext Name="DataContext">
...
ViewModelDataContext = ViewModel?.DataContext != null
    ? WorkspaceReference.Decode(ViewModel.DataContext).ToString()
    : DataContext;

That is the same expression. So for every view reached through a dispatch the two agree by construction and the order changes nothing. What it removes is the view's dependence on an out-of-band delivery of a value it already holds: the control model is a direct [Parameter], it drives the very OnParametersSetBindData pass that does the binding, and it therefore cannot be stale relative to that bind. The cascade can be absent, late, or the ancestor's.

Two views had already reached the same conclusion locally and written their own fallback — CodeEditorView and NotebookEditorView both carry GetEffectiveDataContext() => DataContext ?? ViewModel?.DataContext. That workaround is now the base class's job, for every view.

The fault this closes

MeshWeaver.Plugins#1869 / MeshWeaver#3711. Four times between 2026-09-07 and 2026-09-14 a learner's quiz answer picker failed to bind on memex-cloud, always with the same line:

fail: MeshWeaver.Blazor.EntityViews.RadioGroupView[0]
      Error binding JsonPointerReference 'answers/q1' in Area Quiz/Options1
      System.Text.Json.JsonException: 'q' is an invalid start of a value.

The token is arithmetic, and it says the data context was absent — not "a different context", not "an unparseable one". Every other value produces a different token.

Three explanations were measured and are false:

hypothesis verdict
the control leaves the server without its context QuizLayoutAreas.Choice sets DataContext = GetMeshNodeDataContext(sheetPath)
the delivered /areas/"Quiz/Options1" payload loses it on the wire ❌ measured over a real remote layout-area stream, including as a patch onto the frame it replaces
the quiz's two DispatchView hops drop it when the slot's control is swapped ❌ measured on a live component tree through the swap

What was left is the view itself: it held a control carrying the context and bound without one, because it read only the cascade. NodeBoundPointerContextTest pins all four rows, and its reproduction row fails with the production message verbatim when the fix is reverted.

And when there is genuinely no context

A relative pointer with nothing to resolve it against is left UNBOUND, with a diagnostic naming the pointer and the area. It is never handed to Stream.DataBind — because that does not refuse it either. Core's LayoutClientExtensions.GetPointer promotes a context-less relative pointer to an absolute one, and LayoutExtensions.GetStream then reads segment 0 as a collection and segment 1 as a JSON-encoded id:

pointer, no context resolves to outcome
answers/q1 /answers/q1 Deserialize<string>("q1") throws
answers /answers one segment, no id decode — binds silently against the layout stream's own root

🚨 The crash is the lucky case. The one-segment form reports nothing and reads — and through UpdatePointer, writes — a root key of the layout document, beside /areas and /data. A learner's pick landing there is worse than one that does not land: the read comes back empty either way, and the wrong write is invisible and permanent. So UpdatePointer refuses the same shape, at Error, naming the control that could not be saved.

This is also why the fix is never to make the id decode tolerant: that converts the loud case into the silent one. Core records the same rule at Doc/GUI/DataBinding"An absent DataContext does not disable the binding — it RE-ROOTS it".

Reading a bind that came back empty

  1. Is it a DataContext question? A view that reads nothing, or the wrong thing, with no error is a context question before it is a pointer question.
  2. Look for the refusal line. Leaving '{pointer}' unbound in Area {area} means the control declares no context and none was cascaded — a layout-authoring bug, and the pointer and area in that line say which control.
  3. Only then look at the pointer. A pointer that starts with / ignores the context entirely.
Reconnecting…
The server was updated. Reloading the page to pick up the latest version.