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
60 changes: 53 additions & 7 deletions content/docs/references/kernel/context.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,8 @@ Defines the operating mode of the kernel
## TypeScript Usage

```typescript
import { KernelContext, RuntimeMode } from '@objectstack/spec/kernel';
import type { KernelContext, RuntimeMode } from '@objectstack/spec/kernel';
import { KernelContext, RuntimeMode, PreviewModeConfig } from '@objectstack/spec/kernel';
import type { KernelContext, RuntimeMode, PreviewModeConfig } from '@objectstack/spec/kernel';

// Validate data
const result = KernelContext.parse(data);
Comment on lines +17 to 21
Copy link

Copilot AI Feb 14, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The TypeScript usage snippet is incorrect: KernelContext is a type (not a Zod schema), so KernelContext.parse(...) and the value import will fail. Also importing the same identifiers as both value and type in separate import statements is invalid/redundant. Update the snippet to import and use KernelContextSchema.parse(...), and only import KernelContext/RuntimeMode/PreviewModeConfig as types if needed.

Suggested change
import { KernelContext, RuntimeMode, PreviewModeConfig } from '@objectstack/spec/kernel';
import type { KernelContext, RuntimeMode, PreviewModeConfig } from '@objectstack/spec/kernel';
// Validate data
const result = KernelContext.parse(data);
import { KernelContextSchema } from '@objectstack/spec/kernel';
import type { KernelContext, RuntimeMode, PreviewModeConfig } from '@objectstack/spec/kernel';
// Validate data
const result = KernelContextSchema.parse(data);

Copilot uses AI. Check for mistakes.
Expand All @@ -30,13 +30,14 @@ const result = KernelContext.parse(data);
| Property | Type | Required | Description |
| :--- | :--- | :--- | :--- |
| **instanceId** | `string` | ✅ | Unique UUID for this running kernel process |
| **mode** | `Enum<'development' \| 'production' \| 'test' \| 'provisioning'>` | ✅ | Kernel operating mode |
| **mode** | `Enum<'development' \| 'production' \| 'test' \| 'provisioning' \| 'preview'>` | ✅ | Kernel operating mode |
| **version** | `string` | ✅ | Kernel version |
| **appName** | `string` | optional | Host application name |
| **cwd** | `string` | ✅ | Current working directory |
| **workspaceRoot** | `string` | optional | Workspace root if different from cwd |
| **startTime** | `integer` | ✅ | Boot timestamp (ms) |
| **features** | `Record<string, boolean>` | ✅ | Global feature toggles |
| **previewMode** | `PreviewModeConfig` | optional | Preview/demo mode configuration (used when mode is `'preview'`) |


---
Expand All @@ -47,11 +48,56 @@ Kernel operating mode

### Allowed Values

* `development`
* `production`
* `test`
* `provisioning`
* `development` — Hot-reload, verbose logging
* `production` — Optimized, strict security
* `test` — Mocked interfaces
* `provisioning` — Setup/Migration mode
* `preview` — Demo/preview mode — bypass auth, simulate admin identity


---

## PreviewModeConfig

Configures the kernel's preview/demo mode behaviour. When `mode` is set to `'preview'`, the platform
Copy link

Copilot AI Feb 14, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Spelling: the repo overwhelmingly uses “behavior” (American English). Consider changing “behaviour” to “behavior” in this new PreviewModeConfig section for consistency.

Suggested change
Configures the kernel's preview/demo mode behaviour. When `mode` is set to `'preview'`, the platform
Configures the kernel's preview/demo mode behavior. When `mode` is set to `'preview'`, the platform

Copilot uses AI. Check for mistakes.
skips authentication screens and simulates an admin identity so visitors can explore the system without
registering or logging in.

<Callout type="warn">
**Security:** Preview mode should **never** be used in production environments.
</Callout>

### Properties

| Property | Type | Default | Description |
| :--- | :--- | :--- | :--- |
| **autoLogin** | `boolean` | `true` | Auto-login as simulated user, skipping login/registration pages |
| **simulatedRole** | `Enum<'admin' \| 'user' \| 'viewer'>` | `'admin'` | Permission role for the simulated preview user |
| **simulatedUserName** | `string` | `'Preview User'` | Display name for the simulated preview user |
| **readOnly** | `boolean` | `false` | Restrict the preview session to read-only operations |
| **expiresInSeconds** | `integer` | `0` | Preview session duration in seconds (0 = no expiration) |
| **bannerMessage** | `string` | — | Banner message displayed in the UI during preview mode |

### Example

```typescript
import { KernelContextSchema } from '@objectstack/spec/kernel';

