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
10 changes: 10 additions & 0 deletions .changeset/32977-skills-suite-composes-under-preserve.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
---
'@objectstack/skills': patch
---

docs(skills): a suite composes under `manifest: 'preserve'`; a flat composition with two or more apps under one app or module package id is refused

Clause-②: no
Metadata-protocol: none

`objectstack-platform` `references/bootstrap.md` § Multi-App Composition said no app in this repo uses `composeStacks()` and gave no strategy for a suite. It now says that a suite (an app package plus module packages, one app each) composes with `composeStacks(stacks, { manifest: 'preserve' })`, which keeps each package's own id, as `examples/app-multi-package` does, and that a flat `manifest` (`'last'`, the default, `'first'` or an index) keeps one manifest, so two or more apps under one `app` or `module` package id are refused with `STACK_SINGLE_APP_VIOLATION` (422). `objectstack-ui` `SKILL.md` § App Navigation names the same remedy for a suite's further apps. Both files pay for the new text by deleting restatements in the same file; their token counts stay under their ceilings. No schema or runtime change.
30 changes: 14 additions & 16 deletions skills/objectstack-platform/references/bootstrap.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# Platform — bootstrap examples and composition notes (moved verbatim from SKILL.md)
# Platform — bootstrap examples and composition notes

### Minimal Example

Expand Down Expand Up @@ -37,12 +37,12 @@ All named collections support **map format** where the key becomes the `name` fi

```typescript
export default defineStack({
// Array format (traditional)
// Array format
objects: [
{ name: 'task', fields: { title: Field.text() } },
],

// Map format (key becomes name) — preferred for readability
// Map format — preferred
objects: {
task: { fields: { title: Field.text() } },
project: { fields: { name: Field.text() } },
Expand All @@ -52,8 +52,6 @@ export default defineStack({

### Barrel Import Pattern

Use barrel exports to keep config clean:

```typescript
// src/objects/index.ts
export { default as task } from './task.object';
Expand Down Expand Up @@ -117,21 +115,21 @@ const kernel = runtime.getKernel();

## Multi-App Composition

Host several apps in one runtime by registering an `AppPlugin` per app — this is
how real multi-app composition happens (`packages/cli/src/commands/serve.ts`).
Each app contributes its objects under their canonical `name`; names are
globally unique and equal the physical table name, so use them directly in
queries, hooks, formulas, and REST URLs.
Host several apps in one runtime by registering an `AppPlugin` per app
(`packages/cli/src/commands/serve.ts`). Each app contributes its objects under
their globally unique `name`; use it directly in queries, hooks, formulas, and
REST URLs.

A merge-at-authoring-time alternative, `composeStacks()`, exists in
`@objectstack/spec` (`stack.zod.ts`) with `objectConflict` /
`manifest` strategies. No app in this repo uses it — read the schema before
reaching for it.
`composeStacks()` (`@objectstack/spec`) merges stacks at authoring time, with
`objectConflict` / `manifest` strategies. A suite (an app package plus module
packages, one app each) composes with
`composeStacks(stacks, { manifest: 'preserve' })`, which keeps each package's
own id (`examples/app-multi-package`). A flat `manifest` (`'last'`, the
default, `'first'` or an index) keeps one, so two or more apps under one `app`
or `module` package id are refused (`STACK_SINGLE_APP_VIOLATION`, 422).

## Complete Working Example

A minimal but complete project from scratch:

**`package.json`**:
```json
{
Expand Down
16 changes: 8 additions & 8 deletions skills/objectstack-ui/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -82,9 +82,8 @@ custom page or form config. Prefer, in order:
type: 'simple',
sections: [{ group: 'invoice_header' }], // a declared fieldGroup
subforms: [
{ childObject: 'invoice_line', // relationshipField + columns are
title: 'Line Items', // derived from the child object;
addLabel: 'Add line' }, // set `columns` here only to override.
{ childObject: 'invoice_line', // set `columns` only to override
title: 'Line Items', addLabel: 'Add line' },
],
},
```
Expand Down Expand Up @@ -141,8 +140,10 @@ Build an App with `App.create({...})` from
amended): `defineStack` refuses a second app in a `type: 'app'` and a
`type: 'module'` package alike. A suite (an app package plus its modules) may
surface more apps: each further app goes in a module of its own with the
objects it shows, served under that module's package id and gone when it is
disabled. Audiences sharing one product and its objects stay
objects it shows (gone when it is disabled), and the suite composes with
`composeStacks(stacks, { manifest: 'preserve' })`, which serves each app under
its own package id; a flat composition puts them under one and is refused.
Audiences sharing one product and its objects stay
`requiredPermissions`-gated groups **inside** one app.

Who sees an app (`tabPermissions`) and who may open it (app
Expand All @@ -163,9 +164,8 @@ Who sees an app (`tabPermissions`) and who may open it (app
| `doc` | `book?` / `doc?` (one required), `label`, `icon` | Package docs; inherits docs audience |
| `separator` | — | Visual separator |

> **`requiresObject` / `requiresService`:** Use these on any item that
> depends on an optional system object or kernel service so the nav item is
> automatically hidden when missing — never hard-code conditional UI.
> **`requiresObject` / `requiresService`** hide an item whose optional
> system object or kernel service is missing — never hard-code conditional UI.

---

Expand Down
Loading