Skip to content

Commit fd40831

Browse files
objectstack-fleet[bot]claudegithub-actions[bot]os-zhuangbaozhoutao
authored
merge(ui): objectui becomes the ui/ workspace of this repository — subtree import at the frozen tip 9e1fe03 plus the adaptation series (objectstack#25752) (#25825)
<!-- claude-projects-attribution: {"github_login":"os-tesla"} --> _Requested by **Tesla** · [project thread](https://claude.ai/code/project/chan_01AXsQkLFKRdQCqQ3RTr6Jao?thread=cmsg_01AXsQkLFKRdQCqQ3RTr6JaoYYsrey6RNUKhoT2wtFatch)_ Part of objectstack#25752 (child 5, the cutover procedure; steps 3–4). Part of objectstack#25677. Clause-②: no Metadata-protocol: none ## ⛔ Merged by the maintainer at the Tier H sitting, 2026-10-26 09:00Z (北京 17:00) — not before This PR is **Tier H by size** (the whole objectui tree, ~2.6 M lines) and is the first of four merged in order at the sitting: this PR → 4a #25745 → PR B (objectstack#25697 item 6) → 4b PR 2 #25712, then the required-checks change (step 6). It stays draft until the seat says it is green; the seat never enqueues it. Reviewers: the GOVERNED_APPROVERS, requested when it leaves draft. ## What this PR is objectui (objectstack-ai/objectui) becomes the `ui/` workspace of this repository, per the maintainer's D1 ruling (objectstack#25677: one repository, one fixed version group, the objectui repository archived as a migration notice). 1. **`git subtree add --prefix=ui`** of objectui `main` at its **frozen tip `9e1fe032866965c7cb0460cff2b6af09350236b7`** (objectui#13545, the final `.objectstack-sha` pin, pointing at objectstack `5e7cf63e71`). objectui `main` is frozen since 2026-10-25 15:40Z (freeze notice: objectstack#25752); nothing lands there after that commit. History is kept (no squash). 2. **Root workspace file.** pnpm v10 reads `overrides` and `patchedDependencies` from `pnpm-workspace.yaml` only, so objectui's `pnpm` field moves there: the qs / shell-quote / tmp / uuid floors, the single react line (19.3.0), the `@radix-ui/react-focus-scope@1.1.16` patch; the react-router override narrows to the 8.x line it fixes (objectui's react-router-dom 7.18 pins a 7.x react-router with no fixed version to force). 3. **Adaptation series (objectstack#25803, card J)** — one commit per gate or concern, each named in its message. Drop 1: every `@objectstack/*` range in the 32 ui manifests becomes `workspace:^`; `ui/` stops being a workspace root of its own (nested `pnpm-workspace.yaml` deleted, `pnpm` field and `preinstall` pin check removed, `turbo.json` parked as `turbo.objectui.json`); the ui root manifest drops its turbo-recursive scripts; `scripts/ui-import/splice-lockfile.mjs` carries objectui's lockfile resolutions into the merged lock. Drop 2 (root gates, ledgers, eslint ignore, skills move, the K1 scoping of `check:published-files`) follows in this PR before it leaves draft; the list is in `/mnt/project-files/d1-merge/out/cutover-adaptation/SERIES.md` and will be mirrored here. 4. **One lockfile.** `ui/pnpm-lock.yaml` is removed; the root lock is objectui's resolutions spliced in (`moved 0` against objectui's own lock at the splice), reconciled twice with `pnpm install --lockfile-only`, then `pnpm dedupe`; `pnpm install --frozen-lockfile` and `pnpm dedupe --check` exit 0. `ai` 7.0.118 and `@ai-sdk/react` 4.0.121 hold one copy each. ## What this PR does not do - It does not touch `.github/workflows/**`, `AGENTS.md`, `docs/adr/**` or CODEOWNERS: the pin-gate retirement is PR B, the release lane is 4a #25745, the workflow port is 4b PR 2 #25712, the governance for `ui/` landed in #25732. - It does not remove the `packages/console` pin field or its build switch (sub-issue 4). - It does not conform the 39 `@object-ui` manifests to `check:published-files` (maintainer ruling K1: the gate is scoped off `ui/` here; aftercare objectstack#25753). ## Accepted red classes (procedure #25752, "Accepted red classes") and the bill Anything red on this PR is read against `/mnt/project-files/d1-merge/out/5-cutover-bill-notes.md` (items 1–36). A red that is not on the bill stops the sitting until it is ruled. ## Verification _(filled by the seat before the PR leaves draft: the CI run, the local gate sweep, the lock verdicts and sha, the `git am` acceptance of the series.)_ 🤖 Generated with [Claude Code](https://claude.com/claude-code) https://claude.ai/code/session_01RSDL3SFtJaKzcymvCD8RsU --------- Co-authored-by: objectstack-fleet[bot] <332303061+objectstack-fleet[bot]@users.noreply.github.com> Co-authored-by: Claude <noreply@anthropic.com> Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> Co-authored-by: os-zhuang <jack@objectstack.ai> Co-authored-by: 包周涛 <baozhoutao@hotoa.com> Co-authored-by: marchtian <tianqi@hotoa.com>
1 parent e2aaaf1 commit fd40831

9,296 files changed

Lines changed: 2600744 additions & 1587 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

‎.changeset/config.json‎

Lines changed: 40 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -80,13 +80,51 @@
8080
"@objectstack/account",
8181
"create-objectstack",
8282
"@objectstack/connector-openapi",
83-
"@objectstack/verify"
83+
"@objectstack/verify",
84+
"@object-ui/app-shell",
85+
"@object-ui/auth",
86+
"@object-ui/cli",
87+
"@object-ui/collaboration",
88+
"@object-ui/components",
89+
"@object-ui/console",
90+
"@object-ui/core",
91+
"@object-ui/create-plugin",
92+
"@object-ui/data-objectstack",
93+
"@object-ui/fields",
94+
"@object-ui/i18n",
95+
"@object-ui/layout",
96+
"@object-ui/mobile",
97+
"@object-ui/permissions",
98+
"@object-ui/plugin-ai",
99+
"@object-ui/plugin-calendar",
100+
"@object-ui/plugin-charts",
101+
"@object-ui/plugin-chatbot",
102+
"@object-ui/plugin-dashboard",
103+
"@object-ui/plugin-designer",
104+
"@object-ui/plugin-detail",
105+
"@object-ui/plugin-editor",
106+
"@object-ui/plugin-form",
107+
"@object-ui/plugin-gantt",
108+
"@object-ui/plugin-grid",
109+
"@object-ui/plugin-kanban",
110+
"@object-ui/plugin-list",
111+
"@object-ui/plugin-map",
112+
"@object-ui/plugin-markdown",
113+
"@object-ui/plugin-report",
114+
"@object-ui/plugin-timeline",
115+
"@object-ui/plugin-tree",
116+
"@object-ui/plugin-view",
117+
"@object-ui/providers",
118+
"@object-ui/react",
119+
"@object-ui/react-runtime",
120+
"@object-ui/sdui-parser",
121+
"@object-ui/types"
84122
]
85123
],
86124
"linked": [],
87125
"access": "public",
88126
"baseBranch": "main",
89127
"updateInternalDependencies": "patch",
90-
"ignore": [],
128+
"ignore": ["@object-ui/example-*", "@object-ui/site", "@object-ui/runner", "@object-ui/test-support"],
91129
"privatePackages": { "version": true, "tag": false }
92130
}

