Repository navigation
feat(spec,ui)!: a record opens one way per object — navigation.mode keeps drawer (the default), page, new_window and none; modal, split and popover are retired; the split pane becomes a reader preference - #26804
Conversation
…n modal; drawer default Claude-Session: https://claude.ai/code/session_cdb7a717-def7-4e71-9988-f089fbaa18ff Co-authored-by: Claude <noreply@anthropic.com>
…s and react-blocks Claude-Session: https://claude.ai/code/session_cdb7a717-def7-4e71-9988-f089fbaa18ff Co-authored-by: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_cdb7a717-def7-4e71-9988-f089fbaa18ff Co-authored-by: Claude <noreply@anthropic.com>
…ault; split as a reader preference Claude-Session: https://claude.ai/code/session_cdb7a717-def7-4e71-9988-f089fbaa18ff Co-authored-by: Claude <noreply@anthropic.com>
…er preference; docs Claude-Session: https://claude.ai/code/session_cdb7a717-def7-4e71-9988-f089fbaa18ff Co-authored-by: Claude <noreply@anthropic.com>
…node Claude-Session: https://claude.ai/code/session_cdb7a717-def7-4e71-9988-f089fbaa18ff Co-authored-by: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_cdb7a717-def7-4e71-9988-f089fbaa18ff Co-authored-by: Claude <noreply@anthropic.com>
… the dialog census Claude-Session: https://claude.ai/code/session_cdb7a717-def7-4e71-9988-f089fbaa18ff Co-authored-by: Claude <noreply@anthropic.com>
…ow that the record overlay's modal face is retired Claude-Session: https://claude.ai/code/session_cdb7a717-def7-4e71-9988-f089fbaa18ff Co-authored-by: Claude <noreply@anthropic.com>
…vigation-mode-retirement
…escriptions Claude-Session: https://claude.ai/code/session_cdb7a717-def7-4e71-9988-f089fbaa18ff Co-authored-by: Claude <noreply@anthropic.com>
… so it runs in the repo project Claude-Session: https://claude.ai/code/session_cdb7a717-def7-4e71-9988-f089fbaa18ff Co-authored-by: Claude <noreply@anthropic.com>
…ee pin Claude-Session: https://claude.ai/code/session_cdb7a717-def7-4e71-9988-f089fbaa18ff Co-authored-by: Claude <noreply@anthropic.com>
…vigation-mode-retirement # Conflicts: # ui/packages/types/src/zod/objectql.zod.ts
Claude-Session: https://claude.ai/code/session_cdb7a717-def7-4e71-9988-f089fbaa18ff Co-authored-by: Claude <noreply@anthropic.com>
📓 Docs Drift CheckThis PR changes 1 package(s): 3 hand-written doc(s) NAME something this change touched and may need an implementation-accuracy re-verification:
⛔ 2 release-owned page(s) also name something this change touched. These are read-only:
What this run could not see
Coarse fallback — 147 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): Which tree this was computed onThis run read A worktree cut from an older # while this PR is open — GitHub drops the merge commit once it closes
git fetch origin 11b31d0597ca77ea285e4d20b7d6034c9de0c527 && git checkout 11b31d0597ca77ea285e4d20b7d6034c9de0c527
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin 8b276385e6f1e2b94a4061160c148cfa56f81588 eb20d2207077b470ee491c8bed8c7f782890aa54 && git checkout -B drift-repro 8b276385e6f1e2b94a4061160c148cfa56f81588 && git merge --no-ff eb20d2207077b470ee491c8bed8c7f782890aa54
node scripts/docs-audit/affected-docs.mjs --json 8b276385e6f1e2b94a4061160c148cfa56f81588
|
席位核对 · PR #26804 ← #26677 · epic PM
|
维护者速读(终稿)· PR #26804 ← #26677 · epic PM
|
|
Director note · Recorded by the director seat ( |
Fixes #26677
Clause-②: no (narrowing)
Metadata-protocol: narrowed
Child 1 of the record-page-v2 epic #26676. The maintainer's ruling, verbatim from the card: 「1. 一个对象一个默认打开方式:默认抽屉(带展开整页),
page只给显式声明的对象,modal退役,split / popover 变成用户个人偏好;抽屉和整页是同一张页在两个宽度。」 This PR ships its declaration half and the console's read of it.What changes
Spec (
@objectstack/spec)NavigationModeSchemaisdrawer | page | new_window | none, built withenumWithRetiredValues:modal,splitandpopoverare refused by name, each with its prescription (first sentence: "navigation.mode: 'modal'was removed in @objectstack/spec 18.3.0 (ADR-0087 D2) — …").NavigationConfigSchema.modedefaults todrawer(it waspage). The one def sits behind every carrier: a list view, an object'slistViews, and the eight page list blocks (list-view,object-grid,object-kanban,object-calendar,object-map,object-gantt,object-tree,object-timeline).InterfacePageConfigSchema.recordActionisdrawer | page | none;modalis refused with its prescription.page.form.tsdrops the Modal option.spec-property-retirement§2, re-read afterdbf7e04adf):navigation-mode-overlay-values-removed(retiredFromLoadPath: true,retiredAfter: '18.2.0', MAJOR_18 order 77): deletes a retired member from every carrier above and frominterfaceConfig.recordAction, so a storedsys_metadatarow (Studio's list-view inspector offered all seven modes andPUT /meta/viewpersisted them) converts on read and lands on the drawer. Fixture: 5 notices, disjoint.ui-record-open-overlay-modes-retired(names the conversion inconversionIds), plus its STEP18_RATIONALE fragment.DEFAULT_CHANGES_BY_MAJOR(ui/NavigationConfig:mode "page" → "drawer");authorable-defaults/ui.jsonregenerated.RETIRED_KEYS_BY_MAJORrow and no liveness row: no key changed. As the skill predicts for an enum-value narrowing,authorable-surface/,api-surface/andjson-schema.manifest/are byte-identical;check:generatedreports all 14 artifacts current.navigationdescribes say four values; the react-blocksnavigationtype), reference docs andskills/objectstack-ui/references/react-blocks.mdregenerated.packages/spec/src/ui/navigation-mode-retirement.test.ts(in therepoproject; added tovitest.repo-tests.json): every carrier refuses each retired member withinvalid_valueat the member's path and the prescription's first sentence;defineStackrefuses withSTACK_SCHEMA_INVALID/ 422; live members and thedrawerdefault; a misspelt member keeps zod's own message; a@ts-expect-errorat the authoring site; the conversion over stored view / page / object rows, idempotence, retired from the load path, wiring and the D3 entry; the page form's three options; a tree-scoped absence walk over the radius@objectstack/spec#testalready declares.Console (
ui/)useNavigationOverlay: a block withoutmodefalls back todrawer(the spec default), andisOverlayismode === 'drawer'. The absent-block branch is untouched: it stays the host's call (the hook's docblock explains why a host withholds navigation that way).NavigationOverlay: themodalandpopoverfaces are deleted, withpopoverTrigger,popoverAnchorRefand theuseOverlayAnchorexport — measured unreachable (below). The drawer face and thesplitface stay;NavigationOverlayModeispage | drawer | split | new_window | none.splitbranches and popover anchor plumbing; theirnavIsOverlayreadsdraweronly.objectViewRecordNavigation.tsresolves an un-authored list to the drawer whatever the field count (the drawer's width still follows the count), and plugin-view'sObjectViewuseslayout || (mobile ? page : drawer). Mobile still pages there; the app-shell path never passed a viewport to the guess, so its comment claiming "mobile always pages" was corrected rather than given a new behaviour. The record FORM's surface (recordFormNavigation.ts,AppContent.tsx) is untouched, as ruled.ui/packages/app-shell/src/utils/recordOpenPreference.ts(drawer | split, defaultdrawer,localStoragekeyos:record-open-mode, default on SSR, a throwing storage or an unknown value). The console'sObjectViewdraws the split face when the preference issplitAND the resolved mode isdrawer; a declaredpagekeeps its declaration. The split face reuses the drawer's URL state (?recordId=). The Display-menu control that writes the key is console list Display menu: 「打开记录」 (drawer | split) as a per-user preference; an object or view declaringpageshows the declaration instead; the name cell's hover ↗ opens the record as a full page #26681.appObjectScope.ts:IN_PAGE_RECORD_OVERLAYSand its route check are removed (below).InterfaceListPagenarrowsrecordActionto the three live values. The Studio list-view inspector's mode-label map is keyed by the spec'sNavigationMode, and the three retired labels leave its i18n table.navigationinput descriptions (eight plugins) andsdui.manifest.jsonregenerated (gen-sdui-manifest-node.mjs; the diff is those eight descriptions only). Docs:plugin-viewpage and README stop teaching the field-count guess; the calendar, kanban, gantt, map, timeline, tree, grid and detail docs stop teaching the retired modes;ui/packages/typesdescribes and docblocks citingdefault('page')updated.e2e/toast-under-modal.spec.ts: the centred-dialog case mountedNavigationOverlay's modal face, which is gone; it now mounts the ShadcnDialogprimitive, so the objectui#13340 top-centre path under a centred Radix dialog keeps its coverage.Mechanism assumptions, measured
ViewNavigationConfigis the spec alias, so the narrowing redsType Check UIby itself — held. The first type-check after the spec build readobject-kanban-calendar-navigation-8652.test.ts(233,17): error TS2322: Type '"modal"' is not assignable to type '"none" | "page" | "drawer" | "new_window" | undefined'(and the same in timeline, kanban, calendar and plugin-view tests). That red is also this PR's reverse verification that tsc read the rebuilt declarations.isOverlay— held; the pins that rested on thepagefallback were flipped (list below).NavigationOverlaymounts — there are 19 mounts on this tree, not nine. All but three spread the hook's state (mode from metadata);RecordDetailDrawerpassesdrawer; plugin-view's two passedsplit/popoverderived fromnavigationConfig.mode. With the values gone from metadata no caller reachesmodalorpopover, so those faces were deleted;splitis reached by a non-metadata caller (the preference) and stays.IN_PAGE_RECORD_OVERLAYS— its one consumer widened the object scope for a list whose views open records in an overlay the URL does not carry. No such mode remains, and the split preference rides?recordId=, whichhasRecordOverlayalready covers, so the set and its check were removed.1f3f8880declarespage(lead) anddrawer(opportunity, task, case) only. In this repo no source outsideui/authors a retired member (control: 39drawer/pageauthorings found by the same expression), and the tree-scoped pin holds that.Tests (final head
eb20d22070unless stated)pnpm --filter @objectstack/spec test: 730 files passed, 1 failed (scripts/liveness/check-liveness.test.ts, a child process's JSON truncated near 64 KB: macOS pipes are asynchronous, a known local-only failure, green in CI).test:repo: 79 passed, 1 failed (build-schemas-check-mode.manifest.test.tsgeneration stamp, the other known local-only failure).pnpm --filter @objectstack/spec typecheck: exit 0 (it includescheck:test-typecheck, so the new@ts-expect-erroris compiled).types,react,components, the eight plugins,plugin-view,plugin-detail,app-shell): 77 tasks successful.packages/app-shell/1759 files / 16173 tests passed (pre-merge); the twelve other touched packages 1799 files passed with 10 failures, all fixed and re-run green except threegit grep -Eword-boundary LIT CONTROLs (BSD regex has no\b; local-only). Post-merge re-run of every changed test file plusplugin-grid/andtypestests: 674 passed, the same three local-only failures.scripts/ablation-replace.mjs, restored to the HEAD blob withgit diff HEADempty):enumWithRetiredValues→z.enumreds 12 prescription rows of the spec pin;default('drawer')→default('page')reds 2 rows; the hook's?? 'drawer'→?? 'page'reds 4 rows across the two hook pins. Direction expected and observed: red.e2e/toast-under-modal.spec.ts: NOT MEASURED locally — the cached Playwright browsers are build 1234 and the runner asks for 1243; nothing was installed.Build & E2E UIruns it.Pins flipped, not deleted
Hook default (
useNavigationOverlay.modeDefault,hostRecordNavigator-11293,offline-nav-performance-spec-parity); the per-renderer mode-less rows (kanban, calendar, timeline, map, tree, gridgridNavigationMembers-8071, ganttobjectGanttInputs-11168,navigationModeResolution-7334,navigationPageSink-7334,objectTimelineInputs-11168); the fiveoverlayShellModes-9299files (a retired mode reaching a renderer past the parse now opens no shell, against a drawer lit control);ObjectView.pageSurfaceFallback-11015(the field-heavy object with anonNavigateused to route to a page — it now opens the drawer and never routes);ObjectView.splitPopoverCreate-10975(a stalesplitopens on the fallback surface, never beside the list); the types parity pins (four members, the three retired asserted absent); the spec-default controls inobject-grid-properties-bag-11276andobject-gantt-properties-bag-10859-b6;dialogViewportGutterCensus-12603(the record modal dialog is gone, not converged). The app-shell split tests (recordCountFooter-10636,viewRefusal-12112) now open the pane through the preference, andObjectView.recordOpenPreference-26677.test.tsxpins the three cases (preferencesplit→ the pane; no preference → the drawer; a view declaringpage→ neither).Tests deleted
ui/packages/plugin-view/src/__tests__/ObjectView.overlayTitleI18n.test.tsxandObjectView.overlayTitleNoProviderFallback.test.tsxpinned the heading of plugin-view's split panel, a face this PR removes; the subject no longer exists. Its absence is pinned by the FLIPPED rows ofObjectView.splitPopoverCreate-10975.test.tsx(noTasks Detailheading, noClose panelcontrol).Gates
node scripts/pm/dispatch-gates.mjs --commandson this branch derives 125 families; all were run with exit codes recorded and reconciled with--ran: 124 run, 1 NOT MEASURED (pnpm check:dual-build-cjs-loads, exit 3, PREREQUISITE NOT MET — it needs a full build). Two exit 1s are local-environment readings:pnpm check:bash32-floor(its self-test probes bash 4 syntax; this host's bash is 3.2.57), andpnpm --dir ui exec vitest run scripts/(two tests hit their 15 s timeout under load and pass on a re-run with a longer timeout; two needmapfile, absent from bash 3.2).pnpm check:i18nand the speccheck:skill-examplesfirst refused their prerequisite and passed after building their closures;pnpm check:cross-package-test-inputsfirst named the new tree-walking pin, which now sits in the specrepoproject. Also run:pnpm --dir ui run check:spec-symbols,check:doc-example-readers,check:unreferenced-sources,type-check:coverage,check:handler-key-reads,check:phantom-deps— all exit 0; ui eslint over the changed files: 0 errors. Final-head union re-run: reported in the issue comment.Declared narrowing — verification ran UNLOCKED
scripts/pm/os-verify-lock.shcould not take the shared verify lock on this host: no usableflock. The shared verify lock is declared Linux-only (flockis util-linux, and a stock macOS does not ship it), so every heavy command in this PR (spec build, spec tests, ui type-check and vitest runs, the SDUI manifest regeneration, the ablations) ran directly, without the lock — a declared narrowing, not a silent one. No serialization guarantee held for these runs, nor for any sibling agent in this container while they ran. A second dispatched agent (#26678) shared the container.The downstream sweep
pnpm --filter '...@objectstack/spec' typecheckwas not run; in its place, a source census found no package outsideui/that names a retired member, andTypeScript Type Checkruns every package.Acceptance notes
onNavigate(a page). It is documented as the way a host withholds navigation (the related list's mobile gallery), so it is not changed here; the spec default reaches every block that is present. No card.pageshows the declaration instead; the name cell's hover ↗ opens the record as a full page #26681; if 18.3.0 publishes before it lands, the sentence names a control that is not there yet. Carrier: console list Display menu: 「打开记录」 (drawer | split) as a per-user preference; an object or view declaringpageshows the declaration instead; the name cell's hover ↗ opens the record as a full page #26681.skills/**, so it lands the Tier H way (an authorized approval, then the owning seat).git grep -Eword-boundary controls inui/packages/types, the macOS pipe truncation incheck-liveness.test.ts, the generation-stamp test, bash 3.2 probes) reproduce on any macOS checkout and are green in CI. Carrier: none.skills/**readingsskills/objectstack-ui/rules/navigation.md: 162 → 162 lines (the Surface bullet rewritten in place: the drawer,pageonly where declared, nomodalmode).skills/objectstack-ui/references/react-blocks.md(generated): 115 → 115.SKILL.md: 4606 → 4606. Theobjectstack-uipackage, every file: 2259 → 2259. No re-wrap, no growth.维护者速读(草稿)
page的对象才直接打开整页。元数据里的navigation.mode不再接受modal、split、popover,列表页的recordAction不再接受modal,写了会在校验时被拒并给出改法;省略mode时默认值从page改成drawer。控制台不再按字段多少替用户猜"整页还是抽屉"。左右分栏改成每个用户自己的浏览器偏好(本卡只做读取,设置开关在 console list Display menu: 「打开记录」 (drawer | split) as a per-user preference; an object or view declaringpageshows the declaration instead; the name cell's hover ↗ opens the record as a full page #26681)。os migrate meta --from 17列出改法,--write可直接改);数据库里已保存的视图会在读取时自动转换为抽屉,不会报错。② 以前因字段多而自动整页打开的对象,现在默认用抽屉打开;需要整页的对象要显式声明page。③ 由旧工具链解析过、已把page写死的产物仍按整页打开,删掉该值即可改用抽屉。④ 组件包删除了useOverlayAnchor导出和弹窗、浮层两种外壳,外部若直接用了会编译失败。回滚即 revert 本 PR,不写数据,没有不可逆动作。skills/**(Tier H)且改动超过 3000 行,需要GOVERNED_APPROVERS身份批准后再由席位落地。Generated by Claude Code