') + ')', 'gi');
if (regex.test(text)) {
found = true;
var frag = document.createDocumentFragment();
var parts = text.split(regex);
parts.forEach(function(part, i) {
if (i % 2 === 0) {
frag.appendChild(document.createTextNode(part));
} else {
var span = document.createElement('span');
span.className = 'userscript-highlight';
span.textContent = part;
frag.appendChild(span);
}
});
node.parentNode.replaceChild(frag, node);
}
});
} else if (node.nodeType === 1 && node.childNodes) { // element
var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT'];
if (!skipTags.includes(node.tagName)) {
Array.from(node.childNodes).forEach(highlight);
}
}
}
highlight(document.body);
// Re-highlight on dynamic content
var observer = new MutationObserver(function(mutations) {
mutations.forEach(function(m) {
m.addedNodes.forEach(function(node) {
if (node.nodeType === 1 || node.nodeType === 3) highlight(node);
});
});
});
observer.observe(document.body, { childList: true, subtree: true });
})();
}
} catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); }
})();
(function(){
try {
var __m = "*";
var __re = new RegExp('^' + ".*" + ', 'i');
if (__m === '*' || __re.test(location.href)) {
// Strip utm_, fbclid, gclid, etc. from all links on page
(function() {
var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content',
'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid',
'ref', 'ref_src', 'source', 'medium', 'campaign'];
function cleanUrl(url) {
try {
var u = new URL(url, window.location.origin);
var changed = false;
trackingParams.forEach(function(p) {
if (u.searchParams.has(p)) {
u.searchParams.delete(p);
changed = true;
}
});
return changed ? u.toString() : url;
} catch (e) {
return url;
}
}
function cleanLinks() {
document.querySelectorAll('a[href]').forEach(function(a) {
var clean = cleanUrl(a.href);
if (clean !== a.href) a.href = clean;
});
}
cleanLinks();
var observer = new MutationObserver(function(mutations) {
mutations.forEach(function(m) {
m.addedNodes.forEach(function(node) {
if (node.nodeType === 1) {
if (node.tagName === 'A') cleanLinks();
node.querySelectorAll('a[href]').forEach(function(a) {
var clean = cleanUrl(a.href);
if (clean !== a.href) a.href = clean;
});
}
});
});
});
observer.observe(document.body, { childList: true, subtree: true });
})();
}
} catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); }
})();
(function(){
try {
var __m = "youtube.com";
var __re = new RegExp('^' + "youtube\\.com" + ', 'i');
if (__m === '*' || __re.test(location.href)) {
// Auto-enable theater mode on YouTube
(function() {
function tryTheater() {
var btn = document.querySelector('button[aria-label="Theater mode"], ytd-player #player button[title="Theater mode"]');
if (btn && !btn.classList.contains('activated')) {
btn.click();
}
}
// Try immediately
tryTheater();
// Try after navigation (SPA)
var lastUrl = location.href;
setInterval(function() {
if (location.href !== lastUrl) {
lastUrl = location.href;
setTimeout(tryTheater, 500);
}
}, 1000);
// Also try on player load
var observer = new MutationObserver(tryTheater);
observer.observe(document.body, { childList: true, subtree: true });
})();
}
} catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); }
})();
(function(){
try {
var __m = "*";
var __re = new RegExp('^' + ".*" + ', 'i');
if (__m === '*' || __re.test(location.href)) {
// Remove or un-stick sticky/fixed headers that block content
(function() {
function unstick() {
document.querySelectorAll('header, nav, [role="banner"], .header, .navbar, .sticky, .fixed-top, [style*="position: fixed"], [style*="position:sticky"]').forEach(function(el) {
if (el.style.position === 'fixed' || el.style.position === 'sticky' || getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') {
el.style.position = 'static';
el.style.top = 'auto';
el.style.zIndex = 'auto';
}
});
}
unstick();
var observer = new MutationObserver(unstick);
observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] });
})();
}
} catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); }
})();
})();
fix(plugin-gantt,plugin-tree): drop the undeclared `referenceTo` arm from both target chains by os-sam · Pull Request #6922 · objectstack-ai/objectui · GitHub
You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Second slice of the classification table's suggested slicing, item 1: the referenceTo third arms at ObjectGantt and ObjectTree. ⛔ Nothing else — not
Class B, not the pass-through forwarders, not reference_field, not the other
~16 readers. The card stays open.
Both readers were re-located by text on bf97b98c8; both were still at the
line numbers the card cites (the table had already found two other readers whose
numbers had shifted, so this was not assumed).
1. The accept/reject delta, stated so it does not have to be re-derived
This is a reject-direction change reachable by third-party DataSource
implementations — same clause-② routing as PR #6920, and for the same measured
reason: the ingestion door is not total. normalizeSchemaReferenceKeys has
two production call sites, making it a private property of two implementations,
while getObjectSchema is a required, non-optional member of the published DataSource interface (types/src/data.ts:519) — and both components here call
it on the genericdataSource:
component
how the def arrives
reachable raw?
ObjectGantt
effectiveDataSource.getObjectSchema(resource) at :706
What stops being accepted: a field def carrying onlyreferenceTo — and
which did not pass the ingestion choke point — no longer resolves a
relationship target at these two seams.
What that looks like, concretely, rather than abstractly:
seam
before
after
gantt quick filter
fetches the referenced object's full option domain
falls back to the distinct values present in the loaded rows
tree parent pointer
field is auto-detected as the single-parent pointer
not detected; records render as a flat forest unless parentField is set explicitly
What is NOT affected:
Any def that came through MetadataProvider or ObjectStackAdapter.getObjectSchema. The normalizer reads reference_to ?? reference ?? referenceTo and stamps both snake_case keys,
so such a def already carries reference_to before either component sees it.
This is pinned as a test, not asserted in prose.
Both surviving arms. reference_to and reference are byte-identical to base.
referenceTo clears the same bar the first slice (PR #6920) cleared for RecordDetailDrawer:
Refused by contract, by name. Two-directional probe against this branch's
installed @objectstack/spec 17.2.0, via @objectstack/spec/data:
probe
verdict
reference: 'crm_account'
ACCEPT
reference_to: 'crm_account'
REFUSE — unrecognized_keys, "Did you mean reference_to → reference?"
referenceTo: 'crm_account'
REFUSE — unrecognized_keys, "Did you mean referenceTo → reference?"
The alias entry is a rename hint attached to a refusal, not an acceptance:
the spec names referenceTo explicitly in order to refuse it.
Stripped by the read door.referenceTo is a tombstone in RETIRED_FIELD_KEY_TOMBSTONES
(types/src/internal/retired-field-keys.ts:146, retiredBy: 'objectui#6041', specEquivalent: 'reference') with all three strip sites true.
Zero producers in the cell, against hot controls — §3.
3. The producer census — re-derived at this base, not inherited
⛔ The table's numbers were not inherited. A separately written structure
walk was run at bf97b98c8: TypeScript compiler API over every tracked .ts/.tsx plus parsed JSON, recording each hit's ancestor property chain,
counting emit positions only (PropertyAssignment / ShorthandPropertyAssignment). A PropertyAccessExpression such as fd.referenceTo is a read and is never counted as a producer; a PropertySignature is a declaration and is bucketed separately (38 of those,
never counted as producers). Scanned 3808 TS/TSX + 754 JSON tracked files.
THE CELL = an emit inside a fields container — literally what these
components read, objectSchema.fields[name].
term
role
repo-wide emits
IN THE CELL
strict cell (field-def level)
independent cell (enclosing type is reference-bearing)
referenceTo
SUBJECT
81 / 42 files
2 / 2
2 / 2
30 / 14
reference
CONTROL
195 / 76
92 / 36
91 / 35
156 / 59
reference_to
CONTROL
137 / 88
52 / 36
52 / 36
101 / 65
Control discipline, both halves.
The query ran. The controls are hot — 92 and 52 — in the very cells
where the subject collapses to 2, extracted by the same pass.
The question was right. Subject and control were extracted by the same
pass, from the same cells, in the same units (emitted keys on a field
definition), so a mis-posed cell would have moved subject and control
together instead of separating 92-to-2. The cell was computed two ways
(ancestor chain contains fields; and strictly at field-def level) and they
agree on all three terms.
Third check, the one this key affords:referenceTo is not a term the
scanner cannot see — it is hot repo-wide at 81 emits across 42 files, and
collapses to 2 only under the cell restriction. The near-zero is produced by the restriction, not by scanner blindness. Attributing all 27 non-test emits:
action params (resolveActionParams.ts), designer drafts (FieldDesigner.tsx, MetadataFieldsPage.tsx), dataset inspectors, the report field schema
(reports.zod.ts:69), list-view column meta and filter-condition meta, plus 10
i18n locale strings. Not one is in the cell, and not one is in either package
this PR touches.
The 2 in-cell hits are negative fixtures of the retirement machinery, read
and confirmed rather than assumed: app-shell/.../object-fields-io.spec-keys.test.ts:235 and plugin-designer/src/MetadataFieldsPage.specKeyReference.test.tsx:75 both poison
a draft with the retired key precisely to assert ObjectSchema.safeParse
succeeds after the read door strips it. A fixture asserting removal is not a
producer.
⭐ Seam-local control — the half a repo-wide pass does not give you
The dispatch asked for confirmation that these two readers have no in-cell referenceTo producer, so the census was also attributed per package:
package
referenceTo emits (any position, any cell)
surviving spellings in its own fixtures
plugin-gantt
0
reference_to at ObjectGantt.quickfilter.test.tsx:251, demo/main.tsx:334-335; reference at :306
plugin-tree
0
reference ×6, all in a fields container: ObjectTree.fieldFormatting.test.tsx:51-52, ObjectTree.settledSchemaKeying-6481.test.tsx:62,63,70,71
⇒ The corpus that actually feeds these two readers is hot on what survives and
empty on what goes. The zero here is a local measurement with a local control,
not an extrapolation from the repo-wide one.
⛔ The hard limitation, restated so this PR is not read as more than it is
This licence rests on the key being contract-refused by name and actively
stripped by the read door — ⛔ not on a production-only census. The
table's ceiling still stands and is not stepped over here:
the census cannot distinguish "no production object-metadata producer of reference_to exists" from "this repo contains no production object-metadata
documents at all" — restricting the cell to production files collapses the control too (in-cell non-test reference = 1, reference_to = 2; a
2-versus-1 comparison is not a measurement). This repo is a UI library, not
a metadata-app repo.
⇒ That is exactly why reference_to is untouched here and referenceTo is not.
The argument does not carry to reference_to, and this PR does not extend it.
4. The pins — the published form, copied not invented
ObjectGantt.referenceArms-6837.test.tsx and ObjectTree.referenceArms-6837.test.tsx follow the five-element form PR #6916
published and PR #6920 copied: header carrying the measurement rather than
the conclusion; the live arms pinned in the same file as the dead one; a
named refusal case per deleted key; the precedence-inversion element; and the
floor restated at the pin.
Element 4 — no inversion case exists, and none was fabricated. The deleted
arm sat at the END of both chains (the tree's is already canonical-first), so
it could never preempt a contract-carrying spelling. A { reference: 'projects', referenceTo: 'other' } case would resolve to 'projects' both before and after and would measure nothing. Stated in prose in
both files, per the form.
Element 2 matters here and is load-bearing: each file pins the live arms
beside the refusal, plus a degenerate-pass guard — otherwise a reader that simply
stopped resolving anything would pass the refusals too. The settle signal in both
harnesses is deliberately spelling-independent: buildExpandFields decides $expand from the field's type alone ("the reference / reference_to target
is irrelevant to the decision"), so a find carrying $expand proves the
component consumed the schema — for the refusal probe just as much as for the
live-arm ones. A refusal that passed because the schema never arrived cannot hide
behind it.
5. Verification
Module resolution, stated first: both pins import their component by relative source path (./ObjectGantt, ./ObjectTree) and @object-ui/core
is aliased by the root vitest.config.mts to packages/core/src. Both legs
resolve to SOURCE — no package exports hop, no dist, and therefore no
rebuild leg to get wrong.
Red first
tree
tests
result
base source + the new pins
12
3 failed / 9 passed
after the change
12
12 passed
The 3 failures were exactly the refusal assertions — gantt "does NOT read referenceTo", gantt "degrades to the distinct loaded values", tree "does NOT
read referenceTo" — with all 9 live-arm, degenerate-guard and
through-the-normalizer cases green.
Ablation — the FACT mutated, never the assertion
Run from the committed tree (d812dadb8), so the restore leg has a real
reference. Predictions were written before running; all four matched.
leg
mutation
predicted
measured
control
none
12 passed
12 passed
A
restore the third arm in gantt
2 failed / 10 passed
2 failed / 10 passed
B
restore the third arm in tree
1 failed / 11 passed
1 failed / 11 passed
C
restore both (= the pre-change source)
3 failed / 9 passed
3 failed / 9 passed
Leg B is the informative one: the tree's degenerate-pass guard stayed green
under the mutation, which is what makes it a control rather than a duplicate of
the pin.
Mutation proven on disk per leg — anchored counts (deleted-text 1→0,
injected-text 0→1; an anchor miss aborts the leg with exit 7 rather than silently
no-opping) andgit hash-object diverging from the HEAD blob:
gantt dc20b2f0 → 857b3324, tree 568c4d35 → beca27c1.
Restore proven both ways per leg, scoped to the mutated paths only — git diff HEAD -- path empty (measured in bytes, not by exit code) and git hash-object equal to the HEAD blob. Restore is pinned to git checkout HEAD -- path, never the bare form, which restores from the index
a path-scoped checkout has itself written. Whole-tree git status --short = 0
bytes at the end. Absolute paths from git rev-parse --show-toplevel, under a trap ... EXIT INT TERM.
Suites, types, lint, gates
Package suites:plugin-gantt + plugin-tree → 60 files / 462 tests
passed. Run from the repo root against explicit paths (a package-cwd run and
a zero-match filter are both refused by scripts/vitest-invocation-guard.mjs).
Type-check, both packages, exit 0 with the script echoed
(tsc --noEmit && tsc -p tsconfig.test.json) so it is not a zero-match false
green. The dependency closure was built first (25 packages, exit 0) — tsconfig.test.json sets paths: {} so it resolves workspace deps through
built .d.ts, and an unbuilt closure would have been a false RED (TS2307),
not a finding.
--listFiles, MEASURED both halves: each pin is a program input 1 time
under tsconfig.test.json and 0 times under the build project — the build tsconfig.json excludes the test glob, so only the test project can cover
it. Each edited component is an input 1 time in both projects.
Lint, compared against the BASE version of the same files — not a bare
count. Base blobs 677650a2 / 94ba6dce vs branch blobs dc20b2f0 / 568c4d35, counts from --format json: 0 errors / 113 warnings, identical
in both directions, with a byte-identical per-rule breakdown. Blob ids
verified in both directions and the restore proven by an empty git status.
The two new pins have no base version and are reported standalone: 0 errors /
9 warnings, all @typescript-eslint/no-explicit-any — the same rule and
class as the drawer pin from PR fix(plugin-detail): drop the two undeclared arms from RecordDetailDrawer's relationship-target chain #6920 (6 of the same).
The real package lint gates, not a narrowing: @object-ui/plugin-gantteslint .exit 0 (0 errors, 309 warnings) and @object-ui/plugin-treeeslint .exit 0 (0 errors, 53 warnings).
Gates, each quoted by its own verdict line, all exit 0: check:control-bytes ("scanned 5777 tracked text file(s)"), check:vi-mock-specifiers, check:vi-mock-inherit, check:self-import, check:esm-specifiers, check:phantom-deps, check:designer-field-key-parity, check:element-data-source-declaration, check:side-effects-array, check-changeset-no-major, check-changeset-presence ("4 source file(s) of 2
released package(s) changed, and this change declares 1 changeset(s)"), check-changeset-fixed, check-changeset-overwrite, check-lint-coverage
(46/46), check-type-check-coverage (45/46 + 41/41 test projects).
NOT MEASURED — reported as neither red nor green:
check:sdui-registration-pins exits 2 and says so itself: "This is exit 2,
not a pass: ... a run with nothing to read has measured nothing. Build the
console first". No console build exists in this worktree. The diff touches no
registration array.
check:spec-floors exits 1 with no-artifact and prints its own remedy
("Build the workspace before running this gate"). This worktree built a
25-package closure, not the whole workspace. The diff adds no spec import and
moves no dependency range.
Declared narrowing: repo-wide eslint . and the rest of the gate farm were
not run locally; CI runs the farm once regardless. The narrowing is bounded, not
assumed: eslint.config.js contains no projectService and no project:
key, so type-aware linting is not enabled and this diff cannot move the verdict
of any file it does not touch. Both packages it does touch were linted in full
by their own gate.
Shared verify lock: every acquisition waited 0s; longest hold 88s (the
two package suites). Reported as a measurement, per the dispatch.
All figures above are from the final commit d812dadb8, with git status
clean.
6. Scope, and the boundary that was not reached
⛔ Refs, never Fixes.#6837 has ~18 readers of deferred scope and must
stay open; #6584 lost a decision's home for four days by closing on a first
slice.
The stop-and-report boundary — "if either file's seam turns out to differ, STOP
AND REPORT rather than deleting" — was not reached. Both seams matched the
table exactly: contract (a), fed by a generic dataSource.getObjectSchema,
reachable raw, three spellings read, only reference declared by the contract
fed. One refinement worth recording rather than silently correcting: the table
lists ObjectTree's def provenance as an "objectSchema param". It is more
precisely useSettledSchema(schemaKey, dataSource), which calls dataSource.getObjectSchema itself. That makes it more clearly reachable
raw, not less — the classification is unchanged.
⛔ Deliberately untouched, and still #6837's open scope: the reference_to arms
across Class A (blocked on a corpus this repo cannot provide), Class B rows
18-20 (both spellings have named in-repo producers), the two pass-through
forwarders at plugin-grid/ObjectGrid.tsx:465 and plugin-dashboard/recordFields.tsx:133, and RecordDetailDrawer.tsx:301's reference_field ?? referenceField category
error.
…from both target chains
`ObjectGantt`'s quick-filter option fetch and `ObjectTree`'s `detectParentField`
each resolved a relationship target through three spellings:
fd?.reference_to ?? fd?.reference ?? fd?.referenceTo
def?.reference || def?.reference_to || def?.referenceTo
No contract declares the third. `@objectstack/spec` 17.2.0's `FieldSchema`
refuses `referenceTo` by name with `unrecognized_keys`, carrying its own "Did
you mean `referenceTo` -> `reference`?" rename; it is also a tombstone in
`RETIRED_FIELD_KEY_TOMBSTONES` (objectui#6041) at all three strip sites, so the
designer read door removes it before a draft round-trips. So the arm was not a
redundant fallback but invented tolerance surface -- a silent absorption point
for a producer that ought to fail visibly (AGENTS.md #0.1).
A repo-wide structure-walk producer census (TypeScript compiler API over every
tracked source plus parsed JSON, recording each hit's ancestor property chain,
emit positions only) found `referenceTo` at 2 hits in the cell these components
read -- both of them negative fixtures of the retirement machinery, asserting
that the read door strips the key -- against controls `reference` at 92 and
`reference_to` at 52 in the same cells from the same pass. Neither
`plugin-gantt` nor `plugin-tree` emits `referenceTo` anywhere, while both
packages' own fixtures are hot on the surviving spellings.
The two surviving arms are deliberately untouched: choosing between
`reference_to` and `reference` per reader is objectui#6837's open scope.
The eager closure is every chunk the entry reaches through static imports — what the browser fetches and parses before the app renders. The entry chunk on its own is a small fraction of it.
Reviewed at head d812dadb8 against base bf97b98c8 (= origin/main), in a dedicated detached worktree, removed after review. Clause-② routing (reject-direction change reachable raw by third-party DataSource implementations) was settled at this tier on PR #6920; I sanity-checked its two premises rather than re-litigating — getObjectSchema is a required, non-optional member at types/src/data.ts:519, and normalizeSchemaReferenceKeys has exactly two production call sites (MetadataProvider.tsx:657, data-objectstack/src/index.ts:3753). Both hold.
The crux: the narrower licence holds, and nothing leaks toward reference_to
The licence claimed is conjunctive — referenceTo is (i) refused by name by the contract these seams are fed, (ii) stripped by the read door, (iii) a seam-local zero against hot controls. I checked each leg against the question the classification table's ceiling exists to guard: would this step equally license deleting a reference_to arm?
(i) alone would leak — my probe shows FieldSchema refuses reference_to by name too. But the PR never uses (i) alone.
(ii) does not leak: RETIRED_FIELD_KEY_TOMBSTONES contains referenceTo (retiredBy: 'objectui#6041', specEquivalent: 'reference', all three strip sites true) and does not contain reference_to, which is instead a live declared key of DetailViewFieldSchema (views.zod.ts:49).
(iii) actively contradicts a reference_to deletion: the same package-local query that returns zero referenceTo emits returns hot reference_to fixtures feeding these very readers — deleting that arm would flip the packages' own suites red.
The PR restates the table's hard ceiling verbatim, does not rest on a production-only census, and both pins carry the explicit "what this does NOT rest on" disclaimer. No step in the argument extends to reference_to.
Re-derived / reproduced (not taken on report)
Scope — exactly 5 files (changeset, 2 sources, 2 pins). Each source diff deletes only the trailing referenceTo arm plus a comment; the surviving reference_to / reference arms and every other spelling-bearing line in both files are byte-identical to base (blob ids match the PR's: gantt 677650a23→dc20b2f03, tree 94ba6dce7→568c4d355). Class B, the two forwarders (ObjectGrid.tsx:465, recordFields.tsx:133), reference_field, and the other readers: untouched. Refs #6837 only; finding(objectui): ~20 more relationship-target readers still run a legacy-first reference_to chain the ingestion normalizer already made redundant #6837's closed_by_pull_requests = 0.
⭐ Seam-local control — re-derived at base with an independent query, and it is slightly stronger than stated: referenceTo appears in plugin-gantt exactly twice (one comment, the read itself) and in plugin-tree exactly once (the read itself) — zero emits at any position, not merely zero in-cell — while the same query returns the hot controls at the exact claimed lines (quickfilter.test.tsx:251, demo/main.tsx:334-335, :306; tree reference ×6, each verified inside a fields container). Both halves: the query ran, on the join.
Tombstone + spec probe — tombstone entry verified at retired-field-keys.ts:146 as claimed. Two-directional probe re-run against installed @objectstack/spec 17.2.0: reference ACCEPT; reference_to REFUSE unrecognized_keys; referenceTo REFUSE unrecognized_keys with the "Did you mean referenceTo → reference?" rename hint. Reproduced verbatim.
Ablation — control re-run: 12/12. Leg B re-run (tree arm restored): 1 failed / 11 passed, the failure being exactly the tree refusal, and the tree's degenerate-pass guard stayed GREEN under the mutation — it is a genuine control, not a duplicate of the pin. Leg C re-run (both arms restored = pre-change source): 3 failed / 9 passed, exactly the three named tests — which also corroborates the red-first 3/9 claim. Mutations proven by anchored counts (1→0 / 0→1) and blob divergence; my minimal mutations produced blobs beca27c1 / 857b3324, byte-identical to the PR's reported mutated blobs. Restores proven both ways scoped to the mutated paths (git diff HEAD -- path = 0 bytes, blob = HEAD blob), whole-tree git status = 0 bytes.
Pin instrument — all five elements present in both files; the form's source (RecordDetailDrawer.referenceArms-6837.test.tsx) exists on main. Element 2's settle signal is genuinely spelling-independent: isExpandableFieldDef (core/src/utils/expand-fields.ts:127-136) reads only the field's type. Element 4: verified at base that referenceTo sat last in both chains, so no inversion case exists and none was fabricated. The through-the-normalizer case's premise verified at reference-keys.ts:29-32.
--listFiles, both halves — closure built first (exit 0), then all four type-check legs exit 0, then measured: each pin is a program input 1× under tsconfig.test.json and 0× under the build project; each edited component 1× in both. Only the test project can cover the pins, as claimed.
Package suites — re-run from the repo root with explicit paths through the root config (per the invocation guard): 60 files / 462 tests passed, matching the PR.
The recorded refinement — verified: base ObjectTree.tsx:396 is useSettledSchema<any>(schemaKey, dataSource), and the hook itself calls dataSource.getObjectSchema(settleKey) (useSettledSchema.ts:~140). More clearly reachable raw; classification unchanged; recorded rather than silently corrected. Also verified the two in-cell referenceTo census hits are the claimed negative fixtures poisoning drafts to assert the strip.
NOT MEASURED, restated as neither red nor green (not converted to a colour here either): check:sdui-registration-pins (exit 2, no console build) and check:spec-floors (exit 1 no-artifact). CI on d812dadb8 is fully green (all four test shards, Type Check, Lint, gate farm), which covers the repo-wide half neither of us ran locally.
Shared verify lock, as a measurement: six acquisitions, every wait ≤ 0.005s; longest hold ~92s (the two package suites).
Non-blocking notes (no follow-up required)
The pins'/changeset's headline "the arm NO CONTRACT DECLARES" is seam-scoped shorthand: report columns (reports.zod.ts:69) and designer fields (designer.ts:838) do declare referenceTo for their document kinds. The operative claim — the contract fed to these two readers declares only reference — is correct, and pin §4 states the full tier-boundary fact accurately. No action needed; noted so the headline is not quoted out of seam.
The PR's sweep-C (independent cell) counts differ from the classification table's by 1 (reference 156 vs 157, referenceTo 30 vs 31) while the load-bearing sweep-B cells and controls match exactly — consistent with a genuinely re-derived walker rather than inherited numbers.
The narrower licence holds; the slice stays inside it; #6837 correctly stays open.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Refs #6837
Second slice of the classification table's suggested slicing, item 1: the
referenceTothird arms atObjectGanttandObjectTree. ⛔ Nothing else — notClass B, not the pass-through forwarders, not
reference_field, not the other~16 readers. The card stays open.
Both readers were re-located by text on
bf97b98c8; both were still at theline numbers the card cites (the table had already found two other readers whose
numbers had shifted, so this was not assumed).
1. The accept/reject delta, stated so it does not have to be re-derived
This is a reject-direction change reachable by third-party
DataSourceimplementations — same clause-② routing as PR #6920, and for the same measured
reason: the ingestion door is not total.
normalizeSchemaReferenceKeyshastwo production call sites, making it a private property of two implementations,
while
getObjectSchemais a required, non-optional member of the publishedDataSourceinterface (types/src/data.ts:519) — and both components here callit on the generic
dataSource:ObjectGantteffectiveDataSource.getObjectSchema(resource)at:706ObjectTreeuseSettledSchema(schemaKey, dataSource)→dataSource.getObjectSchema(key)(react/src/hooks/useSettledSchema.ts:141)What stops being accepted: a field def carrying only
referenceTo— andwhich did not pass the ingestion choke point — no longer resolves a
relationship target at these two seams.
What that looks like, concretely, rather than abstractly:
parentFieldis set explicitlyWhat is NOT affected:
MetadataProviderorObjectStackAdapter.getObjectSchema. The normalizer readsreference_to ?? reference ?? referenceToand stamps both snake_case keys,so such a def already carries
reference_tobefore either component sees it.This is pinned as a test, not asserted in prose.
reference_toandreferenceare byte-identical to base.reference_tochain the ingestion normalizer already made redundant #6837.2. Why this arm, and only this arm, is licensed
referenceToclears the same bar the first slice (PR #6920) cleared forRecordDetailDrawer:Refused by contract, by name. Two-directional probe against this branch's
installed
@objectstack/spec17.2.0, via@objectstack/spec/data:reference: 'crm_account'reference_to: 'crm_account'unrecognized_keys, "Did you meanreference_to→reference?"referenceTo: 'crm_account'unrecognized_keys, "Did you meanreferenceTo→reference?"The alias entry is a rename hint attached to a refusal, not an acceptance:
the spec names
referenceToexplicitly in order to refuse it.Stripped by the read door.
referenceTois a tombstone inRETIRED_FIELD_KEY_TOMBSTONES(
types/src/internal/retired-field-keys.ts:146,retiredBy: 'objectui#6041',specEquivalent: 'reference') with all three strip sitestrue.Zero producers in the cell, against hot controls — §3.
3. The producer census — re-derived at this base, not inherited
⛔ The table's numbers were not inherited. A separately written structure
walk was run at
bf97b98c8: TypeScript compiler API over every tracked.ts/.tsxplus parsed JSON, recording each hit's ancestor property chain,counting emit positions only (
PropertyAssignment/ShorthandPropertyAssignment). APropertyAccessExpressionsuch asfd.referenceTois a read and is never counted as a producer; aPropertySignatureis a declaration and is bucketed separately (38 of those,never counted as producers). Scanned 3808 TS/TSX + 754 JSON tracked files.
THE CELL = an emit inside a
fieldscontainer — literally what thesecomponents read,
objectSchema.fields[name].typeis reference-bearing)referenceToreferencereference_toControl discipline, both halves.
where the subject collapses to 2, extracted by the same pass.
pass, from the same cells, in the same units (emitted keys on a field
definition), so a mis-posed cell would have moved subject and control
together instead of separating 92-to-2. The cell was computed two ways
(ancestor chain contains
fields; and strictly at field-def level) and theyagree on all three terms.
Third check, the one this key affords:
referenceTois not a term thescanner cannot see — it is hot repo-wide at 81 emits across 42 files, and
collapses to 2 only under the cell restriction. The near-zero is produced by the
restriction, not by scanner blindness. Attributing all 27 non-test emits:
action params (
resolveActionParams.ts), designer drafts (FieldDesigner.tsx,MetadataFieldsPage.tsx), dataset inspectors, the report field schema(
reports.zod.ts:69), list-view column meta and filter-condition meta, plus 10i18n locale strings. Not one is in the cell, and not one is in either package
this PR touches.
The 2 in-cell hits are negative fixtures of the retirement machinery, read
and confirmed rather than assumed:
app-shell/.../object-fields-io.spec-keys.test.ts:235andplugin-designer/src/MetadataFieldsPage.specKeyReference.test.tsx:75both poisona draft with the retired key precisely to assert
ObjectSchema.safeParsesucceeds after the read door strips it. A fixture asserting removal is not a
producer.
⭐ Seam-local control — the half a repo-wide pass does not give you
The dispatch asked for confirmation that these two readers have no in-cell
referenceToproducer, so the census was also attributed per package:referenceToemits (any position, any cell)plugin-ganttreference_toatObjectGantt.quickfilter.test.tsx:251,demo/main.tsx:334-335;referenceat:306plugin-treereference×6, all in afieldscontainer:ObjectTree.fieldFormatting.test.tsx:51-52,ObjectTree.settledSchemaKeying-6481.test.tsx:62,63,70,71⇒ The corpus that actually feeds these two readers is hot on what survives and
empty on what goes. The zero here is a local measurement with a local control,
not an extrapolation from the repo-wide one.
⛔ The hard limitation, restated so this PR is not read as more than it is
This licence rests on the key being contract-refused by name and actively
stripped by the read door — ⛔ not on a production-only census. The
table's ceiling still stands and is not stepped over here:
⇒ That is exactly why
reference_tois untouched here andreferenceTois not.The argument does not carry to
reference_to, and this PR does not extend it.4. The pins — the published form, copied not invented
ObjectGantt.referenceArms-6837.test.tsxandObjectTree.referenceArms-6837.test.tsxfollow the five-element form PR #6916published and PR #6920 copied: header carrying the measurement rather than
the conclusion; the live arms pinned in the same file as the dead one; a
named refusal case per deleted key; the precedence-inversion element; and the
floor restated at the pin.
Element 4 — no inversion case exists, and none was fabricated. The deleted
arm sat at the END of both chains (the tree's is already canonical-first), so
it could never preempt a contract-carrying spelling. A
{ reference: 'projects', referenceTo: 'other' }case would resolve to'projects'both before and after and would measure nothing. Stated in prose inboth files, per the form.
Element 2 matters here and is load-bearing: each file pins the live arms
beside the refusal, plus a degenerate-pass guard — otherwise a reader that simply
stopped resolving anything would pass the refusals too. The settle signal in both
harnesses is deliberately spelling-independent:
buildExpandFieldsdecides$expandfrom the field'stypealone ("thereference/reference_totargetis irrelevant to the decision"), so a
findcarrying$expandproves thecomponent consumed the schema — for the refusal probe just as much as for the
live-arm ones. A refusal that passed because the schema never arrived cannot hide
behind it.
5. Verification
Module resolution, stated first: both pins import their component by
relative source path (
./ObjectGantt,./ObjectTree) and@object-ui/coreis aliased by the root
vitest.config.mtstopackages/core/src. Both legsresolve to SOURCE — no package
exportshop, nodist, and therefore norebuild leg to get wrong.
Red first
The 3 failures were exactly the refusal assertions — gantt "does NOT read
referenceTo", gantt "degrades to the distinct loaded values", tree "does NOTread
referenceTo" — with all 9 live-arm, degenerate-guard andthrough-the-normalizer cases green.
Ablation — the FACT mutated, never the assertion
Run from the committed tree (
d812dadb8), so the restore leg has a realreference. Predictions were written before running; all four matched.
Leg B is the informative one: the tree's degenerate-pass guard stayed green
under the mutation, which is what makes it a control rather than a duplicate of
the pin.
Mutation proven on disk per leg — anchored counts (deleted-text 1→0,
injected-text 0→1; an anchor miss aborts the leg with exit 7 rather than silently
no-opping) and
git hash-objectdiverging from the HEAD blob:gantt
dc20b2f0→857b3324, tree568c4d35→beca27c1.Restore proven both ways per leg, scoped to the mutated paths only —
git diff HEAD -- pathempty (measured in bytes, not by exit code) andgit hash-objectequal to the HEAD blob. Restore is pinned togit checkout HEAD -- path, never the bare form, which restores from the indexa path-scoped checkout has itself written. Whole-tree
git status --short= 0bytes at the end. Absolute paths from
git rev-parse --show-toplevel, under atrap ... EXIT INT TERM.Suites, types, lint, gates
plugin-gantt+plugin-tree→ 60 files / 462 testspassed. Run from the repo root against explicit paths (a package-cwd run and
a zero-match filter are both refused by
scripts/vitest-invocation-guard.mjs).(
tsc --noEmit && tsc -p tsconfig.test.json) so it is not a zero-match falsegreen. The dependency closure was built first (25 packages, exit 0) —
tsconfig.test.jsonsetspaths: {}so it resolves workspace deps throughbuilt
.d.ts, and an unbuilt closure would have been a false RED (TS2307),not a finding.
--listFiles, MEASURED both halves: each pin is a program input 1 timeunder
tsconfig.test.jsonand 0 times under the build project — the buildtsconfig.jsonexcludes the test glob, so only the test project can coverit. Each edited component is an input 1 time in both projects.
count. Base blobs
677650a2/94ba6dcevs branch blobsdc20b2f0/568c4d35, counts from--format json: 0 errors / 113 warnings, identicalin both directions, with a byte-identical per-rule breakdown. Blob ids
verified in both directions and the restore proven by an empty
git status.The two new pins have no base version and are reported standalone: 0 errors /
9 warnings, all
@typescript-eslint/no-explicit-any— the same rule andclass as the drawer pin from PR fix(plugin-detail): drop the two undeclared arms from RecordDetailDrawer's relationship-target chain #6920 (6 of the same).
@object-ui/plugin-gantteslint .exit 0 (0 errors, 309 warnings) and@object-ui/plugin-treeeslint .exit 0 (0 errors, 53 warnings).check:control-bytes("scanned 5777 tracked text file(s)"),check:vi-mock-specifiers,check:vi-mock-inherit,check:self-import,check:esm-specifiers,check:phantom-deps,check:designer-field-key-parity,check:element-data-source-declaration,check:side-effects-array,check-changeset-no-major,check-changeset-presence("4 source file(s) of 2released package(s) changed, and this change declares 1 changeset(s)"),
check-changeset-fixed,check-changeset-overwrite,check-lint-coverage(46/46),
check-type-check-coverage(45/46 + 41/41 test projects).NOT MEASURED — reported as neither red nor green:
check:sdui-registration-pinsexits 2 and says so itself: "This is exit 2,not a pass: ... a run with nothing to read has measured nothing. Build the
console first". No console build exists in this worktree. The diff touches no
registration array.
check:spec-floorsexits 1 withno-artifactand prints its own remedy("Build the workspace before running this gate"). This worktree built a
25-package closure, not the whole workspace. The diff adds no spec import and
moves no dependency range.
Declared narrowing: repo-wide
eslint .and the rest of the gate farm werenot run locally; CI runs the farm once regardless. The narrowing is bounded, not
assumed:
eslint.config.jscontains noprojectServiceand noproject:key, so type-aware linting is not enabled and this diff cannot move the verdict
of any file it does not touch. Both packages it does touch were linted in full
by their own gate.
Shared verify lock: every acquisition waited 0s; longest hold 88s (the
two package suites). Reported as a measurement, per the dispatch.
All figures above are from the final commit
d812dadb8, withgit statusclean.
6. Scope, and the boundary that was not reached
⛔
Refs, neverFixes.#6837 has ~18 readers of deferred scope and muststay open; #6584 lost a decision's home for four days by closing on a first
slice.
The stop-and-report boundary — "if either file's seam turns out to differ, STOP
AND REPORT rather than deleting" — was not reached. Both seams matched the
table exactly: contract (a), fed by a generic
dataSource.getObjectSchema,reachable raw, three spellings read, only
referencedeclared by the contractfed. One refinement worth recording rather than silently correcting: the table
lists
ObjectTree's def provenance as an "objectSchemaparam". It is moreprecisely
useSettledSchema(schemaKey, dataSource), which callsdataSource.getObjectSchemaitself. That makes it more clearly reachableraw, not less — the classification is unchanged.
⛔ Deliberately untouched, and still #6837's open scope: the
reference_toarmsacross Class A (blocked on a corpus this repo cannot provide), Class B rows
18-20 (both spellings have named in-repo producers), the two pass-through
forwarders at
plugin-grid/ObjectGrid.tsx:465andplugin-dashboard/recordFields.tsx:133, andRecordDetailDrawer.tsx:301'sreference_field ?? referenceFieldcategoryerror.
Generated by Claude Code
Generated by Claude Code