Repository navigation
feat(plugin-timeline): declare navigation on object-timeline, by reference to the spec (objectui#8654) - #11324
Conversation
…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>
✅ Console Performance Budget
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
Size Limits
|
Contract reviewServed-tier: Rendered 2026-10-01T03:53Z from the card (#8654, body and all six comments), the PR (#11324 body, 7-file list, net diff against ① Derived judgmentsEach face the diff moves, named against the spec at
② Semver level
③ Boundary flags
Implemented-by: VERDICT: PASS |
Fixes #8654
Clause-②: yes
navigationis now declared on theobject-timelineblock, 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/spec17.5.0, the release this repo resolves:ComponentPropsMap['object-timeline']declaresnavigation: NavigationConfigSchema.optional(), and its docblock names it the spec half of that ruling, honoured standalone, with no enclosing view.Dispatched by the
domain:spec @ objectuiseat (objectui#10217), sessionhttps://claude.ai/code/session_01VhxTqosz7wn54ahqyxgERT.Premises, re-measured on
origin/maincd5b19a7ebefore the first editObjectTimelineBlockSchemareads the spec row by reference in itspropertiesbag. On the installed 17.5.0,{ type: 'object-timeline', properties: { navigation: { mode: 'drawer' } } }parses onsafeValidateSchemaand onStrictAnyComponentSchema. A badmodeis refused atproperties.navigation.modeand a bogus member atproperties.navigation, on both faces. A parse writes no default.objectql.zod.tsis untouched.getPropertyOfType, not a grep) readsnavigationas NOT DECLARED onObjectTimelineProps['schema']and onTimelineSchema; the read went through(schema as any).navigation. The same instrument's lit control is the reason this PR does not put the member onTimelineSchema:objectName, a member of the same spec row, reads DECLARED on the plugin-local half ofObjectTimelineProps['schema']and NOT DECLARED onTimelineSchema. A nonsense key reads not declared on both.TimelineSchemais the presentational rail (type: 'timeline'). Its docblock calls its vocabulary disjoint from the object-bound one by design, andTimelineRenderernever readsnavigation.navigationon theobject-timelinerow only and has notimelinecomponent row. Declaring it onTimelineSchemawould add the key to a face the spec does not declare it on (the dispatch's zone-1 ruling forbids that).objectNameontype: 'timeline'. Anavigationdeclared there could not build thenew_windowURL, which needs the object name.filterandsorton these same two tags the same way: registrationinputson both tags,TimelineSchemaleft alone.ObjectTimelineProps['schema']inplugin-timeline/src/ObjectTimeline.tsx, besideobjectName,timeline,filterandsort. That file is inside the claim.@object-ui/typesis NOT changed: neitherdata-display.tsnorzod/data-display.zod.tsis touched, so this PR does not overlap the objectui#6152 round-5 region.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.registry-inputs-spec-parity.test.tsbooksobject-timelineinUNJUDGED_SPEC_BLOCKS, owed to objectui#11168 (it does not load the lazily registered block). Its assertionevery member pin names a key that is still array/object-armed on a covered blockwould refuse aMEMBER_PINSentry forobject-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']declaresnavigation?: ViewNavigationConfig, the spec'sNavigationConfigtype thatObjectGridSchema,ObjectKanbanSchemaandObjectCalendarSchemause.ObjectTimelinereadsschema.navigationwith no cast. The read still supplies no default (see Acceptance notes).object-timelineandview:timeline, publish anavigationinput typedobject, with one shared description. Both tags carry it for the reason both carryfilterandsort:view:timelineis the same renderer under a second tag. That is also the calendar precedent from PR objectui#11292 (OBJECT_CALENDAR_INPUTS, shared byobject-calendarandcalendar).README.mdandcontent/docs/plugins/plugin-timeline.mdxdocument the object-bound block'spropertiesbag and itsnavigationkey, member by member, with the three cases that open nothing..changeset/8654-timeline-navigation.md:@object-ui/plugin-timelineminor (a widening). The fixed group moves together anyway.Pins
object-timeline-navigation-8654.test.tsx:modeatnavigation.mode;modeis refused atproperties.navigation.mode, a bogus member is refused, the retiredviewmember is refused, and a parse writes no default;Equalassertion, a@ts-expect-errorbad-mode row, and a valid block that annotates with no cast;object, the html tier accepts the block and refuses a string by kind, and a control prop is still reported as unknown;SchemaRendererand 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 realObjectTimeline,useNavigationOverlayand overlay shell:drawer(the lit control),modalandpopoveropen the entry's record;noneopens nothing, andpreventNavigationoutranks an overlay mode;new_windowopens the record page in a new tab, andopenNewTabdoes too, outranking bothpageanddrawer;sizeresolves through the bucket table, a deprecatedwidthwins oversize, andautogives the unsized width;onRowClickoutranks the whole key;page, a mode-less block andspliteach open nothing.Verification, on
b6a1f6057(the head of this PR)pnpm --filter '@object-ui/plugin-timeline^...' run build, 9 packages).pnpm --filter @object-ui/plugin-timeline run type-checkechoedtsc --noEmit && tsc -p tsconfig.test.jsonand exited 0.tsc -p tsconfig.test.json --listFilesOnlylists 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 existingdateOnlyZone-10866files. The two new files: 22 + 16 passed.Tests 1409 passed (1409). They cover:registry-inputs-spec-parity,public-contract,public-block-binding-reach,timelineAxisRefusalReach-7070,filterContextTokensSweep-10666,pullToRefreshOwner-10105;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;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-rootsand the threecheck:vi-mock-*;check:changeset-claims,check:pending-changeset-literals, changeset presence and no-major;check:readme-exports,check:doc-snippetsandcheck:doc-examples, each run after the doc-snippet build closure was built (the firstreadme-exportsrun reported a collapsed population on an unbuilt tree, which is not a verdict);check:sdui-registration-pinsandcheck: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).eslint --no-inline-config --format jsonover the 4 changed TS files reports 4 files and 0 errors.ObjectTimeline.tsx28 to 27 (the removed cast) andindex.tsx5 to 5. The new contract pin carries 1, the deliberateanyin itsEqualcontrol.eslint.config.jsenables no type-aware linting (noparserOptions/projectService). None of the 10 rule sources undereslint-rules/reads the filesystem; as a control, 9 of them containcontext.report. So this diff cannot move a verdict on an untouched file.pnpm lintis left to CI.Ablations, each committed first, run through
ablation-replace.mjs, and restored toHEADEach ablation was proven landed by anchor count and blob change. Each restore was proven by blob equals
HEADand an emptygit diff HEAD, and the tree was clean afterwards.tsc -p tsconfig.test.jsonexits 2, with TS2344 on theEqualassertion and TS2578 (unused@ts-expect-error) on the bad-mode row. This was the predicted direction.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.object-timelineregistration row removed (theview:timelinerow 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.Acceptance notes
ObjectKanbanandObjectCalendar,ObjectTimelinesupplies no?? { mode: 'drawer' }default, so the hook resolves the spec'spage.page, and a block written withoutmode, open nothing. This is the objectui#11293 family, and the timeline is a third site of it.splitopens nothing. The timeline handsNavigationOverlaynomainContent, 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.onItemClickfires twice per entry click when it is the only host handler.ObjectTimelinehandsonRowClick ?? onItemClickto the hook, which calls it. The composedonItemClickthen callsonItemClickagain afternavigation.handleClick. This was measured once by a probe (two calls, with 2 and 1 arguments). No in-repo producer passesonItemClicktoObjectTimeline. Not pinned. Carrier: none.objectNameontype: 'timeline', while theview:timelineregistration marks itrequired: true. That is pre-existing and was observed while choosing the TS face. It is not measured through a public door. Carrier: none.objectql.zod.ts, which this PR does not touch. objectui#6152 round 5 is indata-display.ts/zod/data-display.zod.ts, which this PR does not touch either.registry-inputs-spec-parity.test.tsis not edited.Generated by Claude Code