test: pin the null widget value contract across every persistence path - #15596
Conversation
|
Important Review skippedAuto reviews are disabled on base/target branches other than the default branch. Please check the settings in the CodeRabbit UI or the ⚙️ Run configurationConfiguration used: Path: .coderabbit.yaml Review profile: ASSERTIVE Plan: Pro Plus Run ID: You can disable this status message by setting the Use the checkbox below for a quick retry:
Comment |
🎨 Storybook: ✅ Built — View Storybook🎭 Playwright: ✅ 1989 passed, 0 failed · 1 flaky📊 Browser Reports
📦 Bundle Size
⚡ Performance Report
Absolute values
Raw data{
"timestamp": "2026-08-22T07:01:02.118Z",
"gitSha": "5aa2526a2c3e872a85cadde285e88b991137aa15",
"branch": "cb__widget-value-contract",
"measurements": [
{
"name": "canvas-idle",
"durationMs": 2092.576000000008,
"styleRecalcs": 6,
"styleRecalcDurationMs": 4.744,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 570.2230000000001,
"heapDeltaBytes": 5946428,
"heapUsedBytes": 75002492,
"domNodes": -282,
"jsHeapTotalBytes": 5234688,
"scriptDurationMs": 9.572000000000001,
"eventListeners": -159,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.666666666666668,
"p95FrameDurationMs": 16.799999999999272
},
{
"name": "canvas-idle",
"durationMs": 2070.938999999953,
"styleRecalcs": 7,
"styleRecalcDurationMs": 7.1979999999999995,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 548.659,
"heapDeltaBytes": 12895128,
"heapUsedBytes": 81798200,
"domNodes": -279,
"jsHeapTotalBytes": 4972544,
"scriptDurationMs": 11.017,
"eventListeners": -155,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.66333333333335,
"p95FrameDurationMs": 16.700000000000728
},
{
"name": "canvas-mouse-sweep",
"durationMs": 2268.8980000000356,
"styleRecalcs": 79,
"styleRecalcDurationMs": 44.42400000000001,
"layouts": 12,
"layoutDurationMs": 4.656,
"taskDurationMs": 1235.96,
"heapDeltaBytes": -1810320,
"heapUsedBytes": 67173360,
"domNodes": -282,
"jsHeapTotalBytes": 5496832,
"scriptDurationMs": 160.983,
"eventListeners": -155,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.666666666666668,
"p95FrameDurationMs": 16.700000000000728
},
{
"name": "canvas-mouse-sweep",
"durationMs": 2241.1189999999124,
"styleRecalcs": 79,
"styleRecalcDurationMs": 44.43000000000001,
"layouts": 12,
"layoutDurationMs": 3.589,
"taskDurationMs": 1315.6509999999998,
"heapDeltaBytes": -11290948,
"heapUsedBytes": 58627976,
"domNodes": -279,
"jsHeapTotalBytes": 6807552,
"scriptDurationMs": 159.353,
"eventListeners": -185,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.666666666666668,
"p95FrameDurationMs": 16.700000000000728
},
{
"name": "canvas-zoom-sweep",
"durationMs": 1746.1640000000216,
"styleRecalcs": 30,
"styleRecalcDurationMs": 19.928,
"layouts": 6,
"layoutDurationMs": 2.698,
"taskDurationMs": 374.481,
"heapDeltaBytes": 4010000,
"heapUsedBytes": 73540048,
"domNodes": 74,
"jsHeapTotalBytes": 5505024,
"scriptDurationMs": 11.644,
"eventListeners": 19,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.666666666666668,
"p95FrameDurationMs": 16.699999999999818
},
{
"name": "canvas-zoom-sweep",
"durationMs": 1736.1799999999903,
"styleRecalcs": 32,
"styleRecalcDurationMs": 17.279000000000003,
"layouts": 6,
"layoutDurationMs": 0.827,
"taskDurationMs": 404.505,
"heapDeltaBytes": 3662256,
"heapUsedBytes": 73040960,
"domNodes": 74,
"jsHeapTotalBytes": 5242880,
"scriptDurationMs": 14.335,
"eventListeners": 19,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.666666666666668,
"p95FrameDurationMs": 16.700000000000728
},
{
"name": "dom-widget-clipping",
"durationMs": 706.7090000000462,
"styleRecalcs": 10,
"styleRecalcDurationMs": 6.4799999999999995,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 415.21100000000007,
"heapDeltaBytes": -6986400,
"heapUsedBytes": 62234804,
"domNodes": 16,
"jsHeapTotalBytes": 6291456,
"scriptDurationMs": 32.428,
"eventListeners": 0,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.666666666666668,
"p95FrameDurationMs": 16.699999999999818
},
{
"name": "dom-widget-clipping",
"durationMs": 743.0480000000443,
"styleRecalcs": 9,
"styleRecalcDurationMs": 6.629000000000003,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 472.40700000000004,
"heapDeltaBytes": 19611280,
"heapUsedBytes": 81725004,
"domNodes": 14,
"jsHeapTotalBytes": 4718592,
"scriptDurationMs": 35.106,
"eventListeners": 2,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.666666666666668,
"p95FrameDurationMs": 16.700000000000728
},
{
"name": "large-graph-idle",
"durationMs": 2050.399000000027,
"styleRecalcs": 3,
"styleRecalcDurationMs": 3.0279999999999996,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 545.248,
"heapDeltaBytes": -17531692,
"heapUsedBytes": 72939756,
"domNodes": -272,
"jsHeapTotalBytes": -1576960,
"scriptDurationMs": 41.001,
"eventListeners": -155,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.66333333333332,
"p95FrameDurationMs": 16.700000000000728
},
{
"name": "large-graph-idle",
"durationMs": 2039.7689999999784,
"styleRecalcs": 4,
"styleRecalcDurationMs": 3.594,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 551.9449999999999,
"heapDeltaBytes": -18126016,
"heapUsedBytes": 72487908,
"domNodes": -271,
"jsHeapTotalBytes": -2625536,
"scriptDurationMs": 37.168000000000006,
"eventListeners": -153,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.66333333333332,
"p95FrameDurationMs": 16.700000000000728
},
{
"name": "large-graph-pan",
"durationMs": 3209.6460000000206,
"styleRecalcs": 62,
"styleRecalcDurationMs": 9.598000000000003,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 2528.68,
"heapDeltaBytes": 25101356,
"heapUsedBytes": 90082196,
"domNodes": 0,
"jsHeapTotalBytes": 487424,
"scriptDurationMs": 916.116,
"eventListeners": 8,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.666666666666696,
"p95FrameDurationMs": 16.700000000000728
},
{
"name": "large-graph-pan",
"durationMs": 3316.6230000000496,
"styleRecalcs": 67,
"styleRecalcDurationMs": 13.338999999999999,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 2633.946,
"heapDeltaBytes": 6656292,
"heapUsedBytes": 97995660,
"domNodes": -282,
"jsHeapTotalBytes": 4677632,
"scriptDurationMs": 938.959,
"eventListeners": -181,
"totalBlockingTimeMs": 7,
"frameDurationMs": 16.66333333333332,
"p95FrameDurationMs": 16.700000000000728
},
{
"name": "large-graph-zoom",
"durationMs": 4356.878999999992,
"styleRecalcs": 66,
"styleRecalcDurationMs": 12.786999999999999,
"layouts": 60,
"layoutDurationMs": 8.485,
"taskDurationMs": 2539.327,
"heapDeltaBytes": 11186392,
"heapUsedBytes": 80104108,
"domNodes": 24,
"jsHeapTotalBytes": 262144,
"scriptDurationMs": 876.0709999999999,
"eventListeners": 12,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.66333333333338,
"p95FrameDurationMs": 16.799999999999272
},
{
"name": "large-graph-zoom",
"durationMs": 4599.658999999974,
"styleRecalcs": 66,
"styleRecalcDurationMs": 13.267999999999999,
"layouts": 60,
"layoutDurationMs": 8.87,
"taskDurationMs": 2774.797,
"heapDeltaBytes": -27931220,
"heapUsedBytes": 68380160,
"domNodes": -234,
"jsHeapTotalBytes": 2355200,
"scriptDurationMs": 965.3389999999999,
"eventListeners": -147,
"totalBlockingTimeMs": 1,
"frameDurationMs": 16.666666666666668,
"p95FrameDurationMs": 16.700000000000728
},
{
"name": "legacy-node-drag",
"durationMs": 3189.7140000000377,
"styleRecalcs": 41,
"styleRecalcDurationMs": 5.8439999999999985,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 2105.381,
"heapDeltaBytes": -1056292,
"heapUsedBytes": 70738036,
"domNodes": -288,
"jsHeapTotalBytes": -1613824,
"scriptDurationMs": 204.82500000000002,
"eventListeners": -1,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.66999999999998,
"p95FrameDurationMs": 16.799999999999272
},
{
"name": "legacy-node-drag",
"durationMs": 3159.057999999959,
"styleRecalcs": 41,
"styleRecalcDurationMs": 5.9670000000000005,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 2091.0190000000002,
"heapDeltaBytes": 13210248,
"heapUsedBytes": 79368860,
"domNodes": 0,
"jsHeapTotalBytes": 417792,
"scriptDurationMs": 214.456,
"eventListeners": 188,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.670000000000012,
"p95FrameDurationMs": 16.800000000000182
},
{
"name": "minimap-idle",
"durationMs": 2046.3100000000054,
"styleRecalcs": 4,
"styleRecalcDurationMs": 4.046000000000001,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 580.7390000000001,
"heapDeltaBytes": -21593884,
"heapUsedBytes": 70159768,
"domNodes": -277,
"jsHeapTotalBytes": 4190208,
"scriptDurationMs": 39.219,
"eventListeners": -185,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.66333333333332,
"p95FrameDurationMs": 16.699999999999818
},
{
"name": "minimap-idle",
"durationMs": 2061.8590000000268,
"styleRecalcs": 4,
"styleRecalcDurationMs": 6.620999999999999,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 593.37,
"heapDeltaBytes": -19348368,
"heapUsedBytes": 73126604,
"domNodes": -269,
"jsHeapTotalBytes": -1052672,
"scriptDurationMs": 42.77899999999999,
"eventListeners": -153,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.666666666666668,
"p95FrameDurationMs": 16.800000000000182
},
{
"name": "subgraph-dom-widget-clipping",
"durationMs": 737.3269999999934,
"styleRecalcs": 44,
"styleRecalcDurationMs": 8.336000000000002,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 514.6789999999999,
"heapDeltaBytes": 17959468,
"heapUsedBytes": 88084372,
"domNodes": 14,
"jsHeapTotalBytes": 5242880,
"scriptDurationMs": 138.21699999999998,
"eventListeners": 8,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.66333333333335,
"p95FrameDurationMs": 16.799999999999272
},
{
"name": "subgraph-dom-widget-clipping",
"durationMs": 798.1359999999995,
"styleRecalcs": 46,
"styleRecalcDurationMs": 11.587,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 580.283,
"heapDeltaBytes": 17919204,
"heapUsedBytes": 87635072,
"domNodes": 18,
"jsHeapTotalBytes": 5505024,
"scriptDurationMs": 150.475,
"eventListeners": 8,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.666666666666668,
"p95FrameDurationMs": 16.800000000000182
},
{
"name": "subgraph-idle",
"durationMs": 2017.6329999999894,
"styleRecalcs": 6,
"styleRecalcDurationMs": 5.675,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 474.36,
"heapDeltaBytes": 397544,
"heapUsedBytes": 64025236,
"domNodes": -299,
"jsHeapTotalBytes": 4448256,
"scriptDurationMs": 7.589,
"eventListeners": -231,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.666666666666668,
"p95FrameDurationMs": 16.800000000000182
},
{
"name": "subgraph-idle",
"durationMs": 2039.3669999999702,
"styleRecalcs": 8,
"styleRecalcDurationMs": 7.828999999999999,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 598.447,
"heapDeltaBytes": -12558860,
"heapUsedBytes": 56857788,
"domNodes": -295,
"jsHeapTotalBytes": 5234688,
"scriptDurationMs": 11.283999999999999,
"eventListeners": -201,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.666666666666668,
"p95FrameDurationMs": 16.700000000000728
},
{
"name": "subgraph-mouse-sweep",
"durationMs": 1872.1720000000346,
"styleRecalcs": 79,
"styleRecalcDurationMs": 39.964,
"layouts": 16,
"layoutDurationMs": 4.898,
"taskDurationMs": 1117.7659999999998,
"heapDeltaBytes": -3171368,
"heapUsedBytes": 66462928,
"domNodes": -296,
"jsHeapTotalBytes": 5758976,
"scriptDurationMs": 110.398,
"eventListeners": -201,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.666666666666668,
"p95FrameDurationMs": 16.799999999999272
},
{
"name": "subgraph-mouse-sweep",
"durationMs": 1968.6719999999696,
"styleRecalcs": 79,
"styleRecalcDurationMs": 43.365,
"layouts": 16,
"layoutDurationMs": 4.882,
"taskDurationMs": 1196.1840000000002,
"heapDeltaBytes": 10591380,
"heapUsedBytes": 80219492,
"domNodes": -297,
"jsHeapTotalBytes": 5496832,
"scriptDurationMs": 120.64299999999999,
"eventListeners": -231,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.666666666666668,
"p95FrameDurationMs": 16.700000000000728
},
{
"name": "subgraph-transition-enter",
"durationMs": 1401.8690000000333,
"styleRecalcs": 18,
"styleRecalcDurationMs": 32.407000000000004,
"layouts": 13,
"layoutDurationMs": 14.106,
"taskDurationMs": 1024.6620000000003,
"heapDeltaBytes": -9358772,
"heapUsedBytes": 83084184,
"domNodes": 13671,
"jsHeapTotalBytes": 10747904,
"scriptDurationMs": 32.049,
"eventListeners": 2383,
"totalBlockingTimeMs": 136,
"frameDurationMs": 16.666666666666668,
"p95FrameDurationMs": 16.700000000000728
},
{
"name": "viewport-pan-sweep",
"durationMs": 12314.523999999949,
"styleRecalcs": 248,
"styleRecalcDurationMs": 42.016,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 9013.66,
"heapDeltaBytes": 1678952,
"heapUsedBytes": 92320904,
"domNodes": -277,
"jsHeapTotalBytes": 4644864,
"scriptDurationMs": 3105.283,
"eventListeners": -159,
"totalBlockingTimeMs": 20,
"frameDurationMs": 16.669999999999952,
"p95FrameDurationMs": 16.700000000000728
},
{
"name": "viewport-pan-sweep",
"durationMs": 12369.847999999933,
"styleRecalcs": 247,
"styleRecalcDurationMs": 42.77499999999999,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 9435.827,
"heapDeltaBytes": 2145940,
"heapUsedBytes": 92509972,
"domNodes": -282,
"jsHeapTotalBytes": 4382720,
"scriptDurationMs": 3214.385,
"eventListeners": -159,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.66333333333338,
"p95FrameDurationMs": 16.700000000000728
},
{
"name": "vue-large-graph-idle",
"durationMs": 18692.655000000002,
"styleRecalcs": 0,
"styleRecalcDurationMs": 0,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 18158.436999999998,
"heapDeltaBytes": -32593576,
"heapUsedBytes": 177134392,
"domNodes": -6548,
"jsHeapTotalBytes": -1146880,
"scriptDurationMs": 386.68,
"eventListeners": -7475,
"totalBlockingTimeMs": 0,
"frameDurationMs": 17.77333333333336,
"p95FrameDurationMs": 16.700000000000728
},
{
"name": "vue-large-graph-idle",
"durationMs": 19162.12399999995,
"styleRecalcs": 0,
"styleRecalcDurationMs": 0,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 18879.808,
"heapDeltaBytes": -31076892,
"heapUsedBytes": 179436476,
"domNodes": -6548,
"jsHeapTotalBytes": -1122304,
"scriptDurationMs": 408.451,
"eventListeners": -7475,
"totalBlockingTimeMs": 0,
"frameDurationMs": 18.333333333333332,
"p95FrameDurationMs": 16.700000000000728
},
{
"name": "vue-large-graph-pan",
"durationMs": 23739.390000000014,
"styleRecalcs": 180,
"styleRecalcDurationMs": 23.73699999999998,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 23479.928,
"heapDeltaBytes": -43849876,
"heapUsedBytes": 166800956,
"domNodes": -6548,
"jsHeapTotalBytes": -1523712,
"scriptDurationMs": 951.94,
"eventListeners": -7467,
"totalBlockingTimeMs": 255,
"frameDurationMs": 17.77333333333348,
"p95FrameDurationMs": 16.700000000004366
},
{
"name": "vue-large-graph-pan",
"durationMs": 24221.140000000105,
"styleRecalcs": 182,
"styleRecalcDurationMs": 29.79200000000004,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 24106.83,
"heapDeltaBytes": -17885472,
"heapUsedBytes": 187957096,
"domNodes": -6548,
"jsHeapTotalBytes": -4702208,
"scriptDurationMs": 1106.0259999999998,
"eventListeners": -7465,
"totalBlockingTimeMs": 198,
"frameDurationMs": 17.776666666666763,
"p95FrameDurationMs": 16.80000000000291
},
{
"name": "workflow-execution",
"durationMs": 130.87399999994886,
"styleRecalcs": 7,
"styleRecalcDurationMs": 15.862000000000002,
"layouts": 3,
"layoutDurationMs": 1.441,
"taskDurationMs": 102.545,
"heapDeltaBytes": 3538588,
"heapUsedBytes": 70083268,
"domNodes": 131,
"jsHeapTotalBytes": 0,
"scriptDurationMs": 7.528,
"eventListeners": 25,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.666666666666668,
"p95FrameDurationMs": 16.800000000000182
},
{
"name": "workflow-execution",
"durationMs": 140.39900000000216,
"styleRecalcs": 9,
"styleRecalcDurationMs": 18.769,
"layouts": 3,
"layoutDurationMs": 1.002,
"taskDurationMs": 95.72699999999998,
"heapDeltaBytes": 3408732,
"heapUsedBytes": 65659008,
"domNodes": 112,
"jsHeapTotalBytes": 262144,
"scriptDurationMs": 5.302,
"eventListeners": 49,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.666666666666668,
"p95FrameDurationMs": 16.700000000000728
}
]
} |
Codecov Report✅ All modified and coverable lines are covered by tests. @@ Coverage Diff @@
## feature/ecs-migration #15596 +/- ##
========================================================
Coverage ? 82.29%
========================================================
Files ? 1900
Lines ? 107781
Branches ? 34113
========================================================
Hits ? 88696
Misses ? 18747
Partials ? 338
Flags with carried forward coverage won't be shown. Click here to find out more. 🚀 New features to boost your workflow:
|
`WidgetValue` and `NodeProperty` both admit `null`, and `LGraphNode.serialize` mints it (`val ?? null`), but no test asserted that any persistence path round-trips it. `main`'s `SubgraphNode.serialize` silently drops it. Adds round-trip coverage for the paths that had none: - workflow write / read, via both the indexed and named-values restore branches, plus draft-cache JSON text and a two-pass idempotence check - API prompt (`graphToPrompt`), with an `options.serialize` control arm - load-time schema validation, array and object forms Each assertion was mutation-verified: mutating the corresponding filter to drop null fails the test, and the unmutated control passes. Documents the contract in ADR 0016.
fced8a9 to
68542bc
Compare
|
The ADR does not appear necessary for this PR. This change does not introduce an architectural decision; it tests an existing type and behavior contract. The ADR adds disproportionate scope and maintenance burden. Suggest removing the ADR and its README entry while keeping the focused tests. |
WidgetValueandNodePropertyboth admitnull,setProperty(name, null)propagates it to the bound widget, andLGraphNode.serializeactively mints it —val ?? nullturns anundefinedwidget value intonullon the way intowidgets_values. Sonullis already in first-party workflow JSON with no extension involved.Nothing asserted that any persistence path round-trips it, and on
mainone path does not:SubgraphNode.serializefilters throughisWidgetValue, whosemainpredicate rejectsnull, so a null promoted value becomesundefinedand — when every promoted widget is null — the wholewidgets_valueskey is deleted. This branch already fixes the behaviour by wideningisWidgetValuetovalue == null. This PR adds the assertions that were missing on both sides.I enumerated every persistence path that filters widget values, not just the subgraph one. Verdicts:
LGraphNode.serializewidget.serialize === false;val ?? nullLGraphNode.configure(indexed + named-values)serialize === false,name in namedValuesSubgraphNode.serializeisWidgetValuemainSubgraphNode._applyPromotedWidgetValuesvalue !== undefinedexecutionUtilAPI promptwidget.options?.serialize === falseworkflowSchemaload validationz.any()_serializeItemsproxyWidgetMigrationisWidgetValuemain(null classed as a hole)isWidgetValuehas two call sites, not one — the same predicate change fixes both.New coverage, all mutation-verified (mutating the corresponding filter to drop null fails the test; unmutated control passes):
widgetValueNullContract.test.ts— workflow write, workflow read via both restore branches, draft-cache JSON text, two-pass idempotence, plus awidget.serialize === falsecontrol arm and a stored-null-vs-absent control armexecutionUtil.test.ts— prompt path emits null, with anoptions.serializecontrol armworkflowSchema.test.ts— array and object forms preserve nullOne trap worth flagging for reviewers: widget state is keyed
graphId:nodeId:nameandconfigure()restores the original graph id, so a naive save/load test re-adopts the livewidgetValueStoreentry and passes without reading the serialized JSON at all. My first draft did exactly that.roundTripGraphdrops the store between save and load; without that line four of these tests are vacuous.ADR 0016 records the contract and the evidence, including the reachability measurement (nine public extension repos assign null to a widget value, against a 4,280-file control arm) and the blast radius of the alternative (removing
nullfromWidgetValueyields 19vue-tscerrors across 11 files, against a 0-error control).Gates run by hand — worktrees run no hooks:
oxfmt,oxlint --type-aware(clean, verified against a control that emits),vue-tsc --noEmitexit 0 / 0 errors, 52 tests passing.