Skip to content

fix(components,plugin-grid,types): withhold a masked grid field's raw value from copy, tooltip, inline edit, the client export, the mobile card and group headers - #10643

Merged
objectstack-fleet[bot] merged 13 commits into
mainfrom
claude/issue-10583-masked-cell-copy
Sep 25, 2026
Merged

objectstack-fleet[bot] merged 13 commits into
mainfrom
claude/issue-10583-masked-cell-copy

Conversation

@objectstack-fleet

@objectstack-fleet objectstack-fleet Bot commented Sep 25, 2026 •

Copy link
Copy Markdown
Contributor

Fixes #10583
Clause-②: yes — TableColumn (published @object-ui/types) gains a declared key, and the data table's Ctrl+C refuses a masked cell.

What changed

The house shape is the detail page's (objectui#8440, option A: no copy at all, never the bullets), with isMaskedFieldType() (objectui#8686) as the one authority. components cannot import @object-ui/fields, so the rule is not restated in the table: the producer asks, and the table obeys a flag.

A masked column's raw value is withheld on these paths:

path owner since
Ctrl+C / Cmd+C on the cell (writes nothing; preventDefault() kept) data-table round 1
the cell's title tooltip data-table round 1
the table's built-in CSV export (the column is left out) data-table round 1
inline edit: no edit mode on Enter, click or double-click; the cell no longer reads as editable data-table round 2
the grid's client export, used when the data source has no exportDownload: every masked field of the grid's object is left out of the CSV columns and out of every JSON record ObjectGrid round 2
the mobile card: a masked title column draws through its cell; a masked field is never classified by name into the raw amount / stage / date / percent rows ObjectGrid round 2
grouping (once the object schema has loaded): a masked field is refused as a grouping key. The entry is ignored, the other levels still group, and a console warning names the field ObjectGrid round 3

