Skip to content

Commit 697e25e

Browse files
committed
Merge remote-tracking branch 'origin/main' into claude/issue-24951-duplicate-message-labels
2 parents fbc46d6 + 93e6c1a commit 697e25e

73 files changed

Lines changed: 3459 additions & 346 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: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
---
2+
'@objectstack/spec': minor
3+
'@objectstack/lint': patch
4+
---
5+
6+
feat(spec)!: a list's `addRecord` block says what renders — `enabled` is required, `position` defaults to `top`, and `mode` / `formView` are retired
7+
8+
Clause-②: yes (narrowing)
9+
10+
<!-- adr-0087: registered add-record-mode-form-view-removed, add-record-enabled-defaulted, add-record-mode-form-view-retired, add-record-enabled-required -->
11+
12+
**BREAKING** (an authoring narrowing), shipped as `minor` under the launch-window convention for breaking changes, and in 18.x under the standing rule for breaking narrowings: an ADR-0087 conversion carries every body that parsed under 18.0, so no stored row or built artifact is refused.
13+
14+
`AddRecordConfigSchema` — the `addRecord` block on a list view, an object's or a view container's `listViews`, a list page's `interfaceConfig` and a page `list-view` node — declared defaults the renderer never applied (`enabled` `true`, `position` `bottom`, `mode` `inline`) and two keys no renderer read. The list's Add button is drawn only for an explicit `enabled: true`, an absent `position` renders at the top, and every Add opens the object's create form. The block now says exactly that:
15+
16+
- **`enabled` is REQUIRED, with no default.** `addRecord: {}` is refused at parse (`os validate`, `os build`, the save doors) with a prescription naming the key, instead of drawing nothing.
17+
- **`position` defaults to `top`**, where the renderer has always placed an absent position. Values stay `top` / `bottom` / `both`. No screen moves.
18+
- **`mode` and `formView` are `retiredKey()` tombstones** (ADR-0049 enforce-or-remove): authoring either is a `tsc` error and a parse error carrying the prescription. The create form is chosen by convention instead: the create entry opens `formViews.create` when the object's view container declares it, else `form` (or `formViews.default`). How that form opens stays the renderer's.
19+
20+
### FROM → TO
21+
22+
| before | after |
23+
| --- | --- |
24+
| `addRecord: {}` (parsed as `enabled: true`, drew nothing) | refused: write `addRecord: { enabled: true }` |
25+
| `addRecord: { position: 'bottom' }` | refused: write `addRecord: { enabled: true, position: 'bottom' }` |
26+
| `addRecord: { enabled: true }` (parsed `position: 'bottom'`) | parses as `position: 'top'`, where it always rendered |
27+
| `addRecord: { enabled: true, mode: 'form' }` (any `mode`) | `addRecord: { enabled: true }` |
28+
| `addRecord: { enabled: true, formView: 'create' }` | `addRecord: { enabled: true }`, with the form declared as `formViews.create` on the object's view container |
29+
30+
**The one-line fix:** delete `mode` and `formView`, and write `enabled: true` (or `enabled: false`) on every `addRecord` block. `os migrate meta --from 17` lists the edits for existing metadata sources; `--write` applies the ones it can prove, and you apply the rest by hand.
31+
32+
### The ADR-0087 disposition
33+
34+
- D2 `add-record-mode-form-view-removed` strips `mode` and `formView` from every `addRecord` block it reaches (`stack.views[]` in all three persisted spellings, `objects[].listViews.*`, `pages[].interfaceConfig` and every page `list-view` node), with a notice per key. Lossless in pixels: no renderer read either key.
35+
- D2 `add-record-enabled-defaulted` writes `enabled: true`, the default the 18.0 spec declared, onto a block that omits `enabled`, with a notice. Measured before choosing it: no producer in this repository (the showcase's two blocks, the lint fixture) or in objectui's Studio design surface writes `addRecord` without `enabled`, so the conversion carries the narrowing with a zero measured population. **One visible consequence, stated:** a stored block that omitted `enabled` drew no Add button, because the renderer read the raw body; converted, it draws the button its declaration always promised. To keep a list without one, write `enabled: false` or drop the block. An explicit `enabled` value is never touched.
36+
- Both are retired from the load path: a live author is refused at parse and taught; stored rows, built artifacts inside their declared-floor window and `os migrate meta --from 17` replay them.
37+
- `RETIRED_KEYS_BY_MAJOR[18]` gains `ui/AddRecordConfig:mode` and `ui/AddRecordConfig:formView`; the D3 entries are `add-record-mode-form-view-retired` and `add-record-enabled-required`; the default moves (`enabled` `true` → none, `position` `bottom` → `top`) are declared in `DEFAULT_CHANGES_BY_MAJOR[18]`.
38+
39+
### Also in this change
40+
41+
- The Studio view form and page form offer exactly `enabled` and `position` for `addRecord`.
42+
- `@objectstack/lint`: `os explain list-view-field-unknown` no longer names `addRecord.formView` as a position another rule checks.
43+
- The showcase's two `addRecord` blocks and the docs (`ui/create-vs-edit-form`, `ui/views`) drop `mode` / `formView` and state the create-form convention.
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
---
2+
'@objectstack/plugin-auth': patch
3+
---
4+
5+
fix(plugin-auth): the Console's OAuth Application Type reaches the provider as `application_type`, so a Native client can register a loopback redirect URI (#24773)
6+
7+
Clause-②: no — the declared type now takes effect under the OAuth provider's own redirect-URI rule, an authentication behaviour the maintainer ruled on; nothing published widens.
8+
9+
`POST /api/v1/auth/sys-oauth-application/register` (the Setup → OAuth Applications form) forwarded the form's Application Type as `type`. The provider's `/oauth2/create-client` body declares `application_type` (`web` | `native`) and no `type`, so the value was dropped and every client was judged and stored as `web`. A `native` client therefore could not register a loopback redirect URI such as `http://localhost:4001/callback`, and its `sys_oauth_application.type` read `web`.
10+
11+
- **Now.** `web` and `native` are forwarded as `application_type`, and the provider's own rule decides. A `native` client may use http only on exactly `localhost`, `127.0.0.1` or `[::1]` (any port), and is stored as `native`. A `web` client still needs an https URI on a non-loopback host.
12+
- **Unchanged, with a warning.** Any other type, including the form's `user-agent-based` and `public`, has no `application_type` in the provider. It keeps today's outcome: registered and judged as `web`, with a client secret. The server now logs one warning per such registration, naming the value. To use a loopback redirect URI, register the client as `native`.
13+
- **Unchanged.** No type at all is `web`. Refusals keep the codes and statuses they had: `400 OAUTH_REGISTER_FAILED`, with `error.details.upstreamError` `invalid_redirect_uri` when the redirect-URI rule refuses.
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
---
2+
'@objectstack/service-settings': patch
3+
---
4+
5+
feat(service-settings): a settings value that misses the format its `email`, `url`, `phone` or `color` type declares is now reported in the server log; the save is still accepted (#24882)
6+
7+
Clause-②: no (warning only: every save and every `OS_*` override accepted before is still accepted, with the same value stored or served, the same status and the same errors)
8+
9+
ADR-0007 declares a format for four specifier types: `email`, `url` and `phone` take "format validation", and `color` takes a hex string. Until now nothing judged that format. `PUT /api/settings/branding` stored a Logo URL of `not a url` and answered 200, and an `OS_*` override was served unchecked.
10+
11+
Both doors now judge the format through one shared check, and report a miss instead of refusing it:
12+
13+
- **Save.** When a save is accepted and a key it touches holds a value that misses its type's format, the service logs one `warn` line for that key. The line names the setting, the type, the expected format and the value's length.
14+
- **Environment override.** An in-force `OS_*` override whose value misses the format draws one `warn` line, at boot or on the first read. The line names the variable, the setting, the type and the expected format. The override stays in force, is served with source `env`, and still locks the key.
15+
- **The value is never printed** in either line, for any key.
16+
17+
The formats are the platform's existing grammars for the same words:
18+
19+
- `email`: an address with a dotted domain, the grammar the mail transport sends with.
20+
- `url`: `scheme://…`, a root-relative `/path`, a `data:` URI or a `blob:` URL. An http(s)-only grammar would flag a relative logo path that the branding reader serves.
21+
- `phone`: digits, spaces and `+ ( ) - .`, at least five characters.
22+
- `color`: `#rgb`, `#rgba`, `#rrggbb` or `#rrggbbaa`.
23+
24+
Empty values and non-string values are not judged. Surrounding whitespace is ignored, because the readers trim it before use.
25+
26+
**Unchanged.** Status codes, response bodies, stored values, the `FieldError`s a refused save carries, and which overrides are in force. Under the standing rule 「甲」, refusing these values waits for the next major version.
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
---
2+
'@objectstack/rest': patch
3+
---
4+
5+
fix(rest): a boolean cell in the list export is written in the caller's language (#24945)
6+
7+
Clause-②: no
8+
9+
`GET /api/v1/data/:object/export` (Display → Export as CSV / XLSX / JSON) wrote every boolean or toggle cell as `是` or `否`, whatever the caller read. The header row of the same file follows the caller's locale, so an English caller got "Converted" and "Do Not Call" over Chinese cells.
10+
11+
- **Now.** A boolean or toggle cell is written in the words the import template already uses for the caller's locale: `yes` / `no`, or `是` / `否` for a `zh*` locale. The locale is the one the header row is translated to: `Accept-Language`, then `?locale=`, then the deployment's default locale. The header and the cells always read one language. This applies to csv, xlsx and json.
12+
- **No locale at all.** When the request names no locale and the deployment declares no default, the cells read `yes` / `no`, the same words the import template writes then.
13+
- **Re-import.** A file exported in either language re-imports its boolean column: the importer reads `yes` / `no` and `是` / `否`.
14+
- **Unchanged.** No query parameter, header, column set or payload shape moves. Only the text of a boolean cell changes, and a Chinese caller's file is byte-for-byte what it was.
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
---
2+
'@objectstack/lint': minor
3+
---
4+
5+
feat(lint): a CEL slot inside a page component's `properties` that overruns the platform's parse bounds is reported as a warning at `os validate`, `os build`, `os lint` and the page publish door
6+
7+
Clause-②: no
8+
9+
A source over `DEFAULT_LIMITS` (`maxAstNodes` 256, `maxDepth`, `maxListElements`) does not parse anywhere on the platform. It faults on every evaluation, and the console fails open, so the element it was written to gate draws for everyone. The component node's own `visibleWhen` was already refused for this (`visibility-predicate-over-budget`). The CEL slots declared inside the props bag were judged by no rule. These include a `page:tabs` item `visibleWhen`, an `object-form` section or inline field `visibleWhen` / `readonlyWhen` / `requiredWhen`, `record:alert` `visible`, and the `visible` / `disabled` of the `action:*` blocks. A page carrying such a slot saved clean through every door.
10+
11+
The new rule `page-predicate-over-budget` (`validatePagePredicateBounds`) reads the slots off each component's `ComponentPropsMap` row: every position where the row's parse produces a `{ dialect: 'cel', source }` envelope. It judges each one with `validateExpression`'s bounds verdict and names the key and the bound. It does not echo the source. It is a **warning** in 18.x: the page still saves and builds, and the publish door carries the finding on its `advisories` channel. A future major release refuses it. A within-bounds source draws nothing.
12+
13+
Fix: shrink the expression. Collapse an `||` chain into one `in [...]` test, or precompute the heavy part into a formula or roll-up field and test that field.

‎content/docs/deployment/cli.mdx‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -701,7 +701,7 @@ os compile --json # JSON output for CI pipelines
701701
→ Normalizing stack definition...
702702
→ Lowering inline handlers...
703703
→ Validating protocol compliance...
704-
→ Running author-time rules (49)...
704+
→ Running author-time rules (50)...
705705
→ Checking that every required capability has a provider installable in this edition...
706706
→ Collecting package docs (ADR-0046)... 0 collected
707707
→ Writing artifact...

‎content/docs/deployment/environment-variables.mdx‎

Lines changed: 19 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -129,13 +129,28 @@ Auth settings precedence:
129129

130130
---
131131

132-
## Branding (single-tenant)
132+
## Branding
133+
134+
The console's product name, logo, favicon and colours are served by `RuntimeConfigPlugin` (`@objectstack/cloud-connection`) as the `branding` block of `GET /api/v1/runtime/config`, which the console reads on every page load. A host that constructs its own `RuntimeConfigPlugin` with an option of the same name (`productName`, `logoUrl`, …) outranks the variable. The first eight variables below are read **once, when that plugin is constructed at boot**: changing one takes a restart. A value saved in **Setup → Branding** is read per request instead (see the precedence below). Mail and auth are named separately, by `OS_APP_NAME`.
133135

134136
| Variable | Type | Default | Description |
135137
|:---|:---|:---|:---|
136-
| `OS_PRODUCT_NAME` | string | `ObjectStack` | Product name shown in the UI chrome and emails. |
137-
| `OS_PRODUCT_SHORT_NAME` | string | `OS` | Short name used in compact UI surfaces (favicons, mobile titles). |
138-
| `OS_APP_NAME` | string | — | The deployment's product name under `objectstack serve`: `{{appName}}` in every email template (auth emails included), the issuer an authenticator app lists a TOTP enrollment under, and the MCP OAuth resource name. Wins over `config.email.appName`, `config.email.defaultTemplateContext.appName` and `config.appName`; with none of them set the name is `ObjectStack`. An explicitly set `branding.workspace_name` setting outranks it in auth. |
138+
| `OS_PRODUCT_NAME` | string | `ObjectOS` | The console's product name (`branding.productName`): the browser-tab title (alone, or after the open app's label), the header's product mark, the sign-in pages, the loading screen and the installed-app (PWA) manifest's `name`. It does **not** name emails, the authenticator app entry or the MCP sign-in — `OS_APP_NAME` does — so an unconfigured deployment reads `ObjectOS` in the console and `ObjectStack` in its mail. Empty reads as unset. |
139+
| `OS_PRODUCT_SHORT_NAME` | string | the product name served | The PWA manifest's `short_name` (`branding.productShortName`). Unset, it is the product name actually served, which under `single` may be a saved Branding name. |
140+
| `OS_PRODUCT_STAGE` | enum | — (no `stage` key) | `preview` \| `beta` \| `ga`, matched exactly. Drives the console's top-bar chip (`branding.stage`): "Preview" for `preview`, "Beta" for `beta`, none for `ga`. Unset sends no `stage` key and the console keeps its own `preview` default, so the chip shows. Any other value (`GA`, `general-availability`) is refused with one `warn` at boot and treated as unset. |
141+
| `OS_LOGO_URL` | url | — | The product logo, absolute or relative (`branding.logoUrl`): the sign-in pages, the loading screen and the header's product mark. Unset, the console draws its built-in mark. |
142+
| `OS_FAVICON_URL` | url | — | The favicon, absolute or relative (`branding.faviconUrl`): the browser-tab icon and the PWA manifest's icon. While an app with its own `branding.favicon` is open, that icon replaces it. No Branding setting overrides this variable, on any posture. |
143+
| `OS_BRAND_COLOR` | string | — | The brand colour as hex, e.g. `#4F46E5` (`branding.brandColor`). The console uses it as the installed-app theme colour, through `OS_PWA_THEME_COLOR`'s default; each app's own `AppBranding` colours are separate. |
144+
| `OS_PWA_DESCRIPTION` | string | `<product name> — runtime console` | The PWA manifest's `description` (`branding.pwaDescription`). Unset, it is built from the product name actually served. |
145+
| `OS_PWA_THEME_COLOR` | string | the brand colour served, else `#4f46e5` | The PWA manifest's `theme_color` (`branding.pwaThemeColor`), as hex. Unset, it is the brand colour actually served, else `#4f46e5`. |
146+
| `OS_APP_NAME` | string | — | The deployment's product name under `objectstack serve` for mail and auth: `{{appName}}` in every email template (auth emails included), the issuer an authenticator app lists a TOTP enrollment under, and the MCP OAuth resource name. Wins over `config.email.appName`, `config.email.defaultTemplateContext.appName` and `config.appName`; with none of them set the name is `ObjectStack`. Under `single`, an explicitly set `branding.workspace_name` setting outranks it in auth (the authenticator issuer takes a change only when the auth instance is rebuilt, such as at a restart); under `group` / `isolated` nothing does. It never names the console — that is `OS_PRODUCT_NAME`. |
147+
148+
Branding precedence, by tenancy posture (`OS_TENANCY_POSTURE`):
149+
150+
- **`single`** — three fields read the `branding` settings namespace on every request, so a save in **Setup → Branding** shows on the next page load with no restart: `workspace_name` → `productName`, `logo_url` → `logoUrl`, `accent_color` → `brandColor`. Each takes the first source that is set: the setting (a value saved in Setup, or the settings layer's own `OS_BRANDING_WORKSPACE_NAME` / `OS_BRANDING_LOGO_URL` / `OS_BRANDING_ACCENT_COLOR`, which outranks a saved value and locks the field), then the host's `RuntimeConfigPlugin` option, then `OS_PRODUCT_NAME` / `OS_LOGO_URL` / `OS_BRAND_COLOR`, then the built-in default. The Branding page's own default for `workspace_name` (`ObjectStack`) is never served to the console. A set `workspace_name` also names sign-in emails, the authenticator app entry and the MCP sign-in, ahead of `OS_APP_NAME`.
151+
- **`group` / `isolated`** — the `branding` namespace is not offered: it is absent from `GET /api/settings`, `GET` and `PUT /api/settings/branding` answer `404 UNKNOWN_NAMESPACE`, and Setup lists no Branding entry. The `OS_BRANDING_*` variables are not read. The console's branding is the host option, then the variables in this table, then the default, and the auth product name is the `OS_APP_NAME` chain alone. A walled deployment that set `OS_BRANDING_WORKSPACE_NAME` moves the value to `OS_APP_NAME`.
152+
153+
On every posture `productShortName`, `pwaDescription` and `pwaThemeColor` follow the name and colour actually served unless set themselves, and `OS_PRODUCT_STAGE` and `OS_FAVICON_URL` have no setting above them.
139154

140155
---
141156

‎content/docs/deployment/validating-metadata.mdx‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -732,7 +732,7 @@ A clean run walks the registry and reports timing:
732732
Config: /path/to/support-desk/objectstack.config.ts
733733
Load time: 21ms
734734
→ Validating against ObjectStack Protocol...
735-
→ Running author-time rules (49)...
735+
→ Running author-time rules (50)...
736736
→ Checking that every required capability has a provider installable in this edition...
737737
→ Checking package docs (ADR-0046)...
738738

‎content/docs/getting-started/build-with-claude-code.mdx‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -305,7 +305,7 @@ visible: 'status != "resolved"'
305305
◆ Validate
306306
────────────────────────────────────────
307307
→ Validating against ObjectStack Protocol...
308-
→ Running author-time rules (49)...
308+
→ Running author-time rules (50)...
309309
310310
✗ Author-time rules failed (1 issue)
311311
• stack · action 'resolve_ticket' visible: bare reference `status` — a

0 commit comments

Comments
 (0)