@notis_ai/cli 0.2.0-beta.157.1 → 0.2.0-beta.158.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/agent-hooks/notis-agent-hook.mjs +8296 -7912
- package/dist/base-skills/notis-apps/SKILL.md +21 -483
- package/dist/base-skills/notis-apps/references/architecture.md +147 -0
- package/dist/base-skills/notis-apps/references/design.md +154 -0
- package/dist/base-skills/notis-apps/references/release.md +93 -0
- package/dist/base-skills/notis-apps/references/sdk.md +60 -0
- package/dist/base-skills/notis-apps/references/troubleshooting.md +26 -0
- package/dist/base-skills/notis-cli/SKILL.md +19 -200
- package/dist/base-skills/notis-cli/references/app-delivery.md +18 -0
- package/dist/base-skills/notis-cli/references/native-databases.md +20 -0
- package/dist/base-skills/notis-cli/references/tool-examples.md +56 -0
- package/dist/base-skills/notis-cli/references/troubleshooting.md +39 -0
- package/dist/base-skills/notis-query/SKILL.md +13 -651
- package/dist/base-skills/notis-query/references/database-discovery.md +59 -0
- package/dist/base-skills/notis-query/references/documents.md +50 -0
- package/dist/base-skills/notis-query/references/query.md +543 -0
- package/dist/skill-sync/index.js +24 -7
- package/dist/skill-sync/index.js.map +4 -4
- package/dist/skill-sync-worker.mjs +2989 -0
- package/package.json +1 -1
- package/src/cli.js +4 -0
- package/src/command-specs/diagnostics.js +37 -0
- package/src/command-specs/skills.js +23 -5
- package/src/runtime/profiles.js +5 -2
- package/src/runtime/skill-sync/cloud-client.ts +2 -1
- package/src/runtime/skill-sync/index.ts +24 -6
- package/src/runtime/skill-sync/types.ts +2 -0
- package/src/runtime/skill-sync-service.js +109 -0
- package/src/skill-sync-worker-entry.js +2 -0
- package/src/skill-sync-worker.js +50 -0
- package/template/packages/sdk/src/components/MultiSelectActionBar.tsx +36 -7
- package/template/packages/sdk/src/components/MultiSelectCheckbox.tsx +3 -1
- package/template/packages/sdk/src/hooks/useCollectionInteractions.ts +138 -28
- package/template/packages/sdk/src/hooks/useLongPressSelection.ts +79 -0
- package/template/packages/sdk/src/hooks/useMultiSelect.ts +2 -8
- package/template/packages/sdk/src/index.ts +3 -0
- package/template/packages/sdk/src/interactions/actions.ts +14 -1
- package/template/packages/sdk/src/interactions/shortcuts.tsx +79 -19
- package/template/packages/sdk/src/interactions/visibility.ts +13 -0
- package/template/packages/sdk/src/interactions.ts +3 -0
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
## How Apps Are Built
|
|
2
|
+
|
|
3
|
+
All Notis apps are built using the Notis CLI, either locally in a repo workspace or inside a Vercel Sandbox. The platform contract is the same in both cases:
|
|
4
|
+
- the app is a Vite + React project
|
|
5
|
+
- the app uses `@notis/sdk`
|
|
6
|
+
- the app is packaged as an ES module bundle
|
|
7
|
+
- the portal renders it as a React component inside the portal's React tree
|
|
8
|
+
|
|
9
|
+
## Architecture
|
|
10
|
+
|
|
11
|
+
```
|
|
12
|
+
Notis CLI (local workspace or Vercel Sandbox)
|
|
13
|
+
-> Vite + React project with @notis/sdk
|
|
14
|
+
-> notis apps init / build / verify / create / link / pull / deploy
|
|
15
|
+
-> ES module bundle (app.js + app.css) + manifest
|
|
16
|
+
-> Portal renders as React component with real tools/databases
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
`deploy` updates the linked installed app for the current account or team. After the user explicitly confirms the App Details page is ready, `apps publish --confirm-ready` submits that deployed version to the Team or Public Store review flow.
|
|
20
|
+
|
|
21
|
+
### Key Components
|
|
22
|
+
|
|
23
|
+
1. **@notis/sdk** (`packages/sdk/`) -- SDK for app developers
|
|
24
|
+
- `@notis/sdk` -- NotisProvider, runtime hooks, editors, selection helpers, and shortcut primitives
|
|
25
|
+
- `@notis/sdk/interactions` -- headless collection actions and interaction types
|
|
26
|
+
- `@notis/sdk/config` -- `defineNotisApp()` for notis.config.ts
|
|
27
|
+
- `@notis/sdk/vite` -- `notisViteConfig()` for vite.config.ts
|
|
28
|
+
- `@notis/sdk/styles.css` -- shadow-safe app shell styles and base app-surface classes
|
|
29
|
+
|
|
30
|
+
2. **CLI** (`packages/cli/src/command-specs/apps.js`) -- release delivery uses init, build, verify, create, deploy, link, pull, doctor, and list
|
|
31
|
+
|
|
32
|
+
3. **Server** (`server/routers/portal_views/`) -- Returns signed bundle URLs, proxies tool calls
|
|
33
|
+
|
|
34
|
+
4. **Portal** (`portal/src/components/apps/`) -- Renders app bundles as React components via AppViewRenderer
|
|
35
|
+
|
|
36
|
+
### Runtime Bridge
|
|
37
|
+
|
|
38
|
+
Apps communicate with the platform through the `NotisRuntime` interface, provided by the portal via React context:
|
|
39
|
+
|
|
40
|
+
- **Portal**: the portal creates a real `NotisRuntime` and passes it as a prop to `NotisProvider`. All calls go to `/portal_views/runtime_query` via fetch with the user's JWT.
|
|
41
|
+
- The portal mounts the app inside a shadow-scoped content surface and injects the runtime before app mount. There is no supported window-global runtime fallback.
|
|
42
|
+
|
|
43
|
+
App code never accesses the runtime directly -- it uses SDK hooks (`useTool`, `useTools`, `useNotis`, etc.) which read from the `NotisProvider` context.
|
|
44
|
+
|
|
45
|
+
## Manifest Format
|
|
46
|
+
|
|
47
|
+
Generated by `npx --package @notis_ai/cli@latest -- notis apps build` at `.notis/output/manifest.json`:
|
|
48
|
+
|
|
49
|
+
```json
|
|
50
|
+
{
|
|
51
|
+
"version": 1,
|
|
52
|
+
"spec_version": 4,
|
|
53
|
+
"app": { "name": "My App", "slug": "my-app", "title": "My App", "description": "...", "icon": "phosphor:..." },
|
|
54
|
+
"routes": [
|
|
55
|
+
{
|
|
56
|
+
"path": "/",
|
|
57
|
+
"slug": "index",
|
|
58
|
+
"name": "Dashboard",
|
|
59
|
+
"icon": "phosphor:squares-four",
|
|
60
|
+
"default": true,
|
|
61
|
+
"export_name": "index",
|
|
62
|
+
"collection": null
|
|
63
|
+
}
|
|
64
|
+
],
|
|
65
|
+
"bundle": {
|
|
66
|
+
"js": "bundle/app.js",
|
|
67
|
+
"css": "bundle/app.css"
|
|
68
|
+
},
|
|
69
|
+
"databases": ["tasks", { "slug": "templates", "seed_documents": true }],
|
|
70
|
+
"tools": ["LOCAL_NOTIS_DATABASE_QUERY"]
|
|
71
|
+
}
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
Use canonical `notis-*` tool names for explicit app tool declarations. App-specific TypeScript shapes for tool arguments and results live in the app code; the SDK exposes the generic `useTool<TArgs, TResult>()` hook instead of database-specific tool hooks.
|
|
75
|
+
|
|
76
|
+
Database strings package schema only. The object form shown above opts that database into copying its current rows as Store starter content. Use it sparingly and only for non-personal fixtures/templates every installer is meant to receive.
|
|
77
|
+
|
|
78
|
+
For a read-only database catalog app, declare `["LOCAL_NOTIS_DATABASE_LIST_DATABASES", "LOCAL_NOTIS_DATABASE_GET_DATABASE"]`. Use the list tool for the left/catalog pane and the get tool for the selected database detail pane.
|
|
79
|
+
|
|
80
|
+
## Database Schema
|
|
81
|
+
|
|
82
|
+
### apps table
|
|
83
|
+
|
|
84
|
+
| Column | Type | Description |
|
|
85
|
+
|---|---|---|
|
|
86
|
+
| id | uuid PK | App ID |
|
|
87
|
+
| user_id | uuid FK | Owner |
|
|
88
|
+
| team_id | uuid FK | Team (nullable) |
|
|
89
|
+
| name | text | Display name |
|
|
90
|
+
| slug | text UNIQUE | URL slug |
|
|
91
|
+
| description | text | App description |
|
|
92
|
+
| icon | text | Phosphor icon (e.g. "phosphor:list") |
|
|
93
|
+
| status | text | draft, active, archived |
|
|
94
|
+
| visibility | text | private, team |
|
|
95
|
+
| manifest | jsonb | Latest deployed manifest |
|
|
96
|
+
| current_version | integer | Version counter |
|
|
97
|
+
| source_listing_id | uuid FK | Source App Store listing for installed store apps; cleared when submitted as a derivative |
|
|
98
|
+
| installed_snapshot | jsonb | Store-installed baseline used for update/reset comparison |
|
|
99
|
+
| customization_overlay | jsonb | User changes over the installed store baseline |
|
|
100
|
+
| update_status | text | up_to_date, update_available, needs_resolution, update_failed |
|
|
101
|
+
| bundled_automation_ids | uuid[] | Linked automations |
|
|
102
|
+
| bundled_skill_ids | uuid[] | Linked skills |
|
|
103
|
+
|
|
104
|
+
### databases ownership
|
|
105
|
+
|
|
106
|
+
Every row in the `databases` table carries `owner_app_id` (uuid FK to
|
|
107
|
+
`apps.id`, `ON DELETE CASCADE`): a database belongs to exactly one app, and
|
|
108
|
+
deleting the app deletes its databases and their documents (`documents` cascade
|
|
109
|
+
from `databases`). Install, resource preparation, and Store updates stamp
|
|
110
|
+
`owner_app_id` automatically; standalone creation requires the `app` argument.
|
|
111
|
+
|
|
112
|
+
### Storage (Supabase)
|
|
113
|
+
|
|
114
|
+
Files stored in `app-code` bucket at `{app_id}/v{version}/`:
|
|
115
|
+
- `manifest.json`
|
|
116
|
+
- `bundle/app.js`
|
|
117
|
+
- `bundle/app.css`
|
|
118
|
+
|
|
119
|
+
Editable source snapshots are stored in the private `app-source` bucket at
|
|
120
|
+
`{app_id}/v{version}/`. Portal App Store listing screenshots are uploaded to
|
|
121
|
+
the public `app-listing-assets` bucket before submission.
|
|
122
|
+
|
|
123
|
+
### Related tables
|
|
124
|
+
|
|
125
|
+
- **databases** -- Apps reference these rows by slug. Schema lives on the database row (`schema_metadata` / `original_fields`), not in the app manifest.
|
|
126
|
+
- **documents** -- `database_id` links to databases. Properties in `properties` jsonb.
|
|
127
|
+
- **app_store_listings** -- Snapshots for publishing to the app store.
|
|
128
|
+
- **app_submissions** -- Portal review submissions keyed to an app source version and registry slug.
|
|
129
|
+
|
|
130
|
+
## Server Endpoints
|
|
131
|
+
|
|
132
|
+
| Endpoint | Method | Purpose |
|
|
133
|
+
|---|---|---|
|
|
134
|
+
| `/portal_views/get` | GET | Route detail + runtime descriptor with signed bundle URLs |
|
|
135
|
+
| `/portal_views/runtime_query` | POST | Proxy tool calls and DB operations |
|
|
136
|
+
| `/portal_views/collection_items` | GET | List collection items |
|
|
137
|
+
| `/portal_views/collection_tree` | GET | List normalized collection tree nodes for a tree sidebar route |
|
|
138
|
+
| `/portal_views/collection_tree/create` | POST | Create a root or child collection row from the sidebar |
|
|
139
|
+
| `/portal_views/collection_tree/rename` | POST | Rename a collection tree item inline |
|
|
140
|
+
| `/portal_views/collection_tree/delete` | POST | Delete a collection tree item from the sidebar |
|
|
141
|
+
| `/portal_apps/list` | GET | List apps |
|
|
142
|
+
| `/portal_apps/get` | GET | Get app detail |
|
|
143
|
+
| `/portal_apps/publish` | POST | Submit a deployed app source snapshot for public store review |
|
|
144
|
+
| `/portal_apps/listing_assets/upload` | POST | Legacy pre-manifest screenshot upload; do not use for current manifest-media workflows |
|
|
145
|
+
| `/portal_apps/submissions` | GET/PATCH | List or edit App Store submissions |
|
|
146
|
+
| `/portal_apps/submissions/withdraw` | POST | Close a pending App Store submission |
|
|
147
|
+
| `/cli_tools` | POST | CLI tool execution (save_app_files, create_app, etc.) |
|
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
## Building an App
|
|
2
|
+
|
|
3
|
+
### Step 1: Define the config
|
|
4
|
+
|
|
5
|
+
Create `notis.config.ts` with:
|
|
6
|
+
- **name** -- Stable machine identity in lowercase kebab-case, such as `link-building`; do not use display casing here
|
|
7
|
+
- **title** -- Human-facing app name with deliberate casing, such as `Link Building`; preserve brands and acronyms exactly
|
|
8
|
+
- **databases** -- Slug references to existing Notis databases
|
|
9
|
+
- **routes** -- Route-first sidebar entries with explicit `slug`, optional `parentSlug`, and optional `collection.sidebar` tree config
|
|
10
|
+
- **tools** -- Final tool names the app can call at runtime. Use the shared discovery flow (`COMPOSIO_SEARCH_TOOLS`, then `COMPOSIO_GET_TOOL_SCHEMAS`) while building the app, and copy the returned final names into this list. Examples include `LOCAL_NOTIS_DATABASE_QUERY`, `LOCAL_NOTIS_MONID_RUN`, `GMAIL_SEND_EMAIL`, `LOCAL_POSTFORME_CREATE_POST`, and `LOCAL_MCP_<SERVER>_<TOOL>`. App code calls each declared name directly through `useTool`; it does not wrap provider or MCP calls in `COMPOSIO_MULTI_EXECUTE_TOOL`. Access stays scoped to the signed-in user's own connections, native database tools stay scoped to the app's databases unless `capabilities.workspaceDatabases: 'read'` is granted, and metered tools use the CLI-equivalent credit-cap and fail-closed usage-billing path.
|
|
11
|
+
|
|
12
|
+
For collection-backed sidebars, use the route schema directly:
|
|
13
|
+
|
|
14
|
+
```ts
|
|
15
|
+
routes: [
|
|
16
|
+
{
|
|
17
|
+
path: '/',
|
|
18
|
+
slug: 'notes',
|
|
19
|
+
name: 'Notes',
|
|
20
|
+
icon: 'phosphor:note-pencil',
|
|
21
|
+
default: true,
|
|
22
|
+
collection: {
|
|
23
|
+
database: 'notes',
|
|
24
|
+
titleProperty: 'Title',
|
|
25
|
+
parentProperty: 'Parent note',
|
|
26
|
+
sidebar: {
|
|
27
|
+
mode: 'tree',
|
|
28
|
+
allowCreate: true,
|
|
29
|
+
},
|
|
30
|
+
},
|
|
31
|
+
},
|
|
32
|
+
]
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
Use the same page template for the root Notes route and collection/sub-collection detail states. The portal sidebar injects live collection items under the static route row when `collection.sidebar.mode === 'tree'`.
|
|
36
|
+
|
|
37
|
+
For arbitrary app-owned resources that are not Notis collection rows, set `resourceDeepLinks: true` on the route. Read the decoded `?resource=` identifier from `useNotis().resourceId`, and link between routes with `toRoute('/inbox', { resourceId })`. Keep collection links on `?item=`. Publish external preview/source links as the resource `url`; the host separately supplies the exact Notis review link as `active_resource.view_url` for opted-in routes. Handle missing or deleted identifiers with a safe view-level fallback.
|
|
38
|
+
|
|
39
|
+
### Step 2: Build pages
|
|
40
|
+
|
|
41
|
+
Standard React pages in `app/`. Use generic SDK tool hooks for data and build on top of the scaffolded flat components and portal shell classes (`notis-app-shell` for ordinary pages, `notis-app-split` for list-plus-detail pages, `notis-app-surface` for a flat panel):
|
|
42
|
+
|
|
43
|
+
```tsx
|
|
44
|
+
import { useDocuments, ViewSkeleton } from '@notis/sdk';
|
|
45
|
+
import { Card } from '@/components/ui/card';
|
|
46
|
+
|
|
47
|
+
export default function TasksPage() {
|
|
48
|
+
const tasks = useDocuments('tasks', { pageSize: 25 });
|
|
49
|
+
return <section className="space-y-4 p-6">
|
|
50
|
+
<h1 className="text-xl font-semibold">Tasks</h1>
|
|
51
|
+
{tasks.error && <p role="alert">{tasks.error.message} <button onClick={tasks.refetch}>Retry</button></p>}
|
|
52
|
+
{tasks.loading ? <ViewSkeleton variant="table" rows={5} /> : tasks.hasData ? (
|
|
53
|
+
tasks.documents.length ? tasks.documents.map((task) => (
|
|
54
|
+
<Card key={task.id} className="p-4"><h2>{task.title || 'Untitled'}</h2></Card>
|
|
55
|
+
)) : <p>No tasks yet.</p>
|
|
56
|
+
) : null}
|
|
57
|
+
</section>;
|
|
58
|
+
}
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
### Instant-view loading contract (required)
|
|
62
|
+
|
|
63
|
+
Build a client-side, multi-route app with one persistent `app/layout.tsx` shell. Navigate with `useNotisNavigation`; never use a document reload for an internal route. “SPA” means preserving that shell and reusing reads, **not** mounting every page or fetching every database at startup.
|
|
64
|
+
|
|
65
|
+
| State | Required UI |
|
|
66
|
+
| --- | --- |
|
|
67
|
+
| First read, no successful data | Keep headings/navigation/layout visible; use content-shaped skeletons only in missing regions. No page spinner or whole-page `Loading...`. |
|
|
68
|
+
| Cached view / successful empty result | Render synchronously from the shared SDK cache. Empty results are real cached results. |
|
|
69
|
+
| Background refresh | Keep current content and selection. Never replace populated content with a skeleton; do not drive the top-bar spinner from mount/refetch state. |
|
|
70
|
+
| Explicit Save / Upload / submitted search | Progress belongs in that button or affected section. Disable only the conflicting action. |
|
|
71
|
+
| Failed read | Show a scoped error and Retry; keep usable cached content. Never show an empty-state message before `hasData` is true. |
|
|
72
|
+
|
|
73
|
+
Use `useDocuments`, `useDocument`, `useDatabaseSchema`, and `useDatabaseSubscription` for native reads. `loading` means no first successful response; `isFetching` includes silent refresh. Do not copy their data into mount-only state, clear rows on error, or gate the entire app on `isFetching`.
|
|
74
|
+
|
|
75
|
+
For another **explicitly identified idempotent read**, use `useToolQuery<Result>(toolName, exactArguments, { readOnly: true })`, or `useQuery(keyArray, readCallback, { readOnly: true })`. Include every filter, selected resource, pagination option, and other input in the key. Call tools within a custom read with `{ readOnly: true, dedupe: true }`; the same SQL/shell tool can also perform writes, so never mark a whole toolkit read-only. Leave mutations as ordinary `useTool` actions.
|
|
76
|
+
|
|
77
|
+
`useQueryClient().prefetch(keyArray, readCallback, { readOnly: true })` prepares small known reads after the current view has rendered or on hover/focus. It shares the host's two-request speculative budget. Match the exact foreground query key. Never prefetch a mutation, login/polling action, provider sweep, `fetchAll` query, or an aggregate that fans out into more requests. Do not invent tool names to prepare a view. Older hosts safely fall back to uncached hook-local reads and skip prefetch.
|
|
78
|
+
|
|
79
|
+
Caches belong to the host's in-memory account/environment/app/version/effective-permission scope. Do not add module-global or `localStorage` caches of user data. Writes and realtime events invalidate reads; logout, access loss, and updates retire scopes. Preserve the last successful snapshot on an ordinary network failure.
|
|
80
|
+
|
|
81
|
+
### Discovering database schema
|
|
82
|
+
|
|
83
|
+
Before writing app code, inspect the database schema to know what properties exist:
|
|
84
|
+
|
|
85
|
+
```bash
|
|
86
|
+
npx --package @notis_ai/cli@latest -- notis tools search "list Notis databases"
|
|
87
|
+
npx --package @notis_ai/cli@latest -- notis tools exec LOCAL_NOTIS_DATABASE_LIST_DATABASES --arguments '{}'
|
|
88
|
+
npx --package @notis_ai/cli@latest -- notis tools exec LOCAL_NOTIS_DATABASE_GET_DATABASE --arguments '{"database_slug":"social_media_calendar"}'
|
|
89
|
+
npx --package @notis_ai/cli@latest -- notis tools exec LOCAL_NOTIS_DATABASE_QUERY --arguments '{"database_id":"social-media-calendar-db-id","query":{"page_size":1}}'
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
Prefer the database `id` returned by `LOCAL_NOTIS_DATABASE_LIST_DATABASES` or `LOCAL_NOTIS_DATABASE_GET_DATABASE` when calling `LOCAL_NOTIS_DATABASE_QUERY`; use `database_slug` only as a fallback.
|
|
93
|
+
|
|
94
|
+
Use `LOCAL_NOTIS_DATABASE_GET_DATABASE` through `useTool` when an app needs schema detail at runtime. Keep database-specific result and property helper types inside the app code.
|
|
95
|
+
For document writes, declare the generated canonical tool for the target database, such as `LOCAL_NOTIS_DATABASE_UPSERT_TASKS`, and call it through `useTool`. Pass flat property values; the server wraps them:
|
|
96
|
+
|
|
97
|
+
```tsx
|
|
98
|
+
const upsertTask = useTool<Record<string, unknown>, { document?: { id: string } }>('LOCAL_NOTIS_DATABASE_UPSERT_TASKS');
|
|
99
|
+
|
|
100
|
+
await upsertTask.call({
|
|
101
|
+
title: 'My Task',
|
|
102
|
+
Status: 'Todo',
|
|
103
|
+
Priority: 'P1',
|
|
104
|
+
Due: '2025-04-01',
|
|
105
|
+
Done: false,
|
|
106
|
+
Count: 5,
|
|
107
|
+
});
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
Do NOT pass Notion-style wrappers (`{select: {name: "Todo"}}`) when upserting.
|
|
111
|
+
|
|
112
|
+
### Design bar (enforced)
|
|
113
|
+
|
|
114
|
+
Every page must read as a native, flat Notis page. `npx --package @notis_ai/cli@latest -- notis apps build` and the deploy endpoint fail on the banned patterns below with the exact file and line; the only override is an inline `// notis-design-allow: <rule-id> <reason>` comment on the line before (reason required, at least 12 characters). Do not work around a failure by moving the markup elsewhere; fix it.
|
|
115
|
+
|
|
116
|
+
Banned in `app/` and `components/` (form controls in `components/ui/{input,textarea,checkbox,switch,button}.tsx` are exempt):
|
|
117
|
+
|
|
118
|
+
- Four-side `border` boxes, `border-dashed`, `divide-*`, `<hr>`, thick `border-l-2` bars, `ring-*` as a box or selection indicator (`focus-visible:ring-2` on controls is fine).
|
|
119
|
+
- `shadow-*` on panels, tiles, rows, or bubbles. Only a floating popover or menu may use `shadow-lg` together with `bg-popover`.
|
|
120
|
+
- Tailwind palette hues (`emerald-500`, `slate-200`, ...), hex colors, gradients, `backdrop-blur`, `font-serif`.
|
|
121
|
+
- Uppercase `tracking-wide` eyebrows and marketing headlines. Page titles are plain nouns matching the route ("Dashboard", "Meetings").
|
|
122
|
+
- Text below 12px (`text-[11px]`); use `text-xs` at minimum and `text-sm` for body.
|
|
123
|
+
- `Badge variant="outline"`, raw `<select>`, in-app search inputs, duplicate sidebars, untouched scaffold placeholder copy.
|
|
124
|
+
- Loading text ("Loading...") or whole-page spinners. Keep headings visible and render `Skeleton` / `ViewSkeleton` from `@notis/sdk` only in the missing region (see the Instant-view contract).
|
|
125
|
+
|
|
126
|
+
Use instead:
|
|
127
|
+
|
|
128
|
+
- `Card` from the scaffold: a flat `bg-muted` panel that becomes `bg-background` when nested. Page sections can also be plain `h2` + content with `space-y-8`.
|
|
129
|
+
- `.list-row` / `.list-row-selected` from `@notis/sdk/styles.css` for rows and table bodies (tinted on mobile, transparent with hover tint on desktop, selection by tint). Tables are flat on the page: `text-xs` muted header, `text-sm` rows, no wrapping panel.
|
|
130
|
+
- Stats as bare figures: `text-xs` label over `text-2xl font-semibold tabular-nums`. Tiles (`rounded-2xl bg-muted p-5`) only when they are the page's single grouping device.
|
|
131
|
+
- `PageHeading` for the header, `NativeSelect` for filters, `Badge` variants `default | secondary | destructive`, tokens only (`text-foreground`, `text-muted-foreground`, `text-primary`, `bg-primary/10`, `text-destructive`, `bg-destructive/10`), `tabular-nums` on numbers, `min-w-0` on every grid item that can hold long text.
|
|
132
|
+
- One hairline (`border-t` / `border-b border-border`) between major sections or large list entries is the only allowed line.
|
|
133
|
+
- List-plus-detail pages are full-bleed: `notis-app-split` with `notis-app-pane-list` (tinted, one `border-r` hairline, fixed width on desktop, stacked on mobile) and `notis-app-pane-detail` (`bg-background`), never the centered `notis-app-shell`.
|
|
134
|
+
- Respect the portal theme in both modes. Never hardcode dark mode or an app palette.
|
|
135
|
+
- For Notes-style apps, the folder tree belongs to the portal sidebar when configured via `collection.sidebar`. The page content should complement that chrome, not duplicate or replace it.
|
|
136
|
+
- Do not render any search input inside the app (in-page search rails, "Ask Notis…" pills, command-palette-style bars, etc.). The portal already owns the top-bar search field. Wire your view to it with `useTopBarSearch({ value, onChange, placeholder, onSubmit })` from `@notis/sdk` and let the page filter or refetch on the values it receives. Use its `setLoading` only for an explicit submitted search, never initial view loading or background refresh.
|
|
137
|
+
|
|
138
|
+
### Sidebar invariants
|
|
139
|
+
|
|
140
|
+
- When a user asks for folders, sections, or hierarchy in the app sidebar, express that through `routes` and `collection.sidebar` in `notis.config.ts`.
|
|
141
|
+
- Treat an existing collection-tree sidebar as a locked structural requirement unless the user explicitly asks to change navigation architecture.
|
|
142
|
+
- If the sidebar appears missing for the installed app, do not silently redesign around it. Preserve the manifest contract, call out the discrepancy, and treat it as a portal/runtime bug.
|
|
143
|
+
|
|
144
|
+
### Step 3: Root layout
|
|
145
|
+
|
|
146
|
+
```tsx
|
|
147
|
+
import { NotisProvider } from '@notis/sdk';
|
|
148
|
+
import '@notis/sdk/styles.css';
|
|
149
|
+
import './globals.css';
|
|
150
|
+
|
|
151
|
+
export default function AppShell({ children }: { children: React.ReactNode }) {
|
|
152
|
+
return <NotisProvider>{children}</NotisProvider>;
|
|
153
|
+
}
|
|
154
|
+
```
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
## Release-only delivery
|
|
2
|
+
|
|
3
|
+
Workspace runs released app versions only. Local and cloud agents use the same workflow.
|
|
4
|
+
A request to create or edit app source authorizes updating that app in Workspace after checks pass.
|
|
5
|
+
Explicit read-only, preview-only or no-deploy requests stop at local artifacts and checks: no remote
|
|
6
|
+
app/resource creation or mutation, Workspace preview, deployment or live verification. Store
|
|
7
|
+
publication always needs separate explicit approval.
|
|
8
|
+
|
|
9
|
+
1. Inspect the effective CLI profile and the exact app's current version with `apps list --json`.
|
|
10
|
+
For an existing released app, preserve local edits and pull its exact app ID into the intended
|
|
11
|
+
directory. Retain the profile/app link, deployment version and revision. An unreleased container
|
|
12
|
+
has no source to pull: recover its original local source and edits, or scaffold locally only if
|
|
13
|
+
that source cannot be recovered. Confirm its exact ID, edit permission and personal/team scope,
|
|
14
|
+
then run `apps link <app-id> <source-directory> --expected-version 0` to resume that same container.
|
|
15
|
+
If a release has appeared, preserve local source separately, pull the current release into a fresh
|
|
16
|
+
directory and reapply the intended edits. The link guard compares against the same remote read
|
|
17
|
+
whose version/revision it saves; deploy still rejects a release racing after that read. Do not pull
|
|
18
|
+
missing source or create another remote app to recover a failed first release.
|
|
19
|
+
2. Scaffold a new app locally, or edit the pulled source. Run `apps build` and automated
|
|
20
|
+
`apps verify` before new remote creation. Missing browser tooling or failed checks blocks delivery;
|
|
21
|
+
printed URLs and `--no-browser` are not passing verification. Install browser tooling with
|
|
22
|
+
`npm exec --yes --package agent-browser@latest -- agent-browser install`; if needed run
|
|
23
|
+
`npx --yes --package @notis_ai/cli@latest --package agent-browser@latest -- notis apps verify`.
|
|
24
|
+
3. Reconcile `apps list --json` and the exact intended name/slug, edit permission and personal/team
|
|
25
|
+
scope. Default to personal only when no team was requested. Reuse a matching editable identity;
|
|
26
|
+
stop on ambiguous matches or conflicting identity/scope. Create only when none exists, using
|
|
27
|
+
`apps create "<exact app name>" .` (or `--team-id <verified team ID>`). Read back the same ID.
|
|
28
|
+
A failed first release leaves a container: reuse it, never duplicate or automatically delete it.
|
|
29
|
+
4. Compare existing app-owned schemas. Create only necessary missing databases against that exact
|
|
30
|
+
app ID. Change existing schemas by verified database ID and ownership, and only with backward-
|
|
31
|
+
compatible changes before release. Read back each change. Breaking changes need separate coordination.
|
|
32
|
+
Ordinary note/record edits and existing resource editors remain immediate.
|
|
33
|
+
5. Run `apps deploy` against the same linked app. It builds, verifies a frozen source/artifact
|
|
34
|
+
snapshot with stubs, then sends that snapshot to the backend. `--skip-build` accepts only unchanged,
|
|
35
|
+
valid output and still verifies. Do not bypass the backend or create implicitly on deploy.
|
|
36
|
+
6. Read back the exact installed app ID, integer version and Portal URL with `apps list --json`.
|
|
37
|
+
Run `apps verify --mode live` and open the installed app in the actual Portal for surface proof.
|
|
38
|
+
A live harness check alone does not prove the deployed bundle rendered in Portal.
|
|
39
|
+
7. Report **failed before activation**, **deployed but unverified**, or **outcome unknown** accurately.
|
|
40
|
+
Never blindly replay an uncertain create/deploy response; reconcile its exact identity/version first.
|
|
41
|
+
`apps publish --confirm-ready` is **Publish to Store**, separately approved and listing-gated.
|
|
42
|
+
Workspace delivery is **Update app**, with no Store screenshot/readiness requirement.
|
|
43
|
+
|
|
44
|
+
### Restore historical source as a new release
|
|
45
|
+
|
|
46
|
+
Pull the current release into a fresh checkout first. Retrieve historical source into a different
|
|
47
|
+
folder (`apps pull <id> <historical-dir> --source-version <n>`). Replace source in the current checkout
|
|
48
|
+
without replacing its `.notis` profile/app link or deployment base. Update `package.json`'s
|
|
49
|
+
`notisAppVersion`, check compatibility with current resources, build, verify and deploy as a new
|
|
50
|
+
release. Preserve app/database/skill IDs. Never decrement the deployment counter, rewrite snapshots,
|
|
51
|
+
or claim to undo user data or external actions.
|
|
52
|
+
|
|
53
|
+
## Workflow
|
|
54
|
+
|
|
55
|
+
Follow the Release-only delivery steps in this guide, subject to the entrypoint’s user/repository policy precedence. Use `apps scaffolds list` to discover public Store starting
|
|
56
|
+
points, `apps init` to scaffold locally, and `apps pull` for existing source. App file operations go
|
|
57
|
+
through the CLI, never raw storage/database writes. Run all Notis commands through NPX.
|
|
58
|
+
|
|
59
|
+
## Testing
|
|
60
|
+
|
|
61
|
+
Build and automated stub verification precede release. `verify` and `screenshot` start temporary,
|
|
62
|
+
explicit test servers only: no folder discovery, watchers, Desktop registration, persistent roots,
|
|
63
|
+
consumer leases or Workspace mounting. They close server/browser resources at completion or interruption.
|
|
64
|
+
Build also enforces the design bar and refreshes the app's embedded SDK copy. Automated verification
|
|
65
|
+
checks every route at desktop (1280px) and phone (390px) widths, including nested boxes, text below
|
|
66
|
+
12px, lingering loading placeholders and horizontal overflow. Standalone verification writes a
|
|
67
|
+
local diagnostic report; deploy always verifies its own frozen snapshot, with no stamp or environment bypass.
|
|
68
|
+
After release, verify live runtime integration and open the installed bundle in Portal. Source edits
|
|
69
|
+
and Desktop restarts cannot change the running version.
|
|
70
|
+
|
|
71
|
+
### Screenshots
|
|
72
|
+
|
|
73
|
+
`apps screenshot` supports declared screenshot scenarios and stub fixtures. A scenario can set
|
|
74
|
+
`theme: 'dark'`; use `--raw` for uncomposited captures. Store screenshots and listing readiness
|
|
75
|
+
are required only for Publish to Store, never for Update app.
|
|
76
|
+
|
|
77
|
+
### Headless harness verification
|
|
78
|
+
|
|
79
|
+
Run `npx --package @notis_ai/cli@latest -- notis apps verify` after `npx --package @notis_ai/cli@latest -- notis apps build`. Use `--mode live` after deploy to exercise the real `/portal_views/runtime_query` with the CLI JWT instead of stub data; live mode also fails a route whose runtime calls all errored, which a well-behaved error state would otherwise hide. In a hosted sandbox, put `agent-browser` on the verification process's `PATH` with the combined-package command above. `--no-browser` only prints URLs for manual triage and does not satisfy the automated deployment gate.
|
|
80
|
+
|
|
81
|
+
#### What the harness catches that `npx --package @notis_ai/cli@latest -- notis apps build` does not
|
|
82
|
+
|
|
83
|
+
- Hooks that mount but throw on first read (`useTool` called with the wrong tool name or argument shape, accessing nested props that are undefined).
|
|
84
|
+
- Runtime database queries whose slug is not declared by the app, and collection routes that never query their configured collection database. Declared databases may also support automations or agent workflows, so ordinary routes do not need to query every app database.
|
|
85
|
+
- Tool names referenced by hooks but missing from `notis.config.ts -> tools`.
|
|
86
|
+
- Suspense / async boundaries that never resolve because a runtime stub returned the wrong shape.
|
|
87
|
+
- Render-time exceptions that the portal would surface as the `View crashed` error boundary.
|
|
88
|
+
|
|
89
|
+
#### What the harness does not catch
|
|
90
|
+
|
|
91
|
+
- Bugs that only manifest with real backend data (auth-scoped filters, RLS, malformed prod records). For those, swap the stub runtime for a real one that posts to `/portal_views/runtime_query` with a JWT.
|
|
92
|
+
- Pixel-level visual regressions beyond the automated design checks (the harness does flag nested boxes, sub-12px text, lingering loading placeholders, and horizontal overflow at 390px). For anything else, use `agent-browser screenshot` + a baseline compare.
|
|
93
|
+
- Bugs that depend on the portal's shadow-DOM stylesheet wrapping. The harness mounts in light DOM, so global Tailwind/shadcn classes work normally; portal-specific theme tokens injected as inline styles are not present.
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
## SDK Hook Reference
|
|
2
|
+
|
|
3
|
+
All hooks and components below are imported from `@notis/sdk`. `NotisProvider`
|
|
4
|
+
already installs `ShortcutProvider`; app code should not add a second provider.
|
|
5
|
+
|
|
6
|
+
| API | Signature | Description |
|
|
7
|
+
|-----|-----------|-------------|
|
|
8
|
+
| `useNotis()` | `() => { app, route, databases, collectionItem, resourceId, ready }` | App metadata, current route, selected collection item, decoded exact-resource id, ready state |
|
|
9
|
+
| `useTool<TArgs, TResult>(name)` | `(name: string) => { call, loading, error }` | Call a declared tool with app-defined argument/result types. Identical idempotent reads may use `call(args, { dedupe: true })`; never dedupe writes |
|
|
10
|
+
| `useTools()` | `() => { tools, loading }` | List available tools |
|
|
11
|
+
| `useNotisNavigation()` | `() => { toRoute, toDocument, toApp }` | Navigate between routes (including `toRoute(path, { resourceId })`), documents, or the app root |
|
|
12
|
+
| `useTopBarSearch(opts)` | `({ value, onChange, placeholder?, onSubmit? }) => { setLoading }` | Bind the current view to the Portal-owned top-bar search input |
|
|
13
|
+
| `useBackend()` | `() => { request }` | Raw backend request proxy with JWT auth |
|
|
14
|
+
| `useDatabaseSubscription(slug, opts?)` | `(slug: string, opts?) => { rows, documents, loading, error, refetch, live }` | Query a database and refetch it when its rows change. `live` is false on hosts without a change feed (temporary test harness, vite preview) -- keep a manual refresh for those |
|
|
15
|
+
| `useHandover()` | `() => { handover, pending, error, available }` | Open manager chat with app/resource context plus an optional starter prompt or declared skill. Omit `prompt` for a context-only composer. `available` is false on hosts with no chat -- fall back to a copyable prompt |
|
|
16
|
+
| `useCloudComputer()` | `() => { facts, loading, error, refresh }` | Read-only cloud computer facts: sandbox existence/status and whether the GitHub CLI is signed in. Requires `capabilities.cloudComputer: 'read'` plus the user's approval; `facts.available === false` means answer from the app's own fallback |
|
|
17
|
+
| `useActiveResource(resource)` | `(ContextResource \| null) => void` | Publish the record currently open in the app so manager handover and context menus stay grounded |
|
|
18
|
+
| `useCollectionInteractions(opts)` | `(opts) => CollectionInteractionController` | Keyboard navigation, active-row state, range/toggle selection, marquee selection, and action dispatch for collection UIs |
|
|
19
|
+
| `useShortcuts(definitions, opts?)` | `(definitions, opts?) => void` | Register scoped keyboard shortcuts. Editable targets are ignored unless explicitly allowed; use `ShortcutHints` to display them |
|
|
20
|
+
| `MarkdownEditor` | `(NotisMarkdownEditorProps) => ReactElement` | Use the host editor with app-owned persistence, stable `resourceKey`, revision-aware `onSave`, and optional `onUploadFile` returning a durable URL |
|
|
21
|
+
| `NotisSelectionBoundary` | `(NotisSelectionBoundaryProps) => ReactElement` | Attach structured, explicitly untrusted app/resource/selection context to selected content and copy operations |
|
|
22
|
+
| `SelectionCheckbox` / `SelectionMarquee` | components | Standard selection controls backed by `useCollectionInteractions` |
|
|
23
|
+
| `MultiSelectActionBar` | component | Standard bulk actions with pending/disabled state and shortcut support |
|
|
24
|
+
|
|
25
|
+
Import headless collection action types and helpers from
|
|
26
|
+
`@notis/sdk/interactions`. Keep an open detail view synchronized with
|
|
27
|
+
`useActiveResource`, and wrap its selectable content in
|
|
28
|
+
`NotisSelectionBoundary` so the manager receives both the active record and the
|
|
29
|
+
user's exact selection. For `MarkdownEditor`, keep `resourceKey` stable per
|
|
30
|
+
record, pass the latest revision back from `onSave`, reject revision conflicts
|
|
31
|
+
instead of overwriting newer data, and implement `onUploadFile` whenever the
|
|
32
|
+
editor should accept media or file blocks.
|
|
33
|
+
|
|
34
|
+
### App configuration additions
|
|
35
|
+
|
|
36
|
+
- `toolBindings` is only for provider-generated public tool names whose upstream
|
|
37
|
+
action cannot be reconstructed. Keep the exact final public `name` in
|
|
38
|
+
`tools`, then bind it to `providerToolName`; the public name remains the
|
|
39
|
+
permission boundary.
|
|
40
|
+
|
|
41
|
+
### Typed tool calls
|
|
42
|
+
|
|
43
|
+
`useTool` accepts generic argument and result types. Query the database at dev time to discover actual property shapes, then keep those types in the app:
|
|
44
|
+
|
|
45
|
+
```tsx
|
|
46
|
+
type QueryTasksArgs = { database_id?: string; database_slug?: string; query: { page_size?: number } };
|
|
47
|
+
interface TaskDoc {
|
|
48
|
+
title: string;
|
|
49
|
+
properties: {
|
|
50
|
+
Status: string;
|
|
51
|
+
Priority: string;
|
|
52
|
+
Due: string;
|
|
53
|
+
};
|
|
54
|
+
};
|
|
55
|
+
type QueryTasksResult = { documents: TaskDoc[] };
|
|
56
|
+
|
|
57
|
+
const queryTasks = useTool<QueryTasksArgs, QueryTasksResult>('LOCAL_NOTIS_DATABASE_QUERY');
|
|
58
|
+
const result = await queryTasks.call({ database_id: 'tasks-db-id', query: { page_size: 25 } });
|
|
59
|
+
// result.documents[0].properties.Status is typed as string
|
|
60
|
+
```
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
## Anti-patterns -- NEVER do these
|
|
2
|
+
|
|
3
|
+
These are the most common mistakes agents make. Each one wastes time and produces broken results.
|
|
4
|
+
|
|
5
|
+
- **NEVER assume app deploys create databases for you** -- Create or update databases through native Notis database tools or the assistant first, then reference them by slug in `notis.config.ts`. Database creation requires the owning app to exist: pass its slug or id in the `app` argument of `LOCAL_NOTIS_DATABASE_UPSERT_DATABASE` (create the app first with `LOCAL_NOTIS_CREATE_APP` if needed). A database can only be referenced by the app that owns it.
|
|
6
|
+
- **NEVER bypass the supported workflow by manually stitching together low-level save or lint calls from a local workspace** -- Local agents should go through the NPX Notis CLI for `apps pull`, `apps build`, `apps verify`, `apps create`, `apps link`, and `apps deploy`.
|
|
7
|
+
- **NEVER use `apps pull` to clone a Store listing** -- `npx --package @notis_ai/cli@latest -- notis apps pull` only pulls source for an app the user can already access as an installed app. To fork a published Store app, run `npx --package @notis_ai/cli@latest -- notis apps init "My App" --from <slug>` instead: it downloads that app's source from the public registry, and installing the app first is not required.
|
|
8
|
+
- **One local/cloud delivery contract** -- The default permits Workspace delivery after checks only when user/repository policy allows it. Explicit read-only, preview-only and no-deploy requests prohibit remote mutations. Neither authorizes Store publication.
|
|
9
|
+
- **NEVER submit without explicit approval** -- A deploy request alone does not authorize Store submission. Run `npx --package @notis_ai/cli@latest -- notis apps publish --confirm-ready` only when the user confirms App Details is ready for Store review.
|
|
10
|
+
- **NEVER write raw `views/<slug>/index.js` files** -- Write standard React pages in `app/`.
|
|
11
|
+
- **NEVER invent `npx --package @notis_ai/cli@latest -- notis apps push` or bypass the review flow** -- Source moves through `apps pull` and `apps deploy`; `apps publish --confirm-ready` submits the deployed snapshot through the same authenticated review endpoint as App Details.
|
|
12
|
+
- **NEVER treat `apps deploy` as store submission** -- It updates the linked installed app for the current account or team scope only. Store submission is a separate, explicitly confirmed step.
|
|
13
|
+
- **NEVER explore server code or tool schemas to invent an alternative app workflow** -- Use the Notis CLI.
|
|
14
|
+
- **NEVER work around a missing `collection.sidebar` portal tree by rendering a duplicate sidebar inside the app** -- keep the route manifest as the source of truth and escalate the missing portal sidebar as a platform bug instead.
|
|
15
|
+
- **NEVER invent a custom visual language** -- Do not ship full-screen gradients, glassmorphism, bright neon palettes, or raw HTML controls as the primary UI. Apps should look like a natural extension of the portal.
|
|
16
|
+
- **NEVER hand-roll buttons/cards/badges when the scaffold already provides flat primitives** -- Prefer `@/components/ui/*` and portal token classes such as `bg-background`, `bg-muted`, and `text-muted-foreground`. Never add `border` or `shadow` classes to `Card`; a `Card` nested in a `Card` is flat automatically. See Design bar.
|
|
17
|
+
|
|
18
|
+
## Troubleshooting
|
|
19
|
+
|
|
20
|
+
### Common issues
|
|
21
|
+
|
|
22
|
+
- **Deploy transport failure**: Run `notis doctor` and read back the exact app/version. Never blindly retry an unknown outcome or write directly to storage.
|
|
23
|
+
- **App shows old code after deploy**: Bundle cache is stale. Hard refresh (Cmd+Shift+R) or clear site data in DevTools.
|
|
24
|
+
- **App is missing from Workspace**: Inspect its exact installed version. Unreleased containers have no runnable routes. A successful release appears through ordinary refresh/navigation.
|
|
25
|
+
- **`LOCAL_NOTIS_DATABASE_QUERY` returns empty documents**: Check that the database ID passed to the tool matches the intended database. Use `npx --package @notis_ai/cli@latest -- notis tools exec LOCAL_NOTIS_DATABASE_LIST_DATABASES --arguments '{}'` to verify the ID; use the database slug only as a fallback.
|
|
26
|
+
- **Properties are `undefined`**: Keep app-local result types for `useTool<TArgs, TResult>` and guard optional nested properties when reading live data.
|