@notis_ai/cli 0.2.0-beta.191.1 → 0.2.0-beta.197.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 +485 -86
- package/config/portal-route-roots.json +47 -0
- package/dist/agent-hooks/notis-agent-hook.mjs +32663 -6357
- package/dist/base-skills/notis-apps/SKILL.md +88 -67
- package/dist/base-skills/notis-apps/references/architecture.md +114 -167
- 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 +104 -142
- package/dist/base-skills/notis-apps/references/sdk.md +113 -60
- package/dist/base-skills/notis-apps/references/troubleshooting.md +22 -21
- package/dist/base-skills/notis-apps/references/views.md +142 -0
- package/dist/base-skills/notis-cli/SKILL.md +4 -4
- package/dist/base-skills/notis-cli/references/app-delivery.md +12 -11
- package/dist/base-skills/notis-cli/references/native-databases.md +6 -6
- 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 +59 -69
- package/dist/base-skills/notis-query/references/documents.md +63 -50
- 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 +141 -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/useCloudComputer.ts +35 -4
- package/dist/sdk/src/hooks/useCloudComputerShell.ts +114 -0
- package/dist/sdk/src/hooks/useHandover.ts +5 -4
- 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 +17 -3
- 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 +146 -8
- 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 +458 -68
- package/dist/skill-sync/index.js.map +4 -4
- package/dist/skill-sync-worker.mjs +451 -77
- package/dist/space-harness/host.js +66 -0
- package/dist/view-renderer/dist/host.css +1 -0
- package/dist/view-renderer/dist/host.js +1042 -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 +173 -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 +11 -5
- package/skills/notis-apps/cli.md +270 -163
- package/skills/notis-cli/AGENT_INSTRUCTIONS.md +3 -3
- package/skills/notis-onboarding/BRIEF.md +7 -3
- package/src/command-specs/apps.js +1 -8
- package/src/command-specs/files.js +29 -0
- package/src/command-specs/handover.js +45 -16
- package/src/command-specs/helpers.js +1 -0
- package/src/command-specs/index.js +10 -4
- package/src/command-specs/skills.js +229 -0
- package/src/command-specs/spaces.js +572 -0
- package/src/command-specs/views.js +69 -0
- package/src/runtime/agent-browser.js +30 -7
- 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/confined-space-build.js +74 -0
- package/src/runtime/confined-space-verify.js +49 -0
- package/src/runtime/native-record-files.js +47 -0
- package/src/runtime/retired-sdk-files.js +20 -0
- package/src/runtime/skill-sync/cloud-client.ts +26 -4
- package/src/runtime/skill-sync/index.ts +154 -35
- package/src/runtime/skill-sync/local-scanner.ts +125 -17
- package/src/runtime/skill-sync/native-identity.ts +102 -0
- package/src/runtime/skill-sync/native-plan.ts +59 -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/sync-skills.js +1 -1
- package/src/runtime/transport.js +8 -1
- package/src/runtime/view-render.js +49 -0
- package/src/space-harness-host.ts +103 -0
- package/template/app/globals.css +1 -0
- package/src/command-specs/reports.js +0 -97
|
@@ -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,111 @@ 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
|
+
/** Show the host Share control (a record Site). Off by default: only Spaces whose records were shared on Beta turn it on. */
|
|
321
|
+
share?: boolean;
|
|
322
|
+
/** Which header parts show. `false` hides the whole header (cover, icon and title). */
|
|
323
|
+
header?: false | { cover?: boolean; icon?: boolean; title?: boolean };
|
|
324
|
+
/** Show the meta strip (Editable or Read only, type, database, dates). Default true. */
|
|
325
|
+
meta?: boolean;
|
|
326
|
+
/** 'panel' (default): properties in the Configure panel under the meta strip. 'hidden': none. Or choose and order them by property name. */
|
|
327
|
+
properties?: 'panel' | 'hidden' | { include?: string[]; exclude?: string[]; order?: string[] };
|
|
328
|
+
/** Rendered between the properties and the body. */
|
|
329
|
+
aboveBody?: ReactNode;
|
|
330
|
+
/** Rendered under the body. */
|
|
331
|
+
belowBody?: ReactNode;
|
|
332
|
+
/** Column width: standard (900px, as Beta), wide (1200px) or full. */
|
|
333
|
+
width?: 'standard' | 'wide' | 'full';
|
|
334
|
+
readOnly?: boolean;
|
|
239
335
|
onDirtyChange?: (dirty: boolean) => void;
|
|
240
336
|
onSavingChange?: (saving: boolean) => void;
|
|
337
|
+
/**
|
|
338
|
+
* Called after a title, icon, cover, property or body save is confirmed, so the Space can refresh its lists.
|
|
339
|
+
* On hosts without DocumentPage it is called when a save finishes, with `title` null.
|
|
340
|
+
*/
|
|
341
|
+
onSaved?: (record: { recordKey: string; title: string | null }) => void;
|
|
342
|
+
/**
|
|
343
|
+
* Rearrange the page. Receives the host-bound parts; the default layout is Toolbar, Header, Meta (with
|
|
344
|
+
* Properties in its panel), Body. With a layout, Meta shows only its strip and Properties shows wherever
|
|
345
|
+
* the layout places it (once), so the record's properties are never edited from two places.
|
|
346
|
+
* aboveBody and belowBody are not drawn: place them in the layout.
|
|
347
|
+
*/
|
|
348
|
+
layout?: (parts: NotisDocumentPageParts) => ReactNode;
|
|
241
349
|
}
|
|
242
350
|
|
|
243
351
|
export interface NotisMarkdownEditorSavePayload {
|
|
@@ -279,7 +387,14 @@ export interface NotisMarkdownEditorProps {
|
|
|
279
387
|
*/
|
|
280
388
|
export interface NotisRuntimeUI {
|
|
281
389
|
DocumentEditor?: ComponentType<NotisDocumentEditorProps>;
|
|
390
|
+
DocumentPage?: ComponentType<NotisDocumentPageProps>;
|
|
282
391
|
MarkdownEditor?: ComponentType<NotisMarkdownEditorProps>;
|
|
392
|
+
RecordProperties?: ComponentType<NotisRecordPropertiesProps>;
|
|
393
|
+
HtmlFrame?: ComponentType<NotisHtmlFrameProps>;
|
|
394
|
+
ReportFrame?: ComponentType<NotisReportFrameProps>;
|
|
395
|
+
ShareControl?: ComponentType<NotisShareControlProps>;
|
|
396
|
+
/** Starts reading a record (and the page's code) before it opens, for example when a list row is hovered. */
|
|
397
|
+
prefetchRecord?: (recordKey: string) => void;
|
|
283
398
|
}
|
|
284
399
|
|
|
285
400
|
// ---------------------------------------------------------------------------
|
|
@@ -305,10 +420,12 @@ export interface HandoverPayload {
|
|
|
305
420
|
/** Optional starter message. Omit it to open a context-only composer. */
|
|
306
421
|
prompt?: string;
|
|
307
422
|
/**
|
|
308
|
-
*
|
|
309
|
-
* rejects
|
|
423
|
+
* Alias of a Skill this Space links (`skills/<alias>/` in its source). The
|
|
424
|
+
* host rejects an alias the Space does not link. Omit to hand over plain work.
|
|
310
425
|
*/
|
|
311
426
|
skill?: string;
|
|
427
|
+
/** Space-only selected row context; never overrides the host's access scope. */
|
|
428
|
+
record?: { binding: string; recordKey: string; readAction: string };
|
|
312
429
|
/**
|
|
313
430
|
* Submit on an active user gesture when the host supports immediate handover;
|
|
314
431
|
* background calls still open an editable draft.
|
|
@@ -364,7 +481,7 @@ export interface CloudComputerFacts {
|
|
|
364
481
|
}
|
|
365
482
|
|
|
366
483
|
export interface RuntimeResource {
|
|
367
|
-
kind: 'app' | 'report';
|
|
484
|
+
kind: 'app' | 'report' | 'space';
|
|
368
485
|
/** Host-provided adoption eligibility, never report-authored authority. */
|
|
369
486
|
analytics_eligible?: boolean;
|
|
370
487
|
/** Host-derived report family for adoption analytics, e.g. skill review reports. */
|
|
@@ -377,6 +494,26 @@ export interface RuntimeResource {
|
|
|
377
494
|
}
|
|
378
495
|
|
|
379
496
|
export interface NotisRuntime {
|
|
497
|
+
space?: SpaceRuntimeDescriptor;
|
|
498
|
+
callAction?<TResult = unknown>(actionId: string, inputs?: Record<string, unknown>, options?: SpaceActionOptions): Promise<TResult>;
|
|
499
|
+
documentBody?(request: SpaceDocumentBodyRequest): Promise<SpaceDocumentBodyResult>;
|
|
500
|
+
/**
|
|
501
|
+
* Space-only declared viewer read (`viewerReads`): what the signed-in viewer can open,
|
|
502
|
+
* with their own authority. Absent on Sites, anonymous links and older hosts.
|
|
503
|
+
*/
|
|
504
|
+
viewerRead?<TResult = unknown>(operation: SpaceViewerReadOperation, input?: Record<string, unknown>): Promise<TResult>;
|
|
505
|
+
/**
|
|
506
|
+
* Space-only V4 list (`shows`): runs exactly the declared filter with these params as the
|
|
507
|
+
* signed-in Editor. Absent on Sites and older hosts.
|
|
508
|
+
*/
|
|
509
|
+
shown?<TResult = unknown>(name: string, params?: Record<string, unknown>, options?: SpaceShownOptions): Promise<TResult>;
|
|
510
|
+
/** Space-only unsent Manager draft; distinct from legacy App activation. */
|
|
511
|
+
draftHandover?(payload: HandoverPayload): Promise<HandoverResult>;
|
|
512
|
+
/**
|
|
513
|
+
* Space-only cloud computer (`cloudComputer`): the signed-in viewer's own cloud computer after that
|
|
514
|
+
* viewer allows it. Absent on Sites, links, verification harnesses and older hosts.
|
|
515
|
+
*/
|
|
516
|
+
spaceCloudComputer?: SpaceCloudComputerTransport;
|
|
380
517
|
/** Authenticated runtime identity; app below is presentation metadata only. */
|
|
381
518
|
resource?: RuntimeResource;
|
|
382
519
|
/** Optional host-scoped in-memory read cache. Older hosts remain supported. */
|
|
@@ -403,7 +540,8 @@ export interface NotisRuntime {
|
|
|
403
540
|
* The change notification is only a signal — it carries no rows. Consumers
|
|
404
541
|
* react by refetching through the normal tool path, so app scoping,
|
|
405
542
|
* permissions and billing are unchanged. Use the `useDatabaseSubscription`
|
|
406
|
-
* hook rather than calling this directly.
|
|
543
|
+
* hook rather than calling this directly. In a Space, `slug` is the database
|
|
544
|
+
* binding alias and the hook is `useNativeDocuments(alias, { subscribe: true })`.
|
|
407
545
|
*/
|
|
408
546
|
subscribeDatabase?(
|
|
409
547
|
slug: string,
|
|
@@ -436,7 +574,7 @@ export interface NotisRuntime {
|
|
|
436
574
|
*/
|
|
437
575
|
cloudComputerFacts?(options?: { refresh?: boolean }): Promise<CloudComputerFacts>;
|
|
438
576
|
|
|
439
|
-
navigate?: (payload: { kind: string; [key: string]: unknown }) => void
|
|
577
|
+
navigate?: (payload: { kind: string; [key: string]: unknown }) => void | Promise<void>;
|
|
440
578
|
|
|
441
579
|
registerTopBarSearch?: (
|
|
442
580
|
config:
|