Uh oh!
There was an error while loading. Please reload this page.
feat(app-shell,data-objectstack): the designer states its package on the publish step (#5420) - #6115
Merged
yinlianghui merged 3 commits intoAug 24, 2026
Conversation
…the publish step Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01CSoz9uGhaaSgiq3hshtN7L
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01CSoz9uGhaaSgiq3hshtN7L
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01CSoz9uGhaaSgiq3hshtN7L
Contributor
✅ 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
|
yinlianghui
marked this pull request as ready for review
August 24, 2026 18:10
Uh oh!
There was an error while loading. Please reload this page.
yinlianghui
deleted the
claude/issue-5420-designer-publish-package-binding
branch
August 24, 2026 18:22
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for freeto join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes#5420
What was broken
Studio's metadata designer (
MetadataResourceEditPage) runs a save→publish loop: Savewrites a draft bound to the software package the editor is scoped to
(
PUT /meta/:type/:name?mode=draft&package=PKG_ID), and Publish promotes that draft(
POST /meta/:type/:name/publish). Verified on the merge-basef471b4f73, both halvesexactly as the card asserted:
ResourceEditPage.doSave→MetadataClient.saveif (options.packageId) params.push(...)ResourceEditPage.doPublish→MetadataClient.publishUntil
objectstack-ai/objectstack#10354the publish route did not accept the parameter, sothe silence was correct. It has landed, so the silence is now a gap:
objectstack#9612'spackage-closure narrowing at the runtime publish gate could not fire on an HTTP-driven
promotion, which is precisely this designer on every edit.
What this changes
packages/data-objectstack—MetadataClient.publish()acceptspackageIdand emits?package=PKG_ID, deliberately the same wire spelling, the sameencodeURIComponenttreatment and the same conditional shape
save()already uses one door over. The siblinglayered()door was already written this way, so this is the file's existing idiom, not anew one.
packages/app-shell—ResourceEditPagenow reads the binding for both steps fromone derivation,
readActivePackageBinding(). That function is the old inline IIFE fromdoSave, hoisted to module scope and called bydoSaveanddoPublishalike. One value,one spelling, and — the part that matters for drift — one derivation: a second inline copy in
the publish path would have been free to disagree with the save path, most easily about the
?package=allfold, while both looked correct in isolation.Omit, never empty
?package=with an empty value and nopackagekey at all are different request bytes eventhough today's normaliser folds them (
alland the empty value both mean "env-local overlay,no package"). The parameter is omitted when the designer holds no binding, because that
is the shape the save door already followed and the two calls of one loop must not disagree.
It also matches how the framework reads it downstream:
promoteDraftForPublishbranches onthe key being present, where a present-but-null package pins the draft lookup to unbound
rows and a packaged draft stops being found.
The acceptance criterion
"The designer states the binding it already knows, so the narrowing is reachable."
Explicitly not "publishing got faster". Package-closure narrowing has a second,
independent gate this does not touch:
narrowObjectsToPackageClosurekeeps any objectcarrying no
_packageIdprovenance unconditionally, and a tenant-authored overlay corpuscarries none — so on such a corpus stating the package narrows nothing at all. Nothing in
this PR asserts, or should be accepted against, a latency claim.
The framework version this was measured against
@objectstack/rest17.2.0. The card's warning — "the parameter is ignored by anyframework build older than it, silently" — was discharged by evidence, not by the PR being
merged:
objectstack#10354is MERGED intomain(merged_at2026-08-21T02:14:01Z, basemain).origin/maincarries it:packages/rest/src/rest-server.ts,the
POST /meta/:type/:name/publishhandler, readsreq.query?.package, foldsallandthe empty value to
undefined, and guards it withrefuseRepeatedQueryParams(req, res, ['package'])— the whole block tagged[#10063].packages/rest/CHANGELOG.mdunder## 17.2.0:"Additive:
POST /meta/:type/:name/publishnow accepts thepackagequery parameter,so a single-item draft→active promotion can state the package it belongs to (#10063)."
@objectstack/*line to 17.2.0 from the registry,i.e. the framework line objectui builds against is at or past the first release carrying it.
Tests
New, both directions pinned in the same run — a lone "publish now sends the package" test
is trivially satisfiable by always sending it, so the unbound counter-probe is not optional:
packages/data-objectstack/src/metadata-client.publishPackageBinding.test.ts— requestbytes: bound →
.../publish?package=com.example.showcase; unbound →.../publish, withabsence asserted as
URLSearchParams.has('package') === false, never as=== ''. Also pinsthat the encoded id is byte-identical to what the save door emits for the same value.
packages/app-shell/src/views/metadata-admin/ResourceEditPage.publishPackageBinding.test.tsx— the loop: one render dirties the draft, lets the real save door fire, and then clicks
Publish, asserting the publish states the same id the save just stated.
?package=alland abare URL both pin key absence on the publish.
Which assertions would still pass on a revert
The bound-direction cases fail on a revert (reverted,
doPublishpasses no third argument atall). The key-absence assertions would also pass on a revert — absence is exactly what the
old door did, and no absence assertion can separate those two worlds by itself. They are not
aimed at the revert; they are the counter-probe for always-sending. Their revert-sensitive
companion sits beside them in the same case (
expect(publishOpts).toBeTypeOf('object'), red onundefined), so the pair separates all three worlds: correct, reverted, always-send.Reverse verification — two ablations, disjoint red sets
Run on the shipping tree
c3e51ceb3; each leg proved on disk by grepping injected andremoved text separately, restored under
trap … EXIT INT TERMwith a cwd-independentgit -C … checkout --, and confirmed clean (git diff HEAD --statempty) afterwards. Theimplementation was committed before any ablation ran, so no restore could take an
uncommitted edit with it. No rebuild stands between a mutation and a run here: this repo's
vitest.config.mtsaliases every@object-ui/*tosrc, and each suite imports its subjectby a same-package relative specifier — which the ablations themselves demonstrate by flipping
the verdict with no build in between.
doPublish's third argument (readActivePackageBindingrefs 3 → 2, bare-publish line 0 → 1)MetadataClient.publish(marker 0 → 1, conditional-qs lines 2 → 1)The two red sets are disjoint. B is the exact mistake
objectstack#10354's own commentwarns about, and the suite catches it.
Gates
Derived by enumerating each CI job's own step list (
ci.yml,lint.yml,changeset-*.yml,control-bytes.yml), not from top-level script names. All run onc3e51ceb3, exit codescaptured before any pipe:
turbo run type-check --filter=@object-ui/app-shell --filter=@object-ui/data-objectstack(script nametype-check, hyphenated;^buildruns first, so this is not an unbuilt closure)vitest run— the two new suites + the pre-existingmetadata-client.test.tsscripts/check-changeset-presence.mjsscripts/check-changeset-no-major.mjsmajorbump."scripts/check-changeset-fixed.mjsscripts/check-control-bytes.mjsscripts/check-lint-coverage.mjsscripts/check-type-check-coverage.mjseslint — declared narrowing. Run path-filtered over the merge-base delta rather than
repo-wide, and the narrowing is measured rather than asserted:
--format jsonreports 4 fileentries, 0 errors, 66 warnings, so all four changed source files are inside eslint's own
configured population (not ignored) and the green is non-vacuous for this diff. Cross-checked
against the added-line ranges: both new test files are 0 warnings / 0 errors, and exactly
one warning falls on an added line —
@typescript-eslint/no-explicit-anyon the movedclient.publishcall, which carries a genericanytype argument. That argument is not new:it is the same one that call already had before this PR, and it matches the surrounding
client.layeredandclient.savecalls, which are written the same way. eslint is nottype-aware in this config, so this diff cannot move the verdict on any file it does not touch.
The repo-wide
pnpm lint(turbo run lint, 46 packages) is CI's run.Not done here
⛔ No change to
MetadataClient.publishDraftorusePublishAllDrafts. That is the"publish everything pending" path, not the designer's loop, and it already routes
package-bound drafts through
POST /packages/:id/publish-drafts— the by-reference door isused there only for drafts whose
packageIdisnull, i.e. exactly the unbound case.Out of this card's file surface.
Generated by Claude Code