const ctx = KernelContextSchema.parse({
instanceId: '550e8400-e29b-41d4-a716-446655440000',
mode: 'preview',
version: '1.0.0',
cwd: '/app',
startTime: Date.now(),
previewMode: {
autoLogin: true,
simulatedRole: 'admin',
simulatedUserName: 'Demo Admin',
readOnly: false,
bannerMessage: 'You are exploring a demo — data will be reset periodically.',
},
});
```

---

9 changes: 7 additions & 2 deletions examples/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -152,14 +152,18 @@ pnpm build
**Level:** 🔴 Advanced
**Protocols:** System, API, Data

**Complete server implementation** showing how to build a metadata-driven backend. Features dynamic schema loading from plugins, auto-generated REST APIs, unified metadata API, and plugin orchestration.
**Complete server implementation** showing how to build a metadata-driven backend. Features dynamic schema loading from plugins, auto-generated REST APIs, unified metadata API, plugin orchestration, and **preview/demo mode**.

**Preview Mode:** Run with `OS_MODE=preview` to bypass login/registration and simulate an admin identity — ideal for marketplace demos and app showcases.

**Quick Start:**
```bash
cd examples/app-host
pnpm install
pnpm dev
# API available at http://localhost:3000

# Preview mode (no login required)
OS_MODE=preview pnpm dev
```

---
Expand Down Expand Up @@ -225,6 +229,7 @@ pnpm typecheck
|----------|---------|----------|
| Manifest | ✅ Complete | All examples with `objectstack.config.ts` |
| Plugin System | ✅ Complete | [App Host](./app-host/) |
| Preview Mode | ✅ Complete | [App Host](./app-host/) — `OS_MODE=preview` |
| Datasources | 🟡 Partial | [App Host](./app-host/) |
| I18n / Translations | ✅ Complete | [Todo Translations](./app-todo/src/translations/), [CRM Translations](./app-crm/src/translations/) |
| Job Scheduling | 🔴 Missing | _Planned_ |
Expand Down
57 changes: 57 additions & 0 deletions examples/app-host/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ It demonstrates how to build a metadata-driven backend that dynamically loads ob
- **Unified Metadata API**: `/api/v1/meta/objects`
- **Unified Data API**: `/api/v1/data/:object` (CRUD)
- **Zero-Code Backend**: No creating routes or controllers per object.
- **Preview Mode**: Run in demo mode — bypass login, auto-simulate admin identity.

## Setup

Expand All @@ -28,6 +29,62 @@ It demonstrates how to build a metadata-driven backend that dynamically loads ob
# Expected: Server starts at http://localhost:3000
```

3. Run in **preview mode** (skip login, simulate admin):
```bash
OS_MODE=preview pnpm dev
# Expected: Server starts in preview mode — no login required
```

## Preview / Demo Mode

Preview mode allows visitors (e.g. marketplace customers) to explore the platform
without registering or logging in. The kernel boots with `mode: 'preview'` and the
frontend skips authentication screens, automatically simulating an admin session.

### How It Works

1. The runtime reads `OS_MODE=preview` from the environment (or the stack config).
2. The `KernelContext` is created with `mode: 'preview'` and a `previewMode` config.
3. The frontend detects `mode === 'preview'` and:
- Hides the login / registration pages.
- Automatically creates a simulated admin session.
- Shows a preview banner to indicate demo mode.

### Configuration

```typescript
import { KernelContextSchema } from '@objectstack/spec/kernel';

const ctx = KernelContextSchema.parse({
instanceId: '550e8400-e29b-41d4-a716-446655440000',
mode: 'preview',
version: '1.0.0',
cwd: process.cwd(),
startTime: Date.now(),
previewMode: {
autoLogin: true, // Skip login/registration pages
simulatedRole: 'admin', // Simulated user role (admin | user | viewer)
simulatedUserName: 'Demo Admin',
readOnly: false, // Allow writes (set true for read-only demos)
expiresInSeconds: 3600, // Session expires after 1 hour (0 = no expiration)
bannerMessage: 'You are exploring a demo — data will be reset periodically.',
},
});
```

### PreviewModeConfig Properties

| Property | Type | Default | Description |
|:---|:---|:---|:---|
| **autoLogin** | `boolean` | `true` | Auto-login as simulated user, skip login/registration |
| **simulatedRole** | `'admin' \| 'user' \| 'viewer'` | `'admin'` | Permission role for the simulated user |
| **simulatedUserName** | `string` | `'Preview User'` | Display name shown in the UI |
| **readOnly** | `boolean` | `false` | Block all write operations |
| **expiresInSeconds** | `integer` | `0` | Session duration (0 = no expiration) |
| **bannerMessage** | `string` | — | Banner message displayed in the UI |

> **⚠️ Security:** Preview mode should NEVER be used in production environments.

## API Usage Examples

### 1. Get All Objects
Expand Down
62 changes: 62 additions & 0 deletions examples/app-host/objectstack.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,3 +33,65 @@ export default defineStack({
new AppPlugin(BiPluginManifest)
]
});

