Skip to content

Commit c3c1f10

Browse files
committed
Merge remote-tracking branch 'origin/main' into claude/project-thread-8un53u
2 parents 794bcd8 + 5787df4 commit c3c1f10

74 files changed

Lines changed: 5417 additions & 185 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: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
---
2+
'@objectstack/spec': patch
3+
'@objectstack/core': patch
4+
---
5+
6+
fix(spec): `TranslationItemSchema.name` no longer tells authors to name a translation item after its locale tag
7+
8+
Clause-②: no
9+
Metadata-protocol: none
10+
11+
The `name` describe said a translation item was "conventionally the locale code (`zh-CN`)", but the metadata save door's item-name grammar refuses `zh-CN` (uppercase and a hyphen), so an author or agent following the describe got a 400 `INVALID_REQUEST` on the first save. The describe now says a translation item is named in snake_case like every metadata item (e.g. `zh_cn`), with the BCP-47 tag in `locale`. The save door and the runtime sync's read-side name fallback for rows stored before `locale` became required are unchanged; the sync's skip warning now points authors at `locale` only.
12+
13+
`@objectstack/core`: the authored-translation sync's no-locale skip warning now tells authors to set `locale` to the item's BCP-47 tag (e.g. `zh-CN`), instead of suggesting they name the item after it.
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
---
2+
'@objectstack/formula': minor
3+
---
4+
5+
feat(formula)!: a template hole takes one formatter, and the `currency` formatter's argument is a literal ISO 4217 code — both refused at compile, never rendered as formatter syntax or in US dollars
6+
7+
Clause-②: no (narrowing) — the in-app template dialect's compile (and so `os validate` and the publish gate for every template slot) refuses a hole with a second formatter (`{{ x | default:0 | number:2 }}`) and a `{{ x | currency:ARG }}` hole whose `ARG` is not three capital letters, both of which it accepted before; `formatValue('currency', value, ARG)` now throws a `RangeError` for such an `ARG`: a path or another argument `Intl` refuses, and an empty one, used to fall back to US dollars, and a lowercase code (`eur`), which `Intl` formatted in that currency, is now refused too. No accept set is widened; the additions are exports only: `judgeHoleFormatter`, `mailHolePattern`, `parseMailHole`, `judgeMailHoleGrammar`, and the types `TemplateHoleGrammarRefusal`, `TemplateHoleGrammarRule`, `ParsedMailHole` and `MailHoleGrammarFinding`.
8+
Metadata-protocol: narrowed
9+
10+
<!-- adr-0087: not-required (no-migration-prescription) No key of any schema is added, removed, renamed or re-shaped and no export is removed: a chained formatter and a currency argument that never rendered as written are now refused where the template is compiled. The refusal names the hole and the rule it breaks, so there is nothing for a migration to rewrite. -->
11+
12+
**BREAKING** accept-set narrowing in the template dialect, and a new shared mail hole grammar.
13+
14+
- **Before, a chained hole.** `{{ record.n | default:0 | number:2 }}` compiled as `default` with the argument `0 | number:2`, and rendered that text.
15+
- **Before, a currency path.** `{{ record.amount | currency:currency_code }}` compiled; `Intl` refused the argument and the formatter caught that and used US dollars, so the amount printed in a currency nobody wrote.
16+
- **Now.** `templateEngine.compile` / `evaluate` answer a `parse` error naming the hole and the rule. `formatValue('currency', …)` throws a `RangeError` for an argument that is not a code, before it reads the value. A lowercase code (`currency:eur`), which formatted in euros before, is refused the same way. No argument still formats in US dollars.
17+
- **Shared grammar.** `judgeHoleFormatter` is the judgement both template dialects use. The mail template grammar is exported once, `mailHolePattern`, `parseMailHole` and `judgeMailHoleGrammar`, and is what `@objectstack/plugin-email`'s renderer, `judgeMailHtmlHoles` and `@objectstack/lint` read; `judgeMailHtmlHoles` no longer keeps a copy of it.
18+
- **Who sees it.** A template slot that chained formatters, or wrote a path or a lowercase code as the currency argument. No template shipped in this repository does.
19+
20+
**The remedy.** Keep one formatter per hole and decide anything more where the data is assembled. Write the currency code itself (`currency:EUR`), or format the amount where the data is assembled and pass the formatted text.
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
---
2+
'@objectstack/service-messaging': patch
3+
---
4+
5+
fix(service-messaging): the email and inbox channels grade a `TEMPLATE_INVALID` template failure permanent, like the other template failures
6+
7+
Clause-②: no
8+
Metadata-protocol: none
9+
10+
`@objectstack/plugin-email`'s `sendTemplate` and `renderTemplate` now fail with `TEMPLATE_INVALID` for a stored template whose hole the mail grammar refuses (a second formatter, or a `currency` argument that is not a literal ISO 4217 code). Retrying cannot succeed until the template is republished, so `classifyError` on both channels grades it `permanent`, as it already grades `TEMPLATE_NOT_FOUND`, `TEMPLATE_INACTIVE`, `MISSING_VARIABLES` and `TEMPLATE_UNSUPPORTED`: the delivery goes dead at once with the code on its row, instead of spending the whole retry schedule first. Every other failure keeps its grade.
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
---
2+
'@objectstack/plugin-email': minor
3+
---
4+
5+
feat(plugin-email)!: a mail template hole that chains a second formatter, or whose `currency` argument is not a literal ISO 4217 code, fails the render with `TEMPLATE_INVALID` instead of printing formatter syntax or US dollars
6+
7+
Clause-②: no (narrowing) — `sendTemplate` and `renderTemplate` refuse a stored template whose `subject`, `body_text` or `body_html` writes a hole with a second formatter, or `{{ x | currency:ARG }}` with an `ARG` that is not three capital letters, both of which rendered before. No accept set is widened, and the package adds no export.
8+
Metadata-protocol: narrowed
9+
10+
<!-- adr-0087: not-required (no-migration-prescription) No key of any schema is added, removed, renamed or re-shaped and no export changes: the renderer refuses a hole the authoring rule refuses, for a template that reached it without being judged. There is nothing for a migration to rewrite. -->
11+
12+
**BREAKING** a render that printed a wrong currency now fails.
13+
14+
- **Before, a chained hole.** `{{ actual_amount | default:0 | number:2 }}` rendered `0 | number:2` for an empty value and `71100` otherwise, in every face.
15+
- **Before, a currency path.** `{{ planned_amount | currency:currency_code }}` with `currency_code = 'eur'` rendered `US$1,234.50` (zh-CN), `$1,234.50` (en-US) and `1 234,50 $US` (fr-FR), in every face.
16+
- **Now.** The render throws `TEMPLATE_INVALID: email template NAME (locale=LOCALE): …`, naming the hole and the rule it breaks (one formatter per hole, or the literal-only currency code); `sendTemplate` sends nothing. The authoring doors refuse the same hole first (`email-template-hole-grammar`), so this is reached only by a template stored before that rule, until its next publish.
17+
- **The grammar.** The renderer parses holes with `@objectstack/formula`'s `parseMailHole`, the grammar the authoring rule and the placement judge read; it keeps no copy of its own.
18+
19+
**The remedy.** Republish the template with one formatter per hole and a literal currency code (`currency:EUR`), or format the amount in the caller and pass the formatted text as its own variable.
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
---
2+
'@objectstack/lint': minor
3+
---
4+
5+
feat(lint)!: a new gating authoring rule, `email-template-hole-grammar`, refuses an email template hole that chains a second formatter or whose `currency` argument is not a literal ISO 4217 code
6+
7+
Clause-②: no (narrowing) — `os validate`, `os build`, `os lint` and the metadata publish door (`422 INVALID_METADATA`) refuse an `email_template` whose `subject`, `bodyText` or `bodyHtml` writes a hole with a second formatter (`{{ x | default:0 | number:2 }}`) or `{{ x | currency:ARG }}` with an `ARG` that is not three capital letters, both of which they accepted before. No accept set is widened; the additions are exports only: `validateEmailTemplateHoleGrammar`, `EMAIL_TEMPLATE_HOLE_GRAMMAR`, the type `EmailTemplateHoleGrammarFinding`, and the rule id `email-template-hole-grammar`.
8+
Metadata-protocol: narrowed
9+
10+
<!-- adr-0087: not-required (no-migration-prescription) No key of any schema is added, removed, renamed or re-shaped: a hole that never rendered as written (the renderer printed formatter syntax, or the amount in US dollars) is now refused where it is authored. The refusal names the hole and the rule it breaks, so there is no key for a migration to rewrite and no tombstone. -->
11+
12+
**BREAKING** accept-set narrowing at every authoring door for `email_template`.
13+
14+
- **Before, a chained hole.** `{{ actual_amount | default:0 | number:2 }}` passed every gate. The second `|` was read as part of `default`'s argument, so the hole printed the text `0 | number:2` for an empty value and the bare `71100` otherwise; `{{ x | upper | lower }}` stayed on the page as written.
15+
- **Before, a currency path.** `{{ planned_amount | currency:currency_code }}` passed every gate. The argument is not read as a path, so the amount rendered in US dollars whatever the record's currency (`$1,234.50` for a record in euros).
16+
- **Now.** The rule refuses both at `error`, naming the hole, the face and the template. A hole takes one formatter; anything more is decided in the caller that supplies the data. The currency argument is a literal code in three capital letters (`currency:EUR`), never a path. A lowercase or empty argument is refused the same way. `currency` with no argument is unchanged (US dollars).
17+
- **Which holes.** The grammar is `@objectstack/formula`'s `judgeMailHoleGrammar`, the one the mail renderer parses with; all three faces are judged.
18+
- **Who sees it.** A template that chained formatters, or wrote a path or a lowercase code as the currency argument. No template shipped in this repository does.
19+
20+
**The remedy.** Keep one formatter per hole and pass any further decided value (a default, a second format) as its own variable. Write the currency code itself (`currency:EUR`). To show each record's own currency, format the amount in the caller that supplies the data and pass the formatted text as its own variable.

