Feat v0.9.0 multi-surface packages (#73)
All checks were successful
CI/CD / detect-changes (pull_request) Successful in 4s
CI/CD / test-runners (pull_request) Has been skipped
CI/CD / e2e-smoke (pull_request) Has been skipped
CI/CD / test-frontend (pull_request) Successful in 5s
CI/CD / test-go-pg (pull_request) Successful in 2m48s
CI/CD / test-sqlite (pull_request) Successful in 2m57s
CI/CD / build-and-deploy (pull_request) Successful in 1m23s
All checks were successful
CI/CD / detect-changes (pull_request) Successful in 4s
CI/CD / test-runners (pull_request) Has been skipped
CI/CD / e2e-smoke (pull_request) Has been skipped
CI/CD / test-frontend (pull_request) Successful in 5s
CI/CD / test-go-pg (pull_request) Successful in 2m48s
CI/CD / test-sqlite (pull_request) Successful in 2m57s
CI/CD / build-and-deploy (pull_request) Successful in 1m23s
Packages can declare a `surfaces` array with per-path access controls, titles, and layouts. A single package can serve a public form, an authenticated dashboard, and an admin page — each with independent access enforcement. Kernel: - Manifest validation for surfaces array (path, access, duplicates) - Auto-synthesis from legacy auth/layout for existing packages - Unified /s/:slug route tree (RegisterExtensionRoutes) dispatches between surface rendering and ext API calls - matchSurface() with Gin-style :param support and specificity ordering - evaluateAccess() for per-surface access checks - Nav filters out pending_review packages (pre-existing bug fix) Frontend: - __SURFACE_PATH__ and __SURFACE_PARAMS__ template injection - sw.navigate(path, params) for SPA-style intra-package routing - surface.navigate event + popstate handling - SDK version bumped to 0.9.0 Docs: - MULTI-SURFACE-GUIDE.md — full developer guide - PACKAGE-FORMAT.md — surfaces field reference - CHANGELOG.md, ROADMAP.md updated 22 new tests (11 manifest validation, 11 route matching/nav). Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -60,9 +60,10 @@ Only `manifest.json` is required. All other directories are optional and include
|
||||
| `description` | Short description |
|
||||
| `icon` | Emoji for sidebar/menu display |
|
||||
| `author` | Package author |
|
||||
| `route` | URL path for surfaces (e.g., `/s/my-package`) |
|
||||
| `auth` | `authenticated` or `public` |
|
||||
| `layout` | Surface layout mode (e.g., `single`) |
|
||||
| `surfaces` | Array of surface entries with per-path access, title, layout (see below) |
|
||||
| `route` | *(deprecated — use `surfaces`)* URL path for surfaces |
|
||||
| `auth` | Default access level for all surfaces: `authenticated`, `public`, `admin` |
|
||||
| `layout` | Default layout for all surfaces: `single`, `editor` |
|
||||
| `permissions` | Array of required capabilities (`db.write`, `http`, `notifications`, `secrets`, `realtime.publish`) |
|
||||
| `api_routes` | Array of `{"method": "GET", "path": "/items"}` |
|
||||
| `db_tables` | Table definitions with columns and indexes |
|
||||
@@ -75,6 +76,71 @@ Only `manifest.json` is required. All other directories are optional and include
|
||||
| `capabilities` | Environment requirements: `{"required": [...], "optional": [...]}` |
|
||||
| `schema_version` | Integer for additive schema migrations |
|
||||
|
||||
## Multi-Surface Packages
|
||||
|
||||
A package can serve multiple pages, each with its own path, access level,
|
||||
title, and layout. Declare a `surfaces` array in the manifest:
|
||||
|
||||
```json
|
||||
{
|
||||
"id": "bug-tracker",
|
||||
"title": "Bug Tracker",
|
||||
"type": "full",
|
||||
"auth": "authenticated",
|
||||
"layout": "single",
|
||||
"surfaces": [
|
||||
{ "path": "/", "title": "Dashboard" },
|
||||
{ "path": "/submit", "access": "public", "title": "Report a Bug" },
|
||||
{ "path": "/:id", "title": "Bug Detail", "nav": false },
|
||||
{ "path": "/admin", "access": "admin", "title": "Settings", "nav": false }
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
### Surface Entry Fields
|
||||
|
||||
| Field | Type | Default | Description |
|
||||
|----------|--------|----------------|-------------|
|
||||
| `path` | string | **required** | Relative to `/s/{pkg-id}`. Supports `:param` segments. |
|
||||
| `access` | string | package `auth` | `public`, `authenticated`, `admin`, `group:{name}`. |
|
||||
| `title` | string | package `title`| Human label. Used in nav if `nav: true`. |
|
||||
| `layout` | string | package `layout`| `single`, `editor`, or future layouts. |
|
||||
| `nav` | bool | see rules | Show in sidebar navigation. |
|
||||
|
||||
### Nav Visibility Rules
|
||||
|
||||
- `path: "/"` defaults to `nav: true` (primary entry point)
|
||||
- All others default to `nav: false` (sub-pages)
|
||||
- Explicit `"nav": true` overrides — a package can put multiple entries in nav
|
||||
- The sidebar links to the first surface with `nav: true`
|
||||
|
||||
### Backward Compatibility
|
||||
|
||||
If `surfaces` is absent, the kernel synthesizes one entry from the legacy
|
||||
`auth` and `layout` fields. No existing packages break.
|
||||
|
||||
### Client-Side Navigation
|
||||
|
||||
Within a multi-surface package, use `sw.navigate()` for SPA-style routing:
|
||||
|
||||
```js
|
||||
const path = window.__SURFACE_PATH__ || '/';
|
||||
const params = window.__SURFACE_PARAMS__ || {};
|
||||
|
||||
if (path === '/') renderDashboard();
|
||||
if (path === '/submit') renderSubmitForm();
|
||||
if (path === '/:id') renderDetail(params.id);
|
||||
|
||||
// Navigate to another surface within the package
|
||||
sw.navigate('/submit');
|
||||
sw.navigate('/:id', { id: 'bug-42' });
|
||||
|
||||
// Listen for navigation events (including back/forward)
|
||||
sw.on('surface.navigate', ({ path, params }) => {
|
||||
renderView(path, params);
|
||||
});
|
||||
```
|
||||
|
||||
## Composability: Slots and Contributions
|
||||
|
||||
Packages compose with each other through named UI injection points (**slots**) and **contributions**.
|
||||
|
||||
Reference in New Issue
Block a user