@notis_ai/cli 0.2.0-beta.157.1 → 0.2.0-beta.159.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 +34 -513
- package/dist/base-skills/notis-apps/references/architecture.md +164 -0
- package/dist/base-skills/notis-apps/references/design.md +165 -0
- package/dist/base-skills/notis-apps/references/release.md +99 -0
- package/dist/base-skills/notis-apps/references/sdk.md +61 -0
- package/dist/base-skills/notis-apps/references/troubleshooting.md +23 -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/useDocuments.ts +4 -1
- 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
- package/template/packages/sdk/src/queryCache.ts +10 -2
|
@@ -0,0 +1,164 @@
|
|
|
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
|
+
Use standard React pages in `app/`, not Next.js or a custom server. The host
|
|
10
|
+
chooses a trusted shadow root or an isolated Store frame; do not create your own
|
|
11
|
+
iframe, query Portal-owned DOM, or install a window-global runtime. The host owns
|
|
12
|
+
theme injection and authentication. App code uses SDK hooks and the final tool
|
|
13
|
+
names discovered through the CLI, declared in `notis.config.ts` and enforced by
|
|
14
|
+
the backend. Runtime permissions stay least-authority; releasing an app does not
|
|
15
|
+
grant new capabilities.
|
|
16
|
+
|
|
17
|
+
## Architecture
|
|
18
|
+
|
|
19
|
+
```
|
|
20
|
+
Notis CLI (local workspace or Vercel Sandbox)
|
|
21
|
+
-> Vite + React project with @notis/sdk
|
|
22
|
+
-> notis apps init / build / verify / create / link / pull / deploy
|
|
23
|
+
-> ES module bundle (app.js + app.css) + manifest
|
|
24
|
+
-> Portal renders as React component with real tools/databases
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
`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.
|
|
28
|
+
|
|
29
|
+
### Key Components
|
|
30
|
+
|
|
31
|
+
1. **@notis/sdk** (`packages/sdk/`) -- SDK for app developers
|
|
32
|
+
- `@notis/sdk` -- NotisProvider, runtime hooks, editors, selection helpers, and shortcut primitives
|
|
33
|
+
- `@notis/sdk/interactions` -- headless collection actions and interaction types
|
|
34
|
+
- `@notis/sdk/config` -- `defineNotisApp()` for notis.config.ts
|
|
35
|
+
- `@notis/sdk/vite` -- `notisViteConfig()` for vite.config.ts
|
|
36
|
+
- `@notis/sdk/styles.css` -- shadow-safe app shell styles and base app-surface classes
|
|
37
|
+
|
|
38
|
+
2. **CLI** (`packages/cli/src/command-specs/apps.js`) -- release delivery uses init, build, verify, create, deploy, link, pull, doctor, and list
|
|
39
|
+
|
|
40
|
+
3. **Server** (`server/routers/portal_views/`) -- Returns signed bundle URLs, proxies tool calls
|
|
41
|
+
|
|
42
|
+
4. **Portal** (`portal/src/components/apps/`) -- Renders app bundles as React components via AppViewRenderer
|
|
43
|
+
|
|
44
|
+
### Runtime Bridge
|
|
45
|
+
|
|
46
|
+
Apps communicate with the platform through the `NotisRuntime` interface, provided by the portal via React context:
|
|
47
|
+
|
|
48
|
+
- **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.
|
|
49
|
+
- 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.
|
|
50
|
+
|
|
51
|
+
App code never accesses the runtime directly -- it uses SDK hooks (`useTool`, `useTools`, `useNotis`, etc.) which read from the `NotisProvider` context.
|
|
52
|
+
|
|
53
|
+
## Manifest Format
|
|
54
|
+
|
|
55
|
+
Generated by `npx --package @notis_ai/cli@latest -- notis apps build` at `.notis/output/manifest.json`:
|
|
56
|
+
|
|
57
|
+
```json
|
|
58
|
+
{
|
|
59
|
+
"version": 1,
|
|
60
|
+
"spec_version": 4,
|
|
61
|
+
"app": { "name": "My App", "slug": "my-app", "title": "My App", "description": "...", "icon": "phosphor:..." },
|
|
62
|
+
"routes": [
|
|
63
|
+
{
|
|
64
|
+
"path": "/",
|
|
65
|
+
"slug": "index",
|
|
66
|
+
"name": "Dashboard",
|
|
67
|
+
"icon": "phosphor:squares-four",
|
|
68
|
+
"default": true,
|
|
69
|
+
"export_name": "index",
|
|
70
|
+
"collection": null
|
|
71
|
+
}
|
|
72
|
+
],
|
|
73
|
+
"bundle": {
|
|
74
|
+
"js": "bundle/app.js",
|
|
75
|
+
"css": "bundle/app.css"
|
|
76
|
+
},
|
|
77
|
+
"databases": ["tasks", { "slug": "templates", "seed_documents": true }],
|
|
78
|
+
"tools": ["LOCAL_NOTIS_DATABASE_QUERY"]
|
|
79
|
+
}
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
Use the exact final names returned by tool discovery for explicit app tool declarations. App-specific TypeScript shapes for tool arguments and results live in the app code; the SDK exposes generic tool hooks alongside its native database hooks.
|
|
83
|
+
|
|
84
|
+
Routes are canonical: define navigation in `routes`, give every route an explicit
|
|
85
|
+
`slug`, and do not rely on legacy `views`. A configured `collection.sidebar` tree
|
|
86
|
+
belongs to the host, not app JSX.
|
|
87
|
+
|
|
88
|
+
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.
|
|
89
|
+
Never seed user-created notes, history, leads, or other private records. In source
|
|
90
|
+
config the explicit opt-in is `{ slug: 'templates', seedDocuments: true }`.
|
|
91
|
+
|
|
92
|
+
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.
|
|
93
|
+
|
|
94
|
+
## Database Schema
|
|
95
|
+
|
|
96
|
+
### apps table
|
|
97
|
+
|
|
98
|
+
| Column | Type | Description |
|
|
99
|
+
|---|---|---|
|
|
100
|
+
| id | uuid PK | App ID |
|
|
101
|
+
| user_id | uuid FK | Owner |
|
|
102
|
+
| team_id | uuid FK | Team (nullable) |
|
|
103
|
+
| name | text | Display name |
|
|
104
|
+
| slug | text UNIQUE | URL slug |
|
|
105
|
+
| description | text | App description |
|
|
106
|
+
| icon | text | Phosphor icon (e.g. "phosphor:list") |
|
|
107
|
+
| status | text | draft, active, archived |
|
|
108
|
+
| visibility | text | private, team |
|
|
109
|
+
| manifest | jsonb | Latest deployed manifest |
|
|
110
|
+
| current_version | integer | Version counter |
|
|
111
|
+
| source_listing_id | uuid FK | Source App Store listing for installed store apps; cleared when submitted as a derivative |
|
|
112
|
+
| installed_snapshot | jsonb | Store-installed baseline used for update/reset comparison |
|
|
113
|
+
| customization_overlay | jsonb | User changes over the installed store baseline |
|
|
114
|
+
| update_status | text | up_to_date, update_available, needs_resolution, update_failed |
|
|
115
|
+
| bundled_automation_ids | uuid[] | Linked automations |
|
|
116
|
+
| bundled_skill_ids | uuid[] | Linked skills |
|
|
117
|
+
|
|
118
|
+
### databases ownership
|
|
119
|
+
|
|
120
|
+
Every row in the `databases` table carries `owner_app_id` (uuid FK to
|
|
121
|
+
`apps.id`, `ON DELETE CASCADE`): a database belongs to exactly one app, and
|
|
122
|
+
deleting the app deletes its databases and their documents (`documents` cascade
|
|
123
|
+
from `databases`). Install, resource preparation, and Store updates stamp
|
|
124
|
+
`owner_app_id` automatically; standalone creation requires the `app` argument.
|
|
125
|
+
The app config references existing database slugs; it does not own the schema.
|
|
126
|
+
The database row is the schema source of truth. Treat deployed slugs as stable
|
|
127
|
+
contracts: rename display titles rather than changing slugs behind live callers.
|
|
128
|
+
|
|
129
|
+
### Storage (Supabase)
|
|
130
|
+
|
|
131
|
+
Files stored in `app-code` bucket at `{app_id}/v{version}/`:
|
|
132
|
+
- `manifest.json`
|
|
133
|
+
- `bundle/app.js`
|
|
134
|
+
- `bundle/app.css`
|
|
135
|
+
|
|
136
|
+
Editable source snapshots are stored in the private `app-source` bucket at
|
|
137
|
+
`{app_id}/v{version}/`. Portal App Store listing screenshots are uploaded to
|
|
138
|
+
the public `app-listing-assets` bucket before submission.
|
|
139
|
+
|
|
140
|
+
### Related tables
|
|
141
|
+
|
|
142
|
+
- **databases** -- Apps reference these rows by slug. Schema lives on the database row (`schema_metadata` / `original_fields`), not in the app manifest.
|
|
143
|
+
- **documents** -- `database_id` links to databases. Properties in `properties` jsonb.
|
|
144
|
+
- **app_store_listings** -- Snapshots for publishing to the app store.
|
|
145
|
+
- **app_submissions** -- Portal review submissions keyed to an app source version and registry slug.
|
|
146
|
+
|
|
147
|
+
## Server Endpoints
|
|
148
|
+
|
|
149
|
+
| Endpoint | Method | Purpose |
|
|
150
|
+
|---|---|---|
|
|
151
|
+
| `/portal_views/get` | GET | Route detail + runtime descriptor with signed bundle URLs |
|
|
152
|
+
| `/portal_views/runtime_query` | POST | Proxy tool calls and DB operations |
|
|
153
|
+
| `/portal_views/collection_items` | GET | List collection items |
|
|
154
|
+
| `/portal_views/collection_tree` | GET | List normalized collection tree nodes for a tree sidebar route |
|
|
155
|
+
| `/portal_views/collection_tree/create` | POST | Create a root or child collection row from the sidebar |
|
|
156
|
+
| `/portal_views/collection_tree/rename` | POST | Rename a collection tree item inline |
|
|
157
|
+
| `/portal_views/collection_tree/delete` | POST | Delete a collection tree item from the sidebar |
|
|
158
|
+
| `/portal_apps/list` | GET | List apps |
|
|
159
|
+
| `/portal_apps/get` | GET | Get app detail |
|
|
160
|
+
| `/portal_apps/publish` | POST | Submit a deployed app source snapshot for public store review |
|
|
161
|
+
| `/portal_apps/listing_assets/upload` | POST | Legacy pre-manifest screenshot upload; do not use for current manifest-media workflows |
|
|
162
|
+
| `/portal_apps/submissions` | GET/PATCH | List or edit App Store submissions |
|
|
163
|
+
| `/portal_apps/submissions/withdraw` | POST | Close a pending App Store submission |
|
|
164
|
+
| `/cli_tools` | POST | CLI tool execution (save_app_files, create_app, etc.) |
|
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
## Building an App
|
|
2
|
+
|
|
3
|
+
### Design defaults
|
|
4
|
+
|
|
5
|
+
- Start from the closest Store scaffold (`apps scaffolds list`, then `apps init
|
|
6
|
+
--from <slug>`), or preserve the existing app's good patterns.
|
|
7
|
+
- Make the main task obvious. Use compact spacing, readable text, plain page
|
|
8
|
+
titles, Phosphor icons, and the scaffold's buttons, filters, rows, and cards.
|
|
9
|
+
- Match Notis in light and dark mode. Use theme tokens and restrained accents;
|
|
10
|
+
prefer flat surfaces and selection tints over decorative boxes and shadows.
|
|
11
|
+
- Choose the right layout: `notis-app-shell` for ordinary content;
|
|
12
|
+
`notis-app-split`, `notis-app-pane-list`, and `notis-app-pane-detail` for a
|
|
13
|
+
full-viewport list and reader. Keep reading text comfortably sized. Let mobile
|
|
14
|
+
stack or adapt the content rather than squeeze a desktop layout onto a phone.
|
|
15
|
+
- Let Notis own navigation, folder trees, and search. Use `PageHeading`,
|
|
16
|
+
`NativeSelect`, `.list-row`, and `useTopBarSearch` instead of duplicating chrome.
|
|
17
|
+
- Prefer inline optimistic edits for simple changes, with rollback on failure.
|
|
18
|
+
Use a dialog for multi-field edits or destructive confirmation.
|
|
19
|
+
|
|
20
|
+
Build enforces the existing design rules and reports violations by file/line.
|
|
21
|
+
Use those diagnostics to fix specific problems; passing them does not establish
|
|
22
|
+
that the design is good. See [troubleshooting](troubleshooting.md) when needed.
|
|
23
|
+
|
|
24
|
+
### Look at the result
|
|
25
|
+
|
|
26
|
+
Run build and verification, then inspect screenshots of the affected view at a
|
|
27
|
+
normal desktop width and a phone width. For new layouts or theme changes, check
|
|
28
|
+
both themes. Keep the review focused on the task, not a new report or approval cycle:
|
|
29
|
+
|
|
30
|
+
- Does the layout use the available viewport correctly, including while loading?
|
|
31
|
+
- Do sizing, spacing, text, and scrolling look right? Is anything clipped or overflowing?
|
|
32
|
+
- Do loading placeholders match the real content instead of changing the layout?
|
|
33
|
+
- Does the main interaction work, and do empty/error states explain what to do?
|
|
34
|
+
|
|
35
|
+
Use temporary fixtures to expose slow reads, empty results, and errors where
|
|
36
|
+
relevant; do not alter real user records for a screenshot. Actually inspect the
|
|
37
|
+
images, fix what is wrong, and recheck the affected state. After an authorized
|
|
38
|
+
release, repeat the affected-view check inside Notis as described in
|
|
39
|
+
[Delivery](release.md). A standalone harness is not proof of the host layout.
|
|
40
|
+
|
|
41
|
+
### Step 1: Define the config
|
|
42
|
+
|
|
43
|
+
Use `~/.notis/apps/<slug>` by default, or pass the user's intended directory to
|
|
44
|
+
`apps init` / `apps pull`. Avoid a parent workspace that selects an unrelated CLI
|
|
45
|
+
profile. Keep the exact installed identity when editing; do not rename a machine
|
|
46
|
+
slug just to correct its display title.
|
|
47
|
+
|
|
48
|
+
Create `notis.config.ts` with:
|
|
49
|
+
- **name** -- Stable machine identity in lowercase kebab-case, such as `link-building`; do not use display casing here
|
|
50
|
+
- **title** -- Human-facing app name with deliberate casing, such as `Link Building`; preserve brands and acronyms exactly
|
|
51
|
+
- **databases** -- Slug references to existing Notis databases
|
|
52
|
+
- **routes** -- Route-first sidebar entries with explicit `slug`, optional `parentSlug`, and optional `collection.sidebar` tree config
|
|
53
|
+
- **tools** -- Final tool names the app can call at runtime. Discover tools with `notis tools search "<what you need>"`, inspect their schemas with `notis tools describe <tool>`, 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.
|
|
54
|
+
|
|
55
|
+
For collection-backed sidebars, use the route schema directly:
|
|
56
|
+
|
|
57
|
+
```ts
|
|
58
|
+
routes: [
|
|
59
|
+
{
|
|
60
|
+
path: '/',
|
|
61
|
+
slug: 'notes',
|
|
62
|
+
name: 'Notes',
|
|
63
|
+
icon: 'phosphor:note-pencil',
|
|
64
|
+
default: true,
|
|
65
|
+
collection: {
|
|
66
|
+
database: 'notes',
|
|
67
|
+
titleProperty: 'Title',
|
|
68
|
+
parentProperty: 'Parent note',
|
|
69
|
+
sidebar: {
|
|
70
|
+
mode: 'tree',
|
|
71
|
+
allowCreate: true,
|
|
72
|
+
},
|
|
73
|
+
},
|
|
74
|
+
},
|
|
75
|
+
]
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
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'`.
|
|
79
|
+
|
|
80
|
+
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.
|
|
81
|
+
|
|
82
|
+
### Step 2: Build pages
|
|
83
|
+
|
|
84
|
+
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):
|
|
85
|
+
|
|
86
|
+
```tsx
|
|
87
|
+
import { useDocuments, ViewSkeleton } from '@notis/sdk';
|
|
88
|
+
import { Card } from '@/components/ui/card';
|
|
89
|
+
|
|
90
|
+
export default function TasksPage() {
|
|
91
|
+
const tasks = useDocuments('tasks', { pageSize: 25 });
|
|
92
|
+
return <section className="space-y-4 p-6">
|
|
93
|
+
<h1 className="text-xl font-semibold">Tasks</h1>
|
|
94
|
+
{tasks.error && <p role="alert">{tasks.error.message} <button onClick={tasks.refetch}>Retry</button></p>}
|
|
95
|
+
{tasks.loading ? <ViewSkeleton variant="table" rows={5} /> : tasks.hasData ? (
|
|
96
|
+
tasks.documents.length ? tasks.documents.map((task) => (
|
|
97
|
+
<Card key={task.id} className="p-4"><h2>{task.title || 'Untitled'}</h2></Card>
|
|
98
|
+
)) : <p>No tasks yet.</p>
|
|
99
|
+
) : null}
|
|
100
|
+
</section>;
|
|
101
|
+
}
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
### Instant-view loading contract (required)
|
|
105
|
+
|
|
106
|
+
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.
|
|
107
|
+
|
|
108
|
+
| State | Required UI |
|
|
109
|
+
| --- | --- |
|
|
110
|
+
| First read, no successful data | Keep headings/navigation/layout visible; use content-shaped skeletons only in missing regions, with the same pane bounds as the loaded view. No page spinner or whole-page `Loading...`. |
|
|
111
|
+
| Cached view / successful empty result | Render synchronously from the shared SDK cache. Empty results are real cached results. |
|
|
112
|
+
| 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. |
|
|
113
|
+
| Explicit Save / Upload / submitted search | Progress belongs in that button or affected section. Disable only the conflicting action. |
|
|
114
|
+
| Failed read | Show a scoped error and Retry; keep usable cached content. Never show an empty-state message before `hasData` is true. |
|
|
115
|
+
|
|
116
|
+
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`.
|
|
117
|
+
|
|
118
|
+
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.
|
|
119
|
+
|
|
120
|
+
`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.
|
|
121
|
+
|
|
122
|
+
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.
|
|
123
|
+
|
|
124
|
+
### Discovering database schema
|
|
125
|
+
|
|
126
|
+
Before writing app code, inspect the database schema to know what properties exist:
|
|
127
|
+
|
|
128
|
+
```bash
|
|
129
|
+
npx --package @notis_ai/cli@latest -- notis tools search "list Notis databases"
|
|
130
|
+
npx --package @notis_ai/cli@latest -- notis tools exec LOCAL_NOTIS_DATABASE_LIST_DATABASES --arguments '{}'
|
|
131
|
+
npx --package @notis_ai/cli@latest -- notis tools exec LOCAL_NOTIS_DATABASE_GET_DATABASE --arguments '{"database_slug":"social_media_calendar"}'
|
|
132
|
+
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}}'
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
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.
|
|
136
|
+
|
|
137
|
+
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.
|
|
138
|
+
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:
|
|
139
|
+
|
|
140
|
+
```tsx
|
|
141
|
+
const upsertTask = useTool<Record<string, unknown>, { document?: { id: string } }>('LOCAL_NOTIS_DATABASE_UPSERT_TASKS');
|
|
142
|
+
|
|
143
|
+
await upsertTask.call({
|
|
144
|
+
title: 'My Task',
|
|
145
|
+
Status: 'Todo',
|
|
146
|
+
Priority: 'P1',
|
|
147
|
+
Due: '2025-04-01',
|
|
148
|
+
Done: false,
|
|
149
|
+
Count: 5,
|
|
150
|
+
});
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
Do NOT pass Notion-style wrappers (`{select: {name: "Todo"}}`) when upserting.
|
|
154
|
+
|
|
155
|
+
### Step 3: Root layout
|
|
156
|
+
|
|
157
|
+
```tsx
|
|
158
|
+
import { NotisProvider } from '@notis/sdk';
|
|
159
|
+
import '@notis/sdk/styles.css';
|
|
160
|
+
import './globals.css';
|
|
161
|
+
|
|
162
|
+
export default function AppShell({ children }: { children: React.ReactNode }) {
|
|
163
|
+
return <NotisProvider>{children}</NotisProvider>;
|
|
164
|
+
}
|
|
165
|
+
```
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
## Delivery
|
|
2
|
+
|
|
3
|
+
Workspace runs released versions only. Local and cloud agents use the same
|
|
4
|
+
workflow. Run Notis commands through
|
|
5
|
+
`npx --package @notis_ai/cli@latest -- notis ...`.
|
|
6
|
+
|
|
7
|
+
A create/edit request normally authorizes updating that app after checks pass,
|
|
8
|
+
**unless user or repository policy requires explicit deployment consent**.
|
|
9
|
+
Preserve authorization already given. Read-only, preview-only, and no-deploy
|
|
10
|
+
instructions stop at local source, build, and stub verification: no remote
|
|
11
|
+
resource mutation, app activation, or live verification. Store publication always
|
|
12
|
+
needs separate explicit approval. Do not deploy just to obtain visual proof when
|
|
13
|
+
deployment is not authorized; report that the host check remains unverified.
|
|
14
|
+
|
|
15
|
+
## Update an app
|
|
16
|
+
|
|
17
|
+
1. **Check identity.** Inspect the effective CLI profile and `apps list --json`.
|
|
18
|
+
Preserve local edits, then pull the exact editable app ID and intended
|
|
19
|
+
personal/team scope. Keep its profile-scoped link, current version, and revision.
|
|
20
|
+
Never silently advance a stale checkout or create a duplicate to avoid a conflict.
|
|
21
|
+
2. **Build and inspect.** Edit the source, increment `notisAppVersion`, and update
|
|
22
|
+
`CHANGELOG.md`. Run `apps build` and automated `apps verify`, then do the
|
|
23
|
+
[visual check](design.md#look-at-the-result). For a new app, complete these
|
|
24
|
+
local checks before creating remote resources.
|
|
25
|
+
3. **Prepare only missing resources.** For an existing app, retain its identity.
|
|
26
|
+
For a new app, reconcile the exact canonical name, edit permission, and scope
|
|
27
|
+
against `apps list --json`; reuse one matching editable identity, stop on
|
|
28
|
+
ambiguity, or create only when none exists. Use `apps create "<display title>"
|
|
29
|
+
<dir>` (with a verified `--team-id` for team scope), and read back the same ID.
|
|
30
|
+
Create only necessary missing databases against that app. Verify ownership
|
|
31
|
+
and database IDs before changing schemas; read back changes. Breaking changes
|
|
32
|
+
require separate coordination. Never mutate user data merely to test the UI.
|
|
33
|
+
4. **Update Workspace.** Run `apps deploy` against that linked app. It builds and
|
|
34
|
+
verifies a frozen source/artifact snapshot before activation. `--skip-build`
|
|
35
|
+
accepts only unchanged valid output and still verifies. Use the supported
|
|
36
|
+
backend path; do not bypass checks or write directly to storage.
|
|
37
|
+
5. **Verify delivery.** Read back the app ID, integer version, and Portal URL with
|
|
38
|
+
`apps list --json`. Run `apps verify --mode live`, then open the released app
|
|
39
|
+
inside Notis and inspect the affected screen and main interaction. Confirm the
|
|
40
|
+
intended bundle/version, not just the existence of an app with the same name.
|
|
41
|
+
A successful live harness check alone is not visual proof inside Notis.
|
|
42
|
+
6. **Report accurately.** Give the app link and a brief description of what changed
|
|
43
|
+
and what was verified. Distinguish local-only, deployed and verified, deployed
|
|
44
|
+
but unverified, failed before activation, and outcome unknown. If create/deploy
|
|
45
|
+
has an uncertain outcome, reconcile its exact identity/version before retrying.
|
|
46
|
+
|
|
47
|
+
## What the checks prove
|
|
48
|
+
|
|
49
|
+
`build` validates the package, enforces design rules, and refreshes its embedded
|
|
50
|
+
SDK. Automated `verify` checks every route at desktop (1280px) and phone (390px)
|
|
51
|
+
widths, render errors, runtime calls, nested boxes, small text, lingering loading
|
|
52
|
+
placeholders, and horizontal overflow. It uses a temporary server and browser;
|
|
53
|
+
printed URLs or `--no-browser` are not passing verification. If tooling is missing,
|
|
54
|
+
install it with `npm exec --yes --package agent-browser@latest -- agent-browser install`.
|
|
55
|
+
|
|
56
|
+
Stub verification does not establish real account data, permissions, host layout,
|
|
57
|
+
or visual quality. Live verification exercises the authenticated runtime but still
|
|
58
|
+
uses the harness. The final installed-app check establishes the result inside Notis.
|
|
59
|
+
If that surface cannot be inspected, say so rather than claim it passed. No extra
|
|
60
|
+
approval round is needed for an already-authorized check.
|
|
61
|
+
|
|
62
|
+
`apps screenshot` supports declared scenarios and stub fixtures, including
|
|
63
|
+
`theme: 'dark'`; `--raw` gives uncomposited captures. Store listing screenshots are
|
|
64
|
+
not required for an ordinary Workspace update.
|
|
65
|
+
|
|
66
|
+
## Special cases — read only when relevant
|
|
67
|
+
|
|
68
|
+
### Unreleased container or stale checkout
|
|
69
|
+
|
|
70
|
+
An unreleased container has no source to pull. Recover its original local source,
|
|
71
|
+
or scaffold only if it cannot be recovered; verify the exact ID and scope and use
|
|
72
|
+
`apps link <app-id> <dir> --expected-version 0`. Reuse the container after a failed
|
|
73
|
+
first release; do not duplicate or automatically delete it. If another release
|
|
74
|
+
has appeared, pull it into a fresh directory and reapply the intended edits without
|
|
75
|
+
replacing its deployment base. Link/deploy guards must reject races and conflicts.
|
|
76
|
+
|
|
77
|
+
### Restore an older source
|
|
78
|
+
|
|
79
|
+
Pull the current release into a fresh checkout and the historical source into a
|
|
80
|
+
separate folder (`apps pull <id> <dir> --source-version <n>`). Replace source without
|
|
81
|
+
replacing the current `.notis` link/base, then check and deploy as a new release.
|
|
82
|
+
Preserve app/database/skill IDs. Never decrement versions or imply that source
|
|
83
|
+
restoration undoes user data or external actions.
|
|
84
|
+
|
|
85
|
+
### Release history and Store publication
|
|
86
|
+
|
|
87
|
+
Keep all release history in root `CHANGELOG.md`, newest first, with headings
|
|
88
|
+
`## [Release title] - YYYY-MM-DD` (or `{PR_MERGE_DATE}` while unpublished). Do not add
|
|
89
|
+
`versionNotes` to the config. App Details reads deployed history; the Store reads
|
|
90
|
+
its published snapshot. Local edits must not change the published listing.
|
|
91
|
+
|
|
92
|
+
`apps deploy` updates Workspace only. Use `apps publish --confirm-ready` only after
|
|
93
|
+
the user explicitly approves the current App Details and Store listing. Deploy the
|
|
94
|
+
exact approved source first. Respect listing completeness, visibility, version,
|
|
95
|
+
and pending-review guards. A public submission includes editable source, Store
|
|
96
|
+
assets, source-declared database schemas, and only explicitly opted-in starter
|
|
97
|
+
rows. Do not hand-edit `notis-listing.json` or strip files to pass review; fix the
|
|
98
|
+
source, redeploy, and resubmit. To start from a Store app, use `apps init --from
|
|
99
|
+
<slug>`; `apps pull` is for an accessible installed app, not a Store listing clone.
|
|
@@ -0,0 +1,61 @@
|
|
|
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. Identified reads use `call(args, { readOnly: true, dedupe: true })`; never dedupe writes. See [cached-read ownership](design.md#instant-view-loading-contract-required). |
|
|
10
|
+
| `useTools()` | `() => { tools, loading }` | List available tools |
|
|
11
|
+
| `useDocuments(slug, opts?)` | `(slug: string, opts?) => { documents, loading, hasData, error, refetch }` | Query an app database. Bodies are included by default. For metadata-only lists, opt into `includeContent: false`; load the opened record with `useDocument` and defer any full-body search query until needed. Metadata and full-content query caches are separate. |
|
|
12
|
+
| `useNotisNavigation()` | `() => { toRoute, toDocument, toApp }` | Navigate between routes (including `toRoute(path, { resourceId })`), documents, or the app root |
|
|
13
|
+
| `useTopBarSearch(opts)` | `({ value, onChange, placeholder?, onSubmit? }) => { setLoading }` | Bind the current view to the Portal-owned top-bar search input |
|
|
14
|
+
| `useBackend()` | `() => { request }` | Raw backend request proxy with JWT auth |
|
|
15
|
+
| `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 |
|
|
16
|
+
| `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 |
|
|
17
|
+
| `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 |
|
|
18
|
+
| `useActiveResource(resource)` | `(ContextResource \| null) => void` | Publish the record currently open in the app so manager handover and context menus stay grounded |
|
|
19
|
+
| `useCollectionInteractions(opts)` | `(opts) => CollectionInteractionController` | Keyboard navigation, active-row state, range/toggle selection, marquee selection, and action dispatch for collection UIs |
|
|
20
|
+
| `useShortcuts(definitions, opts?)` | `(definitions, opts?) => void` | Register scoped keyboard shortcuts. Editable targets are ignored unless explicitly allowed; use `ShortcutHints` to display them |
|
|
21
|
+
| `MarkdownEditor` | `(NotisMarkdownEditorProps) => ReactElement` | Use the host editor with app-owned persistence, stable `resourceKey`, revision-aware `onSave`, and optional `onUploadFile` returning a durable URL |
|
|
22
|
+
| `NotisSelectionBoundary` | `(NotisSelectionBoundaryProps) => ReactElement` | Attach structured, explicitly untrusted app/resource/selection context to selected content and copy operations |
|
|
23
|
+
| `SelectionCheckbox` / `SelectionMarquee` | components | Standard selection controls backed by `useCollectionInteractions` |
|
|
24
|
+
| `MultiSelectActionBar` | component | Standard bulk actions with pending/disabled state and shortcut support |
|
|
25
|
+
|
|
26
|
+
Import headless collection action types and helpers from
|
|
27
|
+
`@notis/sdk/interactions`. Keep an open detail view synchronized with
|
|
28
|
+
`useActiveResource`, and wrap its selectable content in
|
|
29
|
+
`NotisSelectionBoundary` so the manager receives both the active record and the
|
|
30
|
+
user's exact selection. For `MarkdownEditor`, keep `resourceKey` stable per
|
|
31
|
+
record, pass the latest revision back from `onSave`, reject revision conflicts
|
|
32
|
+
instead of overwriting newer data, and implement `onUploadFile` whenever the
|
|
33
|
+
editor should accept media or file blocks.
|
|
34
|
+
|
|
35
|
+
### App configuration additions
|
|
36
|
+
|
|
37
|
+
- `toolBindings` is only for provider-generated public tool names whose upstream
|
|
38
|
+
action cannot be reconstructed. Keep the exact final public `name` in
|
|
39
|
+
`tools`, then bind it to `providerToolName`; the public name remains the
|
|
40
|
+
permission boundary.
|
|
41
|
+
|
|
42
|
+
### Typed tool calls
|
|
43
|
+
|
|
44
|
+
`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:
|
|
45
|
+
|
|
46
|
+
```tsx
|
|
47
|
+
type QueryTasksArgs = { database_id?: string; database_slug?: string; query: { page_size?: number } };
|
|
48
|
+
interface TaskDoc {
|
|
49
|
+
title: string;
|
|
50
|
+
properties: {
|
|
51
|
+
Status: string;
|
|
52
|
+
Priority: string;
|
|
53
|
+
Due: string;
|
|
54
|
+
};
|
|
55
|
+
};
|
|
56
|
+
type QueryTasksResult = { documents: TaskDoc[] };
|
|
57
|
+
|
|
58
|
+
const queryTasks = useTool<QueryTasksArgs, QueryTasksResult>('LOCAL_NOTIS_DATABASE_QUERY');
|
|
59
|
+
const result = await queryTasks.call({ database_id: 'tasks-db-id', query: { page_size: 25 } });
|
|
60
|
+
// result.documents[0].properties.Status is typed as string
|
|
61
|
+
```
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
## Troubleshooting
|
|
2
|
+
|
|
3
|
+
Start with the failing screen or operation. Do not invent a second app workflow;
|
|
4
|
+
use the [delivery guide](release.md) and supported Notis CLI commands.
|
|
5
|
+
|
|
6
|
+
- **App looks wrong despite passing checks:** open it inside Notis and compare
|
|
7
|
+
the affected region with the request. Check loading and loaded states, viewport
|
|
8
|
+
sizing, scrolling, and theme. A standalone harness does not reproduce the host's
|
|
9
|
+
parent layout or shadow boundary. Do not assume the cause from a screenshot alone.
|
|
10
|
+
- **Build reports a design violation:** use the scaffold component or theme token
|
|
11
|
+
suggested by the diagnostic. Fix the reported file/line rather than hiding the
|
|
12
|
+
pattern elsewhere. Existing validator exceptions are for justified cases, not a
|
|
13
|
+
shortcut around visual review.
|
|
14
|
+
- **The configured sidebar is missing:** preserve `routes` and `collection.sidebar`;
|
|
15
|
+
investigate the host mismatch instead of duplicating the sidebar in app code.
|
|
16
|
+
- **The app shows old code or is missing from Workspace:** check the exact installed
|
|
17
|
+
app/version and requested bundle first. Local source edits and Desktop restarts
|
|
18
|
+
do not update a released app. Refresh after confirming the correct release exists.
|
|
19
|
+
- **Deploy transport failure:** run `notis doctor` and read back the exact app/version.
|
|
20
|
+
Reconcile the outcome before retrying; do not bypass the backend with storage writes.
|
|
21
|
+
- **Database query is empty or properties are undefined:** inspect the actual schema,
|
|
22
|
+
database ID, and returned property shape through the CLI. Keep types in the app,
|
|
23
|
+
guard optional fields, and distinguish an error from a successful empty result.
|