/**
* Preview Mode Host Example
*
* Demonstrates how to run the platform in "preview" mode.
* When `mode` is set to `'preview'`, the kernel signals the frontend to:
* - Skip login/registration screens
* - Automatically simulate an admin identity
* - Display a preview-mode banner to the user
*
* Use this for marketplace demos, app showcases, or onboarding
* tours where visitors should explore the system without signing up.
*
* ## Usage
*
* Set the `OS_MODE` environment variable to `preview` at boot:
*
* ```bash
* OS_MODE=preview pnpm dev
* ```
*
* Or use this stack definition directly as a starting point.
*
* ## KernelContext (created by the Runtime at boot)
*
* ```ts
* import { KernelContextSchema } from '@objectstack/spec/kernel';
*
* const ctx = KernelContextSchema.parse({
* instanceId: '550e8400-e29b-41d4-a716-446655440000',
* mode: 'preview',
* version: '1.0.0',
* cwd: process.cwd(),
* startTime: Date.now(),
* previewMode: {
* autoLogin: true,
* simulatedRole: 'admin',
* simulatedUserName: 'Demo Admin',
* readOnly: false,
* bannerMessage: 'You are exploring a demo — data will be reset periodically.',
* },
* });
* ```
*/
export const PreviewHostExample = defineStack({
manifest: {
id: 'app-host-preview',
name: 'app_host_preview',
version: '1.0.0',
description: 'Host application in preview/demo mode — bypasses login, simulates admin user',
type: 'app',
},

// Same plugins as the standard host
plugins: [
new ObjectQLPlugin(),
new DriverPlugin(new InMemoryDriver()),
new AppPlugin(CrmApp),
new AppPlugin(TodoApp),
new AppPlugin(BiPluginManifest)
]
});
75 changes: 74 additions & 1 deletion packages/spec/src/kernel/context.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { describe, it, expect } from 'vitest';
import {
RuntimeMode,
KernelContextSchema,
PreviewModeConfigSchema,
type KernelContext,
} from './context.zod';

Expand All @@ -11,6 +12,7 @@ describe('RuntimeMode', () => {
expect(() => RuntimeMode.parse('production')).not.toThrow();
expect(() => RuntimeMode.parse('test')).not.toThrow();
expect(() => RuntimeMode.parse('provisioning')).not.toThrow();
expect(() => RuntimeMode.parse('preview')).not.toThrow();
});

it('should reject invalid runtime modes', () => {
Expand Down Expand Up @@ -87,10 +89,81 @@ describe('KernelContextSchema', () => {
});

it('should accept all runtime modes in context', () => {
const modes = ['development', 'production', 'test', 'provisioning'] as const;
const modes = ['development', 'production', 'test', 'provisioning', 'preview'] as const;
modes.forEach(mode => {
const parsed = KernelContextSchema.parse({ ...validContext, mode });
expect(parsed.mode).toBe(mode);
});
});

it('should accept preview mode with previewMode config', () => {
const parsed = KernelContextSchema.parse({
...validContext,
mode: 'preview',
previewMode: {
autoLogin: true,
simulatedRole: 'admin',
simulatedUserName: 'Demo Admin',
readOnly: true,
expiresInSeconds: 3600,
bannerMessage: 'You are viewing a demo of this application.',
},
});
expect(parsed.mode).toBe('preview');
expect(parsed.previewMode?.autoLogin).toBe(true);
expect(parsed.previewMode?.simulatedRole).toBe('admin');
expect(parsed.previewMode?.simulatedUserName).toBe('Demo Admin');
expect(parsed.previewMode?.readOnly).toBe(true);
expect(parsed.previewMode?.expiresInSeconds).toBe(3600);
expect(parsed.previewMode?.bannerMessage).toContain('demo');
});

it('should accept context without previewMode (optional)', () => {
const parsed = KernelContextSchema.parse(validContext);
expect(parsed.previewMode).toBeUndefined();
});
});

describe('PreviewModeConfigSchema', () => {
it('should apply defaults for zero-config preview', () => {
const parsed = PreviewModeConfigSchema.parse({});
expect(parsed.autoLogin).toBe(true);
expect(parsed.simulatedRole).toBe('admin');
expect(parsed.simulatedUserName).toBe('Preview User');
expect(parsed.readOnly).toBe(false);
expect(parsed.expiresInSeconds).toBe(0);
expect(parsed.bannerMessage).toBeUndefined();
});

it('should accept all simulated roles', () => {
const roles = ['admin', 'user', 'viewer'] as const;
roles.forEach(role => {
const parsed = PreviewModeConfigSchema.parse({ simulatedRole: role });
expect(parsed.simulatedRole).toBe(role);
});
});

it('should reject invalid simulated role', () => {
expect(() => PreviewModeConfigSchema.parse({ simulatedRole: 'superadmin' })).toThrow();
});

it('should accept read-only preview for marketplace demos', () => {
const parsed = PreviewModeConfigSchema.parse({
autoLogin: true,
simulatedRole: 'viewer',
readOnly: true,
bannerMessage: 'This is a preview. Sign up to get started!',
});
expect(parsed.readOnly).toBe(true);
expect(parsed.simulatedRole).toBe('viewer');
expect(parsed.bannerMessage).toContain('preview');
});

it('should reject negative expiresInSeconds', () => {
expect(() => PreviewModeConfigSchema.parse({ expiresInSeconds: -1 })).toThrow();
});

it('should reject non-integer expiresInSeconds', () => {
expect(() => PreviewModeConfigSchema.parse({ expiresInSeconds: 1.5 })).toThrow();
});
});
Loading