Not covered, and stated in the docs, the JSDoc, the zod describe(), the README and the changeset where each applies:

  • The flag withholds; it does not draw. The mask comes from the producer's cell renderer, and the table draws a column with no cell as its value.
  • The table's client-side search and sort still run over the raw values, and a masked column's width is still sized from the raw value's length (objectui#10657, which folded objectui#10658).
  • The server-streamed export (exportDownload) sends the masked columns as before and relies on the server's masking.
  • The client JSON export writes an expanded lookup record whole, so a credential field of the related object is not pruned. The same holds for the table's CSV export of a lookup column.
  • Other producers of data-table columns (RelatedList, ObjectDataTable) do not set the flag yet (objectui#10657).
  • On the host-fetched path (rows handed down as data, as ListView and ObjectView do), the grid's guards and the cell's own mask depend on the object schema, which the grid fetches after first paint. Until it arrives, and for good if that read fails (the grid swallows the failure and keeps its heuristic column types), an untyped view column over a password / secret field draws and hands out the raw value (objectui#10657, which folded objectui#10706).

By package

  • @object-ui/types:
    • New declared key TableColumn.masked?: boolean.
    • It is mirrored as z.boolean() on TableColumnSchema.
    • It is tombstoned on StaticTableColumn / StaticTableColumnSchema under the lockstep rule.
    • Its JSDoc and describe() list the withheld paths and the uncovered ones.
  • @object-ui/plugin-grid:
    • The rule lives in one helper, isMaskedGridColumn(columnType, objectFieldType) in src/maskedColumn.ts. It is the narrow-only UNION, in the same shape as isMaskedDetailFieldType.
    • The emit seam stamps masked: true from it, BEFORE the normalizeTableColumnType fold that drops password / secret.
    • handleExport's client fallback asks it per key: per column for the CSV, and per record key for the JSON, which includes fields that are not columns.
    • The card view asks it for the title row and for the name-based classifier.
    • Grouping asks it for every grouping.fields entry. It uses the view column's type and the object-declared type, the same pair the group-label formatter reads. A masked entry is dropped before useGroupedData, and one [ObjectUI] ObjectGrid grouping: warning names it. On the unmasked path the authored config reaches useGroupedData unchanged.
  • @object-ui/components data-table.tsx:
    • handleCellKeyDown writes nothing for a masked cell.
    • The title gate reads !col.masked.
    • handleExport filters masked columns.
    • startEdit refuses a masked column. This is the one door that Enter, click and double-click all pass through, so it covers JSON-authored tables too.
    • The render-time isEditable reads !col.masked, so a masked cell shows no edit cursor and no longer swallows the row's click.
  • Docs: content/docs/components/complex/data-table.mdx adds masked to the TableColumn block and a "Masked columns" section. packages/plugin-grid/README.md gains one paragraph under "Cell appearance". The docs frontmatter is byte-identical: the md5 of lines 1-4 is d069943e… before and after.
  • Changeset .changeset/10583-masked-cell-copy.md: '@object-ui/types': minor, '@object-ui/components': patch, '@object-ui/plugin-grid': patch.

Decisions, and what they rest on

  • Copy writes nothing, and preventDefault() stays. I measured this in Chromium (the preinstalled /opt/pw-browsers/chromium). The page was a focused td holding ••••••, and the clipboard was seeded with SENTINEL. With no selection, both options leave SENTINEL. With the mask selected, preventDefault leaves SENTINEL, but the browser default copies the bullets. objectui#8440's ruling refused that payload.
  • The CSV export leaves the column out; it does not blank it. A column of empty strings would claim the records hold nothing, and re-importing it would write that.
  • One stamp site. Measured: every path that writes type reaches the emit-seam .map. The client export and the card view read generateColumns() drafts, which run before the seam, so they ask isMaskedGridColumn directly. The rule is the same and is not restated.
  • The inline-edit refusal belongs to the table, not only to isFieldInlineEditable. That gate reads only the object-declared type. The flag's union also masks a view-authored type: 'password' over an object text field, and a JSON-authored masked: true. On base, only the view-typed case leaked (measured: red at c2d86599b below). The key did not exist there, so no JSON-authored case could leak.
  • Grouping by a masked field: refused, not masked. I measured both options against the reviewer's three-record probe, where two records share the credential. On fc874c1e2, grouping by api_key built 2 groups, one per distinct raw value. Masking the header label would still leave those buckets: they show which records share a credential, and the group order follows the raw value. Only refusing the key closes both. The refusal follows usableGroupingFields' existing contract: one bad entry is dropped, and the rest still group. It uses the grid's existing diagnostic channel, a console warning.
  • Expanded lookup records in the client JSON export: stated, not closed. I measured whether the related object's field types are in hand without a new fetch. ObjectGrid calls getObjectSchema only for its own objectName, once in the inline-data branch and once in the fetch path. DataSource exposes no synchronous schema cache, only the async getObjectSchema. The main schema's lookup field carries reference_to, but not the related object's field types. So pruning would need a new fetch, which the order rules out. It is stated under "Not covered".

Premise (checked against origin/main c2d86599b)

  • Reproduced on base: ObjectGrid with a password field. Ctrl+C on the masked td wrote RAW-PASSWORD-10583. The text control copied Row one.
  • The card's rawInDom: false was a reading of the text only. The data-table cell wrapper carried title={String(cellValue)}, so the raw credential was in the DOM and showed on hover. Leg A3 shows it.

Evidence: red, then green

Pins:

  • packages/plugin-grid/src/__tests__/maskedCellCopyRefusal-10583.test.tsx (6): the keyboard copy and the tooltip, over three column-emit shapes.
  • packages/plugin-grid/src/__tests__/maskedColumnSurfaces-10583.test.tsx (9): inline edit, the client CSV and JSON exports with a no-masked-field byte-identity control, the mobile card at 375px, and grouping. The grouping pins are a text-grouped control, a refused password grouping, and a mixed grouping where the masked level is dropped while the other level still groups.
  • packages/components/src/renderers/complex/__tests__/data-table-masked-column-10583.test.tsx (10): copy, tooltip, CSV, and inline edit in single-click and double-click modes, with flag-absent controls and a zod survival pin.

Every absence carries a control in the same mounted tree.

run result
round 1, leg 0: implementation at base c2d86599b 11 failed / 2 passed (13 round-1 pins); the 2 that pass are the flag-absent controls
round 2, red leg: data-table.tsx + ObjectGrid.tsx at round-1 head 6bad2d4ff, all 22 pins 7 failed / 15 passed (22). Failing: both grid inline-edit cases, CSV ('Name,Notes,API Key,Token,Vault Key,PIN'), JSON (records carried the masked keys), the mobile card (RAW-PASSWORD-10583 in the card), and both data-table inline-edit cases
round 3, red leg: ObjectGrid.tsx at round-2 head fc874c1e2, all 25 pins 2 failed / 23 passed (25): no group was built on the masked field: … got 2, and the category level still groups: expected 5 to be 2. The text-grouped control passes
round 3, red at base: data-table.tsx + ObjectGrid.tsx at c2d86599b, maskedColumnSurfaces (9) 7 failed / 2 passed (9): both view-typed-password inline-edit cases, CSV, JSON, the mobile card, and both grouping cases. The 2 passing are the export byte-identity control and the grouping control
head 965716a72 (after merging origin/main 0c3f70aae) 25 passed (25)
head ef874bd8b (after merging origin/main ed8251189) 25 passed (25)
final head 4d341308a 25 passed (25); ablations G1 and G2 re-run on it, each 2 of 25 red, git diff HEAD empty after

Per-hunk ablations were done with ablation-replace.mjs (anchor hit 1 → 0, blob changed). The round-1 legs ran against the 13 round-1 pins, the round-2 legs against 22, and the round-3 legs against all 25. After each one, git diff HEAD was empty.

leg mutation red (of the pins run)
A1 (r1) table copy refusal removed 8 of 13
A2 (r1) producer stamp → draft 6 of 13 (grid only)
A3 (r1) !col.masked removed from title 7 of 13
A4 (r1) table CSV filter removed 1 of 13
A5 (r1) union narrowed to the column type 2 of 13 (Vault Key (text over secret))
A1 (r2) startEdit guard removed 4 of 22: both data-table and both grid inline-edit cases
A2 (r2) !col.masked removed from render-time isEditable 1 of 22: no edit cursor on a masked cell
B1 (r2) client CSV filter removed 1 of 22: the CSV pin
B2 (r2) client JSON filter → () => true 1 of 22: the JSON pin
C1 (r2) card classifier guard removed 1 of 22: RAW-SECRET-VALUE-10583 is nowhere in the card
C2 (r2) card title route → false 1 of 22: RAW-PASSWORD-10583 is nowhere in the card
G1 (r3) grouping detection → false && … 2 of 25: both refusal pins (the control stays green)
G2 (r3) detected but not applied: useGroupedData gets schema.grouping 2 of 25: the same two pins. The warning alone is not the fix

Gates (objectui families derived by hand from package.json and .github/workflows/)

Verdict lines are quoted. The last code head is 4d341308a. It changes only ObjectGrid.tsx from ef874bd8b (the typed column lookup for the grouping refusal). So plugin-grid and every check that reads that file were re-run on 4d341308a. The types and components suites were run on ef874bd8b: this PR's files in those packages are byte-identical at 4d341308a. Rounds 4 to 6 (244498f70, fe124b769, 2dcc28aae, d45109cb0) change wording only: the docs, the README, the changeset, and comments in data-display.ts, maskedColumn.ts and ObjectGrid.tsx. On d45109cb0 I re-ran turbo run type-check for types and plugin-grid (Tasks: 16 successful, 16 total, 0 error TS), the 25 pins (Tests 25 passed (25)), check:control-bytes (✅ OK), check:new-line-citations (VERDICT … 0 new citation(s)), check-changeset-presence (✅ 9 source file(s) of 3 released package(s) changed …), check-doc-links (Links are valid across 17 scan roots.) and, on fe124b769, check:doc-fences (✅).

gate head verdict
turbo run type-check --filter=@object-ui/types --filter=@object-ui/components --filter=@object-ui/plugin-grid (dependency closure built via ^build) 4d341308a Tasks: 17 successful, 17 total · exit 0 · 0 error TS
turbo run build --filter=@object-ui/plugin-grid (the dts emit, which fails on type errors) 4d341308a Tasks: 14 successful, 14 total · exit 0 · 0 error TS
the 25 pins 4d341308a Tests 25 passed (25)
pnpm exec vitest run packages/plugin-grid/ (under the verify lock, VERDICT command-exit 0) 4d341308a Test Files 160 passed (160) · Tests 1514 passed (1514)
pnpm exec vitest run packages/types/ ef874bd8b Test Files 239 passed (239) · Tests 5270 passed (5270)
pnpm exec vitest run packages/components/ ef874bd8b Test Files 309 passed | 1 skipped (310) · Tests 3041 passed | 17 skipped (3058)
reverse check (round 1): a probe TableColumn literal with masked: 'yes', read through the BUILT @object-ui/types .d.ts round 1 TS2322: Type 'string' is not assignable to type 'boolean | undefined', the only error
check-changeset-presence 4d341308a ✅ 9 source file(s) of 3 released package(s) changed, and this change declares 1 changeset(s)
check-changeset-no-major · check-changeset-overwrite ef874bd8b ✅ No changeset declares a major bump · ✅ No pre-existing changeset was modified or deleted
check-changeset-claims (report-only) ef874bd8b the self-contradiction section is ✅. 29 pending bodies name a touched file, none of them new in ed8251189; the five bodies of the ed8251189 range's commits that touched this PR's source files (objectui#10580, objectui#10625 ×3, objectui#9002) were read separately; none is falsified
check:new-line-citations 4d341308a VERDICT new-cross-file-line-citations: 0 new citation(s)
check:control-bytes 4d341308a ✅ check-control-bytes: OK
check:doc-types · check:prompt-keys · check:doc-fences · check-doc-links ef874bd8b all exit 0 (✅ Every documented component type is registered. · Links are valid across 17 scan roots.)
check:unreferenced-sources 4d341308a OK Every shipped source file in every covered package is reachable.
check:vi-mock-specifiers · check:vi-mock-inherit · check:test-path-roots ef874bd8b all exit 0
eslint on the 9 changed .ts/.tsx files (package mode, --format json) 4d341308a 9 files, 0 errors. The per-rule warning counts on the 3 edited source files equal the merge-base's. Round 3 first added one no-explicit-any; the typed lookup in 4d341308a removed it again. The 3 new test files carry 8 no-explicit-any warnings (schema casts, as sibling tests use)
check-governed-queue-guard --test (12 PR paths) ef874bd8b ✅ NOT GOVERNED
check:readme-exports — NOT MEASURED (reason: its workflow builds every package first). The diff adds no import line to any README
published-text overclaim grep (the round-2 claim class, "hands its raw value to no reader" and its variants) over the PR diff 4d341308a 0 hits

The lint row is a narrowing, not the repo-wide pnpm lint, which CI owns. It covers exactly the changed files. eslint.config.js sets no parserOptions.project / projectService, so linting is not type-aware and the diff cannot move a verdict in an untouched file.

Acceptance notes

  • Two other producers of data-table columns are the same class, and neither is touched here (objectui#10657). RelatedList.normalizeColumn (@object-ui/plugin-detail) and ObjectDataTable.normalizeColumns (@object-ui/plugin-dashboard) draw a password cell as the mask through getCellRenderer, but never set masked. I measured this with a throwaway probe on bcec471b8: both drew ••••••, the raw value was in innerHTML but not in the text, and Ctrl+C on the masked td wrote RAW-PROBE-10583.
  • Not covered: the data table's client search, sort and auto width still read raw values (objectui#10657, which folded objectui#10658). Once the object schema has loaded, a view that authors type: 'text' over a secret field keeps the refusal, but its cell draws the value as the text the author asked for. On the mobile card that cell is the one the card draws.
  • useCellClipboard (plugin-grid, zero in-repo callers) copies raw values. It is already recorded in objectui#10568's notes.
  • Overlap:
    • The draft objectui#10278 edits ObjectGrid.tsx and also packages/plugin-grid/README.md. At its head eab4c8e52, git merge-tree with 4d341308a (the last code head) conflicts in ObjectGrid.tsx. It also conflicts with origin/main on its own, so the rebase it needs is not created by this PR. This PR touches the emit-seam .map, the grouping config handed to useGroupedData, handleExport's client fallback, the card view's classifier and title row, and one import line.
    • objectui#10635 and objectui#10656 are MERGED (9fbbb17a2, 0c3f70aae), and both are already inside this head through the two main merges below. Two open PRs touch these files, both drafts: objectui#10278 above, and objectui#10710 (objectui#10685), which edits packages/types/src/data-display.ts in the DrillDownConfig / ObjectMetricDrillDownConfig region (from base line 2249), while this PR's hunks there are TableColumn (705-751) and StaticTableColumn (811-818); git merge-tree of d45109cb0 with its head a1217b515 is clean.
    • git merge-tree of d45109cb0 against a fresh origin/main 93a558555 was clean, and main has not moved in this PR's files since ed8251189.
    • origin/main moved in this PR's files twice during round 3, so it was merged with a merge commit each time. The first move was ObjectGrid.tsx at 0c3f70aae, merged as 965716a72. The second was data-table.tsx, ObjectGrid.tsx and data-display.ts at ed8251189, which includes objectui#10625's inline date editors, merged as ef874bd8b. Both merges were clean. Type-check, the plugin-grid build and the pins were re-run on each merge head before its push, and the package suites after it. objectui#10670 (plugin-form only) is in ed8251189 and touches no file of this PR.
  • Pending changesets: git grep -l -i "clipboard\|ctrl+c" origin/main -- .changeset finds 8440, 8686, 8395, 8596 and 6278. None of their sentences is falsified. check-changeset-claims flagged the same 29 pending bodies. I read them again for export, card and edit claims, and none is moved.

Implemented by the domain:ui seat 2 dispatch, session https://claude.ai/code/session_014mXUNuFomfj24w7s1pZzhN.


Generated by Claude Code

…alue to nobody

Ctrl+C / Cmd+C on a focused password or secret cell wrote
String(row[accessorKey]) to the clipboard while the cell drew the mask,
and the cell wrapper carried the raw value as its title tooltip.

- types: TableColumn declares `masked?: boolean`, mirrored as
  z.boolean() on TableColumnSchema and tombstoned on the static table
  column (lockstep rule).
- plugin-grid: the emit seam stamps `masked: true` from
  isMaskedFieldType() (via isMaskedGridColumn), as the narrow-only union
  of the column's type and the object-declared type, before the type fold
  erases it.
- components: data-table obeys the flag - the keyboard copy writes
  nothing, no title tooltip is drawn, and the CSV export omits the column.

Claude-Session: https://claude.ai/code/session_014mXUNuFomfj24w7s1pZzhN
Co-authored-by: Claude <noreply@anthropic.com>
…for the masked-cell refusal

Claude-Session: https://claude.ai/code/session_014mXUNuFomfj24w7s1pZzhN
Co-authored-by: Claude <noreply@anthropic.com>
@github-actions

github-actions Bot commented Sep 25, 2026 •

Copy link
Copy Markdown
Contributor

changeset-claim-re-read

⚠️ 29 pending changeset(s) describe a file this change touches

Their bodies publish verbatim into the CHANGELOG at the next release, so this is a request to re-read them against your diff — addressed here because you are the one seat that can answer it without re-deriving anything.

⛔ Nothing here blocks, and nothing here is a verdict on your change. This gate exits 0, is not a required context, and judges name resolution, never meaning: it asked whether a pending body names a file you touched. "Is this sentence still true?" is the one question it will not answer, and the one you are being asked to answer.

.changeset/4730-retire-dead-locale-key-batch.md

  • names ObjectGrid.tsx → packages/plugin-grid/src/ObjectGrid.tsx — edited by this change

    • Superseded twin vocabularies. cellRender.* and rowAction.* duplicated a grid.* vocabulary that won. RowActionMenu.tsx is fully i18n-wired and reads grid.openMenu / grid.edit / grid.delete; ObjectGrid.tsx reads grid.empty / grid.yes / grid.no / grid.systemFields. The twins had no reader on either side. - Labels that outlived their control. calendar.agenda labelled a view mode b55a34647 retired from CalendarViewMode (now 'month' | 'week' | 'day'). home.quickActions.createApp*, layout.systemNav.createApp, actionDialog.defaultActionTitle / .ok and grid.bulk.selectPlaceholder sit in namespaces whose consumers are live and wired but demonstrably read other siblings. - Surfaces that left the product. map.* is the strongest form: @object-ui/plugin-map declares no @object-ui/i18n dependency and contains no t() call at all, so it cannot consume a locale string. home.stats.* and recordDetail.viewersTooltip name surfaces nothing renders.

.changeset/5453-retire-grid-column-wrap-forward.md

  • names data-table.tsx → packages/components/src/renderers/complex/data-table.tsx — edited by this change

    generateColumns() copied a per-column wrap onto every column object handed to data-table, and packages/components' data-table.tsx never read it. Measured on the current ref rather than inherited from the card, with comments stripped so prose mentions cannot be counted as reads: a column-level wrap scores 0, against accessorKey 34, align 5, header 4, className 4, width 8 and fitContent 2 in the same query shape. Those sibling counts are the positive control — the search style does find the keys that are genuinely consumed, so the zero is a measurement and not a mis-aimed grep. The raw string wrap does occur in that file; every occurrence is flex-wrap, whitespace-nowrap, or a variable named wrapper.

.changeset/5853-tablecolumn-type-canonical-union.md

  • names data-table.tsx → packages/components/src/renderers/complex/data-table.tsx — edited by this change

    @object-ui/types exports TABLE_COLUMN_TYPES (the canonical tuple — the single declaration the zod mirror builds its enum from, so the two cannot drift), the TableColumnType union, and normalizeTableColumnType() for producers. The as any cast in data-table.tsx is deleted and the read is typed, so re-introducing an undeclared spelling is a tsc error rather than a silent widening.

.changeset/5926-empty-action-visible-when.md

  • names data-display.zod.ts → packages/types/src/zod/data-display.zod.ts — edited by this change

    emptyAction was the one authored-node exception in the tree. The empty-state CTA slot resolved the registry directly — ComponentRegistry.get(node.type) — and mounted the result itself, so the node never passed through SchemaRenderer and its visibleWhen was never evaluated. @objectstack/spec accepts the key (SchemaNodeSchema carries visibleWhen, and data-display.zod.ts types emptyAction as a SchemaNode), so an author wrote a gate, the platform took it, and nothing enforced it — declared-not-enforced, the same class objectui#5401 / Bind record into node-level visibleWhen, and stop visible swallowing it #5505 closed for record:alert, one level down.

  • names data-table.tsx → packages/components/src/renderers/complex/data-table.tsx — edited by this change

    The fix is a route, not a new check: no visibleWhen test was added to data-table.tsx. A local check on this slot would have been a fourth evaluator, which is the drift page:tabs' item-level predicate already records on this card. The slot now mounts its authored node exactly the way the empty renderer's action slot always has.

.changeset/5993-button-shared-icon-resolver.md

.changeset/6175-column-state-persistence.md

  • names data-table.tsx → packages/components/src/renderers/complex/data-table.tsx — edited by this change

    • @object-ui/components — DataTableSchema has declared onColumnResize?: (columnKey, width) => void all along, and data-table.tsx invoked it nowhere: the resize drag updated the table's local columnWidths state and stopped there. It now reports the settled width once, at mouseup. Once, deliberately — the host turns this callback into a write to shared view config, so a per-mousemove callback would be a write storm. - @object-ui/plugin-grid — ObjectGrid emitted onColumnReorder (singular) while the renderer invokes the near-duplicate onColumnsReorder (with the s), a different declared key with a different signature. The producer now emits the spelling the renderer actually invokes, mapping the reported TableColumn[] to the accessorKey order columnState stores.

.changeset/6349-types-internal-name-collisions-batch-1.md

  • names data-display.ts → packages/types/src/data-display.ts — edited by this change

    BreadcrumbItem / BreadcrumbSchema — re-pointed, because one copy was stale. Both were declared in data-display.ts and in navigation.ts. The data-display pair was not a second dialect but a strict SUBSET: no key declared differently on either side, and missing BreadcrumbItem.icon / onClick / siblings and BreadcrumbSchema.maxItems. Everything that reads a breadcrumb was already on the navigation declaration — registry.ts maps the 'breadcrumb' component type to it, src/index.ts re-exports it under the bare names, zod/navigation.zod.ts mirrors it (icon, onClick, siblings, maxItems included), the ui:breadcrumb renderer consumes it, and the component's own documentation page documents icon and maxItems. data-display.ts now re-exports the one authority.

.changeset/6373-datatable-emit-boundary.md

  • names data-table.tsx → packages/components/src/renderers/complex/data-table.tsx — edited by this change

    The measured read set of the consumer (data-table.tsx, comments stripped) contains none of the six, so all six retire from the emit rather than being declared — declaring a key nothing reads is the same declared != enforced defect facing the other way. Rendering is unchanged because none of those keys was the live path for its own value: the FieldMeta the cell closure captures is what this widget's type-aware rendering has always read, and it is untouched. Authored spellings still pass through, so a column the author wrote as { format: '$0,0' } keeps its format exactly as before.

.changeset/6424-tablecolumn-declares-headericon.md

  • names data-table.tsx → packages/components/src/renderers/complex/data-table.tsx — edited by this change

    Scope of the enforcement claim, measured: the declaration, the parse road, and the renderer's behaviour now agree; the renderer's internal column reads remain any-mediated (the col: any normalization in data-table.tsx) — a standing instrument gap, not closed here.

.changeset/6597-retire-fieldmeta-referenceto.md

  • names plugin-grid/src/ObjectGrid.tsx → packages/plugin-grid/src/ObjectGrid.tsx — edited by this change

    No authoring story survived the search either. ObjectGrid's own relational-meta pass-through (applyRelationalMeta, plugin-grid/src/ObjectGrid.tsx) copies reference_to / reference / display_field / etc. from the SCHEMA field def only, at all three of its call sites — never from an authored column override. No doc, example, or fixture in this repo shows a table column pinning a lookup's target away from what its schema field already says. Under the maintainer's standing startup-stage rule (2026-08-27: deprecated/alias spellings retire immediately, no transition windows), no measured demand selects withdraw.

.changeset/6650-listcolumn-wrap-implemented.md

  • names packages/plugin-grid/README.md → packages/plugin-grid/README.md — edited by this change

    @objectstack/spec declares ListColumn.wrap and describes it to authors as "Allow text wrapping", and packages/plugin-grid/README.md shows it in its authored-column example. No renderer anywhere implemented it. Long cell text stayed clipped to one line, with no error, no warning and no feedback of any kind — a promise made at authoring time and silently broken at render time.

.changeset/6881-retire-data-table-toolbar.md

  • names data-display.ts → packages/types/src/data-display.ts — edited by this change

    What was measured. The key was declared on both published faces — data-display.ts (toolbar?: SchemaNode[], "Table toolbar actions/content") and the Zod mirror (SchemaNode | SchemaNode[]) — documented, mirrored, and read by NOTHING: data-table.tsx, the registered renderer for type: 'data-table', contains the word only in two prose comments and never reads schema.toolbar. The sibling emptyAction slot on the same interface IS mounted through SchemaRenderer, so the census zero is a reading, not a blind query. An author who wrote a toolbar got a green document and a blank result, with no signal anywhere that said so — the declared-vs-enforced failure mode that is worst for AI-authored metadata, which has nothing but the declaration to go on.

  • names data-table.tsx → packages/components/src/renderers/complex/data-table.tsx — edited by this change

    What was measured. The key was declared on both published faces — data-display.ts (toolbar?: SchemaNode[], "Table toolbar actions/content") and the Zod mirror (SchemaNode | SchemaNode[]) — documented, mirrored, and read by NOTHING: data-table.tsx, the registered renderer for type: 'data-table', contains the word only in two prose comments and never reads schema.toolbar. The sibling emptyAction slot on the same interface IS mounted through SchemaRenderer, so the census zero is a reading, not a blind query. An author who wrote a toolbar got a green document and a blank result, with no signal anywhere that said so — the declared-vs-enforced failure mode that is worst for AI-authored metadata, which has nothing but the declaration to go on.

.changeset/6882-datatable-declare-render-cell-editor-and-cell-classname.md

  • names data-table.tsx → packages/components/src/renderers/complex/data-table.tsx — edited by this change

    data-table has read both keys on its production path all along — renderCellEditor through a (schema as any) cast, cellClassName by destructuring it into the class of its three utility cells (the selection checkbox, the row number, the row actions). Neither was declared, so authoring either one was unchecked: a misspelling produced no error and no widget, and no editor completion offered them. DataTableSchema now declares both, and the cast in data-table.tsx is gone rather than replaced.

  • names content/docs/components/complex/data-table.mdx → content/docs/components/complex/data-table.mdx — edited by this change

    ⚠️ What the schema-level cellClassName actually styles. It is NOT the table-level twin of the per-column key: the two reach disjoint cells. Measured on the render, the schema-level key is folded into the utility cells only — the selection-checkbox cell, the row-number cell and the row-actions cell — while every data cell folds TableColumn.cellClassName and nothing else. Row density is therefore a pair of settings (ObjectGrid sets both), and the schema-level key alone leaves data cells at the primitive's default p-4. The docblock, the zod describe and content/docs/components/complex/data-table.mdx all say this now.

.changeset/6909-fieldeditwidget-dom-pass-through.md

  • names data-table.tsx → packages/components/src/renderers/complex/data-table.tsx — edited by this change

    Also corrects a comment in @object-ui/components' data-table.tsx that this change falsifies. It justified the injected editor's document-level pointerdown listener partly with "FieldEditWidget forwards autoFocus and nothing else out of the DOM block, so a host handler could not reach the control through it even if one were passed" — no longer true. The listener is still load-bearing for the other half of that reason, which is untouched: the renderCellEditor context object has nowhere to put an onBlur in the first place. Comment only; no behaviour change in that package.

.changeset/6940-rowactions-boolean-mirror.md

  • names zod/data-display.zod.ts → packages/types/src/zod/data-display.zod.ts — edited by this change

    The hand-written zod mirror in zod/data-display.zod.ts declared rowActions: z.array(z.any()).optional(). Every other face of the same key says boolean: the TS declaration it mirrors (rowActions?: boolean), the renderer's destructuring default (rowActions = false), its two truthiness gates and two colSpan arithmetic sites, the registered authoring input ({ type: 'boolean', label: 'Show Row Actions' }), defaultProps: { rowActions: true }, and the renderer's own docblock example, which authors "rowActions": true. The mirror was the single outlier — and the published one, so safeValidateSchema refused the exact spelling the component's documentation, defaults and authoring UI all teach. Two shipped examples/schema-catalog entries (user-table.json, full-featured-table.json) failed validation for this and no other reason; both now validate unchanged.

.changeset/6951-tree-view-data-retired.md

  • names data-display.ts → packages/types/src/data-display.ts — edited by this change

    Two published faces, one retirement — and why a tombstone, not a deletion. The TypeScript interface TreeViewSchema (@object-ui/types, data-display.ts) declares data?: never; the Zod mirror TreeViewSchema (@object-ui/types/zod, data-display.zod.ts) declares data as a retirementTombstone(). BaseSchema already declares data?: any (z.any().optional() on the mirror), so DELETING the member would not have refused the key — it would have ADMITTED it, unvalidated, through the base member, and the renderer would have drawn an empty tree. The tombstone on the extended schema shadows the base member on both faces; the pin measures the base accepting the very document the extended schema refuses.

  • names data-display.zod.ts → packages/types/src/zod/data-display.zod.ts — edited by this change

    Two published faces, one retirement — and why a tombstone, not a deletion. The TypeScript interface TreeViewSchema (@object-ui/types, data-display.ts) declares data?: never; the Zod mirror TreeViewSchema (@object-ui/types/zod, data-display.zod.ts) declares data as a retirementTombstone(). BaseSchema already declares data?: any (z.any().optional() on the mirror), so DELETING the member would not have refused the key — it would have ADMITTED it, unvalidated, through the base member, and the renderer would have drawn an empty tree. The tombstone on the extended schema shadows the base member on both faces; the pin measures the base accepting the very document the extended schema refuses.

.changeset/6972-markdown-inert-keys-retired.md

  • names data-display.ts → packages/types/src/data-display.ts — edited by this change

    What was measured, on this branch's base. sanitize was declared ?: boolean with @default true on both published faces — data-display.ts and the Zod mirror — documented, and read by NOTHING. Worse than an ordinary inert key, it implied a switch that does not exist: sanitization is unconditional. rehypePlugins in plugin-markdown/src/MarkdownImpl.tsx is a module-level const array whose last link is [rehypeSanitize, sanitizeSchema], handed to ReactMarkdown as-is — no ternary, no if, no runtime assembly. MarkdownRenderer forwards exactly content and className, and MarkdownImplProps accepts only those two. A repo-wide grep for schema.sanitize over packages/ and apps/ returns nothing, against a control of 20 .tsx files reading schema.content in the same query shape, so the zero is a reading, not a blind query. An author writing sanitize: false believed they turned XSS filtering off; one writing sanitize: true believed they turned it on. Neither was true.

.changeset/7352-drill-down-config-mirror.md

  • names zod/data-display.zod.ts → packages/types/src/zod/data-display.zod.ts — edited by this change

    DrillDownConfigSchema is the zod mirror of DrillDownConfig, and both declarations that carry drillDown reference it — ChartSchema (zod/data-display.zod.ts) and ObjectDataTableSchema (zod/objectql.zod.ts) — so the published validator under @object-ui/types/zod reads the key for the first time (objectui#7352).

.changeset/7694-chart-series-chart-type-alias-refusal.md

  • names zod/data-display.zod.ts → packages/types/src/zod/data-display.zod.ts — edited by this change

    • Before: series: [{ name: 'revenue', chartType: 'line' }] validated green through @object-ui/types/zod (safeValidateSchema, objectui check / objectui validate, any pipeline that keeps parse()'s output) — and the key was gone from the output, so a consumer of the parse result drew that series in the chart's own family, precisely what the author was overriding. On the TypeScript face the key was merely an excess property on a fresh literal; a widened object carrying it assigned structurally. - After: the same document REFUSES at series[i].chartType (issue code invalid_type) with one message on both channels — the parse-time issue and the .describe() metadata: Unrecognized key(s) on this chart series: \chartType`. Did you mean `chartType` → `type`? …followed by the reason and the remedy. Writetype: 'bar' | 'line' | 'area'. On the TypeScript face ChartDataSeries.chartTypeis a?: nevertombstone, so both the fresh literal and the widened assignment aretsc errors. - **Both written** ({ type: 'bar', chartType: 'line' }) is refused at chartTypealone — the key is not folded ontotypeand no precedence is minted between the two spellings. - **Which documents to scan.** The narrowing does not stop atChartDataSeriesSchema; it reaches every document through the parents that embed it — ChartSchema.series (zod/data-display.zod.ts, z.array(ChartDataSeriesSchema)) and, one level further out, ReportSectionSchema.chart (zod/reports.zod.ts, ChartSchema.optional()). Authors meet it through safeValidateSchema() (zod/index.zod.ts, which parses AnyComponentSchema) and through the CLI's objectui validate command (packages/cli/src/cli.ts). In practice: every chartnode'sseries[], and every report section whose chart` carries one.

.changeset/7722-wrapper-class-five-more.md

  • names data-display.ts → packages/types/src/data-display.ts — edited by this change

    Each of renderers/form/switch.tsx, textarea.tsx, date-picker.tsx, select.tsx and renderers/data-display/list.tsx reads schema.wrapperClass onto its wrapper element, and neither the TypeScript interface (form.ts, data-display.ts) nor the zod mirror (zod/form.zod.ts, zod/data-display.zod.ts) declared the key. The reads compiled through BaseSchema's index signature (objectui#5155) and the values parsed through .passthrough(), admitted unexamined. The same key, on the same class of read, is declared on CheckboxSchema (objectui#6938), FileUploadSchema and FilterBuilderSchema (objectui#6150); these five were left out only because their doc pages never listed it.

  • names zod/data-display.zod.ts → packages/types/src/zod/data-display.zod.ts — edited by this change

    Each of renderers/form/switch.tsx, textarea.tsx, date-picker.tsx, select.tsx and renderers/data-display/list.tsx reads schema.wrapperClass onto its wrapper element, and neither the TypeScript interface (form.ts, data-display.ts) nor the zod mirror (zod/form.zod.ts, zod/data-display.zod.ts) declared the key. The reads compiled through BaseSchema's index signature (objectui#5155) and the values parsed through .passthrough(), admitted unexamined. The same key, on the same class of read, is declared on CheckboxSchema (objectui#6938), FileUploadSchema and FilterBuilderSchema (objectui#6150); these five were left out only because their doc pages never listed it.

.changeset/7762-object-grid-export-options-bare-array-refusal.md

  • names ObjectGrid.tsx → packages/plugin-grid/src/ObjectGrid.tsx — edited by this change

    What was measured, on this branch's base. The mirror declared NO exportOptions member at all, and BaseSchema is .passthrough(), so ObjectGridSchema.safeParse({ type: 'object-grid', objectName: 'accounts', exportOptions: ['csv', 'xlsx'] }) returned success: true with the array back VERBATIM — as did { formats: ['csv', 'pdf'], compression: 'gzip' }. Nothing on the render path parses, and ObjectGrid.tsx reads schema.exportOptions?.formats and only that, so the authored array then lost SILENTLY to the ['csv', 'json'] default: the useEffect that warns about dropped formats reads .formats too and returns early when it is absent, while !!schema.exportOptions kept the export button on screen. An author declared ['csv', 'xlsx'] and got csv/json with no error, no warning and no console line. The two authoring faces disagreed in the direction opposite to objectui#6956's: the TypeScript interface already declared the object form only, so TS refused what zod admitted.

.changeset/7804-data-table-handler-key-arms.md

  • names renderers/complex/data-table.tsx → packages/components/src/renderers/complex/data-table.tsx — edited by this change

    BaseSchema is .passthrough(), so a key no arm declares is not refused — it stops being judged and the value is KEPT. All seven were in that state while renderers/complex/data-table.tsx read and INVOKED each one, so an authored { "type": "data-table", "onRowClick": { "action": "toast" } } parsed GREEN and that action object was handed to a call site expecting a function. Each key is now a named refusal on the mirror (handlerKeyRefusal, the objectui#6124 shape), and the arm's message says why JSON cannot author it and what to write instead.

.changeset/8331-data-table-empty-action-primitive-node.md

  • names packages/types/src/data-display.ts → packages/types/src/data-display.ts — edited by this change

    Behaviour change on a published surface, deliberately. DataTableSchema.emptyAction is declared SchemaNode on both published faces — packages/types/src/data-display.ts and its Zod twin in packages/types/src/zod/data-display.zod.ts — and SchemaNode is BaseSchema | string | number | boolean | null | undefined. The empty-state render path additionally required typeof … === 'object', so an authored emptyAction: 'Create the first record' rendered nothing and reported nothing: declared wider than enforced, failing in the direction that loses the author's content without a diagnostic. A node that renders nothing today therefore starts rendering.

  • names packages/types/src/zod/data-display.zod.ts → packages/types/src/zod/data-display.zod.ts — edited by this change

    Behaviour change on a published surface, deliberately. DataTableSchema.emptyAction is declared SchemaNode on both published faces — packages/types/src/data-display.ts and its Zod twin in packages/types/src/zod/data-display.zod.ts — and SchemaNode is BaseSchema | string | number | boolean | null | undefined. The empty-state render path additionally required typeof … === 'object', so an authored emptyAction: 'Create the first record' rendered nothing and reported nothing: declared wider than enforced, failing in the direction that loses the author's content without a diagnostic. A node that renders nothing today therefore starts rendering.

.changeset/8478-describe-line-addresses.md

.changeset/9256-list-timeline-content-channels.md

  • names packages/types/src/data-display.ts → packages/types/src/data-display.ts — edited by this change

    These two were held out of the previous family-D slice for a SERIAL constraint on packages/types/src/data-display.ts and never for a verdict. Readership was re-derived for both rather than inherited: a TypeScript compiler-API sweep files every .body / .children read under the declared type of its receiver and answers zero for ListSchema and TimelineSchema while its live controls fire. timeline's bare-key owner is any-typed, so it was attributed directly as well — packages/plugin-timeline contains no channel read of any kind.

.changeset/9722-bulk-executor-datasource-face.md

  • names ObjectGrid.tsx → packages/plugin-grid/src/ObjectGrid.tsx — edited by this change

    • ObjectGrid.tsx → RecordDetailPanel: removing it type-checks GREEN. It was paying for nothing — both sides already declare the same DataSource | undefined from @object-ui/types. Removed outright. - ObjectGrid.tsx → BulkActionDialog: removing it reddens, and it was paying for TWO things at once — the optional-vs-required arm (the grid declares dataSource?: DataSource, the dialog demands one) AND the bulk-door contravariance. Only the first still needs erasing, so it is now a non-null assertion: today's runtime behaviour is preserved exactly, and any future drift of the face reddens at that site instead of passing silently.

.changeset/9853-grid-non-positive-page-size.md

  • names ObjectGrid.tsx → packages/plugin-grid/src/ObjectGrid.tsx — edited by this change

    Refuse a non-positive pageSize at all three of ObjectGrid.tsx's read points, instead of giving two different answers for one authored value (objectui#9853).

.changeset/dashboard-stable-empty-rows-4629.md

  • names data-table.tsx → packages/components/src/renderers/complex/data-table.tsx — edited by this change

    Nothing rendered wrong before or after; this is wasted work in the empty window, plus the live react-hooks/exhaustive-deps warning the conditional raised. It is the same module-scope frozen empty data-table.tsx adopted for its own EMPTY_ROWS (objectui#4618), applied to the provider: 'object' siblings.

.changeset/table-renderer-declared-column-contract-5350.md

  • names packages/types/src/data-display.ts → packages/types/src/data-display.ts — edited by this change

    renderers/complex/table.tsx resolved a heading as col.header || col.label and a cell as row[col.accessorKey || col.name]. Neither label nor name is declared on TableColumn, which declares header and accessorKey — both required (packages/types/src/data-display.ts). This was the fourth site of the column-alias family, after data-table, ObjectDataTable and ObjectGrid (objectui#5350).

Read the paragraph, not the line: both false halves of the objectui#8617 claim sat in one paragraph, and correcting either alone would have left it asserting the same wrong thing.

If a claim did go false, correct the body. That is precedented and prose-only, frontmatter untouched; check-changeset-overwrite.mjs will report the correction as its own case 2 ("correcting a declaration on purpose … legitimate"), which is the intended shape — one gate asks for the read, the other records the write.

Not covered, stated so nobody reads this as more: a born-false claim that spells no line address at all (objectui#9495 coordinated one by ORDINAL — "a grep finds that member first" — and deciding that means reading what the sentence means), a claim spelled as a symbol or a package rather than a backticked file name, and a file named ambiguously.

Compared the checked-out tree with f9d772b16 (merge-base with origin/main): 11 file(s) changed outside .changeset/, read against 1540 pending declaration(s) that publish a body (2126 pending in total). · run

@github-actions

Copy link
Copy Markdown
Contributor

✅ Console Performance Budget

Metric Value Budget
Eager closure (gzip, 329 chunks) 3051.1 KB 3104.5 KB
Main entry chunk (gzip) 147.9 KB 350 KB
Entry file index-CY4ZWg97.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.57KB 6.15KB
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) 27.95KB 10.04KB
auth (AuthContext.js) 0.31KB 0.24KB
auth (AuthGuard.js) 2.07KB 1.00KB
auth (AuthProvider.js) 40.17KB 10.58KB
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.39KB 1.21KB
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.52KB 3.45KB
auth (index.js) 3.63KB 1.64KB
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) 27.13KB 7.95KB
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) 547.09KB 130.85KB
core (index.js) 9.22KB 3.71KB
create-plugin (index.js) 27.94KB 9.51KB
data-objectstack (index.js) 223.91KB 62.28KB
fields (index.js) 260.06KB 65.86KB
i18n (LocalizationContext.js) 1.76KB 0.96KB
i18n (builtinAggregateLabels.js) 0.86KB 0.49KB
i18n (currency.js) 2.59KB 1.22KB
i18n (fallbackInterpolation.js) 6.25KB 2.77KB
i18n (i18n.js) 8.87KB 3.64KB
i18n (index.js) 5.24KB 2.27KB
i18n (pickLocalized.js) 9.86KB 3.95KB
i18n (provider.js) 39.40KB 12.91KB
i18n (useDisplayLocale.js) 3.52KB 1.76KB
i18n (useObjectLabel.js) 34.34KB 9.17KB
i18n (useSafeTranslation.js) 5.60KB 2.33KB
layout (index.js) 39.28KB 11.09KB
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) 6.62KB 2.45KB
mobile (useResponsive.js) 0.72KB 0.42KB
mobile (useSpecGesture.js) 5.52KB 2.10KB
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.33KB 3.07KB
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) 16.01KB 3.93KB
plugin-calendar (index.js) 51.40KB 14.61KB
plugin-charts (index.js) 74.94KB 20.89KB
plugin-chatbot (index.js) 198.36KB 47.20KB
plugin-dashboard (index.js) 133.54KB 35.39KB
plugin-designer (index.js) 216.25KB 44.39KB
plugin-detail (index.js) 232.96KB 61.65KB
plugin-editor (index.js) 2.23KB 1.05KB
plugin-form (index.js) 151.18KB 38.74KB
plugin-gantt (index.js) 169.64KB 41.91KB
plugin-grid (index.js) 215.62KB 58.97KB
plugin-kanban (index.js) 48.26KB 15.04KB
plugin-list (index.js) 114.44KB 28.25KB
plugin-map (index.js) 22.42KB 7.38KB
plugin-markdown (index.js) 13.88KB 4.80KB
plugin-report (index.js) 43.55KB 11.99KB
plugin-timeline (index.js) 30.67KB 8.95KB
plugin-tree (index.js) 10.52KB 3.69KB
plugin-view (index.js) 87.84KB 21.96KB
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) 116.21KB 38.14KB
react (data-invalidation.js) 5.05KB 2.08KB
react (index.js) 4.03KB 1.86KB
react (schema-input.js) 4.25KB 2.04KB
react (spec-input.js) 0.20KB 0.18KB
sdui-parser (body-dialect.js) 4.78KB 2.09KB
sdui-parser (codegen.js) 6.58KB 2.74KB
sdui-parser (dashboard-widget-options.js) 3.08KB 1.30KB
sdui-parser (index.js) 5.78KB 2.56KB
sdui-parser (input-type.js) 2.84KB 1.40KB
sdui-parser (kanban-quick-add.js) 3.89KB 1.87KB
sdui-parser (parse.js) 25.28KB 7.80KB
sdui-parser (provenance.js) 3.66KB 1.82KB
sdui-parser (types.js) 0.28KB 0.23KB
sdui-parser (validate.js) 18.27KB 6.20KB
types (ai.js) 4.11KB 2.06KB
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) 5.00KB 2.39KB
types (navigation.js) 0.20KB 0.18KB
types (objectql.js) 0.20KB 0.18KB
types (overlay.js) 0.20KB 0.18KB
types (permissions.js) 2.52KB 1.31KB
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) 17.15KB 6.32KB
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

@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

Contract review

Served-tier: CONTRACT_REVIEW_TIER
Head-sha: 6bad2d4ff1713ca46f053d3fe950f8fec52ae593

① Derived judgments

Security completeness on this surface (data-table.tsx and ObjectGrid.tsx at head, line numbers from the head blobs):

  • Ctrl+C / Cmd+C on a focused td: CLOSED. handleCellKeyDown 1820-1829 keeps preventDefault() and returns before writeText when columns.find(...)?.masked. Context menu: header-only (handleColumnContextMenu, 2122); no body-cell menu. Row copy / multi-cell range copy: the table has none; a native drag-selection copies rendered text (bullets). useCellClipboard (plugin-grid export, 0 in-repo callers) still copies String(row[field]) raw — pre-recorded in objectui#10568, unchanged.
  • title tooltip: CLOSED (2694). Re-run A3 below confirms it is the gate.
  • Other DOM: no aria-* or data-* attribute carries a cell value (grep: only aria-label="Row actions" and data-testid). textContent is whatever cell draws; a JSON-authored masked column with no cell draws the value, as documented.
  • Built-in CSV export: CLOSED (1438, column omitted, header and all).
  • Row detail / expand: "Open record" button forwards row to onRowClick (2371-2387); no value rendered. Drag: column-header reorder only (2116-2120). Print: prints the DOM, which now holds mask and no title.
  • Inline edit: LEAKS — FAIL. Read from source, not measured. startEdit (1578-1606) has no masked check; Enter (1847-1849), double-click and single-click (2484-2496) reach it; editValue is seeded with the raw row value (1600-1602). When renderCellEditor is absent or returns null, the fallback Input value={editValue} (2672-2680) renders the credential in the clear, and the copy handler yields to the native input copy while editingCell is set (1820). Reachability through the only producer: the pre-existing isFieldInlineEditable gate in the enrichment map (ObjectGrid 3661-3668) forces editable: false from the OBJECT-declared type only, while this PR's union stamps masked from the VIEW type as well. So a view column type: 'password' over an object text field is masked by path A (baseInferredType = col.type || objectDefField?.type, 2854), flagged at the seam, and still editable in an editable: true grid: renderCellEditor (4695-4700) answers hasFieldEditWidget('text') with TextField (EDIT_WIDGETS line 81) holding the raw value; a masked column whose key has no object fieldDef gets null there and lands in data-table's fallback input. A JSON-authored data-table with masked: true and editable: true takes the same fallback. The exposure is bounded (object-declared password / secret columns are closed by the old gate), but it is a path in files this PR owns, created in part by this PR's own union, and it falsifies the published sentences below. Remedy shape: refuse startEdit on a masked column in data-table (the flag's consumer), and/or stamp editable: false beside masked: true at the emit seam, with a pin in each new test file.
  • Client search and sort (1179-1189, 1207-1218) run over raw masked values: a substring search recovers a credential character by character wherever client search is active (ObjectGrid inline data or manualSearch off). Finding, not a raw-form exit; the dev recorded it with no carrier — it should be filed. autoSizedWidths (955-960) also encodes the raw length, min 80 max 400: trivial length oracle.
  • Mobile card view (ObjectGrid 5127-5241): titleCol (the first column) draws coerceToSafeValue(row[titleCol.accessorKey]) raw, bypassing cell; other card fields go through col.cell (5331). A masked field in first position draws raw below 768px. Pre-existing, unfiled — finding to file.

Stamp site: columns = generateColumns().map(enrich).map(seam) is one expression (3625-3729); all four generateColumns() literals and the fieldDef.type enrichment precede the seam, the stamp (3720-3722) precedes normalizeTableColumnType (3724). Downstream passes preserve the key: widths spread (3753-3761), order sort (3764-3770), _actions append, pinned partition by filter (3873-3876), applyColumnChrome spread (4010-4022), grouped buildGroupTableSchema spread (4887-4890), data-table initialColumns spread (925-928). A column added at runtime re-runs generateColumns() and passes the seam. Card view and handleExport read generateColumns() before the seam (neither reads the flag). Union direction verified: A5 re-run reds exactly the two "Vault Key (text over secret)" assertions; the mirror case (object text authored password) is masked and flagged, and is the inline-edit case above.

Declared key: masked?: boolean on TableColumn with JSDoc; z.boolean().optional().describe() on the non-strict TableColumnSchema, so it survives parse (pinned); masked?: never on StaticTableColumn and retirementTombstone(...) on StaticTableColumnSchema — the same form as wrap and fitContent, added to the exhaustive key lists. Justification holds: the static table.tsx has no title=, clipboard, export or keydown (grep: 0 hits). Published sentences on option A: the JSDoc says "does not DRAW … A column with no cell still draws its value" and the docs say the same — correct. But the lead claims overclaim against inline edit: docs "the table hands its raw value to nobody", zod describe "the table never hands the raw value out", changeset "no longer hands its raw value out", README "the cell does not hand the raw value out". The zod describe also ends "the cell renderer draws the mask", which a JSON author (who cannot author cell) can read as the table drawing one; the JSDoc and docs say otherwise, the describe does not.

Ctrl+C with preventDefault: preventDefault() was already unconditional for every cell before the PR (1821, unchanged); the masked early-return keeps it, so nothing is written. The detail page (DetailSection 367, 444-470) only withdraws the copy affordance (copyOffered = canCopy && !isMaskedField); it has no Ctrl+C keydown handler and does not intercept native selection copy, so a selected •••••• there still copies bullets natively. The grid is therefore stricter than the detail page's measured behaviour, and consistent with the ruling's "not the bullets". Not a defect; the PR's "matches option A" is a claim about the ruling, not about the detail page's keyboard.

Pins: re-run in a scratch worktree at head (offline install from the pnpm store), removed afterwards. Head: 13 passed (13). Base leg (4 implementation files at c2d86599b, pins kept): 11 failed / 2 passed; the 2 passing are the flag-absent controls, which assert the RAW copy, the raw title and Name,Key in the CSV — real controls. A3 (!col.masked && removed from title): 7 failed, the tooltip pin plus the 6 grid "raw value is nowhere in the DOM" cases. A5 (union narrowed to the column type): 2 failed, both "Vault Key (text over secret)". Porcelain 0 after every leg.

Dev findings: (1) real — neither RelatedList.tsx nor ObjectDataTable.tsx mentions masked (grep: 0 hits), so both still copy raw and carry the raw title; correctly a family card, not this PR. (2) real — handleExport client fallback (ObjectGrid 3521-3557) writes escapeCsvValue(record[f]) for every generateColumns() field and whole records as JSON, never reading the flag. It SHOULD be in scope by the dev's own reading: the title-tooltip fix was taken under "same defect class, mechanical, same file in the claim", and this export path is the same class, mechanical (filter fields through isMaskedGridColumn), and in ObjectGrid.tsx, a file the claim names and the PR edits, whereas the PR did fix the analogous export in data-table. Leaving it out makes the PR title and the changeset lead sentence false for the grid's own export menu. Either fix it here or narrow those sentences.

② Semver level

@object-ui/types minor: matches the declared-key precedent (6424-tablecolumn-declares-fitcontent.md, -headericon.md: minor; 6650-listcolumn-wrap-implemented.md used patch, so the level is the stricter of two precedents). @object-ui/components patch and @object-ui/plugin-grid patch: match 6650's shape for a table obeying a new column flag. Sentence by sentence: the mechanism sentences (Ctrl+C wrote String(row[accessorKey]), the title carried the raw value, z.boolean() mirror, static refusal, "additive", "written only when true", "left out, not blanked", "withholds; does not draw", "columns without the flag copy, show and export exactly as before") are true and pinned. The lead "A masked grid cell no longer hands its raw value out" is false on the inline-edit path and on the grid's own client export. Clause-②: yes is correct — a published key on @object-ui/types plus a behaviour change in data-table.

③ Boundary flags

Implemented-by: claude/issue-10583-masked-cell-copy
Reviewed-by: session_014mXUNuFomfj24w7s1pZzhN

VERDICT: FAIL

What stops landing: inline edit on a masked column still shows and (via the native input copy) hands out the raw value on a path data-table.tsx and ObjectGrid.tsx own, reachable through this PR's own view-type union (a password-typed view column over a non-credential or absent object field in an editable grid) and through any JSON-authored masked: true plus editable: true table — while every published lead sentence (docs, zod describe, changeset, README) says the table hands the raw value to nobody. Close it (refuse startEdit on masked, or stamp editable: false with the flag, pinned) or narrow every lead sentence to the three enforced paths; and either take ObjectGrid.handleExport's client CSV / JSON fallback under the same "same class, same file" reading the title fix used, or narrow the PR title and changeset lead accordingly.


Generated by Claude Code

…e edit, the grid's client export and the mobile card title

Round 2 of the masked-cell refusal, from the contract review:

- components: data-table's startEdit refuses a masked column (Enter,
  click, double-click), and a masked cell no longer reads as editable
  (no edit cursor, the row click is not swallowed).
