@notis_ai/cli 0.2.0-beta.139.1 → 0.2.0-beta.145.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 +44 -20
- package/dist/agent-hooks/notis-agent-hook.mjs +4579 -1449
- package/dist/base-skills/notis-apps/SKILL.md +83 -55
- package/dist/base-skills/notis-cli/SKILL.md +8 -8
- package/package.json +2 -2
- package/skills/notis-apps/cli.md +38 -17
- package/src/command-specs/apps.js +871 -387
- package/src/command-specs/auth.js +47 -10
- package/src/command-specs/onboarding.js +11 -4
- package/src/runtime/app-dev-consumers.js +154 -0
- package/src/runtime/app-dev-host-lock.js +80 -0
- package/src/runtime/app-dev-roots.js +284 -0
- package/src/runtime/app-dev-server.js +212 -22
- package/src/runtime/app-dev-sessions.js +105 -145
- package/src/runtime/app-platform.js +1291 -165
- package/src/runtime/app-registry-scaffolds.js +367 -0
- package/src/runtime/login-listener.js +15 -0
- package/src/runtime/oauth.js +1506 -141
- package/src/runtime/output.js +7 -0
- package/src/runtime/ports.js +16 -0
- package/src/runtime/profiles.js +0 -5
- package/template/app/layout.tsx +5 -1
- package/template/notis.config.ts +1 -0
- package/template/packages/sdk/package.json +2 -1
- package/template/packages/sdk/src/components/MarkdownEditor.tsx +121 -0
- package/template/packages/sdk/src/components/MultiSelectActionBar.tsx +37 -119
- package/template/packages/sdk/src/components/MultiSelectCheckbox.tsx +5 -1
- package/template/packages/sdk/src/components/MultiSelectDragOverlay.tsx +1 -1
- package/template/packages/sdk/src/components/NotisSelectionBoundary.tsx +55 -0
- package/template/packages/sdk/src/components/ShortcutHints.tsx +56 -0
- package/template/packages/sdk/src/config.ts +19 -0
- package/template/packages/sdk/src/hooks/useActiveResource.ts +19 -0
- package/template/packages/sdk/src/hooks/useCollectionInteractions.ts +709 -0
- package/template/packages/sdk/src/hooks/useHandover.ts +3 -0
- package/template/packages/sdk/src/hooks/useMultiSelect.ts +44 -482
- package/template/packages/sdk/src/hooks/useTool.ts +5 -4
- package/template/packages/sdk/src/index.ts +47 -2
- package/template/packages/sdk/src/interactions/actions.ts +46 -0
- package/template/packages/sdk/src/interactions/shortcuts.tsx +628 -0
- package/template/packages/sdk/src/interactions.ts +41 -0
- package/template/packages/sdk/src/provider.tsx +2 -1
- package/template/packages/sdk/src/runtime.ts +85 -3
- package/dist/scaffolds/notis-database/CHANGELOG.md +0 -5
- package/dist/scaffolds/notis-database/app/globals.css +0 -44
- package/dist/scaffolds/notis-database/app/layout.tsx +0 -6
- package/dist/scaffolds/notis-database/app/page.tsx +0 -1088
- package/dist/scaffolds/notis-database/components/ui/badge.tsx +0 -28
- package/dist/scaffolds/notis-database/components/ui/button.tsx +0 -53
- package/dist/scaffolds/notis-database/components/ui/card.tsx +0 -56
- package/dist/scaffolds/notis-database/components/ui/table.tsx +0 -120
- package/dist/scaffolds/notis-database/components.json +0 -20
- package/dist/scaffolds/notis-database/index.html +0 -12
- package/dist/scaffolds/notis-database/lib/types.ts +0 -132
- package/dist/scaffolds/notis-database/lib/utils.ts +0 -6
- package/dist/scaffolds/notis-database/metadata/screenshot-1.png +0 -0
- package/dist/scaffolds/notis-database/metadata/screenshot-2.png +0 -0
- package/dist/scaffolds/notis-database/metadata/screenshot-3.png +0 -0
- package/dist/scaffolds/notis-database/metadata/screenshot-4.png +0 -0
- package/dist/scaffolds/notis-database/metadata/screenshot-5.png +0 -0
- package/dist/scaffolds/notis-database/metadata/screenshot-fixtures.json +0 -1839
- package/dist/scaffolds/notis-database/notis.config.ts +0 -73
- package/dist/scaffolds/notis-database/package-lock.json +0 -3935
- package/dist/scaffolds/notis-database/package.json +0 -32
- package/dist/scaffolds/notis-database/packages/sdk/package.json +0 -36
- package/dist/scaffolds/notis-database/packages/sdk/src/components/DocumentEditor.tsx +0 -93
- package/dist/scaffolds/notis-database/packages/sdk/src/components/Markdown.tsx +0 -60
- package/dist/scaffolds/notis-database/packages/sdk/src/components/MultiSelectActionBar.tsx +0 -278
- package/dist/scaffolds/notis-database/packages/sdk/src/components/MultiSelectCheckbox.tsx +0 -91
- package/dist/scaffolds/notis-database/packages/sdk/src/components/MultiSelectDragOverlay.tsx +0 -39
- package/dist/scaffolds/notis-database/packages/sdk/src/config.ts +0 -234
- package/dist/scaffolds/notis-database/packages/sdk/src/documents.ts +0 -250
- package/dist/scaffolds/notis-database/packages/sdk/src/hooks/useBackend.ts +0 -41
- package/dist/scaffolds/notis-database/packages/sdk/src/hooks/useCloudComputer.ts +0 -97
- package/dist/scaffolds/notis-database/packages/sdk/src/hooks/useDatabaseSchema.ts +0 -85
- package/dist/scaffolds/notis-database/packages/sdk/src/hooks/useDatabaseSubscription.ts +0 -76
- package/dist/scaffolds/notis-database/packages/sdk/src/hooks/useDocument.ts +0 -78
- package/dist/scaffolds/notis-database/packages/sdk/src/hooks/useDocuments.ts +0 -121
- package/dist/scaffolds/notis-database/packages/sdk/src/hooks/useHandover.ts +0 -75
- package/dist/scaffolds/notis-database/packages/sdk/src/hooks/useMultiSelect.ts +0 -539
- package/dist/scaffolds/notis-database/packages/sdk/src/hooks/useNotis.ts +0 -34
- package/dist/scaffolds/notis-database/packages/sdk/src/hooks/useNotisNavigation.ts +0 -49
- package/dist/scaffolds/notis-database/packages/sdk/src/hooks/useTool.ts +0 -64
- package/dist/scaffolds/notis-database/packages/sdk/src/hooks/useTools.ts +0 -56
- package/dist/scaffolds/notis-database/packages/sdk/src/hooks/useTopBarSearch.ts +0 -73
- package/dist/scaffolds/notis-database/packages/sdk/src/hooks/useUpsertDocument.ts +0 -95
- package/dist/scaffolds/notis-database/packages/sdk/src/index.ts +0 -100
- package/dist/scaffolds/notis-database/packages/sdk/src/provider.tsx +0 -43
- package/dist/scaffolds/notis-database/packages/sdk/src/runtime.ts +0 -351
- package/dist/scaffolds/notis-database/packages/sdk/src/styles.css +0 -186
- package/dist/scaffolds/notis-database/packages/sdk/src/ui.ts +0 -15
- package/dist/scaffolds/notis-database/packages/sdk/src/vite.ts +0 -56
- package/dist/scaffolds/notis-database/packages/sdk/tsconfig.json +0 -15
- package/dist/scaffolds/notis-database/postcss.config.mjs +0 -8
- package/dist/scaffolds/notis-database/src/dev-main.tsx +0 -23
- package/dist/scaffolds/notis-database/src/mock-runtime.ts +0 -561
- package/dist/scaffolds/notis-database/tailwind.config.ts +0 -59
- package/dist/scaffolds/notis-database/tsconfig.json +0 -23
- package/dist/scaffolds/notis-database/vite.config.ts +0 -22
- package/dist/scaffolds/notis-journal/CHANGELOG.md +0 -29
- package/dist/scaffolds/notis-journal/app/globals.css +0 -37
- package/dist/scaffolds/notis-journal/app/insights/page.tsx +0 -513
- package/dist/scaffolds/notis-journal/app/journal-core.tsx +0 -362
- package/dist/scaffolds/notis-journal/app/journal-ui.tsx +0 -337
- package/dist/scaffolds/notis-journal/app/layout.tsx +0 -6
- package/dist/scaffolds/notis-journal/app/page.tsx +0 -486
- package/dist/scaffolds/notis-journal/components/ui/badge.tsx +0 -28
- package/dist/scaffolds/notis-journal/components/ui/button.tsx +0 -53
- package/dist/scaffolds/notis-journal/components/ui/card.tsx +0 -56
- package/dist/scaffolds/notis-journal/components.json +0 -20
- package/dist/scaffolds/notis-journal/index.html +0 -12
- package/dist/scaffolds/notis-journal/lib/utils.ts +0 -6
- package/dist/scaffolds/notis-journal/metadata/screenshot-1.png +0 -0
- package/dist/scaffolds/notis-journal/metadata/screenshot-2.png +0 -0
- package/dist/scaffolds/notis-journal/metadata/screenshot-3.png +0 -0
- package/dist/scaffolds/notis-journal/metadata/screenshot-4.png +0 -0
- package/dist/scaffolds/notis-journal/metadata/screenshot-5.png +0 -0
- package/dist/scaffolds/notis-journal/metadata/screenshot-6.png +0 -0
- package/dist/scaffolds/notis-journal/metadata/screenshot-fixtures.json +0 -132
- package/dist/scaffolds/notis-journal/notis.config.ts +0 -93
- package/dist/scaffolds/notis-journal/package-lock.json +0 -4615
- package/dist/scaffolds/notis-journal/package.json +0 -34
- package/dist/scaffolds/notis-journal/packages/sdk/package.json +0 -36
- package/dist/scaffolds/notis-journal/packages/sdk/src/components/DocumentEditor.tsx +0 -93
- package/dist/scaffolds/notis-journal/packages/sdk/src/components/Markdown.tsx +0 -60
- package/dist/scaffolds/notis-journal/packages/sdk/src/components/MultiSelectActionBar.tsx +0 -278
- package/dist/scaffolds/notis-journal/packages/sdk/src/components/MultiSelectCheckbox.tsx +0 -91
- package/dist/scaffolds/notis-journal/packages/sdk/src/components/MultiSelectDragOverlay.tsx +0 -39
- package/dist/scaffolds/notis-journal/packages/sdk/src/config.ts +0 -234
- package/dist/scaffolds/notis-journal/packages/sdk/src/documents.ts +0 -250
- package/dist/scaffolds/notis-journal/packages/sdk/src/hooks/useBackend.ts +0 -41
- package/dist/scaffolds/notis-journal/packages/sdk/src/hooks/useCloudComputer.ts +0 -97
- package/dist/scaffolds/notis-journal/packages/sdk/src/hooks/useDatabaseSchema.ts +0 -85
- package/dist/scaffolds/notis-journal/packages/sdk/src/hooks/useDatabaseSubscription.ts +0 -76
- package/dist/scaffolds/notis-journal/packages/sdk/src/hooks/useDocument.ts +0 -78
- package/dist/scaffolds/notis-journal/packages/sdk/src/hooks/useDocuments.ts +0 -121
- package/dist/scaffolds/notis-journal/packages/sdk/src/hooks/useHandover.ts +0 -75
- package/dist/scaffolds/notis-journal/packages/sdk/src/hooks/useMultiSelect.ts +0 -539
- package/dist/scaffolds/notis-journal/packages/sdk/src/hooks/useNotis.ts +0 -34
- package/dist/scaffolds/notis-journal/packages/sdk/src/hooks/useNotisNavigation.ts +0 -49
- package/dist/scaffolds/notis-journal/packages/sdk/src/hooks/useTool.ts +0 -64
- package/dist/scaffolds/notis-journal/packages/sdk/src/hooks/useTools.ts +0 -56
- package/dist/scaffolds/notis-journal/packages/sdk/src/hooks/useTopBarSearch.ts +0 -73
- package/dist/scaffolds/notis-journal/packages/sdk/src/hooks/useUpsertDocument.ts +0 -95
- package/dist/scaffolds/notis-journal/packages/sdk/src/index.ts +0 -100
- package/dist/scaffolds/notis-journal/packages/sdk/src/provider.tsx +0 -43
- package/dist/scaffolds/notis-journal/packages/sdk/src/runtime.ts +0 -351
- package/dist/scaffolds/notis-journal/packages/sdk/src/styles.css +0 -186
- package/dist/scaffolds/notis-journal/packages/sdk/src/ui.ts +0 -15
- package/dist/scaffolds/notis-journal/packages/sdk/src/vite.ts +0 -56
- package/dist/scaffolds/notis-journal/packages/sdk/tsconfig.json +0 -15
- package/dist/scaffolds/notis-journal/postcss.config.mjs +0 -8
- package/dist/scaffolds/notis-journal/skills/journal-onboarding/SKILL.md +0 -120
- package/dist/scaffolds/notis-journal/src/dev-main.tsx +0 -58
- package/dist/scaffolds/notis-journal/src/mock-runtime.ts +0 -199
- package/dist/scaffolds/notis-journal/tailwind.config.ts +0 -58
- package/dist/scaffolds/notis-journal/tsconfig.json +0 -23
- package/dist/scaffolds/notis-journal/vite.config.ts +0 -10
- package/dist/scaffolds/notis-notes/CHANGELOG.md +0 -5
- package/dist/scaffolds/notis-notes/app/globals.css +0 -3
- package/dist/scaffolds/notis-notes/app/layout.tsx +0 -6
- package/dist/scaffolds/notis-notes/app/page.tsx +0 -1943
- package/dist/scaffolds/notis-notes/app/phosphor-icons.ts +0 -596
- package/dist/scaffolds/notis-notes/components/ui/badge.tsx +0 -28
- package/dist/scaffolds/notis-notes/components/ui/button.tsx +0 -53
- package/dist/scaffolds/notis-notes/components/ui/card.tsx +0 -56
- package/dist/scaffolds/notis-notes/components.json +0 -20
- package/dist/scaffolds/notis-notes/lib/utils.ts +0 -6
- package/dist/scaffolds/notis-notes/lib/visible-properties.ts +0 -144
- package/dist/scaffolds/notis-notes/metadata/screenshot-1.png +0 -0
- package/dist/scaffolds/notis-notes/metadata/screenshot-2.png +0 -0
- package/dist/scaffolds/notis-notes/metadata/screenshot-3.png +0 -0
- package/dist/scaffolds/notis-notes/metadata/screenshot-4.png +0 -0
- package/dist/scaffolds/notis-notes/metadata/screenshot-5.png +0 -0
- package/dist/scaffolds/notis-notes/metadata/screenshot-fixtures.json +0 -752
- package/dist/scaffolds/notis-notes/notis.config.ts +0 -80
- package/dist/scaffolds/notis-notes/package-lock.json +0 -4636
- package/dist/scaffolds/notis-notes/package.json +0 -35
- package/dist/scaffolds/notis-notes/packages/sdk/package.json +0 -36
- package/dist/scaffolds/notis-notes/packages/sdk/src/components/DocumentEditor.tsx +0 -93
- package/dist/scaffolds/notis-notes/packages/sdk/src/components/Markdown.tsx +0 -60
- package/dist/scaffolds/notis-notes/packages/sdk/src/components/MultiSelectActionBar.tsx +0 -278
- package/dist/scaffolds/notis-notes/packages/sdk/src/components/MultiSelectCheckbox.tsx +0 -91
- package/dist/scaffolds/notis-notes/packages/sdk/src/components/MultiSelectDragOverlay.tsx +0 -39
- package/dist/scaffolds/notis-notes/packages/sdk/src/config.ts +0 -234
- package/dist/scaffolds/notis-notes/packages/sdk/src/documents.ts +0 -250
- package/dist/scaffolds/notis-notes/packages/sdk/src/hooks/useBackend.ts +0 -41
- package/dist/scaffolds/notis-notes/packages/sdk/src/hooks/useCloudComputer.ts +0 -97
- package/dist/scaffolds/notis-notes/packages/sdk/src/hooks/useDatabaseSchema.ts +0 -85
- package/dist/scaffolds/notis-notes/packages/sdk/src/hooks/useDatabaseSubscription.ts +0 -76
- package/dist/scaffolds/notis-notes/packages/sdk/src/hooks/useDocument.ts +0 -78
- package/dist/scaffolds/notis-notes/packages/sdk/src/hooks/useDocuments.ts +0 -121
- package/dist/scaffolds/notis-notes/packages/sdk/src/hooks/useHandover.ts +0 -75
- package/dist/scaffolds/notis-notes/packages/sdk/src/hooks/useMultiSelect.ts +0 -539
- package/dist/scaffolds/notis-notes/packages/sdk/src/hooks/useNotis.ts +0 -34
- package/dist/scaffolds/notis-notes/packages/sdk/src/hooks/useNotisNavigation.ts +0 -49
- package/dist/scaffolds/notis-notes/packages/sdk/src/hooks/useTool.ts +0 -64
- package/dist/scaffolds/notis-notes/packages/sdk/src/hooks/useTools.ts +0 -56
- package/dist/scaffolds/notis-notes/packages/sdk/src/hooks/useTopBarSearch.ts +0 -73
- package/dist/scaffolds/notis-notes/packages/sdk/src/hooks/useUpsertDocument.ts +0 -95
- package/dist/scaffolds/notis-notes/packages/sdk/src/index.ts +0 -100
- package/dist/scaffolds/notis-notes/packages/sdk/src/provider.tsx +0 -43
- package/dist/scaffolds/notis-notes/packages/sdk/src/runtime.ts +0 -351
- package/dist/scaffolds/notis-notes/packages/sdk/src/styles.css +0 -186
- package/dist/scaffolds/notis-notes/packages/sdk/src/ui.ts +0 -15
- package/dist/scaffolds/notis-notes/packages/sdk/src/vite.ts +0 -56
- package/dist/scaffolds/notis-notes/packages/sdk/tsconfig.json +0 -15
- package/dist/scaffolds/notis-notes/postcss.config.mjs +0 -8
- package/dist/scaffolds/notis-notes/tailwind.config.ts +0 -58
- package/dist/scaffolds/notis-notes/tsconfig.json +0 -23
- package/dist/scaffolds/notis-notes/vite.config.ts +0 -10
- package/dist/scaffolds/notis-random/CHANGELOG.md +0 -15
- package/dist/scaffolds/notis-random/README.md +0 -33
- package/dist/scaffolds/notis-random/app/globals.css +0 -11
- package/dist/scaffolds/notis-random/app/history/page.tsx +0 -67
- package/dist/scaffolds/notis-random/app/layout.tsx +0 -7
- package/dist/scaffolds/notis-random/app/page.tsx +0 -289
- package/dist/scaffolds/notis-random/components/ui/button.tsx +0 -50
- package/dist/scaffolds/notis-random/components/ui/card.tsx +0 -16
- package/dist/scaffolds/notis-random/components/ui/input.tsx +0 -23
- package/dist/scaffolds/notis-random/components.json +0 -20
- package/dist/scaffolds/notis-random/index.html +0 -12
- package/dist/scaffolds/notis-random/lib/notis-tools.ts +0 -128
- package/dist/scaffolds/notis-random/lib/rng.ts +0 -202
- package/dist/scaffolds/notis-random/lib/roll-record.ts +0 -189
- package/dist/scaffolds/notis-random/lib/utils.ts +0 -25
- package/dist/scaffolds/notis-random/metadata/screenshot-1.png +0 -0
- package/dist/scaffolds/notis-random/metadata/screenshot-2.png +0 -0
- package/dist/scaffolds/notis-random/metadata/screenshot-3.png +0 -0
- package/dist/scaffolds/notis-random/metadata/screenshot-4.png +0 -0
- package/dist/scaffolds/notis-random/metadata/screenshot-5.png +0 -0
- package/dist/scaffolds/notis-random/metadata/screenshot-fixtures.json +0 -753
- package/dist/scaffolds/notis-random/notis.config.ts +0 -86
- package/dist/scaffolds/notis-random/package-lock.json +0 -4513
- package/dist/scaffolds/notis-random/package.json +0 -36
- package/dist/scaffolds/notis-random/packages/sdk/package.json +0 -36
- package/dist/scaffolds/notis-random/packages/sdk/src/components/DocumentEditor.tsx +0 -93
- package/dist/scaffolds/notis-random/packages/sdk/src/components/Markdown.tsx +0 -60
- package/dist/scaffolds/notis-random/packages/sdk/src/components/MultiSelectActionBar.tsx +0 -278
- package/dist/scaffolds/notis-random/packages/sdk/src/components/MultiSelectCheckbox.tsx +0 -91
- package/dist/scaffolds/notis-random/packages/sdk/src/components/MultiSelectDragOverlay.tsx +0 -39
- package/dist/scaffolds/notis-random/packages/sdk/src/config.ts +0 -234
- package/dist/scaffolds/notis-random/packages/sdk/src/documents.ts +0 -250
- package/dist/scaffolds/notis-random/packages/sdk/src/hooks/useBackend.ts +0 -41
- package/dist/scaffolds/notis-random/packages/sdk/src/hooks/useCloudComputer.ts +0 -97
- package/dist/scaffolds/notis-random/packages/sdk/src/hooks/useDatabaseSchema.ts +0 -85
- package/dist/scaffolds/notis-random/packages/sdk/src/hooks/useDatabaseSubscription.ts +0 -76
- package/dist/scaffolds/notis-random/packages/sdk/src/hooks/useDocument.ts +0 -78
- package/dist/scaffolds/notis-random/packages/sdk/src/hooks/useDocuments.ts +0 -121
- package/dist/scaffolds/notis-random/packages/sdk/src/hooks/useHandover.ts +0 -75
- package/dist/scaffolds/notis-random/packages/sdk/src/hooks/useMultiSelect.ts +0 -539
- package/dist/scaffolds/notis-random/packages/sdk/src/hooks/useNotis.ts +0 -34
- package/dist/scaffolds/notis-random/packages/sdk/src/hooks/useNotisNavigation.ts +0 -49
- package/dist/scaffolds/notis-random/packages/sdk/src/hooks/useTool.ts +0 -64
- package/dist/scaffolds/notis-random/packages/sdk/src/hooks/useTools.ts +0 -56
- package/dist/scaffolds/notis-random/packages/sdk/src/hooks/useTopBarSearch.ts +0 -73
- package/dist/scaffolds/notis-random/packages/sdk/src/hooks/useUpsertDocument.ts +0 -95
- package/dist/scaffolds/notis-random/packages/sdk/src/index.ts +0 -100
- package/dist/scaffolds/notis-random/packages/sdk/src/provider.tsx +0 -43
- package/dist/scaffolds/notis-random/packages/sdk/src/runtime.ts +0 -351
- package/dist/scaffolds/notis-random/packages/sdk/src/styles.css +0 -186
- package/dist/scaffolds/notis-random/packages/sdk/src/ui.ts +0 -15
- package/dist/scaffolds/notis-random/packages/sdk/src/vite.ts +0 -56
- package/dist/scaffolds/notis-random/packages/sdk/tsconfig.json +0 -15
- package/dist/scaffolds/notis-random/postcss.config.mjs +0 -6
- package/dist/scaffolds/notis-random/src/dev-main.tsx +0 -70
- package/dist/scaffolds/notis-random/src/mock-runtime.ts +0 -129
- package/dist/scaffolds/notis-random/tailwind.config.ts +0 -50
- package/dist/scaffolds/notis-random/tsconfig.json +0 -23
- package/dist/scaffolds/notis-random/vite.config.ts +0 -11
- package/dist/scaffolds.json +0 -49
package/src/runtime/output.js
CHANGED
|
@@ -108,6 +108,13 @@ export class OutputManager {
|
|
|
108
108
|
process.stderr.write(`${message}\n`);
|
|
109
109
|
}
|
|
110
110
|
|
|
111
|
+
notice(message) {
|
|
112
|
+
// Some pre-result instructions are required to make progress. They belong
|
|
113
|
+
// on stderr so stdout stays one valid machine envelope, but unlike ordinary
|
|
114
|
+
// table notes they must remain visible in JSON/YAML/NDJSON modes.
|
|
115
|
+
process.stderr.write(`${message}\n`);
|
|
116
|
+
}
|
|
117
|
+
|
|
111
118
|
emitProgress({ phase, message, requestId = null }) {
|
|
112
119
|
if (this.runtime.quiet) {
|
|
113
120
|
return;
|
package/src/runtime/ports.js
CHANGED
|
@@ -1,5 +1,21 @@
|
|
|
1
1
|
import { createServer } from 'node:http';
|
|
2
2
|
|
|
3
|
+
export async function getAvailablePortPreferring(preferredPort) {
|
|
4
|
+
if (Number.isInteger(preferredPort) && preferredPort > 0 && preferredPort <= 65535) {
|
|
5
|
+
const preferredIsFree = await new Promise((resolvePromise) => {
|
|
6
|
+
const server = createServer();
|
|
7
|
+
server.once('error', () => resolvePromise(false));
|
|
8
|
+
server.listen(preferredPort, '127.0.0.1', () => {
|
|
9
|
+
server.close(() => resolvePromise(true));
|
|
10
|
+
});
|
|
11
|
+
});
|
|
12
|
+
if (preferredIsFree) {
|
|
13
|
+
return preferredPort;
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
return getAvailablePort();
|
|
17
|
+
}
|
|
18
|
+
|
|
3
19
|
export async function getAvailablePort() {
|
|
4
20
|
const server = createServer();
|
|
5
21
|
await new Promise((resolvePromise, rejectPromise) => {
|
package/src/runtime/profiles.js
CHANGED
|
@@ -267,10 +267,6 @@ export function resolveWorktreeRuntime(startDir = process.cwd()) {
|
|
|
267
267
|
const profile = typeof runtime?.profile === 'string' ? runtime.profile.trim() : '';
|
|
268
268
|
const devAccessToken =
|
|
269
269
|
typeof runtime?.dev_access_token === 'string' ? runtime.dev_access_token.trim() : '';
|
|
270
|
-
const appDevSessionsFile =
|
|
271
|
-
typeof runtime?.app_dev_sessions_file === 'string' && runtime.app_dev_sessions_file.trim()
|
|
272
|
-
? runtime.app_dev_sessions_file.trim()
|
|
273
|
-
: join(dirname(runtimePath), 'app-dev-sessions.json');
|
|
274
270
|
const desktopDeepLinkScheme =
|
|
275
271
|
typeof runtime?.desktop_deep_link_scheme === 'string'
|
|
276
272
|
? runtime.desktop_deep_link_scheme.trim()
|
|
@@ -306,7 +302,6 @@ export function resolveWorktreeRuntime(startDir = process.cwd()) {
|
|
|
306
302
|
api_base: apiBase,
|
|
307
303
|
profile,
|
|
308
304
|
dev_access_token: devAccessToken,
|
|
309
|
-
app_dev_sessions_file: resolve(dirname(runtimePath), appDevSessionsFile),
|
|
310
305
|
desktop_deep_link_scheme: desktopDeepLinkScheme || undefined,
|
|
311
306
|
runtime_path: runtimePath,
|
|
312
307
|
routing_path: routingPath,
|
package/template/app/layout.tsx
CHANGED
|
@@ -1,6 +1,10 @@
|
|
|
1
1
|
import '@notis/sdk/styles.css';
|
|
2
|
+
import { ShortcutProvider } from '@notis/sdk/interactions';
|
|
2
3
|
import './globals.css';
|
|
3
4
|
|
|
4
5
|
export default function AppShell({ children }: { children: React.ReactNode }) {
|
|
5
|
-
|
|
6
|
+
// NotisProvider supplies this in the product host. Keeping the shell-level
|
|
7
|
+
// provider makes the same scope arbitration available in the local harness;
|
|
8
|
+
// ShortcutProvider is nesting-safe, so the hosted app still has one registry.
|
|
9
|
+
return <ShortcutProvider>{children}</ShortcutProvider>;
|
|
6
10
|
}
|
package/template/notis.config.ts
CHANGED
|
@@ -2,6 +2,7 @@ import { defineNotisApp } from '@notis/sdk/config';
|
|
|
2
2
|
|
|
3
3
|
export default defineNotisApp({
|
|
4
4
|
name: 'my-notis-app',
|
|
5
|
+
devSlug: 'my-notis-app',
|
|
5
6
|
title: 'My Notis App',
|
|
6
7
|
description: 'A focused Notis app with one route and one database-backed workflow.',
|
|
7
8
|
icon: 'phosphor:squares-four',
|
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@notis/sdk",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"main": "./src/index.ts",
|
|
6
6
|
"exports": {
|
|
7
7
|
".": "./src/index.ts",
|
|
8
|
+
"./interactions": "./src/interactions.ts",
|
|
8
9
|
"./config": "./src/config.ts",
|
|
9
10
|
"./vite": "./src/vite.ts",
|
|
10
11
|
"./ui": "./src/ui.ts",
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
import type { ChangeEvent, KeyboardEvent, ReactElement } from 'react';
|
|
2
|
+
import { useCallback, useEffect, useRef, useState } from 'react';
|
|
3
|
+
import { useNotisRuntime } from '../provider';
|
|
4
|
+
import type { NotisMarkdownEditorProps } from '../runtime';
|
|
5
|
+
|
|
6
|
+
function MarkdownEditorFallback({
|
|
7
|
+
value,
|
|
8
|
+
revision,
|
|
9
|
+
readOnly,
|
|
10
|
+
autosaveMs = 1500,
|
|
11
|
+
placeholder = 'Write in Markdown…',
|
|
12
|
+
className,
|
|
13
|
+
onChange,
|
|
14
|
+
onSave,
|
|
15
|
+
onDirtyChange,
|
|
16
|
+
onSavingChange,
|
|
17
|
+
}: NotisMarkdownEditorProps) {
|
|
18
|
+
const [draft, setDraft] = useState(value);
|
|
19
|
+
const [saveAttempt, setSaveAttempt] = useState(0);
|
|
20
|
+
const draftRef = useRef(value);
|
|
21
|
+
const dirtyRef = useRef(false);
|
|
22
|
+
const savingRef = useRef(false);
|
|
23
|
+
const revisionRef = useRef(revision);
|
|
24
|
+
const onSaveRef = useRef(onSave);
|
|
25
|
+
const readOnlyRef = useRef(readOnly);
|
|
26
|
+
const unmountedRef = useRef(false);
|
|
27
|
+
const saveRef = useRef<() => Promise<void>>(async () => undefined);
|
|
28
|
+
|
|
29
|
+
useEffect(() => {
|
|
30
|
+
onSaveRef.current = onSave;
|
|
31
|
+
readOnlyRef.current = readOnly;
|
|
32
|
+
}, [onSave, readOnly]);
|
|
33
|
+
|
|
34
|
+
useEffect(() => {
|
|
35
|
+
if (dirtyRef.current) return;
|
|
36
|
+
draftRef.current = value;
|
|
37
|
+
revisionRef.current = revision;
|
|
38
|
+
setDraft(value);
|
|
39
|
+
}, [revision, value]);
|
|
40
|
+
|
|
41
|
+
const save = useCallback(async () => {
|
|
42
|
+
if (!onSave || !dirtyRef.current || readOnly || savingRef.current) return;
|
|
43
|
+
const markdown = draftRef.current;
|
|
44
|
+
savingRef.current = true;
|
|
45
|
+
if (!unmountedRef.current) onSavingChange?.(true);
|
|
46
|
+
try {
|
|
47
|
+
const result = await onSave({ markdown, expectedRevision: revisionRef.current });
|
|
48
|
+
if (result?.revision) revisionRef.current = result.revision;
|
|
49
|
+
if (draftRef.current === markdown) {
|
|
50
|
+
dirtyRef.current = false;
|
|
51
|
+
if (!unmountedRef.current) onDirtyChange?.(false);
|
|
52
|
+
}
|
|
53
|
+
} finally {
|
|
54
|
+
savingRef.current = false;
|
|
55
|
+
if (!unmountedRef.current) onSavingChange?.(false);
|
|
56
|
+
if (dirtyRef.current && draftRef.current !== markdown) {
|
|
57
|
+
if (unmountedRef.current) {
|
|
58
|
+
void saveRef.current().catch((error) => console.error('Could not save Markdown on unmount', error));
|
|
59
|
+
} else {
|
|
60
|
+
setSaveAttempt((attempt) => attempt + 1);
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
}, [onDirtyChange, onSave, onSavingChange, readOnly]);
|
|
65
|
+
saveRef.current = save;
|
|
66
|
+
|
|
67
|
+
useEffect(() => {
|
|
68
|
+
unmountedRef.current = false;
|
|
69
|
+
return () => {
|
|
70
|
+
unmountedRef.current = true;
|
|
71
|
+
if (!savingRef.current) {
|
|
72
|
+
void saveRef.current().catch((error) => console.error('Could not save Markdown on unmount', error));
|
|
73
|
+
}
|
|
74
|
+
};
|
|
75
|
+
}, []);
|
|
76
|
+
|
|
77
|
+
useEffect(() => {
|
|
78
|
+
if (!dirtyRef.current || !onSave || readOnly) return;
|
|
79
|
+
const timer = window.setTimeout(() => void save(), Math.max(250, autosaveMs));
|
|
80
|
+
return () => window.clearTimeout(timer);
|
|
81
|
+
}, [autosaveMs, draft, onSave, readOnly, save, saveAttempt]);
|
|
82
|
+
|
|
83
|
+
const handleChange = (event: ChangeEvent<HTMLTextAreaElement>) => {
|
|
84
|
+
const next = event.currentTarget.value;
|
|
85
|
+
draftRef.current = next;
|
|
86
|
+
setDraft(next);
|
|
87
|
+
if (!dirtyRef.current) {
|
|
88
|
+
dirtyRef.current = true;
|
|
89
|
+
onDirtyChange?.(true);
|
|
90
|
+
}
|
|
91
|
+
onChange?.(next);
|
|
92
|
+
};
|
|
93
|
+
|
|
94
|
+
const handleKeyDown = (event: KeyboardEvent<HTMLTextAreaElement>) => {
|
|
95
|
+
if ((event.metaKey || event.ctrlKey) && event.key.toLowerCase() === 's') {
|
|
96
|
+
event.preventDefault();
|
|
97
|
+
void save();
|
|
98
|
+
}
|
|
99
|
+
};
|
|
100
|
+
|
|
101
|
+
return (
|
|
102
|
+
<textarea
|
|
103
|
+
value={draft}
|
|
104
|
+
readOnly={readOnly}
|
|
105
|
+
placeholder={placeholder}
|
|
106
|
+
className={className}
|
|
107
|
+
onChange={handleChange}
|
|
108
|
+
onKeyDown={handleKeyDown}
|
|
109
|
+
style={{ minHeight: 360, width: '100%', resize: 'vertical' }}
|
|
110
|
+
/>
|
|
111
|
+
);
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/** Use Notis' editor while leaving persistence entirely under app control. */
|
|
115
|
+
export function MarkdownEditor(props: NotisMarkdownEditorProps): ReactElement {
|
|
116
|
+
const runtime = useNotisRuntime();
|
|
117
|
+
const HostEditor = runtime?.ui?.MarkdownEditor;
|
|
118
|
+
return HostEditor
|
|
119
|
+
? <HostEditor key={props.resourceKey} {...props} />
|
|
120
|
+
: <MarkdownEditorFallback key={props.resourceKey} {...props} />;
|
|
121
|
+
}
|
|
@@ -1,25 +1,16 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
|
|
3
|
-
import {
|
|
4
|
-
useEffect,
|
|
3
|
+
import React, {
|
|
5
4
|
useMemo,
|
|
6
|
-
useRef,
|
|
7
5
|
useState,
|
|
8
6
|
type CSSProperties,
|
|
9
7
|
type ReactElement,
|
|
10
|
-
type ReactNode,
|
|
11
8
|
} from 'react';
|
|
9
|
+
import type { ResolvedCollectionAction } from '../interactions/actions';
|
|
10
|
+
import { shortcutDisplay, useShortcuts, type ShortcutDefinition } from '../interactions/shortcuts';
|
|
11
|
+
import type { ShortcutScope } from '../interactions/shortcuts';
|
|
12
12
|
|
|
13
|
-
export
|
|
14
|
-
id: string;
|
|
15
|
-
label: string;
|
|
16
|
-
icon: ReactNode;
|
|
17
|
-
/** Single-key (e.g. "E", "#") or "Mod+K" shortcut. Bound while the bar is mounted with count > 0. */
|
|
18
|
-
shortcut?: string;
|
|
19
|
-
onRun: () => void | Promise<void>;
|
|
20
|
-
/** Hint for future destructive styling. Currently unused visually. */
|
|
21
|
-
destructive?: boolean;
|
|
22
|
-
}
|
|
13
|
+
export type MultiSelectAction = ResolvedCollectionAction;
|
|
23
14
|
|
|
24
15
|
export interface MultiSelectActionBarProps {
|
|
25
16
|
selectedCount: number;
|
|
@@ -30,73 +21,10 @@ export interface MultiSelectActionBarProps {
|
|
|
30
21
|
className?: string;
|
|
31
22
|
/** Optional override for the bar's positioning style. Defaults to bottom-center floating. */
|
|
32
23
|
style?: CSSProperties;
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
needsMod: boolean;
|
|
38
|
-
needsShift: boolean;
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
function parseShortcut(raw: string): ParsedShortcut | null {
|
|
42
|
-
const parts = raw.split('+').map((p) => p.trim()).filter(Boolean);
|
|
43
|
-
if (parts.length === 0) return null;
|
|
44
|
-
let needsMod = false;
|
|
45
|
-
let needsShift = false;
|
|
46
|
-
let key = '';
|
|
47
|
-
for (const part of parts) {
|
|
48
|
-
const lower = part.toLowerCase();
|
|
49
|
-
if (lower === 'mod' || lower === 'cmd' || lower === 'ctrl' || lower === 'meta') {
|
|
50
|
-
needsMod = true;
|
|
51
|
-
} else if (lower === 'shift') {
|
|
52
|
-
needsShift = true;
|
|
53
|
-
} else {
|
|
54
|
-
key = lower;
|
|
55
|
-
}
|
|
56
|
-
}
|
|
57
|
-
if (!key) return null;
|
|
58
|
-
return { key, needsMod, needsShift };
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
function shortcutMatches(shortcut: ParsedShortcut, event: KeyboardEvent): boolean {
|
|
62
|
-
const eventKey = event.key.toLowerCase();
|
|
63
|
-
// Special-case "#" so Shift+3 matches without the consumer specifying Shift.
|
|
64
|
-
if (shortcut.key === '#') {
|
|
65
|
-
if (eventKey === '#') return true;
|
|
66
|
-
if (event.shiftKey && eventKey === '3') return true;
|
|
67
|
-
return false;
|
|
68
|
-
}
|
|
69
|
-
if (eventKey !== shortcut.key) return false;
|
|
70
|
-
if (shortcut.needsMod !== Boolean(event.metaKey || event.ctrlKey)) return false;
|
|
71
|
-
if (shortcut.needsShift !== event.shiftKey) return false;
|
|
72
|
-
if (event.altKey) return false;
|
|
73
|
-
return true;
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
function isEditableEvent(event: Event): boolean {
|
|
77
|
-
// Notis apps mount inside a Shadow DOM, so a document-level listener sees
|
|
78
|
-
// `event.target` retargeted to the shadow host. `composedPath()[0]` pierces
|
|
79
|
-
// the shadow boundary to the real focused element, so action-bar shortcuts
|
|
80
|
-
// don't fire while the user is typing in an app input.
|
|
81
|
-
const target = event.composedPath?.()[0] ?? event.target;
|
|
82
|
-
if (!(target instanceof HTMLElement)) return false;
|
|
83
|
-
if (target.isContentEditable) return true;
|
|
84
|
-
const tag = target.tagName;
|
|
85
|
-
return tag === 'INPUT' || tag === 'TEXTAREA' || tag === 'SELECT';
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
function shortcutDisplay(raw: string): string {
|
|
89
|
-
const parts = raw.split('+').map((p) => p.trim()).filter(Boolean);
|
|
90
|
-
return parts
|
|
91
|
-
.map((part) => {
|
|
92
|
-
const lower = part.toLowerCase();
|
|
93
|
-
if (lower === 'mod' || lower === 'cmd' || lower === 'meta') return '⌘';
|
|
94
|
-
if (lower === 'ctrl') return '⌃';
|
|
95
|
-
if (lower === 'shift') return '⇧';
|
|
96
|
-
if (lower === 'alt' || lower === 'option') return '⌥';
|
|
97
|
-
return part.length === 1 ? part.toUpperCase() : part;
|
|
98
|
-
})
|
|
99
|
-
.join('');
|
|
24
|
+
/** Disable action key bindings while leaving the visible toolbar mounted. */
|
|
25
|
+
shortcutsEnabled?: boolean;
|
|
26
|
+
/** Override the default collection shortcut scope. */
|
|
27
|
+
shortcutScope?: ShortcutScope;
|
|
100
28
|
}
|
|
101
29
|
|
|
102
30
|
const containerBaseStyle: CSSProperties = {
|
|
@@ -180,43 +108,25 @@ export function MultiSelectActionBar({
|
|
|
180
108
|
itemLabel,
|
|
181
109
|
className,
|
|
182
110
|
style,
|
|
111
|
+
shortcutsEnabled = true,
|
|
112
|
+
shortcutScope = 'collection',
|
|
183
113
|
}: MultiSelectActionBarProps): ReactElement | null {
|
|
184
|
-
const
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
})
|
|
194
|
-
.filter((entry): entry is { action: MultiSelectAction; parsed: ParsedShortcut } => entry !== null);
|
|
114
|
+
const actionShortcuts = useMemo<ShortcutDefinition[]>(() => {
|
|
115
|
+
return actions.flatMap((action): ShortcutDefinition[] => {
|
|
116
|
+
if (!action.shortcut || action.disabled || action.pending) return [];
|
|
117
|
+
return [{
|
|
118
|
+
id: `collection.action.${action.id}`,
|
|
119
|
+
keys: action.shortcut,
|
|
120
|
+
label: action.label,
|
|
121
|
+
onTrigger: () => action.onRun(),
|
|
122
|
+
}];
|
|
123
|
+
});
|
|
195
124
|
}, [actions]);
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
if (isEditableEvent(event)) return;
|
|
202
|
-
for (const { action, parsed } of parsedShortcuts) {
|
|
203
|
-
if (shortcutMatches(parsed, event)) {
|
|
204
|
-
event.preventDefault();
|
|
205
|
-
event.stopPropagation();
|
|
206
|
-
if (typeof event.stopImmediatePropagation === 'function') {
|
|
207
|
-
event.stopImmediatePropagation();
|
|
208
|
-
}
|
|
209
|
-
void action.onRun();
|
|
210
|
-
return;
|
|
211
|
-
}
|
|
212
|
-
}
|
|
213
|
-
};
|
|
214
|
-
|
|
215
|
-
document.addEventListener('keydown', handleKeyDown, true);
|
|
216
|
-
return () => {
|
|
217
|
-
document.removeEventListener('keydown', handleKeyDown, true);
|
|
218
|
-
};
|
|
219
|
-
}, [parsedShortcuts, selectedCount]);
|
|
125
|
+
useShortcuts(actionShortcuts, {
|
|
126
|
+
enabled: shortcutsEnabled && selectedCount > 0,
|
|
127
|
+
scope: shortcutScope,
|
|
128
|
+
priority: 25,
|
|
129
|
+
});
|
|
220
130
|
|
|
221
131
|
if (selectedCount === 0) return null;
|
|
222
132
|
|
|
@@ -258,21 +168,29 @@ function ActionButton({ action }: { action: MultiSelectAction }) {
|
|
|
258
168
|
color: hover
|
|
259
169
|
? 'hsl(var(--background))'
|
|
260
170
|
: baseButtonStyle.color,
|
|
171
|
+
opacity: action.disabled ? 0.45 : 1,
|
|
172
|
+
cursor: action.disabled || action.pending ? 'not-allowed' : 'pointer',
|
|
261
173
|
};
|
|
262
174
|
|
|
263
175
|
return (
|
|
264
176
|
<button
|
|
265
177
|
type="button"
|
|
266
|
-
onClick={() =>
|
|
178
|
+
onClick={() => {
|
|
179
|
+
if (!action.disabled && !action.pending) void action.onRun();
|
|
180
|
+
}}
|
|
181
|
+
disabled={action.disabled || action.pending}
|
|
182
|
+
aria-busy={action.pending || undefined}
|
|
183
|
+
aria-keyshortcuts={action.shortcut || undefined}
|
|
184
|
+
data-destructive={action.destructive ? 'true' : 'false'}
|
|
267
185
|
onMouseEnter={() => setHover(true)}
|
|
268
186
|
onMouseLeave={() => setHover(false)}
|
|
269
187
|
onFocus={() => setHover(true)}
|
|
270
188
|
onBlur={() => setHover(false)}
|
|
271
189
|
style={buttonStyle}
|
|
272
190
|
>
|
|
273
|
-
<span aria-hidden style={iconSlotStyle}>{action.icon}</span>
|
|
191
|
+
{action.icon ? <span aria-hidden style={iconSlotStyle}>{action.icon}</span> : null}
|
|
274
192
|
{display ? <kbd aria-hidden style={keycapStyle}>{display}</kbd> : null}
|
|
275
|
-
<span>{action.label}</span>
|
|
193
|
+
<span>{action.pending ? `${action.label}…` : action.label}</span>
|
|
276
194
|
</button>
|
|
277
195
|
);
|
|
278
196
|
}
|
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
|
|
3
|
-
import { type CSSProperties, type MouseEvent as ReactMouseEvent, type ReactElement } from 'react';
|
|
3
|
+
import React, { type CSSProperties, type MouseEvent as ReactMouseEvent, type ReactElement } from 'react';
|
|
4
4
|
|
|
5
5
|
export interface MultiSelectCheckboxProps {
|
|
6
6
|
isSelected: boolean;
|
|
7
7
|
onClick: (event: ReactMouseEvent) => void;
|
|
8
|
+
disabled?: boolean;
|
|
8
9
|
/** When true, the checkbox is always visible. Default false (hover/focus reveal via the parent's :hover state). */
|
|
9
10
|
alwaysVisible?: boolean;
|
|
10
11
|
/** Optional aria-label override. Defaults to "Select item" / "Deselect item". */
|
|
@@ -40,6 +41,7 @@ const checkPath = 'M3.5 7.5l2.5 2.5 6.5-6.5';
|
|
|
40
41
|
export function MultiSelectCheckbox({
|
|
41
42
|
isSelected,
|
|
42
43
|
onClick,
|
|
44
|
+
disabled = false,
|
|
43
45
|
alwaysVisible = false,
|
|
44
46
|
ariaLabel,
|
|
45
47
|
className,
|
|
@@ -49,6 +51,7 @@ export function MultiSelectCheckbox({
|
|
|
49
51
|
...baseStyle,
|
|
50
52
|
...(isSelected ? selectedStyle : null),
|
|
51
53
|
...(alwaysVisible || isSelected ? { opacity: 1 } : null),
|
|
54
|
+
...(disabled ? { opacity: 0.45, cursor: 'not-allowed' } : null),
|
|
52
55
|
...(style || null),
|
|
53
56
|
};
|
|
54
57
|
|
|
@@ -62,6 +65,7 @@ export function MultiSelectCheckbox({
|
|
|
62
65
|
type="button"
|
|
63
66
|
role="checkbox"
|
|
64
67
|
aria-checked={isSelected}
|
|
68
|
+
disabled={disabled}
|
|
65
69
|
aria-label={ariaLabel ?? (isSelected ? 'Deselect item' : 'Select item')}
|
|
66
70
|
onClick={onClick}
|
|
67
71
|
onMouseDown={(e) => e.stopPropagation()}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import type { CSSProperties, ClipboardEvent, ReactNode } from 'react';
|
|
2
|
+
import { useCallback } from 'react';
|
|
3
|
+
import { useNotisRuntime } from '../provider';
|
|
4
|
+
import type { ContextResource, ContextSelection } from '../runtime';
|
|
5
|
+
|
|
6
|
+
export const NOTIS_CONTEXT_CLIPBOARD_TYPE = 'application/x-notis-context+json';
|
|
7
|
+
|
|
8
|
+
export interface NotisSelectionBoundaryProps {
|
|
9
|
+
resource?: ContextResource | null;
|
|
10
|
+
children: ReactNode;
|
|
11
|
+
className?: string;
|
|
12
|
+
style?: CSSProperties;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
function selectionId(): string {
|
|
16
|
+
return typeof crypto !== 'undefined' && typeof crypto.randomUUID === 'function'
|
|
17
|
+
? crypto.randomUUID()
|
|
18
|
+
: `selection-${Date.now()}-${Math.random().toString(36).slice(2)}`;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Adds structured provenance to ordinary copied text. Pasting still works in
|
|
23
|
+
* every app because `text/plain` is preserved; Notis chat additionally reads
|
|
24
|
+
* the custom MIME payload and renders a separate quote pill.
|
|
25
|
+
*/
|
|
26
|
+
export function NotisSelectionBoundary({
|
|
27
|
+
resource,
|
|
28
|
+
children,
|
|
29
|
+
className,
|
|
30
|
+
style,
|
|
31
|
+
}: NotisSelectionBoundaryProps) {
|
|
32
|
+
const runtime = useNotisRuntime();
|
|
33
|
+
const onCopy = useCallback((event: ClipboardEvent<HTMLDivElement>) => {
|
|
34
|
+
const selection = window.getSelection();
|
|
35
|
+
const text = selection?.toString().trim() ?? '';
|
|
36
|
+
if (
|
|
37
|
+
!text
|
|
38
|
+
|| !selection
|
|
39
|
+
|| !event.currentTarget.contains(selection.anchorNode)
|
|
40
|
+
|| !event.currentTarget.contains(selection.focusNode)
|
|
41
|
+
) return;
|
|
42
|
+
|
|
43
|
+
const payload: ContextSelection = {
|
|
44
|
+
id: selectionId(),
|
|
45
|
+
text: text.slice(0, 12_000),
|
|
46
|
+
...(resource ? { resource: { ...resource, snapshot: undefined } } : {}),
|
|
47
|
+
};
|
|
48
|
+
event.clipboardData.setData('text/plain', text);
|
|
49
|
+
event.clipboardData.setData(NOTIS_CONTEXT_CLIPBOARD_TYPE, JSON.stringify(payload));
|
|
50
|
+
event.preventDefault();
|
|
51
|
+
runtime?.captureContextSelection?.(payload);
|
|
52
|
+
}, [resource, runtime]);
|
|
53
|
+
|
|
54
|
+
return <div className={className} style={style} onCopy={onCopy}>{children}</div>;
|
|
55
|
+
}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import React, { type CSSProperties, type ReactElement } from 'react';
|
|
4
|
+
import { shortcutDisplay } from '../interactions/shortcuts';
|
|
5
|
+
|
|
6
|
+
export interface ShortcutHint {
|
|
7
|
+
id: string;
|
|
8
|
+
keys: string;
|
|
9
|
+
label: string;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export interface ShortcutHintsProps {
|
|
13
|
+
shortcuts: ShortcutHint[];
|
|
14
|
+
className?: string;
|
|
15
|
+
style?: CSSProperties;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
const listStyle: CSSProperties = {
|
|
19
|
+
display: 'flex',
|
|
20
|
+
flexWrap: 'wrap',
|
|
21
|
+
alignItems: 'center',
|
|
22
|
+
gap: '0.75rem',
|
|
23
|
+
color: 'hsl(var(--muted-foreground))',
|
|
24
|
+
fontSize: '12px',
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
const hintStyle: CSSProperties = {
|
|
28
|
+
display: 'inline-flex',
|
|
29
|
+
alignItems: 'center',
|
|
30
|
+
gap: '0.35rem',
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
const keyStyle: CSSProperties = {
|
|
34
|
+
minWidth: '20px',
|
|
35
|
+
border: '1px solid hsl(var(--border))',
|
|
36
|
+
borderRadius: '4px',
|
|
37
|
+
background: 'hsl(var(--muted))',
|
|
38
|
+
color: 'hsl(var(--foreground))',
|
|
39
|
+
padding: '1px 5px',
|
|
40
|
+
textAlign: 'center',
|
|
41
|
+
font: 'inherit',
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
export function ShortcutHints({ shortcuts, className, style }: ShortcutHintsProps): ReactElement | null {
|
|
45
|
+
if (shortcuts.length === 0) return null;
|
|
46
|
+
return (
|
|
47
|
+
<div aria-label="Keyboard shortcuts" className={className} style={{ ...listStyle, ...style }}>
|
|
48
|
+
{shortcuts.map((shortcut) => (
|
|
49
|
+
<span key={shortcut.id} style={hintStyle}>
|
|
50
|
+
<kbd style={keyStyle}>{shortcutDisplay(shortcut.keys)}</kbd>
|
|
51
|
+
<span>{shortcut.label}</span>
|
|
52
|
+
</span>
|
|
53
|
+
))}
|
|
54
|
+
</div>
|
|
55
|
+
);
|
|
56
|
+
}
|
|
@@ -174,9 +174,26 @@ export interface NotisAppCapabilities {
|
|
|
174
174
|
cloudComputer?: 'read' | 'shell';
|
|
175
175
|
}
|
|
176
176
|
|
|
177
|
+
/**
|
|
178
|
+
* Execution hint for a tool whose public name is generated by a provider.
|
|
179
|
+
*
|
|
180
|
+
* MCP public names may be truncated or deduplicated, so the upstream action
|
|
181
|
+
* cannot always be reconstructed from the final `LOCAL_MCP_*` name. Binding
|
|
182
|
+
* the two lets the host skip provider schema discovery on direct calls while
|
|
183
|
+
* the public name remains the user-approved permission boundary.
|
|
184
|
+
*/
|
|
185
|
+
export interface NotisAppToolBinding {
|
|
186
|
+
/** Exact final name also present in `tools`. */
|
|
187
|
+
name: string;
|
|
188
|
+
/** Exact upstream MCP action name, for example `execute_sql`. */
|
|
189
|
+
providerToolName: string;
|
|
190
|
+
}
|
|
191
|
+
|
|
177
192
|
export interface NotisAppConfig {
|
|
178
193
|
/** URL-safe app slug. Existing apps may still use a display name here. */
|
|
179
194
|
name: string;
|
|
195
|
+
/** Stable local-development slug. Defaults to a slug derived from `name`. */
|
|
196
|
+
devSlug?: string;
|
|
180
197
|
/** Human display title, Raycast-style. Falls back to `name`. */
|
|
181
198
|
title?: string;
|
|
182
199
|
description?: string;
|
|
@@ -218,6 +235,8 @@ export interface NotisAppConfig {
|
|
|
218
235
|
* credit-cap and usage-billing path.
|
|
219
236
|
*/
|
|
220
237
|
tools?: string[];
|
|
238
|
+
/** Optional execution bindings for provider-generated tool names. */
|
|
239
|
+
toolBindings?: NotisAppToolBinding[];
|
|
221
240
|
/** Skills shipped from this app's source tree. */
|
|
222
241
|
skills?: NotisAppSkillConfig[];
|
|
223
242
|
/** Optional onboarding entrypoint exposed from the app sidebar. */
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { useEffect, useMemo } from 'react';
|
|
2
|
+
import { useNotisRuntime } from '../provider';
|
|
3
|
+
import type { ContextResource } from '../runtime';
|
|
4
|
+
|
|
5
|
+
function stableResource(resource: ContextResource | null): string {
|
|
6
|
+
return JSON.stringify(resource);
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
/** Keep Notis aware of the resource currently open inside an app view. */
|
|
10
|
+
export function useActiveResource(resource: ContextResource | null): void {
|
|
11
|
+
const runtime = useNotisRuntime();
|
|
12
|
+
const signature = stableResource(resource);
|
|
13
|
+
const stable = useMemo(() => resource, [signature]);
|
|
14
|
+
|
|
15
|
+
useEffect(() => {
|
|
16
|
+
runtime?.publishActiveResource?.(stable);
|
|
17
|
+
return () => runtime?.publishActiveResource?.(null);
|
|
18
|
+
}, [runtime, stable]);
|
|
19
|
+
}
|