Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 14 additions & 0 deletions .changeset/19403-hook-execution-panel-en-echo-decisions.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
---
"@objectstack/platform-objects": patch
---

The `hook` metadata-form panel no longer prints English field names and tooltips to a Chinese, Japanese or Spanish author: five keys — ten string leaves — were decided leaf by leaf and rendered in `zh-CN`, `ja-JP` and `es-ES` (#19403). With them the panel is finished: **zero** of its 47 string leaves now echoes its English source in all three locales.

The keys are the execution controls — `Retry Policy` and its `Max Retries` / `Backoff Ms` children, the hook-level `Timeout Ms`, and `Memory Mb` on the `body` composite. Four of the five labels are the extractor's humanize of a camelCase key rather than authored English, so each is rendered as the concept with its unit in a parenthetical (`Backoff Ms` → 退避(毫秒) / バックオフ(ms) / Retroceso (ms)) instead of being touched up in English. An en-echo is not automatically a defect, so every leaf carries a recorded reason in `hook-execution-panel-echo-decisions.test.ts`, naming the authored leaf its wording came from.

- **Machine tokens stay English — and one near-miss was read at the schema and cleared.** `timeoutMs.helpText` reads "Abort the hook after N milliseconds", and `abort` *is* a legal value of `HookSchema.onError` (`z.enum(['abort','log'])`) — but the key this tooltip describes takes a **number**, so no rendered word can land in it, and the schema's own description uses the word as the runtime's verb. Rendered. `retryPolicy.helpText` names `async`, which is `z.boolean().default(false)`, not an enum member — rendered, taking the panel's own authored 异步 / 非同期 / Asíncrono. The placeholder `N` and the bound `256` stay as written, and all four readings are asserted against the live `HookSchema` rather than described.
- **`body.capabilities` is reworded on both panels that declare it.** zh-CN 功能 and ja-JP 機能 read "feature" for what is a capability **token** from the `HookBodyCapability` enum. Every authored leaf of the objects catalog that names a capability renders it 能力 (zh-CN) and ケイパビリティ (ja-JP), so those are the words now used — on `hook` **and** `action` in one act, because both forms declare the same schema key. es-ES already read Capacidades and is unchanged.
- **Values only.** No key was added or removed: the full flattened key sets are identical on all four bundles (893 keys, 0 added, 0 removed) with a negative control proving the comparator sees a one-key delta, and `en` is untouched. Regenerated with `pnpm i18n:extract`, which dropped exactly the 10 provenance rows per locale that recorded these leaves as unauthored extractor fills, and added none.
- **A debt the previous round wrote down is paid.** Its cross-panel invariant compared 30 `HookBodySchema` pairs and excluded six of them, because `hook.fields.body.memoryMb` was an echo on both panels and an unauthored twin carries no evidence. Deciding it here empties the exclusion set: 30 of 30 pairs compared, `memoryMb` guarded on both panels.

Measured on the metadata-form catalogs: label keys echoing in all three locales fall **23 → 18** while the genuinely-translated control rises **515 → 520** (`zh-CN`) and **499 → 504** (`ja-JP`, `es-ES`), same population (893 string leaves, 538 of them labels), same run.
Original file line number Diff line number Diff line change
Expand Up @@ -58,9 +58,11 @@
// one. So the precedent is not a same-string match found elsewhere in the
// catalog: it is the other rendering of this very schema key.
//
// The fifth, `memoryMb`, is an echo on BOTH panels — the twin rule supplies
// nothing there, and that row says so instead of borrowing evidence it does
// not have.
// The fifth, `memoryMb`, was an echo on BOTH panels when this round ran — the
// twin rule supplied nothing there, and that row says so instead of borrowing
// evidence it does not have. ⭐ #19403 round 6 then decided the hook side from
// THIS round's rendering, so the exclusion at the foot of this file is now
// empty and the invariant covers all thirty pairs.
//
// ## The #19430 trap, met and checked AT THE SCHEMA before a word was rendered
//
Expand Down Expand Up @@ -216,7 +218,7 @@ const DECISIONS: readonly Decision[] = [
en: 'Capabilities',
verdict: ALL_TRANSLATE,
reason:
'THE AUTHORED TWIN AT THE SAME SCHEMA KEY, byte-identical en: hook.fields.body.capabilities.label is 功能 / 機能 / Capacidades. ⚠️ Recorded rather than improved: zh-CN 功能 and ja-JP 機能 read "feature", while the value is a capability TOKEN from the HookBodyCapability enum, and the catalog\'s other `Capabilities` (object.sections.capabilities.label, the capability CHECKBOXES) is 功能开关 — a third sense. The twin is the landed answer for this exact schema key, and diverging here to improve the word would BE the defect the cross-panel invariant below guards. Rewording it is a hook-family edit, not an action-family one.',
'THE AUTHORED TWIN AT THE SAME SCHEMA KEY, byte-identical en: hook.fields.body.capabilities.label. ⚠️ When this round ran the twin read 功能 / 機能 / Capacidades, and this row RECORDED rather than improved it: zh-CN 功能 and ja-JP 機能 read "feature", while the value is a capability TOKEN from the HookBodyCapability enum, and the catalog\'s other `Capabilities` (object.sections.capabilities.label, the capability CHECKBOXES) is 功能开关 — a third sense. The twin is the landed answer for this exact schema key, and diverging HERE to improve the word would BE the defect the cross-panel invariant below guards; rewording it is a hook-family edit, not an action-family one. ⭐ #19403 round 6 is that hook-family edit and moved both sides in one act — 能力 / ケイパビリティ / Capacidades — so this leaf still copies its twin verbatim and the invariant below still holds. See hook-execution-panel-echo-decisions.test.ts for the derivation behind the new word.',
},
{
scope: 'fields',
Expand Down Expand Up @@ -719,6 +721,7 @@ describe('#19403 round 5 — the blind spot, FIFTH shape: one schema, two forms,
const en = enMetadataForms as Record<string, any>;
const divergent: string[] = [];
const noTwinEvidence: string[] = [];
let compared = 0;
for (const child of SHARED_BODY_CHILDREN) {
for (const prop of ['label', 'helpText'] as const) {
const enAction = en.action?.fields?.[`body.${child}`]?.[prop];
Expand All @@ -735,6 +738,7 @@ describe('#19403 round 5 — the blind spot, FIFTH shape: one schema, two forms,
noTwinEvidence.push(`${child}.${prop}`);
continue;
}
compared += 1;
if (actionValue !== hookValue) divergent.push(`${locale} body.${child}.${prop}`);
}
}
Expand All @@ -744,16 +748,17 @@ describe('#19403 round 5 — the blind spot, FIFTH shape: one schema, two forms,
'one schema key rendered two ways on the two panels that declare it — make them agree, do not pick a favourite',
).toEqual([]);
// Lit — the invariant above is not vacuous: it really compared leaves.
// 5 children x 2 props x 3 locales = 30 pairs, of which the `memoryMb`
// pair carries no authored twin in any locale (6 exclusions).
expect(noTwinEvidence.length).toBe(6);
expect([...new Set(noTwinEvidence)].sort()).toEqual(['memoryMb.helpText', 'memoryMb.label']);
// …and that exclusion is exactly the family a later round of this card
// still owes. When it lands, this assertion starts covering `memoryMb`
// too, and reds if that round renders it differently from this one.
// 5 children x 2 props x 3 locales = 30 pairs. ⭐ When this round landed,
// SIX of them were excluded because `memoryMb` carried no authored twin in
// any locale, and this file said a later round of the card still owed it.
// #19403 round 6 paid it: the exclusion set is now EMPTY and every pair is
// compared, so a round that renders `memoryMb` differently on the two
// panels reds here as well as in its own ledger.
expect(noTwinEvidence).toEqual([]);
expect(compared, 'the invariant compared 5 children x 2 props x 3 locales').toBe(30);
const stillEchoingOnHook = SHARED_BODY_CHILDREN.filter((c) =>
TRANSLATED_LOCALES.some(([, forms]) => forms.hook?.fields?.[`body.${c}`]?.label === en.hook?.fields?.[`body.${c}`]?.label),
);
expect(stillEchoingOnHook).toEqual(['memoryMb']);
expect(stillEchoingOnHook).toEqual([]);
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -567,8 +567,8 @@ export const esESMetadataForms: NonNullable<TranslationData['metadataForms']> =
helpText: "Tiempo de espera por invocación (ms)"
},
"body.memoryMb": {
label: "Memory Mb",
helpText: "Per-invocation memory cap (MB, max 256)"
label: "Memoria (MB)",
helpText: "Límite de memoria por invocación (MB, máx. 256)"
},
handler: {
label: "Manejador",
Expand All @@ -582,8 +582,8 @@ export const esESMetadataForms: NonNullable<TranslationData['metadataForms']> =
label: "Al error"
},
timeoutMs: {
label: "Timeout Ms",
helpText: "Abort the hook after N milliseconds"
label: "Tiempo de espera (ms)",
helpText: "Abortar el hook después de N milisegundos"
},
runAs: {
label: "Ejecutar como",
Expand All @@ -594,16 +594,16 @@ export const esESMetadataForms: NonNullable<TranslationData['metadataForms']> =
helpText: "Fórmula opcional — omite el hook cuando evalúa a false"
},
retryPolicy: {
label: "Retry Policy",
helpText: "Retry on failure — most useful for async hooks"
label: "Política de reintentos",
helpText: "Reintentar al fallar — especialmente útil para hooks asíncronos"
},
"retryPolicy.maxRetries": {
label: "Max Retries",
helpText: "Maximum retry attempts"
label: "Reintentos máximos",
helpText: "Número máximo de reintentos"
},
"retryPolicy.backoffMs": {
label: "Backoff Ms",
helpText: "Delay between retries (ms)"
label: "Retroceso (ms)",
helpText: "Retraso entre reintentos (ms)"
}
}
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,16 +27,6 @@ export const esESGeneratedSourceHashes: Readonly<Record<string, string>> = {
"metadataForms.doc.description": "f233af60238b0879",
"metadataForms.doc.label": "3f35cf5088b999ad",
"metadataForms.email_template.fields.variables.helpText": "27197774c2cda633",
"metadataForms.hook.fields.body.memoryMb.helpText": "f53c826ce3c94ad9",
"metadataForms.hook.fields.body.memoryMb.label": "a1e1612eff7f82ea",
"metadataForms.hook.fields.retryPolicy.backoffMs.helpText": "42137a83459ed6b7",
"metadataForms.hook.fields.retryPolicy.backoffMs.label": "cad56dd875f84c22",
"metadataForms.hook.fields.retryPolicy.helpText": "de59f5fbb8ad7fda",
"metadataForms.hook.fields.retryPolicy.label": "7ec5dad15f687f05",
"metadataForms.hook.fields.retryPolicy.maxRetries.helpText": "f7e133cfc5e64240",
"metadataForms.hook.fields.retryPolicy.maxRetries.label": "422d764e2265642c",
"metadataForms.hook.fields.timeoutMs.helpText": "31e114f26389aec7",
"metadataForms.hook.fields.timeoutMs.label": "2b887c62238d3532",
"metadataForms.mapping.description": "654a322ed6e264bb",
"metadataForms.mapping.label": "9baba989f46cd1e8",
"metadataForms.object.fields.enable.activities.label": "1091422f7f3133d3",
Expand Down
Loading
Loading