- plugin-grid: the client export fallback (CSV and JSON) leaves every
  masked field out, asked per key through isMaskedGridColumn; the mobile
  card draws a masked title through its cell and never classifies a
  masked field by name into the raw amount / stage / date / percent rows.
- docs, JSDoc, zod describe, README, changeset: list exactly the paths
  withheld, and state what is not covered (client search and sort, the
  other column producers).

Claude-Session: https://claude.ai/code/session_014mXUNuFomfj24w7s1pZzhN
Co-authored-by: Claude <noreply@anthropic.com>
@github-actions

Copy link
Copy Markdown
Contributor

✅ Console Performance Budget

Metric Value Budget
Eager closure (gzip, 329 chunks) 3051.5 KB 3104.5 KB
Main entry chunk (gzip) 148.0 KB 350 KB
Entry file index-BLRin5DD.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.57KB 6.15KB
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) 27.95KB 10.04KB
auth (AuthContext.js) 0.31KB 0.24KB
auth (AuthGuard.js) 2.07KB 1.00KB
auth (AuthProvider.js) 40.17KB 10.58KB
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.39KB 1.21KB
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.52KB 3.45KB
auth (index.js) 3.63KB 1.64KB
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) 27.13KB 7.95KB
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) 547.14KB 130.87KB
core (index.js) 9.22KB 3.71KB
create-plugin (index.js) 27.94KB 9.51KB
data-objectstack (index.js) 223.91KB 62.28KB
fields (index.js) 260.06KB 65.86KB
i18n (LocalizationContext.js) 1.76KB 0.96KB
i18n (builtinAggregateLabels.js) 0.86KB 0.49KB
i18n (currency.js) 2.59KB 1.22KB
i18n (fallbackInterpolation.js) 6.25KB 2.77KB
i18n (i18n.js) 8.87KB 3.64KB
i18n (index.js) 5.24KB 2.27KB
i18n (pickLocalized.js) 9.86KB 3.95KB
i18n (provider.js) 39.40KB 12.91KB
i18n (useDisplayLocale.js) 3.52KB 1.76KB
i18n (useObjectLabel.js) 34.34KB 9.17KB
i18n (useSafeTranslation.js) 5.60KB 2.33KB
layout (index.js) 39.28KB 11.09KB
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) 6.62KB 2.45KB
mobile (useResponsive.js) 0.72KB 0.42KB
mobile (useSpecGesture.js) 5.52KB 2.10KB
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.33KB 3.07KB
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) 16.01KB 3.93KB
plugin-calendar (index.js) 51.49KB 14.64KB
plugin-charts (index.js) 74.94KB 20.89KB
plugin-chatbot (index.js) 198.36KB 47.20KB
plugin-dashboard (index.js) 133.60KB 35.41KB
plugin-designer (index.js) 216.25KB 44.39KB
plugin-detail (index.js) 232.96KB 61.65KB
plugin-editor (index.js) 2.23KB 1.05KB
plugin-form (index.js) 152.20KB 39.05KB
plugin-gantt (index.js) 169.64KB 41.91KB
plugin-grid (index.js) 215.96KB 59.08KB
plugin-kanban (index.js) 48.35KB 15.08KB
plugin-list (index.js) 114.58KB 28.31KB
plugin-map (index.js) 22.42KB 7.38KB
plugin-markdown (index.js) 13.88KB 4.80KB
plugin-report (index.js) 43.55KB 11.99KB
plugin-timeline (index.js) 30.67KB 8.95KB
plugin-tree (index.js) 10.52KB 3.69KB
plugin-view (index.js) 87.84KB 21.96KB
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) 116.21KB 38.14KB
react (data-invalidation.js) 5.05KB 2.08KB
react (index.js) 4.03KB 1.86KB
react (schema-input.js) 4.25KB 2.04KB
react (spec-input.js) 0.20KB 0.18KB
sdui-parser (body-dialect.js) 4.78KB 2.09KB
sdui-parser (codegen.js) 6.58KB 2.74KB
sdui-parser (dashboard-widget-options.js) 3.08KB 1.30KB
sdui-parser (index.js) 5.78KB 2.56KB
sdui-parser (input-type.js) 2.84KB 1.40KB
sdui-parser (kanban-quick-add.js) 3.89KB 1.87KB
sdui-parser (parse.js) 25.28KB 7.80KB
sdui-parser (provenance.js) 3.66KB 1.82KB
sdui-parser (types.js) 0.28KB 0.23KB
sdui-parser (validate.js) 18.27KB 6.20KB
types (ai.js) 4.11KB 2.06KB
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) 5.00KB 2.39KB
types (navigation.js) 0.20KB 0.18KB
types (objectql.js) 0.20KB 0.18KB
types (overlay.js) 0.20KB 0.18KB
types (permissions.js) 2.52KB 1.31KB
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) 17.15KB 6.32KB
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

