Skip to content

feat(app-shell): Studio customizes an installed package's page into its customization package (#30063) - #30233

Merged
objectstack-fleet[bot] merged 2 commits into
mainfrom
claude/project-thread-6e8nin
Nov 5, 2026
Merged

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

Conversation

@objectstack-fleet

Copy link
Copy Markdown
Contributor

Requested by Yibo Wang · project thread

Fixes #30063

Clause-②: no
Metadata-protocol: none

维护者速读

  • 改了什么:已安装(只读)包里的页面,在 Studio「界面」中打开时现在会出现「定制」按钮;按下后可在页面设计器里编辑,首次保存把整页复制进 包名.custom 配对包并声明 overrides: { package, basedOn },发布后运行中的应用显示定制后的页面;「恢复原版」删除这份副本。导轨的「已定制」标记、「仅看定制」计数和「全部元数据」的页面卡片计数也都覆盖页面。
  • 为什么改:服务器早已支持页面覆盖(卡片实测过),只缺 Studio 的入口,客户想改欢迎页、首页只能等代码发版。
  • 风险与回滚:很小。只把 page 加进 Studio 可定制类型清单,首存、恢复、标记都走仪表板/视图现成的通用路径;无 spec、服务端、元数据类型改动;可写包的页面编辑不变。回滚即 revert 本 PR。定制后的页面是整份副本,厂商后续版本对该页的修改要恢复原版后重做才会出现。
  • 先看:StudioCustomizeControls.tsx 的 STUDIO_CUSTOMIZE_TYPES 一行;InterfacesPillar.customize-28562.test.tsx 新增的 "Customize a read-only base’s page into its pair (objectstack#30063)" 一组测试。
  • 两分钟验证:在仓根运行 pnpm --dir ui exec vitest run packages/app-shell/src/views/studio-design/InterfacesPillar.customize-28562.test.tsx -t 30063,预期 5 条通过、其余跳过。
  • 需要你做的:无,席位落地。

What changes

Studio's Customize door (STUDIO_CUSTOMIZE_TYPES in StudioCustomizeControls.tsx) now lists page beside view, dashboard, report and app. page is overridable in the spec's classification (isOverridableMetadataType), so isStudioCustomizeType('page') now answers true. That one line is the whole runtime change. Everything else a page needs was already type-generic in the Interfaces pillar, and is now reached for pages:

  1. Customize, first save, later saves. A read-only base's page shows Customize in its header (the leaf path leafCustomizable). Until it is pressed, the designer edits nothing. The first saved designer edit asks the ensure door once and writes the whole page into the pair (overrideDraftBody: the page without its _ keys, plus overrides: { package: BASE, basedOn: BASE_MANIFEST_VERSION }), pinned to "no draft there" (ifNoneMatch: '*'). Later edits save into the pair's copy at the version they were answered. The base is never written.
  2. Designer edits reach the running app. The page's draft sits in the pair's pending changes; publishing it serves the customized page at /apps/APP/page/NAME.
  3. Restore package original. The header's override mark offers Restore, which deletes the pair's draft and row of the page after a confirmation; the shipped page reads back.
  4. Marks, "only customizations", All metadata tile. These read the server's customization ledger and are type-agnostic. With the page's override served, the rail row carries the "customized" mark, "only customizations" counts and lists it, and the All metadata page tile counts it.

No spec, server or metadata-type change (card Acceptance 5): the browser check below ran end to end against an unchanged server.

Out of scope, as the card says: a renamed page's label can be hidden by the base's translation. That is filed as #30062 (its third finding) and is not fixed here.

Diff: 244 changed lines (+233 / −11, 5 files), under the 1,500 split line, so Acceptance 1–4 ship together.

Tests

InterfacesPillar.customize-28562.test.tsx (the shared harness), new top-level describe "Customize a read-only base’s page into its pair (objectstack#30063)", 5 tests. The page designer is reduced to a stand-in canvas (its first text block and one in-place edit), the way the harness already stands in for the dashboard canvas:

  • Customize is offered and writes nothing; the first designer edit asks the door once (201) and writes the whole page into the pair with overrides: { package, basedOn: '4.0.0' } and ifNoneMatch: '*'; the base row is unchanged; the override chip appears; the next edit saves into the pair at ifMatch: 'v1' without asking the door again.
  • A served override opens as the pair's copy, editable, with no Customize to press, and an edit saves into the pair.
  • Restore deletes the pair's draft and row (DELETE …state=draft&package=PAIR, then DELETE …package=PAIR), and the shipped page reads back with Customize offered again.
  • The rail marks the customized page (data-kind="customized"), "only customizations" reads 1 and lists the page alone.
  • CONTROL: on a writable package a page edit saves at the package, with no door and no overrides.

