Skip to content

Record tree and chart as host-composition-only view types, and give tree its icon (#5321) - #5589

Merged
os-sales merged 1 commit into
mainfrom
claude/issue-5321-viewtype-host-only-exemption
Aug 21, 2026
Merged

Record tree and chart as host-composition-only view types, and give tree its icon (#5321)#5589
os-sales merged 1 commit into
mainfrom
claude/issue-5321-viewtype-host-only-exemption

Conversation

@os-sales

Copy link
Copy Markdown
Collaborator

Fixes#5321

Verified at d5895ecaf.

The ruling this implements

ObjectView.generateViewSchema switches on eight view types. The unions an author writes admit six of them — ObjectViewSchema.defaultViewType and NamedListView.type are the same seven-value union, and neither spells tree or chart. The only segment that can is the component's viewsprop, typed ViewType, which carries both.

The maintainer ruled on 2026-08-20 (verbatim 「其他接受你的建议。」): option B — record the host-composition-only exemption, following the objectui#5097 precedent, rather than adding two members to the authored unions. Two more authored members is a permanent authoring-surface obligation with no measured pull; the exemption gets revisited the day a real metadata-authoring need arrives. Option A (widening the unions) was considered and rejected, so packages/types is untouched and this PR widens no authoring surface.

What landed

1. The recordOBJECT_VIEW_HOST_COMPOSITION_VIEW_TYPES in packages/plugin-view/src/ObjectView.tsx, in the shape of the objectui#5097 constant beside it: the verdict, the ruling with its provenance, the live host path, and where each half of the claim is pinned.

2. Noted at the branches — the tree and chart cases carry a note saying they are host-only and that the viewOptions.tree.* surface is host config rather than authoring surface. Without it the next reader sees a maintained config surface and reasonably concludes it is authorable, which is how this card was filed in the first place. The switch is fenced with a #region marker so the branch set can be re-derived from source.

3. The pinsobjectViewHostSurface.test.tsx becomes a two-record ledger. The new record 2 asserts the fenced branch set is the eight the card measured, that no recorded name is stale, and that the record still names exactly the two the ruling was made on. The compile-time half is pinned as type-level assertions, because "an author cannot select these" is a claim about what the compiler accepts and cannot be tested at runtime — types are erased before vitest runs, so a force-cast defaultViewType: 'tree'would reach the branch. A new suite, ObjectView.hostOnlyViewTypes.test.tsx, measures the other half: a host views prop still drives both branches. That reachability is the exemption's whole basis, so it has to keep being measured rather than asserted in prose.

4. The one behaviour changeviewSwitcherSchema's iconMap gains tree: 'list-tree'. It had an entry for every view type except tree, so a tree view fell through to the || 'table' fallback and was drawn with the grid glyph. This is live, not hypothetical: the console's CreateViewDialog offers tree among the view types a user can create, and those records arrive here as the views prop.

One bounded widening, declared

The iconMap annotation moved from Record< string, string > to Record< ViewType, string >, which is more than adding the one missing key. Naming it explicitly because it is the kind of change that should not arrive unannounced:

  • Same defect class. objectui#2916 already fixed this exact gap once, for chart, by adding a single key. Nothing recorded that the map had to be complete, so the next missing member went unnoticed. One more key closes one more instance; the annotation closes the class — a future ViewType member now fails type-check instead of silently rendering as a grid.
  • The shape is pinned by existing evidence, not chosen.ViewSwitcher's own DEFAULT_VIEW_ICONS — the consumer of these strings, in the same package — has always been declared Record< ViewType, LucideIcon >. The duty already existed on the consumer side; only the producer was partial, and that asymmetry is the defect.
  • The value is derived, not invented.tree: 'list-tree' PascalCases to ListTree, the same glyph DEFAULT_VIEW_ICONS.tree already names for this view type. A test asserts the two sides keep agreeing.
  • No new verification surface. Same gate family; type-check already ran on this package.

The runtime || 'table' fallback stays: v.type arrives from a host prop and nothing validates it, so the compiler's totality is not a runtime guarantee. A test pins the fallback as the control.

Verification

All at d5895ecaf, each exit code captured before any pipe, each quoted from the gate's own verdict line.

gateexitverdict line
pnpm --filter @object-ui/plugin-view type-check0tsc --noEmit && tsc -p tsconfig.test.json (silent on success)
pnpm --filter @object-ui/plugin-view lint0229 problems (0 errors, 229 warnings)
pnpm exec vitest run packages/plugin-view/0Test Files 19 passed (19) · Tests 194 passed (194)
node scripts/check-control-bytes.mjs0check-control-bytes: OK (scanned 4638 tracked text file(s); skipped 85 binary)
node scripts/check-changeset-presence.mjs03 source file(s) of 1 released package(s) changed, and this change declares 1 changeset(s)
node scripts/check-changeset-no-major.mjs0No changeset declares a major bump
node scripts/check-changeset-fixed.mjs0All workspace packages are in the changeset fixed group
node scripts/check-type-check-coverage.mjs0test type-check coverage: 41/41 packages compile their tests
node scripts/check-lint-coverage.mjs0lint coverage: 46/46 packages linted, 0 with outstanding errors

Two more gates were re-derived from the diff rather than taken from the dispatch list. check-eager-closure-budget.mjs exits 2 here for a reason unrelated to this change — it reads apps/console/dist/eager-closure.json, which only exists after a console build, and reports itself as a broken gauge, not a passing budget. It is left to CI, which builds the console. published-dist-gate likewise needs a full build; the new test file is excluded from dist twice over by tsconfig.json (by the __tests__ directory rule and by the *.test.tsx name rule), which is the same path every sibling test file in this package takes.

Ablation of the behaviour change. The tree: 'list-tree' line was deleted and the suites re-run. Mutation proven on disk in both directions before reading any result — the anchored code line went 1 to 0, the sibling chart: 'bar-chart-3' line stayed at 1, and git diff --stat showed exactly 1 file changed, 1 deletion(-). Result: 1 test failed, 20 passed — only the icon assertion, so the pin is specific rather than a blanket. It failed with expected 'table' to be 'list-tree', i.e. the exact pre-fix behaviour the card describes. Restore ran from a trap ... EXIT INT TERM and the tree came back byte-identical (git status --porcelain empty, git diff --stat zero lines).

Pin-liveness probe for the type-level assertions. A type-level pin that no project compiles is a phantom check, so it was proven to bite: flipping one assertion to claim treeis authored turned type-check red with error TS2344: Type 'false' does not satisfy the constraint 'true' (exit 2) at that line. Mutation proven on disk both directions, restored by trap.

No ablation for the record and the branch notes. They are documentation. There is nothing to mutate that would produce a meaningful red, and manufacturing one would be theatre.

Declared narrowing. The repo-wide pnpm lint was not run; the package-scoped eslint . was. Three pieces of evidence that this cannot hide a failure in the diff: (a) the population comes from eslint's own config resolution — the repo has exactly one eslint.config.js and no package-level config, so the package run resolves the same rule set as the repo-wide run; (b) --format json reports 34 files linted, 0 errors, 229 warnings, and all three changed source files are in that population by path; (c) that config enables no type-aware linting — no projectService, no parserOptions.project, no project: — so no rule reads type information and this diff cannot move the verdict on any file it did not touch. The four warnings the new suite adds are no-explicit-any on its test doubles, matching every sibling suite in the directory.

Found while measuring, filed separately

Two other entries in the same iconMap supply names that no longer resolve: chart: 'bar-chart-3' and gantt: 'gantt-chart' are absent from lucide 1.31's icons record — lucide keeps them as deprecated named exports while dropping them from the by-name lookup, so the compiler cannot see it. getViewIcon does not fall back to a default once an explicit icon is set, so those two view types render with no icon at all today. Different defect, different remedy (new names chosen, not a key added), out of this card's fence: filed as #5586. The new suite's resolvability assertion is deliberately scoped to tree alone, with a comment saying why, so widening it lands with that card instead of as a surprise red.


Generated by Claude Code

…its icon (#5321)
`ObjectView.generateViewSchema` renders eight view types; the unions an author
writes (`ObjectViewSchema.defaultViewType`, `NamedListView.type`) admit six of
them, so `tree` and `chart` are reachable only through the component's `views`
prop. Per the maintainer ruling of 2026-08-20, both stay recorded as
host-composition-only surfaces rather than being added to those unions,
following the objectui#5097 precedent.
- `OBJECT_VIEW_HOST_COMPOSITION_VIEW_TYPES` records the exemption next to the
#5097 one, with the two branches noted in place and the switch fenced by a
`#region` so the branch set can be re-derived.
- The record is pinned in the #5097 ledger: the branch set comes from the
fence, and type-level assertions fail `type-check` if either authored union
grows a `tree` or `chart` member.
- Reachability is measured in a new suite: a host `views` prop still drives
both branches.
- Behaviour: `iconMap` is now total over `ViewType` and gains
`tree: 'list-tree'`, so a host tree view stops falling through to the
`'table'` fallback and the grid glyph.
Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012u2pRjcqAYtoEjgr3wwhnK
@github-actions

Copy link
Copy Markdown
Contributor

✅ Console Performance Budget

MetricValueBudget
Eager closure (gzip, 52 chunks)3784.8 KB3867.2 KB
Main entry chunk (gzip)151.2 KB350 KB
Entry fileindex-vOcDNOGv.js
StatusPASS

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

PackageSizeGzipped
app-shell (index.js)10.04KB3.72KB
app-shell (runtime-config.js)8.91KB2.99KB
app-shell (types.js)0.01KB0.04KB
app-shell (urlParams.js)10.06KB3.86KB
auth (AuthContext.js)0.31KB0.24KB
auth (AuthGuard.js)1.17KB0.53KB
auth (AuthProvider.js)29.34KB7.05KB
auth (AuthShell.js)3.49KB1.40KB
auth (ForgotPasswordForm.js)12.21KB3.45KB
auth (LoginForm.js)18.15KB5.39KB
auth (PreviewBanner.js)0.90KB0.50KB
auth (RegisterForm.js)6.65KB2.22KB
auth (SocialSignInButtons.js)9.61KB3.89KB
auth (UserMenu.js)3.41KB1.23KB
auth (auth-gate-events.js)1.29KB0.66KB
auth (authStyles.js)5.04KB1.72KB
auth (createAuthClient.js)40.21KB10.80KB
auth (createAuthenticatedFetch.js)6.35KB2.43KB
auth (index.js)2.77KB1.22KB
auth (invitation-status.js)1.22KB0.70KB
auth (org-roles.js)6.66KB2.78KB
auth (phone-identifier.js)1.11KB0.66KB
auth (types.js)0.59KB0.35KB
auth (useAuth.js)5.02KB0.89KB
auth (useIsWorkspaceAdmin.js)3.04KB1.45KB
collaboration (CommentThread.js)26.08KB7.56KB
collaboration (LiveCursors.js)3.17KB1.27KB
collaboration (PresenceAvatars.js)6.49KB2.64KB
collaboration (PresenceProvider.js)2.79KB1.13KB
collaboration (index.js)1.68KB0.73KB
collaboration (useCollaborationTranslation.js)6.05KB2.52KB
collaboration (useCommentSearch.js)1.98KB0.88KB
collaboration (useConflictResolution.js)7.75KB1.86KB
collaboration (useMentionNotifications.js)1.81KB0.68KB
collaboration (usePresence.js)6.33KB1.84KB
collaboration (useRealtimeSubscription.js)7.91KB2.01KB
components (index.js)506.88KB113.68KB
core (index.js)4.51KB1.80KB
create-plugin (index.js)10.08KB3.26KB
data-objectstack (index.js)159.80KB44.33KB
fields (index.js)237.61KB59.63KB
i18n (LocalizationContext.js)1.76KB0.96KB
i18n (currency.js)1.22KB0.64KB
i18n (i18n.js)4.28KB1.75KB
i18n (index.js)3.44KB1.39KB
i18n (pickLocalized.js)7.22KB3.08KB
i18n (provider.js)23.13KB7.63KB
i18n (useDisplayLocale.js)2.85KB1.45KB
i18n (useObjectLabel.js)30.51KB7.57KB
i18n (useSafeTranslation.js)7.77KB3.13KB
layout (index.js)38.95KB10.97KB
mobile (MobileProvider.js)0.92KB0.49KB
mobile (ResponsiveContainer.js)0.94KB0.38KB
mobile (breakpoints.js)1.51KB0.70KB
mobile (createOfflineDataSource.js)5.61KB1.75KB
mobile (index.js)1.55KB0.62KB
mobile (offlineQueue.js)3.91KB1.35KB
mobile (pwa.js)0.97KB0.49KB
mobile (serviceWorker.js)1.48KB0.62KB
mobile (serviceWorkerSource.js)3.41KB1.48KB
mobile (useBreakpoint.js)1.54KB0.65KB
mobile (useGesture.js)6.96KB1.98KB
mobile (useOfflineSync.js)1.99KB0.72KB
mobile (usePullToRefresh.js)2.53KB0.85KB
mobile (useResponsive.js)0.72KB0.42KB
mobile (useResponsiveConfig.js)1.37KB0.63KB
mobile (useSpecGesture.js)4.32KB1.64KB
mobile (useTouchTarget.js)1.01KB0.54KB
permissions (MePermissionsProvider.js)9.35KB3.31KB
permissions (PermissionContext.js)0.31KB0.25KB
permissions (PermissionGuard.js)0.89KB0.45KB
permissions (PermissionProvider.js)4.42KB1.42KB
permissions (evaluator.js)5.12KB1.74KB
permissions (index.js)0.93KB0.41KB
permissions (store.js)0.91KB0.42KB
permissions (useFieldPermissions.js)1.28KB0.53KB
permissions (usePermissions.js)1.81KB0.83KB
plugin-ai (index.js)15.75KB3.80KB
plugin-calendar (index.js)46.62KB12.83KB
plugin-charts (index.js)64.72KB18.35KB
plugin-chatbot (index.js)181.21KB43.14KB
plugin-dashboard (index.js)128.53KB32.97KB
plugin-designer (index.js)212.39KB42.83KB
plugin-detail (index.js)242.15KB60.89KB
plugin-editor (index.js)2.46KB1.10KB
plugin-form (index.js)125.07KB30.43KB
plugin-gantt (index.js)164.10KB39.87KB
plugin-grid (index.js)200.79KB54.26KB
plugin-kanban (index.js)52.93KB14.60KB
plugin-list (index.js)111.70KB27.17KB
plugin-map (index.js)20.06KB6.62KB
plugin-markdown (index.js)13.72KB4.69KB
plugin-report (index.js)43.49KB11.93KB
plugin-timeline (index.js)26.68KB7.66KB
plugin-tree (index.js)8.50KB2.88KB
plugin-view (index.js)84.50KB20.68KB
providers (DataSourceProvider.js)0.75KB0.39KB
providers (MetadataProvider.js)1.37KB0.59KB
providers (ThemeProvider.js)1.90KB0.85KB
providers (UploadProvider.js)11.66KB3.50KB
providers (index.js)0.45KB0.23KB
providers (types.js)0.01KB0.04KB
react-runtime (index.js)5.62KB2.34KB
react (LazyPluginLoader.js)3.77KB1.33KB
react (SchemaRenderer.js)43.66KB14.77KB
react (data-invalidation.js)5.05KB2.08KB
react (index.js)1.33KB0.69KB
react (schema-input.js)1.45KB0.83KB
react (spec-input.js)0.20KB0.18KB
sdui-parser (codegen.js)5.41KB2.34KB
sdui-parser (index.js)4.77KB2.16KB
sdui-parser (input-type.js)2.84KB1.40KB
sdui-parser (parse.js)10.76KB3.17KB
sdui-parser (provenance.js)3.66KB1.82KB
sdui-parser (types.js)0.29KB0.24KB
sdui-parser (validate.js)6.92KB2.40KB
types (ai.js)0.20KB0.17KB
types (api-types.js)0.20KB0.18KB
types (app.js)2.87KB0.99KB
types (base.js)0.20KB0.18KB
types (blocks.js)0.20KB0.18KB
types (complex.js)0.20KB0.18KB
types (crud.js)0.20KB0.18KB
types (dashboard-filter-alias.js)6.23KB2.74KB
types (data-display.js)0.20KB0.18KB
types (data-protocol.js)0.20KB0.19KB
types (data.js)0.20KB0.18KB
types (designer.js)1.87KB0.85KB
types (disclosure.js)0.20KB0.18KB
types (error-code.js)1.54KB0.88KB
types (feedback.js)0.20KB0.18KB
types (field-types.js)0.20KB0.18KB
types (form.js)0.20KB0.18KB
types (http-retry.js)4.32KB2.02KB
types (index.js)3.08KB1.53KB
types (layout.js)0.20KB0.18KB
types (managed-by.js)0.19KB0.18KB
types (mobile.js)2.59KB1.31KB
types (navigation.js)0.20KB0.18KB
types (objectql.js)0.20KB0.18KB
types (overlay.js)0.20KB0.18KB
types (permissions.js)0.20KB0.18KB
types (plugin-scope.js)0.20KB0.18KB
types (record-components.js)0.20KB0.19KB
types (record-semantics.js)1.28KB0.67KB
types (registry.js)0.20KB0.18KB
types (reports.js)0.20KB0.18KB
types (spec-report.js)5.05KB1.93KB
types (system-fields.js)3.33KB1.54KB
types (theme.js)0.20KB0.18KB
types (ui-action.js)3.40KB1.71KB
types (views.js)0.20KB0.18KB
types (widget.js)0.20KB0.18KB

Size Limits

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

@os-sales
os-sales marked this pull request as ready for review August 21, 2026 15:47
@os-sales
os-sales added this pull request to the merge queueAug 21, 2026
Merged via the queue into main with commit cad512fAug 21, 2026
23 checks passed
@os-sales
os-sales deleted the claude/issue-5321-viewtype-host-only-exemption branch August 21, 2026 15:47
Sign up for freeto 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.

ObjectViewSchema.defaultViewType omits tree and chart, so two generateViewSchema branches are unreachable from authored metadata (host-prop only)

2 participants

@os-sales@claude