@github-actions

Copy link
Copy Markdown
Contributor

✅ Console Performance Budget

Metric Value Budget
Eager closure (gzip, 329 chunks) 3051.5 KB 3104.5 KB
Main entry chunk (gzip) 148.0 KB 350 KB
Entry file index-BLRin5DD.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.57KB 6.15KB
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) 27.95KB 10.04KB
auth (AuthContext.js) 0.31KB 0.24KB
auth (AuthGuard.js) 2.07KB 1.00KB
auth (AuthProvider.js) 40.17KB 10.58KB
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.39KB 1.21KB
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.52KB 3.45KB
auth (index.js) 3.63KB 1.64KB
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) 27.13KB 7.95KB
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) 547.14KB 130.87KB
core (index.js) 9.22KB 3.71KB
create-plugin (index.js) 27.94KB 9.51KB
data-objectstack (index.js) 223.91KB 62.28KB
fields (index.js) 260.06KB 65.86KB
i18n (LocalizationContext.js) 1.76KB 0.96KB
i18n (builtinAggregateLabels.js) 0.86KB 0.49KB
i18n (currency.js) 2.59KB 1.22KB
i18n (fallbackInterpolation.js) 6.25KB 2.77KB
i18n (i18n.js) 8.87KB 3.64KB
i18n (index.js) 5.24KB 2.27KB
i18n (pickLocalized.js) 9.86KB 3.95KB
i18n (provider.js) 39.40KB 12.91KB
i18n (useDisplayLocale.js) 3.52KB 1.76KB
i18n (useObjectLabel.js) 34.34KB 9.17KB
i18n (useSafeTranslation.js) 5.60KB 2.33KB
layout (index.js) 39.28KB 11.09KB
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) 6.62KB 2.45KB
mobile (useResponsive.js) 0.72KB 0.42KB
mobile (useSpecGesture.js) 5.52KB 2.10KB
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.33KB 3.07KB
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) 16.01KB 3.93KB
plugin-calendar (index.js) 51.49KB 14.64KB
plugin-charts (index.js) 74.94KB 20.89KB
plugin-chatbot (index.js) 198.36KB 47.20KB
plugin-dashboard (index.js) 133.60KB 35.41KB
plugin-designer (index.js) 216.25KB 44.39KB
plugin-detail (index.js) 232.96KB 61.65KB
plugin-editor (index.js) 2.23KB 1.05KB
plugin-form (index.js) 152.20KB 39.05KB
plugin-gantt (index.js) 169.64KB 41.91KB
plugin-grid (index.js) 215.96KB 59.08KB
plugin-kanban (index.js) 48.35KB 15.08KB
plugin-list (index.js) 114.58KB 28.31KB
plugin-map (index.js) 22.42KB 7.38KB
plugin-markdown (index.js) 13.88KB 4.80KB
plugin-report (index.js) 43.55KB 11.99KB
plugin-timeline (index.js) 30.67KB 8.95KB
plugin-tree (index.js) 10.52KB 3.69KB
plugin-view (index.js) 87.84KB 21.96KB
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) 116.21KB 38.14KB
react (data-invalidation.js) 5.05KB 2.08KB
react (index.js) 4.03KB 1.86KB
react (schema-input.js) 4.25KB 2.04KB
react (spec-input.js) 0.20KB 0.18KB
sdui-parser (body-dialect.js) 4.78KB 2.09KB
sdui-parser (codegen.js) 6.58KB 2.74KB
sdui-parser (dashboard-widget-options.js) 3.08KB 1.30KB
sdui-parser (index.js) 5.78KB 2.56KB
sdui-parser (input-type.js) 2.84KB 1.40KB
sdui-parser (kanban-quick-add.js) 3.89KB 1.87KB
sdui-parser (parse.js) 25.28KB 7.80KB
sdui-parser (provenance.js) 3.66KB 1.82KB
sdui-parser (types.js) 0.28KB 0.23KB
sdui-parser (validate.js) 18.27KB 6.20KB
types (ai.js) 4.11KB 2.06KB
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) 5.00KB 2.39KB
types (navigation.js) 0.20KB 0.18KB
types (objectql.js) 0.20KB 0.18KB
types (overlay.js) 0.20KB 0.18KB
types (permissions.js) 2.52KB 1.31KB
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) 17.15KB 6.32KB
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

@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

Contract review

Served-tier: CONTRACT_REVIEW_TIER
Head-sha: fc874c1e2d7b0d7425a5616a1fbf3e116288bcde

① Derived judgments

Round-1 requirements at head (line numbers from the head blobs):

  • Inline edit: CLOSED. startEdit (data-table 1591-1599) returns on column?.masked before the setEditingCell at 1601-1602, which is the only non-null write of editingCell in the file (the other four writes are nulls at 1645/1683/1744/1801). Enter (1853-1855), click (2501-2504) and double-click (2491-2498) all pass through it, so both edit modes, renderCellEditor present (ObjectGrid 4707) or absent, and a JSON-authored masked: true plus editable: true table are all refused. Render-time isEditable (2453) reads !col.masked: no cursor-text, no stopPropagation on the row click. The old object-level gate (INLINE_EXCLUDED_FIELD_TYPES lists password/secret) still closes the object-declared case; the new one is the only gate for the view-typed and JSON-authored cases.
  • Client export: CLOSED at the top level. isMaskedKey (ObjectGrid 3549-3551) passes columnTypeByKey.get(key) (undefined for a non-column key) and objectSchema.fields[key].type, so for a non-column key the OBJECT type alone decides — the right authority for a key with no column. CSV filters generateColumns() (3554); JSON filters Object.entries(record) (3566-3568), top-level keys only.
  • Mobile card: CLOSED for every row type. classify (5200-5208) answers 'other' for a masked key before any name test; amount/stage/date/percent all read classify (5236-5239); stageValue (5245) feeds only a border class; otherCols draws through col.cell (5354), the title through titleCol.cell (5261-5263). No other card row reads row[...] raw. Every generateColumns() literal writes cell from resolveGridCellRendering (3051/3167/3302/3404), so a masked draft always has one; a text-over-secret view column keeps the flag but its cell is the text renderer, as the README and body state.
  • Overclaim grep at head over the changeset, docs, README, types, plugin-grid and data-table for "to nobody / never hands / hands its raw value out / hand the raw value out": 1 hit, ObjectGrid.tsx:798 "never hand-listed", pre-existing at base (797) and unrelated. The claim class has 0 hits.

Measured (scratch worktree at head, offline install, one lock hold: waited 401s, held 131s; worktree removed; git diff HEAD empty after every leg):

  • Head: 3 files, 22 passed (22). Red leg (data-table.tsx and ObjectGrid.tsx at 6bad2d4ff, all 22 pins): 7 failed / 15 passed — both grid inline-edit cases, CSV, JSON, the card, both data-table inline-edit cases. The body's set.
  • Ablations, each anchor hit 1 then 0 with the blob md5 changed, then restored: A1 startEdit guard removed: 4 of 22 (both data-table and both grid inline-edit cases). A2 !col.masked off isEditable: 1 (single-click, no edit cursor). B1 CSV filter removed: 1 (CSV pin). B2 JSON filter replaced by an always-true predicate: 1 (JSON pin). C1 classifier guard removed: 1 (card). C2 title route forced false: 1 (card). Every count matches the body.
  • Two review probes at head (my own file in a second head worktree, run during the same hold, removed):
    1. JSON export with a lookup column whose expanded record carries a password field: the top-level masked key is removed; the nested owner.api_key is written in the clear. A masked field DOES reach the JSON through a nested object. Reach: any grid with a lookup column (the grid auto-injects $expand, ObjectGrid 2244) to an object holding a credential, on a backend that does not mask. In this PR's surface (the JSON branch it rewrote); not closable without the related object's schema, so it must be stated.
    2. Grouping by the masked field (grouping.fields: [{ field: 'api_key' }]): the group header draws RAW-GROUP-10643 as the group label while every cell below draws the mask (useGroupedData.ts:323 buildSegmentLabel(row[f.field]), drawn by renderGroup ObjectGrid 5529-5548 via GroupRow label). Same disclosure class as the card, same file, author-elected path, every viewer sees it. In this PR's surface. Closable mechanically (draw the mask in resolveGroupHeader when isMaskedGridColumn holds for group.field, or refuse a masked grouping field), or state it.

Other paths, from source, unmeasured: the server-streamed export (ObjectGrid 3480-3505) sends the masked columns in fields as before — the server's masking is the only guard, and neither the bare "export" in the title nor "Not covered" says so. Column summaries and group aggregations are numeric-only (useColumnSummary.ts:244-251, GroupRow.tsx:136), so only a numeric-looking credential authored with summary: 'max' would print — tiny reach. autoSizedWidths (data-table 932-960) still sizes a masked column from the raw string length, min 80 max 400: a length oracle for 4 to 44 characters, undocumented. useCellClipboard (0 callers) unchanged, pre-recorded. Data-table has no grouping, footer, row expansion or print path (grep: none); no aria-* or data-* attribute carries a value (only data-testid, data-state, literal aria-labels); "Open record" forwards row to onRowClick and draws nothing.

Open question: A (withhold-only) is right — a fields-free table must not become a second mask authority; the "draws a column with no cell as its value" sentence is in all five places and should stay.

② Semver level

@object-ui/types minor (declared key, the round-1 precedent), components and plugin-grid patch, Clause-②: yes — still right. The enumerated claims in the changeset, JSDoc, zod describe(), docs section, README, body and title are true and pinned; the docs frontmatter md5 of lines 1-4 is d069943eaa540e3e4d0a4db85dbd48c0 at base, head and main; the static table.tsx has no edit, title, clipboard, CSV or keydown (grep: 0). What is not right:

  • Title and changeset lead say "export" bare. At head only the two CLIENT files withhold, the JSON only at the top level, and the server-streamed export is unchanged. Replace in both: …inline edit, the client export and the mobile card (title: fix(components,plugin-grid,types): withhold a masked grid field's raw value from copy, tooltip, inline edit, the client export and the mobile card).
  • Changeset "The same rule leaves every masked field out of the grid's client export (CSV and JSON, …)": replace every masked field with every masked field of the grid's object.
  • "Not covered" (changeset, README, docs, JSDoc) is incomplete. Append to the changeset paragraph and the README sentence: The server-streamed export (exportDownload) sends the masked columns as before and relies on the server's masking. The client JSON export writes an expanded lookup record whole, so a credential field of the related object is not pruned. A grid grouped by a masked field draws the raw value as the group label. Drop the last sentence if the grouping header is closed instead.
  • Changeset "Inline edit seeded its editor with the raw value" is true at base only for the view-typed and JSON-authored cases (the object-declared case was already gated); exact wording: Inline edit, for a view-typed password column, seeded its editor with the raw value.
  • Body: "The pm:blocked draft objectui#10278" — that PR carries no such label (documentation, plugin, tests, needs:contract-review), and it also overlaps packages/plugin-grid/README.md, unmentioned.

③ Boundary flags

Implemented-by: claude/issue-10583-masked-cell-copy
Reviewed-by: session_014mXUNuFomfj24w7s1pZzhN

VERDICT: FAIL

What stops landing is wording plus one unnamed path, not the round-1 code: the four required paths are closed, pinned and ablation-proven. (1) The title and changeset lead claim "export" while the server-streamed export is unchanged and the client JSON still writes an expanded related record's credential (measured): narrow both to "the client export" and complete "Not covered" with the three sentences above. (2) A grid grouped by a masked field draws the raw value as its group label (measured, ObjectGrid.tsx, this PR's surface): close it with a pin, or name it in "Not covered". Neither needs a new mechanism.


Generated by Claude Code

…uncovered paths exactly

Round 3 of the masked-cell refusal, from the security re-review:

- plugin-grid: a grouping entry on a masked field (isMaskedGridColumn over
  the view column type and the object-declared type) is dropped, and a
  console warning names it; the other grouping levels still apply. A
  masked group label alone would still bucket the records that share a
  credential, in its raw order.
- The expanded-lookup JSON export stays open: the grid holds only its own
  object's schema, and the related object's field types are not in hand
  without a new fetch. It is stated as not covered.
- docs, README, JSDoc, zod describe, changeset: add the server-streamed
  export, the expanded lookup record and the auto-width length to "not
  covered"; cite objectui#10657 and objectui#10658.

Claude-Session: https://claude.ai/code/session_014mXUNuFomfj24w7s1pZzhN
Co-authored-by: Claude <noreply@anthropic.com>
@github-actions

Copy link
Copy Markdown
Contributor

ℹ️ Console Performance Budget — not measured

This run did not produce a console bundle to measure, so there is no pass/fail verdict for the performance budget.

This is not a budget violation. Nothing was measured — the numbers a real violation would carry are simply absent.

Step Outcome
Build packages failure
Check console performance budget skipped

See the workflow run for details.

No package size report: it is only generated from a complete package build, so a partial one is never shown.

…t the type

`unmaskedGroupingFields` is now always the filtered usable list, and the
unmasked or ungrouped path hands `useGroupedData` the authored config
itself, so the config passed never carries `fields: undefined`.

Claude-Session: https://claude.ai/code/session_014mXUNuFomfj24w7s1pZzhN
Co-authored-by: Claude <noreply@anthropic.com>
@github-actions

Copy link
Copy Markdown
Contributor

✅ Console Performance Budget

