Summary
The vendored ContextMenu widget (third_party/shadcn_flutter/lib/src/components/menu/context_menu.dart) was extended to support a lazy itemsBuilder alternative to eager items (used by the grid's per-cell context menu, #983). The two modes are distinguished by whether _ContextMenuState._children is null:
@override
void initState() {
super.initState();
final items = widget.items;
if (items != null) {
_children = ValueNotifier(items);
}
}
ValueListenable<List<MenuItem>> _resolveChildren(BuildContext context) {
final eager = _children;
if (eager != null) return eager;
return ValueNotifier(widget.itemsBuilder!(context));
}
If a ContextMenu widget instance is ever rebuilt going from items: [...] (non-null) to items: null, itemsBuilder: ... (i.e. the caller switches modes across a rebuild, same widget position so the same State is reused): initState already ran with the old items and set _children to a non-null ValueNotifier, so didUpdateWidget never re-derives it, and _resolveChildren keeps returning the stale eager _children — the old, hardcoded item list — silently, forever (not a crash in this direction, but stale menu items with no way to recover other than remounting).
Going the other direction — itemsBuilder first, then rebuilt with items instead — is worse: _children was never initialized (stays null since initState only sets it when widget.items != null at first build), and if some intermediate code path expects _children to be non-null once items is provided... actually tracing further, _resolveChildren handles this gracefully today (falls through to null-check via eager == null → itemsBuilder branch), but only as long as itemsBuilder is still non-null. If a caller passes items on a later build without itemsBuilder, widget.itemsBuilder!(context) on line 614 throws Null check operator used on a null value.
Current risk
Today's single caller (_GridCell in result_grid_view.dart) always uses itemsBuilder and never items, and never switches modes on the same widget position, so this is not currently reachable. Flagging it because it's a real latent crash/staleness bug in a shared, vendored widget that other call sites could hit in the future without realizing the constraint.
Suggested fix
In didUpdateWidget, re-derive _children/mode whenever (widget.items == null) != (oldWidget.items == null) flips, instead of only reacting to items list-content changes.
Summary
The vendored
ContextMenuwidget (third_party/shadcn_flutter/lib/src/components/menu/context_menu.dart) was extended to support a lazyitemsBuilderalternative to eageritems(used by the grid's per-cell context menu, #983). The two modes are distinguished by whether_ContextMenuState._childrenis null:If a
ContextMenuwidget instance is ever rebuilt going fromitems: [...](non-null) toitems: null, itemsBuilder: ...(i.e. the caller switches modes across a rebuild, same widget position so the sameStateis reused):initStatealready ran with the olditemsand set_childrento a non-nullValueNotifier, sodidUpdateWidgetnever re-derives it, and_resolveChildrenkeeps returning the stale eager_children— the old, hardcoded item list — silently, forever (not a crash in this direction, but stale menu items with no way to recover other than remounting).Going the other direction —
itemsBuilderfirst, then rebuilt withitemsinstead — is worse:_childrenwas never initialized (staysnullsinceinitStateonly sets it whenwidget.items != nullat first build), and if some intermediate code path expects_childrento be non-null onceitemsis provided... actually tracing further,_resolveChildrenhandles this gracefully today (falls through to null-check viaeager == null→ itemsBuilder branch), but only as long asitemsBuilderis still non-null. If a caller passesitemson a later build withoutitemsBuilder,widget.itemsBuilder!(context)on line 614 throwsNull check operator used on a null value.Current risk
Today's single caller (
_GridCellinresult_grid_view.dart) always usesitemsBuilderand neveritems, and never switches modes on the same widget position, so this is not currently reachable. Flagging it because it's a real latent crash/staleness bug in a shared, vendored widget that other call sites could hit in the future without realizing the constraint.Suggested fix
In
didUpdateWidget, re-derive_children/mode whenever(widget.items == null) != (oldWidget.items == null)flips, instead of only reacting toitemslist-content changes.