Skip to content

Commit a47d369

Browse files
committed
Merge remote-tracking branch 'origin/main' into claude/issue-26307-bulk-confirm-locale
# Conflicts: # scripts/sdui-manifest.sources/plugin-grid.json
2 parents 64e9dbc + 222755e commit a47d369

155 files changed

Lines changed: 5091 additions & 1543 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.
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
---
2+
'@object-ui/react': patch
3+
'@object-ui/core': patch
4+
'@object-ui/data-objectstack': patch
5+
'@object-ui/app-shell': patch
6+
'@object-ui/plugin-designer': patch
7+
---
8+
9+
refactor(react): `AdapterCtx` / `useAdapter` are typed by `DataSource`, and `@object-ui/react` no longer depends on `@object-ui/data-objectstack`
10+
11+
Clause-②: yes (narrowing)
12+
Metadata-protocol: none
13+
14+
<!-- adr-0087: not-required (runtime-interface-only ui/packages/react/src/context/AppShellContext.tsx#ContextAdapter) No metadata moves: the narrowed type is the one `AdapterCtx` and `useAdapter()` carry, a runtime TypeScript type of a React context, with no Zod schema, spec declaration or stored row behind it, so there is nothing for objectstack migrate meta to rewrite; the compiler names the affected call site. -->
15+
16+
The runtime no longer depends on one adapter, so a bring-your-own backend installs `@object-ui/react` without the ObjectStack adapter, and learns at the type level which `DataSource` members it lacks.
17+
18+
- **`@object-ui/react`**: `AdapterCtx` and `useAdapter()` are typed by the protocol-level `DataSource` from `@object-ui/types`, not by `ObjectStackAdapter`. A new exported extension point, `AdapterContextRegister`, lets a host declare the concrete adapter its provider installs; `useAdapter()` then returns that adapter and an `AdapterCtx.Provider` value is checked against it. `ContextAdapter` names the resolved type. `@object-ui/data-objectstack` moves from `dependencies` to `devDependencies` (one boundary test still reads it).
19+
- **`@object-ui/core`**: exports `API_ACCESS_DENIED_CODES` and `isApiAccessDeniedError`, the `enable`-block API denial predicate, moved here unchanged so `@object-ui/react` can classify the denial without the adapter.
20+
- **`@object-ui/data-objectstack`**: re-exports both names from `@object-ui/core`; its published face is unchanged.
21+
- **`@object-ui/app-shell`**: declares `ObjectStackAdapter` on `AdapterContextRegister`, and the declaration ships in its published types, so a host that mounts `AdapterProvider` keeps today's `useAdapter()` type.
22+
- **`@object-ui/plugin-designer`**: declares the same adapter for its own pages, which read the ObjectStack client.
23+
24+
**BREAKING** (a type narrowing) for a TypeScript reader of `useAdapter()` / `AdapterCtx` from `@object-ui/react` that reads an `ObjectStackAdapter` member `DataSource` does not declare (`getClient`, `probeAppAccess`, `probeMetaItemAccess`, `getConnectionState`, `onConnectionStateChange`, `clearCache`, `queryDataset`, …) and does not import `@object-ui/app-shell`. Runtime behaviour is unchanged: the context holds the same adapter object as before.
25+
26+
- **Before.** `useAdapter()` returned `ObjectStackAdapter | null` whatever the host installed.
27+
- **Now.** It returns `DataSource | null` unless the host declares its adapter, and reading `getClient` off it is `error TS2339`.
28+
- **Fix.** Either declare the adapter your provider installs, once, in your own package:
29+
30+
```ts
31+
import type { ObjectStackAdapter } from '@object-ui/data-objectstack';
32+
33+
declare module '@object-ui/react' {
34+
interface AdapterContextRegister {
35+
adapter: ObjectStackAdapter;
36+
}
37+
}
38+
```
39+
40+
or narrow at the call site that needs the ObjectStack-only member, and keep reading `DataSource` members everywhere else.

‎.changeset/25959-activity-fold.md‎

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
---
2+
'@object-ui/types': patch
3+
'@object-ui/plugin-detail': patch
4+
'@object-ui/i18n': patch
5+
---
6+
7+
feat(plugin-detail): the record activity timeline folds consecutive updates by one actor into one row with a count
8+
9+
Clause-②: yes
10+
Metadata-protocol: none
11+
12+
- **Before.** Every `sys_activity` update was its own feed row. When a flow, a rollup or a seed updated a record repeatedly, the system's rows buried the people's comments: a demo campaign showed thirteen consecutive "System · Updated Campaign …" rows in its discussion feed.
13+
- **Now.** `RecordActivityTimeline` folds a run of adjacent `updated` entries by the same actor (`actor_id`, `null` for the system, and the same display name), each no more than 10 minutes after the one before it, into one row. The row shows the actor, the time of the latest entry, the entry count and the union of the changed fields, by their field labels where the page has the object schema and by machine name otherwise (`Updated Number Sent, Leads, Responses · 13 updates`). A toggle (`aria-expanded`) expands it to every entry.
14+
- **Rendering only.** No entry is dropped or merged in the data, and the panel heading still counts every entry. Create, delete, assign, share, comment, completed and milestone rows never fold, and any of them between two updates ends the run. An entry without a summary still folds and still counts. Field names come from `sys_activity.changed_fields`, never from the recorded values.
15+
- **`FeedItem` (`@object-ui/types`).** Four new optional members, set by `activityRowToFeedItem`: `activityType` (the row's own `sys_activity.type`), `activityKind` (`metadata.kind`, e.g. `'milestone'`), `actorId` and `changedFields`. A host item without them renders as before and never folds.
16+
- **Strings.** `detail.foldedUpdates`, `detail.foldedUpdateCount`, `detail.foldedMoreFields`, `detail.expandFolded` and `detail.collapseFolded`, in all ten packs.
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
---
2+
'@object-ui/plugin-grid': patch
3+
---
4+
5+
refactor(plugin-grid): `ObjectGrid`'s host-facing props types and its footer-summary reporter move into sibling modules
6+
7+
Clause-②: no
8+
Metadata-protocol: none
9+
10+
`ObjectGridColumnState`, `ObjectGridFooterSummary` and `ObjectGridExternalPaginationProps` move verbatim out of `ObjectGrid.tsx` into `objectGridProps.ts`, and the renderless `FooterSummaryReport` moves into `objectGridFooterSummaryReport.tsx`. No logic, name or rendered output changes. `ObjectGrid.tsx` re-exports the three types, so every name it exported before is still importable from the same path, and the package entry is unchanged.
11+
12+
Correction to the slice 1 note: the record-source ladder (`getDataConfig`) stayed in `ObjectGrid.tsx`; it moved in neither slice.
13+
14+
What to do: nothing.
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
---
2+
'@objectstack/plugin-audit': patch
3+
---
4+
5+
fix(plugin-audit): a member removes their own comment reaction through an own-row unreact door, with no object delete grant
6+
7+
A member whose grants are the every-member ones could add a comment reaction and never take it back. The console removes a reaction through the generic data door's `DELETE /api/v1/data/sys_comment_reaction/:id`, which needs an object-level `delete` grant, and no every-member set may carry one: ADR-0090 D5 keeps `delete` off the `everyone` anchor (`security-anchor-high-privilege`), and `member_default` carries none. Measured as a plain member: add a reaction → `201`, delete it → `403`.
8+
9+
Clause-②: yes (widening)
10+
Metadata-protocol: none
11+
12+
`AuditPlugin` now mounts `DELETE /api/v1/comments/reactions/:id` on the kernel's HTTP server. It removes the caller's own `sys_comment_reaction` row and nothing else:
13+
14+
- The row is read as the caller, so every read gate decides. A reaction on a comment the caller cannot read answers `404 RECORD_NOT_FOUND`, the same answer as an id that names no row, so the door says nothing about a reaction the caller cannot see.
15+
- The row's reactor (`created_by`, the column the platform's own-record floor reads) must be the caller. Anyone else's reaction is refused with `403 PERMISSION_DENIED` and stays.
16+
- Only then is the row removed, under a system context attributed to the caller and kept in the caller's organization. Success answers `200` with `{ success: true, data: { id, deleted: true } }`.
17+
- Before any of that the door answers what every door answers: `401 UNAUTHENTICATED` for a request that names nobody, a held sign-in's own `403`, the permission store's `503` when it cannot be read, and `400 VALIDATION_ERROR` for any query parameter.
18+
19+
The grant model does not move. The generic data-door delete answers exactly what it answered before (`403` for a member with no delete grant), no permission set gains `delete`, and `member_default` and the `security-anchor-high-privilege` rule are unchanged. The console's reaction chip moves to the new route in a separate change; until then the route is reachable but nothing in the console calls it.
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
---
2+
'@object-ui/app-shell': patch
3+
---
4+
5+
fix(app-shell): the Delegated Admin Scope block lists the companion grants a delegate needs, and grants the missing ones in one click
6+
7+
Clause-②: no
8+
Metadata-protocol: none
9+
10+
A permission set's admin scope says what its holder may administer; it does not let the holder in. A delegate also needs the `setup.access` capability to enter Setup, read on `sys_user`, `sys_business_unit` and `sys_business_unit_member` so the lookups can pick people and units, and create, read, edit and delete on `sys_user_position` for the assignment rows. The scope block in the permission set editor now lists these five once the scope names a business unit, and marks each one as granted or not by the set being edited.
11+
12+
"Grant the missing ones" adds only what is missing to the same set, in one edit that the set's normal Save persists: `setup.access` joins `systemPermissions`, and each object row gains only the flags it lacks. No grant the set already carries is removed or narrowed, and no new key is written. Under a package (the Studio Access pillar) the list is shown without the action, with a note that these grants belong on an environment permission set in Setup: the package editor saves only the package's own objects.
13+
14+
What to do: nothing. A set's stored shape is unchanged.
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
---
2+
'@objectstack/cli': patch
3+
---
4+
5+
docs(cli): the README says the three code subpaths are ESM
6+
7+
Clause-②: no
8+
Metadata-protocol: none
9+
10+
`@objectstack/cli`'s README now states that `.`, `./console` and `./hook-body` are ES modules: load them with `import`, and from CommonJS with `import()`, or with `require()` only on a Node that loads ES modules synchronously (22.12 and later). No export map or code changes.
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
---
2+
'@objectstack/spec': patch
3+
'@objectstack/runtime': patch
4+
'@objectstack/service-settings': patch
5+
---
6+
7+
fix(spec,service-settings): the AI 501 remedy and the AI / knowledge settings hints name the hosted product "ObjectOS Cloud"
8+
9+
Clause-②: no
10+
Metadata-protocol: none
11+
12+
ObjectOS is the product, in Community, Cloud and Enterprise editions; ObjectStack is the protocol, SDK, CLI and framework. Three runtime sentences still named the hosted product by its retired name:
13+
14+
- **`@objectstack/spec`.** `REMEDY_DETAIL['ai']` — the message every `/ai/*` route's 501 body carries and `/discovery` reports as `services.ai.message` on an open-edition boot — now reads "Provided by @objectstack/service-ai in ObjectOS Cloud/Enterprise — no implementation ships in the open framework".
15+
- **`@objectstack/service-settings`.** The AI live-call hint ("realized only by an ObjectOS Cloud tier") and the knowledge `turso` adapter's "Test connection" hint ("built in the ObjectOS Cloud monorepo") on Settings → AI & Embedder.
16+
- **`@objectstack/runtime`.** A doc comment on the kernel-resolver seam; no behaviour change.
17+
18+
What to do: a client that string-matches the old 501 / discovery message updates the product name in its expected text. Nothing else changes: package names, `OS_CLOUD_*` env vars, `~/.objectstack/cloud.json` and the `os cloud …` commands keep their names.
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
---
2+
'@object-ui/components': patch
3+
---
4+
5+
fix(components)!: the filter builder's row takes the spec rule's value, so the authored group a `filter-builder` node carries reaches the builder as it is
6+
7+
Clause-②: yes (narrowing)
8+
Metadata-protocol: none
9+
10+
<!-- adr-0087: not-required (runtime-interface-only ui/packages/components/src/custom/filter-builder.tsx#FilterBuilderRow) A published runtime TypeScript interface of a React component with no metadata surface: no Zod schema, no spec declaration and no stored representation, so `objectstack migrate meta` has nothing to rewrite; the affected party is a TypeScript consumer, reached by its own compiler. -->
11+
12+
**BREAKING** type-surface change, compile-time only. `FilterBuilderRow` is exported from `@object-ui/components`. What the builder accepts widens, and what a reader of `row.value` may assume narrows: the member is now optional and admits `null`. Nothing changes at runtime for a reader: the builder already passed both through.
13+
14+
`FilterBuilderRow.value` is now `ViewFilterRule['value']` from `@objectstack/spec/ui`, taken by reference, plus the builder's own list member `(string | number | boolean)[]`. Before, it was the builder's own `string | number | boolean | (string | number | boolean)[]`, and it was required. Measured against the spec's rule, that refused two shapes an authored row takes:
15+
16+
- **Value omitted.** The spec's value is optional, because a value-less operator (`is_null`, `is_empty`, …) carries none. The builder's was required, so the authored `FilterGroup` from `@object-ui/types` was not assignable to the builder's group. The member is optional now.
17+
- **Value `null`.** The spec admits it; the builder's member did not. It does now.
18+
19+
The operator vocabulary needed no change: every spec operator id is a builder id.
20+
21+
Both cases also fix a field switch. Switching the field of a row with no value used to convert the absent value: on a number column it threw on `.trim()`, and on a text column it came back as the string `"undefined"`. A row with no value, or a `null` one, now keeps it through a field switch.
22+
23+
The `filter-builder` renderer now hands the authored `value` and `fields` to the builder with no cast. It declares `value` on its props, because a host that binds the node forwards the group it holds.
24+
25+
**Migration.** Code that reads `row.value` from a `FilterBuilderRow` (a host's `onChange` handler, for instance) handles `undefined` and `null` before using the value, or narrows first with an unset check (`value === undefined || value === null || value === ''`). Code that only writes rows needs no change.

‎.github/workflows/check-links.yml‎

Lines changed: 24 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -62,30 +62,39 @@ jobs:
6262
# green at all: 1286 root-relative links fail as "Cannot resolve
6363
# root-relative link ... provide a root dir".
6464
#
65-
# ⛔ Do NOT add `docs/adr/**/*.md` here. It looks like the one-line fix
66-
# for #6592 and it is not: measured on the pinned lychee 0.24.2, that
67-
# glob reports 8 broken links today, every one a pre-existing ADR →
68-
# source-tree link whose target moved out of this repo. This job would
69-
# be red on every PR from the moment it merged, which is how an
70-
# advisory lane becomes a lane nobody reads (#6028 landed it
71-
# advisory-first specifically to earn a green streak). `docs/adr/` is
72-
# checked by the `Check ADR cross-links` step below instead, which can
73-
# freeze those 8 on a shrink-only baseline and fail on a NEW one --
74-
# something neither `exclude` nor `.lycheeignore` can express, because
75-
# neither ever tells you an entry stopped being needed.
76-
# `ARCHITECTURE.md` joined this glob in #6867: unlike `docs/adr/**`, it
77-
# carried no pre-existing rot once its 10 dead links + 2 stale path
78-
# references were fixed in the same PR, so -- unlike the ADR directory
79-
# above -- it needs no KNOWN_DEAD_TARGETS-style baseline to land clean.
65+
# ⛔ `docs/adr/` stays OUT of lychee's reach: `'docs/**/*.md'` below
66+
# would sweep it in, so `--exclude-path '^docs/adr/'` takes it back out
67+
# (a regex over the input path as globbed, measured to drop exactly that
68+
# directory on the pinned 0.24.2 while `lychee.toml`'s own `exclude_path`
69+
# entries keep applying -- the CLI flag adds to them, it does not replace
70+
# them). The decision registry has ONE link owner, the `Check ADR
71+
# cross-links` step below, which can freeze a pre-existing break on a
72+
# shrink-only baseline and fail on a NEW one -- something neither
73+
# `exclude` nor `.lycheeignore` can express, because neither ever tells
74+
# you an entry stopped being needed. Checking the same files twice, under
75+
# two rule sets, would put two verdicts on one link.
76+
# `ARCHITECTURE.md` joined this glob in #6867; `docs/**` and the root
77+
# history/governance pages (CHANGELOG, RELEASE_NOTES, CONTRIBUTING,
78+
# ROADMAP, LICENSING, CODE_OF_CONDUCT) joined in #26087, each landing
79+
# clean once its dead links were fixed in the same PR, so none needs a
80+
# KNOWN_DEAD_TARGETS-style baseline.
8081
LYCHEE_ARGS: >-
8182
--offline
8283
--root-dir ${{ github.workspace }}/content
8384
--fallback-extensions mdx,md
8485
--config lychee.toml
86+
--exclude-path '^docs/adr/'
8587
'content/**/*.md'
8688
'content/**/*.mdx'
8789
'README.md'
8890
'ARCHITECTURE.md'
91+
'docs/**/*.md'
92+
'CHANGELOG.md'
93+
'RELEASE_NOTES.md'
94+
'CONTRIBUTING.md'
95+
'ROADMAP.md'
96+
'LICENSING.md'
97+
'CODE_OF_CONDUCT.md'
8998
9099
# ⛔ Pin the lychee binary explicitly rather than inheriting the action's
91100
# default (#8238). Three comments in this file already reason about "the

0 commit comments

Comments
 (0)