@notis_ai/cli 0.2.4 → 0.2.6
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 +47 -5
- package/dist/scaffolds/notis-database/CHANGELOG.md +5 -0
- package/dist/scaffolds/notis-database/metadata/screenshot-1.png +0 -0
- package/dist/scaffolds/notis-database/notis.config.ts +0 -1
- package/dist/scaffolds/notis-database/src/mock-runtime.ts +1 -0
- package/dist/scaffolds/notis-journal/CHANGELOG.md +25 -0
- package/dist/scaffolds/notis-journal/app/globals.css +37 -0
- package/dist/scaffolds/notis-journal/app/insights/page.tsx +513 -0
- package/dist/scaffolds/notis-journal/app/journal-core.tsx +362 -0
- package/dist/scaffolds/notis-journal/app/journal-ui.tsx +337 -0
- package/dist/scaffolds/notis-journal/app/layout.tsx +6 -0
- package/dist/scaffolds/notis-journal/app/page.tsx +485 -0
- package/dist/scaffolds/notis-journal/components/ui/badge.tsx +28 -0
- package/dist/scaffolds/notis-journal/components/ui/button.tsx +53 -0
- package/dist/scaffolds/notis-journal/components/ui/card.tsx +56 -0
- package/dist/scaffolds/notis-journal/components.json +20 -0
- package/dist/scaffolds/notis-journal/index.html +12 -0
- package/dist/scaffolds/notis-journal/lib/utils.ts +6 -0
- 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 +132 -0
- package/dist/scaffolds/notis-journal/notis.config.ts +93 -0
- package/dist/scaffolds/notis-journal/package.json +34 -0
- package/dist/scaffolds/notis-journal/packages/sdk/package.json +36 -0
- package/dist/scaffolds/notis-journal/packages/sdk/src/components/DocumentEditor.tsx +93 -0
- package/dist/scaffolds/notis-journal/packages/sdk/src/components/Markdown.tsx +60 -0
- package/dist/scaffolds/notis-journal/packages/sdk/src/components/MultiSelectActionBar.tsx +278 -0
- package/dist/scaffolds/notis-journal/packages/sdk/src/components/MultiSelectCheckbox.tsx +91 -0
- package/dist/scaffolds/notis-journal/packages/sdk/src/components/MultiSelectDragOverlay.tsx +39 -0
- package/dist/scaffolds/notis-journal/packages/sdk/src/config.ts +145 -0
- package/dist/scaffolds/notis-journal/packages/sdk/src/documents.ts +229 -0
- package/dist/scaffolds/notis-journal/packages/sdk/src/hooks/useBackend.ts +41 -0
- package/dist/scaffolds/notis-journal/packages/sdk/src/hooks/useDatabaseSchema.ts +85 -0
- package/dist/scaffolds/notis-journal/packages/sdk/src/hooks/useDocument.ts +78 -0
- package/dist/scaffolds/notis-journal/packages/sdk/src/hooks/useDocuments.ts +121 -0
- package/dist/scaffolds/notis-journal/packages/sdk/src/hooks/useMultiSelect.ts +539 -0
- package/dist/scaffolds/notis-journal/packages/sdk/src/hooks/useNotis.ts +34 -0
- package/dist/scaffolds/notis-journal/packages/sdk/src/hooks/useNotisNavigation.ts +49 -0
- package/dist/scaffolds/notis-journal/packages/sdk/src/hooks/useTool.ts +64 -0
- package/dist/scaffolds/notis-journal/packages/sdk/src/hooks/useTools.ts +56 -0
- package/dist/scaffolds/notis-journal/packages/sdk/src/hooks/useTopBarSearch.ts +73 -0
- package/dist/scaffolds/notis-journal/packages/sdk/src/hooks/useUpsertDocument.ts +95 -0
- package/dist/scaffolds/notis-journal/packages/sdk/src/index.ts +83 -0
- package/dist/scaffolds/notis-journal/packages/sdk/src/provider.tsx +43 -0
- package/dist/scaffolds/notis-journal/packages/sdk/src/runtime.ts +220 -0
- package/dist/scaffolds/notis-journal/packages/sdk/src/styles.css +186 -0
- package/dist/scaffolds/notis-journal/packages/sdk/src/ui.ts +15 -0
- package/dist/scaffolds/notis-journal/packages/sdk/src/vite.ts +56 -0
- package/dist/scaffolds/notis-journal/packages/sdk/tsconfig.json +15 -0
- package/dist/scaffolds/notis-journal/postcss.config.mjs +8 -0
- package/dist/scaffolds/notis-journal/skills/journal-onboarding/SKILL.md +120 -0
- package/dist/scaffolds/notis-journal/src/dev-main.tsx +58 -0
- package/dist/scaffolds/notis-journal/src/mock-runtime.ts +197 -0
- package/dist/scaffolds/notis-journal/tailwind.config.ts +58 -0
- package/dist/scaffolds/notis-journal/tsconfig.json +23 -0
- package/dist/scaffolds/notis-journal/vite.config.ts +10 -0
- package/dist/scaffolds/notis-notes/CHANGELOG.md +5 -0
- package/dist/scaffolds/notis-notes/app/page.tsx +325 -543
- package/dist/scaffolds/notis-notes/metadata/screenshot-1.png +0 -0
- package/dist/scaffolds/notis-notes/notis.config.ts +0 -1
- package/dist/scaffolds/notis-random/CHANGELOG.md +5 -0
- 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/notis.config.ts +0 -1
- package/dist/scaffolds/notis-random/src/mock-runtime.ts +1 -0
- package/dist/scaffolds.json +11 -0
- package/package.json +3 -3
- package/skills/notis-apps/SKILL.md +64 -6
- package/skills/notis-apps/cli.md +38 -2
- package/skills/notis-cli/SKILL.md +20 -2
- package/skills/notis-query/cli.md +1 -1
- package/src/command-specs/apps.js +529 -16
- package/src/command-specs/index.js +1 -1
- package/src/command-specs/meta.js +8 -2
- package/src/command-specs/tools.js +50 -37
- package/src/runtime/agent-browser.js +273 -1
- package/src/runtime/app-changelog.js +79 -0
- package/src/runtime/app-dev-server.js +113 -6
- package/src/runtime/app-dev-sessions.js +176 -12
- package/src/runtime/app-platform.js +200 -39
- package/src/runtime/assets/store-screenshot-dark.png +0 -0
- package/src/runtime/desktop-auth.js +93 -0
- package/src/runtime/profiles.js +37 -15
- package/src/runtime/store-screenshot.js +138 -0
- package/src/runtime/transport.js +21 -82
- package/template/.harness/index.html.tmpl +128 -6
- package/template/CHANGELOG.md +5 -0
- package/template/app/page.tsx +11 -41
- package/template/notis.config.ts +0 -1
- package/template/package-lock.json +4137 -0
- package/template/package.json +1 -0
- package/template/packages/sdk/package.json +4 -0
- package/template/packages/sdk/src/components/DocumentEditor.tsx +93 -0
- package/template/packages/sdk/src/components/Markdown.tsx +60 -0
- package/template/packages/sdk/src/components/MultiSelectActionBar.tsx +7 -2
- package/template/packages/sdk/src/config.ts +74 -0
- package/template/packages/sdk/src/documents.ts +229 -0
- package/template/packages/sdk/src/hooks/useDatabaseSchema.ts +85 -0
- package/template/packages/sdk/src/hooks/useDocument.ts +78 -0
- package/template/packages/sdk/src/hooks/useDocuments.ts +121 -0
- package/template/packages/sdk/src/hooks/useMultiSelect.ts +38 -2
- package/template/packages/sdk/src/hooks/useUpsertDocument.ts +54 -9
- package/template/packages/sdk/src/index.ts +30 -1
- package/template/packages/sdk/src/runtime.ts +76 -17
- package/template/packages/sdk/src/styles.css +148 -0
- package/dist/scaffolds/notis-database/metadata/cover.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-notes/metadata/cover.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-random/metadata/cover.png +0 -0
- package/dist/scaffolds/notis-random/metadata/screenshot-3.png +0 -0
- package/template/metadata/cover.png +0 -0
- package/template/packages/sdk/src/hooks/useDatabase.ts +0 -76
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import { useCallback, useEffect, useState } from 'react';
|
|
4
|
+
import { useNotisRuntime } from '../provider';
|
|
5
|
+
import { normalizeDocumentRecord } from '../documents';
|
|
6
|
+
import type { DocumentRecord, QueryFilter } from '../runtime';
|
|
7
|
+
|
|
8
|
+
interface QueryDatabaseResult {
|
|
9
|
+
documents?: unknown[];
|
|
10
|
+
has_more?: boolean;
|
|
11
|
+
next_offset?: number | null;
|
|
12
|
+
message?: string;
|
|
13
|
+
error?: string;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export interface UseDocumentsOptions {
|
|
17
|
+
filter?: QueryFilter;
|
|
18
|
+
pageSize?: number;
|
|
19
|
+
offset?: number;
|
|
20
|
+
/** Fetch every page, starting at offset, instead of returning only one page. */
|
|
21
|
+
fetchAll?: boolean;
|
|
22
|
+
enabled?: boolean;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export interface UseDocumentsResult {
|
|
26
|
+
documents: DocumentRecord[];
|
|
27
|
+
loading: boolean;
|
|
28
|
+
error: Error | null;
|
|
29
|
+
refetch: () => void;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Query a Notis database and get normalized `DocumentRecord`s.
|
|
34
|
+
*
|
|
35
|
+
* ```tsx
|
|
36
|
+
* const { documents, loading, refetch } = useDocuments('notes', {
|
|
37
|
+
* filter: { filters: [{ property: 'Folder', operator: 'contains', type: 'relation', value: folderId }] },
|
|
38
|
+
* pageSize: 250,
|
|
39
|
+
* });
|
|
40
|
+
* ```
|
|
41
|
+
*/
|
|
42
|
+
export function useDocuments(
|
|
43
|
+
databaseSlug: string,
|
|
44
|
+
options: UseDocumentsOptions = {},
|
|
45
|
+
): UseDocumentsResult {
|
|
46
|
+
const runtime = useNotisRuntime();
|
|
47
|
+
const [documents, setDocuments] = useState<DocumentRecord[]>([]);
|
|
48
|
+
const [loading, setLoading] = useState(true);
|
|
49
|
+
const [error, setError] = useState<Error | null>(null);
|
|
50
|
+
const [fetchKey, setFetchKey] = useState(0);
|
|
51
|
+
|
|
52
|
+
const enabled = options.enabled !== false;
|
|
53
|
+
const filterKey = JSON.stringify(options.filter ?? null);
|
|
54
|
+
|
|
55
|
+
const refetch = useCallback(() => {
|
|
56
|
+
setFetchKey((key) => key + 1);
|
|
57
|
+
}, []);
|
|
58
|
+
|
|
59
|
+
useEffect(() => {
|
|
60
|
+
if (!runtime || !enabled) {
|
|
61
|
+
setLoading(false);
|
|
62
|
+
return;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
let cancelled = false;
|
|
66
|
+
setLoading(true);
|
|
67
|
+
setError(null);
|
|
68
|
+
|
|
69
|
+
const filter = filterKey === 'null' ? null : (JSON.parse(filterKey) as QueryFilter);
|
|
70
|
+
|
|
71
|
+
const fetchDocuments = async (): Promise<unknown[]> => {
|
|
72
|
+
const allDocuments: unknown[] = [];
|
|
73
|
+
let offset = options.offset ?? 0;
|
|
74
|
+
|
|
75
|
+
while (true) {
|
|
76
|
+
const result = await runtime.callTool<QueryDatabaseResult>('LOCAL_NOTIS_DATABASE_QUERY', {
|
|
77
|
+
database_slug: databaseSlug,
|
|
78
|
+
query: {
|
|
79
|
+
...(filter ?? {}),
|
|
80
|
+
...(options.pageSize !== undefined ? { page_size: options.pageSize } : {}),
|
|
81
|
+
},
|
|
82
|
+
...(offset > 0 ? { offset } : {}),
|
|
83
|
+
});
|
|
84
|
+
const message = result.error ?? result.message;
|
|
85
|
+
if (!result.documents && message) {
|
|
86
|
+
throw new Error(message);
|
|
87
|
+
}
|
|
88
|
+
allDocuments.push(...(result.documents ?? []));
|
|
89
|
+
if (!options.fetchAll || !result.has_more) return allDocuments;
|
|
90
|
+
|
|
91
|
+
const nextOffset = result.next_offset;
|
|
92
|
+
if (typeof nextOffset !== 'number' || nextOffset <= offset) {
|
|
93
|
+
throw new Error('Database query returned an invalid pagination offset');
|
|
94
|
+
}
|
|
95
|
+
offset = nextOffset;
|
|
96
|
+
}
|
|
97
|
+
};
|
|
98
|
+
|
|
99
|
+
fetchDocuments()
|
|
100
|
+
.then((result) => {
|
|
101
|
+
if (cancelled) return;
|
|
102
|
+
setDocuments(
|
|
103
|
+
result
|
|
104
|
+
.map(normalizeDocumentRecord)
|
|
105
|
+
.filter((document) => document.id),
|
|
106
|
+
);
|
|
107
|
+
setLoading(false);
|
|
108
|
+
})
|
|
109
|
+
.catch((err) => {
|
|
110
|
+
if (cancelled) return;
|
|
111
|
+
setError(err instanceof Error ? err : new Error(String(err)));
|
|
112
|
+
setLoading(false);
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
return () => {
|
|
116
|
+
cancelled = true;
|
|
117
|
+
};
|
|
118
|
+
}, [runtime, databaseSlug, enabled, fetchKey, filterKey, options.fetchAll, options.offset, options.pageSize]);
|
|
119
|
+
|
|
120
|
+
return { documents, loading, error, refetch };
|
|
121
|
+
}
|
|
@@ -59,6 +59,19 @@ export interface MultiSelectController<T> {
|
|
|
59
59
|
|
|
60
60
|
/** Spread on each selectable row so drag-select can find them. */
|
|
61
61
|
getRowProps: (id: string) => { [ROW_ATTR]: string };
|
|
62
|
+
/**
|
|
63
|
+
* Spread on each selectable row/card — bundles `getRowProps` (the drag-select
|
|
64
|
+
* attribute) with the Cmd/Ctrl+click `onMouseDown` handler in one spread.
|
|
65
|
+
*/
|
|
66
|
+
getItemProps: (id: string) => {
|
|
67
|
+
[ROW_ATTR]: string;
|
|
68
|
+
onMouseDown: (event: ReactMouseEvent) => void;
|
|
69
|
+
};
|
|
70
|
+
/** Spread onto `<MultiSelectCheckbox {...getCheckboxProps(id)} />`. */
|
|
71
|
+
getCheckboxProps: (id: string) => {
|
|
72
|
+
isSelected: boolean;
|
|
73
|
+
onClick: (event: ReactMouseEvent) => void;
|
|
74
|
+
};
|
|
62
75
|
/** Spread on the list container — owns the drag-select mousedown/move/up + click-empty-to-clear. */
|
|
63
76
|
getContainerProps: () => {
|
|
64
77
|
ref: (node: HTMLElement | null) => void;
|
|
@@ -68,7 +81,12 @@ export interface MultiSelectController<T> {
|
|
|
68
81
|
};
|
|
69
82
|
}
|
|
70
83
|
|
|
71
|
-
function
|
|
84
|
+
function isEditableEvent(event: Event): boolean {
|
|
85
|
+
// Notis apps mount inside a Shadow DOM, so a document-level listener sees
|
|
86
|
+
// `event.target` retargeted to the shadow host rather than the real focused
|
|
87
|
+
// element. `composedPath()[0]` pierces the shadow boundary to the actual
|
|
88
|
+
// target, so typing in an app input doesn't trigger selection shortcuts.
|
|
89
|
+
const target = event.composedPath?.()[0] ?? event.target;
|
|
72
90
|
if (!(target instanceof HTMLElement)) return false;
|
|
73
91
|
if (target.isContentEditable) return true;
|
|
74
92
|
const tag = target.tagName;
|
|
@@ -218,6 +236,22 @@ export function useMultiSelect<T>(opts: UseMultiSelectOptions<T>): MultiSelectCo
|
|
|
218
236
|
[],
|
|
219
237
|
);
|
|
220
238
|
|
|
239
|
+
const getItemProps = useCallback(
|
|
240
|
+
(id: string) => ({
|
|
241
|
+
[ROW_ATTR]: id,
|
|
242
|
+
onMouseDown: onRowMouseDown(id),
|
|
243
|
+
}),
|
|
244
|
+
[onRowMouseDown],
|
|
245
|
+
);
|
|
246
|
+
|
|
247
|
+
const getCheckboxProps = useCallback(
|
|
248
|
+
(id: string) => ({
|
|
249
|
+
isSelected: selectedIdsRef.current.has(id),
|
|
250
|
+
onClick: onCheckboxClick(id),
|
|
251
|
+
}),
|
|
252
|
+
[onCheckboxClick],
|
|
253
|
+
);
|
|
254
|
+
|
|
221
255
|
// ---------------------------------------------------------------------------
|
|
222
256
|
// Drag-select
|
|
223
257
|
// ---------------------------------------------------------------------------
|
|
@@ -386,7 +420,7 @@ export function useMultiSelect<T>(opts: UseMultiSelectOptions<T>): MultiSelectCo
|
|
|
386
420
|
if (!bindKeyboardShortcuts) return;
|
|
387
421
|
|
|
388
422
|
const handleKeyDown = (event: KeyboardEvent) => {
|
|
389
|
-
if (
|
|
423
|
+
if (isEditableEvent(event)) return;
|
|
390
424
|
|
|
391
425
|
// Esc — clear selection
|
|
392
426
|
if (event.key === 'Escape') {
|
|
@@ -498,6 +532,8 @@ export function useMultiSelect<T>(opts: UseMultiSelectOptions<T>): MultiSelectCo
|
|
|
498
532
|
onCheckboxClick,
|
|
499
533
|
onRowMouseDown,
|
|
500
534
|
getRowProps,
|
|
535
|
+
getItemProps,
|
|
536
|
+
getCheckboxProps,
|
|
501
537
|
getContainerProps,
|
|
502
538
|
};
|
|
503
539
|
}
|
|
@@ -2,39 +2,84 @@
|
|
|
2
2
|
|
|
3
3
|
import { useCallback, useState } from 'react';
|
|
4
4
|
import { useNotisRuntime } from '../provider';
|
|
5
|
+
import { normalizeDocumentRecord } from '../documents';
|
|
5
6
|
import type { DocumentRecord } from '../runtime';
|
|
6
7
|
|
|
7
|
-
interface
|
|
8
|
-
databaseSlug: string;
|
|
8
|
+
export interface UpsertDocumentArgs {
|
|
9
9
|
documentId?: string;
|
|
10
10
|
title?: string;
|
|
11
|
+
/** Property values keyed by property name (already-plain values). */
|
|
11
12
|
properties?: Record<string, unknown>;
|
|
13
|
+
/** Markdown content; converted server-side to BlockNote. Exclusive with contentBlocknote. */
|
|
14
|
+
contentMarkdown?: string | null;
|
|
15
|
+
/** BlockNote block JSON. Exclusive with contentMarkdown. */
|
|
12
16
|
contentBlocknote?: Array<Record<string, unknown>> | null;
|
|
17
|
+
/** 'archive' soft-deletes the document (sets archived_at); 'restore' undoes it. */
|
|
18
|
+
operation?: 'create' | 'update' | 'archive' | 'restore';
|
|
13
19
|
}
|
|
14
20
|
|
|
15
|
-
interface
|
|
16
|
-
|
|
21
|
+
interface UpsertToolResult {
|
|
22
|
+
status?: string;
|
|
23
|
+
document?: unknown;
|
|
24
|
+
message?: string;
|
|
25
|
+
error?: string;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export interface UseUpsertDocumentResult {
|
|
29
|
+
upsert: (args: UpsertDocumentArgs) => Promise<DocumentRecord>;
|
|
17
30
|
loading: boolean;
|
|
18
31
|
error: Error | null;
|
|
19
32
|
}
|
|
20
33
|
|
|
21
|
-
|
|
34
|
+
function upsertToolName(databaseSlug: string): string {
|
|
35
|
+
return `LOCAL_NOTIS_DATABASE_UPSERT_${databaseSlug.replace(/-/g, '_').toUpperCase()}`;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function buildToolArgs(args: UpsertDocumentArgs): Record<string, unknown> {
|
|
39
|
+
const payload: Record<string, unknown> = {};
|
|
40
|
+
if (args.documentId) payload.document_id = args.documentId;
|
|
41
|
+
if (typeof args.title === 'string') payload.title = args.title;
|
|
42
|
+
if (args.properties) Object.assign(payload, args.properties);
|
|
43
|
+
if ('contentMarkdown' in args) payload.content_markdown = args.contentMarkdown ?? '';
|
|
44
|
+
if ('contentBlocknote' in args) payload.content_blocknote = args.contentBlocknote ?? null;
|
|
45
|
+
if (args.operation) payload.operation = args.operation;
|
|
46
|
+
return payload;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Create/update/archive documents in a Notis database.
|
|
51
|
+
*
|
|
52
|
+
* ```tsx
|
|
53
|
+
* const { upsert } = useUpsertDocument('journal_entries');
|
|
54
|
+
* await upsert({ documentId, properties: { Mood: 'Great' }, contentMarkdown: reflection });
|
|
55
|
+
* ```
|
|
56
|
+
*/
|
|
57
|
+
export function useUpsertDocument(databaseSlug: string): UseUpsertDocumentResult {
|
|
22
58
|
const runtime = useNotisRuntime();
|
|
23
59
|
const [loading, setLoading] = useState(false);
|
|
24
60
|
const [error, setError] = useState<Error | null>(null);
|
|
25
61
|
|
|
26
62
|
const upsert = useCallback(
|
|
27
|
-
async (args:
|
|
63
|
+
async (args: UpsertDocumentArgs): Promise<DocumentRecord> => {
|
|
28
64
|
if (!runtime) {
|
|
29
65
|
throw new Error('Notis runtime not available. Ensure NotisProvider is mounted.');
|
|
30
66
|
}
|
|
67
|
+
if ('contentMarkdown' in args && 'contentBlocknote' in args) {
|
|
68
|
+
throw new Error('Provide contentMarkdown or contentBlocknote, not both.');
|
|
69
|
+
}
|
|
31
70
|
|
|
32
71
|
setLoading(true);
|
|
33
72
|
setError(null);
|
|
34
73
|
|
|
35
74
|
try {
|
|
36
|
-
const result = await runtime.
|
|
37
|
-
|
|
75
|
+
const result = await runtime.callTool<UpsertToolResult>(
|
|
76
|
+
upsertToolName(databaseSlug),
|
|
77
|
+
buildToolArgs(args),
|
|
78
|
+
);
|
|
79
|
+
if (!result.document) {
|
|
80
|
+
throw new Error(result.error ?? result.message ?? 'Failed to save document');
|
|
81
|
+
}
|
|
82
|
+
return normalizeDocumentRecord(result.document);
|
|
38
83
|
} catch (err) {
|
|
39
84
|
const e = err instanceof Error ? err : new Error(String(err));
|
|
40
85
|
setError(e);
|
|
@@ -43,7 +88,7 @@ export function useUpsertDocument(): UseUpsertDocumentResult {
|
|
|
43
88
|
setLoading(false);
|
|
44
89
|
}
|
|
45
90
|
},
|
|
46
|
-
[runtime],
|
|
91
|
+
[runtime, databaseSlug],
|
|
47
92
|
);
|
|
48
93
|
|
|
49
94
|
return { upsert, loading, error };
|
|
@@ -11,8 +11,14 @@ export { NotisProvider, useNotisRuntime } from './provider';
|
|
|
11
11
|
|
|
12
12
|
// Hooks
|
|
13
13
|
export { useNotis } from './hooks/useNotis';
|
|
14
|
-
export {
|
|
14
|
+
export { useDocuments } from './hooks/useDocuments';
|
|
15
|
+
export type { UseDocumentsOptions, UseDocumentsResult } from './hooks/useDocuments';
|
|
16
|
+
export { useDocument } from './hooks/useDocument';
|
|
17
|
+
export type { UseDocumentOptions, UseDocumentResult } from './hooks/useDocument';
|
|
15
18
|
export { useUpsertDocument } from './hooks/useUpsertDocument';
|
|
19
|
+
export type { UpsertDocumentArgs, UseUpsertDocumentResult } from './hooks/useUpsertDocument';
|
|
20
|
+
export { useDatabaseSchema } from './hooks/useDatabaseSchema';
|
|
21
|
+
export type { UseDatabaseSchemaResult } from './hooks/useDatabaseSchema';
|
|
16
22
|
export { useTool } from './hooks/useTool';
|
|
17
23
|
export type { ToolCallState, UseToolResult } from './hooks/useTool';
|
|
18
24
|
export { useTools } from './hooks/useTools';
|
|
@@ -21,6 +27,24 @@ export { useTopBarSearch } from './hooks/useTopBarSearch';
|
|
|
21
27
|
export { useBackend } from './hooks/useBackend';
|
|
22
28
|
export { useMultiSelect } from './hooks/useMultiSelect';
|
|
23
29
|
|
|
30
|
+
// Documents & markdown
|
|
31
|
+
export { Markdown } from './components/Markdown';
|
|
32
|
+
export type { MarkdownProps } from './components/Markdown';
|
|
33
|
+
export { DocumentEditor } from './components/DocumentEditor';
|
|
34
|
+
export {
|
|
35
|
+
asRecord,
|
|
36
|
+
blockNoteToPlainText,
|
|
37
|
+
extractRichText,
|
|
38
|
+
getDocumentPreview,
|
|
39
|
+
getRelationIds,
|
|
40
|
+
isPresentString,
|
|
41
|
+
markdownToPlainText,
|
|
42
|
+
normalizeDatabaseProperty,
|
|
43
|
+
normalizeDocumentRecord,
|
|
44
|
+
normalizePropertyValue,
|
|
45
|
+
optionalString,
|
|
46
|
+
} from './documents';
|
|
47
|
+
|
|
24
48
|
// Multi-select components
|
|
25
49
|
export { MultiSelectActionBar } from './components/MultiSelectActionBar';
|
|
26
50
|
export { MultiSelectCheckbox } from './components/MultiSelectCheckbox';
|
|
@@ -44,9 +68,14 @@ export type {
|
|
|
44
68
|
CollectionItemDetail,
|
|
45
69
|
DatabaseDescriptor,
|
|
46
70
|
DatabaseProperty,
|
|
71
|
+
DatabasePropertyOption,
|
|
72
|
+
DatabasePropertyType,
|
|
73
|
+
DocumentContentType,
|
|
47
74
|
DocumentRecord,
|
|
75
|
+
NotisDocumentEditorProps,
|
|
48
76
|
NotisRuntime,
|
|
49
77
|
NotisRuntimeContext,
|
|
78
|
+
NotisRuntimeUI,
|
|
50
79
|
QueryFilter,
|
|
51
80
|
RouteDescriptor,
|
|
52
81
|
ToolDescriptor,
|
|
@@ -7,15 +7,39 @@
|
|
|
7
7
|
* (useTool, useNotis, etc.) which read from the NotisProvider context.
|
|
8
8
|
*/
|
|
9
9
|
|
|
10
|
+
import type { ComponentType } from 'react';
|
|
11
|
+
|
|
10
12
|
// ---------------------------------------------------------------------------
|
|
11
13
|
// Database types
|
|
12
14
|
// ---------------------------------------------------------------------------
|
|
13
15
|
|
|
16
|
+
export type DatabasePropertyType =
|
|
17
|
+
| 'title'
|
|
18
|
+
| 'rich_text'
|
|
19
|
+
| 'text'
|
|
20
|
+
| 'number'
|
|
21
|
+
| 'checkbox'
|
|
22
|
+
| 'date'
|
|
23
|
+
| 'select'
|
|
24
|
+
| 'multi_select'
|
|
25
|
+
| 'status'
|
|
26
|
+
| 'relation'
|
|
27
|
+
| 'formula'
|
|
28
|
+
| 'files';
|
|
29
|
+
|
|
30
|
+
export interface DatabasePropertyOption {
|
|
31
|
+
id?: string | null;
|
|
32
|
+
name: string;
|
|
33
|
+
color?: string | null;
|
|
34
|
+
order?: number;
|
|
35
|
+
}
|
|
36
|
+
|
|
14
37
|
export interface DatabaseProperty {
|
|
38
|
+
id?: string | null;
|
|
15
39
|
name: string;
|
|
16
|
-
type:
|
|
17
|
-
description?: string;
|
|
18
|
-
options?:
|
|
40
|
+
type: DatabasePropertyType;
|
|
41
|
+
description?: string | null;
|
|
42
|
+
options?: DatabasePropertyOption[];
|
|
19
43
|
}
|
|
20
44
|
|
|
21
45
|
export interface DatabaseDescriptor {
|
|
@@ -26,13 +50,25 @@ export interface DatabaseDescriptor {
|
|
|
26
50
|
properties: DatabaseProperty[];
|
|
27
51
|
}
|
|
28
52
|
|
|
53
|
+
/**
|
|
54
|
+
* A document's content is typed. `markdown` documents carry rich text as
|
|
55
|
+
* BlockNote JSON (canonical) with a derived markdown projection; `file`
|
|
56
|
+
* documents carry an uploaded file whose format is in `fileType`
|
|
57
|
+
* (pdf, xlsx, pptx, ...). Future content types (canvas, ...) extend this
|
|
58
|
+
* union without changing the component contracts.
|
|
59
|
+
*/
|
|
60
|
+
export type DocumentContentType = 'markdown' | 'file';
|
|
61
|
+
|
|
29
62
|
export interface DocumentRecord {
|
|
30
63
|
id: string;
|
|
31
64
|
title: string;
|
|
32
65
|
properties: Record<string, unknown>;
|
|
66
|
+
url?: string | null;
|
|
33
67
|
icon?: string | null;
|
|
34
68
|
cover?: string | null;
|
|
35
69
|
databaseSlug?: string;
|
|
70
|
+
contentType?: DocumentContentType | null;
|
|
71
|
+
fileType?: string | null;
|
|
36
72
|
contentBlocknote?: Array<Record<string, unknown>> | null;
|
|
37
73
|
contentMarkdown?: string | null;
|
|
38
74
|
plainText?: string | null;
|
|
@@ -99,6 +135,7 @@ export interface QueryFilter {
|
|
|
99
135
|
filters?: Array<{
|
|
100
136
|
property: string;
|
|
101
137
|
operator: string;
|
|
138
|
+
type?: string;
|
|
102
139
|
value: unknown;
|
|
103
140
|
}>;
|
|
104
141
|
sorts?: Array<{
|
|
@@ -110,6 +147,41 @@ export interface QueryFilter {
|
|
|
110
147
|
|
|
111
148
|
export interface NotisRuntimeContext {
|
|
112
149
|
collectionItem?: CollectionItemDetail | null;
|
|
150
|
+
/**
|
|
151
|
+
* Set when the app is being rendered by the screenshot harness (`notis apps
|
|
152
|
+
* screenshot`) for the named listing scenario. Lets apps and SDK components
|
|
153
|
+
* hide dev-only chrome from listing images.
|
|
154
|
+
*/
|
|
155
|
+
screenshotScenario?: string | null;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
// ---------------------------------------------------------------------------
|
|
159
|
+
// Host-provided UI
|
|
160
|
+
// ---------------------------------------------------------------------------
|
|
161
|
+
|
|
162
|
+
/**
|
|
163
|
+
* Props for the host-provided document editor. The host implementation
|
|
164
|
+
* dispatches on the document's content type (markdown -> rich text editor,
|
|
165
|
+
* file -> matching viewer/editor), so this contract stays stable as new
|
|
166
|
+
* content types ship.
|
|
167
|
+
*/
|
|
168
|
+
export interface NotisDocumentEditorProps {
|
|
169
|
+
documentId: string;
|
|
170
|
+
/** 'full' renders icon/title/cover above the content; 'body' renders content only. */
|
|
171
|
+
variant?: 'full' | 'body';
|
|
172
|
+
readOnly?: boolean;
|
|
173
|
+
className?: string;
|
|
174
|
+
onDirtyChange?: (dirty: boolean) => void;
|
|
175
|
+
onSavingChange?: (saving: boolean) => void;
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
/**
|
|
179
|
+
* Components the host (portal) injects through the runtime. Apps consume them
|
|
180
|
+
* via the SDK wrappers (e.g. `DocumentEditor`), which fall back gracefully
|
|
181
|
+
* when a host component is unavailable (standalone dev harness, snapshots).
|
|
182
|
+
*/
|
|
183
|
+
export interface NotisRuntimeUI {
|
|
184
|
+
DocumentEditor?: ComponentType<NotisDocumentEditorProps>;
|
|
113
185
|
}
|
|
114
186
|
|
|
115
187
|
// ---------------------------------------------------------------------------
|
|
@@ -121,6 +193,7 @@ export interface NotisRuntime {
|
|
|
121
193
|
route: RouteDescriptor;
|
|
122
194
|
databases: DatabaseDescriptor[];
|
|
123
195
|
context: NotisRuntimeContext;
|
|
196
|
+
ui?: NotisRuntimeUI;
|
|
124
197
|
|
|
125
198
|
navigate?: (payload: { kind: string; [key: string]: unknown }) => void;
|
|
126
199
|
|
|
@@ -139,20 +212,6 @@ export interface NotisRuntime {
|
|
|
139
212
|
listTools(): Promise<ToolDescriptor[]>;
|
|
140
213
|
callTool<TResult = unknown>(name: string, args?: Record<string, unknown>): Promise<TResult>;
|
|
141
214
|
|
|
142
|
-
queryDatabase(args: {
|
|
143
|
-
databaseSlug: string;
|
|
144
|
-
query?: QueryFilter;
|
|
145
|
-
offset?: number;
|
|
146
|
-
}): Promise<{ documents: DocumentRecord[] }>;
|
|
147
|
-
|
|
148
|
-
upsertDocument(args: {
|
|
149
|
-
databaseSlug: string;
|
|
150
|
-
documentId?: string;
|
|
151
|
-
title?: string;
|
|
152
|
-
properties?: Record<string, unknown>;
|
|
153
|
-
contentBlocknote?: Array<Record<string, unknown>> | null;
|
|
154
|
-
}): Promise<{ status: string; document: DocumentRecord }>;
|
|
155
|
-
|
|
156
215
|
request(path: string, options?: {
|
|
157
216
|
method?: string;
|
|
158
217
|
headers?: Record<string, string>;
|
|
@@ -36,3 +36,151 @@
|
|
|
36
36
|
@apply p-5 md:p-6;
|
|
37
37
|
}
|
|
38
38
|
}
|
|
39
|
+
|
|
40
|
+
/*
|
|
41
|
+
* Markdown value-type rendering (see the `Markdown` component). Plain CSS on
|
|
42
|
+
* theme variables so it works inside the portal shadow root and the dev
|
|
43
|
+
* harness without depending on the app's Tailwind content scan.
|
|
44
|
+
*/
|
|
45
|
+
.notis-markdown {
|
|
46
|
+
font-size: 14px;
|
|
47
|
+
line-height: 1.65;
|
|
48
|
+
color: hsl(var(--foreground));
|
|
49
|
+
overflow-wrap: break-word;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
.notis-markdown--sm {
|
|
53
|
+
font-size: 13px;
|
|
54
|
+
line-height: 1.55;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
.notis-markdown > :first-child {
|
|
58
|
+
margin-top: 0;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
.notis-markdown > :last-child {
|
|
62
|
+
margin-bottom: 0;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
.notis-markdown p {
|
|
66
|
+
margin: 0.625em 0;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
.notis-markdown h1,
|
|
70
|
+
.notis-markdown h2,
|
|
71
|
+
.notis-markdown h3,
|
|
72
|
+
.notis-markdown h4,
|
|
73
|
+
.notis-markdown h5,
|
|
74
|
+
.notis-markdown h6 {
|
|
75
|
+
margin: 1.25em 0 0.5em;
|
|
76
|
+
font-weight: 650;
|
|
77
|
+
letter-spacing: -0.01em;
|
|
78
|
+
line-height: 1.3;
|
|
79
|
+
color: hsl(var(--foreground));
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
.notis-markdown h1 { font-size: 1.5em; }
|
|
83
|
+
.notis-markdown h2 { font-size: 1.25em; }
|
|
84
|
+
.notis-markdown h3 { font-size: 1.1em; }
|
|
85
|
+
.notis-markdown h4,
|
|
86
|
+
.notis-markdown h5,
|
|
87
|
+
.notis-markdown h6 { font-size: 1em; }
|
|
88
|
+
|
|
89
|
+
.notis-markdown ul,
|
|
90
|
+
.notis-markdown ol {
|
|
91
|
+
margin: 0.625em 0;
|
|
92
|
+
padding-left: 1.5em;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
.notis-markdown ul { list-style-type: disc; }
|
|
96
|
+
.notis-markdown ol { list-style-type: decimal; }
|
|
97
|
+
|
|
98
|
+
.notis-markdown li {
|
|
99
|
+
margin: 0.25em 0;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
.notis-markdown li > ul,
|
|
103
|
+
.notis-markdown li > ol {
|
|
104
|
+
margin: 0.25em 0;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
.notis-markdown ul.contains-task-list {
|
|
108
|
+
list-style: none;
|
|
109
|
+
padding-left: 0.375em;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
.notis-markdown li.task-list-item input[type='checkbox'] {
|
|
113
|
+
margin-right: 0.5em;
|
|
114
|
+
accent-color: hsl(var(--primary));
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
.notis-markdown blockquote {
|
|
118
|
+
margin: 0.75em 0;
|
|
119
|
+
padding: 0.125em 0 0.125em 0.875em;
|
|
120
|
+
border-left: 3px solid hsl(var(--border));
|
|
121
|
+
color: hsl(var(--muted-foreground));
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
.notis-markdown a {
|
|
125
|
+
color: hsl(var(--primary));
|
|
126
|
+
text-decoration: underline;
|
|
127
|
+
text-decoration-color: hsl(var(--primary) / 0.35);
|
|
128
|
+
text-underline-offset: 2px;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
.notis-markdown a:hover {
|
|
132
|
+
text-decoration-color: hsl(var(--primary));
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
.notis-markdown code {
|
|
136
|
+
font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
|
|
137
|
+
font-size: 0.875em;
|
|
138
|
+
padding: 0.125em 0.375em;
|
|
139
|
+
border-radius: 0.25rem;
|
|
140
|
+
background: hsl(var(--muted));
|
|
141
|
+
color: hsl(var(--foreground));
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
.notis-markdown pre {
|
|
145
|
+
margin: 0.75em 0;
|
|
146
|
+
padding: 0.75em 1em;
|
|
147
|
+
border-radius: 0.5rem;
|
|
148
|
+
background: hsl(var(--muted));
|
|
149
|
+
overflow-x: auto;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
.notis-markdown pre code {
|
|
153
|
+
padding: 0;
|
|
154
|
+
background: transparent;
|
|
155
|
+
font-size: 0.875em;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
.notis-markdown hr {
|
|
159
|
+
margin: 1.25em 0;
|
|
160
|
+
border: 0;
|
|
161
|
+
border-top: 1px solid hsl(var(--border));
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
.notis-markdown img {
|
|
165
|
+
max-width: 100%;
|
|
166
|
+
border-radius: 0.5rem;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
.notis-markdown table {
|
|
170
|
+
margin: 0.75em 0;
|
|
171
|
+
width: 100%;
|
|
172
|
+
border-collapse: collapse;
|
|
173
|
+
font-size: 0.95em;
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
.notis-markdown th,
|
|
177
|
+
.notis-markdown td {
|
|
178
|
+
padding: 0.375em 0.75em;
|
|
179
|
+
border: 1px solid hsl(var(--border));
|
|
180
|
+
text-align: left;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
.notis-markdown th {
|
|
184
|
+
background: hsl(var(--muted));
|
|
185
|
+
font-weight: 600;
|
|
186
|
+
}
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|