Metric Value Budget
Eager closure (gzip, 329 chunks) 3052.7 KB 3104.5 KB
Main entry chunk (gzip) 147.9 KB 350 KB
Entry file index-B9n-v-td.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.57KB 6.15KB
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) 27.95KB 10.04KB
auth (AuthContext.js) 0.31KB 0.24KB
auth (AuthGuard.js) 2.07KB 1.00KB
auth (AuthProvider.js) 40.17KB 10.58KB
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.39KB 1.21KB
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.52KB 3.45KB
auth (index.js) 3.63KB 1.64KB
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) 27.13KB 7.95KB
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) 547.14KB 130.87KB
core (index.js) 9.22KB 3.71KB
create-plugin (index.js) 27.94KB 9.51KB
data-objectstack (index.js) 223.91KB 62.28KB
fields (index.js) 260.14KB 65.89KB
i18n (LocalizationContext.js) 1.76KB 0.96KB
i18n (builtinAggregateLabels.js) 0.86KB 0.49KB
i18n (currency.js) 2.59KB 1.22KB
i18n (fallbackInterpolation.js) 6.25KB 2.77KB
i18n (i18n.js) 8.87KB 3.64KB
i18n (index.js) 5.24KB 2.27KB
i18n (pickLocalized.js) 9.86KB 3.95KB
i18n (provider.js) 39.40KB 12.91KB
i18n (useDisplayLocale.js) 3.52KB 1.76KB
i18n (useObjectLabel.js) 34.34KB 9.17KB
i18n (useSafeTranslation.js) 5.60KB 2.33KB
layout (index.js) 39.28KB 11.09KB
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) 6.62KB 2.45KB
mobile (useResponsive.js) 0.72KB 0.42KB
mobile (useSpecGesture.js) 5.52KB 2.10KB
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.33KB 3.07KB
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) 16.01KB 3.93KB
plugin-calendar (index.js) 51.49KB 14.64KB
plugin-charts (index.js) 74.94KB 20.89KB
plugin-chatbot (index.js) 198.36KB 47.20KB
plugin-dashboard (index.js) 133.60KB 35.41KB
plugin-designer (index.js) 216.25KB 44.39KB
plugin-detail (index.js) 232.85KB 61.58KB
plugin-editor (index.js) 2.23KB 1.05KB
plugin-form (index.js) 152.20KB 39.05KB
plugin-gantt (index.js) 169.71KB 41.93KB
plugin-grid (index.js) 218.05KB 59.70KB
plugin-kanban (index.js) 48.35KB 15.08KB
plugin-list (index.js) 115.72KB 28.71KB
plugin-map (index.js) 22.42KB 7.38KB
plugin-markdown (index.js) 13.88KB 4.80KB
plugin-report (index.js) 43.55KB 11.99KB
plugin-timeline (index.js) 30.67KB 8.95KB
plugin-tree (index.js) 10.52KB 3.69KB
plugin-view (index.js) 87.77KB 21.99KB
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) 116.21KB 38.14KB
react (data-invalidation.js) 5.05KB 2.08KB
react (index.js) 4.03KB 1.86KB
react (schema-input.js) 4.25KB 2.04KB
react (spec-input.js) 0.20KB 0.18KB
sdui-parser (body-dialect.js) 4.78KB 2.09KB
sdui-parser (codegen.js) 6.58KB 2.74KB
sdui-parser (dashboard-widget-options.js) 3.08KB 1.30KB
sdui-parser (index.js) 5.78KB 2.56KB
sdui-parser (input-type.js) 2.84KB 1.40KB
sdui-parser (kanban-quick-add.js) 3.89KB 1.87KB
sdui-parser (parse.js) 25.28KB 7.80KB
sdui-parser (provenance.js) 3.66KB 1.82KB
sdui-parser (types.js) 0.28KB 0.23KB
sdui-parser (validate.js) 18.27KB 6.20KB
types (ai.js) 4.11KB 2.06KB
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) 5.00KB 2.39KB
types (navigation.js) 0.20KB 0.18KB
types (objectql.js) 0.20KB 0.18KB
types (overlay.js) 0.20KB 0.18KB
types (permissions.js) 2.52KB 1.31KB
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) 17.15KB 6.32KB
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

@github-actions

Copy link
Copy Markdown
Contributor

✅ Console Performance Budget

Metric Value Budget
Eager closure (gzip, 329 chunks) 3060.7 KB 3104.5 KB
Main entry chunk (gzip) 148.1 KB 350 KB
Entry file index-DcXMO5Tc.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.57KB 6.15KB
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) 27.95KB 10.04KB
auth (AuthContext.js) 0.31KB 0.24KB
auth (AuthGuard.js) 2.07KB 1.00KB
auth (AuthProvider.js) 40.17KB 10.58KB
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.39KB 1.21KB
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.52KB 3.45KB
auth (index.js) 3.63KB 1.64KB
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) 27.13KB 7.95KB
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) 547.35KB 131.00KB
core (index.js) 9.52KB 3.79KB
create-plugin (index.js) 27.94KB 9.51KB
data-objectstack (index.js) 223.91KB 62.28KB
fields (index.js) 259.18KB 65.61KB
i18n (LocalizationContext.js) 1.76KB 0.96KB
i18n (builtinAggregateLabels.js) 0.86KB 0.49KB
i18n (currency.js) 2.59KB 1.22KB
i18n (fallbackInterpolation.js) 6.25KB 2.77KB
i18n (i18n.js) 8.87KB 3.64KB
i18n (index.js) 5.24KB 2.27KB
i18n (pickLocalized.js) 9.86KB 3.95KB
i18n (provider.js) 39.40KB 12.91KB
i18n (useDisplayLocale.js) 3.52KB 1.76KB
i18n (useObjectLabel.js) 34.34KB 9.17KB
i18n (useSafeTranslation.js) 5.60KB 2.33KB
layout (index.js) 39.28KB 11.09KB
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) 6.62KB 2.45KB
mobile (useResponsive.js) 0.72KB 0.42KB
mobile (useSpecGesture.js) 5.52KB 2.10KB
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.33KB 3.07KB
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) 16.01KB 3.93KB
plugin-calendar (index.js) 51.52KB 14.64KB
plugin-charts (index.js) 80.42KB 22.22KB
plugin-chatbot (index.js) 198.36KB 47.20KB
plugin-dashboard (index.js) 133.93KB 35.54KB
plugin-designer (index.js) 216.25KB 44.39KB
plugin-detail (index.js) 232.85KB 61.58KB
plugin-editor (index.js) 2.23KB 1.05KB
plugin-form (index.js) 152.55KB 39.16KB
plugin-gantt (index.js) 169.71KB 41.93KB
plugin-grid (index.js) 218.09KB 59.71KB
plugin-kanban (index.js) 48.36KB 15.09KB
plugin-list (index.js) 115.82KB 28.76KB
plugin-map (index.js) 23.01KB 7.60KB
plugin-markdown (index.js) 13.88KB 4.80KB
plugin-report (index.js) 43.55KB 11.99KB
plugin-timeline (index.js) 30.84KB 9.01KB
plugin-tree (index.js) 10.52KB 3.69KB
plugin-view (index.js) 87.77KB 21.99KB
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) 116.21KB 38.14KB
react (data-invalidation.js) 5.05KB 2.08KB
react (index.js) 4.03KB 1.86KB
react (schema-input.js) 4.25KB 2.04KB
react (spec-input.js) 0.20KB 0.18KB
sdui-parser (body-dialect.js) 4.78KB 2.09KB
sdui-parser (codegen.js) 6.58KB 2.74KB
sdui-parser (dashboard-widget-options.js) 3.08KB 1.30KB
sdui-parser (index.js) 5.78KB 2.56KB
sdui-parser (input-type.js) 2.84KB 1.40KB
sdui-parser (kanban-quick-add.js) 3.89KB 1.87KB
sdui-parser (parse.js) 25.28KB 7.80KB
sdui-parser (provenance.js) 3.66KB 1.82KB
sdui-parser (types.js) 0.28KB 0.23KB
sdui-parser (validate.js) 18.27KB 6.20KB
types (ai.js) 4.11KB 2.06KB
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) 5.00KB 2.39KB
types (navigation.js) 0.20KB 0.18KB
types (objectql.js) 0.20KB 0.18KB
types (overlay.js) 0.20KB 0.18KB
types (permissions.js) 2.52KB 1.31KB
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) 17.15KB 6.32KB
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

…ead of an any cast

Claude-Session: https://claude.ai/code/session_014mXUNuFomfj24w7s1pZzhN
Co-authored-by: Claude <noreply@anthropic.com>
@github-actions

Copy link
Copy Markdown
Contributor

✅ Console Performance Budget

Metric Value Budget
Eager closure (gzip, 329 chunks) 3061.0 KB 3104.5 KB
Main entry chunk (gzip) 148.2 KB 350 KB
Entry file index-C3tcE7w-.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.57KB 6.15KB
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) 27.95KB 10.04KB
auth (AuthContext.js) 0.31KB 0.24KB
auth (AuthGuard.js) 2.07KB 1.00KB
auth (AuthProvider.js) 40.17KB 10.58KB
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.39KB 1.21KB
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.52KB 3.45KB
auth (index.js) 3.63KB 1.64KB
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) 27.13KB 7.95KB
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) 547.48KB 131.02KB
core (index.js) 9.52KB 3.79KB
create-plugin (index.js) 27.94KB 9.51KB
data-objectstack (index.js) 223.91KB 62.28KB
fields (index.js) 259.18KB 65.61KB
i18n (LocalizationContext.js) 1.76KB 0.96KB
i18n (builtinAggregateLabels.js) 0.86KB 0.49KB
i18n (currency.js) 2.59KB 1.22KB
i18n (fallbackInterpolation.js) 6.25KB 2.77KB
i18n (i18n.js) 8.87KB 3.64KB
i18n (index.js) 5.24KB 2.27KB
i18n (pickLocalized.js) 9.86KB 3.95KB
i18n (provider.js) 39.40KB 12.91KB
i18n (useDisplayLocale.js) 3.52KB 1.76KB
i18n (useObjectLabel.js) 34.34KB 9.17KB
i18n (useSafeTranslation.js) 5.60KB 2.33KB
layout (index.js) 39.28KB 11.09KB
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) 6.62KB 2.45KB
mobile (useResponsive.js) 0.72KB 0.42KB
mobile (useSpecGesture.js) 5.52KB 2.10KB
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.33KB 3.07KB
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) 16.01KB 3.93KB
plugin-calendar (index.js) 51.52KB 14.64KB
plugin-charts (index.js) 80.42KB 22.22KB
plugin-chatbot (index.js) 198.40KB 47.22KB
plugin-dashboard (index.js) 133.85KB 35.54KB
plugin-designer (index.js) 216.25KB 44.39KB
plugin-detail (index.js) 232.85KB 61.58KB
plugin-editor (index.js) 2.23KB 1.05KB
plugin-form (index.js) 152.55KB 39.16KB
plugin-gantt (index.js) 169.71KB 41.93KB
plugin-grid (index.js) 218.12KB 59.72KB
plugin-kanban (index.js) 48.36KB 15.09KB
plugin-list (index.js) 115.82KB 28.76KB
plugin-map (index.js) 22.85KB 7.58KB
plugin-markdown (index.js) 13.88KB 4.80KB
plugin-report (index.js) 43.55KB 11.99KB
plugin-timeline (index.js) 30.84KB 9.01KB
plugin-tree (index.js) 10.52KB 3.69KB
plugin-view (index.js) 87.83KB 22.01KB
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) 116.21KB 38.14KB
react (data-invalidation.js) 5.05KB 2.08KB
react (index.js) 4.03KB 1.86KB
react (schema-input.js) 4.25KB 2.04KB
react (spec-input.js) 0.20KB 0.18KB
sdui-parser (body-dialect.js) 4.78KB 2.09KB
sdui-parser (codegen.js) 6.58KB 2.74KB
sdui-parser (dashboard-widget-options.js) 3.08KB 1.30KB
sdui-parser (index.js) 5.78KB 2.56KB
sdui-parser (input-type.js) 2.84KB 1.40KB
sdui-parser (kanban-quick-add.js) 3.89KB 1.87KB
sdui-parser (parse.js) 25.28KB 7.80KB
sdui-parser (provenance.js) 3.66KB 1.82KB
sdui-parser (types.js) 0.28KB 0.23KB
sdui-parser (validate.js) 18.27KB 6.20KB
types (ai.js) 4.11KB 2.06KB
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) 5.00KB 2.39KB
types (navigation.js) 0.20KB 0.18KB
types (objectql.js) 0.20KB 0.18KB
types (overlay.js) 0.20KB 0.18KB
types (permissions.js) 2.52KB 1.31KB
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) 17.15KB 6.32KB
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

@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

Contract review

Served-tier: CONTRACT_REVIEW_TIER
Head-sha: 4d341308a2c71d7cc33be9abd6faab2aeaea2e4a

① Derived judgments

Grouping refusal, every route (source at head): ObjectGrid reads grouping from schema.grouping alone (ObjectGridSchema declares no groupBy; grep 0). Every producer lands on that key: ListView folds schema.grouping, the legacy groupBy / groupBy2, and the ViewSettingsPopover choice into one groupingConfig and spreads { grouping: groupingConfig } into the object-grid schema (three sites); plugin-view's ObjectView relays NamedListView.grouping ?? activeView.grouping into the list-view schema. One useGroupedData call, and the refusal sits in front of it. Spelling: the refusal and the hook both normalise through usableGroupingFields, which admits only { field: string } entries, so a bare-string entry is dropped by both and nested levels are judged one by one (the mixed pin). The warning prints field NAMES only (masked.join(', ')), never a value. Aggregates: group count / count_distinct and footer count_* are cardinalities; the numeric families parse Number(row[field]), so a numeric-looking credential authored with min / max prints in a footer or group row — author-elected, tiny, unstated (round 2 said the same). Unstated UX consequence: the popover offers every field, so a user who groups by a masked field gets a flat table, a badge of 1 and only a console warning.

Measured (scratch worktree at head, offline install, one lock hold: waited 230s, held 55s; worktree removed; git diff HEAD empty after every leg): 25 pins at head: 25 passed (25). G1, detection to false && … (anchor 1 to 0, md5 23075308… to daf1b448…, restored, diff 0 lines): 2 of 25 red, both grouping-refusal pins, control green. G2, the ternary replaced by schema.grouping (md5 963f197a…, restored, diff 0 lines): 2 of 25 red, the same two. Both match the body.

FAIL — an unnamed, measured window on the host-fetched path. Every guard this PR added reads objectSchema: the emit-seam stamp, the client-export isMaskedKey, the card classifier and the grouping signature all take objectSchema?.fields?.[key]?.type. On the inline path (dataConfig.provider === 'value', the path every fetching host takes: ListView passes its window as the grid's data prop, the ObjectGrid comment on objectui#6677 says so, and the 6677 pin header says "a fetching host always passes data"), data is set by a synchronous effect while objectSchema stays null until the grid's own getObjectSchema settles. A view column with no authored type (the console's ordinary shape) is untyped in that window. My probe (own file, run unlocked at one worker, removed): object-declared password column api_key, untyped view column, inline rows, getObjectSchema pending. Before the schema: rawAsText true, 2 rows drawn, and with grouping: { fields: [{ field: 'api_key' }] } one group row whose .group-label is the raw credential. After the schema: mask drawn, 0 group rows. So on that path, until the schema round-trip completes, the cell draws the credential in the clear and the group header is exactly round 2's finding; the copy, tooltip, edit, export and card guards are open in the same window (unmeasured, same key). The draw half is pre-existing (the cell renderer needs the type); the grouping half is the round-3 mechanism inheriting the window. In this PR's surface, neither closed nor stated: the README's "A password or secret field draws a mask, and the grid withholds…" and every "refuses them as grouping keys" sentence are unconditional. Reach in production: a schema cache miss (first visit to the object in data-objectstack's MetadataCache, or any adapter without one) is a network round-trip. Remedy: close it (a host hands the grid the object types it already holds, or an object-bound untyped column is treated as unknown-not-text until the schema settles), or state it in the changeset, README, docs, JSDoc and body: "On the host-fetched path (rows handed down as data), the grid's guards and the cell's own mask depend on the object schema, which the grid fetches after first paint; until it settles, an untyped view column over a password / secret field draws and hands out the raw value" — and file the draw half as a card.

objectui#10625 date editors (merged in ef874bd8b): they render only inside the isEditing branch; the sole non-null setEditingCell write is in startEdit (four null writes elsewhere), which returns on column?.masked; all three startEdit( call sites (Enter, double-click, single-click) go through it; render-time isEditable still reads !col.masked. No new door. The main-side data-table diff c2d86599b..ed8251189 touches no clipboard / title / startEdit / handleExport / isEditable line. The PR's hunks in data-table.tsx are byte-identical before and after both merges; the ObjectGrid.tsx hunks are identical plus the grouping block. Nothing dropped.

Other surfaces: data-table has no filter menu of distinct values, no body-cell context menu, no row copy, no selection export; aria-* / data-* carry no value; "Open record" forwards row. useCellClipboard still 0 callers. Column summaries: above.

Expanded lookup, "stated, not closed": the reason is true. getObjectSchema is called only for objectName (two sites); @object-ui/react's useSettledSchema is async over the same call; data-objectstack has a MetadataCache but the DataSource contract exposes no synchronous read; the lookup field def carries reference_to only. Stated in the changeset, docs (the table CSV of a lookup column too), README, JSDoc and body; the zod describe() is table-scoped and needs no more.

② Semver level

@object-ui/types minor, components and plugin-grid patch, Clause-②: yes — right (declared-key precedent 6424). check-changeset-presence: 9 source files, 3 packages, 1 changeset, verified. Docs frontmatter md5 of lines 1-4 is d069943eaa540e3e4d0a4db85dbd48c0 at base, head and main. Overclaim grep of the round-2 class over the PR files, the title file and the body file: 0 hits. The changeset, README, docs, JSDoc and zod describe() sentences are true except for the window above. Corrections:

  • Five published places (changeset twice, docs twice, README, JSDoc) and body lines 22 and 131 cite objectui#10658 for search / sort / width; that card was closed as a duplicate at 15:46Z and folded into objectui#10657. Replace (objectui#10658) with (objectui#10657, which folded objectui#10658).
  • Body gate row check-changeset-claims: "29 pending bodies name a touched file. They were read again, including the three new ones from ed8251189 (objectui#10580, objectui#10625 ×3, objectui#9002)". My run flags the same 29, none of which is new in ed8251189; the five bodies ed8251189 added name no touched file in backticks, and "three" counts five. Replace with: "29 pending bodies name a touched file, none of them new in ed8251189; the five bodies ed8251189 added (objectui#10580, objectui#10625 ×3, objectui#9002) were read separately; none is falsified." I spot-read the 10625 (×2), 10580, 10607, 10639, 10528 and 5453 bodies: none is falsified.
  • Body Overlap bullet: objectui#10635 and objectui#10656 are merged (9fbbb17a2, 0c3f70aae) and already inside this head through the two merges; the only open PR on these files is feat(plugin-grid): one display page size read from the spec, and a distinct fetch batch (objectui#9853) #10278. Say so.
  • Body red legs (lines 76-77) and the package-suite rows on ef874bd8b / 4d341308a: unmeasured by me; I re-ran the 25 pins, G1, G2 and one probe.

③ Boundary flags

  • CI on the head: 43 check runs, 40 success, 3 skipped, none red (every test shard, Type Check, Lint and Spec Main Shape Gate finished green after an in-progress first read); Vercel status success.
  • git merge-tree --write-tree against current main 1e946c96a: clean (f0797e504); main has not moved in any of the 12 files since merge-base ed8251189 (5 main commits, none touching them).
  • Open PRs on these files: feat(plugin-grid): one display page size read from the spec, and a distinct fetch batch (objectui#9853) #10278 only (draft; ObjectGrid.tsx and packages/plugin-grid/README.md); it conflicts with main on its own (merge-tree main vs its head eab4c8e52: conflict in ObjectGrid.tsx) and with this head, inherited. The other 10 open PRs touch none of the 12.
  • objectui#10685: this PR's data-display.ts hunks are TableColumn (702-742) and StaticTableColumn (801-815); DrillDownConfig starts at 2310 and ObjectMetricDrillDownConfig at 2415 — disjoint. No PR is open for 10685 yet (its worktree sits at bc97f9247).
  • Unmeasured: type-check, lint and the package suites (CI covers the first two).

Implemented-by: claude/issue-10583-masked-cell-copy
Reviewed-by: session_014mXUNuFomfj24w7s1pZzhN

VERDICT: FAIL

What stops landing: one measured, unnamed path in this PR's surface plus wording. (1) On the host-fetched path (every ListView / ObjectView grid), all of this PR's guards and the round-3 grouping refusal key on objectSchema, which is null until the grid's own schema fetch settles after first paint; measured, an untyped view column over an object-declared password field draws the raw value as cell text and as the group label in that window. Close it, or state it in "Not covered" in all five places and the body with the sentence above, and file the draw half. (2) Wording: objectui#10658 is a closed duplicate — cite objectui#10657; the check-changeset-claims row misstates which bodies the gate flagged; the Overlap bullet lists two merged PRs as open. The rounds-1 to 3 code is otherwise intact, pinned and ablation-proven at head, and the two main merges dropped nothing.


Generated by Claude Code

…dow; scope the schema-dependent claims

Round 4 (wording only, no behaviour change):

- Add the host-fetched-path sentence (objectui#NEWCARD placeholder) to
  the changeset, the docs "Masked columns" section, the plugin-grid
  README, the TableColumn.masked JSDoc, the isMaskedGridColumn header and
  the grouping comment.
- Scope the schema-dependent sentences with "once the object schema has
  loaded".
- Cite objectui#10658 as folded into objectui#10657.

Claude-Session: https://claude.ai/code/session_014mXUNuFomfj24w7s1pZzhN
Co-authored-by: Claude <noreply@anthropic.com>
…fetched schema window

Claude-Session: https://claude.ai/code/session_014mXUNuFomfj24w7s1pZzhN
Co-authored-by: Claude <noreply@anthropic.com>
@github-actions

Copy link
Copy Markdown
Contributor

✅ Console Performance Budget

Metric Value Budget
Eager closure (gzip, 329 chunks) 3061.8 KB 3104.5 KB
Main entry chunk (gzip) 148.2 KB 350 KB
Entry file index-YTQzIygP.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.57KB 6.15KB
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) 27.95KB 10.04KB
auth (AuthContext.js) 0.31KB 0.24KB
auth (AuthGuard.js) 2.07KB 1.00KB
auth (AuthProvider.js) 40.17KB 10.58KB
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.39KB 1.21KB
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.52KB 3.45KB
auth (index.js) 3.63KB 1.64KB
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) 27.13KB 7.95KB
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) 547.48KB 131.02KB
core (index.js) 9.52KB 3.79KB
create-plugin (index.js) 27.94KB 9.51KB
data-objectstack (index.js) 223.91KB 62.28KB
fields (index.js) 259.18KB 65.61KB
i18n (LocalizationContext.js) 1.76KB 0.96KB
i18n (builtinAggregateLabels.js) 0.86KB 0.49KB
i18n (currency.js) 2.59KB 1.22KB
i18n (fallbackInterpolation.js) 6.25KB 2.77KB
i18n (i18n.js) 8.87KB 3.64KB
i18n (index.js) 5.24KB 2.27KB
i18n (pickLocalized.js) 9.86KB 3.95KB
i18n (provider.js) 39.40KB 12.91KB
i18n (useDisplayLocale.js) 3.52KB 1.76KB
i18n (useObjectLabel.js) 34.34KB 9.17KB
i18n (useSafeTranslation.js) 5.60KB 2.33KB
layout (index.js) 39.28KB 11.09KB
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) 6.62KB 2.45KB
mobile (useResponsive.js) 0.72KB 0.42KB
mobile (useSpecGesture.js) 5.52KB 2.10KB
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.33KB 3.07KB
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) 16.01KB 3.93KB
plugin-calendar (index.js) 51.52KB 14.64KB
plugin-charts (index.js) 80.42KB 22.22KB
plugin-chatbot (index.js) 198.40KB 47.22KB
plugin-dashboard (index.js) 133.85KB 35.54KB
plugin-designer (index.js) 216.25KB 44.39KB
plugin-detail (index.js) 232.85KB 61.58KB
plugin-editor (index.js) 2.23KB 1.05KB
plugin-form (index.js) 154.22KB 39.52KB
plugin-gantt (index.js) 169.71KB 41.93KB
plugin-grid (index.js) 218.12KB 59.72KB
plugin-kanban (index.js) 48.36KB 15.09KB
plugin-list (index.js) 115.91KB 28.80KB
plugin-map (index.js) 22.85KB 7.58KB
plugin-markdown (index.js) 13.88KB 4.80KB
plugin-report (index.js) 43.55KB 11.99KB
plugin-timeline (index.js) 30.84KB 9.01KB
plugin-tree (index.js) 10.52KB 3.69KB
plugin-view (index.js) 87.83KB 22.01KB
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) 116.21KB 38.14KB
react (data-invalidation.js) 5.05KB 2.08KB
react (index.js) 4.03KB 1.86KB
react (schema-input.js) 4.25KB 2.04KB
react (spec-input.js) 0.20KB 0.18KB
sdui-parser (body-dialect.js) 4.78KB 2.09KB
sdui-parser (codegen.js) 6.58KB 2.74KB
sdui-parser (dashboard-widget-options.js) 3.08KB 1.30KB
sdui-parser (index.js) 5.78KB 2.56KB
sdui-parser (input-type.js) 2.84KB 1.40KB
sdui-parser (kanban-quick-add.js) 3.89KB 1.87KB
sdui-parser (parse.js) 25.28KB 7.80KB
sdui-parser (provenance.js) 3.66KB 1.82KB
sdui-parser (types.js) 0.28KB 0.23KB
sdui-parser (validate.js) 18.27KB 6.20KB
types (ai.js) 4.11KB 2.06KB
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) 5.00KB 2.39KB
types (navigation.js) 0.20KB 0.18KB
types (objectql.js) 0.20KB 0.18KB
types (overlay.js) 0.20KB 0.18KB
types (permissions.js) 2.52KB 1.31KB
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) 17.15KB 6.32KB
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

