Skip to content

docs(plugin-gantt): stop documenting a navigation key the spec refuses - #6256

Merged
yinlianghui merged 2 commits into
mainfrom
claude/issue-6050-gantt-readme-navigation-basepath
Aug 25, 2026
Merged

docs(plugin-gantt): stop documenting a navigation key the spec refuses#6256
yinlianghui merged 2 commits into
mainfrom
claude/issue-6050-gantt-readme-navigation-basepath

Conversation

@yinlianghui

Copy link
Copy Markdown
Collaborator

Fixes#6050

What was wrong

packages/plugin-gantt/README.md:48 documented the record-navigation override as
{ mode: 'page', basePath: '/console/apps/.../campaign' }. basePath is not a
NavigationConfig member, and because NavigationConfigSchema is a strict
object the key did not merely sit there inert — it rejected the whole config
with unrecognized_keys, taking down the mode: 'page' the sentence was
actually teaching. Copy the documented snippet, get no page navigation.

The intent judgment (this was explicitly not a sed)

The sentence teaches one thing: route to the standalone detail page instead of
the drawer
. Verified against the real consumer, useNavigationOverlay:

  • it builds no URL out of the config in any branch — mode: 'page' calls
    onNavigate(recordId, view ?? 'view'), and the only URL it ever constructs
    (the new_window fallback) comes from objectName, not from config;
  • ObjectGantt.tsx:1154 calls the hook with no onNavigate, so a
    page-mode click falls through to the host's onRowClick.

So the destination route is host-owned and was never authorable here under any
spelling. mode: 'page' alone is the whole demonstration, and that is what the
example now shows.

view was NOT substituted for basePath. Triage floated it as a
hypothesis; the schema's own .describe() calls it "Name of the form view to
use for details"
and the hook forwards it to onNavigate as the action
argument. It names a form view, not a route — putting it where basePath stood
would have swapped an invented key for a wrong one. It is documented for what it
actually does, separately.

Vocabulary is aligned with PR #6053's ObjectGanttSchema.navigation doc comment
on merged main, including its instruction not to restate the member list: the
README now points at @objectstack/spec's NavigationConfigSchema instead of
enumerating members.

The measurement (assumptions verified, not inherited)

Probed against the installed @objectstack/spec@17.2.0, carrying a control:

DECLARED MEMBERS (6): ["mode","view","preventNavigation","openNewTab","size","width"] ← from the schema's own shape
README-as-shipped { mode, basePath } FAIL unrecognized_keys keys:["basePath"]
CONTROL bare { mode: page } PASS
CONTROL { mode: page, view: summary_view } PASS
CONTROL all six declared PASS

The controls are what make the failure a key-by-key result rather than a schema
that refuses everything.

Repo-wide basePath sweep: the only read sites are UploadProvider.tsx
(storage prefix), createAuthClient.ts (better-auth URL split) and
@object-ui/layout's AppSchemaRenderer/NavigationRendererprop (an app-nav
href prefix). The layout one is a real basePath but a different concept and is
unreachable from useNavigationOverlay; none of the three is a NavigationConfig
member. No producer/consumer disagreement — this is a docs defect, as filed.

⚠️ The gates are structurally blind to this card

check-doc-snippet-types compiles ts/tsx fences and
check-doc-component-types reads type literals. Neither parses a metadata key
in a README — that gate's own header records schema-key validity as "a different
question with a different answer … left unruled on purpose"
. Every gate below
being green means I broke nothing; it is not evidence the new example is
correct.
The safeParse measurement above and the pin test are the
verification.