‎.changeset/26670-seed-file-pool.md‎

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
---
2+
'@objectstack/metadata-protocol': patch
3+
---
4+
5+
fix(metadata-protocol): a seed that stores many `seedFile` values no longer starves sign-ins and other requests while it runs
6+
7+
Clause-②: no
8+
Metadata-protocol: none
9+
10+
A seed that stores many `seedFile(path)` values no longer starves the requests that arrive while it runs. On a synchronous driver (better-sqlite3, the wasm fallback) every step of a seed file's work — the `sys_file` row, the bytes, the claim that commits it — resolves as a microtask, so the claims the seed loader settled after each 200-row insert batch ran as one unbroken chain and a sign-in during a background boot seed waited seconds unread behind it. The connection pool was never the bottleneck; the event loop was. The loader now hands the event loop one turn after each row's files are stored and after each row's claim, and writes a dataset that carries seed files in insert batches of 20 rows instead of 200. Datasets without seed files are unchanged.
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
---
2+
'@object-ui/core': patch
3+
---
4+
5+
fix(core): an `api` action's target is interpolated before a registered `api` handler sends it
6+
7+
Clause-②: no
8+
Metadata-protocol: none
9+
10+
An `api` action whose `target` carries `${ctx.recordId}` (or any other `${ctx.X}` / `${param.X}` token) went out on the wire with the literal placeholder (`/api/v1/data/gapdata_item/$%7Bctx.recordId%7D`) when a host had registered its own `api` handler, as the Console does, so a record-level PATCH or POST hit a record that does not exist. A `url` action with the same token already resolved it. `ActionRunner` now runs its one target substitution on an `api` target before handing the action to a registered `api` handler, with the same scopes and the same per-value `encodeURIComponent` a `url` target gets. The built-in executor already did this and is unchanged; a registered handler for any other type still receives its target as authored.
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
---
2+
'@object-ui/app-shell': patch
3+
'@object-ui/i18n': patch
4+
---
5+
6+
fix(app-shell): editing an action in Studio no longer stops the object from saving
7+
8+
Clause-②: no
9+
Metadata-protocol: none
10+
11+
Three edits in Studio's action editor (Data › an object › Actions) wrote an action the spec refuses, and from then on every save of the object was refused until a reload, with nothing on screen naming the action:
12+
13+
- **Expose to AI agents** wrote a flat `aiExposed` (and its description a flat `aiDescription`). It now writes the spec's nested block, `ai: { exposed, description }`, and reads it back; unticking leaves a draft the spec accepts. A draft that still carries the flat keys is cleaned by the control's next write. The action preview's AI pill reads the same block.
14+
- **Switching the Type** kept keys the new type refuses (a script `body` on an API action, `onSuccess`, `outcomeMessages`, `undoable: true`). The switch now clears exactly those, and keeps everything the new type accepts.
15+
- **Body Shape** was a free-text box, so the documented `{ "wrap": "payload" }` was written as a string the spec refuses. It is now a Flat / Wrap control with a key input; a stored `{ wrap }` reads back as Wrap with its key, and Wrap is written only once it has a key.
16+
17+
The Data pillar now holds an action that is only unfinished, as it already held a field with no options: after "Run a script" before a source is typed, a Type switch before its endpoint, address or target is filled, or "Expose to AI agents" before a long enough description, no save goes out, and a neutral line names the input on the action until it is filled. A save the server still refuses is shown at the action: the strip names it (and the input, when the editor has one for that key), and "Show me" opens it on the Actions view. The next accepted save clears the strip, with no reload.
18+
19+
New `studio` namespace strings (en, zh): the Body Shape control, and the refusal and held lines for an action.