@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

Contract review

Served-tier: CONTRACT_REVIEW_TIER
Head-sha: fe124b7699db4aefa503812addbf0e1c00c5dd86

① Derived judgments

Round 4 is wording only (measured). 4d341308a..fe124b769 is two commits over the six named files. In ObjectGrid.tsx, maskedColumn.ts and data-display.ts every added or removed line is a // or * comment line (mechanical filter: 0 non-comment lines). Docs lines 1-4 md5 d069943eaa540e3e4d0a4db85dbd48c0; changeset lines 1-5 md5 31ebe89158bc32e03e2ddccb6b8b28ce; git grep NEWCARD at head: 0. Scratch worktree at head, offline install, unlocked at one worker: 25 pins, Tests 25 passed (25); also check:control-bytes OK, check:doc-fences OK, check:new-line-citations 0 new, check-changeset-presence 9 files / 3 packages / 1 changeset, check-changeset-claims against ed8251189 29 distinct pending bodies (35 spans), none among the 22 changesets new in 0c3f70aae..ed8251189. git diff HEAD empty; worktree removed.

The window sentence: true on the pending arm, false on the failed arm. Source at head: the inline effect (:1920-1932) sets rows and ends loading synchronously; the schema effect (:1938-1961) awaits getObjectSchema afterwards; every guard reads objectSchema?.fields?.[key]?.type (:2680, :3715, :3896, :5362). The grid's own fetch path awaits the schema before its rows (:2001 then :2017), so "host-fetched" is the right scope. But the catch at :1951-1955 swallows a failed read and leaves objectSchema null, so the exposure is permanent on failure, as objectui#10706's title says. "Until it settles" (changeset 62, README 400, body 26; "so until it settles" in docs 171 and JSDoc 737; "until that schema settles" in maskedColumn.ts 28) tells the reader it ends. Replacement, long form: "Until it arrives, and for good if that read fails (the grid swallows the failure and keeps its heuristic column types), an untyped view column over a password / secret field draws and hands out the raw value (objectui#10706)." Inline form (docs, JSDoc): "so until it arrives, and for good if that read fails, an untyped view column over a password / secret field draws and hands out the raw value (objectui#10706)". maskedColumn.ts 27-28: "an untyped view column is therefore unmasked until that schema arrives, and for good if the read fails (objectui#10706)." The ObjectGrid.tsx 2662-2664 comment ("until then") promises no end and needs no change.

Unconditional survivor, same class as round 3. isMaskedGridColumn('text', undefined) is false, so in the window a text-typed view column over a secret field is unflagged too (copy, tooltip, edit, export open; it draws the value regardless). Three published places state the opposite unconditionally, and each sits directly BEFORE a sentence that begins "Once the object schema has loaded, the same rule…", which tells the reader the stamp itself does not wait. Changeset 39-43: replace "It reads the column's type and the object-declared type as a narrow-only union, the same shape as the detail page's isMaskedDetailFieldType. So a view that authors type: 'text' over a secret field keeps the refusal, and a view that authors type: 'password' masks a field the object declares as text." with "It reads the column's type and, once the object schema has loaded, the object-declared type as a narrow-only union, the same shape as the detail page's isMaskedDetailFieldType. So a view that authors type: 'password' masks a field the object declares as text from first paint, and a view that authors type: 'text' over a secret field keeps the refusal once the schema has loaded." Docs 163-165: replace "It reads both the view's column type and the object's field type, so a view that shows a secret field as text keeps the flag." with "It reads both the view's column type and, once the object schema has loaded, the object's field type, so a view that shows a secret field as text keeps the flag once that schema has loaded." JSDoc 727-729: replace "reading the view-authored type and the object-declared type as a narrow-only UNION — a view authoring type: 'text' over a secret column keeps the flag." with "reading the view-authored type and, once the grid's object schema has loaded, the object-declared type as a narrow-only UNION — a view authoring type: 'text' over a secret column keeps the flag once that schema has loaded." Body 132: prefix "Once the object schema has loaded, " to "A view that authors type: 'text' over a secret field keeps the refusal". README 388-390 is inside the paragraph its "Once the grid has loaded its object schema" opener scopes, and ObjectGrid.tsx 3893 is a code comment beside the objectSchema read; both stand. The other scoped sentences (changeset 7, 43, 46; README 378; docs 165; JSDoc 729; body 18) are conservative, not false. The zod describe() is table-scoped and needs no window sentence.

Title (title-10643-r3.txt): "withhold a masked grid field's raw value from copy, tooltip, inline edit, the client export, the mobile card and group headers". Not an overclaim of the round-2 class: it names six paths, not a universal reader, and "a masked grid field" is one the grid has recognised; the window is a precondition on recognition and lives in "Not covered". No angle brackets.

The three wording fixes. objectui#10658: closed state_reason=duplicate at 15:46:37Z, closing comment "Closed as a duplicate of objectui#10657 — one family, folded into its closure card"; objectui#10657's triage comment at 15:44:05Z names it. Every 10658 citation at head (changeset 57, 59; docs 153, 155; README 396; JSDoc 717-718) and in the body (22, 132) is the prescribed form. Gate row (body 116): applied verbatim; measured true (29, none range-new). Precision only: ed8251189 itself added one changeset and the range added 22; the five named are those of the three range commits that touched this PR's source files (526fc1125 objectui#9002, 9fbbb17a2 objectui#10580, 4758b33c2 objectui#10625 ×3). Optional tightening: "the five bodies of the ed8251189 range's commits that touched this PR's source files (…)". Overlap bullet (body 136): objectui#10635 merged 9fbbb17a2, objectui#10656 merged 0c3f70aae, both ancestors of the head; of the 10 open PRs only the draft objectui#10278 touches these files (ObjectGrid.tsx, README), PR 5400's 1732 files included.

Body. The only > is the arrow in the JSON-filter ablation label on line 95, inside a code span, not tag-shaped; the only closing keyword is line 1. Changed sentences: 18, 22, 103, 116, 136, 137 true (of 103 I measured the pins and four gates; type-check and doc-links unmeasured by me); 26 and 132 as above.

② Semver level

Unchanged and right: @object-ui/types minor, components and plugin-grid patch, Clause-②: yes. Round 4 adds no code; the changeset frontmatter is byte-identical; check-changeset-presence measured.

③ Boundary flags

  • CI on the head: 43 check runs, 40 success, 3 skipped, 0 in progress, 0 red; Vercel status success.
  • git merge-tree --write-tree against main 383a34f52 (live main, same sha): clean (cdc9a8f83); main has not moved in the 12 files since merge-base ed8251189.
  • Open PRs on these files: objectui#10278 only (draft), as the body says.
  • Unmeasured by me: type-check, lint, the package suites, check-doc-links (CI covers the first two).

Implemented-by: claude/issue-10583-masked-cell-copy
Reviewed-by: session_014mXUNuFomfj24w7s1pZzhN

VERDICT: FAIL

Wording only; the code, pins, gates, merges and CI all hold. Two things to change before landing: (1) "until it settles" implies the exposure ends, but a failed schema read is swallowed and leaves the column raw for the life of the grid; use the replacement sentences above in the changeset, docs, README, JSDoc, maskedColumn.ts and body line 26. (2) The "text over secret keeps the flag" sentence is unconditional in the changeset, docs, JSDoc and body line 132, and false in the window; scope it as given. The title, the 10657 citations, the gate row and the Overlap bullet pass.


Generated by Claude Code

…t; scope the text-over-secret union

Round 5 (wording only, no behaviour change):

- "until it settles" becomes "until it arrives, and for good if that
  read fails": the grid swallows a failed schema read and keeps its
  heuristic column types (objectui#10706).
- The object-declared half of the narrow-only union applies once the
  object schema has loaded; say so wherever the text-over-secret case
  is stated.

Claude-Session: https://claude.ai/code/session_014mXUNuFomfj24w7s1pZzhN
Co-authored-by: Claude <noreply@anthropic.com>
@github-actions

Copy link
Copy Markdown
Contributor

✅ Console Performance Budget

Metric Value Budget
Eager closure (gzip, 329 chunks) 3061.8 KB 3104.5 KB
Main entry chunk (gzip) 148.2 KB 350 KB
Entry file index-YTQzIygP.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.57KB 6.15KB
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) 27.95KB 10.04KB
auth (AuthContext.js) 0.31KB 0.24KB
auth (AuthGuard.js) 2.07KB 1.00KB
auth (AuthProvider.js) 40.17KB 10.58KB
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.39KB 1.21KB
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.52KB 3.45KB
auth (index.js) 3.63KB 1.64KB
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) 27.13KB 7.95KB
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) 547.48KB 131.02KB
core (index.js) 9.52KB 3.79KB
create-plugin (index.js) 27.94KB 9.51KB
data-objectstack (index.js) 223.91KB 62.28KB
fields (index.js) 259.18KB 65.61KB
i18n (LocalizationContext.js) 1.76KB 0.96KB
i18n (builtinAggregateLabels.js) 0.86KB 0.49KB
i18n (currency.js) 2.59KB 1.22KB
i18n (fallbackInterpolation.js) 6.25KB 2.77KB
i18n (i18n.js) 8.87KB 3.64KB
i18n (index.js) 5.24KB 2.27KB
i18n (pickLocalized.js) 9.86KB 3.95KB
i18n (provider.js) 39.40KB 12.91KB
i18n (useDisplayLocale.js) 3.52KB 1.76KB
i18n (useObjectLabel.js) 34.34KB 9.17KB
i18n (useSafeTranslation.js) 5.60KB 2.33KB
layout (index.js) 39.28KB 11.09KB
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) 6.62KB 2.45KB
mobile (useResponsive.js) 0.72KB 0.42KB
mobile (useSpecGesture.js) 5.52KB 2.10KB
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.33KB 3.07KB
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) 16.01KB 3.93KB
plugin-calendar (index.js) 51.52KB 14.64KB
plugin-charts (index.js) 80.42KB 22.22KB
plugin-chatbot (index.js) 198.40KB 47.22KB
plugin-dashboard (index.js) 133.85KB 35.54KB
plugin-designer (index.js) 216.25KB 44.39KB
plugin-detail (index.js) 232.85KB 61.58KB
plugin-editor (index.js) 2.23KB 1.05KB
plugin-form (index.js) 154.22KB 39.52KB
plugin-gantt (index.js) 169.71KB 41.93KB
plugin-grid (index.js) 218.12KB 59.72KB
plugin-kanban (index.js) 48.36KB 15.09KB
plugin-list (index.js) 115.91KB 28.80KB
plugin-map (index.js) 22.85KB 7.58KB
plugin-markdown (index.js) 13.88KB 4.80KB
plugin-report (index.js) 43.55KB 11.99KB
plugin-timeline (index.js) 30.84KB 9.01KB
plugin-tree (index.js) 10.52KB 3.69KB
plugin-view (index.js) 87.83KB 22.01KB
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) 116.21KB 38.14KB
react (data-invalidation.js) 5.05KB 2.08KB
react (index.js) 4.03KB 1.86KB
react (schema-input.js) 4.25KB 2.04KB
react (spec-input.js) 0.20KB 0.18KB
sdui-parser (body-dialect.js) 4.78KB 2.09KB
sdui-parser (codegen.js) 6.58KB 2.74KB
sdui-parser (dashboard-widget-options.js) 3.08KB 1.30KB
sdui-parser (index.js) 5.78KB 2.56KB
sdui-parser (input-type.js) 2.84KB 1.40KB
sdui-parser (kanban-quick-add.js) 3.89KB 1.87KB
sdui-parser (parse.js) 25.28KB 7.80KB
sdui-parser (provenance.js) 3.66KB 1.82KB
sdui-parser (types.js) 0.28KB 0.23KB
sdui-parser (validate.js) 18.27KB 6.20KB
types (ai.js) 4.11KB 2.06KB
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) 5.00KB 2.39KB
types (navigation.js) 0.20KB 0.18KB
types (objectql.js) 0.20KB 0.18KB
types (overlay.js) 0.20KB 0.18KB
types (permissions.js) 2.52KB 1.31KB
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) 17.15KB 6.32KB
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

… objectui#10706

Round 6: citation only, no behaviour change.

Claude-Session: https://claude.ai/code/session_014mXUNuFomfj24w7s1pZzhN
Co-authored-by: Claude <noreply@anthropic.com>
@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

Contract review

Served-tier: CONTRACT_REVIEW_TIER
Head-sha: 2dcc28aae9df25bb36d9741da5dcb5cc15a8d7ef

① Derived judgments

Round 5 is wording only (measured). fe124b769..2dcc28aae is one commit over five files (changeset, docs, README, maskedColumn.ts, data-display.ts); in the two .ts files every added or removed line is a * comment line (mechanical filter: 0 non-comment lines; the same filter over 4d341308a..2dcc28aae, six files, is also 0, so body 103 holds). Docs lines 1-4 md5 d069943eaa540e3e4d0a4db85dbd48c0; changeset lines 1-5 md5 31ebe89158bc32e03e2ddccb6b8b28ce; git grep NEWCARD at head: 0. Scratch worktree at head, offline install, unlocked at one worker: 25 pins, Tests 25 passed (25); check:control-bytes OK; check:doc-fences OK; git diff HEAD empty; worktree removed.

