Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions .changeset/spellgantt-revoked-proxy-exclusion.md
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
---
---

Docs and tests only for `@object-ui/plugin-timeline`. `spellGanttDateValue`'s
docblock claimed every branch was total; measured in-render, `Array.isArray`
throws on a revoked `Proxy`, so the claim is now scoped to the input set that
is actually exercised and the exclusion is pinned as rows in the existing
adversarial set. The same measurement falsified the wider claim that
`Array.isArray` is the last non-total operation on the gantt date path — five
reads upstream of the helper throw first, recorded in objectui#7153. No
published behaviour changes.
Original file line numberDiff line numberDiff line change
Expand Up@@ -30,6 +30,17 @@
* about being a Date, each one asserted to reach a DEFINED outcome. That is
* what #7026 gave the speller and what the gate did not have.
*
* 5. objectui#7036: the gate's repair held, and the SPELLER still was not
* total — `Array.isArray` throws on a revoked `Proxy`. That card did not
* repair it; it ruled the exclusion STATED AND EXERCISED, which is what
* `pin 4` at the foot of this file is. It also measured that the card's
* own headline was wrong: `Array.isArray` is not the last non-total
* operation on the PATH, only inside that function (objectui#7153).
*
* ⚠️ So this file's input set is not a claim of totality either. It is the
* boundary that has actually been exercised, and the sixth card will be the
* one that says where it ends.
*
* ## What "a defined outcome" means here — two kinds, and never a third
*
* ⚠️ Not every row below is a refusal, and forcing them all to be one would
Expand DownExpand Up@@ -181,6 +192,17 @@ class HijackedGetTime extends Date {
}
}

/**
* A REVOKED proxy — the one input `spellGanttDateValue` is documented NOT to
* survive (objectui#7036). `Proxy.revocable` is the only way to spell it, and
* nothing in any package `src/` calls it: this is a test-only value.
*/
const revokedProxy = (target: object = {}) => {
const { proxy, revoke } = Proxy.revocable(target, {});
revoke();
return proxy as unknown;
};

/** An object whose `Symbol.toStringTag` is a throwing getter. */
const throwingToStringTag = () =>
({
Expand DownExpand Up@@ -347,3 +369,74 @@ describe('pin 3 — the ACCEPT SET is unchanged: #6781’s ruling does not move
}
});
});

