Skip to content

feat(plugin-timeline): declare navigation on object-timeline, by reference to the spec (objectui#8654) - #11324

Merged
objectstack-fleet[bot] merged 2 commits into
mainfrom
claude/issue-8654-timeline-navigation
Oct 1, 2026
Merged

objectstack-fleet[bot] merged 2 commits into
mainfrom
claude/issue-8654-timeline-navigation

Conversation

@objectstack-fleet

Copy link
Copy Markdown
Contributor

Fixes #8654
Clause-②: yes

navigation is now declared on the object-timeline block, by reference to the spec. This is the timeline arm of the objectui#8652 maintainer ruling (verbatim 「B」): declare the key on the platform element schema first, then mirror it here. The platform half shipped in @objectstack/spec 17.5.0, the release this repo resolves: ComponentPropsMap['object-timeline'] declares navigation: NavigationConfigSchema.optional(), and its docblock names it the spec half of that ruling, honoured standalone, with no enclosing view.

Dispatched by the domain:spec @ objectui seat (objectui#10217), session https://claude.ai/code/session_01VhxTqosz7wn54ahqyxgERT.

Premises, re-measured on origin/main cd5b19a7e before the first edit

  • H1 holds; no edit there. ObjectTimelineBlockSchema reads the spec row by reference in its properties bag. On the installed 17.5.0, { type: 'object-timeline', properties: { navigation: { mode: 'drawer' } } } parses on safeValidateSchema and on StrictAnyComponentSchema. A bad mode is refused at properties.navigation.mode and a bogus member at properties.navigation, on both faces. A parse writes no default. objectql.zod.ts is untouched.
  • H2 confirmed, but its placement is falsified. The TypeScript checker (getPropertyOfType, not a grep) reads navigation as NOT DECLARED on ObjectTimelineProps['schema'] and on TimelineSchema; the read went through (schema as any).navigation. The same instrument's lit control is the reason this PR does not put the member on TimelineSchema: objectName, a member of the same spec row, reads DECLARED on the plugin-local half of ObjectTimelineProps['schema'] and NOT DECLARED on TimelineSchema. A nonsense key reads not declared on both.
    • TimelineSchema is the presentational rail (type: 'timeline'). Its docblock calls its vocabulary disjoint from the object-bound one by design, and TimelineRenderer never reads navigation.
    • The spec declares navigation on the object-timeline row only and has no timeline component row. Declaring it on TimelineSchema would add the key to a face the spec does not declare it on (the dispatch's zone-1 ruling forbids that).
    • Measured: the strict face refuses objectName on type: 'timeline'. A navigation declared there could not build the new_window URL, which needs the object name.
    • objectui#8220 handled filter and sort on these same two tags the same way: registration inputs on both tags, TimelineSchema left alone.
    • ⇒ The member is declared on the object-bound half of ObjectTimelineProps['schema'] in plugin-timeline/src/ObjectTimeline.tsx, beside objectName, timeline, filter and sort. That file is inside the claim. @object-ui/types is NOT changed: neither data-display.ts nor zod/data-display.zod.ts is touched, so this PR does not overlap the objectui#6152 round-5 region.
  • H3 confirmed. Neither registration published navigation. The description was written from measurement on the real component, and it differs from the board's and the calendar's in two places, as listed under Acceptance notes.
  • H4 confirmed: the guard owes nothing here and cannot take a pin yet. registry-inputs-spec-parity.test.ts books object-timeline in UNJUDGED_SPEC_BLOCKS, owed to objectui#11168 (it does not load the lazily registered block). Its assertion every member pin names a key that is still array/object-armed on a covered block would refuse a MEMBER_PINS entry for object-timeline.navigation. So that file is NOT edited. The pin file below names the block and the key, so the locator accepts it when slice 3 registers the entry.

What changed

  • @object-ui/plugin-timeline:
    • ObjectTimelineProps['schema'] declares navigation?: ViewNavigationConfig, the spec's NavigationConfig type that ObjectGridSchema, ObjectKanbanSchema and ObjectCalendarSchema use. ObjectTimeline reads schema.navigation with no cast. The read still supplies no default (see Acceptance notes).
    • Both registrations, object-timeline and view:timeline, publish a navigation input typed object, with one shared description. Both tags carry it for the reason both carry filter and sort: view:timeline is the same renderer under a second tag. That is also the calendar precedent from PR objectui#11292 (OBJECT_CALENDAR_INPUTS, shared by object-calendar and calendar).
    • Docs (AGENTS.md Add automated testing infrastructure and CI/CD workflows #2): README.md and content/docs/plugins/plugin-timeline.mdx document the object-bound block's properties bag and its navigation key, member by member, with the three cases that open nothing.
  • Changeset .changeset/8654-timeline-navigation.md: @object-ui/plugin-timeline minor (a widening). The fixed group moves together anyway.

Pins

  • object-timeline-navigation-8654.test.tsx:
    • the spec premise, read live from the installed spec: it declares the key, and it refuses a bad mode at navigation.mode;
    • on both validator faces, value-level: every mode parses, a bad mode is refused at properties.navigation.mode, a bogus member is refused, the retired view member is refused, and a parse writes no default;
    • the TS face: an Equal assertion, a @ts-expect-error bad-mode row, and a valid block that annotates with no cast;
    • both registrations: the input is declared as object, the html tier accepts the block and refuses a string by kind, and a control prop is still reported as unknown;
    • end to end through the real SchemaRenderer and registry, on both tags: an authored drawer block opens the record, and the same node without the key opens nothing.
  • timelineNavigationMembers-8654.test.tsx, the member pin: every member through a real entry click on the real ObjectTimeline, useNavigationOverlay and overlay shell:
    • drawer (the lit control), modal and popover open the entry's record;
    • none opens nothing, and preventNavigation outranks an overlay mode;
    • new_window opens the record page in a new tab, and openNewTab does too, outranking both page and drawer;
    • size resolves through the bucket table, a deprecated width wins over size, and auto gives the unsized width;
    • a parent onRowClick outranks the whole key;
    • four warning rows hold the description to the truth: an absent key, page, a mode-less block and split each open nothing.

Verification, on b6a1f6057 (the head of this PR)

  • The dependency closure was built first (pnpm --filter '@object-ui/plugin-timeline^...' run build, 9 packages). pnpm --filter @object-ui/plugin-timeline run type-check echoed tsc --noEmit && tsc -p tsconfig.test.json and exited 0. tsc -p tsconfig.test.json --listFilesOnly lists both new pin files, with the objectui#8220 pin as the control.
  • pnpm exec vitest run packages/plugin-timeline/: Test Files 41 passed (41), Tests 461 passed | 34 skipped (495). The skips are all in the existing dateOnlyZone-10866 files. The two new files: 22 + 16 passed.
  • Neighbouring suites that read these registrations, this source, or the object-timeline arm: 26 files, Tests 1409 passed (1409). They cover:
    • console: registry-inputs-spec-parity, public-contract, public-block-binding-reach, timelineAxisRefusalReach-7070, filterContextTokensSweep-10666, pullToRefreshOwner-10105;
    • types: object-timeline-arm-10859-b3, object-kanban-calendar-navigation-8652, timeline-declared-keys, zod-mirror-parity, base-bind-declared, timeline-catalog-fixture-migrated;
    • navigationOverlayConsumers.onRowClickArity-9357, ObjectTree.schemaTyped-8655, registered-types-validate-ratchet-10859, catalog-gallery-render, the list and view timeline-binding suites;
    • the script tests for component-surface-parity, designer-field-key-parity, handler-key-read-sites, prompt-component-keys, sdui-registration-pins, registry-bare-names and date-only-zone-pins.
  • Gates exiting 0:
    • check:designer-field-key-parity, check:component-surface-parity, check:prompt-keys, check:handler-key-reads, check:registry-bare-names, check:element-data-source-declaration, check:spec-symbols;
    • check:doc-types, check:doc-fences, check:doc-example-ids, check:doc-example-readers;
    • check:new-line-citations, check:control-bytes, check:installed-pin-claims, check:comment-mask-corpus, check:unreferenced-sources, check:phantom-deps, check:shell-escape-residue, check:i18n-keys;
    • check:test-path-roots and the three check:vi-mock-*;
    • check:changeset-claims, check:pending-changeset-literals, changeset presence and no-major;
    • check:readme-exports, check:doc-snippets and check:doc-examples, each run after the doc-snippet build closure was built (the first readme-exports run reported a collapsed population on an unbuilt tree, which is not a verdict);
    • check:sdui-registration-pins and check:eager-closure, each run after a full console build (the first console build stopped on an unbuilt @object-ui/plugin-tree, which is a prerequisite miss, not a verdict).
  • Lint, as a declared narrowing:
    • eslint --no-inline-config --format json over the 4 changed TS files reports 4 files and 0 errors.
    • Warnings against the base blobs: ObjectTimeline.tsx 28 to 27 (the removed cast) and index.tsx 5 to 5. The new contract pin carries 1, the deliberate any in its Equal control.
    • eslint.config.js enables no type-aware linting (no parserOptions / projectService). None of the 10 rule sources under eslint-rules/ reads the filesystem; as a control, 9 of them contain context.report. So this diff cannot move a verdict on an untouched file.
    • The repo-wide pnpm lint is left to CI.

Ablations, each committed first, run through ablation-replace.mjs, and restored to HEAD

Each ablation was proven landed by anchor count and blob change. Each restore was proven by blob equals HEAD and an empty git diff HEAD, and the tree was clean afterwards.

  • TS member removed (renamed away, so the import stays used): tsc -p tsconfig.test.json exits 2, with TS2344 on the Equal assertion and TS2578 (unused @ts-expect-error) on the bad-mode row. This was the predicted direction.
  • Renderer read ignores the authored key (navigation: undefined): 11 failed and 27 passed. The 11 are exactly the rows where a click opens something: 9 member rows and the 2 end-to-end drawer rows. Every "opens nothing" row and the parent-handler row stayed green. ⚠️ The count pre-registered before the run was 12, a miscount by me; enumerating the "opens something" rows gives 11.
  • object-timeline registration row removed (the view:timeline row kept): 2 failed and 20 passed, as predicted. The failures were the declared-as-object row and the html-tier row for that tag. The first attempt was a no-op, which the tool refused before running anything: the replacement was a suffix of the anchor, so its count could not rise. It was re-anchored and re-run.
  • The validator face was not ablated, because this PR does not change it. Its rows pin the existing by-reference reading (objectui#10859 batch 3).

Acceptance notes

  • ⚠️ Three cases open nothing on a standalone timeline. All three are measured on the real component. The description, the docs and four pin rows state them. They are not changed here.
    • An absent key opens nothing. Unlike ObjectKanban and ObjectCalendar, ObjectTimeline supplies no ?? { mode: 'drawer' } default, so the hook resolves the spec's page.
    • page, and a block written without mode, open nothing. This is the objectui#11293 family, and the timeline is a third site of it.
    • split opens nothing. The timeline hands NavigationOverlay no mainContent, and the split shell returns nothing without it. The board, the calendar, the gantt and the grid all pass it. This is reported to the seat as a finding.
  • onItemClick fires twice per entry click when it is the only host handler. ObjectTimeline hands onRowClick ?? onItemClick to the hook, which calls it. The composed onItemClick then calls onItemClick again after navigation.handleClick. This was measured once by a probe (two calls, with 2 and 1 arguments). No in-repo producer passes onItemClick to ObjectTimeline. Not pinned. Carrier: none.
  • The strict authoring face refuses objectName on type: 'timeline', while the view:timeline registration marks it required: true. That is pre-existing and was observed while choosing the TS face. It is not measured through a public door. Carrier: none.
  • Overlap: PR objectui#11312 (objectui#11117) is in objectql.zod.ts, which this PR does not touch. objectui#6152 round 5 is in data-display.ts / zod/data-display.zod.ts, which this PR does not touch either. registry-inputs-spec-parity.test.ts is not edited.

Generated by Claude Code

claude added 2 commits October 1, 2026 03:14
…ference to the spec (objectui#8654)

`@objectstack/spec` 17.5.0 declares `navigation: NavigationConfigSchema.optional()`
on the `object-timeline` row, for the block standalone: the timeline arm of the
objectui#8652 maintainer ruling. This is the objectui half.

- `ObjectTimelineProps['schema']` declares `navigation?: ViewNavigationConfig`
  on its object-bound half, beside `objectName`, `timeline`, `filter` and
  `sort`; the read drops its `as any` cast. `TimelineSchema` (the
  presentational rail, which never reads the key, and which has no spec row)
  is untouched.
- Both registrations (`object-timeline`, `view:timeline`) publish a
  `navigation` input, typed `object`, whose description states what the
  renderer does with each member, as measured on the real component.
- The validator face (`ObjectTimelineBlockSchema`'s `properties` bag) already
  reads the spec row by reference and needed no edit; its value-level
  verdicts are pinned here.

Pins: object-timeline-navigation-8654 (spec premise, both validator faces,
TS face, both registrations, an end-to-end mount on both tags) and
timelineNavigationMembers-8654 (every member through a real entry click).

Claude-Session: https://claude.ai/code/session_01VhxTqosz7wn54ahqyxgERT
Co-authored-by: Claude <noreply@anthropic.com>
…its changeset (objectui#8654)

AGENTS.md #2: the README and the docs page document the object-bound
`object-timeline` block's `properties` bag and its `navigation` key, by
reference to the spec's `NavigationConfig` (`ViewNavigationConfig`). Each
states what an entry click does with each member, and the three things that
open nothing on a timeline no parent view navigates for: an absent key,
`page` (also what a block without `mode` resolves to, objectui#11293) and
`split`.

Changeset: `@object-ui/plugin-timeline` minor (a widening).

Claude-Session: https://claude.ai/code/session_01VhxTqosz7wn54ahqyxgERT
Co-authored-by: Claude <noreply@anthropic.com>
@github-actions github-actions Bot added documentation Improvements or additions to documentation plugin tests labels Oct 1, 2026
@github-actions

github-actions Bot commented Oct 1, 2026

Copy link
Copy Markdown
Contributor

✅ Console Performance Budget

Metric Value Budget
Eager closure (gzip, 330 chunks) 3585.7 KB 3607.4 KB
Main entry chunk (gzip) 150.4 KB 350 KB
Entry file index-C7nYCiwC.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.88KB 6.25KB
app-shell (runtime-config.js) 20.68KB 7.36KB
app-shell (types.js) 0.01KB 0.04KB
app-shell (urlParams.js) 10.11KB 3.87KB
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.22KB 10.61KB
auth (AuthShell.js) 3.49KB 1.40KB
auth (ForgotPasswordForm.js) 12.21KB 3.45KB
auth (LoginForm.js) 18.17KB 5.40KB
auth (PreviewBanner.js) 0.90KB 0.50KB
auth (RegisterForm.js) 6.72KB 2.24KB
auth (SocialSignInButtons.js) 9.70KB 3.93KB
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.70KB 10.94KB
auth (createAuthenticatedFetch.js) 8.54KB 3.46KB
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) 571.60KB 136.88KB
core (index.js) 10.00KB 3.96KB
create-plugin (index.js) 27.94KB 9.51KB
data-objectstack (index.js) 229.96KB 63.80KB
fields (index.js) 261.05KB 66.23KB
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 (translateFn.js) 0.20KB 0.18KB
i18n (useDisplayLocale.js) 3.52KB 1.76KB
i18n (useObjectLabel.js) 34.49KB 9.23KB
i18n (useSafeTranslation.js) 5.60KB 2.33KB
layout (index.js) 40.95KB 11.48KB
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.86KB 5.00KB
permissions (PermissionContext.js) 0.31KB 0.25KB
permissions (PermissionGuard.js) 0.89KB 0.45KB
permissions (PermissionProvider.js) 6.52KB 2.26KB
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) 52.90KB 15.35KB
plugin-charts (index.js) 84.09KB 22.93KB
plugin-chatbot (index.js) 198.22KB 46.97KB
plugin-dashboard (index.js) 139.34KB 37.30KB
plugin-designer (index.js) 216.45KB 44.60KB
plugin-detail (index.js) 244.79KB 64.47KB
plugin-editor (index.js) 2.23KB 1.05KB
plugin-form (index.js) 173.70KB 44.63KB
plugin-gantt (index.js) 173.03KB 43.07KB
plugin-grid (index.js) 231.61KB 63.61KB
plugin-kanban (index.js) 49.32KB 15.48KB
plugin-list (index.js) 116.63KB 28.97KB
plugin-map (index.js) 23.54KB 7.83KB
plugin-markdown (index.js) 13.88KB 4.80KB
plugin-report (index.js) 44.04KB 12.21KB
plugin-timeline (index.js) 33.91KB 10.00KB
plugin-tree (index.js) 11.20KB 3.89KB
plugin-view (index.js) 90.32KB 22.76KB
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.81KB 3.58KB
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) 119.55KB 39.23KB
react (data-invalidation.js) 5.05KB 2.08KB
react (index.js) 4.50KB 2.06KB
react (schema-input.js) 4.25KB 2.04KB
react (spec-input.js) 0.20KB 0.18KB
sdui-parser (body-dialect.js) 4.50KB 1.99KB
sdui-parser (codegen.js) 9.45KB 3.76KB
sdui-parser (dashboard-widget-options.js) 3.08KB 1.30KB
sdui-parser (index.js) 6.06KB 2.68KB
sdui-parser (input-type.js) 2.84KB 1.40KB
sdui-parser (parse.js) 25.28KB 7.80KB
sdui-parser (provenance.js) 3.84KB 1.90KB
sdui-parser (types.js) 0.28KB 0.23KB
sdui-parser (validate.js) 21.42KB 7.05KB
types (ai.js) 4.39KB 2.17KB
types (api-types.js) 0.20KB 0.18KB
types (app.js) 4.12KB 1.61KB
types (base.js) 0.20KB 0.18KB
types (blocks.js) 0.20KB 0.18KB
types (complex.js) 3.19KB 1.62KB
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.26KB
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) 4.99KB 1.96KB
types (spec-ui-namespace.js) 0.20KB 0.19KB
types (strict-authoring-face.js) 21.59KB 7.71KB
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: b6a1f605750429080779b7ff5117d417abe0baa1
Local-runs: none

Rendered 2026-10-01T03:53Z from the card (#8654, body and all six comments), the PR (#11324 body, 7-file list, net diff against main) and the head's check-runs, read last. Governing texts: triage 5619621149 ("declared in spec ⇒ align the mirror"), 协议为基准, the installed spec 17.5.0 at objectstack 0f6dcac5e9, the objectui#8652 ruling 「B」 as that spec's ObjectTimelinePropsSchema docblock carries it, AGENTS.md #2 at origin/main, and the landed precedent PR objectui#11292.

① Derived judgments

Each face the diff moves, named against the spec at 0f6dcac5e9 and the renderer at this head.

  1. Premise — the spec declares the key on this element, standalone. RIGHT. ComponentPropsMap['object-timeline'] is ObjectTimelinePropsSchema, whose row reads navigation: NavigationConfigSchema.optional(); its docblock names it "the spec half of the objectui#8652 maintainer ruling, verbatim B" and the read as "standalone, with no enclosing view". So triage's screening rule applies as written: declared in spec ⇒ align the mirror; P1's fork does not arise. ComponentPropsMap carries no bare 'timeline' row.
  2. TS face — ObjectTimelineProps['schema'] gains navigation?: ViewNavigationConfig, in @object-ui/plugin-timeline. RIGHT, and the placement is right. ViewNavigationConfig is NavigationConfig by alias (the objectql.ts docblock: "They are one type now"), so the vocabulary is the spec's by reference — the same type ObjectKanbanSchema.navigation and ObjectCalendarSchema.navigation took in feat(types): declare navigation on the object-kanban and object-calendar arms, by reference to the spec (objectui#8652) #11292. The type is published: the package entry has export type { ObjectTimelineProps } from './ObjectTimeline'. The card's own measurement named the undeclared read's type as TimelineSchema & { objectName?: …; timeline?: … } — this intersection — so the key is now declared exactly where the card measured it missing. The Equal pin and the @ts-expect-error bad-mode row establish that the member is the declared type and not the index signature's any; the Type Check run on this head is green.
  3. Renderer read — schema.navigation with no cast and no default. RIGHT. Behaviour is unchanged by construction: useNavigationOverlay resolves navigation?.mode ?? 'page', and its if (!navigation) branch calls onNavigate or nothing. The board and the calendar supply schema.navigation ?? { mode: 'drawer' }; the timeline does not, and the PR publishes that difference instead of silently closing it. Right for a declare-the-key card.
  4. Designer face — { name: 'navigation', type: 'object', description } on object-timeline and view:timeline. RIGHT on both tags. One renderer, ObjectTimelineRenderer, under two tags; objectui#8220 declared filter and sort on both the same way, and feat(types): declare navigation on the object-kanban and object-calendar arms, by reference to the spec (objectui#8652) #11292's calendar shares its input list between object-calendar and calendar. Accept-set effect at the html tier: navigation stops reading as unknown-prop, and a string value is refused as type-mismatch — pinned on both tags, with a control that an unknown prop is still reported.
  5. The description, judged member by member against the hook and the overlay shell. TRUE, with two wording nits. A parent onRowClick (fed onRowClick ?? onItemClick) returns before anything else — "outranks the whole key": true. Absent key, page, mode-less block: the hook's if (!navigation) and mode === 'page' branches do nothing without onNavigate — true. drawer / modal / popover: isOverlay opens the shell — true. new_window and openNewTab: window.open on the record URL built from the block's own objectName, checked before page and before the overlay branch — true. none and preventNavigation: one branch, nothing — true. size: resolveOverlayWidth(navigation) — true. split: the shell's split branch returns null without mainContent; the timeline spreads the hook's return, which carries no mainContent, and passes none, while kanban, calendar, gantt, grid, tree and view each pass mainContent= — true. Nits, non-blocking: (a) "page, none and split open nothing here, because the timeline hands the split shell no main panel" attaches split's reason to all three — page's reason (no parent view) is stated correctly one sentence earlier, and none needs none; (b) "openNewTab: true … outranks the mode" holds for every mode but none, which, like preventNavigation, is checked first — the landed kanban and calendar descriptions carry the same sentence. Neither teaches a wrong behaviour; both are for the next touch of that string.
  6. Validator face — ObjectTimelineBlockSchema NOT edited, on the claim that it already judges the key by reference. VERIFIED. Its properties is propsBag('object-timeline', stripImportedDefaults(SpecObjectTimelinePropsSchema)), the spec row itself, and its docblock already lists "the navigation members" among the spec defaults the boundary strips. So both faces judged properties.navigation from the 17.5.0 bump; the PR pins the value-level verdicts (every mode parses, a bad mode is refused at properties.navigation.mode, a bogus member is refused, the retired view is refused, no default is written) and changes nothing there. A flat navigation on the node is the bag discipline's pre-existing question, not this card's; the docs author the key inside properties.
  7. @object-ui/types — untouched, and no published face of it mis-states the key for object-timeline. Swept packages/types/src outside tests for object-timeline or timeline beside navigation: the one hit is the bag docblock above, which is correct. undeclared-but-consumed-keys-6150 has no timeline row. content/docs/api/schema-reference.md has no object-timeline table (the dev's claim, verified). The 8652 types test's "⛔ Not here" note says the timeline arm is objectui#8654's and untouched by that card — a test comment, still true. (ObjectTree and ObjectView still read (schema as any).navigation; other cards, not this diff.)
  8. README and docs page (AGENTS.md Add automated testing infrastructure and CI/CD workflows #2). RIGHT. Both document the object-bound block in its properties bag; the mode list is the spec's NavigationModeSchema verbatim (page, drawer, modal, split, popover, new_window, none); size as sm … full with auto the default matches NavigationConfigSchema.size; the three open-nothing cases carry the objectui#11293 pointer. content/docs/guide/ has no page listing per-block navigation support, so no guide page is owed; the plugin page is the same class feat(types): declare navigation on the object-kanban and object-calendar arms, by reference to the spec (objectui#8652) #11292 updated. Build Docs, README Export Check, Doc Snippet Type Check and Internal Docs Link Check are green on the head.
  9. Changeset body (publishes verbatim). TRUE, sentence by sentence — the TS member and its type, TimelineSchema unchanged, both registrations, the bag already judging and now pinned, click behaviour unchanged, the three open-nothing cases.

② Semver level

Clause-②: yes — right: an exported interface gains an optional member and two registrations gain an input, a widening by reference to the spec's own declaration. Changeset .changeset/8654-timeline-navigation.md: '@object-ui/plugin-timeline': minor. That matches what the diff publishes: the only released package whose src/ moves is @object-ui/plugin-timeline (in the fixed group, not private); the other six files are the changeset, two docs pages and two tests in the same package. @object-ui/types has no changed file, so the dispatch's suggested types minor is correctly absent — #11292 carried three minors because it changed three packages. No major. Changeset Declaration, Bump Policy, Fixed Group Check, Claim Re-read and Overwrite Report are green on the head.

③ Boundary flags

  • open_question 1 (H2 placement: A, ObjectTimelineProps['schema'] only, or B, also TimelineSchema) — A, upheld on the evidence; the dispatch's H2 placement was wrong, and ACCEPT 5924275171 stands. (i) The checker's objectName control: TimelineSchema in data-display.ts declares type, variant, items and the presentational rail keys and no objectName; objectName, timeline, filter and sort, the other members of the same spec row, live on the plugin-local half of the intersection — read directly, not grepped for absence. (ii) The spec has no timeline row: ComponentPropsMap at 0f6dcac5e9 carries 'object-timeline' and no bare 'timeline', so B would declare the key on a face the spec does not declare it on (zone 1). (iii) objectui#8220 declared filter and sort on both registrations and left TimelineSchema alone. Also: @object-ui/types has no ObjectTimelineSchema TS type (its bag docblock: "no TypeScript declaration of this node"), so the object-bound TS face IS ObjectTimelineProps['schema']; feat(types): declare navigation on the object-kanban and object-calendar arms, by reference to the spec (objectui#8652) #11292's ObjectKanbanSchema / ObjectCalendarSchema are object-bound node types, which is why the analogue there sat in @object-ui/types and here does not. Not escalated.
  • Deviation 2 (input on both registrations) — upheld, ① item 4.
  • Deviation 3 / H4 (registry-inputs-spec-parity.test.ts untouched) — upheld: UNJUDGED_SPEC_BLOCKS['object-timeline'] is OWED_TO('objectui#11168', 'REGISTERED LAZILY, NOT LOADED HERE …'), structuredInputs is built from covered only, and "every member pin names a key that is still array/object-armed on a covered block" would refuse object-timeline.navigation today. The MEMBER_PINS line is booked to objectui#11168 slice 3 in the ACCEPT; the pin file names the block and the key, so the locator will take it. Escalation recorded, nothing owed here.
  • Deviation 4 (pins in plugin-timeline) — consequence of A. Deviation 5 (no schema-reference.md edit) — verified, no object-timeline table. Deviation 6 (ablation miscount 12 → 11; refused no-op re-anchored) — reporting, no contract effect.
  • Out-of-scope 1 and 2 (split; absent / page / mode-less open nothing) — the PR's published text states each truthfully (① item 5), four warning rows pin them, and both point to objectui#11293 as its timeline site, where the seat says a pointer went. Whether to fix is not judged here.
  • Out-of-scope 3 (onItemClick fires twice) — verified in source: the composed onItemClick calls navigation.handleClick(record), whose onRowClick ?? onItemClick fires it, then calls onItemClick?.(record) again. Pre-existing, no in-repo producer, carrier none; not a contract item of this PR. Out-of-scope 4 (the strict face refuses objectName on type: 'timeline' while view:timeline requires it) — pre-existing, untouched, carrier none.
  • Serial constraints — data-display.ts, zod/data-display.zod.ts, objectql.zod.ts and the parity test are untouched, so objectui#6152 round 5, PR fix(types): a gate-wrapped arm waives objectName when dataSource.object binds the node (objectui#11117) #11312 and the hot file meet nothing here.
  • Check-runs on the head, read last at 2026-10-01T03:49Z: 42 runs, all on this sha. 33 success — among them Type Check, Lint, Build & E2E, Build Docs, Test (dist pins), Test shards 2/8 and 7/8, Spec Main Shape Gate, Line Citation Gate, the five changeset gates and the doc gates. 3 skipped (dependabot and the two coverage placeholders). 6 in_progress — Test shards 1/8, 3/8, 4/8, 5/8, 6/8 and 8/8 — recorded as in progress, not as verdicts. No failure on the head.

Implemented-by: claude/issue-8654-timeline-navigation
Reviewed-by: session_01VhxTqosz7wn54ahqyxgERT

VERDICT: PASS

@objectstack-fleet
objectstack-fleet Bot marked this pull request as ready for review October 1, 2026 03:54
@objectstack-fleet
objectstack-fleet Bot enabled auto-merge October 1, 2026 03:54
@objectstack-fleet
objectstack-fleet Bot added this pull request to the merge queue Oct 1, 2026
Merged via the queue into main with commit 95bd23c Oct 1, 2026
45 checks passed
@objectstack-fleet
objectstack-fleet Bot deleted the claude/issue-8654-timeline-navigation branch October 1, 2026 04:09
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentation Improvements or additions to documentation plugin tests

Projects

None yet

2 participants