Repository navigation
Description
epic:#28130#1 · hotlong · session_040dfd80c4c9411b895c698e785a6848
Filing gate: ④ child of epic #28236 (P2, cut 2026-11-01 on the maintainer's 「四项提速措施全部按推荐执行」, schedule reviewed) · plan of record: ADR-0146 Rollout P2 "N-25 email template and translation editors into Studio", and the last paragraph of its Consequences. ⛔ Not a claim.
Blocked-by: #28410, #28407, #28562, #28561
Part of #28236 (P2, child N-25) · governs: ADR-0146 Consequences (last paragraph: "their simplified form editor is offered inside Studio's All-metadata area"), D1 (no Setup designer), D2 (the override lives in .custom) · lane: domain:ui · size: M.
Business reason. Today an admin who changes one line of HotCRM's welcome email, or one translated label, does it in Setup. That writes a package-less row which never exports, and which N-16b will refuse. With this card they edit it in Studio's All metadata area, and the change is an override in hotcrm.custom.
Measured (35b178bd0a).
- Setup edits both types on the generic item page
ui/packages/app-shell/src/views/metadata-admin/ResourceEditPage.tsx, which does the overlay save and thelayered.resettableReset. It uses:- forms from
default-schemas.ts(translation:118,email_template:302); previews/EmailTemplatePreview.tsx(sandboxed HTML with variables) andpreviews/TranslationPreview.tsx(read-only coverage), registered atpreviews/index.ts:60–61.
- forms from
email_templateis a bundle type keyed bylocale(default-schemas.ts:364).metadata-protocol/src/served-override.ts#isServedOverrideRowOfreads no bundle member. Whether N-10's read and N-09's gate key an override by(name, locale): not verified.- Studio has no host for either type. The Interfaces off-nav types are page, dashboard, report and view (
studio-design/StudioDesignSurface.tsx:2981). The All metadata area is N-26a. - Both types are
overridableinpackages/spec/src/kernel/customization-package.ts#OVERRIDE_TYPE_CLASSIFICATION.
Wanted.
- In Studio's All metadata area (N-26a), an email template or a translation opens in a simplified form editor. That editor is the fields Setup's form offers today plus the existing preview, reused: one component, not a copy.
- On a writable package, the editor writes that package's draft. On a read-only base, Customize takes N-17's path: ensure, a draft in the pair with
overrides: { package, basedOn }, Publish on the pair, and Restore package original. - An email template is edited per locale, and its override is per
(name, locale). If the server serves or gates a per-locale override wrongly, the dev stops and reports a server gap, with no client workaround. - A translation's override is its whole locale bundle (whole replace, D2). The editor says so before the first save.
- Setup's pages stay until N-28. This card removes nothing there.
Tests.
- Writable package: an edit writes that package's draft.
- Read-only base: Customize and an edit write a pair draft carrying the declaration, for one email template locale and one translation bundle.
- The other locale of the same template is untouched.
- Restore deletes the pair's item and the base reads back.
- A walled tenant admin sees Customize disabled.
Release safety. Safe on its own: it writes only through N-13 (#28407) and N-09 (#28410), and Setup keeps its form. N-16b (closing the package-less write) waits on this card, so admins always have a door for these two types.
Not here. The directory, lists and generic item page (N-26a). Directory marks (N-26b). Setup redirects (N-28). Removing Studio's overlay link (N-16b). View, dashboard and report (N-17).
Dedupe. #26649 and #27036 (closed: Setup opens each template locale as its own member). Searched email template editor studio, translation editor studio: no open card.
Links back: #28236, #28407, #28410, #26649, ADR-0146 D1, D2, Consequences.
Generated by Claude Code
Activity
objectstack-fleet commentedon Nov 1, 2026
domain:ui#4 · zhuangjianguo · session_01QsR2jR7ZFkpqmm4U8S1f9y
Unblocked · 2026-11-01T15:21Z ·
pm:blocked→pm:queueBlocked-by: #28410, #28407, #28562, #28561is met:.custommay declare; undeclared same names and a dependent'soverridesare refused at publish,os validateand install #28410 (N-09, the override write gate) closed completed, landed asd6e09673.0ecc2c49.d62d9737. ItsStudioCustomizeControls.tsx(Customize button, override badge, Restore item and dialog,overrideDraftBody,useServedOverride) andpackages-io.ts#ensureCustomizationPackageare the pieces this card reuses.3694d3b8.domain:ui#1's to claim.domain:ui#4does not claim it.Generated by Claude Code
objectstack-fleet commentedon Nov 1, 2026
domain:ui#1 · zhuangjianguo · session_01QsR2jUJ19MstFvFhjgiYTu
Claim: PM loop round 2 · 2026-11-01T15:24Z
Session:
session_01QsR2jTJcKuqRSJyv696zZ9(card thread, os-dev); dispatched by seat sessionsession_01QsR2jUJ19MstFvFhjgiYTuAccount:
zhuangjianguoBranch:
claude/project-thread-mtqfy5(platform-assigned to the card thread; it can push only this branch)Worktree:
objectstack-issue-28569Domain:
domain:uiSeat:
domain:ui#1File surface: the Studio All metadata area's item route in
ui/packages/app-shell/src/views/studio-design/StudioDesignSurface.tsx, a simplifiedemail_template/translationeditor reusingviews/metadata-admin/default-schemas.tsforms andpreviews/EmailTemplatePreview.tsx+previews/TranslationPreview.tsx(one component, not a copy),studio-design/StudioCustomizeControls.tsxandstudio-design/packages-io.ts(reuse; edit only where needed), their tests, studio i18n strings, and a root.changeset(stop on breach; explain in the report). Setup's pages are untouched.Container & model: M,
mode:thread,model: default tier (dispatch-gates --tier: no path-derived mandate),effort: highClause-②: no
Metadata-protocol: none
Responsibility:
n/a — not a defect cardThread-read: 6559905672
Premise: holds (at
origin/maind62d9737, Studio has no editor for either type: the All metadata area links an item to Setup's/apps/setup/metadata/<type>/<name>page,StudioDesignSurface.tsx:913; N-17's Customize controls are onmain)Serial constraints cleared: seat 4's #28572 (N-19, a base and its
.customas one surface), claimed now, may editStudioDesignSurface.tsx/StudioCustomizeControls.tsx; whichever lands later mergesmain.The
Clause-②: noline holds for strings in thestudionamespace (Record<string, string>). A newlocales/*pack key makes ityes (widening); the report says so beforepr_createand the seat amends this line. Per the card: if the server serves or gates a per-(name, locale)email-template override wrongly, stop and report a server gap, with no client workaround.objectstack-fleet commentedon Nov 1, 2026
domain:ui#1 · zhuangjianguo · session_01QsR2jUJ19MstFvFhjgiYTu
Claim amended · 2026-11-01T15:28Z · file surface widened and the premise line corrected; it adds to claim 6559946816. Branch, session, size and declaration lines are unchanged.
AreaItemOpeninstudio-design/StudioMetadataArea.tsx;StudioDesignSurface.tsx:913isStudioReadOnlyLink, the read-only chip. The premise itself holds: Studio has no editor foremail_templateortranslationatd62d9737.ui/packages/app-shell/src/views/studio-design/StudioMetadataArea.tsx(route both types on a package leg to the editor; take the surface's customization and draft-saved/publish props), one newstudio-design/StudioBundleItemEditor.tsx(the simplified editor: the served schema, else thedefault-schemas.tsentry, plus the registered preview, mounted not copied), andstudio-design/metadataArea.ts(the pure routing predicate).StudioDesignSurface.tsxonly passes the customization props down.(name, locale)member. The write gate judges shipping by(type, name)only; the editor never writes an unshipped member, so the dev reports that as an out-of-scope note for this seat to weigh.objectstack-fleet commentedon Nov 1, 2026
domain:ui#1 · zhuangjianguo · session_01QsR2jTJcKuqRSJyv696zZ9
os-dev-report
{ "issue": 28569, "status": "done", "branch": "claude/project-thread-mtqfy5", "pr": "https://github.com/objectstack-ai/objectstack/pull/28743", "head": "ca3cc5658795b0c138c527b2acbc4fdd735d5975", "session": "session_01QsR2jTJcKuqRSJyv696zZ9", "premise_still_valid": true, "claim_lines": [ "Clause-②: no", "Metadata-protocol: none" ], "measured": "Before code: the server's per-member reads hold. findServedOverlayRow narrows by overlayMemberWhere and isServedOverrideRowOf, and the layered read passes memberOption(member), so a pair row of one email template locale is served for that locale alone; the card's stop rule did not fire. The item route is AreaItemOpen in StudioMetadataArea.tsx (the seat amended the claim for it). Clause-② on source: three keys in the studio namespace table (Record<string,string>, outside the locales packs), so 'no' holds. Live on objectstack dev --fresh (examples/app-showcase, base com.example.showcase 0.1.0): ensure 201 created:true then 200 created:false, pair com.example.showcase.custom; a declared en-US draft in the pair saved 200; a draft PUT rejects ?member= (400 VALIDATION_ERROR, supported force, mode, package, surface), so the member is the body's locale.", "summary": "Studio's All metadata area opens an email_template or a translation of one package in a simplified editor (StudioBundleItemEditor.tsx, new): Setup's form fields (served schema, else the default schema) beside the type's registered preview (EmailTemplatePreview / TranslationPreview, mounted, not copied). areaItemOpensFormEditor routes it; ~all, ?scope=environment and ?review=1 keep the generic page. A writable package saves its draft through useDraftAutoSave and the shared version-pin guard. A read-only base takes N-17's Customize: the first saved edit calls the surface's ensure() once and saves into the pair with overrides {package, basedOn}; the badge reads the served declaration; Restore package original deletes the pair's draft then row. An email template is one item per (name, locale): reads, saves, badge and Restore carry ?member=<locale>, the locale is hidden in the form, and the other locales are untouched; a note before the first save says only that locale is customized. A translation's override is the whole bundle, and the editor says so before the first save. useServedOverride takes an optional member. StudioDesignSurface only passes customization, publishNonce and onDraftSaved. Setup's pages are unchanged.", "tests": "New StudioBundleItemEditor.customize-28569 (6): writable draft; read-only Customize of one template locale (ensure once, pair draft exact with declaration, badge, base untouched); the other locale untouched; Restore deletes the pair draft and row of that locale and the base reads back; walled admin sees Customize disabled; translation whole-bundle note and whole bundle saved. StudioMetadataArea.routes-28561 +4 (20 total), StudioDesignSurface.metadataArea-28561 +2. Ablations A1-A6 via scripts/ablation-replace.mjs, each landed, went red (4, 3, 1, 2, 1 failed in the editor file; 2 failed in the routes file) and was restored to HEAD by the tool. Wider set under the lock: 159 files (every test naming the touched modules plus every studio-namespace importer, zh-base-term and count-family census included), 2064 tests passed at 37edd173. turbo type-check app-shell + i18n exit 0 at 1f6a135c. ESLint on 10 changed files: 0 errors (one react-hooks/static-components error fixed with the comment EmbeddedItemEditor uses).", "gates": [ "derived set at ca3cc565, reconciled with dispatch-gates --ran: 34 derived, 34 run, 0 NOT-MEASURED, 0 UNRUN", "node scripts/check-adr-0087-registration.mjs --base origin/main :: exit 0", "node scripts/check-adr-0087-registration.mjs --self-test :: exit 0", "node scripts/check-changeset-no-major.mjs --base origin/main :: exit 0", "node scripts/check-closing-keyword-parity.mjs :: exit 0", "node scripts/check-closing-keyword-parity.mjs --self-test :: exit 0", "node scripts/check-comment-mask-corpus.mjs :: exit 0", "node scripts/check-empty-changeset.mjs --base origin/main :: exit 0", "node scripts/check-empty-changeset.mjs --self-test :: exit 0", "node scripts/check-sdui-manifest.mjs :: exit 0", "node scripts/check-sdui-manifest.mjs --self-test :: exit 0", "node scripts/pm/release-rehearsal-clone.mjs --self-test :: exit 0", "node scripts/release-pending-publish.mjs --self-test :: exit 0", "pnpm --filter @objectstack/spec run check:duration-unit-keys :: exit 0", "pnpm check:changeset-gate-self-tests :: exit 0", "pnpm check:closed-ceremony-text :: exit 0", "pnpm check:cross-package-test-inputs :: exit 0", "pnpm check:dispatcher-error-vocabulary :: exit 0", "pnpm check:driver-memory-census :: exit 0", "pnpm check:engine-double-contract :: exit 0", "pnpm check:gitlink-declared :: exit 0", "pnpm check:nul-bytes :: exit 0", "pnpm check:pm-changeset-deadline-census :: exit 0", "pnpm check:published-files :: exit 0", "pnpm check:query-options-erasure :: exit 0", "pnpm check:refd-timer-probe :: exit 0", "pnpm check:skill-identifier-liveness :: exit 0", "pnpm check:test-source-alias :: exit 0", "pnpm check:tier-file-adoption :: exit 0", "pnpm check:type-check-coverage :: exit 0", "pnpm check:type-check-debt :: exit 0", "pnpm check:undeclared-dep-imports :: exit 0", "pnpm check:watch-hint-literal :: exit 0", "pnpm check:where-matcher :: exit 0", "pnpm --dir ui exec vitest run scripts/ :: exit 0", "PR-body gates on the sent body: check-changeset-no-major (GITHUB_EVENT_PATH) 0, check-adr-0087-registration 0, check-closing-keyword-parity 0 (--body binds only #28569), check-partof-closing-keyword (PR_BODY) 0", "check-sdui-manifest 0", "CI check-runs on #28743: not yet reported at report time" ], "dogfood_battery": "n/a: no ! in the title and no (narrowing) arm", "line_budget": "11 files, +1175 / -25 vs the merge base (dispatch-gates: 1199 changed lines), under the 3000-line Tier H threshold", "files_changed": [ ".changeset/28569-studio-email-template-translation-editor.md", "ui/packages/app-shell/src/views/studio-design/StudioBundleItemEditor.tsx (new)", "ui/packages/app-shell/src/views/studio-design/StudioBundleItemEditor.customize-28569.test.tsx (new)", "ui/packages/app-shell/src/views/studio-design/{StudioCustomizeControls,StudioDesignSurface,StudioMetadataArea}.tsx", "ui/packages/app-shell/src/views/studio-design/metadataArea.ts", "ui/packages/app-shell/src/views/studio-design/{StudioMetadataArea.routes-28561,StudioDesignSurface.metadataArea-28561}.test.tsx", "ui/packages/i18n/src/namespaces/studio/{en,zh}.ts" ], "deviations": [ "Two existing route/surface tests used a translation as 'a type no pillar designs'; that case now opens the editor, so they were repointed to agent/support_bot and agent/a1.", "Restore does not refresh the header's pending count at once; it emits the metadata-refresh pulse, as the Interfaces pillar's Restore does (in Acceptance notes).", "Merged origin/main once (3 commits, none in ui); the merge commit carries the two trailers.", "PR opened without assignees or labels; no GitHub MCP write." ], "mcp_calls": "2 reads: issue_read on #28569 (card) and pull_request_read get on #28711 (body shape). No MCP write. Separately, subscribe_pr_activity on #28743 (claude-code-remote).", "api_writes": "2: relay repository_dispatch x2 (pr_create #28743, run 41327284775, read-back identical 8587 bytes; this os-dev-report comment). git push x2 is not a REST write.", "open_questions": [], "out_of_scope_findings": [ "class: b · Seam: spec:overrides {package, basedOn} (ADR-0146 D2) → runtime:overrideWriteVerdict / judgeOverrideWrite · reach: measured once on objectstack dev --fresh (examples/app-showcase, base com.example.showcase 0.1.0 ships email_template showcase_task_done_email in en-US only): PUT /api/v1/meta/email_template/showcase_task_done_email?mode=draft&package=com.example.showcase.custom with body locale fr-FR and overrides {package: com.example.showcase, basedOn: 0.1.0} answered 200 (seq 2), the pair's draft at member fr-FR read back carrying the declaration, and POST .../publish?package=com.example.showcase.custom&member=fr-FR answered 200 (seq 3): a declared override of a locale the base does not ship is admitted and goes live. (?member= on the PUT itself is refused 400 as unsupported; the member is the body's locale.) · evidence: packages/metadata-protocol/src/protocol-packaged-base-refusal.ts:459 shippers = shippedArtifactsOf(request.type, request.name), not per member; :339-340 declared_package_ships_no_such_item checks only that list; OverrideWriteSubject (:202) carries no member · the editor never writes such a row (it saves the locale it read) · dedupe words: override unshipped locale, declared_package_ships_no_such_item member, email_template override per locale gate" ] }objectstack-fleet commentedon Nov 1, 2026
domain:ui#1 · zhuangjianguo · session_01QsR2jUJ19MstFvFhjgiYTu
Landed · 2026-11-01T17:15Z · PR #28743 merged through the merge queue as
272fec05at 2026-11-01T17:14:23Z (GitHub's clock). ACCEPT 6561010583 at headca3cc565.272fec05~1)origin/main)ui/packages/app-shell/src/views/studio-design/StudioBundleItemEditor.tsxexistsareaItemOpensFormEditorinstudio-design/metadataArea.tsFixes #28569closed this card;pm:dispatchedcomes off in the same stroke as this note. Follow-up filed from this card: #28746 (domain:server). Unblocks: #28578 (N-28), whose last open blocker this was.