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
37 changes: 37 additions & 0 deletions .changeset/app-form-tombstone-controls.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
---
"@objectstack/spec": patch
"@objectstack/platform-objects": patch
---

fix(spec): stop offering retired `app` keys in the metadata form, and make the reconciliation gate see tombstones (#5280)

The `app` authoring form rendered **eight** controls for keys `AppSchema` had
already retired to `retiredKey()` tombstones in 17.0.0 — `version`, `homePageId`,
`objects`, `apis`, `sharing`, `embed`, `mobileNavigation` and `aria`. A tombstone
is `z.never().optional()`, so filling one of those controls did not lose the
value quietly: it failed the **entire save** with the key's removal
prescription. The controls are gone, each with a comment in place naming where
the capability went (`manifest.version`; the first `navigation` item by `order`
plus `isDefault`; `defineStack({ objects })`; `defineStack({ apis })`;
`FormView.sharing` for both public access and embedding; the component that
renders the DOM node for `aria`).

Nothing about the contract changes — every one of these keys was already
rejected at parse. What changes is that an author is no longer shown a control
that can only produce a 422.

**The reconciliation gate now judges the right thing.** #3786's
`metadata-form-zod-reconciliation.test.ts` asked whether an offered key was
`∈ shape`. That was the same question as "may the author write this" until
`retiredKey()` existed: a tombstone **deliberately stays in the shape** so the
removal can carry its own upgrade prescription, so every one of those eight keys
read as "the Zod accepts it" and the gate stayed green over all of them. It now
asserts `∈ shape` **and not a tombstone**, in both directions — a retired key
may not be offered, and its absence needs no ledger entry to excuse it. The
detector reads the schema node (`z.never()` under the optional wrapper), never a
list of key names, mirroring `isRetired()` on the JSON-Schema side of
`build-schemas.ts`. The next `retiredKey()` retirement that forgets a form now
fails this test instead of reaching an author.

Retiring an authorable key already required pruning its form input; that step is
now enforced rather than remembered.
Original file line number Diff line number Diff line change
Expand Up @@ -937,15 +937,15 @@ export const enMetadataForms: NonNullable<TranslationData['metadataForms']> = {
},
content: {
label: "Content",
description: "Objects and APIs this app uses."
description: "Ambient assistant binding. Objects and endpoints are declared on the stack, not the app."
},
branding: {
label: "Branding",
description: "Theme colors and logo."
},
access_and_sharing: {
label: "Access & sharing",
description: "Who can access this app and how it can be embedded."
description: "Who can access this app. Public access and embedding are per form view, not app-level."
}
},
fields: {
Expand All @@ -959,9 +959,6 @@ export const enMetadataForms: NonNullable<TranslationData['metadataForms']> = {
description: {
label: "Description"
},
version: {
label: "Version"
},
icon: {
label: "Icon",
helpText: "Lucide icon name (e.g. \"users\", \"briefcase\")"
Expand All @@ -981,22 +978,6 @@ export const enMetadataForms: NonNullable<TranslationData['metadataForms']> = {
label: "Areas",
helpText: "Group items into collapsible areas"
},
homePageId: {
label: "Home Page Id",
helpText: "Landing page when app opens"
},
mobileNavigation: {
label: "Mobile Navigation",
helpText: "Bottom tab bar config for mobile"
},
objects: {
label: "Objects",
helpText: "Object names this app exposes"
},
apis: {
label: "Apis",
helpText: "API endpoint definitions"
},
defaultAgent: {
label: "Default Agent",
helpText: "AI agent for the ambient assistant button"
Expand All @@ -1008,18 +989,6 @@ export const enMetadataForms: NonNullable<TranslationData['metadataForms']> = {
requiredPermissions: {
label: "Required Permissions",
helpText: "Permissions needed to access this app"
},
sharing: {
label: "Sharing",
helpText: "Public/internal/restricted access control"
},
embed: {
label: "Embed",
helpText: "iFrame embed configuration"
},
aria: {
label: "Aria",
helpText: "Accessibility labels"
}
}
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -937,15 +937,15 @@ export const esESMetadataForms: NonNullable<TranslationData['metadataForms']> =
},
content: {
label: "Contenido",
description: "Objetos y API que usa esta app."
description: "Asignación del asistente ambiental. Los objetos y endpoints se declaran en el stack, no en la app."
},
branding: {
label: "Marca",
description: "Colores de tema y logotipo."
},
access_and_sharing: {
label: "Acceso y uso compartido",
description: "Quién puede acceder a esta app y cómo puede incrustarse."
description: "Quién puede acceder a esta app. El acceso público y la incrustación son por vista de formulario, no a nivel de app."
}
},
fields: {
Expand All @@ -959,9 +959,6 @@ export const esESMetadataForms: NonNullable<TranslationData['metadataForms']> =
description: {
label: "Descripción"
},
version: {
label: "Versión"
},
icon: {
label: "Icono",
helpText: "Nombre de icono Lucide (p. ej. \"users\", \"briefcase\")"
Expand All @@ -981,22 +978,6 @@ export const esESMetadataForms: NonNullable<TranslationData['metadataForms']> =
label: "Áreas",
helpText: "Agrupa elementos en áreas plegables"
},
homePageId: {
label: "ID de página inicial",
helpText: "Página de inicio al abrir la app"
},
mobileNavigation: {
label: "Navegación móvil",
helpText: "Configuración de barra de pestañas inferior para móvil"
},
objects: {
label: "Permisos de objeto",
helpText: "Nombres de objeto que expone esta app"
},
apis: {
label: "API",
helpText: "Definiciones de endpoints API"
},
defaultAgent: {
label: "Agente predeterminado",
helpText: "Agente de IA para el botón de asistente ambiental"
Expand All @@ -1008,18 +989,6 @@ export const esESMetadataForms: NonNullable<TranslationData['metadataForms']> =
requiredPermissions: {
label: "Permisos requeridos",
helpText: "Permisos necesarios para acceder a esta app"
},
sharing: {
label: "Compartición",
helpText: "Control de acceso público/interno/restringido"
},
embed: {
label: "Incrustación",
helpText: "Configuración de incrustación iFrame"
},
aria: {
label: "Accesibilidad",
helpText: "Etiquetas de accesibilidad"
}
}
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -937,15 +937,15 @@ export const jaJPMetadataForms: NonNullable<TranslationData['metadataForms']> =
},
content: {
label: "コンテンツ",
description: "このアプリが使用するオブジェクトと API。"
description: "アンビエントアシスタントの割り当て。オブジェクトとエンドポイントはアプリではなくスタックで宣言します。"
},
branding: {
label: "ブランディング",
description: "テーマカラーとロゴ。"
},
access_and_sharing: {
label: "アクセスと共有",
description: "このアプリにアクセス可能なユーザーと埋め込み方法。"
description: "このアプリにアクセスできるユーザー。公開アクセスと埋め込みはアプリ単位ではなくフォームビュー単位です。"
}
},
fields: {
Expand All @@ -959,9 +959,6 @@ export const jaJPMetadataForms: NonNullable<TranslationData['metadataForms']> =
description: {
label: "説明"
},
version: {
label: "バージョン"
},
icon: {
label: "アイコン",
helpText: "Lucide アイコン名(例: \"users\", \"briefcase\")"
Expand All @@ -981,22 +978,6 @@ export const jaJPMetadataForms: NonNullable<TranslationData['metadataForms']> =
label: "領域",
helpText: "項目を折りたたみ可能なエリアにグループ化"
},
homePageId: {
label: "ホームページ ID",
helpText: "アプリ起動時のランディングページ"
},
mobileNavigation: {
label: "モバイルナビゲーション",
helpText: "モバイル用ボトムタブバー設定"
},
objects: {
label: "オブジェクト権限",
helpText: "このアプリが公開するオブジェクト名"
},
apis: {
label: "API",
helpText: "API エンドポイント定義"
},
defaultAgent: {
label: "既定エージェント",
helpText: "常駐アシスタントボタン用 AI エージェント"
Expand All @@ -1008,18 +989,6 @@ export const jaJPMetadataForms: NonNullable<TranslationData['metadataForms']> =
requiredPermissions: {
label: "必要な権限",
helpText: "このアプリへのアクセスに必要な権限"
},
sharing: {
label: "共有",
helpText: "公開/内部/制限付きアクセス制御"
},
embed: {
label: "埋め込み",
helpText: "iFrame 埋め込み設定"
},
aria: {
label: "アクセシビリティ",
helpText: "アクセシビリティラベル"
}
}
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -929,23 +929,23 @@ export const zhCNMetadataForms: NonNullable<TranslationData['metadataForms']> =
sections: {
basics: {
label: "基础信息",
description: "标识、版本与图标"
description: "应用标识与启用状态"
},
navigation: {
label: "导航",
description: "菜单结构与首页"
description: "侧边栏菜单项与分区分组"
},
content: {
label: "内容",
description: "对象、API 与默认智能体"
description: "环境助手绑定。对象与接口在 stack 上声明,不在 app 上"
},
branding: {
label: "品牌",
description: "主题色与 Logo"
},
access_and_sharing: {
label: "访问与共享",
description: "权限、共享与嵌入"
description: "谁可以访问此应用。公开访问与嵌入按表单视图授予,不是应用级开关"
}
},
fields: {
Expand All @@ -959,9 +959,6 @@ export const zhCNMetadataForms: NonNullable<TranslationData['metadataForms']> =
description: {
label: "描述"
},
version: {
label: "版本"
},
icon: {
label: "图标",
helpText: "Lucide 图标名(如 users、briefcase)"
Expand All @@ -981,22 +978,6 @@ export const zhCNMetadataForms: NonNullable<TranslationData['metadataForms']> =
label: "区域",
helpText: "将菜单项组织为可折叠分组"
},
homePageId: {
label: "首页 ID",
helpText: "应用打开时跳转的页面"
},
mobileNavigation: {
label: "移动端导航",
helpText: "移动端底部 Tab 栏配置"
},
objects: {
label: "对象权限",
helpText: "此应用暴露的对象名"
},
apis: {
label: "API",
helpText: "API 端点定义"
},
defaultAgent: {
label: "默认智能体",
helpText: "右下角浮动助手按钮调用的 AI 智能体"
Expand All @@ -1008,18 +989,6 @@ export const zhCNMetadataForms: NonNullable<TranslationData['metadataForms']> =
requiredPermissions: {
label: "所需权限",
helpText: "访问该应用需要的权限"
},
sharing: {
label: "共享",
helpText: "公开 / 内部 / 受限的访问控制"
},
embed: {
label: "嵌入",
helpText: "iFrame 嵌入配置"
},
aria: {
label: "无障碍",
helpText: "无障碍标签与角色"
}
}
},
Expand Down
Loading
Loading