The pin (#6053's dev found this defect exactly this way)

packages/plugin-gantt/src/readme-navigation-example.test.tsextracts the
example from the README on every run (bounded to its own ### Create / Edit / Delete / View section, anchor asserted unique) and parses it with
JSON.parseNavigationConfigSchema.safeParse. Nothing is hand-retyped; a
copy would drift and pin nothing. To make mechanical extraction practical the
example moved from inline prose backticks into a json fence — authored
metadata is JSON, and json fences are established in nine other package
READMEs.

The pin ships its own live control: the same parse must still reject an
undeclared key by name, so its green cannot come from a schema that stopped
being strict.

Which assertions would still pass on a revert

Measured, not asserted — two ablations, each proving its mutation on disk
(injected text and removed text grepped separately) and restoring under
trap … EXIT INT TERM, with git diff HEAD --stat empty afterwards:

AblationResultDetail
Revert the README to the pre-fix prose1 failed (1), no testsExtraction throws at module load: Error: no ```json fence follows the navigation-override sentence in the README.
Keep the fence, reinject basePath into it3 failed | 2 passed (5)is a shape NavigationConfigSchema ACCEPTS · ✗ names only members the schema declares · ✗ does not reintroduce the route-prefix key

So, stated plainly: on a straight revert every assertion in the file fails,
but it fails as a suite error rather than five assertion failures — the
extractor dies before any test runs. The two assertions that survive the
sharper ablation are CONTROL: the same parse still REJECTS an undeclared key
and still teaches … page mode — the first is a statement about the schema
rather than about the README and would survive any README revert whose fence
still parsed; the second survives only because that ablation added a key
rather than removing mode.

No ablation here needed a rebuild: the subject is a file read off disk at
runtime plus the prebuilt @objectstack/spec dist, so there is no stale-dist/
path for a mutation to hide behind. Both legs were run from a committed state.

Gates run locally, at the final commit 04facb0bd (tree clean)

GateVerdict line it printed
check-changeset-presence✅ 1 source file(s) of 1 released package(s) changed, and this change declares 1 changeset(s): .changeset/6050-gantt-navigation-basepath.md.
check-changeset-no-major✅ No changeset declares a 'major' bump.
check-doc-fence-languages (+ --self-test)✅ check:doc-fences — … No unknown fence spelling hides one. (self-test: 26 cases pass)
check-doc-component-types✅ Every documented component type is registered.
check-doc-linksLinks are valid across 15 scan roots.
check-control-bytes✅ check-control-bytes: OK (scanned 5159 tracked text file(s); skipped 85 binary).
check-type-check-coverage✅ test type-check coverage: 41/41 packages compile their tests, 0 declared debt…
check-lint-coverage✅ lint coverage: 46/46 packages linted, 0 with outstanding errors (0 total).
check-vi-mock-specifiers✅ check-vi-mock-specifiers: OK (…)
check-shell-escape-residue✅ check-shell-escape-residue: OK (4/4 root(s) resolved…)
pnpm --filter @object-ui/plugin-gantt type-checkexit 0; echoed tsc --noEmit && tsc -p tsconfig.test.json (so it really ran — a zero-match filter exits 0 silently)
vitest run packages/plugin-gantt (from the repo root)Test Files 47 passed (47) · Tests 402 passed (402)
eslint packages/plugin-gantt/src/readme-navigation-example.test.tsexit 0

The vitest count is the Trap ③ sanity check: 47 reported files == 47 files on
disk, so this is not apps/console's 22 running under a false green.

Declared narrowing:check-readme-exports was not run locally — its job
builds every package first, and this README adds no self-import example for it
to judge. CI owns that run, along with the repo-wide pnpm lint.

Two findings fixed in place, both inside this card's own file set

  • tsconfig.test.json names node in types so the README-reading pin
    compiles. Its comment asserted "none of them touches a Node global" — this
    PR makes that false, so the comment is rewritten rather than left standing.
    Same shape as packages/plugin-calendar/packages/layout, which name it for
    the same reason.
  • The extractor's wrapper try/catch was dropped: preserve-caught-error
    requires an attached cause, and Error.cause is ES2022, above this
    project's ES2020 lib. JSON.parse's own SyntaxError throws from the
    extracting line, which is louder than the wrapper was.

Not folded in

#6051 (24 more undeclared gantt keys) is open in this same package and is
untouched here. packages/plugin-gantt/CHANGELOG.md is history and was not
edited.


Generated by Claude Code

The record-navigation override example showed
`{ mode: 'page', basePath: '/console/apps/.../campaign' }`. `basePath` is not
a `NavigationConfig` member: `useNavigationOverlay` — where a gantt's
`navigation` lands — builds no URL out of the config, and `ObjectGantt` calls
it with no `onNavigate`, so a page-mode click falls through to the host's
`onRowClick`. The route was never authorable through this key.
`NavigationConfigSchema` is a strict object, so the key was worse than inert:
it rejected the whole config with `unrecognized_keys`, and the `mode: 'page'`
the sentence was teaching never took effect.
Corrects the example to the shape the sentence actually demonstrates, says who
owns the destination route, and points at the spec for the member list rather
than restating it. Adds a pin that EXTRACTS the example from the README and
parses it against the schema, with a control proving the parse still rejects
an undeclared key.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CSoz9uGhaaSgiq3hshtN7L
`tsconfig.test.json` names `node` in `types` so the README-reading pin
compiles; its comment had recorded that no test in this package touches a Node
global, and that is corrected rather than left standing. The extractor drops
its wrapper try/catch — `JSON.parse`'s own SyntaxError is thrown from the
extracting line, and a wrapper would need `Error.cause` (ES2022) to satisfy
`preserve-caught-error` under this project's ES2020 lib.
Adds the changeset `check-changeset-presence` asked for.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CSoz9uGhaaSgiq3hshtN7L
@github-actionsgithub-actionsBot added documentation Improvements or additions to documentation plugin tests labels Aug 25, 2026
@github-actions

Copy link
Copy Markdown
Contributor

✅ Console Performance Budget

MetricValueBudget
Eager closure (gzip, 52 chunks)3222.7 KB3266.6 KB
Main entry chunk (gzip)153.8 KB350 KB
Entry fileindex-BFemEoLC.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 (consoleActionDispatch.js)0.20KB0.19KB
app-shell (index.js)10.38KB3.90KB
app-shell (runtime-config.js)18.10KB6.51KB
app-shell (types.js)0.01KB0.04KB
app-shell (urlParams.js)10.06KB3.86KB
auth (ActiveOrganizationStorage.js)25.05KB9.16KB
auth (AuthContext.js)0.31KB0.24KB
auth (AuthGuard.js)2.07KB1.00KB
auth (AuthProvider.js)40.18KB10.59KB
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)8.46KB3.43KB
auth (index.js)3.19KB1.44KB
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.30KB1.02KB
auth (useWorkspaceAdminStatus.js)5.13KB2.35KB
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)505.63KB114.68KB
core (index.js)5.30KB2.13KB
create-plugin (index.js)10.08KB3.26KB
data-objectstack (index.js)171.74KB47.48KB
fields (index.js)238.40KB59.89KB
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.62KB3.26KB
i18n (provider.js)23.13KB7.63KB
i18n (useDisplayLocale.js)2.85KB1.45KB
i18n (useObjectLabel.js)33.40KB8.71KB
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.53KB3.38KB
permissions (PermissionContext.js)0.31KB0.25KB
permissions (PermissionGuard.js)0.89KB0.45KB
permissions (PermissionProvider.js)4.64KB1.50KB
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.93KB0.88KB
plugin-ai (index.js)15.75KB3.80KB
plugin-calendar (index.js)46.62KB12.83KB
plugin-charts (index.js)64.66KB18.32KB
plugin-chatbot (index.js)188.21KB44.67KB
plugin-dashboard (index.js)133.35KB34.45KB
plugin-designer (index.js)212.32KB42.81KB
plugin-detail (index.js)244.13KB61.93KB
plugin-editor (index.js)2.46KB1.10KB
plugin-form (index.js)126.39KB30.80KB
plugin-gantt (index.js)164.17KB39.89KB
plugin-grid (index.js)201.14KB54.40KB
plugin-kanban (index.js)52.89KB14.59KB
plugin-list (index.js)111.94KB27.24KB
plugin-map (index.js)20.11KB6.64KB
plugin-markdown (index.js)13.72KB4.69KB
plugin-report (index.js)43.49KB11.93KB
plugin-timeline (index.js)26.49KB7.59KB
plugin-tree (index.js)9.26KB3.13KB
plugin-view (index.js)84.55KB20.74KB
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)4.47KB1.63KB
react (SchemaRenderer.js)54.84KB18.43KB
react (data-invalidation.js)5.05KB2.08KB
react (index.js)1.35KB0.70KB
react (schema-input.js)2.32KB1.24KB
react (spec-input.js)0.20KB0.18KB
sdui-parser (codegen.js)5.41KB2.34KB
sdui-parser (dashboard-widget-options.js)3.08KB1.30KB
sdui-parser (index.js)4.93KB2.24KB
sdui-parser (input-type.js)2.84KB1.40KB
sdui-parser (parse.js)12.13KB3.65KB
sdui-parser (provenance.js)3.66KB1.82KB
sdui-parser (types.js)0.28KB0.23KB
sdui-parser (validate.js)7.54KB2.63KB
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)2.74KB1.41KB
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-inflight.js)8.87KB3.73KB
types (http-retry.js)4.32KB2.02KB
types (icon-key-migration.js)4.26KB1.63KB
types (index.js)4.49KB2.14KB
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 (spec-ui-namespace.js)0.20KB0.19KB
types (system-fields.js)3.33KB1.54KB
types (theme.js)6.28KB2.87KB
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

@yinlianghui
yinlianghui marked this pull request as ready for review August 25, 2026 05:01
@yinlianghui
yinlianghui added this pull request to the merge queueAug 25, 2026
Merged via the queue into main with commit 9b61cf1Aug 25, 2026
28 checks passed
@yinlianghui
yinlianghui deleted the claude/issue-6050-gantt-readme-navigation-basepath branch August 25, 2026 05:12
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentationImprovements or additions to documentationplugintests

Projects

None yet

Development

Successfully merging this pull request may close these issues.

plugin-gantt README's navigation example shows basePath, which no read site consumes and the spec's NavigationConfigSchema refuses

2 participants

@yinlianghui@claude