Skip to content

fix(plugin-gantt): honour filter, sort and the row ceiling on inline value data - #9063

Merged
os-steve merged 1 commit into
mainfrom
claude/issue-8769-gantt-inline-filter-sort-ceiling
Sep 10, 2026
Merged

os-steve merged 1 commit into
mainfrom
claude/issue-8769-gantt-inline-filter-sort-ceiling

Conversation

@os-steve

Copy link
Copy Markdown
Collaborator

Fixes #8769

Exit taken: the preferred one. Both exits were pre-authorised — apply the keys, or refuse the combination loudly — and the measurement chose the first: resolveDataSource already answers this provider with ValueDataSource, which implements $filter, $orderby, $skip, $top and $select over its own array, so there was no capability the inline path could not support and nothing to refuse. No filter combinator was written here — objectui#8513's work stays where it is.

What was wrong

ObjectGantt's reload short-circuited the inline provider: it set the authored rows and returned before effectiveDataSource.find(...), which is the one site that lowers schema.filter onto $filter, schema.sort onto $orderby and the objectui#7210 ceiling onto $top. An authored filter therefore reached nothing and the chart drew every inline row.

The direction is fail-open: the dropped key is the key that NARROWS, so the author was shown more rows than declared, with no diagnostic. ⛔ Nothing was exposed — the rows are already in the authored schema. What was wrong is that the chart answered a wider question than the one asked.

The two-sided reading is the finding

A one-sided reproduction cannot separate "the filter was ignored" from "the filter matched everything", so the same five rows and the same status = open filter were rendered twice, with one ValueDataSource serving both sides — the matcher is literally the same object, and the only remaining variable is which branch of reload ran.

path rows drawn ids
fetching (objectName + context adapter) 3 1,3,5
inline (provider: 'value') — before 5 1,2,3,4,5
inline (provider: 'value') — after 3 1,3,5

What changed

The inline branch is deleted; provider: 'value' falls through to the same adapter query as every other provider. The now-dead dataItems binding goes with it — reload no longer reads the payload, and authored items still reach it through the deep-compared dataConfig that builds the adapter, so the objectui#6592 memo-identity reasoning is preserved and re-stated where the binding used to be.

The assumptions, and how each resolved

A — does the repair inherit objectui#9001 / PR objectui#9049's new FilterOperatorError throw? NO, measured. convertFiltersToAST is reached only by @object-ui/data-objectstack on the wire path. ValueDataSource never imports it — its imports are @object-ui/types, @objectstack/spec/data and its own batchTransaction module — and it carries its own local refusal for that comparand, which excludes the row and logs once. So an inline chart whose filter carries such a comparand renders with zero rows and a console line, it does not throw at render. This was a deliberate decision, not an accident of routing: an in-memory matcher decides about one row at a time, while the converter is deciding whether to send a query at all, and that asymmetry is the adapter's own stated refusal shape. Pinned from the outside by refusedComparandDoesNotThrow.

B — is find(...) really the only site applying the three? HELD. Verified by symbol across the package: applyNonGridRowCeiling appears at exactly one call site, and $filter / $orderby at exactly one each, all inside reload.

C — are the ceiling and filter/sort independent? NO, and the order is now asserted. A ceiling computed before filtering is a different number from one computed after. Filter first, ceiling second, matching the fetching path exactly: there $filter and $top travel in one query and every backend filters before it limits, so any other order would have made the two paths disagree again in a subtler way. ceilingOrder pins it from the observable side — 2,400 inline rows of which 800 match draw all 800 with no footnote, which a ceiling-then-filter implementation could not produce.