‎content/docs/automation/email-templates.mdx‎

Lines changed: 22 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,21 @@ never needs triple braces.
8282
same way they do in-app. `datetime` honours the reference timezone the caller
8383
passes; calendar-day `date` values are timezone-naive.
8484

85+
**One formatter per hole.** `{{ amount | default:0 | number:2 }}` is refused by
86+
`os validate`, `os build`, `os lint` and the metadata publish door
87+
(`email-template-hole-grammar`): a hole takes one formatter, and there is no
88+
chaining. Decide the value in the caller that supplies the data — a default, a
89+
second format — and pass the finished value as its own variable.
90+
91+
**The `currency` argument is a literal code.** `currency:EUR` formats in euros,
92+
and `currency` with no argument formats in US dollars. The argument is an ISO
93+
4217 code in three capital letters, never a path: `currency:currency_code` does
94+
not read the record's `currency_code`. `os validate`, `os build`, `os lint` and
95+
the metadata publish door refuse any other argument (`email-template-hole-grammar`,
96+
a `422 INVALID_METADATA` at the publish door), naming the hole and the rule. To
97+
show an amount in each record's own currency, format it in the caller that
98+
supplies the data and pass the formatted text as its own variable.
99+
85100
**Where a hole may sit in `bodyHtml`.** The renderer's escaping is right for
86101
element text and for a quoted attribute value that holds plain text, so those
87102
are the supported placements for `{{path}}`. A hole anywhere else — inside a
@@ -94,10 +109,14 @@ the `href` shape above. The refusal (`email-template-hole-placement`, a
94109
supported ones. A template already stored keeps sending; it is judged the next
95110
time it is published.
96111

97-
Two properties of the renderer to author around:
112+
Three properties of the renderer to author around:
98113

99-
- **A missing placeholder renders as an empty string.** Rendering never throws.
100-
Declare a variable `required` (below) if absence should be an error instead.
114+
- **A missing placeholder renders as an empty string.** Declare a variable
115+
`required` (below) if absence should be an error instead.
116+
- **A hole the authoring rules refuse is refused at render too.** A template
117+
stored before a rule existed is judged at its next publish; until then
118+
`sendTemplate` and `renderTemplate` fail with `TEMPLATE_INVALID`, naming the
119+
template and the hole, rather than send a wrong value.
101120
- **There are no loops, conditionals or partials.** A template is a data-only
102121
rendering by design; branching belongs in the caller, which passes in the
103122
already-decided values.

‎content/docs/deployment/cli.mdx‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -758,7 +758,7 @@ os compile --json # JSON output for CI pipelines
758758
→ Normalizing stack definition...
759759
→ Lowering inline handlers...
760760
→ Validating protocol compliance...
761-
→ Running author-time rules (54)...
761+
→ Running author-time rules (55)...
762762
→ Checking that every required capability has a provider installable in this edition...
763763
→ Collecting package docs (ADR-0046)... 0 collected
764764
→ Writing artifact...

0 commit comments

Comments
 (0)