docs(plugin-timeline): scope spellGanttDateValue's totality claim to what is exercised - #7157
Conversation
…what is exercised `spellGanttDateValue`'s docblock claimed every branch was total. Measured in-render on dd35800 through the real `TimelineRenderer`, one branch is not: `Array.isArray` throws on a revoked `Proxy`, because `IsArray` recurses into `[[ProxyTarget]]` and a revoked proxy has none. The exclusion is stated and exercised rather than repaired. A `catch` here would substitute `an object` for a failure rather than read anything, unlike `isDate`'s catch, whose docblock argues the catch IS the read; the input is unreachable from an authored document; and it would buy no invariant, because `Array.isArray` is NOT the last non-total operation on the gantt date path. Five reads that FETCH the date throw first, all upstream of this function. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012wwHa4aaFybxXrfmfHioDM
Empty frontmatter: the change is a docblock correction plus exercised rows in an existing pinned adversarial set. No published behaviour moves. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012wwHa4aaFybxXrfmfHioDM
✅ Console Performance Budget
The eager closure is every chunk the entry reaches through static imports — what the browser fetches and parses before the app renders. The entry chunk on its own is a small fraction of it. 📦 Bundle Size Report
Size Limits
|
✅ Reviewed and armedAll 30 check runs in one call at You falsified the card's headline, and that is what chose the routeI ruled "leave it and say so in the docblock" a full pass, but the interesting part is why it turned out to be the right branch rather than the cheap one. A3.4 is FALSE. That demolishes the card's value proposition. It argued: this is the last one, so repairing it closes the path. With five upstream sites, a Two further reasons, both substantive rather than economical: the input is unreachable from an authored document (re-swept with live controls), and a catch here would substitute The ablation is an inversion, and it is the right instrumentYou ablated the repair you declined. Wrapping Mutation proven by injected-marker count and blob hash; restore proven by state.
|
Fixes #7036
Took the card's second branch: the docblock overclaimed, so the claim is corrected and the exclusion is exercised.
Array.isArrayis left exactly as #6907 chose it. Measured ondd35800af; verified one615be490.Why not "make the array test total"
Three reasons, none of them cost. A
try/catcharoundArray.isArraywould be four lines.dd35800af, each zero beside a live control on the same instrument: zeroProxy.revocablein the repo (control: 52Proxymentions in package sources), zeroObject.setPrototypeOfcalls in package sources (control: 19Object.assigncalls). The three non-comment__proto__hits are denylists that block it.isDate's catch is argued in its own docblock as the read — the language exposes the[[DateValue]]bit only by throwing, so there is no other way to ask. A revoked proxy has no array-ness to read: catching would discard a failure and substitutean object. That is the consumer-side tolerance finding(plugin-timeline): a gantt timeline with an EMPTY literalitemsarray throws —calculateDateRangereduces an empty list #6750 and finding(plugin-timeline): two more unusable gantt date ranges — a malformed date still throws, and an inverted author-pinned range silently draws a negative-width bar on no axis #6759 both refused, and a secondcatchsitting besideisDate's would erase the distinction this file is built on.The card's headline is false as measured
#7036's title says
Array.isArrayis the last non-total operation on the gantt date path. It is not. It is the last one insidespellGanttDateValue. Measured in-render through the realTimelineRenderer, five further crash sites exist and every one runs before that helper is entered — in the property reads that fetch the date out of the document (findUnusableGanttDate'sitems[i]?.itemsandrowItems[j]?.[key];calculateDateRangerepeats both):items[0].items[0]with a throwingendDategetterError: endDate getter throwsitems[0].items[0]is a revokedProxyTypeError: Cannot perform 'get' on a proxy that has been revokeditems[0]is a revokedProxyitems[0].itemsis a revokedProxyitems[0]with a throwingitemsgetterError: items getter throwsEnumerated in #7153 (p3, same reachability class), not repaired here — different function, outside this card's ruled scope.
What was measured, and how
The card measured with "a node probe replicating both functions' branch bodies verbatim (not measured in-render)". This PR did not use a replica: every reading below comes from
render(<TimelineRenderer …>)driving the real code.Two facts the card did not have: a revoked proxy over a real
Datealso lands here (isDaterefuses it first, so it falls through toArray.isArray), and the pinnedminDate/maxDatelimb reaches the same site.Not a regression, confirmed. On
7fc5c3c12the value crashed atisGanttDateType'svalue instanceof Date(instanceofwalks[[GetPrototypeOf]], which throws on a revoked proxy — measured), and that tree'sspellGanttDateValuecarried a secondinstanceofplus the sameArray.isArray. #7027 did not introduce anything: it cut this input's throw sites from three to one.The change
packages/plugin-timeline/src/renderer.tsx— docblock only, no executable line moved. The section heading no longer says every branch is total; the "everything else" bullet carries the exclusion inline; a⚠️ #7036note follows finding(plugin-timeline): a slot-lessDateimpostor passes the gantt type gate and then crashesnew Datemid-render #7027's, naming the sentence it falsifies, the measurement, the three reasons, and the five upstream sites. The falsified sentences above it are kept verbatim — the file's own idiom (finding(plugin-timeline): a slot-lessDateimpostor passes the gantt type gate and then crashesnew Datemid-render #7027 did the same), so the chain stays readable.packages/plugin-timeline/src/__tests__/timeline-gantt-date-brand-7027.test.tsx—pin 4, six rows in the existing set (no new file), plus a header entry for the fifth claim.No new confident totality sentence. Everything the docblock now claims is explicitly bounded by the exercised input set, because prose has been the failure mode on this path four times running. That is also why the exclusion is pinned rather than merely written: a sentence would be a fifth claim of the same species.
Tests
pnpm exec vitest run packages/plugin-timeline/one615be490— 19 files, 226 passed (220 before;pin 4adds 6). The two source blobs are byte-identical between the tested commit and the head above.Rows assert the throw's message, never a bare
toThrow(): a bare one passes for any error from any line, so it would stay green if the crash moved toinstanceof, toObject.prototype.toString, or upstream into the row walk — which is exactly what has happened here four times. A live-proxy CONTROL row keeps the four throw rows from passing on a wholly broken gantt branch.Ablation. Predicted before running: applying the repair this card declined (wrapping
Array.isArrayintry/catch) turns the 5 throw rows red and leaves the CONTROL green — 10 passed / 5 failed of 15. Observed exactly that. Mutation proven on disk by injected-marker count (0 → 1) and blob hash (5acd179b…→bcd53d6a…), never by an editor's exit code. No rebuild was needed: the suite imports../rendererby relative path, so it resolves to source, not through the package'sexportsintodist/. Restore proven by state —git diff HEAD,git diff --cachedandgit status --shortall empty, the on-disk blob back to5acd179b…, marker count 0.Gates run locally (union, on
e615be490)check-changeset-presence·check-changeset-no-major·check-changeset-fixed·check-control-bytes·check-lint-coverage·check-type-check-coverage·check-i18n-call-site-keys·check-i18n-en-drift— all exit 0.Changeset gate verdict, verbatim and not predicted:
eslint .inpackages/plugin-timeline— exit 0, 0 errors (97 warnings, all pre-existing and none on added lines).pnpm --filter @object-ui/plugin-timeline run type-check— exit 0, andtsc --listFilesconfirms both edited files are actually in the program (1369 files), so that green is a reading rather than an exclusion.Repo-wide
pnpm lintwas not run locally; CI owns that farm.Generated by Claude Code