@notis_ai/cli 0.2.0-beta.156.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/README.md +11 -45
- package/config/notis_app_design_rules.json +135 -0
- package/dist/agent-hooks/notis-agent-hook.mjs +8893 -10612
- package/dist/base-skills/notis-apps/SKILL.md +25 -573
- 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 -267
- 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 -2
- package/skills/notis-apps/cli.md +34 -95
- package/skills/notis-cli/AGENT_INSTRUCTIONS.md +1 -1
- package/src/cli.js +4 -0
- package/src/command-specs/apps.js +322 -1560
- package/src/command-specs/diagnostics.js +37 -0
- package/src/command-specs/skills.js +23 -5
- package/src/runtime/agent-browser.js +169 -1
- package/src/runtime/app-boundary-validator.js +221 -0
- package/src/runtime/app-platform.js +359 -233
- package/src/runtime/app-test-server.js +292 -0
- 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/app/page.tsx +45 -44
- package/template/components/page-heading.tsx +23 -0
- package/template/components/ui/badge.tsx +7 -4
- package/template/components/ui/card.tsx +24 -11
- package/template/components/ui/native-select.tsx +24 -0
- package/template/notis.config.ts +0 -1
- package/template/package.json +2 -2
- package/template/packages/sdk/package.json +1 -2
- package/template/packages/sdk/src/components/MultiSelectActionBar.tsx +55 -13
- package/template/packages/sdk/src/components/MultiSelectCheckbox.tsx +3 -1
- package/template/packages/sdk/src/config.ts +0 -2
- 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 +5 -1
- package/template/packages/sdk/src/styles.css +28 -1
- package/src/runtime/app-dev-build-supervisor.js +0 -47
- package/src/runtime/app-dev-build.js +0 -41
- package/src/runtime/app-dev-consumers.js +0 -154
- package/src/runtime/app-dev-host-lock.js +0 -80
- package/src/runtime/app-dev-process-identity.js +0 -111
- package/src/runtime/app-dev-roots.js +0 -284
- package/src/runtime/app-dev-server.js +0 -1136
- package/src/runtime/app-dev-sessions.js +0 -185
- package/src/runtime/cli-mode.generated.js +0 -5
- package/src/runtime/cli-mode.js +0 -34
|
@@ -4,6 +4,7 @@ description: Design and package Notis apps. Use when users want an app that grou
|
|
|
4
4
|
feature_flag: store
|
|
5
5
|
mcp_resource: true
|
|
6
6
|
mcp_tool_patterns: ["LOCAL_NOTIS_INSTALL_APP"]
|
|
7
|
+
mcp_references: ["references/release.md", "references/architecture.md", "references/design.md", "references/sdk.md", "references/troubleshooting.md"]
|
|
7
8
|
---
|
|
8
9
|
|
|
9
10
|
# Notis Apps Skill
|
|
@@ -11,94 +12,15 @@ mcp_tool_patterns: ["LOCAL_NOTIS_INSTALL_APP"]
|
|
|
11
12
|
Use this skill when the user wants a packaged Notis app -- task manager, CRM, dashboard, internal tool, etc. Notis apps are **Vite + React projects** that deploy into the Notis portal as installed apps for the current user or team.
|
|
12
13
|
|
|
13
14
|
Run the Notis CLI through NPX, for example `npx --package @notis_ai/cli@latest -- notis apps list`. Sign the CLI in once with `notis login`; each account you authorize is a profile you can switch between with `notis profile use`. The CLI bundles this `notis-apps` base skill and refreshes its canonical copy under `~/.notis/skills/base/` on every launch. It is independent of account-skill sync, feature flags, target selection, and cloud deletion.
|
|
15
|
+
## User and repository policy takes precedence
|
|
14
16
|
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
## App Workspace Tool Rules
|
|
24
|
-
|
|
25
|
-
- Apps are the top-level packaging unit in Notis.
|
|
26
|
-
- Choose the execution path before changing an app. A prompt that says the
|
|
27
|
-
shell is a hosted/Vercel sandbox, or a shell rooted at `/vercel/sandbox`, is
|
|
28
|
-
the **hosted sandbox** path. A shell on the user's computer with Notis
|
|
29
|
-
Desktop available is the **local Desktop** path.
|
|
30
|
-
- In a hosted sandbox, do not run `apps dev`: the user's Desktop cannot mount
|
|
31
|
-
that sandbox filesystem. Unless the user explicitly requests preview-only,
|
|
32
|
-
read-only, or no deployment, a request to create or edit an app authorizes
|
|
33
|
-
deploying that app to the user's Workspace after `apps build` and automated
|
|
34
|
-
`apps verify` pass. An opt-out stops after those tests with no remote app
|
|
35
|
-
create/link, database mutation, deploy, or post-deploy checks. Pulling an existing app provides its exact link. For a new
|
|
36
|
-
app, test first, then reconcile profile state and `apps list --json` against
|
|
37
|
-
the canonical `notis.config.ts` `name` and intended personal/team scope: link
|
|
38
|
-
one exact editable non-development match after a metadata-only
|
|
39
|
-
(`include_documents: false`) detail read proves scope, fail on ambiguity or
|
|
40
|
-
scope mismatch, or create only when none exists. New CLI-created apps default
|
|
41
|
-
to personal scope. Before creation, prove that canonicalizing the config
|
|
42
|
-
`title` yields the config `name`. For personal scope, run `apps create
|
|
43
|
-
"<canonical-config-title>" . --json` exactly once. For explicitly requested
|
|
44
|
-
team scope, discover and inspect `LOCAL_NOTIS_CREATE_APP`, dry-run it, execute
|
|
45
|
-
it exactly once with team visibility and the verified current team scope,
|
|
46
|
-
verify the returned id/slug/team scope/edit permission, then `apps link` that
|
|
47
|
-
exact id. Stop for read-only
|
|
48
|
-
reconciliation if creation is ambiguous or outcome-unknown. In the local
|
|
49
|
-
Desktop path, use `apps dev [folder]`, let the user test the DEV app, and deploy
|
|
50
|
-
that development identity directly only after an explicit request. Use
|
|
51
|
-
`LOCAL_NOTIS_CREATE_APP` only for a hosted team-scoped creation or another
|
|
52
|
-
non-CLI administrative flow that explicitly requires a server-side app row.
|
|
53
|
-
- Use `LOCAL_NOTIS_UPDATE_APP` to update app metadata.
|
|
54
|
-
- Use `LOCAL_NOTIS_LIST_APPS` to discover the user's apps.
|
|
55
|
-
- The full app lifecycle uses the CLI in the shell. Always run it through the registry-resolved package, for example `npx --package @notis_ai/cli@latest -- notis apps init`; use the same prefix for `build` and `deploy`. In hosted shells, the CLI is pre-authenticated through `NOTIS_JWT`.
|
|
56
|
-
- There are no `save_app` or `load_app` tools. Do not attempt to call them. Use only the CLI for app file operations.
|
|
57
|
-
- Use `npx --package @notis_ai/cli@latest -- notis apps scaffolds list` (optionally with `--search <term>`) to discover starting points before scaffolding. Every published Store app is a scaffold; the catalog is served from the public registry, not bundled inside the CLI.
|
|
58
|
-
- Use `LOCAL_NOTIS_LIST_PUBLIC_APP_STORE` only to help users choose apps to install, not as a source-clone workflow.
|
|
59
|
-
- Use `LOCAL_NOTIS_INSTALL_APP` only when the user explicitly wants to install from a listing.
|
|
60
|
-
- Before installing, inspect the listing's `required_capabilities`. Explain each
|
|
61
|
-
requested capability and obtain explicit approval; only then pass the matching
|
|
62
|
-
token in `approved_capabilities`. Never infer capability approval. The current
|
|
63
|
-
workspace-wide read token is `workspace_databases_read`; the read-only
|
|
64
|
-
cloud computer token is `cloud_computer_read`.
|
|
65
|
-
|
|
66
|
-
## Architecture
|
|
67
|
-
|
|
68
|
-
```
|
|
69
|
-
Notis CLI (local workspace or Vercel Sandbox)
|
|
70
|
-
-> Vite + React project with @notis/sdk
|
|
71
|
-
-> notis apps init / dev / build / verify / create / link / pull / deploy
|
|
72
|
-
-> ES module bundle (app.js + app.css) + manifest
|
|
73
|
-
-> Portal renders as React component with real tools/databases
|
|
74
|
-
```
|
|
75
|
-
|
|
76
|
-
`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.
|
|
77
|
-
|
|
78
|
-
### Key Components
|
|
79
|
-
|
|
80
|
-
1. **@notis/sdk** (`packages/sdk/`) -- SDK for app developers
|
|
81
|
-
- `@notis/sdk` -- NotisProvider, runtime hooks, editors, selection helpers, and shortcut primitives
|
|
82
|
-
- `@notis/sdk/interactions` -- headless collection actions and interaction types
|
|
83
|
-
- `@notis/sdk/config` -- `defineNotisApp()` for notis.config.ts
|
|
84
|
-
- `@notis/sdk/vite` -- `notisViteConfig()` for vite.config.ts
|
|
85
|
-
- `@notis/sdk/styles.css` -- shadow-safe app shell styles and base app-surface classes
|
|
86
|
-
|
|
87
|
-
2. **CLI** (`packages/cli/src/command-specs/apps.js`) -- local development should center on `apps dev`, plus init, build, verify, create, deploy, link, pull, doctor, and list
|
|
88
|
-
|
|
89
|
-
3. **Server** (`server/routers/portal_views/`) -- Returns signed bundle URLs, proxies tool calls
|
|
90
|
-
|
|
91
|
-
4. **Portal** (`portal/src/components/apps/`) -- Renders app bundles as React components via AppViewRenderer
|
|
92
|
-
|
|
93
|
-
### Runtime Bridge
|
|
94
|
-
|
|
95
|
-
Apps communicate with the platform through the `NotisRuntime` interface, provided by the portal via React context:
|
|
96
|
-
|
|
97
|
-
- **Portal development**: the portal loads a local bundle for an active dev session and still provides a real `NotisRuntime`.
|
|
98
|
-
- **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.
|
|
99
|
-
- 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.
|
|
100
|
-
|
|
101
|
-
App code never accesses the runtime directly -- it uses SDK hooks (`useTool`, `useTools`, `useNotis`, etc.) which read from the `NotisProvider` context.
|
|
17
|
+
Default delivery below applies only when no more restrictive user or repository
|
|
18
|
+
instruction exists. Explicit preview-only/no-deploy requests and standing requirements
|
|
19
|
+
for explicit deployment consent override the default. Preserve that authority across
|
|
20
|
+
local and cloud runs. For local-only work, build and run stub verification; do not
|
|
21
|
+
create remote resources or activate an app. `apps dev` is not a supported delivery
|
|
22
|
+
path; use the CLI's documented build/verification harness. Store publication remains
|
|
23
|
+
separately authorized.
|
|
102
24
|
|
|
103
25
|
## Hard Rules
|
|
104
26
|
|
|
@@ -110,7 +32,7 @@ App code never accesses the runtime directly -- it uses SDK hooks (`useTool`, `u
|
|
|
110
32
|
5. **Declarative tools** -- Tool access is declared in `notis.config.ts` by the final names returned by tool discovery and enforced server-side. Views can call native Notis, connected integrations, PostForMe, and MCP tools directly; metered calls use the same credit-cap and usage-billing path as the CLI.
|
|
111
33
|
6. **shadcn + Notis theme** -- Apps must use shadcn components with the live Notis theme provided by the portal
|
|
112
34
|
7. **Phosphor icons only** -- Always `phosphor:` prefix. Never emojis.
|
|
113
|
-
8. **Database refs only** -- `notis.config.ts` references existing databases by slug. The schema source of truth lives in the `databases` table, not in the manifest. Every native database is owned by exactly one app (`databases.owner_app_id`): creating one through `LOCAL_NOTIS_DATABASE_UPSERT_DATABASE` requires the owning app's slug or id in the `app` argument,
|
|
35
|
+
8. **Database refs only** -- `notis.config.ts` references existing databases by slug. The schema source of truth lives in the `databases` table, not in the manifest. Every native database is owned by exactly one app (`databases.owner_app_id`): creating one through `LOCAL_NOTIS_DATABASE_UPSERT_DATABASE` requires the owning app's slug or id in the `app` argument, installation stamps ownership automatically, and deleting an app deletes its databases and their documents.
|
|
114
36
|
An app-owned database slug is a stable deployed contract because bundles and
|
|
115
37
|
collection routes may call it directly. Do not try to rename that slug with
|
|
116
38
|
a schema tool; rename the display title instead.
|
|
@@ -120,497 +42,27 @@ App code never accesses the runtime directly -- it uses SDK hooks (`useTool`, `u
|
|
|
120
42
|
12. **Portal-owned sidebars stay portal-owned** -- If a route uses `collection.sidebar`, treat that sidebar as platform chrome. Do not remove it, recreate it inside app JSX, or replace it with a custom in-app folder rail.
|
|
121
43
|
13. **Portal globals are off-limits** -- Never use `window.__NOTIS_RUNTIME__`, query portal-owned DOM hooks, or create global DOM portals.
|
|
122
44
|
14. **Prefer inline optimistic edits** -- Rename-like edits for collections, app-owned rows, and sidebar-backed entities should use inline editing with an optimistic UI update, then roll back on backend failure. Use modals only when the edit requires multiple fields or destructive confirmation.
|
|
123
|
-
15. **
|
|
124
|
-
16. **
|
|
125
|
-
17. **
|
|
126
|
-
18. **
|
|
45
|
+
15. **One delivery gate** -- Follow the release guide on local and cloud computers, subject to the policy precedence above. No DEV runtime exists.
|
|
46
|
+
16. **Exact identity** -- Preserve the intended profile, editable app ID, personal/team scope and current deployment base. Never silently advance a stale checkout.
|
|
47
|
+
17. **Automatic source updates** -- Build and check requested app source changes, then update Workspace only when allowed by the user/repository policy above. Store publication stays separate.
|
|
48
|
+
18. **Runtime permissions stay least-authority** -- Release activation preserves existing grants and keeps restricted capabilities denied until approved.
|
|
127
49
|
19. **Store submission is user-gated** -- Run `apps publish --confirm-ready` only after the user explicitly confirms the current App Details page and Store listing are ready. Deploy the exact approved local state first. The command must reject missing confirmation, incomplete listing media, a local/deployed version mismatch, private visibility, or an existing pending review.
|
|
128
|
-
20. **
|
|
50
|
+
20. **Source restoration** -- Restore historical source as a new release using the current deployment base; never revert data or decrement versions.
|
|
129
51
|
21. **`CHANGELOG.md` owns release history** -- Keep the complete release history in one root `CHANGELOG.md`, newest entry first. Do not add new `versionNotes` values to `notis.config.ts`. Use `## [Release title] - YYYY-MM-DD`, or `{PR_MERGE_DATE}` for an unpublished entry. App Details reads **What’s New** and **Version History** from the deployed package manifest, while the Store reads them from the latest published snapshot; unpublished workspace edits must never change the Store page. The manifest also exposes `package.json` `notisAppVersion` as the package version shown in App Details.
|
|
130
52
|
22. **Database rows are private unless explicitly seeded** -- A string declaration such as `databases: ['notes']` publishes schema only and never includes the developer's rows. Use `{ slug: 'templates', seedDocuments: true }` only for small, intentional starter content that every installer should receive. Never enable it for user-created notes, history, leads, or other personal data.
|
|
131
53
|
23. **Public submissions are complete, reviewable packages** -- The registry PR must contain the full editable source tree, Store assets, exact source-declared database schemas, and only explicitly seeded starter rows. Registry CI validates those boundaries before merge; do not hand-edit `notis-listing.json` or strip source files to make a check pass. Fix the app locally, redeploy, and resubmit.
|
|
132
54
|
24. **New projects default to `~/.notis/apps/<slug>`, and `[dir]` overrides it** -- `apps init` and `apps pull` use this stable, predictable home unless the app belongs in a specific repository, monorepo, or user-chosen location. In those cases, pass `[dir]` and report the resulting path. Do not nest an app inside a directory whose local workspace metadata selects an unrelated Notis runtime or profile: later CLI calls inherit that routing and may target the wrong environment.
|
|
133
55
|
25. **Machine names and display titles use different casing** -- In `notis.config.ts`, `name` is the stable machine identity and must be lowercase kebab-case (`name: 'link-building'`). `title` is the human-facing app name and must use deliberate display casing (`title: 'Link Building'`), preserving product spelling and acronyms such as `Notis` and `SEO`. Never put a title-cased phrase in `name`, never show a raw slug as the title, and never change an existing canonical `name` or remote slug merely to repair display casing. The persisted `apps.name`, Workspace sidebar, App Details, and Store listing must use `title`.
|
|
134
56
|
|
|
135
|
-
##
|
|
136
|
-
|
|
137
|
-
These are the most common mistakes agents make. Each one wastes time and produces broken results.
|
|
138
|
-
|
|
139
|
-
- **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.
|
|
140
|
-
- **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 dev`, `apps build`, `apps verify`, `apps create`, `apps link`, and `apps deploy`.
|
|
141
|
-
- **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.
|
|
142
|
-
- **NEVER apply the local deploy gate to a hosted sandbox** -- On the user's local computer, a clean `apps build` + `apps verify` is not deploy consent: hand off the DEV app and wait. In a hosted sandbox, the user's create or edit request is deploy consent for that app because `apps dev` cannot reach their Desktop; deploy only after both commands pass, then verify the remote version. Neither path authorizes Store submission.
|
|
143
|
-
- **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.
|
|
144
|
-
- **NEVER write raw `views/<slug>/index.js` files** -- Write standard React pages in `app/`.
|
|
145
|
-
- **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.
|
|
146
|
-
- **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.
|
|
147
|
-
- **NEVER explore server code or tool schemas to invent an alternative app workflow** -- Use the Notis CLI.
|
|
148
|
-
- **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.
|
|
149
|
-
- **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.
|
|
150
|
-
- **NEVER hand-roll buttons/cards/badges when the scaffold already provides shadcn primitives** -- Prefer `@/components/ui/*` and portal token classes such as `bg-background`, `bg-card`, `border-border`, and `text-muted-foreground`.
|
|
151
|
-
|
|
152
|
-
## Workflow
|
|
153
|
-
|
|
154
|
-
**Default to the Store scaffold catalog, not a blank project.** Every published Store app is a scaffold: `notis apps scaffolds list` reads the catalog from the public registry, and `notis apps init --from <slug>` downloads that app's source. Most user requests overlap with a published app, and starting from one is faster than a bare app.
|
|
155
|
-
|
|
156
|
-
1. **Find a starting point.** Run `npx --package @notis_ai/cli@latest -- notis apps scaffolds list` (add `--search <term>` to filter) to list the published Store apps. If something close matches, run `npx --package @notis_ai/cli@latest -- notis apps init "My App" --from <slug>` to download that app's source from the registry. Only run plain `notis apps init "My App"` when no published app fits. Either way the project lands in `~/.notis/apps/<slug>`; add a `[dir]` argument when the user wants it somewhere else (a tracked git repo, an existing monorepo), and report the path you used.
|
|
157
|
-
2. **Pull your own apps; fork Store apps with `--from`.** `apps pull` is for apps the user already has installed or deployed: run `npx --package @notis_ai/cli@latest -- notis apps list`, preserve any local edits in the target directory, then run `npx --package @notis_ai/cli@latest -- notis apps pull <app-id>` (lands in `~/.notis/apps/<app-slug>`; pass a `[dir]` argument to place it elsewhere). A pull reproduces the installed release, so increment `package.json` `notisAppVersion` above that release before `apps dev`; until then the online bundle remains active. To fork a published Store app, use `apps init --from <slug>` instead -- it downloads the source from the registry and does not require installing the app first.
|
|
158
|
-
3. **Edit the listing source.** In `notis.config.ts`, set `name` to the stable lowercase kebab-case identity and set `title` to the correctly cased human-facing name; for example, `name: 'link-building'` with `title: 'Link Building'`. Treat acronym and brand casing as editorial input, not something to derive mechanically from the slug. Then update description, icon, accent, author, categories, tagline, databases, routes, and tools. Declare a database as a string for schema-only Store packaging; use `{ slug: 'templates', seedDocuments: true }` only when its rows are deliberate starter content for every installer. Keep the complete Store release history in the root `CHANGELOG.md`, newest entry first, using `## [Release title] - YYYY-MM-DD` (or `{PR_MERGE_DATE}` before publication). The first entry powers **What’s New** and the same file powers **Version History**. `icon` is a `phosphor:<name>` value or `metadata/icon.png`; when unset the app shows its **two-letter initials** everywhere (store, sidebar, app details). `accent` optionally pins the avatar color to one of `blue|violet|emerald|amber|rose|sky|fuchsia|teal` (default derived from the app id). Icon/accent flow through deploy onto the app row + listing and can also be set later via the `update_app` tool.
|
|
159
|
-
4. **Build pages in `app/`.** Reuse scaffold code wherever it fits.
|
|
160
|
-
5. **Test the source before remote mutation.** Before changing a linked installed app, increment `package.json` `notisAppVersion` above the installed release. In a fresh hosted sandbox, bootstrap Agent Browser with `npm exec --yes --package agent-browser@latest -- agent-browser install`. Generate configured screenshots; use `theme: 'dark'` or `theme: 'light'` where appropriate and reserve screenshot `--raw` for diagnostics. Run `npm install`, run `npx --package @notis_ai/cli@latest -- notis apps build`, then run `npx --yes --package @notis_ai/cli@latest --package agent-browser@latest -- notis apps verify` in the sandbox (or the normal NPX verification command locally). Fix every failure. Do not create an app, mutate a database, or deploy before both checks pass. `--no-browser` is manual triage, not a passing automated gate.
|
|
161
|
-
6. **Finish the local Desktop path at the DEV handoff.** Run `npx --package @notis_ai/cli@latest -- notis apps dev [folder]`; it creates the development identity and materializes available scaffold database snapshots without requiring a hosted app id. Hand off after the user can see and test the app in its DEV-badged Workspace row. Do not deploy until the user asks, and then run `apps deploy` directly so the existing `dev_app_id` is promoted in place; never run `apps create` after `apps dev`. **Before handing off, complete all three acceptance checks:**
|
|
162
|
-
1. Root: `apps roots list` contains the intended folder (or the app is under the implicit default root).
|
|
163
|
-
2. Bundle: the loopback `/snapshot` responds successfully and contains the expected manifest/routes.
|
|
164
|
-
3. Mount and render: the app appears exactly once with a compact `DEV` badge and its default route renders. For multi-instance work, verify each requested Desktop independently.
|
|
165
|
-
See Troubleshooting → *App is missing from the sidebar* if any check fails.
|
|
166
|
-
7. **Gate and resolve one hosted identity after tests pass.** If the request is preview-only, read-only, or no-deploy, stop after step 5: do not create or link an app, mutate a database, deploy, or run post-deploy checks. Otherwise, existing edits keep the exact profile-scoped id linked by `apps pull`, after a metadata-only (`include_documents: false`) app-detail read validates its edit permission and scope without materializing databases. For a new hosted app, default the intended scope to personal unless the user explicitly requests team scope, inspect `.notis/state.json`, and run `apps list --json`. Consider every accessible exact canonical-slug row, including development rows. Link only one editable, non-development candidate whose metadata-only exact app-detail read proves the intended scope; fail on multiple matches, development-row collisions, missing scope proof, or scope mismatch. Create only when there are zero exact-slug rows. First prove that lowercasing the config `title`, replacing non-alphanumeric runs with `-`, and trimming hyphens yields the config `name`. For personal scope, run `npx --package @notis_ai/cli@latest -- notis apps create "<canonical-config-title>" . --json` exactly once and verify the returned id, remote slug, edit permission, and personal scope. For explicitly requested team scope, use `notis tools search` to discover the team-capable app-creation tool, inspect its schema, dry-run it, then execute `LOCAL_NOTIS_CREATE_APP` exactly once with the canonical display title, `visibility: "team"`, and the exact current `team_id` when resolved. Verify the result's id, canonical slug, `team_id`, team visibility, and edit permission, then run `notis apps link <returned-id> .` before database reconciliation. Never retry an outcome-unknown create; reconcile read-only and stop on ambiguity or any returned identity/scope mismatch.
|
|
167
|
-
8. **Reconcile hosted database schemas safely.** Read the exact app detail and current schemas first; mutate only missing or changed declarations. For creation, pass the exact app id in the database tool's `app` argument. For an update, resolve the exact `database_id`, verify its `owner_app_id` equals the linked app id, update by that `database_id`, then read back slug, owner, and schema. Apply only backward-compatible schema expansion before deployment. Stage breaking or destructive changes through an expand-contract sequence and obtain the required destructive approval; never make the currently deployed bundle incompatible before its replacement is live.
|
|
168
|
-
9. **Deploy and prove the hosted sandbox result.** Use only the exact id established in step 7. Run `npx --package @notis_ai/cli@latest -- notis apps deploy`, read the matching row back with `npx --package @notis_ai/cli@latest -- notis apps list --json`, confirm its id and deployed version, then run `npx --yes --package @notis_ai/cli@latest --package agent-browser@latest -- notis apps verify --mode live`. Return that row's exact profile-appropriate `portal_url` only after every proof passes. Report state precisely: a definite pre-commit rejection is **tested but not deployed**; a timeout/network/incomplete mutation response is **tested, deployment outcome unknown**; a confirmed deploy followed by failed readback is **deployed but not remotely verified**; a failed live check is **deployed but live verification failed**. Never retry an outcome-unknown mutation.
|
|
169
|
-
10. **Submit only after confirmation.** When the user explicitly confirms the current App Details page is ready, ensure the approved state is deployed, then run `npx --package @notis_ai/cli@latest -- notis apps publish --confirm-ready`. The command submits Team apps immediately or opens the Public Store registry review PR. Without that confirmation, stop after deploy.
|
|
170
|
-
|
|
171
|
-
### Quick start
|
|
172
|
-
|
|
173
|
-
Choose the local or hosted finish after verification. Local deployment is
|
|
174
|
-
user-gated; hosted-sandbox deployment is the default for app create or edit
|
|
175
|
-
tasks unless the user explicitly requests preview-only, read-only, or no deploy.
|
|
176
|
-
|
|
177
|
-
```bash
|
|
178
|
-
# 1. Pick a published Store app as the scaffold (catalog comes from the public registry)
|
|
179
|
-
# The project lands in ~/.notis/apps/<slug>; append a directory argument when
|
|
180
|
-
# the user wants the app in a repo they track.
|
|
181
|
-
npx --package @notis_ai/cli@latest -- notis apps scaffolds list
|
|
182
|
-
npx --package @notis_ai/cli@latest -- notis apps init "My App" --from <slug>
|
|
183
|
-
cd ~/.notis/apps/my-app
|
|
184
|
-
npm install
|
|
185
|
-
|
|
186
|
-
# 2. LOCAL COMPUTER: register with Desktop and iterate. After the user approves
|
|
187
|
-
# deployment, run deploy directly to promote the existing dev_app_id.
|
|
188
|
-
npx --package @notis_ai/cli@latest -- notis apps dev
|
|
189
|
-
# ... user tests the DEV-badged app ...
|
|
190
|
-
npx --package @notis_ai/cli@latest -- notis apps build
|
|
191
|
-
npx --package @notis_ai/cli@latest -- notis apps screenshot
|
|
192
|
-
npx --package @notis_ai/cli@latest -- notis apps verify
|
|
193
|
-
npx --package @notis_ai/cli@latest -- notis apps deploy
|
|
194
|
-
```
|
|
195
|
-
|
|
196
|
-
Hosted sandbox finish — never run `apps dev`. Test first, then reconcile the
|
|
197
|
-
exact app identity and changed databases before deployment:
|
|
198
|
-
|
|
199
|
-
```bash
|
|
200
|
-
npm exec --yes --package agent-browser@latest -- agent-browser install
|
|
201
|
-
npx --yes --package @notis_ai/cli@latest --package agent-browser@latest -- notis apps screenshot
|
|
202
|
-
npx --package @notis_ai/cli@latest -- notis apps build
|
|
203
|
-
npx --yes --package @notis_ai/cli@latest --package agent-browser@latest -- notis apps verify
|
|
204
|
-
# New unlinked app only: reconcile exact canonical slug with apps list. If no
|
|
205
|
-
# match exists, create once with the config title and verify the returned slug.
|
|
206
|
-
npx --package @notis_ai/cli@latest -- notis apps list --json
|
|
207
|
-
# Before create, prove canonicalize(config.title) == config.name.
|
|
208
|
-
npx --package @notis_ai/cli@latest -- notis apps create "<canonical-config-title>" . --json
|
|
209
|
-
# Compare schemas, then create only missing databases or update changed ones by
|
|
210
|
-
# verified database_id and read back owner/schema before continuing.
|
|
211
|
-
npx --package @notis_ai/cli@latest -- notis apps deploy
|
|
212
|
-
npx --package @notis_ai/cli@latest -- notis apps list --json
|
|
213
|
-
npx --yes --package @notis_ai/cli@latest --package agent-browser@latest -- notis apps verify --mode live
|
|
214
|
-
```
|
|
215
|
-
|
|
216
|
-
Store submission on either path remains a separate approval-gated action:
|
|
217
|
-
|
|
218
|
-
```bash
|
|
219
|
-
npx --package @notis_ai/cli@latest -- notis apps publish --confirm-ready
|
|
220
|
-
```
|
|
221
|
-
|
|
222
|
-
For an existing app, link the checkout first so every later command uses the
|
|
223
|
-
same profile-scoped installed identity:
|
|
224
|
-
|
|
225
|
-
```bash
|
|
226
|
-
npx --package @notis_ai/cli@latest -- notis apps link <app-id> .
|
|
227
|
-
npx --package @notis_ai/cli@latest -- notis apps deploy
|
|
228
|
-
```
|
|
229
|
-
|
|
230
|
-
Or if editing an installed app locally:
|
|
231
|
-
|
|
232
|
-
```bash
|
|
233
|
-
npx --package @notis_ai/cli@latest -- notis apps list
|
|
234
|
-
npx --package @notis_ai/cli@latest -- notis apps pull <installed-app-id>
|
|
235
|
-
cd ~/.notis/apps/my-app
|
|
236
|
-
npm install
|
|
237
|
-
# Increment package.json notisAppVersion above the pulled online release.
|
|
238
|
-
npx --package @notis_ai/cli@latest -- notis apps dev
|
|
239
|
-
npx --package @notis_ai/cli@latest -- notis apps build
|
|
240
|
-
npx --package @notis_ai/cli@latest -- notis apps verify
|
|
241
|
-
npx --package @notis_ai/cli@latest -- notis apps link <installed-app-id> .
|
|
242
|
-
# Only after the user explicitly asks to deploy:
|
|
243
|
-
npx --package @notis_ai/cli@latest -- notis apps deploy
|
|
244
|
-
```
|
|
245
|
-
|
|
246
|
-
In a hosted sandbox, use the same pull/build/verify sequence but omit `apps
|
|
247
|
-
dev`; bootstrap `agent-browser`, materialize any new or changed database schemas
|
|
248
|
-
against the exact linked id, deploy automatically after verification, read back
|
|
249
|
-
the exact app id/version and `portal_url` with `apps list --json`, run live verification, and return that
|
|
250
|
-
exact Portal URL. Never run `apps publish --confirm-ready` without separate
|
|
251
|
-
Store approval.
|
|
252
|
-
|
|
253
|
-
## Building an App
|
|
254
|
-
|
|
255
|
-
### Step 1: Define the config
|
|
256
|
-
|
|
257
|
-
Create `notis.config.ts` with:
|
|
258
|
-
- **name** -- Stable machine identity in lowercase kebab-case, such as `link-building`; do not use display casing here
|
|
259
|
-
- **title** -- Human-facing app name with deliberate casing, such as `Link Building`; preserve brands and acronyms exactly
|
|
260
|
-
- **databases** -- Slug references to existing Notis databases
|
|
261
|
-
- **routes** -- Route-first sidebar entries with explicit `slug`, optional `parentSlug`, and optional `collection.sidebar` tree config
|
|
262
|
-
- **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.
|
|
263
|
-
|
|
264
|
-
For collection-backed sidebars, use the route schema directly:
|
|
265
|
-
|
|
266
|
-
```ts
|
|
267
|
-
routes: [
|
|
268
|
-
{
|
|
269
|
-
path: '/',
|
|
270
|
-
slug: 'notes',
|
|
271
|
-
name: 'Notes',
|
|
272
|
-
icon: 'phosphor:note-pencil',
|
|
273
|
-
default: true,
|
|
274
|
-
collection: {
|
|
275
|
-
database: 'notes',
|
|
276
|
-
titleProperty: 'Title',
|
|
277
|
-
parentProperty: 'Parent note',
|
|
278
|
-
sidebar: {
|
|
279
|
-
mode: 'tree',
|
|
280
|
-
allowCreate: true,
|
|
281
|
-
},
|
|
282
|
-
},
|
|
283
|
-
},
|
|
284
|
-
]
|
|
285
|
-
```
|
|
286
|
-
|
|
287
|
-
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'`.
|
|
288
|
-
|
|
289
|
-
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.
|
|
290
|
-
|
|
291
|
-
### Step 2: Build pages
|
|
292
|
-
|
|
293
|
-
Standard React pages in `app/`. Use generic SDK tool hooks for data and build on top of the scaffolded shadcn components and portal shell classes (`notis-app-shell`, `notis-app-surface`):
|
|
294
|
-
|
|
295
|
-
```tsx
|
|
296
|
-
import { useDocuments, ViewSkeleton } from '@notis/sdk';
|
|
297
|
-
import { Card } from '@/components/ui/card';
|
|
298
|
-
|
|
299
|
-
export default function TasksPage() {
|
|
300
|
-
const tasks = useDocuments('tasks', { pageSize: 25 });
|
|
301
|
-
return <section className="space-y-4 p-6">
|
|
302
|
-
<h1 className="text-xl font-semibold">Tasks</h1>
|
|
303
|
-
{tasks.error && <p role="alert">{tasks.error.message} <button onClick={tasks.refetch}>Retry</button></p>}
|
|
304
|
-
{tasks.loading ? <ViewSkeleton variant="table" rows={5} /> : tasks.hasData ? (
|
|
305
|
-
tasks.documents.length ? tasks.documents.map((task) => (
|
|
306
|
-
<Card key={task.id} className="p-4"><h2>{task.title || 'Untitled'}</h2></Card>
|
|
307
|
-
)) : <p>No tasks yet.</p>
|
|
308
|
-
) : null}
|
|
309
|
-
</section>;
|
|
310
|
-
}
|
|
311
|
-
```
|
|
312
|
-
|
|
313
|
-
### Instant-view loading contract (required)
|
|
314
|
-
|
|
315
|
-
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.
|
|
316
|
-
|
|
317
|
-
| State | Required UI |
|
|
318
|
-
| --- | --- |
|
|
319
|
-
| 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...`. |
|
|
320
|
-
| Cached view / successful empty result | Render synchronously from the shared SDK cache. Empty results are real cached results. |
|
|
321
|
-
| 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. |
|
|
322
|
-
| Explicit Save / Upload / submitted search | Progress belongs in that button or affected section. Disable only the conflicting action. |
|
|
323
|
-
| Failed read | Show a scoped error and Retry; keep usable cached content. Never show an empty-state message before `hasData` is true. |
|
|
324
|
-
|
|
325
|
-
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`.
|
|
326
|
-
|
|
327
|
-
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.
|
|
328
|
-
|
|
329
|
-
`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.
|
|
330
|
-
|
|
331
|
-
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.
|
|
332
|
-
|
|
333
|
-
### Discovering database schema
|
|
334
|
-
|
|
335
|
-
Before writing app code, inspect the database schema to know what properties exist:
|
|
336
|
-
|
|
337
|
-
```bash
|
|
338
|
-
npx --package @notis_ai/cli@latest -- notis tools search "list Notis databases"
|
|
339
|
-
npx --package @notis_ai/cli@latest -- notis tools exec LOCAL_NOTIS_DATABASE_LIST_DATABASES --arguments '{}'
|
|
340
|
-
npx --package @notis_ai/cli@latest -- notis tools exec LOCAL_NOTIS_DATABASE_GET_DATABASE --arguments '{"database_slug":"social_media_calendar"}'
|
|
341
|
-
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}}'
|
|
342
|
-
```
|
|
343
|
-
|
|
344
|
-
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.
|
|
345
|
-
|
|
346
|
-
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.
|
|
347
|
-
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:
|
|
348
|
-
|
|
349
|
-
```tsx
|
|
350
|
-
const upsertTask = useTool<Record<string, unknown>, { document?: { id: string } }>('LOCAL_NOTIS_DATABASE_UPSERT_TASKS');
|
|
351
|
-
|
|
352
|
-
await upsertTask.call({
|
|
353
|
-
title: 'My Task',
|
|
354
|
-
Status: 'Todo',
|
|
355
|
-
Priority: 'P1',
|
|
356
|
-
Due: '2025-04-01',
|
|
357
|
-
Done: false,
|
|
358
|
-
Count: 5,
|
|
359
|
-
});
|
|
360
|
-
```
|
|
361
|
-
|
|
362
|
-
Do NOT pass Notion-style wrappers (`{select: {name: "Todo"}}`) when upserting.
|
|
363
|
-
|
|
364
|
-
### Design rules
|
|
365
|
-
|
|
366
|
-
- Start from the scaffolded `@/components/ui/*` components before writing new UI primitives.
|
|
367
|
-
- Use restrained portal surfaces: `bg-background`, `bg-card`, `border-border`, `text-foreground`, `text-muted-foreground`.
|
|
368
|
-
- Keep layouts compact and dashboard-like. Prefer cards, sections, badges, and tables over marketing-style hero treatments.
|
|
369
|
-
- Respect the portal theme. Do not hardcode dark mode or create an app-specific palette.
|
|
370
|
-
- If a screen looks like a standalone microsite instead of a portal tool, it is too custom.
|
|
371
|
-
- 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.
|
|
372
|
-
- Never indicate selected items with a heavy left-border bar (e.g. `border-l-2 border-l-foreground` paired with a muted background). It looks dated and clashes with the portal chrome. Use a single subtle background change (`bg-muted` for selected, `hover:bg-muted/50` for hover) and let typography or an icon carry the rest of the state.
|
|
373
|
-
- 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.
|
|
374
|
-
|
|
375
|
-
### Sidebar invariants
|
|
376
|
-
|
|
377
|
-
- When a user asks for folders, sections, or hierarchy in the app sidebar, express that through `routes` and `collection.sidebar` in `notis.config.ts`.
|
|
378
|
-
- Treat an existing collection-tree sidebar as a locked structural requirement unless the user explicitly asks to change navigation architecture.
|
|
379
|
-
- If the sidebar appears missing for the substituted DEV entry or deployed portal build, do not silently redesign around it. Preserve the manifest contract, call out the discrepancy, and treat it as a portal/runtime bug.
|
|
380
|
-
|
|
381
|
-
### Step 3: Root layout
|
|
382
|
-
|
|
383
|
-
```tsx
|
|
384
|
-
import { NotisProvider } from '@notis/sdk';
|
|
385
|
-
import '@notis/sdk/styles.css';
|
|
386
|
-
import './globals.css';
|
|
387
|
-
|
|
388
|
-
export default function AppShell({ children }: { children: React.ReactNode }) {
|
|
389
|
-
return <NotisProvider>{children}</NotisProvider>;
|
|
390
|
-
}
|
|
391
|
-
```
|
|
392
|
-
|
|
393
|
-
## Manifest Format
|
|
394
|
-
|
|
395
|
-
Generated by `npx --package @notis_ai/cli@latest -- notis apps build` at `.notis/output/manifest.json`:
|
|
396
|
-
|
|
397
|
-
```json
|
|
398
|
-
{
|
|
399
|
-
"version": 1,
|
|
400
|
-
"spec_version": 4,
|
|
401
|
-
"app": { "name": "My App", "slug": "my-app", "title": "My App", "description": "...", "icon": "phosphor:..." },
|
|
402
|
-
"routes": [
|
|
403
|
-
{
|
|
404
|
-
"path": "/",
|
|
405
|
-
"slug": "index",
|
|
406
|
-
"name": "Dashboard",
|
|
407
|
-
"icon": "phosphor:squares-four",
|
|
408
|
-
"default": true,
|
|
409
|
-
"export_name": "index",
|
|
410
|
-
"collection": null
|
|
411
|
-
}
|
|
412
|
-
],
|
|
413
|
-
"bundle": {
|
|
414
|
-
"js": "bundle/app.js",
|
|
415
|
-
"css": "bundle/app.css"
|
|
416
|
-
},
|
|
417
|
-
"databases": ["tasks", { "slug": "templates", "seed_documents": true }],
|
|
418
|
-
"tools": ["LOCAL_NOTIS_DATABASE_QUERY"]
|
|
419
|
-
}
|
|
420
|
-
```
|
|
421
|
-
|
|
422
|
-
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.
|
|
423
|
-
|
|
424
|
-
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.
|
|
425
|
-
|
|
426
|
-
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.
|
|
427
|
-
|
|
428
|
-
## Database Schema
|
|
429
|
-
|
|
430
|
-
### apps table
|
|
431
|
-
|
|
432
|
-
| Column | Type | Description |
|
|
433
|
-
|---|---|---|
|
|
434
|
-
| id | uuid PK | App ID |
|
|
435
|
-
| user_id | uuid FK | Owner |
|
|
436
|
-
| team_id | uuid FK | Team (nullable) |
|
|
437
|
-
| name | text | Display name |
|
|
438
|
-
| slug | text UNIQUE | URL slug |
|
|
439
|
-
| description | text | App description |
|
|
440
|
-
| icon | text | Phosphor icon (e.g. "phosphor:list") |
|
|
441
|
-
| status | text | draft, active, archived |
|
|
442
|
-
| visibility | text | private, team |
|
|
443
|
-
| manifest | jsonb | Latest deployed manifest |
|
|
444
|
-
| current_version | integer | Version counter |
|
|
445
|
-
| source_listing_id | uuid FK | Source App Store listing for installed store apps; cleared when submitted as a derivative |
|
|
446
|
-
| installed_snapshot | jsonb | Store-installed baseline used for update/reset comparison |
|
|
447
|
-
| customization_overlay | jsonb | User changes over the installed store baseline |
|
|
448
|
-
| update_status | text | up_to_date, update_available, needs_resolution, update_failed |
|
|
449
|
-
| bundled_automation_ids | uuid[] | Linked automations |
|
|
450
|
-
| bundled_skill_ids | uuid[] | Linked skills |
|
|
451
|
-
|
|
452
|
-
### databases ownership
|
|
453
|
-
|
|
454
|
-
Every row in the `databases` table carries `owner_app_id` (uuid FK to
|
|
455
|
-
`apps.id`, `ON DELETE CASCADE`): a database belongs to exactly one app, and
|
|
456
|
-
deleting the app deletes its databases and their documents (`documents` cascade
|
|
457
|
-
from `databases`). Install, dev materialization, and store updates stamp
|
|
458
|
-
`owner_app_id` automatically; standalone creation requires the `app` argument.
|
|
459
|
-
|
|
460
|
-
### Storage (Supabase)
|
|
461
|
-
|
|
462
|
-
Files stored in `app-code` bucket at `{app_id}/v{version}/`:
|
|
463
|
-
- `manifest.json`
|
|
464
|
-
- `bundle/app.js`
|
|
465
|
-
- `bundle/app.css`
|
|
466
|
-
|
|
467
|
-
Editable source snapshots are stored in the private `app-source` bucket at
|
|
468
|
-
`{app_id}/v{version}/`. Portal App Store listing screenshots are uploaded to
|
|
469
|
-
the public `app-listing-assets` bucket before submission.
|
|
470
|
-
|
|
471
|
-
### Related tables
|
|
472
|
-
|
|
473
|
-
- **databases** -- Apps reference these rows by slug. Schema lives on the database row (`schema_metadata` / `original_fields`), not in the app manifest.
|
|
474
|
-
- **documents** -- `database_id` links to databases. Properties in `properties` jsonb.
|
|
475
|
-
- **app_store_listings** -- Snapshots for publishing to the app store.
|
|
476
|
-
- **app_submissions** -- Portal review submissions keyed to an app source version and registry slug.
|
|
477
|
-
|
|
478
|
-
## Server Endpoints
|
|
479
|
-
|
|
480
|
-
| Endpoint | Method | Purpose |
|
|
481
|
-
|---|---|---|
|
|
482
|
-
| `/portal_views/get` | GET | Route detail + runtime descriptor with signed bundle URLs |
|
|
483
|
-
| `/portal_views/runtime_query` | POST | Proxy tool calls and DB operations |
|
|
484
|
-
| `/portal_views/collection_items` | GET | List collection items |
|
|
485
|
-
| `/portal_views/collection_tree` | GET | List normalized collection tree nodes for a tree sidebar route |
|
|
486
|
-
| `/portal_views/collection_tree/create` | POST | Create a root or child collection row from the sidebar |
|
|
487
|
-
| `/portal_views/collection_tree/rename` | POST | Rename a collection tree item inline |
|
|
488
|
-
| `/portal_views/collection_tree/delete` | POST | Delete a collection tree item from the sidebar |
|
|
489
|
-
| `/portal_apps/list` | GET | List apps |
|
|
490
|
-
| `/portal_apps/get` | GET | Get app detail |
|
|
491
|
-
| `/portal_apps/publish` | POST | Submit a deployed app source snapshot for public store review |
|
|
492
|
-
| `/portal_apps/listing_assets/upload` | POST | Legacy pre-manifest screenshot upload; do not use for current manifest-media workflows |
|
|
493
|
-
| `/portal_apps/submissions` | GET/PATCH | List or edit App Store submissions |
|
|
494
|
-
| `/portal_apps/submissions/withdraw` | POST | Close a pending App Store submission |
|
|
495
|
-
| `/cli_tools` | POST | CLI tool execution (save_app_files, create_app, etc.) |
|
|
496
|
-
|
|
497
|
-
## SDK Hook Reference
|
|
498
|
-
|
|
499
|
-
All hooks and components below are imported from `@notis/sdk`. `NotisProvider`
|
|
500
|
-
already installs `ShortcutProvider`; app code should not add a second provider.
|
|
501
|
-
|
|
502
|
-
| API | Signature | Description |
|
|
503
|
-
|-----|-----------|-------------|
|
|
504
|
-
| `useNotis()` | `() => { app, route, databases, collectionItem, resourceId, ready }` | App metadata, current route, selected collection item, decoded exact-resource id, ready state |
|
|
505
|
-
| `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 |
|
|
506
|
-
| `useTools()` | `() => { tools, loading }` | List available tools |
|
|
507
|
-
| `useNotisNavigation()` | `() => { toRoute, toDocument, toApp }` | Navigate between routes (including `toRoute(path, { resourceId })`), documents, or the app root |
|
|
508
|
-
| `useTopBarSearch(opts)` | `({ value, onChange, placeholder?, onSubmit? }) => { setLoading }` | Bind the current view to the Portal-owned top-bar search input |
|
|
509
|
-
| `useBackend()` | `() => { request }` | Raw backend request proxy with JWT auth |
|
|
510
|
-
| `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 (dev harness, vite preview) -- keep a manual refresh for those |
|
|
511
|
-
| `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 |
|
|
512
|
-
| `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 |
|
|
513
|
-
| `useActiveResource(resource)` | `(ContextResource \| null) => void` | Publish the record currently open in the app so manager handover and context menus stay grounded |
|
|
514
|
-
| `useCollectionInteractions(opts)` | `(opts) => CollectionInteractionController` | Keyboard navigation, active-row state, range/toggle selection, marquee selection, and action dispatch for collection UIs |
|
|
515
|
-
| `useShortcuts(definitions, opts?)` | `(definitions, opts?) => void` | Register scoped keyboard shortcuts. Editable targets are ignored unless explicitly allowed; use `ShortcutHints` to display them |
|
|
516
|
-
| `MarkdownEditor` | `(NotisMarkdownEditorProps) => ReactElement` | Use the host editor with app-owned persistence, stable `resourceKey`, revision-aware `onSave`, and optional `onUploadFile` returning a durable URL |
|
|
517
|
-
| `NotisSelectionBoundary` | `(NotisSelectionBoundaryProps) => ReactElement` | Attach structured, explicitly untrusted app/resource/selection context to selected content and copy operations |
|
|
518
|
-
| `SelectionCheckbox` / `SelectionMarquee` | components | Standard selection controls backed by `useCollectionInteractions` |
|
|
519
|
-
| `MultiSelectActionBar` | component | Standard bulk actions with pending/disabled state and shortcut support |
|
|
520
|
-
|
|
521
|
-
Import headless collection action types and helpers from
|
|
522
|
-
`@notis/sdk/interactions`. Keep an open detail view synchronized with
|
|
523
|
-
`useActiveResource`, and wrap its selectable content in
|
|
524
|
-
`NotisSelectionBoundary` so the manager receives both the active record and the
|
|
525
|
-
user's exact selection. For `MarkdownEditor`, keep `resourceKey` stable per
|
|
526
|
-
record, pass the latest revision back from `onSave`, reject revision conflicts
|
|
527
|
-
instead of overwriting newer data, and implement `onUploadFile` whenever the
|
|
528
|
-
editor should accept media or file blocks.
|
|
529
|
-
|
|
530
|
-
### App configuration additions
|
|
531
|
-
|
|
532
|
-
- `devSlug` is the stable local-development identity. Set it when a display
|
|
533
|
-
rename must not create a second DEV app; otherwise the CLI derives it from
|
|
534
|
-
`name`.
|
|
535
|
-
- `toolBindings` is only for provider-generated public tool names whose upstream
|
|
536
|
-
action cannot be reconstructed. Keep the exact final public `name` in
|
|
537
|
-
`tools`, then bind it to `providerToolName`; the public name remains the
|
|
538
|
-
permission boundary.
|
|
539
|
-
|
|
540
|
-
### Typed tool calls
|
|
541
|
-
|
|
542
|
-
`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:
|
|
543
|
-
|
|
544
|
-
```tsx
|
|
545
|
-
type QueryTasksArgs = { database_id?: string; database_slug?: string; query: { page_size?: number } };
|
|
546
|
-
interface TaskDoc {
|
|
547
|
-
title: string;
|
|
548
|
-
properties: {
|
|
549
|
-
Status: string;
|
|
550
|
-
Priority: string;
|
|
551
|
-
Due: string;
|
|
552
|
-
};
|
|
553
|
-
};
|
|
554
|
-
type QueryTasksResult = { documents: TaskDoc[] };
|
|
555
|
-
|
|
556
|
-
const queryTasks = useTool<QueryTasksArgs, QueryTasksResult>('LOCAL_NOTIS_DATABASE_QUERY');
|
|
557
|
-
const result = await queryTasks.call({ database_id: 'tasks-db-id', query: { page_size: 25 } });
|
|
558
|
-
// result.documents[0].properties.Status is typed as string
|
|
559
|
-
```
|
|
560
|
-
|
|
561
|
-
## Development Modes
|
|
562
|
-
|
|
563
|
-
### Hosted sandbox development
|
|
564
|
-
|
|
565
|
-
Do not run `apps dev` in a hosted sandbox. The sandbox filesystem is not on the
|
|
566
|
-
user's computer, so Desktop cannot mount it. Bootstrap sandbox `agent-browser`,
|
|
567
|
-
then build and verify before any remote mutation. For a create or edit request,
|
|
568
|
-
unless the user explicitly says preview-only, read-only, or no-deploy, resolve
|
|
569
|
-
one exact identity, safely materialize only missing or changed database schemas,
|
|
570
|
-
deploy, read back the exact app id/version, run live verification, and return
|
|
571
|
-
the exact Portal URL. Inspection, review, and diagnosis remain read-only.
|
|
572
|
-
|
|
573
|
-
### Canonical local development
|
|
574
|
-
|
|
575
|
-
```bash
|
|
576
|
-
npx --package @notis_ai/cli@latest -- notis apps dev
|
|
577
|
-
```
|
|
578
|
-
|
|
579
|
-
Runs the real desktop-local development workflow. The CLI should discover all apps in the target workspace and serve their bundles from loopback. Unpublished apps appear in the Electron Portal's Workspace group; linked apps substitute their installed entry only when the local `notisAppVersion` is strictly greater than the installed `release_version`.
|
|
580
|
-
|
|
581
|
-
## Testing
|
|
582
|
-
|
|
583
|
-
1. **Build validation**: `npx --package @notis_ai/cli@latest -- notis apps build` must succeed without errors. Vite surfaces TypeScript and bundling errors during this step.
|
|
584
|
-
2. **Headless render verification** (recommended after every build): run `npx --package @notis_ai/cli@latest -- notis apps verify` locally. In a hosted sandbox, first run `npm exec --yes --package agent-browser@latest -- agent-browser install`, then run `npx --yes --package @notis_ai/cli@latest --package agent-browser@latest -- notis apps verify`. It builds unless `--skip-build` is passed, spins up a loopback harness, drives `agent-browser` against every route, and reports per-route pass/fail with captured render errors and runtime calls.
|
|
585
|
-
3. **Local development acceptance**: Run `notis apps dev [folder]` once to register the root, then verify each signed-in Desktop instance independently. For an unpublished app, expect one DEV-badged Workspace row. For a linked app, first confirm local `notisAppVersion` is strictly greater than installed `release_version`, then expect one substituted DEV-badged row; equal or lower must keep the online row and bundle. Verify the default route renders and live edits appear without restarting the CLI or Desktop. Use `notis apps roots list` as the persistence proof. Loopback bundle health alone does not prove that an authenticated instance mounted or rendered the app.
|
|
586
|
-
4. **Post-deploy**: Read back the exact app id, version, and `portal_url` with `apps list --json`, run `apps verify --mode live`, verify the deployed bundle via `/portal_views/get` -> `runtime_descriptor.bundle.js_url`, and return that profile-appropriate exact Portal URL. A confirmed deploy followed by failed readback is deployed but not remotely verified; a failed live check is deployed but live verification failed. The portal renders app bundles directly as React components, so navigate to the app page when an authenticated browser is available.
|
|
587
|
-
|
|
588
|
-
### Headless harness verification
|
|
589
|
-
|
|
590
|
-
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.
|
|
591
|
-
|
|
592
|
-
#### What the harness catches that `npx --package @notis_ai/cli@latest -- notis apps build` does not
|
|
593
|
-
|
|
594
|
-
- Hooks that mount but throw on first read (`useTool` called with the wrong tool name or argument shape, accessing nested props that are undefined).
|
|
595
|
-
- 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.
|
|
596
|
-
- Tool names referenced by hooks but missing from `notis.config.ts -> tools`.
|
|
597
|
-
- Suspense / async boundaries that never resolve because a runtime stub returned the wrong shape.
|
|
598
|
-
- Render-time exceptions that the portal would surface as the `View crashed` error boundary.
|
|
599
|
-
|
|
600
|
-
#### What the harness does not catch
|
|
601
|
-
|
|
602
|
-
- 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.
|
|
603
|
-
- Visual regressions (use `agent-browser screenshot` + a baseline compare if you need this).
|
|
604
|
-
- 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.
|
|
605
|
-
|
|
606
|
-
## Troubleshooting
|
|
57
|
+
## Task guides
|
|
607
58
|
|
|
608
|
-
|
|
59
|
+
Read only the guide needed for this task. Relative links resolve in the skill bundle.
|
|
60
|
+
For hosted MCP, fetch the matching `notis://docs/notis-apps/references/<file>.md` URI
|
|
61
|
+
with resources/read or the available Notis resource-fetch tool; the root resource
|
|
62
|
+
also rewrites these links to their published URIs.
|
|
609
63
|
|
|
610
|
-
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
-
|
|
614
|
-
-
|
|
615
|
-
- **`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.
|
|
616
|
-
- **Properties are `undefined`**: Keep app-local result types for `useTool<TArgs, TResult>` and guard optional nested properties when reading live data.
|
|
64
|
+
- [Release-only delivery](references/release.md)
|
|
65
|
+
- [How Apps Are Built](references/architecture.md)
|
|
66
|
+
- [Building an App](references/design.md)
|
|
67
|
+
- [SDK Hook Reference](references/sdk.md)
|
|
68
|
+
- [Anti-patterns -- NEVER do these](references/troubleshooting.md)
|