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
41 changes: 41 additions & 0 deletions .changeset/apps-mdx-retired-version-mobile-navigation.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
---
---

docs(ui): `ui/apps` 停止教 17.0.0 已退役的 `App.version` 与 `App.mobileNavigation`,两个 `os:check` 示例改用 `defineApp()` 以便退役键在 tsc 就红 (#5313)

`content/docs/ui/apps.mdx` 有四处仍把 17.0.0(2026-06 liveness audit / ADR-0049
enforce-or-remove)已退役的键当作可作者化面在教:「Basic Structure」示例里的
`version: '1.0.0'`、App Properties 表里的 `version` 行、整节 `## Mobile Navigation`
(含 `mobileNavigation: { mode, bottomNavItems }` 示例与 `mode` 取值说明),以及
「Complete Example」里的 `version: '2.0.0'`。

墓碑是 `retiredKey()`(`z.never().optional()`),所以照抄这两个示例不是「多写一个没用
的键」,而是**整条 save 硬失败**。实测把「Basic Structure」块原样喂给
`getMetadataTypeSchema('app')`:

version :: `App.version` was removed in @objectstack/spec 17.0.0 (2026-06 liveness
audit — no consumer in framework or objectui). An app is versioned by its owning
package: use `manifest.version`. Delete the key.

处置按墓碑自己的处方:`version` 删键(应用的版本是其所属包的 `manifest.version`);
`mobileNavigation` 没有替代能力——它是完全未实现的键,连 `packages/mobile` 都没读过,
`mode` 选择器不改变任何东西——故整节删除,并在 App Properties 表后新增 Callout 点名这
两个键、给出各自处方,顺带指回已在正文交代过的 `homePageId`(#4667 / #4709)。

同时给该页两个 `{/* os:check */}` 块加上 `defineApp()` 标注。此前两块都是无类型标注的
对象字面量(`const crmApp = { … }`),没有任何东西把它们和 `AppSchema` 关联起来,
`retiredKey()` 赖以在编译期开火的 `never` 入参类型永远不参与推断——`check:skill-examples`
只做 tsc,于是对退役键这一类恒绿。加标注后同一个门在旧示例上会红:

✗ Prose TypeScript examples do not compile against @objectstack/spec:
content/docs/ui/apps.mdx:19:3 error TS2322: Type 'string' is not assignable to type 'undefined'.
content/docs/ui/apps.mdx:239:3 error TS2322: Type 'string' is not assignable to type 'undefined'.

改后 `✅ 204 prose examples type-check against @objectstack/spec`,两个块喂 schema 也都
`parses: true`。这条标注是本次修复的护栏:此后该页示例里任何退役键都在门里当场红,而不是
等作者照抄后在 save 时才发现。

门本体(`packages/spec/scripts/check-skill-examples.ts`)不动——issue 里的 B 方案(对能
推断出 schema 的块追加一次 `safeParse`)覆盖更广,但需要先解决「哪个块对应哪个 schema」
的归属推断,是独立的一次设计。不碰 `packages/spec/**`、`content/docs/references/**`
与 `content/docs/releases/`。Docs-only。
47 changes: 27 additions & 20 deletions content/docs/ui/apps.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,11 @@ An **App** is a logical container that bundles objects, views, pages, and dashbo

{/* os:check */}
```typescript
const crmApp = {
import { defineApp } from '@objectstack/spec';

const crmApp = defineApp({
name: 'crm',
label: 'CRM',
version: '1.0.0',
description: 'Customer Relationship Management',
icon: 'briefcase',
active: true,
Expand All @@ -33,7 +34,7 @@ const crmApp = {
],

requiredPermissions: ['crm_access'],
};
});
```

## App Properties
Expand All @@ -42,7 +43,6 @@ const crmApp = {
| :--- | :--- | :--- | :--- |
| `name` | `string` | ✅ | Machine name (`snake_case`) |
| `label` | `string` | ✅ | Display name |
| `version` | `string` | optional | App version |
| `description` | `string` | optional | App description |
| `icon` | `string` | optional | App icon (Lucide) |
| `active` | `boolean` | optional | Is app active (default: `true`) |
Expand All @@ -51,6 +51,25 @@ const crmApp = {
| `branding` | `AppBranding` | optional | Visual customization |
| `requiredPermissions` | `string[]` | optional | Required permissions to access |

<Callout type="warn">
**Older app samples no longer parse** — check yours before copying it forward.
`@objectstack/spec` 17.0.0 (2026-06 liveness audit, ADR-0049 enforce-or-remove)
removed `version` and `mobileNavigation`, and both are now refused at parse
time rather than ignored, so one leftover key fails the whole save.

- `version` — an app is versioned by its owning package. Use
`manifest.version` and delete the key; nothing in framework or objectui ever
read the per-app number, which could silently disagree with the package's.
- `mobileNavigation` — fully unimplemented: no renderer, `packages/mobile`
included, ever read it, so the `mode` picker changed nothing. Delete the
key; the block returns if and when a real mobile navigation ships.

`homePageId` (removed in the same major, #4667/#4709) is covered under
[Common Navigation Properties](#common-navigation-properties). Every rejection
carries its own replacement instruction, so paste the old app and read what
the error tells you.
</Callout>

## Navigation Items

The navigation tree supports nine item types, combined to create rich menu structures: `object`, `dashboard`, `page`, `url`, `report`, `action`, `component`, `group` and `separator`. Each is documented below.
Expand Down Expand Up @@ -212,27 +231,15 @@ branding: {
| `logo` | `string` | Logo image URL |
| `favicon` | `string` | Favicon URL |

## Mobile Navigation

Configure mobile-specific navigation behavior:

```typescript
mobileNavigation: {
mode: 'bottom_nav',
bottomNavItems: ['nav_home', 'nav_accounts', 'nav_contacts', 'nav_settings'],
}
```

`mode` accepts `'drawer'` (default), `'bottom_nav'`, or `'hamburger'`. `bottomNavItems` lists the navigation item `id`s to surface in the bottom bar (max 5).

## Complete Example

{/* os:check */}
```typescript
const projectApp = {
import { defineApp } from '@objectstack/spec';

const projectApp = defineApp({
name: 'project_management',
label: 'Project Management',
version: '2.0.0',
description: 'Track projects, tasks, and team workload',
icon: 'folder-kanban',
active: true,
Expand Down Expand Up @@ -287,7 +294,7 @@ const projectApp = {
// and the ROOT landing follows `isDefault`. The key was removed in 17.0.0
// (#4667, #4709) — it did have a consumer, but it pointed at a navigation item
// by id and fell back silently when that id dangled.
};
});
```

## Related
Expand Down
Loading