describe('pin 4 — the ONE documented EXCLUSION, exercised not asserted (objectui#7036)', () => {
/**
* `spellGanttDateValue`'s `Array.isArray` is not total: `IsArray` recurses
* into `[[ProxyTarget]]` and a REVOKED proxy has none, so it throws while
* naming the value. objectui#7036 adjudicated this as STATED-AND-EXERCISED
* rather than repaired, on three grounds recorded in that function's
* docblock: it is unreachable from an authored document (JSON cannot spell
* a proxy), a `catch` here would SUBSTITUTE `an object` for a failure
* rather than read anything the way `isDate`'s catch does, and it would not
* make the PATH total anyway — five reads that FETCH the date throw first
* (objectui#7153).
*
* ## Why a THROW is pinned here, and why not with a bare `toThrow()`
*
* This file exists because four prose totality claims on this path were
* each falsified by the next card's measurement. A docblock sentence saying
* "except a revoked proxy" would be a fifth claim of the same species. The
* rows below make the exclusion a MEASUREMENT instead, and they assert the
* throw's own MESSAGE: a bare `toThrow()` passes for any error from any
* line, so it would stay green if the crash moved to `instanceof`, to
* `Object.prototype.toString`, or upstream into the row walk — which is
* precisely what has happened on this path four times. The message names
* both the operation (`IsArray`) and the cause (`revoked`), so the pin
* fails if the site moves and fails if the exclusion is ever repaired,
* either of which must come with a docblock edit.
*/

const REVOKED_ISARRAY = /Cannot perform 'IsArray' on a proxy that has been revoked/;

// Every target shape: the throw is about revocation, not about the target.
const targets: [string, () => object][] = [
['{}', () => ({})],
['[]', () => []],
['a function', () => function noop() {}],
['a real Date', () => new Date('2024-03-01')],
];

for (const [label, makeTarget] of targets) {
it(`a revoked Proxy over ${label} throws at \`Array.isArray\`, by design`, () => {
expect(() =>
gantt({ items: rowWith({ startDate: '2024-01-01', endDate: revokedProxy(makeTarget()) }) }),
).toThrow(REVOKED_ISARRAY);
});
}

it('a revoked Proxy pinned as `minDate` reaches the same site', () => {
// The pinned limb takes the same speller, so the exclusion is not
// confined to a row item. `value &&` is ToBoolean and does not throw.
expect(() =>
gantt({
items: rowWith({ startDate: '2024-01-01', endDate: '2024-03-01' }),
minDate: revokedProxy(),
}),
).toThrow(REVOKED_ISARRAY);
});

it('CONTROL — a LIVE Proxy is still NAMED `an object`, so the rows above are about REVOCATION', () => {
// Without this the four rows above would also pass if the whole gantt
// branch had broken. `typeof` does not separate a revoked proxy out
// either: it answers `'object'` for one, exactly as it does here.
const { container } = gantt({
items: rowWith({ startDate: '2024-01-01', endDate: new Proxy({}, {}) }),
});

const text = diagnosticOf(container) ?? '';
expect(text).toContain(PATH);
expect(text).toContain('is an object');
expect(barCountOf(container)).toBe(0);
});
});
70 changes: 68 additions & 2 deletions packages/plugin-timeline/src/renderer.tsx
Original file line numberDiff line numberDiff line change
Expand Up@@ -427,7 +427,7 @@ const isDate = (value: unknown): value is Date => {
* worse than naming a category: it costs the author a search for a fault that
* is not there.
*
* ## The branches, and why each is total
* ## The branches, and how far each one's totality reaches
*
* - `string` -> quoted (#6759's pin; empty and space-padded stay visible).
* - `undefined` / `null` -> themselves (#6759 / #6770's pins; how an author
Expand DownExpand Up@@ -455,7 +455,10 @@ const isDate = (value: unknown): value is Date => {
* `Array.isArray` and `typeof`, which read no author-controlled property.
* Deliberately NOT `Object.prototype.toString.call`: that consults
* `Symbol.toStringTag`, which can be a throwing getter (measured), so the
* more informative spelling is the non-total one.
* more informative spelling is the non-total one. `Array.isArray` reads no
* property and is STILL not total — on a revoked `Proxy` it throws. That is
* the one exclusion this docblock claims, and it is stated and exercised
* rather than repaired: see the objectui#7036 note below.
*
* All eight `typeof` results are covered and no branch falls through to author
* code. The single reflective operation it performs is the Date test, which is
Expand All@@ -471,6 +474,69 @@ const isDate = (value: unknown): value is Date => {
* operation adds no throw site because it HAS none — not because the gate
* absorbed it first.
*
* ⚠️ objectui#7036 — READ THE TWO PARAGRAPHS ABOVE AS A SEQUENCE, NOT AS A
* CONCLUSION. Each was written as the settled answer and the next card's
* measurement moved it (#6759 -> #6905 -> #6907 -> #7027). This is the fifth
* entry and it is deliberately NOT a fifth claim of totality. The sentence it
* falsifies is the one directly above the #7027 note: this function DOES add
* a throw site the accept gate does not have, and it is `Array.isArray`.
*
* THE EXCLUSION, measured in-render on dd35800af through `TimelineRenderer`
* itself — not a replica of these branch bodies:
*
* endDate: <a revoked Proxy, over any target>
* -> THREW TypeError: Cannot perform 'IsArray' on a proxy that has
* been revoked (here, at `Array.isArray`)
*
* `IsArray` recurses into `[[ProxyTarget]]`, which a revoked proxy does not
* have, so this throws on an INTERNAL condition while still reading no
* author-controlled property. All four target shapes throw (`{}`, `[]`, a
* function, a real `Date`), and so does a revoked proxy pinned as
* `schema.minDate` / `maxDate`. `typeof` does not separate it out — it
* answers `'object'` (or `'function'`) without throwing.
*
* WHY IT IS LEFT, and none of the three reasons is cost:
*
* 1. It is not reachable from an authored document — ObjectUI metadata is
* JSON and JSON cannot spell a proxy. Re-swept on dd35800af: zero
* `Proxy.revocable` in the repo, zero `Object.setPrototypeOf` calls in
* any package `src/`, each read beside a live control on the same
* instrument
* (52 `Proxy` mentions, 19 `Object.assign` calls) so the zeros are
* readings and not a broken query. The `__proto__` hits are denylists.
* 2. A `catch` here would be SUBSTITUTION, not a read — the opposite of
* `isDate`'s. `isDate` catches because the language exposes the
* `[[DateValue]]` bit ONLY by throwing, so its catch IS the read. A
* revoked proxy has no array-ness to read, so catching would discard a
* failure and substitute `an object`: consumer-side tolerance, which is
* what #6750 and #6759 both refused. A second `catch` beside `isDate`'s
* would erase the distinction this file is built on.
* 3. It would buy no invariant, because of the paragraph below.
*
* ⛔ `Array.isArray` IS NOT THE LAST NON-TOTAL OPERATION ON THE GANTT DATE
* PATH, and this function cannot make the path total. Measured in the same
* run: five further crash sites, every one of them reached BEFORE this
* function is entered, in the property reads that FETCH the date out of the
* authored document (`findUnusableGanttDate`'s `items[i]?.items` and
* `rowItems[j]?.[key]`) —
*
* items[0].items[0] with a throwing `endDate` getter -> THREW
* items[0].items[0] is a revoked Proxy -> THREW
* items[0] is a revoked Proxy -> THREW
* items[0].items is a revoked Proxy -> THREW
* items[0] with a throwing `items` getter -> THREW
*
* They are the same reachability class (JSON spells neither a getter nor a
* proxy) and they are enumerated in objectui#7153 rather than repaired here —
* they are a different function's surface. The true and much narrower
* sentence is that `Array.isArray` is the last non-total operation INSIDE
* THIS FUNCTION.
*
* ⚠️ Whatever totality this docblock claims is bounded by an EXERCISED input
* set — the rows in `__tests__/timeline-gantt-date-brand-7027.test.tsx`,
* which now include the revoked proxy — and by nothing else. On this path
* prose has been a hypothesis four times running.
*
* ## What this deliberately does NOT do
*
* It does not change WHICH values are refused — that is #6781's ruling and it
Expand Down
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Add copy buttons to all \u003cpre\u003e\u003ccode\u003e blocks\n(function() {\n function addCopyButtons() {\n document.querySelectorAll('pre code').forEach(function(codeBlock) {\n if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;\n codeBlock.parentElement.setAttribute('data-copy-added', 'true');\n \n var btn = document.createElement('button');\n btn.textContent = 'Copy';\n btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';\n btn.onmouseover = function() { this.style.opacity = '1'; };\n btn.onmouseout = function() { this.style.opacity = '0.7'; };\n btn.onclick = function() {\n navigator.clipboard.writeText(codeBlock.textContent).then(function() {\n btn.textContent = 'Copied!';\n setTimeout(function() { btn.textContent = 'Copy'; }, 1500);\n });\n };\n codeBlock.parentElement.style.position = 'relative';\n codeBlock.parentElement.appendChild(btn);\n });\n }\n \n addCopyButtons();\n \n // Re-run on dynamic content\n var observer = new MutationObserver(addCopyButtons);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Add Copy Buttons to Code Blocks"); } } catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); } })(); (function(){ try { var __m = "github.com"; var __re = new RegExp('^' + "github\\.com" + '
Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions .changeset/spellgantt-revoked-proxy-exclusion.md
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
---
---

Docs and tests only for `@object-ui/plugin-timeline`. `spellGanttDateValue`'s
docblock claimed every branch was total; measured in-render, `Array.isArray`
throws on a revoked `Proxy`, so the claim is now scoped to the input set that
is actually exercised and the exclusion is pinned as rows in the existing
adversarial set. The same measurement falsified the wider claim that
`Array.isArray` is the last non-total operation on the gantt date path — five
reads upstream of the helper throw first, recorded in objectui#7153. No
published behaviour changes.
Original file line numberDiff line numberDiff line change
Expand Up@@ -30,6 +30,17 @@
* about being a Date, each one asserted to reach a DEFINED outcome. That is
* what #7026 gave the speller and what the gate did not have.
*
* 5. objectui#7036: the gate's repair held, and the SPELLER still was not
* total — `Array.isArray` throws on a revoked `Proxy`. That card did not
* repair it; it ruled the exclusion STATED AND EXERCISED, which is what
* `pin 4` at the foot of this file is. It also measured that the card's
* own headline was wrong: `Array.isArray` is not the last non-total
* operation on the PATH, only inside that function (objectui#7153).
*
* ⚠️ So this file's input set is not a claim of totality either. It is the
* boundary that has actually been exercised, and the sixth card will be the
* one that says where it ends.
*
* ## What "a defined outcome" means here — two kinds, and never a third
*
* ⚠️ Not every row below is a refusal, and forcing them all to be one would
Expand DownExpand Up@@ -181,6 +192,17 @@ class HijackedGetTime extends Date {
}
}

/**
* A REVOKED proxy — the one input `spellGanttDateValue` is documented NOT to
* survive (objectui#7036). `Proxy.revocable` is the only way to spell it, and
* nothing in any package `src/` calls it: this is a test-only value.
*/
const revokedProxy = (target: object = {}) => {
const { proxy, revoke } = Proxy.revocable(target, {});
revoke();
return proxy as unknown;
};

/** An object whose `Symbol.toStringTag` is a throwing getter. */
const throwingToStringTag = () =>
({
Expand DownExpand Up@@ -347,3 +369,74 @@ describe('pin 3 — the ACCEPT SET is unchanged: #6781’s ruling does not move
}
});
});

describe('pin 4 — the ONE documented EXCLUSION, exercised not asserted (objectui#7036)', () => {
/**
* `spellGanttDateValue`'s `Array.isArray` is not total: `IsArray` recurses
* into `[[ProxyTarget]]` and a REVOKED proxy has none, so it throws while
* naming the value. objectui#7036 adjudicated this as STATED-AND-EXERCISED
* rather than repaired, on three grounds recorded in that function's
* docblock: it is unreachable from an authored document (JSON cannot spell
* a proxy), a `catch` here would SUBSTITUTE `an object` for a failure
* rather than read anything the way `isDate`'s catch does, and it would not
* make the PATH total anyway — five reads that FETCH the date throw first
* (objectui#7153).
*
* ## Why a THROW is pinned here, and why not with a bare `toThrow()`
*
* This file exists because four prose totality claims on this path were
* each falsified by the next card's measurement. A docblock sentence saying
* "except a revoked proxy" would be a fifth claim of the same species. The
* rows below make the exclusion a MEASUREMENT instead, and they assert the
* throw's own MESSAGE: a bare `toThrow()` passes for any error from any
* line, so it would stay green if the crash moved to `instanceof`, to
* `Object.prototype.toString`, or upstream into the row walk — which is
* precisely what has happened on this path four times. The message names
* both the operation (`IsArray`) and the cause (`revoked`), so the pin
* fails if the site moves and fails if the exclusion is ever repaired,
* either of which must come with a docblock edit.
*/

const REVOKED_ISARRAY = /Cannot perform 'IsArray' on a proxy that has been revoked/;

// Every target shape: the throw is about revocation, not about the target.
const targets: [string, () => object][] = [
['{}', () => ({})],
['[]', () => []],
['a function', () => function noop() {}],
['a real Date', () => new Date('2024-03-01')],
];

for (const [label, makeTarget] of targets) {
it(`a revoked Proxy over ${label} throws at \`Array.isArray\`, by design`, () => {
expect(() =>
gantt({ items: rowWith({ startDate: '2024-01-01', endDate: revokedProxy(makeTarget()) }) }),
).toThrow(REVOKED_ISARRAY);
});
}

it('a revoked Proxy pinned as `minDate` reaches the same site', () => {
// The pinned limb takes the same speller, so the exclusion is not
// confined to a row item. `value &&` is ToBoolean and does not throw.
expect(() =>
gantt({
items: rowWith({ startDate: '2024-01-01', endDate: '2024-03-01' }),
minDate: revokedProxy(),
}),
).toThrow(REVOKED_ISARRAY);
});

it('CONTROL — a LIVE Proxy is still NAMED `an object`, so the rows above are about REVOCATION', () => {
// Without this the four rows above would also pass if the whole gantt
// branch had broken. `typeof` does not separate a revoked proxy out
// either: it answers `'object'` for one, exactly as it does here.
const { container } = gantt({
items: rowWith({ startDate: '2024-01-01', endDate: new Proxy({}, {}) }),
});

const text = diagnosticOf(container) ?? '';
expect(text).toContain(PATH);
expect(text).toContain('is an object');
expect(barCountOf(container)).toBe(0);
});
});
70 changes: 68 additions & 2 deletions packages/plugin-timeline/src/renderer.tsx
Original file line numberDiff line numberDiff line change
Expand Up@@ -427,7 +427,7 @@ const isDate = (value: unknown): value is Date => {
* worse than naming a category: it costs the author a search for a fault that
* is not there.
*
* ## The branches, and why each is total
* ## The branches, and how far each one's totality reaches
*
* - `string` -> quoted (#6759's pin; empty and space-padded stay visible).
* - `undefined` / `null` -> themselves (#6759 / #6770's pins; how an author
Expand DownExpand Up@@ -455,7 +455,10 @@ const isDate = (value: unknown): value is Date => {
* `Array.isArray` and `typeof`, which read no author-controlled property.
* Deliberately NOT `Object.prototype.toString.call`: that consults
* `Symbol.toStringTag`, which can be a throwing getter (measured), so the
* more informative spelling is the non-total one.
* more informative spelling is the non-total one. `Array.isArray` reads no
* property and is STILL not total — on a revoked `Proxy` it throws. That is
* the one exclusion this docblock claims, and it is stated and exercised
* rather than repaired: see the objectui#7036 note below.
*
* All eight `typeof` results are covered and no branch falls through to author
* code. The single reflective operation it performs is the Date test, which is
Expand All@@ -471,6 +474,69 @@ const isDate = (value: unknown): value is Date => {
* operation adds no throw site because it HAS none — not because the gate
* absorbed it first.
*
* ⚠️ objectui#7036 — READ THE TWO PARAGRAPHS ABOVE AS A SEQUENCE, NOT AS A
* CONCLUSION. Each was written as the settled answer and the next card's
* measurement moved it (#6759 -> #6905 -> #6907 -> #7027). This is the fifth
* entry and it is deliberately NOT a fifth claim of totality. The sentence it
* falsifies is the one directly above the #7027 note: this function DOES add
* a throw site the accept gate does not have, and it is `Array.isArray`.
*
* THE EXCLUSION, measured in-render on dd35800af through `TimelineRenderer`
* itself — not a replica of these branch bodies:
*
* endDate: <a revoked Proxy, over any target>
* -> THREW TypeError: Cannot perform 'IsArray' on a proxy that has
* been revoked (here, at `Array.isArray`)
*
* `IsArray` recurses into `[[ProxyTarget]]`, which a revoked proxy does not
* have, so this throws on an INTERNAL condition while still reading no
* author-controlled property. All four target shapes throw (`{}`, `[]`, a
* function, a real `Date`), and so does a revoked proxy pinned as
* `schema.minDate` / `maxDate`. `typeof` does not separate it out — it
* answers `'object'` (or `'function'`) without throwing.
*
* WHY IT IS LEFT, and none of the three reasons is cost:
*
* 1. It is not reachable from an authored document — ObjectUI metadata is
* JSON and JSON cannot spell a proxy. Re-swept on dd35800af: zero
* `Proxy.revocable` in the repo, zero `Object.setPrototypeOf` calls in
* any package `src/`, each read beside a live control on the same
* instrument
* (52 `Proxy` mentions, 19 `Object.assign` calls) so the zeros are
* readings and not a broken query. The `__proto__` hits are denylists.
* 2. A `catch` here would be SUBSTITUTION, not a read — the opposite of
* `isDate`'s. `isDate` catches because the language exposes the
* `[[DateValue]]` bit ONLY by throwing, so its catch IS the read. A
* revoked proxy has no array-ness to read, so catching would discard a
* failure and substitute `an object`: consumer-side tolerance, which is
* what #6750 and #6759 both refused. A second `catch` beside `isDate`'s
* would erase the distinction this file is built on.
* 3. It would buy no invariant, because of the paragraph below.
*
* ⛔ `Array.isArray` IS NOT THE LAST NON-TOTAL OPERATION ON THE GANTT DATE
* PATH, and this function cannot make the path total. Measured in the same
* run: five further crash sites, every one of them reached BEFORE this
* function is entered, in the property reads that FETCH the date out of the
* authored document (`findUnusableGanttDate`'s `items[i]?.items` and
* `rowItems[j]?.[key]`) —
*
* items[0].items[0] with a throwing `endDate` getter -> THREW
* items[0].items[0] is a revoked Proxy -> THREW
* items[0] is a revoked Proxy -> THREW
* items[0].items is a revoked Proxy -> THREW
* items[0] with a throwing `items` getter -> THREW
*
* They are the same reachability class (JSON spells neither a getter nor a
* proxy) and they are enumerated in objectui#7153 rather than repaired here —
* they are a different function's surface. The true and much narrower
* sentence is that `Array.isArray` is the last non-total operation INSIDE
* THIS FUNCTION.
*
* ⚠️ Whatever totality this docblock claims is bounded by an EXERCISED input
* set — the rows in `__tests__/timeline-gantt-date-brand-7027.test.tsx`,
* which now include the revoked proxy — and by nothing else. On this path
* prose has been a hypothesis four times running.
*
* ## What this deliberately does NOT do
*
* It does not change WHICH values are refused — that is #6781's ruling and it
Expand Down
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Force GitHub README to respect dark mode\n(function() {\n var style = document.createElement('style');\n style.textContent = '\n .markdown-body {\n color-scheme: dark light;\n }\n .markdown-body pre { background: #161b22 !important; }\n .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; }\n .markdown-body table th, .markdown-body table td { border-color: #30363d !important; }\n .markdown-body img { background: #0d1117; }\n .markdown-body blockquote { border-left-color: #8b949e; }\n .markdown-body hr { border-color: #30363d; }\n ';\n document.head.appendChild(style);\n})();", "GitHub Dark Mode README Fix"); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions .changeset/spellgantt-revoked-proxy-exclusion.md
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
---
---

Docs and tests only for `@object-ui/plugin-timeline`. `spellGanttDateValue`'s
docblock claimed every branch was total; measured in-render, `Array.isArray`
throws on a revoked `Proxy`, so the claim is now scoped to the input set that
is actually exercised and the exclusion is pinned as rows in the existing
adversarial set. The same measurement falsified the wider claim that
`Array.isArray` is the last non-total operation on the gantt date path — five
reads upstream of the helper throw first, recorded in objectui#7153. No
published behaviour changes.
Original file line numberDiff line numberDiff line change
Expand Up@@ -30,6 +30,17 @@
* about being a Date, each one asserted to reach a DEFINED outcome. That is
* what #7026 gave the speller and what the gate did not have.
*
* 5. objectui#7036: the gate's repair held, and the SPELLER still was not
* total — `Array.isArray` throws on a revoked `Proxy`. That card did not
* repair it; it ruled the exclusion STATED AND EXERCISED, which is what
* `pin 4` at the foot of this file is. It also measured that the card's
* own headline was wrong: `Array.isArray` is not the last non-total
* operation on the PATH, only inside that function (objectui#7153).
*
* ⚠️ So this file's input set is not a claim of totality either. It is the
* boundary that has actually been exercised, and the sixth card will be the
* one that says where it ends.
*
* ## What "a defined outcome" means here — two kinds, and never a third
*
* ⚠️ Not every row below is a refusal, and forcing them all to be one would
Expand DownExpand Up@@ -181,6 +192,17 @@ class HijackedGetTime extends Date {
}
}

/**
* A REVOKED proxy — the one input `spellGanttDateValue` is documented NOT to
* survive (objectui#7036). `Proxy.revocable` is the only way to spell it, and
* nothing in any package `src/` calls it: this is a test-only value.
*/
const revokedProxy = (target: object = {}) => {
const { proxy, revoke } = Proxy.revocable(target, {});
revoke();
return proxy as unknown;
};

/** An object whose `Symbol.toStringTag` is a throwing getter. */
const throwingToStringTag = () =>
({
Expand DownExpand Up@@ -347,3 +369,74 @@ describe('pin 3 — the ACCEPT SET is unchanged: #6781’s ruling does not move
}
});
});

describe('pin 4 — the ONE documented EXCLUSION, exercised not asserted (objectui#7036)', () => {
/**
* `spellGanttDateValue`'s `Array.isArray` is not total: `IsArray` recurses
* into `[[ProxyTarget]]` and a REVOKED proxy has none, so it throws while
* naming the value. objectui#7036 adjudicated this as STATED-AND-EXERCISED
* rather than repaired, on three grounds recorded in that function's
* docblock: it is unreachable from an authored document (JSON cannot spell
* a proxy), a `catch` here would SUBSTITUTE `an object` for a failure
* rather than read anything the way `isDate`'s catch does, and it would not
* make the PATH total anyway — five reads that FETCH the date throw first
* (objectui#7153).
*
* ## Why a THROW is pinned here, and why not with a bare `toThrow()`
*
* This file exists because four prose totality claims on this path were
* each falsified by the next card's measurement. A docblock sentence saying
* "except a revoked proxy" would be a fifth claim of the same species. The
* rows below make the exclusion a MEASUREMENT instead, and they assert the
* throw's own MESSAGE: a bare `toThrow()` passes for any error from any
* line, so it would stay green if the crash moved to `instanceof`, to
* `Object.prototype.toString`, or upstream into the row walk — which is
* precisely what has happened on this path four times. The message names
* both the operation (`IsArray`) and the cause (`revoked`), so the pin
* fails if the site moves and fails if the exclusion is ever repaired,
* either of which must come with a docblock edit.
*/

const REVOKED_ISARRAY = /Cannot perform 'IsArray' on a proxy that has been revoked/;

// Every target shape: the throw is about revocation, not about the target.
const targets: [string, () => object][] = [
['{}', () => ({})],
['[]', () => []],
['a function', () => function noop() {}],
['a real Date', () => new Date('2024-03-01')],
];

for (const [label, makeTarget] of targets) {
it(`a revoked Proxy over ${label} throws at \`Array.isArray\`, by design`, () => {
expect(() =>
gantt({ items: rowWith({ startDate: '2024-01-01', endDate: revokedProxy(makeTarget()) }) }),
).toThrow(REVOKED_ISARRAY);
});
}

it('a revoked Proxy pinned as `minDate` reaches the same site', () => {
// The pinned limb takes the same speller, so the exclusion is not
// confined to a row item. `value &&` is ToBoolean and does not throw.
expect(() =>
gantt({
items: rowWith({ startDate: '2024-01-01', endDate: '2024-03-01' }),
minDate: revokedProxy(),
}),
).toThrow(REVOKED_ISARRAY);
});

it('CONTROL — a LIVE Proxy is still NAMED `an object`, so the rows above are about REVOCATION', () => {
// Without this the four rows above would also pass if the whole gantt
// branch had broken. `typeof` does not separate a revoked proxy out
// either: it answers `'object'` for one, exactly as it does here.
const { container } = gantt({
items: rowWith({ startDate: '2024-01-01', endDate: new Proxy({}, {}) }),
});

const text = diagnosticOf(container) ?? '';
expect(text).toContain(PATH);
expect(text).toContain('is an object');
expect(barCountOf(container)).toBe(0);
});
});
70 changes: 68 additions & 2 deletions packages/plugin-timeline/src/renderer.tsx
Original file line numberDiff line numberDiff line change
Expand Up@@ -427,7 +427,7 @@ const isDate = (value: unknown): value is Date => {
* worse than naming a category: it costs the author a search for a fault that
* is not there.
*
* ## The branches, and why each is total
* ## The branches, and how far each one's totality reaches
*
* - `string` -> quoted (#6759's pin; empty and space-padded stay visible).
* - `undefined` / `null` -> themselves (#6759 / #6770's pins; how an author
Expand DownExpand Up@@ -455,7 +455,10 @@ const isDate = (value: unknown): value is Date => {
* `Array.isArray` and `typeof`, which read no author-controlled property.
* Deliberately NOT `Object.prototype.toString.call`: that consults
* `Symbol.toStringTag`, which can be a throwing getter (measured), so the
* more informative spelling is the non-total one.
* more informative spelling is the non-total one. `Array.isArray` reads no
* property and is STILL not total — on a revoked `Proxy` it throws. That is
* the one exclusion this docblock claims, and it is stated and exercised
* rather than repaired: see the objectui#7036 note below.
*
* All eight `typeof` results are covered and no branch falls through to author
* code. The single reflective operation it performs is the Date test, which is
Expand All@@ -471,6 +474,69 @@ const isDate = (value: unknown): value is Date => {
* operation adds no throw site because it HAS none — not because the gate
* absorbed it first.
*
* ⚠️ objectui#7036 — READ THE TWO PARAGRAPHS ABOVE AS A SEQUENCE, NOT AS A
* CONCLUSION. Each was written as the settled answer and the next card's
* measurement moved it (#6759 -> #6905 -> #6907 -> #7027). This is the fifth
* entry and it is deliberately NOT a fifth claim of totality. The sentence it
* falsifies is the one directly above the #7027 note: this function DOES add
* a throw site the accept gate does not have, and it is `Array.isArray`.
*
* THE EXCLUSION, measured in-render on dd35800af through `TimelineRenderer`
* itself — not a replica of these branch bodies:
*
* endDate: <a revoked Proxy, over any target>
* -> THREW TypeError: Cannot perform 'IsArray' on a proxy that has
* been revoked (here, at `Array.isArray`)
*
* `IsArray` recurses into `[[ProxyTarget]]`, which a revoked proxy does not
* have, so this throws on an INTERNAL condition while still reading no
* author-controlled property. All four target shapes throw (`{}`, `[]`, a
* function, a real `Date`), and so does a revoked proxy pinned as
* `schema.minDate` / `maxDate`. `typeof` does not separate it out — it
* answers `'object'` (or `'function'`) without throwing.
*
* WHY IT IS LEFT, and none of the three reasons is cost:
*
* 1. It is not reachable from an authored document — ObjectUI metadata is
* JSON and JSON cannot spell a proxy. Re-swept on dd35800af: zero
* `Proxy.revocable` in the repo, zero `Object.setPrototypeOf` calls in
* any package `src/`, each read beside a live control on the same
* instrument
* (52 `Proxy` mentions, 19 `Object.assign` calls) so the zeros are
* readings and not a broken query. The `__proto__` hits are denylists.
* 2. A `catch` here would be SUBSTITUTION, not a read — the opposite of
* `isDate`'s. `isDate` catches because the language exposes the
* `[[DateValue]]` bit ONLY by throwing, so its catch IS the read. A
* revoked proxy has no array-ness to read, so catching would discard a
* failure and substitute `an object`: consumer-side tolerance, which is
* what #6750 and #6759 both refused. A second `catch` beside `isDate`'s
* would erase the distinction this file is built on.
* 3. It would buy no invariant, because of the paragraph below.
*
* ⛔ `Array.isArray` IS NOT THE LAST NON-TOTAL OPERATION ON THE GANTT DATE
* PATH, and this function cannot make the path total. Measured in the same
* run: five further crash sites, every one of them reached BEFORE this
* function is entered, in the property reads that FETCH the date out of the
* authored document (`findUnusableGanttDate`'s `items[i]?.items` and
* `rowItems[j]?.[key]`) —
*
* items[0].items[0] with a throwing `endDate` getter -> THREW
* items[0].items[0] is a revoked Proxy -> THREW
* items[0] is a revoked Proxy -> THREW
* items[0].items is a revoked Proxy -> THREW
* items[0] with a throwing `items` getter -> THREW
*
* They are the same reachability class (JSON spells neither a getter nor a
* proxy) and they are enumerated in objectui#7153 rather than repaired here —
* they are a different function's surface. The true and much narrower
* sentence is that `Array.isArray` is the last non-total operation INSIDE
* THIS FUNCTION.
*
* ⚠️ Whatever totality this docblock claims is bounded by an EXERCISED input
* set — the rows in `__tests__/timeline-gantt-date-brand-7027.test.tsx`,
* which now include the revoked proxy — and by nothing else. On this path
* prose has been a hypothesis four times running.
*
* ## What this deliberately does NOT do
*
* It does not change WHICH values are refused — that is #6781's ruling and it
Expand Down
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Highlight search terms from Google/DuckDuckGo/Bing referrer\n(function() {\n var ref = document.referrer;\n var terms = [];\n \n if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) {\n var url = new URL(ref);\n var q = url.searchParams.get('q') || url.searchParams.get('p');\n if (q) {\n terms = q.split(/\\s+/).filter(function(t) { return t.length \u003e 2; });\n }\n }\n \n if (terms.length === 0) return;\n \n var style = document.createElement('style');\n style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }';\n document.head.appendChild(style);\n \n function highlight(node) {\n if (node.nodeType === 3) { // text node\n var text = node.textContent;\n var found = false;\n terms.forEach(function(term) {\n var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\') + ')', 'gi');\n if (regex.test(text)) {\n found = true;\n var frag = document.createDocumentFragment();\n var parts = text.split(regex);\n parts.forEach(function(part, i) {\n if (i % 2 === 0) {\n frag.appendChild(document.createTextNode(part));\n } else {\n var span = document.createElement('span');\n span.className = 'userscript-highlight';\n span.textContent = part;\n frag.appendChild(span);\n }\n });\n node.parentNode.replaceChild(frag, node);\n }\n });\n } else if (node.nodeType === 1 && node.childNodes) { // element\n var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT'];\n if (!skipTags.includes(node.tagName)) {\n Array.from(node.childNodes).forEach(highlight);\n }\n }\n }\n \n highlight(document.body);\n \n // Re-highlight on dynamic content\n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1 || node.nodeType === 3) highlight(node);\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Highlight Search Terms"); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions .changeset/spellgantt-revoked-proxy-exclusion.md
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
---
---

Docs and tests only for `@object-ui/plugin-timeline`. `spellGanttDateValue`'s
docblock claimed every branch was total; measured in-render, `Array.isArray`
throws on a revoked `Proxy`, so the claim is now scoped to the input set that
is actually exercised and the exclusion is pinned as rows in the existing
adversarial set. The same measurement falsified the wider claim that
`Array.isArray` is the last non-total operation on the gantt date path — five
reads upstream of the helper throw first, recorded in objectui#7153. No
published behaviour changes.
Original file line numberDiff line numberDiff line change
Expand Up@@ -30,6 +30,17 @@
* about being a Date, each one asserted to reach a DEFINED outcome. That is
* what #7026 gave the speller and what the gate did not have.
*
* 5. objectui#7036: the gate's repair held, and the SPELLER still was not
* total — `Array.isArray` throws on a revoked `Proxy`. That card did not
* repair it; it ruled the exclusion STATED AND EXERCISED, which is what
* `pin 4` at the foot of this file is. It also measured that the card's
* own headline was wrong: `Array.isArray` is not the last non-total
* operation on the PATH, only inside that function (objectui#7153).
*
* ⚠️ So this file's input set is not a claim of totality either. It is the
* boundary that has actually been exercised, and the sixth card will be the
* one that says where it ends.
*
* ## What "a defined outcome" means here — two kinds, and never a third
*
* ⚠️ Not every row below is a refusal, and forcing them all to be one would
Expand DownExpand Up@@ -181,6 +192,17 @@ class HijackedGetTime extends Date {
}
}

/**
* A REVOKED proxy — the one input `spellGanttDateValue` is documented NOT to
* survive (objectui#7036). `Proxy.revocable` is the only way to spell it, and
* nothing in any package `src/` calls it: this is a test-only value.
*/
const revokedProxy = (target: object = {}) => {
const { proxy, revoke } = Proxy.revocable(target, {});
revoke();
return proxy as unknown;
};

/** An object whose `Symbol.toStringTag` is a throwing getter. */
const throwingToStringTag = () =>
({
Expand DownExpand Up@@ -347,3 +369,74 @@ describe('pin 3 — the ACCEPT SET is unchanged: #6781’s ruling does not move
}
});
});

describe('pin 4 — the ONE documented EXCLUSION, exercised not asserted (objectui#7036)', () => {
/**
* `spellGanttDateValue`'s `Array.isArray` is not total: `IsArray` recurses
* into `[[ProxyTarget]]` and a REVOKED proxy has none, so it throws while
* naming the value. objectui#7036 adjudicated this as STATED-AND-EXERCISED
* rather than repaired, on three grounds recorded in that function's
* docblock: it is unreachable from an authored document (JSON cannot spell
* a proxy), a `catch` here would SUBSTITUTE `an object` for a failure
* rather than read anything the way `isDate`'s catch does, and it would not
* make the PATH total anyway — five reads that FETCH the date throw first
* (objectui#7153).
*
* ## Why a THROW is pinned here, and why not with a bare `toThrow()`
*
* This file exists because four prose totality claims on this path were
* each falsified by the next card's measurement. A docblock sentence saying
* "except a revoked proxy" would be a fifth claim of the same species. The
* rows below make the exclusion a MEASUREMENT instead, and they assert the
* throw's own MESSAGE: a bare `toThrow()` passes for any error from any
* line, so it would stay green if the crash moved to `instanceof`, to
* `Object.prototype.toString`, or upstream into the row walk — which is
* precisely what has happened on this path four times. The message names
* both the operation (`IsArray`) and the cause (`revoked`), so the pin
* fails if the site moves and fails if the exclusion is ever repaired,
* either of which must come with a docblock edit.
*/

const REVOKED_ISARRAY = /Cannot perform 'IsArray' on a proxy that has been revoked/;

// Every target shape: the throw is about revocation, not about the target.
const targets: [string, () => object][] = [
['{}', () => ({})],
['[]', () => []],
['a function', () => function noop() {}],
['a real Date', () => new Date('2024-03-01')],
];

for (const [label, makeTarget] of targets) {
it(`a revoked Proxy over ${label} throws at \`Array.isArray\`, by design`, () => {
expect(() =>
gantt({ items: rowWith({ startDate: '2024-01-01', endDate: revokedProxy(makeTarget()) }) }),
).toThrow(REVOKED_ISARRAY);
});
}

it('a revoked Proxy pinned as `minDate` reaches the same site', () => {
// The pinned limb takes the same speller, so the exclusion is not
// confined to a row item. `value &&` is ToBoolean and does not throw.
expect(() =>
gantt({
items: rowWith({ startDate: '2024-01-01', endDate: '2024-03-01' }),
minDate: revokedProxy(),
}),
).toThrow(REVOKED_ISARRAY);
});

it('CONTROL — a LIVE Proxy is still NAMED `an object`, so the rows above are about REVOCATION', () => {
// Without this the four rows above would also pass if the whole gantt
// branch had broken. `typeof` does not separate a revoked proxy out
// either: it answers `'object'` for one, exactly as it does here.
const { container } = gantt({
items: rowWith({ startDate: '2024-01-01', endDate: new Proxy({}, {}) }),
});

const text = diagnosticOf(container) ?? '';
expect(text).toContain(PATH);
expect(text).toContain('is an object');
expect(barCountOf(container)).toBe(0);
});
});
70 changes: 68 additions & 2 deletions packages/plugin-timeline/src/renderer.tsx
Original file line numberDiff line numberDiff line change
Expand Up@@ -427,7 +427,7 @@ const isDate = (value: unknown): value is Date => {
* worse than naming a category: it costs the author a search for a fault that
* is not there.
*
* ## The branches, and why each is total
* ## The branches, and how far each one's totality reaches
*
* - `string` -> quoted (#6759's pin; empty and space-padded stay visible).
* - `undefined` / `null` -> themselves (#6759 / #6770's pins; how an author
Expand DownExpand Up@@ -455,7 +455,10 @@ const isDate = (value: unknown): value is Date => {
* `Array.isArray` and `typeof`, which read no author-controlled property.
* Deliberately NOT `Object.prototype.toString.call`: that consults
* `Symbol.toStringTag`, which can be a throwing getter (measured), so the
* more informative spelling is the non-total one.
* more informative spelling is the non-total one. `Array.isArray` reads no
* property and is STILL not total — on a revoked `Proxy` it throws. That is
* the one exclusion this docblock claims, and it is stated and exercised
* rather than repaired: see the objectui#7036 note below.
*
* All eight `typeof` results are covered and no branch falls through to author
* code. The single reflective operation it performs is the Date test, which is
Expand All@@ -471,6 +474,69 @@ const isDate = (value: unknown): value is Date => {
* operation adds no throw site because it HAS none — not because the gate
* absorbed it first.
*
* ⚠️ objectui#7036 — READ THE TWO PARAGRAPHS ABOVE AS A SEQUENCE, NOT AS A
* CONCLUSION. Each was written as the settled answer and the next card's
* measurement moved it (#6759 -> #6905 -> #6907 -> #7027). This is the fifth
* entry and it is deliberately NOT a fifth claim of totality. The sentence it
* falsifies is the one directly above the #7027 note: this function DOES add
* a throw site the accept gate does not have, and it is `Array.isArray`.
*
* THE EXCLUSION, measured in-render on dd35800af through `TimelineRenderer`
* itself — not a replica of these branch bodies:
*
* endDate: <a revoked Proxy, over any target>
* -> THREW TypeError: Cannot perform 'IsArray' on a proxy that has
* been revoked (here, at `Array.isArray`)
*
* `IsArray` recurses into `[[ProxyTarget]]`, which a revoked proxy does not
* have, so this throws on an INTERNAL condition while still reading no
* author-controlled property. All four target shapes throw (`{}`, `[]`, a
* function, a real `Date`), and so does a revoked proxy pinned as
* `schema.minDate` / `maxDate`. `typeof` does not separate it out — it
* answers `'object'` (or `'function'`) without throwing.
*
* WHY IT IS LEFT, and none of the three reasons is cost:
*
* 1. It is not reachable from an authored document — ObjectUI metadata is
* JSON and JSON cannot spell a proxy. Re-swept on dd35800af: zero
* `Proxy.revocable` in the repo, zero `Object.setPrototypeOf` calls in
* any package `src/`, each read beside a live control on the same
* instrument
* (52 `Proxy` mentions, 19 `Object.assign` calls) so the zeros are
* readings and not a broken query. The `__proto__` hits are denylists.
* 2. A `catch` here would be SUBSTITUTION, not a read — the opposite of
* `isDate`'s. `isDate` catches because the language exposes the
* `[[DateValue]]` bit ONLY by throwing, so its catch IS the read. A
* revoked proxy has no array-ness to read, so catching would discard a
* failure and substitute `an object`: consumer-side tolerance, which is
* what #6750 and #6759 both refused. A second `catch` beside `isDate`'s
* would erase the distinction this file is built on.
* 3. It would buy no invariant, because of the paragraph below.
*
* ⛔ `Array.isArray` IS NOT THE LAST NON-TOTAL OPERATION ON THE GANTT DATE
* PATH, and this function cannot make the path total. Measured in the same
* run: five further crash sites, every one of them reached BEFORE this
* function is entered, in the property reads that FETCH the date out of the
* authored document (`findUnusableGanttDate`'s `items[i]?.items` and
* `rowItems[j]?.[key]`) —
*
* items[0].items[0] with a throwing `endDate` getter -> THREW
* items[0].items[0] is a revoked Proxy -> THREW
* items[0] is a revoked Proxy -> THREW
* items[0].items is a revoked Proxy -> THREW
* items[0] with a throwing `items` getter -> THREW
*
* They are the same reachability class (JSON spells neither a getter nor a
* proxy) and they are enumerated in objectui#7153 rather than repaired here —
* they are a different function's surface. The true and much narrower
* sentence is that `Array.isArray` is the last non-total operation INSIDE
* THIS FUNCTION.
*
* ⚠️ Whatever totality this docblock claims is bounded by an EXERCISED input
* set — the rows in `__tests__/timeline-gantt-date-brand-7027.test.tsx`,
* which now include the revoked proxy — and by nothing else. On this path
* prose has been a hypothesis four times running.
*
* ## What this deliberately does NOT do
*
* It does not change WHICH values are refused — that is #6781's ruling and it
Expand Down
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Strip utm_, fbclid, gclid, etc. from all links on page\n(function() {\n var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content',\n 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid',\n 'ref', 'ref_src', 'source', 'medium', 'campaign'];\n \n function cleanUrl(url) {\n try {\n var u = new URL(url, window.location.origin);\n var changed = false;\n trackingParams.forEach(function(p) {\n if (u.searchParams.has(p)) {\n u.searchParams.delete(p);\n changed = true;\n }\n });\n return changed ? u.toString() : url;\n } catch (e) {\n return url;\n }\n }\n \n function cleanLinks() {\n document.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n \n cleanLinks();\n \n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1) {\n if (node.tagName === 'A') cleanLinks();\n node.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Remove Tracking Parameters from Links"); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + '
Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions .changeset/spellgantt-revoked-proxy-exclusion.md
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
---
---

Docs and tests only for `@object-ui/plugin-timeline`. `spellGanttDateValue`'s
docblock claimed every branch was total; measured in-render, `Array.isArray`
throws on a revoked `Proxy`, so the claim is now scoped to the input set that
is actually exercised and the exclusion is pinned as rows in the existing
adversarial set. The same measurement falsified the wider claim that
`Array.isArray` is the last non-total operation on the gantt date path — five
reads upstream of the helper throw first, recorded in objectui#7153. No
published behaviour changes.
Original file line numberDiff line numberDiff line change
Expand Up@@ -30,6 +30,17 @@
* about being a Date, each one asserted to reach a DEFINED outcome. That is
* what #7026 gave the speller and what the gate did not have.
*
* 5. objectui#7036: the gate's repair held, and the SPELLER still was not
* total — `Array.isArray` throws on a revoked `Proxy`. That card did not
* repair it; it ruled the exclusion STATED AND EXERCISED, which is what
* `pin 4` at the foot of this file is. It also measured that the card's
* own headline was wrong: `Array.isArray` is not the last non-total
* operation on the PATH, only inside that function (objectui#7153).
*
* ⚠️ So this file's input set is not a claim of totality either. It is the
* boundary that has actually been exercised, and the sixth card will be the
* one that says where it ends.
*
* ## What "a defined outcome" means here — two kinds, and never a third
*
* ⚠️ Not every row below is a refusal, and forcing them all to be one would
Expand DownExpand Up@@ -181,6 +192,17 @@ class HijackedGetTime extends Date {
}
}

/**
* A REVOKED proxy — the one input `spellGanttDateValue` is documented NOT to
* survive (objectui#7036). `Proxy.revocable` is the only way to spell it, and
* nothing in any package `src/` calls it: this is a test-only value.
*/
const revokedProxy = (target: object = {}) => {
const { proxy, revoke } = Proxy.revocable(target, {});
revoke();
return proxy as unknown;
};

/** An object whose `Symbol.toStringTag` is a throwing getter. */
const throwingToStringTag = () =>
({
Expand DownExpand Up@@ -347,3 +369,74 @@ describe('pin 3 — the ACCEPT SET is unchanged: #6781’s ruling does not move
}
});
});

describe('pin 4 — the ONE documented EXCLUSION, exercised not asserted (objectui#7036)', () => {
/**
* `spellGanttDateValue`'s `Array.isArray` is not total: `IsArray` recurses
* into `[[ProxyTarget]]` and a REVOKED proxy has none, so it throws while
* naming the value. objectui#7036 adjudicated this as STATED-AND-EXERCISED
* rather than repaired, on three grounds recorded in that function's
* docblock: it is unreachable from an authored document (JSON cannot spell
* a proxy), a `catch` here would SUBSTITUTE `an object` for a failure
* rather than read anything the way `isDate`'s catch does, and it would not
* make the PATH total anyway — five reads that FETCH the date throw first
* (objectui#7153).
*
* ## Why a THROW is pinned here, and why not with a bare `toThrow()`
*
* This file exists because four prose totality claims on this path were
* each falsified by the next card's measurement. A docblock sentence saying
* "except a revoked proxy" would be a fifth claim of the same species. The
* rows below make the exclusion a MEASUREMENT instead, and they assert the
* throw's own MESSAGE: a bare `toThrow()` passes for any error from any
* line, so it would stay green if the crash moved to `instanceof`, to
* `Object.prototype.toString`, or upstream into the row walk — which is
* precisely what has happened on this path four times. The message names
* both the operation (`IsArray`) and the cause (`revoked`), so the pin
* fails if the site moves and fails if the exclusion is ever repaired,
* either of which must come with a docblock edit.
*/

const REVOKED_ISARRAY = /Cannot perform 'IsArray' on a proxy that has been revoked/;

// Every target shape: the throw is about revocation, not about the target.
const targets: [string, () => object][] = [
['{}', () => ({})],
['[]', () => []],
['a function', () => function noop() {}],
['a real Date', () => new Date('2024-03-01')],
];

for (const [label, makeTarget] of targets) {
it(`a revoked Proxy over ${label} throws at \`Array.isArray\`, by design`, () => {
expect(() =>
gantt({ items: rowWith({ startDate: '2024-01-01', endDate: revokedProxy(makeTarget()) }) }),
).toThrow(REVOKED_ISARRAY);
});
}

it('a revoked Proxy pinned as `minDate` reaches the same site', () => {
// The pinned limb takes the same speller, so the exclusion is not
// confined to a row item. `value &&` is ToBoolean and does not throw.
expect(() =>
gantt({
items: rowWith({ startDate: '2024-01-01', endDate: '2024-03-01' }),
minDate: revokedProxy(),
}),
).toThrow(REVOKED_ISARRAY);
});

it('CONTROL — a LIVE Proxy is still NAMED `an object`, so the rows above are about REVOCATION', () => {
// Without this the four rows above would also pass if the whole gantt
// branch had broken. `typeof` does not separate a revoked proxy out
// either: it answers `'object'` for one, exactly as it does here.
const { container } = gantt({
items: rowWith({ startDate: '2024-01-01', endDate: new Proxy({}, {}) }),
});

const text = diagnosticOf(container) ?? '';
expect(text).toContain(PATH);
expect(text).toContain('is an object');
expect(barCountOf(container)).toBe(0);
});
});
70 changes: 68 additions & 2 deletions packages/plugin-timeline/src/renderer.tsx
Original file line numberDiff line numberDiff line change
Expand Up@@ -427,7 +427,7 @@ const isDate = (value: unknown): value is Date => {
* worse than naming a category: it costs the author a search for a fault that
* is not there.
*
* ## The branches, and why each is total
* ## The branches, and how far each one's totality reaches
*
* - `string` -> quoted (#6759's pin; empty and space-padded stay visible).
* - `undefined` / `null` -> themselves (#6759 / #6770's pins; how an author
Expand DownExpand Up@@ -455,7 +455,10 @@ const isDate = (value: unknown): value is Date => {
* `Array.isArray` and `typeof`, which read no author-controlled property.
* Deliberately NOT `Object.prototype.toString.call`: that consults
* `Symbol.toStringTag`, which can be a throwing getter (measured), so the
* more informative spelling is the non-total one.
* more informative spelling is the non-total one. `Array.isArray` reads no
* property and is STILL not total — on a revoked `Proxy` it throws. That is
* the one exclusion this docblock claims, and it is stated and exercised
* rather than repaired: see the objectui#7036 note below.
*
* All eight `typeof` results are covered and no branch falls through to author
* code. The single reflective operation it performs is the Date test, which is
Expand All@@ -471,6 +474,69 @@ const isDate = (value: unknown): value is Date => {
* operation adds no throw site because it HAS none — not because the gate
* absorbed it first.
*
* ⚠️ objectui#7036 — READ THE TWO PARAGRAPHS ABOVE AS A SEQUENCE, NOT AS A
* CONCLUSION. Each was written as the settled answer and the next card's
* measurement moved it (#6759 -> #6905 -> #6907 -> #7027). This is the fifth
* entry and it is deliberately NOT a fifth claim of totality. The sentence it
* falsifies is the one directly above the #7027 note: this function DOES add
* a throw site the accept gate does not have, and it is `Array.isArray`.
*
* THE EXCLUSION, measured in-render on dd35800af through `TimelineRenderer`
* itself — not a replica of these branch bodies:
*
* endDate: <a revoked Proxy, over any target>
* -> THREW TypeError: Cannot perform 'IsArray' on a proxy that has
* been revoked (here, at `Array.isArray`)
*
* `IsArray` recurses into `[[ProxyTarget]]`, which a revoked proxy does not
* have, so this throws on an INTERNAL condition while still reading no
* author-controlled property. All four target shapes throw (`{}`, `[]`, a
* function, a real `Date`), and so does a revoked proxy pinned as
* `schema.minDate` / `maxDate`. `typeof` does not separate it out — it
* answers `'object'` (or `'function'`) without throwing.
*
* WHY IT IS LEFT, and none of the three reasons is cost:
*
* 1. It is not reachable from an authored document — ObjectUI metadata is
* JSON and JSON cannot spell a proxy. Re-swept on dd35800af: zero
* `Proxy.revocable` in the repo, zero `Object.setPrototypeOf` calls in
* any package `src/`, each read beside a live control on the same
* instrument
* (52 `Proxy` mentions, 19 `Object.assign` calls) so the zeros are
* readings and not a broken query. The `__proto__` hits are denylists.
* 2. A `catch` here would be SUBSTITUTION, not a read — the opposite of
* `isDate`'s. `isDate` catches because the language exposes the
* `[[DateValue]]` bit ONLY by throwing, so its catch IS the read. A
* revoked proxy has no array-ness to read, so catching would discard a
* failure and substitute `an object`: consumer-side tolerance, which is
* what #6750 and #6759 both refused. A second `catch` beside `isDate`'s
* would erase the distinction this file is built on.
* 3. It would buy no invariant, because of the paragraph below.
*
* ⛔ `Array.isArray` IS NOT THE LAST NON-TOTAL OPERATION ON THE GANTT DATE
* PATH, and this function cannot make the path total. Measured in the same
* run: five further crash sites, every one of them reached BEFORE this
* function is entered, in the property reads that FETCH the date out of the
* authored document (`findUnusableGanttDate`'s `items[i]?.items` and
* `rowItems[j]?.[key]`) —
*
* items[0].items[0] with a throwing `endDate` getter -> THREW
* items[0].items[0] is a revoked Proxy -> THREW
* items[0] is a revoked Proxy -> THREW
* items[0].items is a revoked Proxy -> THREW
* items[0] with a throwing `items` getter -> THREW
*
* They are the same reachability class (JSON spells neither a getter nor a
* proxy) and they are enumerated in objectui#7153 rather than repaired here —
* they are a different function's surface. The true and much narrower
* sentence is that `Array.isArray` is the last non-total operation INSIDE
* THIS FUNCTION.
*
* ⚠️ Whatever totality this docblock claims is bounded by an EXERCISED input
* set — the rows in `__tests__/timeline-gantt-date-brand-7027.test.tsx`,
* which now include the revoked proxy — and by nothing else. On this path
* prose has been a hypothesis four times running.
*
* ## What this deliberately does NOT do
*
* It does not change WHICH values are refused — that is #6781's ruling and it
Expand Down
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Auto-enable theater mode on YouTube\n(function() {\n function tryTheater() {\n var btn = document.querySelector('button[aria-label=\"Theater mode\"], ytd-player #player button[title=\"Theater mode\"]');\n if (btn && !btn.classList.contains('activated')) {\n btn.click();\n }\n }\n \n // Try immediately\n tryTheater();\n \n // Try after navigation (SPA)\n var lastUrl = location.href;\n setInterval(function() {\n if (location.href !== lastUrl) {\n lastUrl = location.href;\n setTimeout(tryTheater, 500);\n }\n }, 1000);\n \n // Also try on player load\n var observer = new MutationObserver(tryTheater);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "YouTube Theater Mode Default"); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions .changeset/spellgantt-revoked-proxy-exclusion.md
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
---
---

Docs and tests only for `@object-ui/plugin-timeline`. `spellGanttDateValue`'s
docblock claimed every branch was total; measured in-render, `Array.isArray`
throws on a revoked `Proxy`, so the claim is now scoped to the input set that
is actually exercised and the exclusion is pinned as rows in the existing
adversarial set. The same measurement falsified the wider claim that
`Array.isArray` is the last non-total operation on the gantt date path — five
reads upstream of the helper throw first, recorded in objectui#7153. No
published behaviour changes.
Original file line numberDiff line numberDiff line change
Expand Up@@ -30,6 +30,17 @@
* about being a Date, each one asserted to reach a DEFINED outcome. That is
* what #7026 gave the speller and what the gate did not have.
*
* 5. objectui#7036: the gate's repair held, and the SPELLER still was not
* total — `Array.isArray` throws on a revoked `Proxy`. That card did not
* repair it; it ruled the exclusion STATED AND EXERCISED, which is what
* `pin 4` at the foot of this file is. It also measured that the card's
* own headline was wrong: `Array.isArray` is not the last non-total
* operation on the PATH, only inside that function (objectui#7153).
*
* ⚠️ So this file's input set is not a claim of totality either. It is the
* boundary that has actually been exercised, and the sixth card will be the
* one that says where it ends.
*
* ## What "a defined outcome" means here — two kinds, and never a third
*
* ⚠️ Not every row below is a refusal, and forcing them all to be one would
Expand DownExpand Up@@ -181,6 +192,17 @@ class HijackedGetTime extends Date {
}
}

/**
* A REVOKED proxy — the one input `spellGanttDateValue` is documented NOT to
* survive (objectui#7036). `Proxy.revocable` is the only way to spell it, and
* nothing in any package `src/` calls it: this is a test-only value.
*/
const revokedProxy = (target: object = {}) => {
const { proxy, revoke } = Proxy.revocable(target, {});
revoke();
return proxy as unknown;
};

/** An object whose `Symbol.toStringTag` is a throwing getter. */
const throwingToStringTag = () =>
({
Expand DownExpand Up@@ -347,3 +369,74 @@ describe('pin 3 — the ACCEPT SET is unchanged: #6781’s ruling does not move
}
});
});

describe('pin 4 — the ONE documented EXCLUSION, exercised not asserted (objectui#7036)', () => {
/**
* `spellGanttDateValue`'s `Array.isArray` is not total: `IsArray` recurses
* into `[[ProxyTarget]]` and a REVOKED proxy has none, so it throws while
* naming the value. objectui#7036 adjudicated this as STATED-AND-EXERCISED
* rather than repaired, on three grounds recorded in that function's
* docblock: it is unreachable from an authored document (JSON cannot spell
* a proxy), a `catch` here would SUBSTITUTE `an object` for a failure
* rather than read anything the way `isDate`'s catch does, and it would not
* make the PATH total anyway — five reads that FETCH the date throw first
* (objectui#7153).
*
* ## Why a THROW is pinned here, and why not with a bare `toThrow()`
*
* This file exists because four prose totality claims on this path were
* each falsified by the next card's measurement. A docblock sentence saying
* "except a revoked proxy" would be a fifth claim of the same species. The
* rows below make the exclusion a MEASUREMENT instead, and they assert the
* throw's own MESSAGE: a bare `toThrow()` passes for any error from any
* line, so it would stay green if the crash moved to `instanceof`, to
* `Object.prototype.toString`, or upstream into the row walk — which is
* precisely what has happened on this path four times. The message names
* both the operation (`IsArray`) and the cause (`revoked`), so the pin
* fails if the site moves and fails if the exclusion is ever repaired,
* either of which must come with a docblock edit.
*/

const REVOKED_ISARRAY = /Cannot perform 'IsArray' on a proxy that has been revoked/;

// Every target shape: the throw is about revocation, not about the target.
const targets: [string, () => object][] = [
['{}', () => ({})],
['[]', () => []],
['a function', () => function noop() {}],
['a real Date', () => new Date('2024-03-01')],
];

for (const [label, makeTarget] of targets) {
it(`a revoked Proxy over ${label} throws at \`Array.isArray\`, by design`, () => {
expect(() =>
gantt({ items: rowWith({ startDate: '2024-01-01', endDate: revokedProxy(makeTarget()) }) }),
).toThrow(REVOKED_ISARRAY);
});
}

it('a revoked Proxy pinned as `minDate` reaches the same site', () => {
// The pinned limb takes the same speller, so the exclusion is not
// confined to a row item. `value &&` is ToBoolean and does not throw.
expect(() =>
gantt({
items: rowWith({ startDate: '2024-01-01', endDate: '2024-03-01' }),
minDate: revokedProxy(),
}),
).toThrow(REVOKED_ISARRAY);
});

it('CONTROL — a LIVE Proxy is still NAMED `an object`, so the rows above are about REVOCATION', () => {
// Without this the four rows above would also pass if the whole gantt
// branch had broken. `typeof` does not separate a revoked proxy out
// either: it answers `'object'` for one, exactly as it does here.
const { container } = gantt({
items: rowWith({ startDate: '2024-01-01', endDate: new Proxy({}, {}) }),
});

const text = diagnosticOf(container) ?? '';
expect(text).toContain(PATH);
expect(text).toContain('is an object');
expect(barCountOf(container)).toBe(0);
});
});
70 changes: 68 additions & 2 deletions packages/plugin-timeline/src/renderer.tsx
Original file line numberDiff line numberDiff line change
Expand Up@@ -427,7 +427,7 @@ const isDate = (value: unknown): value is Date => {
* worse than naming a category: it costs the author a search for a fault that
* is not there.
*
* ## The branches, and why each is total
* ## The branches, and how far each one's totality reaches
*
* - `string` -> quoted (#6759's pin; empty and space-padded stay visible).
* - `undefined` / `null` -> themselves (#6759 / #6770's pins; how an author
Expand DownExpand Up@@ -455,7 +455,10 @@ const isDate = (value: unknown): value is Date => {
* `Array.isArray` and `typeof`, which read no author-controlled property.
* Deliberately NOT `Object.prototype.toString.call`: that consults
* `Symbol.toStringTag`, which can be a throwing getter (measured), so the
* more informative spelling is the non-total one.
* more informative spelling is the non-total one. `Array.isArray` reads no
* property and is STILL not total — on a revoked `Proxy` it throws. That is
* the one exclusion this docblock claims, and it is stated and exercised
* rather than repaired: see the objectui#7036 note below.
*
* All eight `typeof` results are covered and no branch falls through to author
* code. The single reflective operation it performs is the Date test, which is
Expand All@@ -471,6 +474,69 @@ const isDate = (value: unknown): value is Date => {
* operation adds no throw site because it HAS none — not because the gate
* absorbed it first.
*
* ⚠️ objectui#7036 — READ THE TWO PARAGRAPHS ABOVE AS A SEQUENCE, NOT AS A
* CONCLUSION. Each was written as the settled answer and the next card's
* measurement moved it (#6759 -> #6905 -> #6907 -> #7027). This is the fifth
* entry and it is deliberately NOT a fifth claim of totality. The sentence it
* falsifies is the one directly above the #7027 note: this function DOES add
* a throw site the accept gate does not have, and it is `Array.isArray`.
*
* THE EXCLUSION, measured in-render on dd35800af through `TimelineRenderer`
* itself — not a replica of these branch bodies:
*
* endDate: <a revoked Proxy, over any target>
* -> THREW TypeError: Cannot perform 'IsArray' on a proxy that has
* been revoked (here, at `Array.isArray`)
*
* `IsArray` recurses into `[[ProxyTarget]]`, which a revoked proxy does not
* have, so this throws on an INTERNAL condition while still reading no
* author-controlled property. All four target shapes throw (`{}`, `[]`, a
* function, a real `Date`), and so does a revoked proxy pinned as
* `schema.minDate` / `maxDate`. `typeof` does not separate it out — it
* answers `'object'` (or `'function'`) without throwing.
*
* WHY IT IS LEFT, and none of the three reasons is cost:
*
* 1. It is not reachable from an authored document — ObjectUI metadata is
* JSON and JSON cannot spell a proxy. Re-swept on dd35800af: zero
* `Proxy.revocable` in the repo, zero `Object.setPrototypeOf` calls in
* any package `src/`, each read beside a live control on the same
* instrument
* (52 `Proxy` mentions, 19 `Object.assign` calls) so the zeros are
* readings and not a broken query. The `__proto__` hits are denylists.
* 2. A `catch` here would be SUBSTITUTION, not a read — the opposite of
* `isDate`'s. `isDate` catches because the language exposes the
* `[[DateValue]]` bit ONLY by throwing, so its catch IS the read. A
* revoked proxy has no array-ness to read, so catching would discard a
* failure and substitute `an object`: consumer-side tolerance, which is
* what #6750 and #6759 both refused. A second `catch` beside `isDate`'s
* would erase the distinction this file is built on.
* 3. It would buy no invariant, because of the paragraph below.
*
* ⛔ `Array.isArray` IS NOT THE LAST NON-TOTAL OPERATION ON THE GANTT DATE
* PATH, and this function cannot make the path total. Measured in the same
* run: five further crash sites, every one of them reached BEFORE this
* function is entered, in the property reads that FETCH the date out of the
* authored document (`findUnusableGanttDate`'s `items[i]?.items` and
* `rowItems[j]?.[key]`) —
*
* items[0].items[0] with a throwing `endDate` getter -> THREW
* items[0].items[0] is a revoked Proxy -> THREW
* items[0] is a revoked Proxy -> THREW
* items[0].items is a revoked Proxy -> THREW
* items[0] with a throwing `items` getter -> THREW
*
* They are the same reachability class (JSON spells neither a getter nor a
* proxy) and they are enumerated in objectui#7153 rather than repaired here —
* they are a different function's surface. The true and much narrower
* sentence is that `Array.isArray` is the last non-total operation INSIDE
* THIS FUNCTION.
*
* ⚠️ Whatever totality this docblock claims is bounded by an EXERCISED input
* set — the rows in `__tests__/timeline-gantt-date-brand-7027.test.tsx`,
* which now include the revoked proxy — and by nothing else. On this path
* prose has been a hypothesis four times running.
*
* ## What this deliberately does NOT do
*
* It does not change WHICH values are refused — that is #6781's ruling and it
Expand Down
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Remove or un-stick sticky/fixed headers that block content\n(function() {\n function unstick() {\n document.querySelectorAll('header, nav, [role=\"banner\"], .header, .navbar, .sticky, .fixed-top, [style*=\"position: fixed\"], [style*=\"position:sticky\"]').forEach(function(el) {\n if (el.style.position === 'fixed' || el.style.position === 'sticky' || \n getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') {\n el.style.position = 'static';\n el.style.top = 'auto';\n el.style.zIndex = 'auto';\n }\n });\n }\n \n unstick();\n \n var observer = new MutationObserver(unstick);\n observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] });\n})();", "Kill Sticky Headers"); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions .changeset/spellgantt-revoked-proxy-exclusion.md
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
---
---

Docs and tests only for `@object-ui/plugin-timeline`. `spellGanttDateValue`'s
docblock claimed every branch was total; measured in-render, `Array.isArray`
throws on a revoked `Proxy`, so the claim is now scoped to the input set that
is actually exercised and the exclusion is pinned as rows in the existing
adversarial set. The same measurement falsified the wider claim that
`Array.isArray` is the last non-total operation on the gantt date path — five
reads upstream of the helper throw first, recorded in objectui#7153. No
published behaviour changes.
Original file line numberDiff line numberDiff line change
Expand Up@@ -30,6 +30,17 @@
* about being a Date, each one asserted to reach a DEFINED outcome. That is
* what #7026 gave the speller and what the gate did not have.
*
* 5. objectui#7036: the gate's repair held, and the SPELLER still was not
* total — `Array.isArray` throws on a revoked `Proxy`. That card did not
* repair it; it ruled the exclusion STATED AND EXERCISED, which is what
* `pin 4` at the foot of this file is. It also measured that the card's
* own headline was wrong: `Array.isArray` is not the last non-total
* operation on the PATH, only inside that function (objectui#7153).
*
* ⚠️ So this file's input set is not a claim of totality either. It is the
* boundary that has actually been exercised, and the sixth card will be the
* one that says where it ends.
*
* ## What "a defined outcome" means here — two kinds, and never a third
*
* ⚠️ Not every row below is a refusal, and forcing them all to be one would
Expand DownExpand Up@@ -181,6 +192,17 @@ class HijackedGetTime extends Date {
}
}

/**
* A REVOKED proxy — the one input `spellGanttDateValue` is documented NOT to
* survive (objectui#7036). `Proxy.revocable` is the only way to spell it, and
* nothing in any package `src/` calls it: this is a test-only value.
*/
const revokedProxy = (target: object = {}) => {
const { proxy, revoke } = Proxy.revocable(target, {});
revoke();
return proxy as unknown;
};

/** An object whose `Symbol.toStringTag` is a throwing getter. */
const throwingToStringTag = () =>
({
Expand DownExpand Up@@ -347,3 +369,74 @@ describe('pin 3 — the ACCEPT SET is unchanged: #6781’s ruling does not move
}
});
});

describe('pin 4 — the ONE documented EXCLUSION, exercised not asserted (objectui#7036)', () => {
/**
* `spellGanttDateValue`'s `Array.isArray` is not total: `IsArray` recurses
* into `[[ProxyTarget]]` and a REVOKED proxy has none, so it throws while
* naming the value. objectui#7036 adjudicated this as STATED-AND-EXERCISED
* rather than repaired, on three grounds recorded in that function's
* docblock: it is unreachable from an authored document (JSON cannot spell
* a proxy), a `catch` here would SUBSTITUTE `an object` for a failure
* rather than read anything the way `isDate`'s catch does, and it would not
* make the PATH total anyway — five reads that FETCH the date throw first
* (objectui#7153).
*
* ## Why a THROW is pinned here, and why not with a bare `toThrow()`
*
* This file exists because four prose totality claims on this path were
* each falsified by the next card's measurement. A docblock sentence saying
* "except a revoked proxy" would be a fifth claim of the same species. The
* rows below make the exclusion a MEASUREMENT instead, and they assert the
* throw's own MESSAGE: a bare `toThrow()` passes for any error from any
* line, so it would stay green if the crash moved to `instanceof`, to
* `Object.prototype.toString`, or upstream into the row walk — which is
* precisely what has happened on this path four times. The message names
* both the operation (`IsArray`) and the cause (`revoked`), so the pin
* fails if the site moves and fails if the exclusion is ever repaired,
* either of which must come with a docblock edit.
*/

const REVOKED_ISARRAY = /Cannot perform 'IsArray' on a proxy that has been revoked/;

// Every target shape: the throw is about revocation, not about the target.
const targets: [string, () => object][] = [
['{}', () => ({})],
['[]', () => []],
['a function', () => function noop() {}],
['a real Date', () => new Date('2024-03-01')],
];

for (const [label, makeTarget] of targets) {
it(`a revoked Proxy over ${label} throws at \`Array.isArray\`, by design`, () => {
expect(() =>
gantt({ items: rowWith({ startDate: '2024-01-01', endDate: revokedProxy(makeTarget()) }) }),
).toThrow(REVOKED_ISARRAY);
});
}

it('a revoked Proxy pinned as `minDate` reaches the same site', () => {
// The pinned limb takes the same speller, so the exclusion is not
// confined to a row item. `value &&` is ToBoolean and does not throw.
expect(() =>
gantt({
items: rowWith({ startDate: '2024-01-01', endDate: '2024-03-01' }),
minDate: revokedProxy(),
}),
).toThrow(REVOKED_ISARRAY);
});

it('CONTROL — a LIVE Proxy is still NAMED `an object`, so the rows above are about REVOCATION', () => {
// Without this the four rows above would also pass if the whole gantt
// branch had broken. `typeof` does not separate a revoked proxy out
// either: it answers `'object'` for one, exactly as it does here.
const { container } = gantt({
items: rowWith({ startDate: '2024-01-01', endDate: new Proxy({}, {}) }),
});

const text = diagnosticOf(container) ?? '';
expect(text).toContain(PATH);
expect(text).toContain('is an object');
expect(barCountOf(container)).toBe(0);
});
});
70 changes: 68 additions & 2 deletions packages/plugin-timeline/src/renderer.tsx
Original file line numberDiff line numberDiff line change
Expand Up@@ -427,7 +427,7 @@ const isDate = (value: unknown): value is Date => {
* worse than naming a category: it costs the author a search for a fault that
* is not there.
*
* ## The branches, and why each is total
* ## The branches, and how far each one's totality reaches
*
* - `string` -> quoted (#6759's pin; empty and space-padded stay visible).
* - `undefined` / `null` -> themselves (#6759 / #6770's pins; how an author
Expand DownExpand Up@@ -455,7 +455,10 @@ const isDate = (value: unknown): value is Date => {
* `Array.isArray` and `typeof`, which read no author-controlled property.
* Deliberately NOT `Object.prototype.toString.call`: that consults
* `Symbol.toStringTag`, which can be a throwing getter (measured), so the
* more informative spelling is the non-total one.
* more informative spelling is the non-total one. `Array.isArray` reads no
* property and is STILL not total — on a revoked `Proxy` it throws. That is
* the one exclusion this docblock claims, and it is stated and exercised
* rather than repaired: see the objectui#7036 note below.
*
* All eight `typeof` results are covered and no branch falls through to author
* code. The single reflective operation it performs is the Date test, which is
Expand All@@ -471,6 +474,69 @@ const isDate = (value: unknown): value is Date => {
* operation adds no throw site because it HAS none — not because the gate
* absorbed it first.
*
* ⚠️ objectui#7036 — READ THE TWO PARAGRAPHS ABOVE AS A SEQUENCE, NOT AS A
* CONCLUSION. Each was written as the settled answer and the next card's
* measurement moved it (#6759 -> #6905 -> #6907 -> #7027). This is the fifth
* entry and it is deliberately NOT a fifth claim of totality. The sentence it
* falsifies is the one directly above the #7027 note: this function DOES add
* a throw site the accept gate does not have, and it is `Array.isArray`.
*
* THE EXCLUSION, measured in-render on dd35800af through `TimelineRenderer`
* itself — not a replica of these branch bodies:
*
* endDate: <a revoked Proxy, over any target>
* -> THREW TypeError: Cannot perform 'IsArray' on a proxy that has
* been revoked (here, at `Array.isArray`)
*
* `IsArray` recurses into `[[ProxyTarget]]`, which a revoked proxy does not
* have, so this throws on an INTERNAL condition while still reading no
* author-controlled property. All four target shapes throw (`{}`, `[]`, a
* function, a real `Date`), and so does a revoked proxy pinned as
* `schema.minDate` / `maxDate`. `typeof` does not separate it out — it
* answers `'object'` (or `'function'`) without throwing.
*
* WHY IT IS LEFT, and none of the three reasons is cost:
*
* 1. It is not reachable from an authored document — ObjectUI metadata is
* JSON and JSON cannot spell a proxy. Re-swept on dd35800af: zero
* `Proxy.revocable` in the repo, zero `Object.setPrototypeOf` calls in
* any package `src/`, each read beside a live control on the same
* instrument
* (52 `Proxy` mentions, 19 `Object.assign` calls) so the zeros are
* readings and not a broken query. The `__proto__` hits are denylists.
* 2. A `catch` here would be SUBSTITUTION, not a read — the opposite of
* `isDate`'s. `isDate` catches because the language exposes the
* `[[DateValue]]` bit ONLY by throwing, so its catch IS the read. A
* revoked proxy has no array-ness to read, so catching would discard a
* failure and substitute `an object`: consumer-side tolerance, which is
* what #6750 and #6759 both refused. A second `catch` beside `isDate`'s
* would erase the distinction this file is built on.
* 3. It would buy no invariant, because of the paragraph below.
*
* ⛔ `Array.isArray` IS NOT THE LAST NON-TOTAL OPERATION ON THE GANTT DATE
* PATH, and this function cannot make the path total. Measured in the same
* run: five further crash sites, every one of them reached BEFORE this
* function is entered, in the property reads that FETCH the date out of the
* authored document (`findUnusableGanttDate`'s `items[i]?.items` and
* `rowItems[j]?.[key]`) —
*
* items[0].items[0] with a throwing `endDate` getter -> THREW
* items[0].items[0] is a revoked Proxy -> THREW
* items[0] is a revoked Proxy -> THREW
* items[0].items is a revoked Proxy -> THREW
* items[0] with a throwing `items` getter -> THREW
*
* They are the same reachability class (JSON spells neither a getter nor a
* proxy) and they are enumerated in objectui#7153 rather than repaired here —
* they are a different function's surface. The true and much narrower
* sentence is that `Array.isArray` is the last non-total operation INSIDE
* THIS FUNCTION.
*
* ⚠️ Whatever totality this docblock claims is bounded by an EXERCISED input
* set — the rows in `__tests__/timeline-gantt-date-brand-7027.test.tsx`,
* which now include the revoked proxy — and by nothing else. On this path
* prose has been a hypothesis four times running.
*
* ## What this deliberately does NOT do
*
* It does not change WHICH values are refused — that is #6781's ruling and it
Expand Down
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Universal Dark Mode - works on any site\n(function() {\n var enabled = true;\n \n function applyDarkMode() {\n if (!enabled) return;\n \n // Create style element if it doesn't exist\n var style = document.getElementById('universal-dark-mode-style');\n if (!style) {\n style = document.createElement('style');\n style.id = 'universal-dark-mode-style';\n document.head.appendChild(style);\n }\n \n // Dark mode CSS - inverts colors but preserves images/video\n style.textContent = '\n /* Invert everything except media */\n html {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #1a1a2e !important;\n }\n \n /* Restore images, videos, iframes, canvas */\n img, video, iframe, canvas, svg, picture, [style*=\"background-image\"] {\n filter: invert(1) hue-rotate(180deg) !important;\n }\n \n /* Preserve specific elements that should not be inverted */\n .no-dark-mode, .no-dark-mode *,\n [data-theme=\"light\"], [data-theme=\"light\"],\n .ace_editor, .ace_editor *,\n .CodeMirror, .CodeMirror *,\n .monaco-editor, .monaco-editor *,\n .markdown-body pre, .markdown-body pre *,\n .highlight, .highlight *,\n pre code, pre code * {\n filter: none !important;\n }\n \n /* Fix common UI elements */\n .modal, .popup, .dropdown-menu, .tooltip, .popover {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #2d2d44 !important;\n border-color: #444 !important;\n }\n \n /* Scrollbars */\n ::-webkit-scrollbar { background: #1a1a2e !important; }\n ::-webkit-scrollbar-thumb { background: #444 !important; }\n ::-webkit-scrollbar-thumb:hover { background: #555 !important; }\n \n /* Selection */\n ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ';\n }\n \n function removeDarkMode() {\n var style = document.getElementById('universal-dark-mode-style');\n if (style) style.remove();\n }\n \n // Toggle with Alt+Shift+D\n document.addEventListener('keydown', function(e) {\n if (e.altKey && e.shiftKey && e.key === 'D') {\n e.preventDefault();\n enabled = !enabled;\n if (enabled) {\n applyDarkMode();\n console.log('[Universal Dark Mode] Enabled');\n } else {\n removeDarkMode();\n console.log('[Universal Dark Mode] Disabled');\n }\n }\n });\n \n // Apply on load\n applyDarkMode();\n \n // Re-apply on dynamic content\n var observer = new MutationObserver(function(mutations) {\n if (enabled && !document.getElementById('universal-dark-mode-style')) {\n applyDarkMode();\n }\n });\n observer.observe(document.head, { childList: true });\n \n console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle');\n})();", "Universal Dark Mode"); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })();
Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions .changeset/spellgantt-revoked-proxy-exclusion.md
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
---
---

Docs and tests only for `@object-ui/plugin-timeline`. `spellGanttDateValue`'s
docblock claimed every branch was total; measured in-render, `Array.isArray`
throws on a revoked `Proxy`, so the claim is now scoped to the input set that
is actually exercised and the exclusion is pinned as rows in the existing
adversarial set. The same measurement falsified the wider claim that
`Array.isArray` is the last non-total operation on the gantt date path — five
reads upstream of the helper throw first, recorded in objectui#7153. No
published behaviour changes.
Original file line numberDiff line numberDiff line change
Expand Up@@ -30,6 +30,17 @@
* about being a Date, each one asserted to reach a DEFINED outcome. That is
* what #7026 gave the speller and what the gate did not have.
*
* 5. objectui#7036: the gate's repair held, and the SPELLER still was not
* total — `Array.isArray` throws on a revoked `Proxy`. That card did not
* repair it; it ruled the exclusion STATED AND EXERCISED, which is what
* `pin 4` at the foot of this file is. It also measured that the card's
* own headline was wrong: `Array.isArray` is not the last non-total
* operation on the PATH, only inside that function (objectui#7153).
*
* ⚠️ So this file's input set is not a claim of totality either. It is the
* boundary that has actually been exercised, and the sixth card will be the
* one that says where it ends.
*
* ## What "a defined outcome" means here — two kinds, and never a third
*
* ⚠️ Not every row below is a refusal, and forcing them all to be one would
Expand DownExpand Up@@ -181,6 +192,17 @@ class HijackedGetTime extends Date {
}
}

/**
* A REVOKED proxy — the one input `spellGanttDateValue` is documented NOT to
* survive (objectui#7036). `Proxy.revocable` is the only way to spell it, and
* nothing in any package `src/` calls it: this is a test-only value.
*/
const revokedProxy = (target: object = {}) => {
const { proxy, revoke } = Proxy.revocable(target, {});
revoke();
return proxy as unknown;
};

/** An object whose `Symbol.toStringTag` is a throwing getter. */
const throwingToStringTag = () =>
({
Expand DownExpand Up@@ -347,3 +369,74 @@ describe('pin 3 — the ACCEPT SET is unchanged: #6781’s ruling does not move
}
});
});

describe('pin 4 — the ONE documented EXCLUSION, exercised not asserted (objectui#7036)', () => {
/**
* `spellGanttDateValue`'s `Array.isArray` is not total: `IsArray` recurses
* into `[[ProxyTarget]]` and a REVOKED proxy has none, so it throws while
* naming the value. objectui#7036 adjudicated this as STATED-AND-EXERCISED
* rather than repaired, on three grounds recorded in that function's
* docblock: it is unreachable from an authored document (JSON cannot spell
* a proxy), a `catch` here would SUBSTITUTE `an object` for a failure
* rather than read anything the way `isDate`'s catch does, and it would not
* make the PATH total anyway — five reads that FETCH the date throw first
* (objectui#7153).
*
* ## Why a THROW is pinned here, and why not with a bare `toThrow()`
*
* This file exists because four prose totality claims on this path were
* each falsified by the next card's measurement. A docblock sentence saying
* "except a revoked proxy" would be a fifth claim of the same species. The
* rows below make the exclusion a MEASUREMENT instead, and they assert the
* throw's own MESSAGE: a bare `toThrow()` passes for any error from any
* line, so it would stay green if the crash moved to `instanceof`, to
* `Object.prototype.toString`, or upstream into the row walk — which is
* precisely what has happened on this path four times. The message names
* both the operation (`IsArray`) and the cause (`revoked`), so the pin
* fails if the site moves and fails if the exclusion is ever repaired,
* either of which must come with a docblock edit.
*/

const REVOKED_ISARRAY = /Cannot perform 'IsArray' on a proxy that has been revoked/;

// Every target shape: the throw is about revocation, not about the target.
const targets: [string, () => object][] = [
['{}', () => ({})],
['[]', () => []],
['a function', () => function noop() {}],
['a real Date', () => new Date('2024-03-01')],
];

for (const [label, makeTarget] of targets) {
it(`a revoked Proxy over ${label} throws at \`Array.isArray\`, by design`, () => {
expect(() =>
gantt({ items: rowWith({ startDate: '2024-01-01', endDate: revokedProxy(makeTarget()) }) }),
).toThrow(REVOKED_ISARRAY);
});
}

it('a revoked Proxy pinned as `minDate` reaches the same site', () => {
// The pinned limb takes the same speller, so the exclusion is not
// confined to a row item. `value &&` is ToBoolean and does not throw.
expect(() =>
gantt({
items: rowWith({ startDate: '2024-01-01', endDate: '2024-03-01' }),
minDate: revokedProxy(),
}),
).toThrow(REVOKED_ISARRAY);
});

it('CONTROL — a LIVE Proxy is still NAMED `an object`, so the rows above are about REVOCATION', () => {
// Without this the four rows above would also pass if the whole gantt
// branch had broken. `typeof` does not separate a revoked proxy out
// either: it answers `'object'` for one, exactly as it does here.
const { container } = gantt({
items: rowWith({ startDate: '2024-01-01', endDate: new Proxy({}, {}) }),
});

const text = diagnosticOf(container) ?? '';
expect(text).toContain(PATH);
expect(text).toContain('is an object');
expect(barCountOf(container)).toBe(0);
});
});
70 changes: 68 additions & 2 deletions packages/plugin-timeline/src/renderer.tsx
Original file line numberDiff line numberDiff line change
Expand Up@@ -427,7 +427,7 @@ const isDate = (value: unknown): value is Date => {
* worse than naming a category: it costs the author a search for a fault that
* is not there.
*
* ## The branches, and why each is total
* ## The branches, and how far each one's totality reaches
*
* - `string` -> quoted (#6759's pin; empty and space-padded stay visible).
* - `undefined` / `null` -> themselves (#6759 / #6770's pins; how an author
Expand DownExpand Up@@ -455,7 +455,10 @@ const isDate = (value: unknown): value is Date => {
* `Array.isArray` and `typeof`, which read no author-controlled property.
* Deliberately NOT `Object.prototype.toString.call`: that consults
* `Symbol.toStringTag`, which can be a throwing getter (measured), so the
* more informative spelling is the non-total one.
* more informative spelling is the non-total one. `Array.isArray` reads no
* property and is STILL not total — on a revoked `Proxy` it throws. That is
* the one exclusion this docblock claims, and it is stated and exercised
* rather than repaired: see the objectui#7036 note below.
*
* All eight `typeof` results are covered and no branch falls through to author
* code. The single reflective operation it performs is the Date test, which is
Expand All@@ -471,6 +474,69 @@ const isDate = (value: unknown): value is Date => {
* operation adds no throw site because it HAS none — not because the gate
* absorbed it first.
*
* ⚠️ objectui#7036 — READ THE TWO PARAGRAPHS ABOVE AS A SEQUENCE, NOT AS A
* CONCLUSION. Each was written as the settled answer and the next card's
* measurement moved it (#6759 -> #6905 -> #6907 -> #7027). This is the fifth
* entry and it is deliberately NOT a fifth claim of totality. The sentence it
* falsifies is the one directly above the #7027 note: this function DOES add
* a throw site the accept gate does not have, and it is `Array.isArray`.
*
* THE EXCLUSION, measured in-render on dd35800af through `TimelineRenderer`
* itself — not a replica of these branch bodies:
*
* endDate: <a revoked Proxy, over any target>
* -> THREW TypeError: Cannot perform 'IsArray' on a proxy that has
* been revoked (here, at `Array.isArray`)
*
* `IsArray` recurses into `[[ProxyTarget]]`, which a revoked proxy does not
* have, so this throws on an INTERNAL condition while still reading no
* author-controlled property. All four target shapes throw (`{}`, `[]`, a
* function, a real `Date`), and so does a revoked proxy pinned as
* `schema.minDate` / `maxDate`. `typeof` does not separate it out — it
* answers `'object'` (or `'function'`) without throwing.
*
* WHY IT IS LEFT, and none of the three reasons is cost:
*
* 1. It is not reachable from an authored document — ObjectUI metadata is
* JSON and JSON cannot spell a proxy. Re-swept on dd35800af: zero
* `Proxy.revocable` in the repo, zero `Object.setPrototypeOf` calls in
* any package `src/`, each read beside a live control on the same
* instrument
* (52 `Proxy` mentions, 19 `Object.assign` calls) so the zeros are
* readings and not a broken query. The `__proto__` hits are denylists.
* 2. A `catch` here would be SUBSTITUTION, not a read — the opposite of
* `isDate`'s. `isDate` catches because the language exposes the
* `[[DateValue]]` bit ONLY by throwing, so its catch IS the read. A
* revoked proxy has no array-ness to read, so catching would discard a
* failure and substitute `an object`: consumer-side tolerance, which is
* what #6750 and #6759 both refused. A second `catch` beside `isDate`'s
* would erase the distinction this file is built on.
* 3. It would buy no invariant, because of the paragraph below.
*
* ⛔ `Array.isArray` IS NOT THE LAST NON-TOTAL OPERATION ON THE GANTT DATE
* PATH, and this function cannot make the path total. Measured in the same
* run: five further crash sites, every one of them reached BEFORE this
* function is entered, in the property reads that FETCH the date out of the
* authored document (`findUnusableGanttDate`'s `items[i]?.items` and
* `rowItems[j]?.[key]`) —
*
* items[0].items[0] with a throwing `endDate` getter -> THREW
* items[0].items[0] is a revoked Proxy -> THREW
* items[0] is a revoked Proxy -> THREW
* items[0].items is a revoked Proxy -> THREW
* items[0] with a throwing `items` getter -> THREW
*
* They are the same reachability class (JSON spells neither a getter nor a
* proxy) and they are enumerated in objectui#7153 rather than repaired here —
* they are a different function's surface. The true and much narrower
* sentence is that `Array.isArray` is the last non-total operation INSIDE
* THIS FUNCTION.
*
* ⚠️ Whatever totality this docblock claims is bounded by an EXERCISED input
* set — the rows in `__tests__/timeline-gantt-date-brand-7027.test.tsx`,
* which now include the revoked proxy — and by nothing else. On this path
* prose has been a hypothesis four times running.
*
* ## What this deliberately does NOT do
*
* It does not change WHICH values are refused — that is #6781's ruling and it
Expand Down
Loading