‎content/docs/protocol/backward-compatibility.mdx‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -116,7 +116,7 @@ Read the **Breaking?** column, not the version number: in the 17.x minors every
116116
1. **RFC (Request for Comments)** — Breaking changes are proposed as GitHub issues with the `protocol:breaking` label.
117117
2. **Deprecation** — The old behavior is deprecated in a MINOR release (see timeline above).
118118
3. **Migration Guide** — A detailed migration guide is published before the removal lands, in the release notes for the version that carries it.
119-
4. **Release** — In the 17.x minors the breaking change shipped in the next **MINOR** version, carrying a changeset entry marked `**BREAKING**`, and `scripts/check-changeset-no-major.mjs` failed a pull request that declared a `major` bump, because under lockstep one `major` would promote all 73 published packages. During the v18 prerelease it shipped in the next `18.0.0-next.N`, because the guard stood aside in pre mode and a pull request could declare a major bump. Changesets has exited pre mode, so the guard fails a `major` bump again. See [how a breaking stage lands on the v18 line](/docs/releases/v18#how-a-breaking-stage-lands-on-this-line).
119+
4. **Release** — In the 17.x minors the breaking change shipped in the next **MINOR** version, carrying a changeset entry marked `**BREAKING**`, and `scripts/check-changeset-no-major.mjs` failed a pull request that declared a `major` bump, because under lockstep one `major` would promote all 111 published packages. During the v18 prerelease it shipped in the next `18.0.0-next.N`, because the guard stood aside in pre mode and a pull request could declare a major bump. Changesets has exited pre mode, so the guard fails a `major` bump again. See [how a breaking stage lands on the v18 line](/docs/releases/v18#how-a-breaking-stage-lands-on-this-line).
120120

121121
---
122122

@@ -196,7 +196,7 @@ The `@objectstack/spec` package provides additional stability guarantees:
196196

197197
### Which surfaces this covers
198198

199-
**All of them.** It was not scoped to an experimental corner or a pre-release channel: all **73** packages published from this repository belong to a single Changesets `fixed` group, so they share one version number and one policy. No published surface was exempt.
199+
**All of them.** It was not scoped to an experimental corner or a pre-release channel: all **111** packages published from this repository belong to a single Changesets `fixed` group, so they share one version number and one policy. No published surface was exempt.
200200

201201
The convention was enforced rather than informal — `scripts/check-changeset-no-major.mjs` failed a pull request that introduced a `major` bump, because under lockstep a single `major` on one package would promote the entire stack. During the v18 prerelease Changesets was in pre mode with the tag `next` and the guard stood aside, so a pull request on the v18 line could declare a major bump. Changesets has exited pre mode, and the guard fails a `major` bump again.
202202

‎eslint.config.mjs‎

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -912,6 +912,30 @@ const NEVER_LINTED = Object.freeze([
912912
'**/.turbo/**',
913913
]);
914914

915+
// ───────────────────────────────────────────────────────────────────────────
916+
// Source trees linted by THEIR OWN flat config, not by this one. Global-only:
917+
// unlike NEVER_LINTED this is authored source, so it is not spread into the
918+
// per-object `ignores` below — the one global object removes it from this
919+
// config's population, and nothing here may ever start matching it by accident
920+
// of a `files` glob.
921+
//
922+
// `ui/**` is the objectui tree imported at the cutover (decision G1 on #25752,
923+
// verbatim 「17e · 17f · 17g · 同意」): `ui/eslint.config.js` lints it, the
924+
// `Lint UI` CI job runs that config, and the two configs converge in the
925+
// aftercare card #25753. Census of what this one line stops, measured over the
926+
// merged tree on the verify branch (9e37acd126, `pnpm lint` exactly as
927+
// package.json spells it): 121 errors and 16,511 warnings, every one of them
928+
// under `ui/**` (dominant rule `react-hooks/static-components`, then
929+
// `react-hooks/purity`, `no-restricted-imports`,
930+
// `@typescript-eslint/no-empty-object-type`, `no-console`). Outside `ui/` the
931+
// population this config lints is byte-for-byte the pre-import one, so its
932+
// verdict there does not move. Delete the entry in the PR that adopts this
933+
// config across `ui/` — never by widening a per-object `ignores` instead.
934+
// ───────────────────────────────────────────────────────────────────────────
935+
const LINTED_BY_OWN_CONFIG = Object.freeze([
936+
'ui/**',
937+
]);
938+
915939
export default [
916940
// ───────────────────────────────────────────────────────────────────────────
917941
// GLOBAL ignore. This is the ONLY object in this array with no `files` key,
@@ -965,7 +989,7 @@ export default [
965989
// either of them becoming the next `.next`. See #12334 / #12304.
966990
// ───────────────────────────────────────────────────────────────────────────
967991
{
968-
ignores: [...NEVER_LINTED],
992+
ignores: [...NEVER_LINTED, ...LINTED_BY_OWN_CONFIG],
969993
},
970994
{
971995
files: ['**/*.{ts,tsx,mts,cts,js,jsx,mjs,cjs}'],

‎packages/client/src/envelope-caller-census.test.ts‎

Lines changed: 39 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -105,8 +105,13 @@
105105
* ships to consumers no repo sweep can see; the changeset carries their
106106
* migration. This census bounds the first-party population only.
107107
* - **`objectui` is a RECORDED constant, not a live scan.** A test in this
108-
* repo cannot read that checkout. `OBJECTUI_CENSUS` carries the revision it
109-
* was measured at and the command that reproduces it.
108+
* repo could not read that checkout. Since objectui was imported under `ui/`
109+
* (objectstack#25803) it can, but the walk still skips the top-level `ui/`:
110+
* reading it would make `ui/**` an input of `@objectstack/client#test`,
111+
* and ci.yml's `crosspkg` filter does not cover that path yet
112+
* (`check-ci-filter-parity`). `OBJECTUI_CENSUS` is re-recorded at the
113+
* imported revision, and the live scan returns with the card that adds the
114+
* path to that filter.
110115
* - **The ratchet in section 3 is only as live as turbo's cache — for the
111116
* trees still undeclared.** This suite walks the whole workspace, while
112117
* `@objectstack/client#test` declares as inputs its own package plus the
@@ -199,9 +204,18 @@ interface Site {
199204
index: number;
200205
}
201206

207+
/**
208+
* [#25803] Top-level directories of THIS repository the walk does not enter:
209+
* the imported objectui tree, recorded as `OBJECTUI_CENSUS` instead (see the
210+
* header). Matched at the repository root only, never as a name at any depth
211+
* the way `SKIP_DIRS` is: `packages/spec/src/ui` is this census's own ground.
212+
*/
213+
const SKIP_TOP_LEVEL = new Set(['ui']);
214+
202215
function walk(dir: string, acc: string[] = []): string[] {
203216
for (const entry of readdirSync(dir)) {
204217
if (SKIP_DIRS.has(entry)) continue;
218+
if (dir === REPO_ROOT && SKIP_TOP_LEVEL.has(entry)) continue;
205219
const p = join(dir, entry);
206220
let st;
207221
try { st = statSync(p); } catch { continue; }
@@ -566,41 +580,31 @@ const LEDGER: readonly LedgerRow[] = [
566580
];
567581

568582
/**
569-
* `objectui` @ b84dc1854922c266850d6e573daf3ad59cbd0623 (origin/main,
570-
* 2026-08-31). Recorded, not live-scanned — a test here cannot read that
571-
* checkout. Reproduce with `OBJECTUI_CENSUS_COMMAND`.
572-
*
573-
* ⭐ The single production call site in either reachable repo, and it SURVIVES
574-
* the convergence untouched. `aggregate()` feeds the resolved value through a
575-
* tolerant chain that accepts both spellings:
576-
*
577-
* const rawRows = Array.isArray(data) ? data
578-
* : data?.rows && Array.isArray(data.rows) ? data.rows // post-#13079: this branch
579-
* : data?.data && Array.isArray(data.data) ? data.data
580-
* : data?.data?.rows && Array.isArray(data.data.rows) ? data.data.rows // pre-#13079: this branch
581-
* : data?.results && Array.isArray(data.results) ? data.results
582-
* : [];
583+
* `objectui` @ 9e1fe032866965c7cb0460cff2b6af09350236b7, the revision imported
584+
* under `ui/` (objectstack#25803; first recorded at b84dc18549, 2026-08-31).
585+
* Recorded, not live-scanned: the walk skips `ui/` (see the header). Reproduce
586+
* with `OBJECTUI_CENSUS_COMMAND`; the file count is the walk's own rule applied
587+
* to the tracked `ui/` tree.
583588
*
584-
* Before #13079 branch 4 matched; after it branch 2 matches. ⚠️ It survives
585-
* by DEFENSIVE CODING, not by a designed migration path — the adapter was
586-
* written tolerant because the producer was ambiguous, which is the shape a
587-
* contract-first fix removes rather than relies on. Ruling item 3: objectui#7028
588-
* tightens the chain to the single post-unwrap spelling, time-gated behind
589-
* this convergence landing and objectui taking the SDK version — the chain is
590-
* load-bearing until then, so it is deliberately NOT part of this change.
589+
* ⭐ The single production call site in either tree. `aggregate()` used to feed
590+
* the resolved value through a tolerant chain that accepted both the
591+
* pre-#13079 envelope and the post-unwrap `AnalyticsResult`. objectui#7028, the
592+
* tightening this record named as owed, has landed: the adapter reads `rows`
593+
* off the post-unwrap result and throws `AnalyticsResultShapeError` on any
594+
* other shape, so it is payload-dependent and reads no envelope.
591595
*/
592596
const OBJECTUI_CENSUS = {
593-
revision: 'b84dc1854922c266850d6e573daf3ad59cbd0623',
594-
filesScanned: 4024,
597+
revision: '9e1fe032866965c7cb0460cff2b6af09350236b7',
598+
filesScanned: 7954,
595599
sdkCallSites: 1,
596600
productionCallSites: 1,
597601
wouldBreak: 0,
598-
site: 'packages/data-objectstack/src/index.ts:4846 (analytics.query, via this.client)',
602+
site: 'ui/packages/data-objectstack/src/index.ts:6625 (analytics.query, via this.client)',
599603
tighteningOwner: 'objectui#7028',
600604
} as const;
601605

602606
const OBJECTUI_CENSUS_COMMAND =
603-
"git -C <objectui> grep -nE 'analytics\\s*\\.\\s*(query|meta|explain)\\s*\\(|automation\\s*\\.\\s*trigger\\s*\\(' origin/main -- '*.ts' '*.tsx' '*.js'";
607+
"git grep -nE 'analytics\\s*\\.\\s*(query|meta|explain)\\s*\\(|automation\\s*\\.\\s*trigger\\s*\\(' HEAD -- 'ui/*.ts' 'ui/*.tsx' 'ui/*.js'";
604608

605609
/**
606610
* ⛔ `objectstack-ai/cloud` is NOT MEASURED — see the header. Post-#13079
@@ -819,6 +823,9 @@ describe('#13079 §3 — every call site is classified', () => {
819823
// The three above are LEDGER sums and cannot move on a census reading;
820824
// this one is census-derived, so it carries the note. [#13874]
821825
expect(sdkSites.length, literalNote()).toBe(28);
826+
// [#25803] 28 -> 29 first-party SDK sites since the objectui import: the
827+
// walk's 28 plus the recorded objectui one, which it does not enter.
828+
expect(sdkSites.length + OBJECTUI_CENSUS.sdkCallSites, literalNote()).toBe(29);
822829
});
823830
});
824831

@@ -859,7 +866,11 @@ describe('#13079 §5 — what was NOT measured', () => {
859866
expect(OBJECTUI_CENSUS.wouldBreak).toBe(0);
860867
expect(OBJECTUI_CENSUS.revision).toMatch(/^[0-9a-f]{40}$/);
861868
expect(OBJECTUI_CENSUS.tighteningOwner).toBe('objectui#7028');
862-
expect(OBJECTUI_CENSUS_COMMAND).toContain('origin/main');
869+
expect(OBJECTUI_CENSUS.site.startsWith('ui/')).toBe(true);
870+
expect(OBJECTUI_CENSUS_COMMAND).toContain("'ui/*.ts'");
871+
// [#25803] And the walk really does not enter it, so the record is the
872+
// only reading of that tree here.
873+
expect(CENSUS.sites.some((s) => s.file.startsWith('ui/'))).toBe(false);
863874
});
864875

865876
it('⛔ cloud is NOT MEASURED, ruled so, and must not be read as clean', () => {
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
diff --git a/dist/index.js b/dist/index.js
2+
index e8906db3b1e87bfc568b75d72b7ed3d8d1d3c09a..7a05e0e4cffba466301ba1fb67067c0861af0a4d 100644
3+
--- a/dist/index.js
4+
+++ b/dist/index.js
5+
@@ -110,6 +110,10 @@ var FocusScope = /* @__PURE__ */ React.forwardRef(
6+
}, [trapped, container, focusScope.paused]);
7+
React.useEffect(() => {
8+
if (container) {
9+
+ if (focusScope.__pendingStackRemove !== void 0) {
10+
+ clearTimeout(focusScope.__pendingStackRemove);
11+
+ focusScope.__pendingStackRemove = void 0;
12+
+ }
13+
focusScopesStack.add(focusScope);
14+
const previouslyFocusedElement = document.activeElement;
15+
const hasFocusedCandidate = container.contains(previouslyFocusedElement);
16+
@@ -126,7 +130,8 @@ var FocusScope = /* @__PURE__ */ React.forwardRef(
17+
}
18+
return () => {
19+
container.removeEventListener(AUTOFOCUS_ON_MOUNT, onMountAutoFocus);
20+
- setTimeout(() => {
21+
+ focusScope.__pendingStackRemove = setTimeout(() => {
22+
+ focusScope.__pendingStackRemove = void 0;
23+
const unmountEvent = new CustomEvent(AUTOFOCUS_ON_UNMOUNT, EVENT_OPTIONS);
24+
container.addEventListener(AUTOFOCUS_ON_UNMOUNT, onUnmountAutoFocus);
25+
container.dispatchEvent(unmountEvent);
26+
diff --git a/dist/index.mjs b/dist/index.mjs
27+
index 030ff1a4d2383c9a0a1ba850aa4591890f028784..3dd39a90db9886bc495014a6e4136773e3548448 100644
28+
--- a/dist/index.mjs
29+
+++ b/dist/index.mjs
30+
@@ -75,6 +75,10 @@ var FocusScope = /* @__PURE__ */ React.forwardRef(
31+
}, [trapped, container, focusScope.paused]);
32+
React.useEffect(() => {
33+
if (container) {
34+
+ if (focusScope.__pendingStackRemove !== void 0) {
35+
+ clearTimeout(focusScope.__pendingStackRemove);
36+
+ focusScope.__pendingStackRemove = void 0;
37+
+ }
38+
focusScopesStack.add(focusScope);
39+
const previouslyFocusedElement = document.activeElement;
40+
const hasFocusedCandidate = container.contains(previouslyFocusedElement);
41+
@@ -91,7 +95,8 @@ var FocusScope = /* @__PURE__ */ React.forwardRef(
42+
}
43+
return () => {
44+
container.removeEventListener(AUTOFOCUS_ON_MOUNT, onMountAutoFocus);
45+
- setTimeout(() => {
46+
+ focusScope.__pendingStackRemove = setTimeout(() => {
47+
+ focusScope.__pendingStackRemove = void 0;
48+
const unmountEvent = new CustomEvent(AUTOFOCUS_ON_UNMOUNT, EVENT_OPTIONS);
49+
container.addEventListener(AUTOFOCUS_ON_UNMOUNT, onUnmountAutoFocus);
50+
container.dispatchEvent(unmountEvent);

0 commit comments

Comments
 (0)