Round 4's replacements: all applied, verbatim. Long form at changeset 61-64, README 398-402 and body 26; inline form at docs 171-173 and JSDoc 738-740; maskedColumn.ts 27-29; the scoped union at changeset 39-43, docs 163-165 and JSDoc 727-730; the body-132 prefix. settles is 0 in the six published places and the body (the settle hits left in ObjectGrid.tsx and data-table.tsx are older, unrelated sentences). Survivor hunt over the six places, the title and the body's withheld-paths table: README 380-390 sits under its "Once the grid has loaded its object schema" opener; ObjectGrid.tsx 2662-2664 ("until then") and 3893-3894 (a comment beside the objectSchema read) stand; maskedColumn.ts 38-44 is the helper's own two-input contract directly beneath its window paragraph; body 18 is scoped; the title names six paths and makes no first-paint claim. No sentence of either class survives.

The new sentences are true at head. (1) "swallows the failure and keeps its heuristic column types": the catch at :1950-1954 only console.warns, objectSchema stays null, and :3019 resolves the cell's type as col.type || objectDefField?.type || inferColumnType(...), so the heuristic answer stays for the life of the grid. (2) "type: 'password' masks a field the object declares as text from first paint": :3019 reads col.type first, so the cell renderer is the mask before any schema, and isMaskedGridColumn('password', undefined) is true at the seam (:3896), so the flag is stamped from first paint too. (3) The scoped union sentences describe :3896, :2680, :3715 and :5362 exactly; the view-typed arm stays conservative.

FAIL, wording, same class as round 3's item (2): every window citation points at a closed duplicate. objectui#10706 was closed state_reason=duplicate at 20:30:29Z, its closing comment "Closed as a duplicate of objectui#10657 — one family, folded into its closure card"; objectui#10657's triage comment at 20:28:53Z carries the row (first paint before the schema, and the failed read), grade priority:p3 · security, open. The round-5 commit is timestamped 20:25:43Z, five minutes before the fold, so the head is stale rather than careless, but it publishes the bare form at seven sites: changeset 64, docs 173, README 402, maskedColumn.ts 29, JSDoc 740, ObjectGrid.tsx 2664 and body 26. Replace (objectui#10706) with (objectui#10657, which folded objectui#10706) at the first five and at body 26; at ObjectGrid.tsx 2664 replace "the host-fetched window, objectui#10706" with "the host-fetched window, objectui#10657, which folded objectui#10706". This is the form round 3 prescribed for objectui#10658 and round 4 verified at every site.

Body. The only closing keyword is line 1; the only > is the arrow in the B2 ablation label on line 95, inside a code span; the title has none. Changed sentences: 26 and 132 verbatim; 103 measured (six files, 0 non-comment lines; the pins, check:control-bytes and check:doc-fences re-run by me on this head; type-check, check:new-line-citations and check-doc-links unmeasured by me). Precision only: line 137's "fresh origin/main 383a34f52" is now one commit behind (f9d772b16), which touches none of the 12 files, so its second clause still holds. Cosmetic: the spliced lines at changeset 43 and docs 165 and 173 run past the files' wrap width.

② Semver level

Unchanged and right: @object-ui/types minor, components and plugin-grid patch, Clause-②: yes. No code moved since 4d341308a; the changeset frontmatter is byte-identical (md5 measured). check-changeset-presence I ran only against main's tip, where it counts main's own movement since the merge-base; the round-4 measurement at the merge-base (9 source files, 3 packages, 1 changeset) is unchanged by a frontmatter-identical head.

③ Boundary flags

  • CI on the head: 42 check runs, 24 success, 3 skipped, 15 in progress (every test shard, Type Check, Lint, Build Docs, README Export Check, Bundle Analysis, Doc Snippet Type Check, Spec Main Shape Gate), 0 red; Vercel status success.
  • git merge-tree --write-tree against current main f9d772b16: clean (526751d1e); merge-base still ed8251189; main's one commit since 383a34f52 touches none of the 12 files.
  • objectui#10706 is closed as a duplicate of objectui#10657 (open); the row now lives on 10657.
  • Unmeasured by me: type-check, lint, the package suites, check:new-line-citations, check-doc-links, check-changeset-presence at the merge-base (CI covers the first two).

Implemented-by: claude/issue-10583-masked-cell-copy
Reviewed-by: session_014mXUNuFomfj24w7s1pZzhN

VERDICT: FAIL

Wording only; both round-4 replacements are applied verbatim, the new sentences are true at head, the code is untouched, the pins and gates pass, the merge is clean and CI is green so far. One thing to change before landing: objectui#10706 was folded into objectui#10657 after the round-5 commit, so replace (objectui#10706) with (objectui#10657, which folded objectui#10706) in the changeset, docs, README, maskedColumn.ts, the JSDoc and body line 26, and reword the ObjectGrid.tsx 2664 comment as given above.


Generated by Claude Code

@github-actions

Copy link
Copy Markdown
Contributor

✅ Console Performance Budget

Metric Value Budget
Eager closure (gzip, 329 chunks) 3061.8 KB 3104.5 KB
Main entry chunk (gzip) 148.2 KB 350 KB
Entry file index-BGDGVeS6.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.57KB 6.15KB
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) 27.95KB 10.04KB
auth (AuthContext.js) 0.31KB 0.24KB
auth (AuthGuard.js) 2.07KB 1.00KB
auth (AuthProvider.js) 40.17KB 10.58KB
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.39KB 1.21KB
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.52KB 3.45KB
auth (index.js) 3.63KB 1.64KB
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) 27.13KB 7.95KB
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) 547.48KB 131.02KB
core (index.js) 9.52KB 3.79KB
create-plugin (index.js) 27.94KB 9.51KB
data-objectstack (index.js) 223.91KB 62.28KB
fields (index.js) 259.18KB 65.61KB
i18n (LocalizationContext.js) 1.76KB 0.96KB
i18n (builtinAggregateLabels.js) 0.86KB 0.49KB
i18n (currency.js) 2.59KB 1.22KB
i18n (fallbackInterpolation.js) 6.25KB 2.77KB
i18n (i18n.js) 8.87KB 3.64KB
i18n (index.js) 5.24KB 2.27KB
i18n (pickLocalized.js) 9.86KB 3.95KB
i18n (provider.js) 39.40KB 12.91KB
i18n (useDisplayLocale.js) 3.52KB 1.76KB
i18n (useObjectLabel.js) 34.34KB 9.17KB
i18n (useSafeTranslation.js) 5.60KB 2.33KB
layout (index.js) 39.28KB 11.09KB
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) 6.62KB 2.45KB
mobile (useResponsive.js) 0.72KB 0.42KB
mobile (useSpecGesture.js) 5.52KB 2.10KB
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.33KB 3.07KB
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) 16.01KB 3.93KB
plugin-calendar (index.js) 51.52KB 14.64KB
plugin-charts (index.js) 80.42KB 22.22KB
plugin-chatbot (index.js) 198.40KB 47.22KB
plugin-dashboard (index.js) 133.85KB 35.54KB
plugin-designer (index.js) 216.25KB 44.39KB
plugin-detail (index.js) 232.85KB 61.58KB
plugin-editor (index.js) 2.23KB 1.05KB
plugin-form (index.js) 154.22KB 39.52KB
plugin-gantt (index.js) 169.71KB 41.93KB
plugin-grid (index.js) 218.12KB 59.72KB
plugin-kanban (index.js) 48.36KB 15.09KB
plugin-list (index.js) 115.91KB 28.80KB
plugin-map (index.js) 22.85KB 7.58KB
plugin-markdown (index.js) 13.88KB 4.80KB
plugin-report (index.js) 43.55KB 11.99KB
plugin-timeline (index.js) 30.84KB 9.01KB
plugin-tree (index.js) 10.52KB 3.69KB
plugin-view (index.js) 87.83KB 22.01KB
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) 116.21KB 38.14KB
react (data-invalidation.js) 5.05KB 2.08KB
react (index.js) 4.03KB 1.86KB
react (schema-input.js) 4.25KB 2.04KB
react (spec-input.js) 0.20KB 0.18KB
sdui-parser (body-dialect.js) 4.78KB 2.09KB
sdui-parser (codegen.js) 6.58KB 2.74KB
sdui-parser (dashboard-widget-options.js) 3.08KB 1.30KB
sdui-parser (index.js) 5.78KB 2.56KB
sdui-parser (input-type.js) 2.84KB 1.40KB
sdui-parser (kanban-quick-add.js) 3.89KB 1.87KB
sdui-parser (parse.js) 25.28KB 7.80KB
sdui-parser (provenance.js) 3.66KB 1.82KB
sdui-parser (types.js) 0.28KB 0.23KB
sdui-parser (validate.js) 18.27KB 6.20KB
types (ai.js) 4.11KB 2.06KB
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) 5.00KB 2.39KB
types (navigation.js) 0.20KB 0.18KB
types (objectql.js) 0.20KB 0.18KB
types (overlay.js) 0.20KB 0.18KB
types (permissions.js) 2.52KB 1.31KB
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) 17.15KB 6.32KB
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

@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

Contract review

Served-tier: CONTRACT_REVIEW_TIER
Head-sha: d45109cb09eaddf9764a602c66274bfc33de1045

① Derived judgments

