@notis_ai/cli 0.2.0-beta.178.1 → 0.2.0-beta.1792022492092.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 +548 -98
- package/config/portal-route-roots.json +47 -0
- package/dist/agent-hooks/notis-agent-hook.mjs +32901 -5934
- package/dist/base-skills/notis-apps/SKILL.md +76 -51
- package/dist/base-skills/notis-apps/references/architecture.md +116 -164
- package/dist/base-skills/notis-apps/references/design.md +26 -107
- package/dist/base-skills/notis-apps/references/memory.md +58 -0
- package/dist/base-skills/notis-apps/references/reading.md +52 -74
- package/dist/base-skills/notis-apps/references/release.md +105 -96
- package/dist/base-skills/notis-apps/references/sdk.md +115 -60
- package/dist/base-skills/notis-apps/references/troubleshooting.md +22 -21
- package/dist/base-skills/notis-apps/references/views.md +145 -0
- package/dist/base-skills/notis-cli/SKILL.md +29 -12
- package/dist/base-skills/notis-cli/references/app-delivery.md +12 -11
- package/dist/base-skills/notis-cli/references/intelligence.md +94 -0
- package/dist/base-skills/notis-cli/references/native-databases.md +6 -5
- package/dist/base-skills/notis-cli/references/tool-examples.md +4 -4
- package/dist/base-skills/notis-query/SKILL.md +34 -60
- package/dist/base-skills/notis-query/references/database-discovery.md +60 -59
- package/dist/base-skills/notis-query/references/documents.md +53 -39
- package/dist/base-skills/notis-query/references/query.md +57 -524
- package/dist/sdk/src/bundleModule.ts +79 -0
- package/dist/sdk/src/components/DocumentEditor.tsx +6 -3
- package/dist/sdk/src/components/DocumentPage.tsx +144 -0
- package/dist/sdk/src/components/RecordComponents.tsx +33 -0
- package/dist/sdk/src/components/RenderChartData.tsx +19 -0
- package/dist/sdk/src/config.ts +4 -26
- package/dist/sdk/src/documents.ts +2 -0
- package/dist/sdk/src/hooks/useAction.ts +28 -0
- package/dist/sdk/src/hooks/useBackend.ts +3 -2
- package/dist/sdk/src/hooks/useCloudComputer.ts +35 -4
- package/dist/sdk/src/hooks/useCloudComputerShell.ts +114 -0
- package/dist/sdk/src/hooks/useCollectionInteractions.ts +4 -2
- package/dist/sdk/src/hooks/useHandover.ts +7 -7
- package/dist/sdk/src/hooks/useNativeDocumentBody.ts +50 -0
- package/dist/sdk/src/hooks/useNativeDocuments.ts +83 -0
- package/dist/sdk/src/hooks/useNotis.ts +3 -0
- package/dist/sdk/src/hooks/useNotisNavigation.ts +26 -11
- package/dist/sdk/src/hooks/useQuery.ts +7 -4
- package/dist/sdk/src/hooks/useViewParams.ts +121 -0
- package/dist/sdk/src/hooks/useViewerReads.ts +129 -0
- package/dist/sdk/src/index.ts +36 -0
- package/dist/sdk/src/interactions/shortcuts.tsx +42 -5
- package/dist/sdk/src/nativeData.ts +39 -0
- package/dist/sdk/src/presentation.tsx +82 -0
- package/dist/sdk/src/provider.tsx +9 -4
- package/dist/sdk/src/queryCache.ts +5 -3
- package/dist/sdk/src/queryErrors.ts +51 -0
- package/dist/sdk/src/runtime.ts +163 -13
- package/dist/sdk/src/space.ts +425 -0
- package/dist/sdk/src/styles.css +96 -72
- package/dist/sdk/src/vite.ts +11 -1
- package/dist/skill-sync/index.js +571 -80
- package/dist/skill-sync/index.js.map +4 -4
- package/dist/skill-sync-worker.mjs +858 -233
- package/dist/space-harness/host.js +66 -0
- package/dist/view-renderer/dist/host.css +1 -0
- package/dist/view-renderer/dist/host.js +1028 -0
- package/dist/view-renderer/dist/theme.css +73 -0
- package/dist/view-renderer/src/dom.js +180 -0
- package/dist/view-renderer/src/errors.js +8 -0
- package/dist/view-renderer/src/host.tsx +175 -0
- package/dist/view-renderer/src/index.js +4 -0
- package/dist/view-renderer/src/pool.js +76 -0
- package/dist/view-renderer/src/remote-broker.js +59 -0
- package/dist/view-renderer/src/renderer.js +201 -0
- package/package.json +16 -5
- package/skills/notis-apps/cli.md +323 -163
- package/skills/notis-cli/AGENT_INSTRUCTIONS.md +3 -3
- package/skills/notis-onboarding/BRIEF.md +7 -3
- package/src/command-specs/apps.js +2 -1
- package/src/command-specs/auth.js +2 -2
- package/src/command-specs/files.js +29 -0
- package/src/command-specs/handover.js +331 -79
- package/src/command-specs/helpers.js +2 -0
- package/src/command-specs/index.js +11 -5
- package/src/command-specs/profile.js +42 -1
- package/src/command-specs/skills.js +242 -1
- package/src/command-specs/spaces.js +645 -0
- package/src/command-specs/tools.js +0 -6
- package/src/command-specs/views.js +69 -0
- package/src/runtime/agent-browser.js +92 -29
- package/src/runtime/app-boundary-validator.js +7 -4
- package/src/runtime/app-data-literals.js +82 -0
- package/src/runtime/app-platform.js +321 -253
- package/src/runtime/computer-identity.d.ts +30 -0
- package/src/runtime/computer-identity.js +151 -0
- package/src/runtime/config-lock.js +135 -0
- package/src/runtime/confined-space-build.js +74 -0
- package/src/runtime/confined-space-verify.js +49 -0
- package/src/runtime/delegated-context.js +5 -21
- package/src/runtime/native-record-files.js +47 -0
- package/src/runtime/oauth.js +18 -1
- package/src/runtime/profiles.js +5 -107
- package/src/runtime/retired-sdk-files.js +20 -0
- package/src/runtime/screenshot-framing.js +85 -0
- package/src/runtime/skill-sync/cloud-client.ts +26 -4
- package/src/runtime/skill-sync/index.ts +230 -35
- package/src/runtime/skill-sync/local-scanner.ts +188 -29
- package/src/runtime/skill-sync/native-identity.ts +102 -0
- package/src/runtime/skill-sync/native-plan.ts +70 -0
- package/src/runtime/skill-sync/sync-plan.ts +16 -9
- package/src/runtime/skill-sync/types.ts +32 -6
- package/src/runtime/source-root.js +32 -0
- package/src/runtime/space-collection-scaffold.js +66 -0
- package/src/runtime/space-database-structures.js +229 -0
- package/src/runtime/space-fixture-broker.js +50 -0
- package/src/runtime/space-lock.js +337 -0
- package/src/runtime/space-platform.js +105 -0
- package/src/runtime/space-source.js +281 -0
- package/src/runtime/space-test-server.js +327 -0
- package/src/runtime/space-verification.js +89 -0
- package/src/runtime/space-verify-browser-worker.js +51 -0
- package/src/runtime/space-view-manifest.js +254 -0
- package/src/runtime/store-screenshot.js +10 -7
- package/src/runtime/sync-skills.d.ts +3 -1
- package/src/runtime/sync-skills.js +10 -2
- package/src/runtime/transport.js +8 -6
- package/src/runtime/view-render.js +49 -0
- package/src/skill-sync-worker.js +14 -2
- package/src/space-harness-host.ts +103 -0
- package/template/.harness/index.html.tmpl +7 -10
- package/template/app/globals.css +1 -0
- package/src/command-specs/reports.js +0 -97
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
/** The native row contract, shared by direct tools and scoped Space actions. */
|
|
2
|
+
export type NativeField = { property_id: string } | { property: string } | {
|
|
3
|
+
column: 'record_key' | 'id' | 'title' | 'created_at' | 'updated_at' | 'last_edited_time' | 'content_type' | 'file_type';
|
|
4
|
+
};
|
|
5
|
+
export type NativeFilter = { and: NativeFilter[] } | { or: NativeFilter[] } | {
|
|
6
|
+
field: NativeField;
|
|
7
|
+
op: 'equals' | 'not_equals' | 'in' | 'contains' | 'not_contains' | 'does_not_contain'
|
|
8
|
+
| 'starts_with' | 'ends_with' | 'is_empty' | 'is_not_empty' | 'greater_than' | 'less_than'
|
|
9
|
+
| 'greater_than_or_equal_to' | 'less_than_or_equal_to' | 'before' | 'after' | 'on_or_before' | 'on_or_after';
|
|
10
|
+
value?: unknown;
|
|
11
|
+
};
|
|
12
|
+
export interface NativeQuery {
|
|
13
|
+
include_content?: boolean;
|
|
14
|
+
include_archived?: boolean;
|
|
15
|
+
mode?: 'rows' | 'search' | 'count' | 'aggregate';
|
|
16
|
+
filter?: NativeFilter;
|
|
17
|
+
sorts?: Array<{ field: NativeField; direction: 'asc' | 'desc' }>;
|
|
18
|
+
search?: { text: string; fields: NativeField[] };
|
|
19
|
+
aggregate?: { op: 'sum' | 'avg' | 'min' | 'max'; field: NativeField };
|
|
20
|
+
page_size?: number;
|
|
21
|
+
offset?: number;
|
|
22
|
+
}
|
|
23
|
+
export interface NativeInsert {
|
|
24
|
+
columns?: { title?: string | null; icon?: string | null; cover?: string | null };
|
|
25
|
+
properties?: Record<string, unknown>;
|
|
26
|
+
body?: { blocks: Array<Record<string, unknown>> };
|
|
27
|
+
}
|
|
28
|
+
export interface NativeUpdate extends NativeInsert { record_key: string; expected_revision: number; }
|
|
29
|
+
export interface NativeDelete { record_key: string; expected_revision: number; }
|
|
30
|
+
export interface NativeMutationReceipt {
|
|
31
|
+
record_key: string;
|
|
32
|
+
id: string;
|
|
33
|
+
database_id: string;
|
|
34
|
+
revision: number;
|
|
35
|
+
schema_revision: number;
|
|
36
|
+
receipt_id: string;
|
|
37
|
+
operation: 'insert' | 'update' | 'delete';
|
|
38
|
+
replayed?: boolean;
|
|
39
|
+
}
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import React, { useCallback, useLayoutEffect, type CSSProperties, type ErrorInfo, type ReactNode } from 'react';
|
|
4
|
+
import { createPortal } from 'react-dom';
|
|
5
|
+
import { NotisProvider } from './provider';
|
|
6
|
+
import type { NotisRuntime } from './runtime';
|
|
7
|
+
|
|
8
|
+
type BoundaryProps = {
|
|
9
|
+
children: ReactNode;
|
|
10
|
+
resetKey: string;
|
|
11
|
+
fallback: (error: Error) => ReactNode;
|
|
12
|
+
onError: (error: Error, info: ErrorInfo) => void;
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
/** A navigation can recover a crashed view without remounting healthy drafts. */
|
|
16
|
+
export class PresentationErrorBoundary extends React.Component<BoundaryProps, { error: Error | null; resetKey: string }> {
|
|
17
|
+
constructor(props: BoundaryProps) { super(props); this.state = { error: null, resetKey: props.resetKey }; }
|
|
18
|
+
static getDerivedStateFromError(error: Error) { return { error }; }
|
|
19
|
+
static getDerivedStateFromProps(props: BoundaryProps, state: { resetKey: string }) {
|
|
20
|
+
return props.resetKey !== state.resetKey ? { error: null, resetKey: props.resetKey } : null;
|
|
21
|
+
}
|
|
22
|
+
componentDidCatch(error: Error, info: ErrorInfo) { this.props.onError(error, info); }
|
|
23
|
+
render() { return this.state.error ? this.props.fallback(this.state.error) : this.props.children; }
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function CommittedPresentation({ mount, onCommit, children }: { mount: HTMLElement; onCommit?: (hasContent: boolean) => void; children: ReactNode }) {
|
|
27
|
+
useLayoutEffect(() => {
|
|
28
|
+
if (!onCommit) return;
|
|
29
|
+
const publish = () => onCommit?.(Boolean(mount.querySelector('[data-notis-app-root]')?.childNodes.length));
|
|
30
|
+
publish();
|
|
31
|
+
// A source may render null while its first query resolves. Observe the
|
|
32
|
+
// committed source DOM, not only commits of the unchanged host component.
|
|
33
|
+
const Observer = mount.ownerDocument.defaultView?.MutationObserver;
|
|
34
|
+
const observer = Observer ? new Observer(publish) : null;
|
|
35
|
+
observer?.observe(mount, { childList: true, subtree: true, characterData: true });
|
|
36
|
+
return () => observer?.disconnect();
|
|
37
|
+
}, [mount, onCommit]);
|
|
38
|
+
return children;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/** Portal and offline verification share this exact React/Shadow presentation.
|
|
42
|
+
* Source is trusted code in the host JS realm; Shadow DOM only isolates CSS.
|
|
43
|
+
* Loading, credentials and action authority belong to the respective host.
|
|
44
|
+
*/
|
|
45
|
+
export function SdkPresentation({ mount, runtime, component: Component, shell: Shell, theme, resetKey, active = true,
|
|
46
|
+
fallback, onError, onCommit, boundsCss, onProtocolError }: {
|
|
47
|
+
mount: HTMLElement | null;
|
|
48
|
+
runtime: NotisRuntime | null;
|
|
49
|
+
component: React.ComponentType | null;
|
|
50
|
+
shell?: React.ComponentType<{ children: ReactNode }>;
|
|
51
|
+
theme: { mode: 'light' | 'dark'; style: CSSProperties };
|
|
52
|
+
resetKey: string;
|
|
53
|
+
fallback: BoundaryProps['fallback'];
|
|
54
|
+
onError: BoundaryProps['onError'];
|
|
55
|
+
onCommit?: (hasContent: boolean) => void;
|
|
56
|
+
boundsCss?: string;
|
|
57
|
+
active?: boolean;
|
|
58
|
+
onProtocolError?: (error: Error) => void;
|
|
59
|
+
}) {
|
|
60
|
+
const shortcutsAvailable = useCallback(() => {
|
|
61
|
+
if (!active || !mount?.isConnected) return false;
|
|
62
|
+
let element: HTMLElement | null = mount;
|
|
63
|
+
while (element) {
|
|
64
|
+
const style = element.ownerDocument.defaultView?.getComputedStyle(element);
|
|
65
|
+
if (element.hidden || element.hasAttribute('inert') || element.getAttribute('aria-hidden') === 'true'
|
|
66
|
+
|| style?.display === 'none' || style?.visibility === 'hidden') return false;
|
|
67
|
+
const root = element.getRootNode();
|
|
68
|
+
element = element.parentElement || ('host' in root ? (root as ShadowRoot).host as HTMLElement : null);
|
|
69
|
+
}
|
|
70
|
+
return true;
|
|
71
|
+
}, [active, mount]);
|
|
72
|
+
return <PresentationErrorBoundary resetKey={resetKey} fallback={fallback} onError={onError}>
|
|
73
|
+
{mount && runtime && Component ? createPortal(<CommittedPresentation mount={mount} onCommit={onCommit}>
|
|
74
|
+
{boundsCss ? <style data-notis-runtime-bounds>{boundsCss}</style> : null}
|
|
75
|
+
<NotisProvider runtime={runtime} onProtocolError={onProtocolError} shortcutsAvailable={shortcutsAvailable}>
|
|
76
|
+
<div data-notis-app-root data-notis-theme={theme.mode} style={theme.style}>
|
|
77
|
+
{Shell ? <Shell><Component /></Shell> : <Component />}
|
|
78
|
+
</div>
|
|
79
|
+
</NotisProvider>
|
|
80
|
+
</CommittedPresentation>, mount) : null}
|
|
81
|
+
</PresentationErrorBoundary>;
|
|
82
|
+
}
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
|
|
3
|
-
import React, { createContext, useContext, type Context, type ReactNode } from 'react';
|
|
3
|
+
import React, { createContext, useContext, useMemo, type Context, type ReactNode } from 'react';
|
|
4
4
|
import type { NotisRuntime } from './runtime';
|
|
5
5
|
import { ShortcutProvider } from './interactions/shortcuts';
|
|
6
|
+
import { guardSpaceRuntime } from './space';
|
|
6
7
|
|
|
7
8
|
const NOTIS_CONTEXT_SYMBOL = Symbol.for('notis.sdk.runtime_context');
|
|
8
9
|
|
|
@@ -24,13 +25,17 @@ const NotisContext = getNotisContext();
|
|
|
24
25
|
* Provides the Notis runtime context to all child components. The portal owns
|
|
25
26
|
* the runtime and injects it into the rendered app tree.
|
|
26
27
|
*/
|
|
27
|
-
export function NotisProvider({ children, runtime
|
|
28
|
+
export function NotisProvider({ children, runtime, onProtocolError, shortcutsAvailable }: {
|
|
29
|
+
children: ReactNode; runtime: NotisRuntime | null; onProtocolError?: (error: Error) => void;
|
|
30
|
+
shortcutsAvailable?: () => boolean;
|
|
31
|
+
}) {
|
|
32
|
+
const scopedRuntime = useMemo(() => guardSpaceRuntime(runtime, onProtocolError), [runtime, onProtocolError]);
|
|
28
33
|
if (runtime === undefined) {
|
|
29
34
|
throw new Error('NotisProvider requires an explicit runtime prop.');
|
|
30
35
|
}
|
|
31
36
|
return (
|
|
32
|
-
<NotisContext.Provider value={
|
|
33
|
-
<ShortcutProvider>{children}</ShortcutProvider>
|
|
37
|
+
<NotisContext.Provider value={scopedRuntime}>
|
|
38
|
+
<ShortcutProvider isAvailable={shortcutsAvailable}>{children}</ShortcutProvider>
|
|
34
39
|
</NotisContext.Provider>
|
|
35
40
|
);
|
|
36
41
|
}
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { SdkQueryError } from './queryErrors';
|
|
2
|
+
|
|
1
3
|
/** In-memory read snapshots. The host owns the lifetime and authorization scope. */
|
|
2
4
|
export interface QuerySnapshot<T = unknown> {
|
|
3
5
|
data: T | undefined;
|
|
@@ -109,7 +111,7 @@ export function createQueryClient(options: {
|
|
|
109
111
|
return () => { value.listeners.delete(listener); prune(); };
|
|
110
112
|
},
|
|
111
113
|
fetch<T>(key: string, read: () => Promise<T>, fetchOptions: QueryFetchOptions = {}) {
|
|
112
|
-
if (disposed) return Promise.reject(new
|
|
114
|
+
if (disposed) return Promise.reject(new SdkQueryError('scopeUnavailable'));
|
|
113
115
|
const value = entry(key);
|
|
114
116
|
if (value.pending) return value.pending as Promise<T>;
|
|
115
117
|
if (!fetchOptions.force && value.snapshot.hasData && value.snapshot.updatedAt > 0
|
|
@@ -121,7 +123,7 @@ export function createQueryClient(options: {
|
|
|
121
123
|
const canCommit = () => epoch === requestEpoch && value.generation === generation && entries.get(key) === value;
|
|
122
124
|
let timeout: ReturnType<typeof setTimeout>;
|
|
123
125
|
const deadline = new Promise<never>((_, reject) => {
|
|
124
|
-
timeout = setTimeout(() => reject(new
|
|
126
|
+
timeout = setTimeout(() => reject(new SdkQueryError('timedOut')), options.readTimeoutMs ?? 30_000);
|
|
125
127
|
// Node-based verification must not stay alive for a retired browser read.
|
|
126
128
|
if (typeof timeout === 'object' && 'unref' in timeout) timeout.unref();
|
|
127
129
|
});
|
|
@@ -140,7 +142,7 @@ export function createQueryClient(options: {
|
|
|
140
142
|
prefetch<T>(key: string, read: () => Promise<T>, fetchOptions?: QueryFetchOptions) {
|
|
141
143
|
const requestEpoch = epoch;
|
|
142
144
|
return schedule(() => {
|
|
143
|
-
if (epoch !== requestEpoch) throw new
|
|
145
|
+
if (epoch !== requestEpoch) throw new SdkQueryError('scopeCleared');
|
|
144
146
|
return client.fetch(key, read, fetchOptions);
|
|
145
147
|
});
|
|
146
148
|
},
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
/** SDK-owned read failures. The host still owns messages for server/action refusals. */
|
|
2
|
+
const copy = {
|
|
3
|
+
en: {
|
|
4
|
+
bindingUnavailable: 'This Space cannot read this database yet.',
|
|
5
|
+
actionUnavailable: 'This action is unavailable in this Space.',
|
|
6
|
+
readUnavailable: 'These data cannot be read in this Space.',
|
|
7
|
+
schemaChanged: 'This database changed. Refresh the page and try again.',
|
|
8
|
+
responseIncomplete: 'This database could not be loaded. Refresh the page and try again.',
|
|
9
|
+
listUnavailable: 'This list is unavailable in this view.',
|
|
10
|
+
timedOut: 'This read took too long. Please retry.',
|
|
11
|
+
scopeUnavailable: 'App query scope is no longer available.',
|
|
12
|
+
scopeCleared: 'App query scope was cleared.',
|
|
13
|
+
},
|
|
14
|
+
fr: {
|
|
15
|
+
bindingUnavailable: 'Ce Space ne peut pas encore lire cette base de données.',
|
|
16
|
+
actionUnavailable: 'Cette action n’est pas disponible dans ce Space.',
|
|
17
|
+
readUnavailable: 'Ces données ne sont pas accessibles dans ce Space.',
|
|
18
|
+
schemaChanged: 'Cette base de données a changé. Actualise la page et réessaie.',
|
|
19
|
+
responseIncomplete: 'Cette base de données n’a pas pu être chargée. Actualise la page et réessaie.',
|
|
20
|
+
listUnavailable: 'Cette liste n’est pas disponible dans cette vue.',
|
|
21
|
+
timedOut: 'Cette lecture a pris trop de temps. Réessaie.',
|
|
22
|
+
scopeUnavailable: 'Les données de cette vue ne sont plus accessibles.',
|
|
23
|
+
scopeCleared: 'Les données de cette vue ont été réinitialisées.',
|
|
24
|
+
},
|
|
25
|
+
} as const;
|
|
26
|
+
|
|
27
|
+
export type QueryErrorKey = keyof typeof copy.en;
|
|
28
|
+
const PREFIX = 'notis_sdk_query:';
|
|
29
|
+
|
|
30
|
+
/** Code and diagnostic survive localisation; only SDK-owned messages are translated. */
|
|
31
|
+
export class SdkQueryError extends Error {
|
|
32
|
+
readonly code: string;
|
|
33
|
+
readonly diagnostic?: string;
|
|
34
|
+
constructor(key: QueryErrorKey, diagnostic?: string, locale?: string, cause?: Error) {
|
|
35
|
+
super(copy[locale === 'fr' ? 'fr' : 'en'][key], cause ? { cause } : undefined);
|
|
36
|
+
this.name = 'SdkQueryError';
|
|
37
|
+
this.code = PREFIX + key;
|
|
38
|
+
this.diagnostic = diagnostic;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/** Called at render time, so a cached failure changes language without replaying its read. */
|
|
43
|
+
export function localizeQueryError(error: Error | null, locale?: string): Error | null {
|
|
44
|
+
const failure = error as (Error & { code?: unknown; diagnostic?: unknown }) | null;
|
|
45
|
+
if (!failure || typeof failure.code !== 'string' || !failure.code.startsWith(PREFIX)) return error;
|
|
46
|
+
const key = failure.code.slice(PREFIX.length);
|
|
47
|
+
if (!Object.hasOwn(copy.en, key)) return error;
|
|
48
|
+
const message = copy[locale === 'fr' ? 'fr' : 'en'][key as QueryErrorKey];
|
|
49
|
+
if (failure.message === message) return error;
|
|
50
|
+
return new SdkQueryError(key as QueryErrorKey, typeof failure.diagnostic === 'string' ? failure.diagnostic : undefined, locale, failure);
|
|
51
|
+
}
|
package/dist/sdk/src/runtime.ts
CHANGED
|
@@ -8,8 +8,9 @@ import type { AgentContextContent, AgentContextItem, AgentContextSource } from '
|
|
|
8
8
|
* (useTool, useNotis, etc.) which read from the NotisProvider context.
|
|
9
9
|
*/
|
|
10
10
|
|
|
11
|
-
import type { ComponentType } from 'react';
|
|
11
|
+
import type { ComponentType, ReactNode } from 'react';
|
|
12
12
|
import type { NotisQueryClient } from './queryCache';
|
|
13
|
+
import type { SpaceRuntimeDescriptor, SpaceActionOptions, SpaceDocumentBodyRequest, SpaceDocumentBodyResult, SpaceViewerReadOperation, SpaceShownOptions, SpaceCloudComputerTransport } from './space';
|
|
13
14
|
|
|
14
15
|
// ---------------------------------------------------------------------------
|
|
15
16
|
// Database types
|
|
@@ -77,6 +78,8 @@ export type DocumentContentType = 'markdown' | 'file' | 'view';
|
|
|
77
78
|
|
|
78
79
|
export interface DocumentRecord {
|
|
79
80
|
id: string;
|
|
81
|
+
recordKey?: string;
|
|
82
|
+
revision?: number;
|
|
80
83
|
title: string;
|
|
81
84
|
properties: Record<string, unknown>;
|
|
82
85
|
url?: string | null;
|
|
@@ -176,6 +179,14 @@ export interface QueryFilter {
|
|
|
176
179
|
}
|
|
177
180
|
|
|
178
181
|
export interface NotisRuntimeContext {
|
|
182
|
+
/** Host UI language. */
|
|
183
|
+
locale?: 'en' | 'fr';
|
|
184
|
+
/** Untrusted UI selection, separate from the host's fixed record authority. */
|
|
185
|
+
selection?: string | null;
|
|
186
|
+
/** Raw URL query values; `useViewParams()` keeps only the declared, valid ones. */
|
|
187
|
+
params?: Record<string, string> | null;
|
|
188
|
+
/** Trusted anonymous record-Site pin; no credentials or physical native target. */
|
|
189
|
+
siteRecord?: { recordKey: string; param: string; params: Record<string, string | number | boolean> } | null;
|
|
179
190
|
collectionItem?: CollectionItemDetail | null;
|
|
180
191
|
/** App-owned resource requested through the route's `?resource=<id>` link. */
|
|
181
192
|
resourceId?: string | null;
|
|
@@ -230,14 +241,119 @@ export interface ContextSelection extends AgentContextContent {
|
|
|
230
241
|
* file -> matching viewer/editor), so this contract stays stable as new
|
|
231
242
|
* content types ship.
|
|
232
243
|
*/
|
|
233
|
-
export interface
|
|
234
|
-
|
|
244
|
+
export interface NotisRecordProps {
|
|
245
|
+
/** Stable native record key, authorized through this Space's declared databases. */
|
|
246
|
+
recordKey: string;
|
|
247
|
+
className?: string;
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
export type NotisDocumentEditorProps = ({ recordKey: string; documentId?: never } | {
|
|
251
|
+
/** Legacy Apps only. Spaces use recordKey. */
|
|
252
|
+
documentId: string; recordKey?: never;
|
|
253
|
+
}) & {
|
|
235
254
|
/** 'full' renders icon/title/cover above the content; 'body' renders content only. */
|
|
236
255
|
variant?: 'full' | 'body';
|
|
256
|
+
/** Show the database properties above the body (default true). */
|
|
257
|
+
showProperties?: boolean;
|
|
258
|
+
readOnly?: boolean;
|
|
259
|
+
className?: string;
|
|
260
|
+
onDirtyChange?: (dirty: boolean) => void;
|
|
261
|
+
onSavingChange?: (saving: boolean) => void;
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
export interface NotisRecordPropertiesProps extends NotisRecordProps {
|
|
237
265
|
readOnly?: boolean;
|
|
266
|
+
onSavingChange?: (saving: boolean) => void;
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
/** Inline HTML is useful for declared Site read actions; recordKey loads a signed-in record. */
|
|
270
|
+
export type NotisHtmlFrameProps = ({ recordKey: string; html?: never } | { html: string; recordKey?: never }) & {
|
|
271
|
+
title?: string;
|
|
238
272
|
className?: string;
|
|
273
|
+
};
|
|
274
|
+
export interface NotisReportFrameProps extends NotisRecordProps {}
|
|
275
|
+
export interface NotisShareControlProps extends NotisRecordProps {}
|
|
276
|
+
|
|
277
|
+
/** One breadcrumb before the record title; the title is always the last crumb. */
|
|
278
|
+
export interface NotisDocumentPageCrumb {
|
|
279
|
+
label: string;
|
|
280
|
+
onSelect?: () => void;
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
/** An extra entry in the page's '...' menu, after the host's own entries. */
|
|
284
|
+
export interface NotisDocumentPageMenuItem {
|
|
285
|
+
label: string;
|
|
286
|
+
onSelect: () => void;
|
|
287
|
+
destructive?: boolean;
|
|
288
|
+
disabled?: boolean;
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
/**
|
|
292
|
+
* The host-bound parts of one document page. They share one editor session and one save queue,
|
|
293
|
+
* so a Space can arrange them (for example the properties in a right column) without a second editor.
|
|
294
|
+
*/
|
|
295
|
+
export interface NotisDocumentPageParts {
|
|
296
|
+
/** Breadcrumb, Saving or Saved status, actions, Share and the '...' menu. */
|
|
297
|
+
Toolbar: ComponentType;
|
|
298
|
+
/** Cover, icon and title. */
|
|
299
|
+
Header: ComponentType;
|
|
300
|
+
/** Editable or Read only, type, database, created and updated, with the Configure panel toggle. */
|
|
301
|
+
Meta: ComponentType;
|
|
302
|
+
/** The record's properties, saved automatically with the title, icon and cover. */
|
|
303
|
+
Properties: ComponentType;
|
|
304
|
+
/** The collaborative body (or the matching viewer for files, HTML and reports). */
|
|
305
|
+
Body: ComponentType;
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
/**
|
|
309
|
+
* The full page for one record that is the main content of a Space view (a note, a document):
|
|
310
|
+
* the same page Beta's document page showed, inside the Space. Every edit (title, icon, cover,
|
|
311
|
+
* properties, body) goes through the host's single save queue and live collaboration.
|
|
312
|
+
*/
|
|
313
|
+
export interface NotisDocumentPageProps extends NotisRecordProps {
|
|
314
|
+
/** Crumbs before the record title, usually a way back to the list. `false` hides the breadcrumb. */
|
|
315
|
+
breadcrumb?: NotisDocumentPageCrumb[] | false;
|
|
316
|
+
/** Extra toolbar buttons, before Share and the '...' menu (for example the Space's own Trash button). */
|
|
317
|
+
actions?: ReactNode;
|
|
318
|
+
/** Extra '...' menu entries (for example a destructive Delete, as Beta's page had). Plain toolbar buttons on hosts without DocumentPage. */
|
|
319
|
+
menuItems?: NotisDocumentPageMenuItem[];
|
|
320
|
+
/**
|
|
321
|
+
* Offer the record's Site: a row of its Space view's top bar Share panel, or a toolbar Share when the page shows in
|
|
322
|
+
* another Space's view. Off by default: only Spaces whose records were shared on Beta turn it on.
|
|
323
|
+
*/
|
|
324
|
+
share?: boolean;
|
|
325
|
+
/** Which header parts show. `false` hides the whole header (cover, icon and title). */
|
|
326
|
+
header?: false | { cover?: boolean; icon?: boolean; title?: boolean };
|
|
327
|
+
/** Show the meta strip (Editable or Read only, type, database, dates). Default true. */
|
|
328
|
+
meta?: boolean;
|
|
329
|
+
/** 'panel' (default): properties in the Configure panel under the meta strip. 'hidden': none. Or choose and order them by property name. */
|
|
330
|
+
properties?: 'panel' | 'hidden' | { include?: string[]; exclude?: string[]; order?: string[] };
|
|
331
|
+
/** Rendered between the properties and the body. */
|
|
332
|
+
aboveBody?: ReactNode;
|
|
333
|
+
/** Rendered under the body. */
|
|
334
|
+
belowBody?: ReactNode;
|
|
335
|
+
/** Column width: standard (900px, as Beta), wide (1200px) or full. */
|
|
336
|
+
width?: 'standard' | 'wide' | 'full';
|
|
337
|
+
/**
|
|
338
|
+
* 'html': the record's one stored `.html` file is the page. It opens full height under the toolbar,
|
|
339
|
+
* like an HTML record, without the header, meta, properties or body editor.
|
|
340
|
+
*/
|
|
341
|
+
viewer?: 'html';
|
|
342
|
+
readOnly?: boolean;
|
|
239
343
|
onDirtyChange?: (dirty: boolean) => void;
|
|
240
344
|
onSavingChange?: (saving: boolean) => void;
|
|
345
|
+
/**
|
|
346
|
+
* Called after a title, icon, cover, property or body save is confirmed, so the Space can refresh its lists.
|
|
347
|
+
* On hosts without DocumentPage it is called when a save finishes, with `title` null.
|
|
348
|
+
*/
|
|
349
|
+
onSaved?: (record: { recordKey: string; title: string | null }) => void;
|
|
350
|
+
/**
|
|
351
|
+
* Rearrange the page. Receives the host-bound parts; the default layout is Toolbar, Header, Meta (with
|
|
352
|
+
* Properties in its panel), Body. With a layout, Meta shows only its strip and Properties shows wherever
|
|
353
|
+
* the layout places it (once), so the record's properties are never edited from two places.
|
|
354
|
+
* aboveBody and belowBody are not drawn: place them in the layout.
|
|
355
|
+
*/
|
|
356
|
+
layout?: (parts: NotisDocumentPageParts) => ReactNode;
|
|
241
357
|
}
|
|
242
358
|
|
|
243
359
|
export interface NotisMarkdownEditorSavePayload {
|
|
@@ -279,7 +395,14 @@ export interface NotisMarkdownEditorProps {
|
|
|
279
395
|
*/
|
|
280
396
|
export interface NotisRuntimeUI {
|
|
281
397
|
DocumentEditor?: ComponentType<NotisDocumentEditorProps>;
|
|
398
|
+
DocumentPage?: ComponentType<NotisDocumentPageProps>;
|
|
282
399
|
MarkdownEditor?: ComponentType<NotisMarkdownEditorProps>;
|
|
400
|
+
RecordProperties?: ComponentType<NotisRecordPropertiesProps>;
|
|
401
|
+
HtmlFrame?: ComponentType<NotisHtmlFrameProps>;
|
|
402
|
+
ReportFrame?: ComponentType<NotisReportFrameProps>;
|
|
403
|
+
ShareControl?: ComponentType<NotisShareControlProps>;
|
|
404
|
+
/** Starts reading a record (and the page's code) before it opens, for example when a list row is hovered. */
|
|
405
|
+
prefetchRecord?: (recordKey: string) => void;
|
|
283
406
|
}
|
|
284
407
|
|
|
285
408
|
// ---------------------------------------------------------------------------
|
|
@@ -305,14 +428,15 @@ export interface HandoverPayload {
|
|
|
305
428
|
/** Optional starter message. Omit it to open a context-only composer. */
|
|
306
429
|
prompt?: string;
|
|
307
430
|
/**
|
|
308
|
-
*
|
|
309
|
-
* rejects
|
|
431
|
+
* Alias of a Skill this Space links (`skills/<alias>/` in its source). The
|
|
432
|
+
* host rejects an alias the Space does not link. Omit to hand over plain work.
|
|
310
433
|
*/
|
|
311
434
|
skill?: string;
|
|
435
|
+
/** Space-only selected row context; never overrides the host's access scope. */
|
|
436
|
+
record?: { binding: string; recordKey: string; readAction: string };
|
|
312
437
|
/**
|
|
313
|
-
*
|
|
314
|
-
*
|
|
315
|
-
* reserved for a host that can genuinely dispatch the run.
|
|
438
|
+
* Submit on an active user gesture when the host supports immediate handover;
|
|
439
|
+
* background calls still open an editable draft.
|
|
316
440
|
*/
|
|
317
441
|
autoSend?: boolean;
|
|
318
442
|
}
|
|
@@ -326,8 +450,8 @@ export interface HandoverResult {
|
|
|
326
450
|
* The user's cloud computer, as far as an app may see it.
|
|
327
451
|
*
|
|
328
452
|
* `exists` is false when the user has never had a sandbox provisioned. A
|
|
329
|
-
* `status`
|
|
330
|
-
* these facts never wakes
|
|
453
|
+
* `status` is the last observed VM state; `freshness` and `observed_at` describe
|
|
454
|
+
* its age. Reading these facts never wakes the VM.
|
|
331
455
|
*/
|
|
332
456
|
export interface CloudComputerSandboxFacts {
|
|
333
457
|
exists: boolean;
|
|
@@ -335,6 +459,8 @@ export interface CloudComputerSandboxFacts {
|
|
|
335
459
|
provider: string | null;
|
|
336
460
|
created_at: string | null;
|
|
337
461
|
updated_at: string | null;
|
|
462
|
+
observed_at?: string | null;
|
|
463
|
+
freshness?: 'fresh' | 'stale' | 'unknown';
|
|
338
464
|
}
|
|
339
465
|
|
|
340
466
|
/**
|
|
@@ -349,6 +475,7 @@ export interface CloudComputerCliAuthFacts {
|
|
|
349
475
|
authenticated: boolean | null;
|
|
350
476
|
account: string | null;
|
|
351
477
|
checked_at: string | null;
|
|
478
|
+
freshness?: 'fresh' | 'stale' | 'unknown';
|
|
352
479
|
reason: string | null;
|
|
353
480
|
}
|
|
354
481
|
|
|
@@ -365,9 +492,11 @@ export interface CloudComputerFacts {
|
|
|
365
492
|
}
|
|
366
493
|
|
|
367
494
|
export interface RuntimeResource {
|
|
368
|
-
kind: 'app' | 'report';
|
|
495
|
+
kind: 'app' | 'report' | 'space';
|
|
369
496
|
/** Host-provided adoption eligibility, never report-authored authority. */
|
|
370
497
|
analytics_eligible?: boolean;
|
|
498
|
+
/** Host-derived report family for adoption analytics, e.g. skill review reports. */
|
|
499
|
+
report_kind?: 'skill_review' | 'custom';
|
|
371
500
|
id: string;
|
|
372
501
|
revision: number;
|
|
373
502
|
name?: string;
|
|
@@ -376,6 +505,26 @@ export interface RuntimeResource {
|
|
|
376
505
|
}
|
|
377
506
|
|
|
378
507
|
export interface NotisRuntime {
|
|
508
|
+
space?: SpaceRuntimeDescriptor;
|
|
509
|
+
callAction?<TResult = unknown>(actionId: string, inputs?: Record<string, unknown>, options?: SpaceActionOptions): Promise<TResult>;
|
|
510
|
+
documentBody?(request: SpaceDocumentBodyRequest): Promise<SpaceDocumentBodyResult>;
|
|
511
|
+
/**
|
|
512
|
+
* Space-only declared viewer read (`viewerReads`): what the signed-in viewer can open,
|
|
513
|
+
* with their own authority. Absent on Sites, anonymous links and older hosts.
|
|
514
|
+
*/
|
|
515
|
+
viewerRead?<TResult = unknown>(operation: SpaceViewerReadOperation, input?: Record<string, unknown>): Promise<TResult>;
|
|
516
|
+
/**
|
|
517
|
+
* Space-only V4 list (`shows`): runs exactly the declared filter with these params as the
|
|
518
|
+
* signed-in Editor. Absent on Sites and older hosts.
|
|
519
|
+
*/
|
|
520
|
+
shown?<TResult = unknown>(name: string, params?: Record<string, unknown>, options?: SpaceShownOptions): Promise<TResult>;
|
|
521
|
+
/** Space-only unsent Manager draft; distinct from legacy App activation. */
|
|
522
|
+
draftHandover?(payload: HandoverPayload): Promise<HandoverResult>;
|
|
523
|
+
/**
|
|
524
|
+
* Space-only cloud computer (`cloudComputer`): the signed-in viewer's own cloud computer after that
|
|
525
|
+
* viewer allows it. Absent on Sites, links, verification harnesses and older hosts.
|
|
526
|
+
*/
|
|
527
|
+
spaceCloudComputer?: SpaceCloudComputerTransport;
|
|
379
528
|
/** Authenticated runtime identity; app below is presentation metadata only. */
|
|
380
529
|
resource?: RuntimeResource;
|
|
381
530
|
/** Optional host-scoped in-memory read cache. Older hosts remain supported. */
|
|
@@ -402,7 +551,8 @@ export interface NotisRuntime {
|
|
|
402
551
|
* The change notification is only a signal — it carries no rows. Consumers
|
|
403
552
|
* react by refetching through the normal tool path, so app scoping,
|
|
404
553
|
* permissions and billing are unchanged. Use the `useDatabaseSubscription`
|
|
405
|
-
* hook rather than calling this directly.
|
|
554
|
+
* hook rather than calling this directly. In a Space, `slug` is the database
|
|
555
|
+
* binding alias and the hook is `useNativeDocuments(alias, { subscribe: true })`.
|
|
406
556
|
*/
|
|
407
557
|
subscribeDatabase?(
|
|
408
558
|
slug: string,
|
|
@@ -435,7 +585,7 @@ export interface NotisRuntime {
|
|
|
435
585
|
*/
|
|
436
586
|
cloudComputerFacts?(options?: { refresh?: boolean }): Promise<CloudComputerFacts>;
|
|
437
587
|
|
|
438
|
-
navigate?: (payload: { kind: string; [key: string]: unknown }) => void
|
|
588
|
+
navigate?: (payload: { kind: string; [key: string]: unknown }) => void | Promise<void>;
|
|
439
589
|
|
|
440
590
|
registerTopBarSearch?: (
|
|
441
591
|
config:
|