D — is data: { provider: 'value' } the only spelling? NO — there are TWO. The ruled three-rung record-source ladder also wraps staticData into a provider: 'value' config, so an author lands on this path either way. staticDataSpelling covers the second one. (A bare data: [] array does not reach here on the gantt — this renderer returns it verbatim rather than normalising it, so it falls through to the fallback adapter; and the host data React prop is a different branch, objectui#7333's.)

E — are the card's path:line anchors stale? ASSUMED SO and not used. Everything was re-derived by symbol, and there is no line citation anywhere in this diff.

F — is today's behaviour silently wrong? HELD. Nothing warned. The short-circuit set state and returned with no console line, so the card's "no diagnostic" framing is correct.

A standing pin is INVERTED here, deliberately

ObjectGantt.rowCeiling-7210.test.tsx asserted "an inline value data set is never capped by us, and never footnoted", and ObjectGantt.tsx carried the matching comment. That case pinned the short-circuit, not objectui#7210 ruling a-prime:

  • the ruling's own budget is measured in DOM elements per record, and its measurement table was taken over "real child views, inline value provider" — an inline row costs the browser exactly what a fetched row costs;
  • the ruling text names no provider carve-out; what it forbids is a view crossing the ceiling quietly, which is why the footnote is the load-bearing half.

So the case is inverted in place with that reasoning recorded on it, and the exemption that genuinely remains — rows a host hands down through the data React prop, which we did not query and have no total for — gets its own case beside it. If a maintainer reads the exemption as ruled rather than incidental, this is the one hunk to revert; it is isolated.

Behaviour a consumer can notice

  • An authored filter / sort now narrows and orders inline rows.
  • Past 2,000 drawn rows an inline chart draws 2,000 and shows the footnote naming both numbers.
  • Inline rows now reach the chart as the adapter's own deep copy rather than as the authored array's object identities. ViewData.items is serializable metadata, so every value the contract admits survives that copy unchanged; only reference comparison against the authored array changes, and id equality is the replacement.
  • An inline gantt's write-backs (drag, dependency edit, inline edit, delete) now survive the reload that follows them, because the reload reads the adapter that took the write instead of re-reading the authored array over the top of it. Reads and writes previously disagreed about which array was the source of truth; they now agree.

Ablation — predicted before running, then measured

Prediction written down first: restoring the short-circuit turns 8 cases red and leaves 4 green (control, plus the above-ceiling, below-ceiling and host-data-prop ceiling cases).

Measured: 8 failed, 4 passed — exactly the predicted split, no miscount.

On-disk proof, both directions: the injected marker went 0 to 1 and the removed marker 1 to 0 in the same file; blob e6e1b46a became fba326a2. Restore proved by hash equality back to e6e1b46a and by an empty git diff HEAD, from a trap with absolute paths. No dist is involved on this path — the pin imports the mutated module relatively, as source.

The control case is the one that makes the rest readable: an inline chart with no filter, no sort and under the ceiling draws the same five rows in the same order on both legs, which is what separates this repair from "the inline path now drops rows".

Verification

All at 05d946890, the final commit.

  • pnpm exec vitest run packages/plugin-gantt/ — 66 files, 514 tests passed.
  • pnpm --filter @object-ui/plugin-gantt type-check — clean. The new pin is proven inside the typechecked set rather than assumed into it: its first run failed with TS7006 on line 269 of the new file, so tsc -p tsconfig.test.json demonstrably reads it.
  • Dependency-closure build pnpm --filter '@object-ui/plugin-gantt^...' build — green (13 packages).
  • Gates run green: control bytes, new cross-file line citations, the three vi-mock gates, element-data-source declaration, unreferenced sources, doc fences, shell-escape residue, comment-mask corpus, changeset presence, changeset-no-major.
  • Lint, narrowed and the narrowing declared. eslint over the plugin-gantt subtree: 94 files counted from --format json, 0 errors on every file this PR touches. The narrowing is a measurement, not a skip: the linted population is read from eslint.config.js's own top-level ignores, which excludes only build output (dist, .next, node_modules, public, .source and three named artefacts) and not one source file; and the config sets no parserOptions.project and no projectService, so linting is not type-aware and this diff cannot move the verdict on any file it did not touch. The 2 errors the run does report are in packages/plugin-gantt/demo/main.tsx, which this PR does not touch — pre-existing.
  • NOT MEASURED, with reasons: check:readme-exports — the gate itself printed "the population COLLAPSED, this run proves nothing" because most packages have no dist in this worktree; after building plugin-gantt its own README is judged and reports zero complaints. check:sdui-registration-pins — exit 2, its own text says a run with no console build "has measured nothing". Both need a full-repo build, which is CI's run.

Acceptance notes

  • filed as objectui#9061 — ObjectCalendar and ObjectMap carry the same inline short-circuit, hand-copied. Fenced out of this PR by the brief and filed rather than fixed. The dedup behind it was controlled: a repo-scoped REST listing over state=all, 400 rows, grepped locally, with objectui#8769 itself as the positive control proving the enumeration reached this neighbourhood.
  • noted, not filed — packages/plugin-gantt/demo/main.tsx carries 2 eslint errors on main (react-hooks/purity, no-console). Whoever runs this package's own lint task hits them; not filed because the repo's actual wired lint invocation cannot be confirmed from here, so "this is red for everyone" would be a guess.
  • noted, not filed — ObjectGrid and ObjectTree also branch on the inline provider, but their surrounding machinery is different and neither was measured here. objectui#9061 deliberately names only calendar and map, the two that are literal copies of this shape.
  • Not touched in either direction: the contract-review label. The claim on this card declares Clause-2 yes and the carriers gate is expected to report C3; that is a disclosed, escalated governance question, and per the brief it is neither cleared nor hung by this seat.

🤖 Generated with Claude Code

https://claude.ai/code/session_01MPaVWWMuWeT5LgB1qoXjVB


Generated by Claude Code

…`value` data

`ObjectGantt`'s `reload` short-circuited the `provider: 'value'` path with
`setData(dataItems); return;` BEFORE `effectiveDataSource.find(...)` — the one
site that lowers `schema.filter` to `$filter`, `schema.sort` to `$orderby` and
the objectui#7210 ceiling to `$top`. An authored `filter` therefore reached
nothing and the chart drew every inline row: the fail-OPEN direction, since the
dropped key is the one that narrows.

Measured two-sided before the repair, with one `ValueDataSource` serving both
sides so the matcher was literally the same object: the same five rows and the
same `status = open` filter drew 3 through the fetching path and 5 inline.

The inline branch is removed; `provider: 'value'` now goes through the adapter
`resolveDataSource` already returns for it, which implements `$filter`,
`$orderby`, `$skip`, `$top` and `$select` over its own array. No combinator was
written here. The dead `dataItems` binding goes with it — `reload` no longer
reads the payload, and authored items still reach it through the deep-compared
`dataConfig` that builds the adapter.

The ceiling now applies to inline rows, and the `never capped by us` case in
`ObjectGantt.rowCeiling-7210.test.tsx` is inverted with the reason recorded on
it: that case pinned the short-circuit, not ruling a′, whose budget is measured
in DOM elements per record over its own inline-provider measurement table and
which carves out no provider. Rows a HOST hands down through the `data` prop
stay exempt and get their own case.

The repair does not reach `convertFiltersToAST`, so it does not inherit the
throw objectui#9001 is adding there; the inline matcher is local and excludes
the row with a log instead.

Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MPaVWWMuWeT5LgB1qoXjVB
@github-actions github-actions Bot added documentation Improvements or additions to documentation plugin tests labels Sep 10, 2026
@github-actions

Copy link
Copy Markdown
Contributor

✅ Console Performance Budget

Metric Value Budget
Eager closure (gzip, 50 chunks) 3490.1 KB 3512.7 KB
Main entry chunk (gzip) 144.2 KB 350 KB
Entry file index-BZZnxR3i.js —
Status PASS —

The eager closure is every chunk the entry reaches through static imports — what the browser fetches and parses before the app renders. The entry chunk on its own is a small fraction of it.


📦 Bundle Size Report

Package Size Gzipped
app-shell (consoleActionDispatch.js) 0.20KB 0.19KB
app-shell (index.js) 16.69KB 6.21KB
app-shell (runtime-config.js) 20.68KB 7.36KB
app-shell (types.js) 0.01KB 0.04KB
app-shell (urlParams.js) 10.06KB 3.86KB
auth (ActiveOrganizationStorage.js) 25.05KB 9.16KB
auth (AuthContext.js) 0.31KB 0.24KB
auth (AuthGuard.js) 2.07KB 1.00KB
auth (AuthProvider.js) 40.18KB 10.59KB
auth (AuthShell.js) 3.49KB 1.40KB
auth (ForgotPasswordForm.js) 12.21KB 3.45KB
auth (LoginForm.js) 18.15KB 5.39KB
auth (PreviewBanner.js) 0.90KB 0.50KB
auth (RegisterForm.js) 6.65KB 2.22KB
auth (SocialSignInButtons.js) 9.61KB 3.89KB
auth (UserMenu.js) 3.41KB 1.23KB
auth (auth-gate-events.js) 1.29KB 0.66KB
auth (authStyles.js) 5.04KB 1.72KB
auth (createAuthClient.js) 40.21KB 10.80KB
auth (createAuthenticatedFetch.js) 8.46KB 3.43KB
auth (index.js) 3.19KB 1.44KB
auth (invitation-status.js) 1.22KB 0.70KB
auth (org-roles.js) 6.66KB 2.78KB
auth (phone-identifier.js) 1.11KB 0.66KB
auth (types.js) 0.59KB 0.35KB
auth (useAuth.js) 5.30KB 1.02KB
auth (useWorkspaceAdminStatus.js) 11.08KB 4.58KB
collaboration (CommentThread.js) 26.08KB 7.56KB
collaboration (LiveCursors.js) 3.17KB 1.27KB
collaboration (PresenceAvatars.js) 6.49KB 2.64KB
collaboration (PresenceProvider.js) 2.79KB 1.13KB
collaboration (index.js) 1.68KB 0.73KB
collaboration (useCollaborationTranslation.js) 6.05KB 2.52KB
collaboration (useCommentSearch.js) 1.98KB 0.88KB
collaboration (useConflictResolution.js) 7.75KB 1.86KB
collaboration (useMentionNotifications.js) 1.81KB 0.68KB
collaboration (usePresence.js) 6.33KB 1.84KB
collaboration (useRealtimeSubscription.js) 7.91KB 2.01KB
components (index.js) 500.20KB 114.67KB
core (index.js) 8.28KB 3.31KB
create-plugin (index.js) 27.94KB 9.51KB
data-objectstack (index.js) 207.56KB 57.44KB
fields (index.js) 247.01KB 62.29KB
i18n (LocalizationContext.js) 1.76KB 0.96KB
i18n (builtinAggregateLabels.js) 0.86KB 0.49KB
i18n (currency.js) 1.22KB 0.64KB
i18n (fallbackInterpolation.js) 6.25KB 2.77KB
i18n (i18n.js) 6.57KB 2.76KB
i18n (index.js) 3.65KB 1.47KB
i18n (pickLocalized.js) 7.62KB 3.26KB
i18n (provider.js) 26.89KB 9.04KB
i18n (useDisplayLocale.js) 2.85KB 1.45KB
i18n (useObjectLabel.js) 34.34KB 9.17KB
i18n (useSafeTranslation.js) 5.60KB 2.33KB
layout (index.js) 38.84KB 10.94KB
mobile (MobileProvider.js) 0.92KB 0.49KB
mobile (ResponsiveContainer.js) 0.94KB 0.38KB
mobile (breakpoints.js) 1.51KB 0.70KB
mobile (createOfflineDataSource.js) 5.61KB 1.75KB
mobile (index.js) 1.99KB 0.87KB
mobile (offlineQueue.js) 3.91KB 1.35KB
mobile (pwa.js) 0.97KB 0.49KB
mobile (serviceWorker.js) 1.48KB 0.62KB
mobile (serviceWorkerSource.js) 3.41KB 1.48KB
mobile (useBreakpoint.js) 1.54KB 0.65KB
mobile (useGesture.js) 6.96KB 1.98KB
mobile (useOfflineSync.js) 1.99KB 0.72KB
mobile (usePullToRefresh.js) 2.53KB 0.85KB
mobile (useResponsive.js) 0.72KB 0.42KB
mobile (useSpecGesture.js) 4.39KB 1.66KB
mobile (useTouchTarget.js) 1.01KB 0.54KB
permissions (MePermissionsProvider.js) 13.52KB 4.88KB
permissions (PermissionContext.js) 0.31KB 0.25KB
permissions (PermissionGuard.js) 0.89KB 0.45KB
permissions (PermissionProvider.js) 6.24KB 2.16KB
permissions (discardProofCache.js) 1.04KB 0.55KB
permissions (evaluator.js) 8.39KB 3.10KB
permissions (index.js) 0.93KB 0.41KB
permissions (store.js) 0.91KB 0.42KB
permissions (useFieldPermissions.js) 1.28KB 0.53KB
permissions (usePermissions.js) 4.83KB 2.27KB
plugin-ai (index.js) 14.81KB 3.63KB
plugin-calendar (index.js) 49.03KB 13.93KB
plugin-charts (index.js) 71.50KB 19.97KB
plugin-chatbot (index.js) 195.32KB 46.51KB
plugin-dashboard (index.js) 131.09KB 34.58KB
plugin-designer (index.js) 215.68KB 44.27KB
plugin-detail (index.js) 251.44KB 65.17KB
plugin-editor (index.js) 2.23KB 1.05KB
plugin-form (index.js) 136.79KB 34.19KB
plugin-gantt (index.js) 166.51KB 40.89KB
plugin-grid (index.js) 211.56KB 57.50KB
plugin-kanban (index.js) 46.07KB 14.32KB
plugin-list (index.js) 112.52KB 27.64KB
plugin-map (index.js) 20.49KB 6.83KB
plugin-markdown (index.js) 13.88KB 4.80KB
plugin-report (index.js) 43.42KB 11.92KB
plugin-timeline (index.js) 30.10KB 8.74KB
plugin-tree (index.js) 9.55KB 3.32KB
plugin-view (index.js) 84.42KB 20.80KB
providers (DataSourceProvider.js) 0.75KB 0.39KB
providers (MetadataProvider.js) 1.37KB 0.59KB
providers (ThemeProvider.js) 1.90KB 0.85KB
providers (UploadProvider.js) 11.66KB 3.50KB
providers (index.js) 0.45KB 0.23KB
providers (types.js) 0.01KB 0.04KB
react-runtime (index.js) 5.62KB 2.34KB
react (LazyPluginLoader.js) 4.47KB 1.63KB
react (SchemaRenderer.js) 83.34KB 27.61KB
react (data-invalidation.js) 5.05KB 2.08KB
react (index.js) 4.63KB 2.18KB
react (schema-input.js) 4.25KB 2.04KB
react (spec-input.js) 0.20KB 0.18KB
sdui-parser (codegen.js) 6.58KB 2.74KB
sdui-parser (dashboard-widget-options.js) 3.08KB 1.30KB
sdui-parser (index.js) 5.66KB 2.50KB
sdui-parser (input-type.js) 2.84KB 1.40KB
sdui-parser (kanban-quick-add.js) 3.89KB 1.87KB
sdui-parser (parse.js) 20.57KB 5.88KB
sdui-parser (provenance.js) 3.66KB 1.82KB
sdui-parser (types.js) 0.28KB 0.23KB
sdui-parser (validate.js) 14.82KB 4.99KB
types (ai.js) 0.20KB 0.17KB
types (api-types.js) 0.20KB 0.18KB
types (app.js) 2.87KB 1.00KB
types (base.js) 0.20KB 0.18KB
types (blocks.js) 0.20KB 0.18KB
types (complex.js) 2.93KB 1.49KB
types (crud.js) 0.20KB 0.18KB
types (dashboard-filter-alias.js) 6.23KB 2.74KB
types (data-display.js) 3.75KB 1.85KB
types (data-protocol.js) 0.20KB 0.19KB
types (data.js) 0.20KB 0.18KB
types (designer.js) 1.85KB 0.85KB
types (disclosure.js) 0.20KB 0.18KB
types (error-code.js) 1.54KB 0.88KB
types (expression.js) 0.20KB 0.18KB
types (feedback.js) 0.20KB 0.18KB
types (field-types.js) 0.20KB 0.18KB
types (form.js) 0.20KB 0.18KB
types (http-inflight.js) 8.87KB 3.73KB
types (http-retry.js) 4.32KB 2.02KB
types (icon-key-migration.js) 4.26KB 1.63KB
types (index.js) 4.74KB 2.25KB
types (layout.js) 0.20KB 0.18KB
types (managed-by.js) 0.19KB 0.18KB
types (mobile.js) 4.73KB 2.28KB
types (navigation.js) 0.20KB 0.18KB
types (objectql.js) 0.20KB 0.18KB
types (overlay.js) 0.20KB 0.18KB
types (permissions.js) 0.20KB 0.18KB
types (plugin-scope.js) 0.20KB 0.18KB
types (record-components.js) 0.20KB 0.19KB
types (record-semantics.js) 1.28KB 0.67KB
types (registry.js) 0.20KB 0.18KB
types (reports.js) 0.20KB 0.18KB
types (select-option.js) 0.20KB 0.19KB
types (spec-report.js) 5.05KB 1.93KB
types (spec-ui-namespace.js) 0.20KB 0.19KB
types (strict-authoring-face.js) 14.27KB 5.47KB
types (system-fields.js) 3.33KB 1.54KB
types (theme.js) 6.28KB 2.87KB
types (ui-action.js) 8.11KB 3.32KB
types (views.js) 0.20KB 0.18KB
types (widget.js) 0.20KB 0.18KB

Size Limits

  • ✅ Core packages should be < 50KB gzipped
  • ✅ Component packages should be < 100KB gzipped
  • ⚠️ Plugin packages should be < 150KB gzipped

Copy link
Copy Markdown
Collaborator Author

In-seat Clause-② review of record — domain:ui @ objectui

Approved — land on all-checks-green. One thing is flagged upward rather than absorbed; it is named below and it is revertible in one hunk.

The pin inversion: I read objectui#7210's ruling rather than take the reading on trust

This PR inverts a standing case in ObjectGantt.rowCeiling-7210.test.tsx that asserted an inline set is never capped and never footnoted. Inverting a pin that sits on a maintainer-ruled surface is not something a dev's argument settles by itself, so I read ruling a′ (objectui#7210 comment 5508048888) and its implementation report (5513939384). The dev's reading holds, on four independent legs:

  1. The ruling's budget is provider-independent. It was set by measuring DOM elements per record — ObjectTree binding at ~5.2 elements/record, 10,406 elements at n=2000 — against a budget of "keep the worst of the four inside ~10,000 elements". An inline row costs exactly what a fetched row costs.
  2. The ruling's text carves out no provider. It says a non-grid visualisation "is allowed to fetch the whole filtered result set … but the fetch carries a platform-level hard ceiling." The word is fetch, but that is the mechanism available at the time — all four views fetched — not a stated exemption.
  3. ⭐ The direction of this change is toward the ruling's stated hazard, not away from it. a′'s own words: "The dangerous direction is silent truncation: a cut-off schedule still looks like a schedule, so the ceiling must never be crossed quietly." An uncapped inline set of 3,000 rows is not silent truncation — but capping it with the loud footnote is precisely the remedy the ruling prescribes. Nothing about this makes a quiet cut-off more likely.
  4. Triage independently ruled it. objectui#8769's triage says "Apply filter / sort / the ceiling on the inline path too" — the ceiling named explicitly, not inferred by this PR.

⇒ this seat reads the change as applying ruling a′ to a path that escaped it, not as re-scoping the ruling. Application is this lane's; re-scoping would not be.

⚠️ Flagged upward anyway, because a deliberate pin was written the other way. Whoever wrote that case in PR #7391 may have meant it as a carve-out. The dev is right that it is one isolated hunk, revertible without touching the filter/sort repair — and the worst case of being wrong is conservative and visible (an inline gantt draws 2,000 of 3,000 rows with a note saying so), not silent. That asymmetry is why this is approved-and-flagged rather than held. ⭐ The dev flagged it rather than quietly shipping it, which is what made the check possible.

⭐ ZONE 2 A resolved NEGATIVE, and it was the item that mattered most

I handed this over as the live hazard: PR objectui#9049 is making convertFiltersToAST throw, so if this repair routed schema.filter through core's converter, an inline chart with such a filter would go from "silently draws everything" to "throws at render."

Measured, by import graph rather than by assumption: the repair does not reach that converter. ValueDataSource's matcher is local — its only imports are @object-ui/types, @objectstack/spec/data and its own batchTransaction — while convertFiltersToAST is reached only by @object-ui/data-objectstack on the wire path. ⇒ an inline chart with a refused comparand renders zero rows plus one console line, matching the adapter's own declared refusal shape, and it is pinned from the outside rather than argued. ⛔ No merge-queue surprise.

The other assumptions

  • ⭐ C resolved NOT independent, which I had listed as a live possibility and would not have caught myself. Filter first, ceiling second — matching the fetching path where $filter and $top travel in one query — and asserted observably by a case that is over the ceiling before filtering and under it after. That single case is what makes the ordering a measurement rather than a claim.
  • D resolved TWO spellings, not one: the ruled ladder also wraps staticData into a value config. Both pinned. My brief assumed one.
  • B held (one call site each, verified by symbol). F held (nothing warned — the card's "silent" framing was accurate). E treated as stale throughout; no line citation in the diff.
  • ⭐ The refusal exit never became available, for the right reason. resolveDataSource already answers provider: 'value' with ValueDataSource, which implements $filter, $orderby, $skip, $top and $select over its own array. ⇒ there was no capability to refuse, so ZONE 1 Add automated testing infrastructure and CI/CD workflows #2's second exit was not a judgement call that got skipped — it was measured out of existence. objectui#8513's combinators were neither needed nor touched.

Verification hygiene worth keeping

  • Two gates declared NOT MEASURED in their own words rather than counted as passes: check:readme-exports ("the population COLLAPSED, this run proves nothing") and check:sdui-registration-pins (exit 2, "has measured nothing"). Both need a full-repo build, which is CI's. ⇒ lane fact ㉛ applied correctly, unprompted.
  • The new pin is proven inside the typechecked set the hard way — its first tsc -p tsconfig.test.json run failed with a TS7006 in that file, which demonstrates the file is read rather than asserting it.
  • Ablation predicted 8 red / 4 green in writing before running; measured exactly that, with on-disk mutation and restore both proven by blob hash.
  • ✅ Attribution checked by this seat: the commit carries the model-free trailers, and a probe over the whole diff for any model identifier returns zero with a lit control. The footer question the report raises is moot — the injected block names no model.

Verdict

Approved — land on green. Successor objectui#9061 (ObjectCalendar and ObjectMap carry the same hand-copied short-circuit) is correctly a card and correctly scoped to the two that are literal copies, with the instruction to re-measure per renderer rather than transplant.

PM seat · domain:ui @ objectui · seat post objectui#5560 · in-seat review of record, default tier


Generated by Claude Code

@os-steve
os-steve marked this pull request as ready for review September 10, 2026 21:48
@os-steve
os-steve added this pull request to the merge queue Sep 10, 2026
@github-merge-queue
github-merge-queue Bot removed this pull request from the merge queue due to failed status checks Sep 10, 2026
@os-steve
os-steve added this pull request to the merge queue Sep 10, 2026
Merged via the queue into main with commit a1c00e3 Sep 10, 2026
37 checks passed
@os-steve
os-steve deleted the claude/issue-8769-gantt-inline-filter-sort-ceiling branch September 10, 2026 22:59
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentation Improvements or additions to documentation plugin tests

Projects

None yet

2 participants