Skip to content

fix(ui): a calendar event and a gantt bar open the one record page in the console (#27057) - #27564

Merged
objectstack-fleet[bot] merged 6 commits into
mainfrom
claude/project-thread-rplaiu
Oct 29, 2026
Merged

objectstack-fleet[bot] merged 6 commits into
mainfrom
claude/project-thread-rplaiu

Conversation

@objectstack-fleet

Copy link
Copy Markdown
Contributor

Requested by Sales · project thread

Fixes #27057
Clause-②: no
Metadata-protocol: none

维护者速读

  • 改了什么:控制台里点日历事件、点甘特条的打开按钮,现在和点表格行、看板卡一样,在抽屉里打开同一张记录页(可展开整页,可在抽屉里逐层打开关联记录);不再弹那个缩小版的快速查看面板。声明了 page 的日历视图也真的跳整页了(以前同样弹快速查看)。
  • 为什么改:维护者裁定「一个对象一个默认打开方式……抽屉和整页是同一张页在两个宽度」,并把快速查看并入这张记录页。日历和甘特原先抢在控制台前面自己开快速查看,所以同一条记录换个视图就看到两种页面。
  • 风险与回滚:只改了「这一下点击归谁」:有宿主接点击时交给宿主,没有宿主时(页面上的独立区块、没有宿主的列表、控制台外)日历和甘特照旧开自己的抽屉。不动数据、不动元数据,回滚即 revert 本 PR。
  • 先看:ui/packages/plugin-list/src/ListView.tsx(routedByHost 一段:只在宿主接点击时把路由交给日历和甘特)、ui/packages/plugin-calendar/src/ObjectCalendar.tsx(navIsOverlay 那一行)、ui/packages/plugin-gantt/src/index.tsx(注册只转发函数型 onRowClick)。
  • 两分钟验证:cd ui && pnpm exec vitest run apps/console/src/__tests__/recordOpenFromEveryView-27057.test.tsx,预期 5 条全绿:日历事件、甘特条、看板卡各自在抽屉里打开嵌入的记录页,日历打开的抽屉能再下一层并出现层级轨迹,声明 page 的日历视图跳到记录页路由。
  • 需要你做的:无,席位落地。

What changes

Inside the console every view now opens a record in the one record page: ObjectView's handleRowClick writes ?recordId=, and RecordStackDrawer mounts RecordDetailView embedded. No renderRecord host channel was added: the measurement found no console path from these views that cannot reach ?recordId=.

  • ObjectCalendar: a host onRowClick takes the click in every navigation mode. The calendar's own drawer opens only when nobody hands it one (navIsOverlay = !onRowClick && mode === 'drawer'). onEventClick beside a host onRowClick fires as it already did in the page mode. onEventClick alone routes nothing, so the drawer still opens.
  • ObjectGantt: the same. The onRowClick handed to useNavigationOverlay is no longer dropped in the drawer mode, and the page sink (objectui#7334) stays for a chart with no host.
  • ObjectGanttRenderer: forwards a function-typed onRowClick from the node and nothing else. This is the same answer as the calendar registration's declared host callbacks (objectui#4492). An authored non-function value is dropped, and host data stays dropped (objectui#7210 / objectui#7222, still pinned in ObjectGantt.hostDataProp-7210).
  • ListView: hands the calendar and the gantt its click router only when its own host passes onRowClick (routedByHost). A list with no host handler routes nothing for a view that authored no navigation (the absent key is the host's call in useNavigationOverlay). Handing the router over there would have turned the two views' drawer into a click that opens nothing. Every other child view gets the router as before.
  • RecordDetailPanel: docblock only. It names the panel as the fallback for a host that routes no click, and its retirement as ADR-0049's enforce-or-remove path once the fallback measures zero callers. No tombstone.

Per-site verdict

Site Verdict Evidence or reason
calendar (ObjectCalendar) changed Dropped the host onRowClick whenever its mode was the drawer, and ListView puts no navigation on its node, so in the console it always was. Console test rows 1, 4 and 5.
gantt (ObjectGantt and ObjectGanttRenderer) changed The registration forwarded no host prop at all, and the component dropped a direct caller's handler in the drawer mode. Console test row 2.
ListView, router to calendar and gantt changed routedByHost gate. ListView.hostRoutedRecordClick-27057.
grid already reaches the page ListView hands onRowClick to every child view, and useNavigationOverlay gives an external onRowClick full priority. The grid CONTROL row of ListView.hostRoutedRecordClick-27057 reads the router on its node.
kanban already reaches the page externalClick = onRowClick ?? onCardClick. Pinned in the console: test row 3, the CONTROL that stays green under both ablations.
gallery, map, timeline, tree already reach the page baseProps.onRowClick reaches each unconditionally. Their registrations spread the node's props, and each passes onRowClick to useNavigationOverlay (props.onRowClick ?? props.onCardClick on the gallery, onRowClick ?? onItemClick on the timeline), where it wins.
ListView's own quick-look already reaches the page in the console Its useNavigationOverlay is handed the host's onRowClick, which takes the click before the overlay. The read-only panel is drawn only for a list with no host handler.
blocks on a custom page (PageView) not reachable in the console's drawer PageView mounts no record drawer host, so there is no ?recordId= overlay to reach. A calendar or gantt there keeps its own drawer, and an authored page reaches the page through the host navigator (objectui#11293). Reaching the record page in a drawer there needs a host channel outside this card's file surface (see findings).

Why letting the host click win does not reopen what objectui#7334 guarded

The suppression's rationale (the calendar's comment and the gantt branch in ListView) was that a parent which page-navigates on a row click must not take a drawer the view authored away from it. In the console that parent is ObjectView's handleRowClick, and it no longer page-navigates by default. It reads the view's navigation, then the object's, defaulting to the drawer, and in the drawer case writes ?recordId= and opens the drawer itself. That drawer is now the full record page. So the host keeps an authored drawer, and it honours an authored page too. That part was measured: before this change a calendar view that declared page opened the quick-look, because the calendar never saw the view's navigation. Where there is no host, ListView no longer hands over its router, so a click the list cannot route never replaces the views' own drawer, or the gantt's page sink.

Tests

  • New: ObjectCalendar.hostRowClick-27057 (4 rows) and ObjectGantt.hostRowClick-27057 (4 rows), both through the real SchemaRenderer and registration. ListView.hostRoutedRecordClick-27057 (6 rows). The console join apps/console/src/__tests__/recordOpenFromEveryView-27057 (5 rows) mounts the real calendar, gantt and kanban under the console ObjectView, with RecordDetailView as a sink.
  • Updated: calendarNavigationMembers-8652. Its row "an OVERLAY mode keeps the click from a parent view's handlers" pinned exactly the precedence this card reverses. It now pins the reverse, and a new row pins that onEventClick alone still leaves the drawer to the calendar. ListView.ganttNavigationForward-7334: its typeof onRowClick === 'function' controls now mount the list under a host handler. ObjectGantt.navigationPageSink-7334: one docblock line about the renderer brought up to date.

Reverse verification, each through scripts/ablation-replace.mjs (the mutation landed by blob hash, and each restore read blob == HEAD with an empty git diff HEAD):

Mutation Test file Reading
calendar: drop the host onRowClick in the drawer mode again ObjectCalendar.hostRowClick-27057 2 failed / 2 passed (the two host rows)
same console recordOpenFromEveryView-27057 3 failed / 2 passed. The three calendar rows, the page row among them (not predicted, explained above).
gantt registration: forward no onRowClick ObjectGantt.hostRowClick-27057 2 failed / 2 passed (the two host rows)
same console recordOpenFromEveryView-27057 1 failed / 4 passed (the gantt row)
ObjectGantt: drop the host onRowClick in the drawer mode again ObjectGantt.hostRowClick-27057 1 failed / 3 passed (the drawer-mode host row; the page row is unaffected, since the old code dropped it only in the drawer mode)
ListView: hand the router to calendar and gantt unconditionally ListView.hostRoutedRecordClick-27057 2 failed / 4 passed (the two hostless rows)

Acceptance notes

  • A calendar event click in the console mounts RecordDetailView embedded, not RecordDetailPanel: console row 1, with ?recordId=1 in the URL and no quick-look panel.
  • A gantt bar click: console row 2. The click is the bar's open control: a click on the bar itself only selects it, by GanttView's design. Double-click on the name, Enter and the context menu's Open fire the same onTaskClick.
  • The drawer stack's trail from a calendar event: console row 4. A record link on the page opens one level down, and record-drawer-trail names the level left.
  • Kanban card pin: console row 3.
  • Outside the console the fallback still renders: the FALLBACK rows of both new component files (no host, so the chart's own RecordDetailPanel opens) and the hostless rows of ListView.hostRoutedRecordClick-27057.
  • Seat 5's pointer (a when-locked field opens on a row the server's write gate admits, in a console drawer opened from these renderers): met by composition, not measured end to end. Console rows 1 and 2 show that the drawer opened from a calendar or a gantt mounts the same RecordDetailView embedded a grid row mounts, and record-blocks.recordFieldAccess-27146 pins that page's record:details / record:highlights reading the stored row's field access, so summary edits on a draft the server unlocks and stays locked on an approved row. No server with a when lock was driven for this card.
  • Screenshots: taken on examples/app-showcase (HotCRM is not in this repository), its showcase_task calendar and gantt views, against this branch: a console dev server (ui/apps/console, sources aliased) proxied to an os dev --seed-admin backend on its own port, driven with Playwright.
    • Before (the old calendar line held on disk through ablation-replace --hold, then restored to blob == HEAD): the event "Design system" opened the quick-look panel. The URL kept no recordId, there was no expand control, and the panel showed six fields and nothing else.
    • After: the same event, and the gantt row's open control for the same task, each open the record page in the drawer. The URL carries ?recordId=VovxsPa9FWLiZTat, "Open as full page" is there, and the page draws its status path, header actions, highlights and sections.
    • The image files are kept with the dispatching seat. No relay op uploads an image, so none is embedded here.

Verification

All on the final head's tree unless noted; each exit recorded before any pipe.

  • Affected packages, full suites: pnpm exec vitest run packages/plugin-calendar packages/plugin-gantt packages/plugin-list packages/plugin-detail plus the console join. 673 files, 5994 passed, 1 failed, 227 skipped. The one failure was calendarNavigationMembers-8652's overlay row, the precedence this card reverses, rewritten above.
  • Rerun with neighbours: the two edited files, packages/plugin-view, the app-shell view tests that mount calendars or gantts (PageView.recordNavigator-11293, ObjectView.calendarBinding-7029, ObjectView.recordOpenPreference-26677 and five more), the two SchemaRenderer data-prop files and timelineAxisRefusalReach-7070: 90 files, 858 passed.
  • After the last main merge: the card's seven test files, 56 passed.
  • Type-check: turbo run type-check for @object-ui/plugin-calendar, plugin-gantt, plugin-list, plugin-detail and @object-ui/console: 74 of 74 tasks. The first run caught an unused import in the new console test, now removed.
  • ESLint on every changed ui/ file: 0 errors (warnings are the files' existing no-explicit-any).
  • dispatch-gates --commands: 34 commands, each run, all exit 0. --ran reconciles 34 derived, 34 run, 0 NOT-MEASURED, 0 UNRUN. Also run: check:changeset-fixed (its roster sits under .changeset/), check:new-line-citations (0 new), check:doc-authoring, check:nul-bytes, check:test-source-alias.
  • main merged twice (no overlap with this card's files). After each: @objectstack/spec rebuilt; after the second, check:generated green. The delta against main is these 13 files only.

Findings outside this card's surface

Read from the code unless marked measured; none of them is changed here.

  1. Custom-page list blocks route nothing for most views. A list-view block on a custom page mounts ListView with no host handler. For a view that authored no navigation, its router answers a grid, kanban, gallery, map, timeline or tree row with nothing (not measured). The calendar and the gantt there keep their own drawer, as before.
  2. Blocks on a custom page cannot reach the console's drawer. PageView mounts no record drawer host, so their records open in RecordDetailPanel (or the page, for an authored page). Bringing the one record page there needs a host channel outside this card's file surface.
  3. ListView puts no navigation on the calendar's node (the gantt branch has since objectui#7334). Inside the console the host now reads the view's navigation (measured, console row 5). A list with no host still drops a calendar view's authored page.
  4. Seat 5's pointer, the half this card does not retire. RecordDetailPanel survives as the fallback, so its in-place edit outside the console still draws the row-blind lock (no RecordFieldAccessScope). If the fallback is kept, that scope is owed there.
  5. An interface page with recordAction: 'none' hands ListView no handler, so its calendar and gantt still open their own drawer, as they did before this change.

Generated by Claude Code

…he one record page (#27057)

The console-level join: the real renderers under the console object page,
the record page as a sink. Also the changeset and the RecordDetailPanel
docblock naming it as the fallback.

Claude-Session: https://claude.ai/code/session_01L6bhgWNzwmuUkLM8wBbXXx
Co-authored-by: Claude <noreply@anthropic.com>
… read the new click owner (#27057)

The 8652 row that pinned the drawer keeping the click from a parent's
onRowClick now pins the reverse, with a row for onEventClick alone.

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

Copy link
Copy Markdown
Contributor Author

domain:ui#1 · os-sales · session_01L6bhgXU26UBEjd1KKHgNEx

席位核对 · ACCEPT · 2026-10-29T13:30Z

Head-sha: ee2b51946981d962108e5a3456ba672d85ec4586 · card #27057 · claim 6495581264 · dev report 6497652882.

  • 形态: draft → main, Fixes #27057 only, 13 files (+849/−44): ObjectCalendar.tsx, ObjectGantt.tsx, gantt registration index.tsx, ListView.tsx, a RecordDetailPanel.tsx docblock, tests beside them and one console join test, one patch changeset over four packages. check-governed-merges --pr 27564: NOT governed, 893 lines ≤ 3000.
  • 前提实测: seat read of the diff against origin/main, against the corrected premise 6495422396 (renderers mount their own overlay and calendar/gantt suppressed the host click): both views now hand the click to a host onRowClick in every mode and keep their own drawer only when no host routes one; ListView forwards its router to calendar/gantt only when its own host passes onRowClick (presence read as a primitive in the memo deps), so a hostless list keeps the drawer instead of a dead click; ObjectGanttRenderer forwards a function-typed onRowClick only, an authored non-function value is dropped and host data stays dropped (fix(objectql)!: registerHook refuses an empty object target and a self-cancelling scope (#6573) #7210 pin intact). No renderRecord host channel was added, per the order.
  • Clause-② reading: no holds. The authored-metadata accept set does not move (a JSON value under onRowClick is still dropped); the one new prop is a host function on an existing renderer, not a new export.
  • 门禁 check-runs (pinned to head): 44 success, 11 skipped, 0 failed, 0 pending; mergeable_state: clean. New tests 19/19; four ablations (calendar suppression, gantt registration forward, gantt suppression, ListView gate) each turn their rows red with the kanban control green.
  • 接受的偏差: the calendarNavigationMembers-8652 overlay row is rewritten, because it pinned exactly the precedence this card reverses (the ruling in order 6495389681); file surface grew to the registration and ListView gate named in the per-site verdict; screenshots from app-showcase kept in project files, not on GitHub; seat 5's when-lock met by composition, not end to end.
  • 范围外去向: five class-none notes → Acceptance notes, none filed: page list-view blocks without a host handler route nothing; PageView blocks cannot reach the console drawer (carrier epic: record page v2 — one default open mode per object (drawer by default, page by declaration; modal / split / popover retired as metadata); the drawer and the page are one page at two widths; related records open in place #26676); hostless list calendar drops an authored page; the fallback RecordDetailPanel lacks RecordFieldAccessScope (record-page chain, this seat's lane); interface pages with recordAction: none keep the local drawer. Skills-docs: not owed (Metadata-protocol: none).
  • 落地条款: ordinary queue landing; pr_ready + automerge_enable. This seat's landing op is refused by its session's permission classifier (Studio OWD panel: saving "Controlled by parent" on a detail whose master_detail is not yet required answers 409 DESTRUCTIVE_CHANGE with no way forward #27458 6497508946), so the maintainer readies and merges.

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 size/l tooling Fix lands in tooling, not a product package — enters pm:queue only with Unblocks: or a named surface

Projects

None yet

1 participant