Also:

  • StudioCustomizeControls.customize-28562.test.tsx: page moves from the "is not customized" list to the "is customized" list.
  • metadataAreaHost.customizations-28568.test.tsx: the All metadata page tile counts a page's override ("1 customized"), and the header's total includes it.
  • CONTROL for the other types: the existing dashboard, view, list-view and app (navigation) describes in the same harness ran unchanged and green.
Ablation (one-time, not kept)

Committed first, then node scripts/ablation-replace.mjs replaced 'report', 'page', 'app' with 'report', /* ablated-30063 */ 'app' in StudioCustomizeControls.tsx (anchor 1 → 0, blob 1452690e → f2d008e3) and ran pnpm --dir ui exec vitest run packages/app-shell/src/views/studio-design/InterfacesPillar.customize-28562.test.tsx -t 30063 (ui vitest resolves @object-ui/* to src, so no build leg):

  • Red: 3 failed, 2 passed. The first-save pin, the served-override pin and the Restore pin failed (no Customize button, no override chip).
  • Green as expected under the ablation: the rail-mark pin (marks read the server's ledger, not this list) and the writable-package CONTROL.
  • Restored: blob back to 1452690e = HEAD, git diff HEAD empty (the script's own check).

Acceptance notes

Verification

All under scripts/pm/os-verify-lock.sh unless noted. Head a73ecd64 (origin/main dfafae81 merged, clean) for the rows marked "after the merge"; the first rows ran on eee39383.

  • Build: turbo run build --filter='@object-ui/app-shell...', 68/68 tasks; then the full build (--filter=!@objectstack/docs), 119/119, for the dev boot.
  • The three touched test files: 3 files, 73 tests, all passed.
  • The whole packages/app-shell/src/views/studio-design/ directory: 324 files, 2397 tests, all passed. After the merge (full build 119/119 first), that directory plus metadataAreaHost.customizations-28568.test.tsx: 325 files, 2415 tests, all passed.
  • @object-ui/app-shell type-check (both tsconfigs) → exit 0, before and after the merge.
  • npx eslint on the 4 touched source/test files (unlocked): 0 errors, 15 warnings (all react-refresh/only-export-components on existing exports of StudioCustomizeControls.tsx).
  • node scripts/check-sdui-manifest.mjs → exit 0.
  • node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack: 35 commands (the same 35 before and after the merge), each run on a73ecd64 and recorded :: exit 0; --ran → "35 derived, 35 run, 0 NOT-MEASURED, 0 UNRUN". Record: project file ui-orders/30063-gates-ran.list.
  • Changed lines vs the human-merge threshold: 244 (+233 / −11, 5 files), under 3,000.

🤖 Generated with Claude Code


Generated by Claude Code

…ts customization package

Studio's Customize door now covers page: on a read-only base a page
opened in the Interfaces pillar offers Customize, the first designer
edit copies the whole page into the base's .custom pair declaring
overrides { package, basedOn }, and Restore package original deletes
the copy. Marks, the only-customizations count and the All metadata
page tile cover it through the existing type-agnostic paths.

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

Copy link
Copy Markdown
Contributor Author

domain:ui#1 · os-support-ai · session_01WGufzfE4V9fzZgJLrLDmMC

维护者速读(席位终稿)

  • 改了什么:已安装(只读)应用里的页面,比如 CRM 的欢迎页,在 Studio 里现在也有「定制」按钮,和仪表板、报表、视图一样。第一次保存时把整页复制进该应用的定制包,发布后运行中的应用显示改过的页面;「恢复原版」删掉定制,原页面回来。
  • 为什么改:客户装了应用后想改首页或落地页的文字和区块,以前 Studio 只显示「只读」,没有入口;服务端其实早就支持页面覆盖,只差 Studio 这道门。
  • 风险与回滚:低。只把 page 加进 Studio 可定制类型表,其余全部复用已有的定制、恢复、标记逻辑;可写包的页面编辑不变,基础包从不被写。回滚即 revert 本 PR。
  • 先看:ui/packages/app-shell/src/views/studio-design/StudioCustomizeControls.tsx 的 STUDIO_CUSTOMIZE_TYPES。
  • 两分钟验证:pnpm --dir ui exec vitest run packages/app-shell/src/views/studio-design/InterfacesPillar.customize-28562.test.tsx;或看 PR 里的 9 张浏览器截图(定制 → 发布 → 运行时显示 → 恢复原版)。
  • 需要你做的:无。不是受管面,CI 全绿,席位直接落地。
  • 席位意见:ACCEPT。

席位核对 · ACCEPT · head a73ecd643f951b2470bbf096f586c94f42bd39e2 · 2026-11-05T03:05Z

  1. Shape: draft, base main, head merges origin/main dfafae81. Fixes #30063. One changeset: @object-ui/app-shell patch, Clause-②: no / Metadata-protocol: none. 5 files, +233/−11 (one-line product change: page in STUDIO_CUSTOMIZE_TYPES, plus its docblock; the rest is tests). check-governed-merges --pr 30233: NOT governed. No spec, server or metadata-type change.
  2. Premise and mechanism, read by the seat: the existing first-save copy (overrideDraftBody, overrides: { package, basedOn }), restoreOriginal, rail marks and the All metadata tile are type-driven, so adding page is the whole door; the five new tests drive the first save (ifNoneMatch, then ifMatch v1), editing a served override, Restore (DELETE draft then row at the pair), rail mark + 仅看定制 1, and a writable-package CONTROL.
  3. Evidence: ablation (drop page) turns the three behaviour pins red, the two ledger/CONTROL pins stay green as expected; studio-design 325 files / 2,415 tests green after the main merge; app-shell type-check exit 0; dispatch-gates 35/35 exit 0 with --ran reconciled. Browser on examples/app-crm (zh-CN, admin), 9 shots in the PR: 定制 → edit → ensure 201 + PUT draft into com.example.crm.custom → publish → /apps/com.example.crm/page/crm_welcome shows the edit (seat read shot 04) → 恢复原版 → shipped page back, base body identical. CI on this head: 44 success, 11 skipped.
  4. Accepted deviations: the All metadata tile pin lives in metadata-admin/metadataAreaHost.customizations-28568.test.tsx, outside the claimed studio-design/** surface: the existing harness for that tile, accepted. Browser run on eee39383 before the main merge, which changed no line of this diff: accepted.
  5. Out of scope: after 恢复原版 the rail keeps the mark and 仅看定制 (1) until a reload (shared restore path; seat read shot 08) → filed Studio: after 恢复原版 the rail keeps the customized mark and 仅看定制 (1) until a reload #30238. The page tile's 只读 word → carried by fix(metadata-protocol,app-shell): serve a pair's mapping override, let a pair remove any override it holds, name a working remedy for an excluded type (#30062) #30226 (Declared overrides: a mapping override publishes but is never served, doc and mapping overrides cannot be restored, and an override's label can be hidden by translation #30062 item 4). Serial: feat(studio): customize a read-only package's dataset in the All metadata area, see what uses it before publishing, and read a refused publish at the dataset #30224 (Studio: customize an installed package's dataset, showing what else it changes before the customer publishes #30064) touches the same STUDIO_CUSTOMIZE_TYPES docblock; whichever lands second merges main and keeps both sentences. Skills-docs: n/a.
  6. Landing: ordinary queue, now: relay pr_ready + automerge_enable, follow to MERGED, landing record on Studio: customize an installed package's page, so a customer can change a home or landing page without a code release #30063.

@objectstack-fleet
objectstack-fleet Bot marked this pull request as ready for review November 5, 2026 03:07
@objectstack-fleet
objectstack-fleet Bot added this pull request to the merge queue Nov 5, 2026
Merged via the queue into main with commit 797f925 Nov 5, 2026
56 checks passed
@objectstack-fleet
objectstack-fleet Bot deleted the claude/project-thread-6e8nin branch November 5, 2026 03:23
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/m tooling

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Studio: customize an installed package's page, so a customer can change a home or landing page without a code release

1 participant