Round 6 is exactly the six one-line edits (measured). git diff --name-status 2dcc28aae..d45109cb0: M for .changeset/10583-masked-cell-copy.md, content/docs/components/complex/data-table.mdx, packages/plugin-grid/README.md, packages/plugin-grid/src/ObjectGrid.tsx, packages/plugin-grid/src/maskedColumn.ts, packages/types/src/data-display.ts; numstat 1 1 for each. The changed lines: changeset 64 (list-item prose), docs 173 (paragraph prose, outside any fence), README 402 (list item), ObjectGrid.tsx 2664 (a // line), maskedColumn.ts 29 and data-display.ts 740 ( * JSDoc lines). Mechanical filter over the .ts/.tsx part of the delta: 0 non-comment lines; the same filter over 4d341308a..d45109cb0 (six files, four wording commits): 0. Five edits are the single substitution (objectui#10706) to (objectui#10657, which folded objectui#10706); the sixth is the round-5 sentence for ObjectGrid.tsx 2664, verbatim. Control bytes in the added lines: 0. Suite: not re-run by me (comment-only delta; no worktree created); CI's Control Byte Scan, Line Citation Gate, Changeset Declaration, Internal Docs Link Check, Doc Fence Language Check, Type Check and Lint are green on the head.

Both new citations are true. objectui#10657: open, bug · security · priority:p3 · pm:queue; its triage comment 5839121438 (2026-09-25T20:28:53Z), "closure-card scope extended — objectui#10706 folded in": "A fourth position of the masked-column family joins this card's enumeration pin. It is the first paint before the object schema settles ... an untyped view column over a password / secret field resolves its type from objectSchema, which starts null ... A failed getObjectSchema is swallowed as non-fatal (ObjectGrid.tsx ~:1950-1954), which leaves the column drawn in the clear for the life of the grid" and "keep it withheld when the read fails: fail closed". objectui#10706: state: closed, state_reason: duplicate, closed 20:30:29Z; closing comment 5839140717 (20:30:26Z): "Closed as a duplicate of objectui#10657 — one family, folded into its closure card ... objectui#10657 now carries this row: the first paint before the schema settles, and the failed schema read." The round-6 commit (20:38:34Z) postdates the fold, and "which folded" runs the right way: 10657 did the folding.

No bare citation survives. git grep -n "objectui#10706" at head: exactly 6 lines (changeset 64, docs 173, README 402, ObjectGrid.tsx 2664, maskedColumn.ts 29, data-display.ts 740), all the folded form; the digits 10706 outside that form hit only a vector coordinate in examples/schema-catalog (pre-existing, not a citation). 10658 at head appears only as "which folded objectui#10658" (changeset 57 and 59, docs 153 and 155, README 396, data-display.ts 717-718) plus one pre-existing app-shell CHANGELOG line that is not a citation. The zod describe() (data-display.zod.ts 289-292 and the tombstone at 349) names no card. Body: lines 22, 26 and 132 folded; 25 and 131 bare objectui#10657, which is the open card; no 10706 or 10658 outside the folded form.

Frontmatter unchanged. Changeset lines 1-5 md5 31ebe89158bc32e03e2ddccb6b8b28ce at head and at 2dcc28aae; docs lines 1-4 md5 d069943eaa540e3e4d0a4db85dbd48c0 at both.

Cosmetic, not blocking: the spliced lines now run 117 (changeset 64), 137 (docs 173) and 143 (JSDoc 740) columns past their files' wrap width, as round 5 noted for their predecessors.

② Semver level

Unchanged and right: '@object-ui/types': minor, '@object-ui/components': patch, '@object-ui/plugin-grid': patch (md5 above), Clause-②: yes on body line 2. No code has moved since 4d341308a (filter 0). Hand count of the merge-base diff: 12 files, 9 of them source in 3 released packages, 1 changeset, matching the check-changeset-presence row; CI Changeset Declaration and Changeset Bump Policy green on the head.

Title (title-10643-r3.txt, unchanged, no angle brackets): its six paths are the withheld-paths table's rows (the client export covers the table's CSV and the grid's fallback), and every guard is at head: data-table.tsx 1423 (export filter), 1584 (startEdit), 1820 (copy), 2438 (isEditable), 2706 (title); ObjectGrid.tsx 36, 2680 (grouping), 3715 (export), 3896-3897 (stamp), 5362 (card). Still true; it makes no first-paint claim.

The three changed body lines. Line 26: the prescribed sentence verbatim, identical to changeset 64 and README 402. Line 103: "Rounds 4 to 6 (244498f70, fe124b769, 2dcc28aae, d45109cb0)" is exactly git log 4d341308a..d45109cb0, measured wording-only above; the gate re-runs on d45109cb0 are the dev's word (unmeasured by me), corroborated by CI Type Check, Control Byte Scan, Line Citation Gate, Changeset Declaration and Internal Docs Link Check green on the head. Line 137: git merge-tree --write-tree of d45109cb0 against f9d772b16 is clean (cd2423896) and against the live main a695f505f (objectui#8261, landed 20:37:12Z, 82 seconds before the round-6 commit) also clean (c606f3d85); merge-base still ed8251189; no main commit since it touches any of the 12 files, so the second clause holds. "Fresh origin/main f9d772b16" is one commit behind, as 383a34f52 was in round 5: precision only.

FAIL, wording, one unchanged body line is false: line 136. "The only open PR on these files is the draft objectui#10278." Of the 14 open PRs, two touch the 12 files: the draft objectui#10278 (ObjectGrid.tsx, packages/plugin-grid/README.md, head eab4c8e52, as line 135 says) and the draft objectui#10710 (objectui#10685, opened 20:17:35Z, head ded290bc8), which edits packages/types/src/data-display.ts (+124/-14) and has since its first commit ea8ee30db (19:40:53Z). So the sentence has been false since before the round-5 and round-6 commits; round 4 verified it against 10 open PRs, before 10710 existed. The overlap is harmless (measured in ③), but the Overlap bullet is where a reader looks for exactly this, and round 3 required this bullet to be right. Replace the second sentence of line 136 with: "Two open PRs touch these files, both drafts: objectui#10278 above, and objectui#10710 (objectui#10685), which edits packages/types/src/data-display.ts in the DrillDownConfig / ObjectMetricDrillDownConfig region (from base line 2249), while this PR's hunks there are TableColumn (705-751) and StaticTableColumn (811-818); git merge-tree of d45109cb0 with its head ded290bc8 is clean." Optional, in the same edit: in line 137 replace f9d772b16 with a695f505f. Body only: the head's published text is right at every site, so no dev commit is needed.

③ Boundary flags

  • CI on the head: 42 check runs, 30 success (Action Ref Convention, Build & E2E, Build Docs, Bundle Analysis, Changeset Bump Policy, Changeset Claim Re-read, Changeset Declaration, Changeset Fixed Group Check, Changeset Overwrite Report, Control Byte Scan, Doc Component Type Check, Doc Example Id Check, Doc Fence Language Check, Doc Snippet Type Check, Docs Route Eager Closure Check, Governed Surface Queue Guard, Inert vi.mock Specifier Check, Internal Docs Link Check, Line Citation Gate, Lint, Live E2E (informational), Pre-Install Import Graph Check, README Export Check, Shell Escape Residue Scan, Skill Eval Token Check, Skill Example Check, Skill Guide Path Check, Test (dist pins), Type Check, label), 3 skipped (Test (coverage), Test (coverage shard), dependabot), 9 in progress at my last read (Spec Main Shape Gate, Test shards 1/8 to 8/8), 0 red; Vercel status success.
  • git merge-tree --write-tree against current main a695f505f: clean (c606f3d85); merge-base ed8251189; main has not moved in any of the 12 files since it (measured: git log ed8251189..a695f505f over the 12 paths is empty).
  • Open PRs on the 12 files: objectui#10278 (draft) and objectui#10710 (draft); the other 12 open PRs touch none.
  • objectui#10710 (objectui#10685): the one shared file is packages/types/src/data-display.ts; git merge-tree --write-tree of the two heads d45109cb0 and ded290bc8 (merge-base ed8251189) is clean (945b02e86); the hunks are disjoint, this PR at base lines 704 and 763 (TableColumn, StaticTableColumn), 10710 at base lines 2249-2278 and 2386 (PivotAggregation / DrillDownConfig / ObjectMetricDrillDownConfig); 10710 also merges clean with current main (ea93b2168).
  • Labels on the PR: documentation, package: types, package: components, plugin, tests; no needs:contract-review although the body says Clause-②: yes. The seat's omission, recorded as a fact.
  • objectui#10706 is closed as a duplicate of objectui#10657 (open); the round-6 commit postdates the fold.
  • Unmeasured by me: the pins, the package suites and the gates named in body 103 on this head (CI covers Type Check, Lint, control bytes, line citations, changeset declaration and doc links; the test shards were still running). No worktree was created, so none to remove; the /home/user/objectui checkout was not modified, only refs/review/* were fetched.

Implemented-by: claude/issue-10583-masked-cell-copy
Reviewed-by: session_014mXUNuFomfj24w7s1pZzhN

VERDICT: FAIL

Wording only, and in the body alone: the round-6 delta is exactly the six one-line citation edits, both new citations are true, no bare citation survives anywhere in the PR, both frontmatters are unchanged, semver is unchanged, the title is still true of the head, the merge is clean against live main and against objectui#10710, and CI is green so far. One thing to change before landing: body line 136 says the draft objectui#10278 is the only open PR on these files, but the draft objectui#10710 has edited packages/types/src/data-display.ts since before the round-5 commit; use the replacement sentence above (and, optionally, a695f505f in line 137). No dev commit is needed.


Generated by Claude Code

@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

Contract review

Served-tier: CONTRACT_REVIEW_TIER
Head-sha: d45109cb09eaddf9764a602c66274bfc33de1045

① Derived judgments

Head unchanged. REST head.sha is d45109cb09eaddf9764a602c66274bfc33de1045 at 21:01:52Z; refs/review/pr-10643 resolves to it; merge-base with main is still ed8251189; 12 files against it, the same 12 paths round 6 counted.

The landing text is exactly the two prescribed edits (measured). diff pr-body-10643-r6.md pr-body-10643-r7.md: 136,137c136,137 only, 144 lines each. Line 136's first sentence is byte-identical to round 6's; its second sentence equals the round-6 prescription byte for byte. Line 137 equals round 6's line 137 with f9d772b16 replaced by a695f505f and nothing else. title-10643-r3.txt is unchanged (161 bytes, no angle brackets); no code has moved, so round 6's guard-by-guard reading of it stands.

Line 136, re-measured now, sentence by sentence.

  • Open list: 13 open PRs (round 6 counted 14; objectui#10711 has since merged as main 93a558555: plugin-timeline, plugin-detail, app-shell, none of the 12 files). Every open PR's /files was read against the 12 paths (objectui#5400 paged, 400 files; objectui#8941, 42): hits only objectui#10278 (packages/plugin-grid/README.md, packages/plugin-grid/src/ObjectGrid.tsx) and objectui#10710 (packages/types/src/data-display.ts). Both draft: true. "Two open PRs touch these files, both drafts" is true.
  • "objectui#10710 (objectui#10685)": its title ends "(objectui#10685, first half)" and its body opens "Part of finding(plugin-charts,plugin-dashboard): object-chart, object-pivot and object-data-table accept DrillDownConfig members they never read — the sibling blocks of objectui#9002 #10685".
  • Region: at both 10710 heads, its data-display.ts diff against its merge-base f9d772b16 (identical against ed8251189) is -2249,3 +2249,9, -2266, -2268,9, -2278, -2386,0 +2395,102, +124/-14. Base line 2249 lies in the JSDoc that closes at 2261 before export interface DrillDownConfig (2262); 2386 lies in ObjectMetricDrillDownConfig (2367-2392). data-display.ts is byte-identical on main at ed8251189, f9d772b16, a695f505f and 93a558555, so "base line" is unambiguous. This PR's hunks there are -704,0 +705,47 and -763,0 +811,8, pure insertions, so 705-751 and 811-818 are the inserted lines in head numbering (base insertion points 704 and 763). Disjoint from 10710's region in either numbering.
  • "git merge-tree of d45109cb0 with its head ded290bc8 is clean": git merge-tree --write-tree gives 945b02e86, exit 0. True as a reading at the named sha.
  • objectui#10710's head is now a1217b515 (committed 20:52:29Z, before round 6's record posted at 20:57:31Z, so the prescription named the head it had measured). ded290bc8..a1217b515 changes only packages/types/src/objectql.ts (+1/-1), not one of the 12 files, and its five data-display.ts hunks are unchanged. git merge-tree --write-tree d45109cb0 a1217b515 is clean (cac590ab8), merge-base ed8251189; 10710's current head also merges clean with current main (8e48283e5). The picture is unchanged; the named sha is one commit behind in the same way round 6 judged f9d772b16, precision only. Optional: replace "its head ded290bc8" with "its head a1217b515".

Line 137. git merge-tree --write-tree d45109cb0 a695f505f: clean (c606f3d85). Against current main 93a558555: clean (235d6d068). git log ed8251189..93a558555 over the 12 paths is empty, so "main has not moved in this PR's files since ed8251189" holds. "Fresh origin/main a695f505f" is now one commit behind (93a558555, objectui#10711); the durable clause is the second and it holds. Optional: 93a558555.

Nothing else contradicts. Line 135 re-measured: objectui#10278 is still a draft at eab4c8e52 and touches ObjectGrid.tsx and the README (7 files); merge-tree with 4d341308a conflicts in ObjectGrid.tsx only (the README auto-merges), and it conflicts the same way with main at a695f505f and at 93a558555, and with d45109cb0. Line 136's first sentence: 9fbbb17a2 (objectui#10635) and 0c3f70aae (objectui#10656) are ancestors of the head, as are the two merge commits 965716a72 and ef874bd8b. Line 103's "Rounds 4 to 6" list is still exactly git log 4d341308a..d45109cb0 (four docs commits); no round-7 commit exists. Lines 138 and 139 are untouched history. Line 2's Clause-②: yes against the missing label is recorded in ③.

② Semver level

Unchanged: the head has not moved since round 6, so the changeset ('@object-ui/types': minor, '@object-ui/components': patch, '@object-ui/plugin-grid': patch) and Clause-②: yes on body line 2 stand as round 6 measured; CI Changeset Declaration and Changeset Bump Policy are green on the head. Round 7 changes no file.

③ Boundary flags

  • CI on d45109cb0 at 21:01:52Z: 43 check runs, 40 success, 3 skipped (Test (coverage), Test (coverage shard), dependabot), 0 in progress, 0 failed. The two still running when round 6 posted have finished green: Test (shard 1/8) at 20:58:32Z and Spec Main Shape Gate at 20:58:04Z; all eight test shards are success (2/8 to 8/8 finished between 20:55:53Z and 20:57:43Z). Combined commit status: success (Vercel success).
  • Labels on the PR: documentation, package: types, package: components, plugin, tests; no needs:contract-review although body line 2 says Clause-②: yes. The seat's omission, recorded as a fact.
  • Live targets moved since round 6, none changing a verdict: main a695f505f to 93a558555 (objectui#10711, none of the 12 files); objectui#10710 ded290bc8 to a1217b515 (objectql.ts only); open PR count 14 to 13. Merge-trees are clean at all four targets. If the seat wants the body current at posting, swap ded290bc8 to a1217b515 in line 136 and a695f505f to 93a558555 in line 137; neither is required.
  • The PR's published title and body on GitHub are still the round-1 text (title "hands its raw value to nobody", body with the 13-pin evidence table); the landing text judged here exists only in scratch until the seat posts it.
  • Not measured by me: no test or gate was run (body-only round, head unchanged). No worktree was created; the /home/user/objectui checkout was not modified, only refs/review/pr-10643, refs/review/main, refs/review/pr-10710 and refs/review/pr-10278 were fetched. No GitHub write was made.

Implemented-by: claude/issue-10583-masked-cell-copy
Reviewed-by: session_014mXUNuFomfj24w7s1pZzhN

VERDICT: PASS


Generated by Claude Code

@objectstack-fleet objectstack-fleet Bot changed the title fix(components,plugin-grid,types): a masked grid cell hands its raw value to nobody fix(components,plugin-grid,types): withhold a masked grid field's raw value from copy, tooltip, inline edit, the client export, the mobile card and group headers Sep 25, 2026
@objectstack-fleet
objectstack-fleet Bot marked this pull request as ready for review September 25, 2026 21:05
@objectstack-fleet
objectstack-fleet Bot added this pull request to the merge queue Sep 25, 2026
Merged via the queue into main with commit a66e58e Sep 25, 2026
45 checks passed
@objectstack-fleet
objectstack-fleet Bot deleted the claude/issue-10583-masked-cell-copy branch September 25, 2026 21:18
akarma-synetal pushed a commit to akarma-synetal/objectui that referenced this pull request Sep 28, 2026
…nderer's fetch keys on the query it issues, and the map reads once, expanded (objectui#10664) (objectstack-ai#10688)

Fixes objectstack-ai#10664
Clause-②: no

Each data renderer's fetch effect now keys on the inputs of the query it
issues. The map's query waits for a settled definition, so a mount reads
once and that read is expanded. The repeater and four other census rows
re-read when a query input they send changes. None of them re-reads when
an equal value arrives in a new object. Folds objectui#10665 (the
repeater row), per the triage.

Scope, per the rulings: fetch gating and dependency lists only. No
view's `error` lifecycle is touched; that is objectui#10663, dispatched
in parallel. Every new key is a string compared by value (AGENTS.md
objectstack-ai#10).

## What changed, per renderer

| Renderer (package) | Mismatch on the base | Change |
|:--|:--|:--|
| `object-map` / `ObjectMap` (plugin-map) | **Over-key.** The definition
sat in a local `useState` fed by its own effect and was listed in the
fetch effect's dependencies. Measured: 2 `find` per mount, first without
`$expand`; switching the bound object sent the new object's query with
the previous object's expansion (`[['owner'], ['manager']]` for
`depot`). | The definition comes from `useSettledSchema`, keyed on
`recordSourceObjectName`. The object branch waits on `ready` (the
objectui#7895 / objectui#7903 gate) and holds the loading placeholder
while it waits. Host rows and inline `value` sets are not held. |
| `element:repeater` / `RepeaterRenderer` (components) | **Under-key.**
`properties.sort` goes onto `$orderby`; the dependency list had no sort.
| `sortKey`, by content, the way `filterKey` keys the filter. |
| `element:record_picker` (components) | **Under-key**, same shape:
`sort` (flat, or the `dataSource` binding's) goes onto `$orderby`, not
keyed. | `sortKey`, by content. |
| `object-data-table` / `ObjectDataTable` (plugin-dashboard) |
**Over-key**, the map's shape. Measured: expand sets `[null, ['account',
'owner_dept']]` per mount. **Under-key**: the expansion reads
`schema.columns`, which was not keyed. | `useSettledSchema` gate on the
fetching branch. `lookupExpandKey` (the expansion the columns produce,
by content), so a relabelled column does not re-read. |
| Dashboard filter bar options (plugin-dashboard) | **Under-key.**
`optionsFrom.filter` goes onto both option reads (`runtimeFilter`,
`$filter`), not keyed. | `optionsFilterKey`, by content. |
| `object-view` / `ObjectView` non-grid read (plugin-view) |
**Under-key.** The read falls back to `schema.table.sort` for
`$orderby`; the named-view and active-view sort sources are keyed, this
one was not. | `tableSortKey`, by content. |

Fixture re-judged: the `expandFor` helper in
`ObjectDataTable.expandFls-7230.test.tsx` waited for a second `find`, so
it pinned the double read. It now reads the single post-definition
`find` and asserts there is exactly one, so an ungated regression
reddens instead of reading an unexpanded first call. The header of
`ObjectMap.invalidationRefetch-10623.test.tsx` said the map issues two
`find` calls on mount. It now points at the new pin.

## Census: every registered data renderer's fetch effect

Population: renderers registered through `ComponentRegistry` in
`packages/components` and `packages/plugin-*` whose `useEffect` issues a
record read (`find`, `aggregate`, `queryDataset`). Enumerated by a
scratch script over `origin/main` `4758b33`, comparing what each query
reads with the effect's dependency list. The script is not committed;
this table is a reading taken once, not a live count (AGENTS.md objectstack-ai#9).
Field widgets (`LookupField` and siblings), app-shell pages and the
`useViewData` hook (no in-tree renderer consumes it) are outside the
population.

| Renderer | Reading | Action |
|:--|:--|:--|
| the six rows above | mismatch | changed here |
| `object-grid` / `ObjectGrid` (plugin-grid) | **Under-key.** The
`$select` harvest reads `conditionalFormatting`, `rowActionDefs` and
`bulkActionDefs` (predicate operands), and `$searchFields` reads
`searchableFields`. None is keyed. Measured by an uncommitted probe:
adding a `conditionalFormatting` rule referencing `industry` to a
mounted grid issued no read (`$select` stayed `['id','name']`); a fresh
mount with the rule selected `['id','name','industry']`. | **Not
edited.** The file is on seat 2's live claims: objectui#9853 (PR objectstack-ai#10278)
and objectui#10583 (PR objectstack-ai#10643). Handed to the seat. |
| `list-view` / `ListView` (plugin-list) | **Under-key**, same harvest
(`conditionalFormatting`, `rowActionDefs`, `bulkActionDefs`). Same
probe, same reading. | **Not edited.** The file is on objectui#9853's
file surface (PR objectstack-ai#10278, seat 2). Handed to the seat. |
| `RelatedList` (plugin-detail) | The same over-key shape: `expandKey`,
`selectKey` and the arity flag move when the child definition lands, so
a child with expandable columns or a multi-valued relationship reads
twice. | **Not edited.** Ungated by a recorded decision, pinned as
`DECLARED COST` in `RelatedList.multiValueParentScope-7299.test.tsx`.
Raised as an open question in the report, not overridden here. |
| `ObjectTimeline`, `ObjectGallery`, `ObjectKanban`, `ObjectCalendar`,
`ObjectGantt` (main read and quick-filter options), `ObjectTree` |
settled-schema gate; content keys; match | none |
| `ObjectChart`, `ObjectMetricWidget` | match by content keys; the
effect keys on a `useCallback` identity (`fetchData`, `fetchMetric`) |
none (Acceptance notes) |
| `ObjectPivotTable`, `element:number`, tab-count probe
(`containers.tsx`), `record-activity`, `record-history`, reference rail,
`DatasetWidget`, `DatasetReportRenderer`, `LineItemsPanel` | match |
none |

## Evidence

Every measured leg ran through `SchemaRenderer` and each package's own
registration, or rendered the component directly where a sibling test
already does. Vitest resolves every `@object-ui/*` specifier to its
package's `src` (root `vitest.config.mts` alias), so no leg's resolution
path goes through `dist`, and no rebuild leg applies.

**On the base, before any source change** (pins written first):

- `ObjectMap.fetchGate-10664`: exit 1, 4 failed / 3 passed. The 4 are
the SUBJECT cases; the green 3 are the two SETTLES cases and the
CONTROL.
- `data-list.sortKey-10664` + `record-picker.sortKey-10664`: exit 1, 3
failed / 2 passed (3 SUBJECT red, 2 CONTROL green).
- `ObjectDataTable.fetchGate-10664`: exit 1, 3 failed / 3 passed.
- `DashboardFilterBar.optionsFilterKey-10664`: exit 1, 2 failed / 1
passed.
- `ObjectView.tableSortKey-10664`: exit 1, 1 failed / 1 passed.

**Reverse verification**, fix committed first (`46c5432`). Each of the
six source files was set to its `4758b33` blob; the mutation was proven
on disk (blob equals base, not head; each new anchor counted 0). The
seven pin files then ran red, exit 1, 20 failed / 10 passed. The 20 are
every SUBJECT case plus the seven FLS cases, which now assert one read.
The 10 are every CONTROL and SETTLES case. Restore was `git checkout
HEAD -- PATH` under a `trap … EXIT INT TERM`, proven by each blob
equalling HEAD and an empty `git diff HEAD`. The restored run exited 0,
30 / 30.

**Targeted ablations**, both through `ablation-replace.mjs`, which
confirms the anchor hit and verifies the restore:

- `lookupExpandKey` removed from the data table's dependency list, gate
kept: exit 1, only the column-change SUBJECT red (1 failed / 5 passed).
This isolates the columns under-key from the double read.
- `objectSchema` removed from the map's dependency list, gate kept: exit
1, only the CONTROL red. `$expand` stayed `['owner']` where
`['owner','region']` was due. The control can fail for the reason it
exists.

## Gates

The four package suites, the type-check and eslint ran on `2d48eb5`,
after merging `origin/main` `526fc11`. The branch then merged
`origin/main` `4df0f3d` to give `6451600`. That merge touches only
`app-shell` and `plugin-charts`, no file in the four packages here. On
`6451600` the seven pin files and the ratchet gates were re-run. The
components suite's full run is from the `f133b5c` merge head. On
`2d48eb5`, where the merge brought only the `RefreshIndicator` change
into `components`, the component files this PR edits and the
`refresh-indicator` tests were re-run. That is a declared narrowing; CI
runs the full farm.

- `6451600`, the seven pin files: exit 0, 7 / 7 files, 30 / 30 tests.
- `6451600`, exit 0: `check-changeset-presence` (4 changesets for 4
released packages), `check-changeset-overwrite`, `check:control-bytes`,
`check:test-path-roots`, `check:vi-mock-specifiers`,
`check:vi-mock-inherit`, `check:vi-mock-override-shape`,
`check:new-line-citations` (0 new), `check:changeset-claims`,
`check:pending-changeset-literals`, `check:phantom-deps`.
- `2d48eb5`, `pnpm exec vitest run --maxWorkers=2
packages/plugin-dashboard/`: exit 0, 135 / 135 files, 1268 / 1268 tests.
- `2d48eb5`, `… packages/plugin-map/ packages/plugin-view/` plus the 12
`data-list` / `record-picker` / `refresh-indicator` test files in
`components`: exit 0, 94 / 94 files, 743 / 743 tests.
- `f133b5c`, `… packages/components/`: exit 0, 309 files passed and 1
skipped; 3032 tests passed and 17 skipped.
- `f133b5c`, the consumer sample: exit 0, 10 / 10 files, 900 / 900
tests. The sample is the schema-catalog render tests,
`InterfaceListPage.mapConfig`, `widget-dom-leak-sweep`,
`public-block-binding-reach`, three `ListView.map*` and two app-shell
drill tests.
- `2d48eb5`, `pnpm turbo run type-check --filter @object-ui/plugin-map
--filter @object-ui/components --filter @object-ui/plugin-dashboard
--filter @object-ui/plugin-view --concurrency=2`: exit 0, 22 / 22 tasks.
- `2d48eb5`, `pnpm exec eslint` on the 14 changed `.ts`/`.tsx` files,
plain form (what each package's `lint` runs): exit 0, 0 errors. The
warnings are the files' existing `no-explicit-any` and React Compiler
advisories. Each new `useMemo` key draws the same
`preserve-manual-memoization` advisory the `filterKey` beside it already
draws.
- `2d48eb5`, also exit 0: `check-changeset-fixed`,
`check-changeset-no-major`, `check-type-check-coverage`,
`check:element-data-source-declaration`, `check:handler-key-reads`,
`check:self-import`, `check:unreferenced-sources`, and
`check-governed-queue-guard --test` (NOT GOVERNED, 18 paths).
- `check:changeset-claims` (report-only): it names ten pending
changesets that cite files this change edits. Each paragraph was
re-read, and none describes the fetch gating or dependency lists changed
here.

Changesets: one `patch` per touched package (`plugin-map`, `components`,
`plugin-dashboard`, `plugin-view`), each naming its renderers and citing
the card.

## Acceptance notes

- Latent AGENTS.md objectstack-ai#10 identity keys, recorded and not changed here. A
discard does not happen in this tree on its own, so none is reachable
today:
- `ObjectChart` keys its fetch effect on `fetchData`, and
`ObjectMetricWidget` keys on `fetchMetric`; both are `useCallback`
results.
- `LineItemsPanel` keys on `load`, a `useCallback` over the memoised
`listFilterNode` / `orderBy`.
  - `ObjectGrid` lists the memoised `dataConfig` and `schemaFilter`.
  - Each query's content is keyed correctly.
- `useViewData` (`@object-ui/react`, exported, no in-tree renderer
consumer) rebuilds its adapter on an inline `value` set's length alone,
not its content.
- The map and the data table now hold their loading placeholder while a
changed object's definition settles, instead of drawing the previous
object's rows. The gate closes only when the key moves, and on those
branches the key is the queried object, so a closed gate always means a
changed query.

---

_Generated by [Claude
Code](https://claude.ai/code/session_01KUxVUa7e39aNjhkKi1gsoy)_

---------

Co-authored-by: Claude <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

finding(components,plugin-grid,security): Ctrl+C / Cmd+C on a masked password or secret grid cell copies the RAW value to the clipboard

2 participants