@notis_ai/cli 0.2.5 → 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 +31 -9
- package/dist/scaffolds/notis-database/CHANGELOG.md +5 -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 +17 -373
- package/dist/scaffolds/notis-notes/notis.config.ts +0 -1
- package/dist/scaffolds/notis-random/CHANGELOG.md +5 -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 +43 -5
- package/skills/notis-apps/cli.md +22 -6
- package/skills/notis-cli/SKILL.md +20 -2
- package/skills/notis-query/cli.md +1 -1
- package/src/command-specs/apps.js +307 -36
- 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 +204 -21
- package/src/runtime/app-changelog.js +79 -0
- package/src/runtime/app-dev-server.js +21 -4
- package/src/runtime/app-dev-sessions.js +99 -1
- package/src/runtime/app-platform.js +197 -18
- 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/template/packages/sdk/src/hooks/useDatabase.ts +0 -76
package/src/runtime/transport.js
CHANGED
|
@@ -3,11 +3,11 @@ import { createReadStream } from 'node:fs';
|
|
|
3
3
|
import { CliError, EXIT_CODES } from './errors.js';
|
|
4
4
|
import {
|
|
5
5
|
DEFAULT_PROFILE,
|
|
6
|
-
ensureProfile,
|
|
7
6
|
getProfile,
|
|
7
|
+
isJwtExpired,
|
|
8
8
|
loadConfig,
|
|
9
|
-
saveConfig,
|
|
10
9
|
} from './profiles.js';
|
|
10
|
+
import { createExpiredAuthError, createInvalidAuthHints } from './desktop-auth.js';
|
|
11
11
|
|
|
12
12
|
function escapeMultipartHeaderValue(value) {
|
|
13
13
|
return String(value ?? '')
|
|
@@ -76,7 +76,7 @@ function createMultipartFileUpload(payload, bindingMetadata, fileBindings) {
|
|
|
76
76
|
};
|
|
77
77
|
}
|
|
78
78
|
|
|
79
|
-
function normalizeBackendError(status, payload) {
|
|
79
|
+
function normalizeBackendError(status, payload, runtime) {
|
|
80
80
|
const backendError = payload?.error;
|
|
81
81
|
const message =
|
|
82
82
|
backendError?.message ||
|
|
@@ -91,10 +91,7 @@ function normalizeBackendError(status, payload) {
|
|
|
91
91
|
message,
|
|
92
92
|
exitCode: EXIT_CODES.auth,
|
|
93
93
|
hints: [
|
|
94
|
-
|
|
95
|
-
command: 'Open the Notis desktop app and sign in',
|
|
96
|
-
reason: 'Open the Notis desktop app to refresh CLI auth, or set NOTIS_JWT',
|
|
97
|
-
},
|
|
94
|
+
...createInvalidAuthHints(runtime),
|
|
98
95
|
],
|
|
99
96
|
details: payload || {},
|
|
100
97
|
});
|
|
@@ -145,46 +142,10 @@ function normalizeBackendError(status, payload) {
|
|
|
145
142
|
});
|
|
146
143
|
}
|
|
147
144
|
|
|
148
|
-
function
|
|
149
|
-
if (runtime.
|
|
150
|
-
return false;
|
|
151
|
-
}
|
|
152
|
-
|
|
153
|
-
if (force) {
|
|
154
|
-
return true;
|
|
155
|
-
}
|
|
156
|
-
|
|
157
|
-
if (typeof runtime.accessExpiresAt !== 'number' || runtime.accessExpiresAt <= 0) {
|
|
145
|
+
function reloadJwtFromConfig(runtime) {
|
|
146
|
+
if (runtime.credentialSource === 'env') {
|
|
158
147
|
return false;
|
|
159
148
|
}
|
|
160
|
-
|
|
161
|
-
const thresholdSeconds = 60;
|
|
162
|
-
return runtime.accessExpiresAt - Math.floor(Date.now() / 1000) <= thresholdSeconds;
|
|
163
|
-
}
|
|
164
|
-
|
|
165
|
-
function persistDevPortalRuntimeAuth(runtime, session) {
|
|
166
|
-
const config = ensureProfile(loadConfig(), runtime.profileName || DEFAULT_PROFILE);
|
|
167
|
-
const profileName = runtime.profileName || DEFAULT_PROFILE;
|
|
168
|
-
config.current_profile = profileName;
|
|
169
|
-
config.profiles[profileName] = {
|
|
170
|
-
...config.profiles[profileName],
|
|
171
|
-
jwt: session.access_token,
|
|
172
|
-
api_base: runtime.apiBase,
|
|
173
|
-
auth_mode: 'dev_portal',
|
|
174
|
-
refresh_token: session.refresh_token,
|
|
175
|
-
access_expires_at: session.access_expires_at,
|
|
176
|
-
refresh_expires_at: session.refresh_expires_at,
|
|
177
|
-
};
|
|
178
|
-
saveConfig(config);
|
|
179
|
-
|
|
180
|
-
runtime.jwt = session.access_token;
|
|
181
|
-
runtime.authMode = 'dev_portal';
|
|
182
|
-
runtime.refreshToken = session.refresh_token;
|
|
183
|
-
runtime.accessExpiresAt = session.access_expires_at;
|
|
184
|
-
runtime.refreshExpiresAt = session.refresh_expires_at;
|
|
185
|
-
}
|
|
186
|
-
|
|
187
|
-
function reloadJwtFromConfig(runtime) {
|
|
188
149
|
const profileName = runtime.profileName || DEFAULT_PROFILE;
|
|
189
150
|
let profile;
|
|
190
151
|
try {
|
|
@@ -197,39 +158,8 @@ function reloadJwtFromConfig(runtime) {
|
|
|
197
158
|
return false;
|
|
198
159
|
}
|
|
199
160
|
runtime.jwt = nextJwt;
|
|
200
|
-
runtime.
|
|
201
|
-
runtime.
|
|
202
|
-
runtime.accessExpiresAt = profile.access_expires_at;
|
|
203
|
-
runtime.refreshExpiresAt = profile.refresh_expires_at;
|
|
204
|
-
return true;
|
|
205
|
-
}
|
|
206
|
-
|
|
207
|
-
async function maybeRefreshDevPortalAuth(runtime, { force = false } = {}) {
|
|
208
|
-
if (!shouldAttemptDevPortalRefresh(runtime, { force })) {
|
|
209
|
-
return false;
|
|
210
|
-
}
|
|
211
|
-
|
|
212
|
-
const response = await fetch(`${runtime.apiBase}/portal_auth/dev-refresh`, {
|
|
213
|
-
method: 'POST',
|
|
214
|
-
headers: {
|
|
215
|
-
'Content-Type': 'application/json',
|
|
216
|
-
'X-Notis-CLI-Version': runtime.cliVersion,
|
|
217
|
-
},
|
|
218
|
-
body: JSON.stringify({ refresh_token: runtime.refreshToken }),
|
|
219
|
-
});
|
|
220
|
-
|
|
221
|
-
let payload = null;
|
|
222
|
-
try {
|
|
223
|
-
payload = await response.json();
|
|
224
|
-
} catch {
|
|
225
|
-
payload = null;
|
|
226
|
-
}
|
|
227
|
-
|
|
228
|
-
if (!response.ok || !payload?.session?.access_token || !payload?.session?.refresh_token) {
|
|
229
|
-
throw normalizeBackendError(response.status, payload);
|
|
230
|
-
}
|
|
231
|
-
|
|
232
|
-
persistDevPortalRuntimeAuth(runtime, payload.session);
|
|
161
|
+
runtime.desktopAppName = profile.desktop_app_name;
|
|
162
|
+
runtime.desktopPid = profile.desktop_pid;
|
|
233
163
|
return true;
|
|
234
164
|
}
|
|
235
165
|
|
|
@@ -241,7 +171,14 @@ export async function httpRequest({
|
|
|
241
171
|
multipart = false,
|
|
242
172
|
requireAuth = true,
|
|
243
173
|
}) {
|
|
244
|
-
|
|
174
|
+
// The desktop renderer is the single owner of the rotating Supabase refresh
|
|
175
|
+
// token. Each CLI request only consumes the newest access token it synced to
|
|
176
|
+
// disk, avoiding refresh-token races between independent CLI processes and
|
|
177
|
+
// the running desktop session.
|
|
178
|
+
reloadJwtFromConfig(runtime);
|
|
179
|
+
if (requireAuth && isJwtExpired(runtime.jwt)) {
|
|
180
|
+
throw createExpiredAuthError(runtime);
|
|
181
|
+
}
|
|
245
182
|
|
|
246
183
|
let controller = new AbortController();
|
|
247
184
|
let timeout = setTimeout(() => controller.abort(), runtime.timeoutMs);
|
|
@@ -298,8 +235,7 @@ export async function httpRequest({
|
|
|
298
235
|
}
|
|
299
236
|
|
|
300
237
|
if (response.status === 401) {
|
|
301
|
-
const refreshed = (
|
|
302
|
-
|| reloadJwtFromConfig(runtime);
|
|
238
|
+
const refreshed = reloadJwtFromConfig(runtime) && !isJwtExpired(runtime.jwt);
|
|
303
239
|
if (refreshed) {
|
|
304
240
|
if (requireAuth && runtime.jwt) {
|
|
305
241
|
headers.Authorization = `Bearer ${runtime.jwt}`;
|
|
@@ -324,7 +260,10 @@ export async function httpRequest({
|
|
|
324
260
|
clearTimeout(timeout);
|
|
325
261
|
|
|
326
262
|
if (!response.ok) {
|
|
327
|
-
|
|
263
|
+
if (response.status === 401 && isJwtExpired(runtime.jwt)) {
|
|
264
|
+
throw createExpiredAuthError(runtime);
|
|
265
|
+
}
|
|
266
|
+
throw normalizeBackendError(response.status, payload, runtime);
|
|
328
267
|
}
|
|
329
268
|
|
|
330
269
|
return {
|
|
@@ -3,6 +3,11 @@
|
|
|
3
3
|
<head>
|
|
4
4
|
<meta charset="utf-8">
|
|
5
5
|
<title>Notis App Harness</title>
|
|
6
|
+
<script>
|
|
7
|
+
const harnessTheme = new URLSearchParams(window.location.search).get('theme') === 'dark' ? 'dark' : 'light';
|
|
8
|
+
document.documentElement.classList.toggle('dark', harnessTheme === 'dark');
|
|
9
|
+
document.documentElement.dataset.theme = harnessTheme;
|
|
10
|
+
</script>
|
|
6
11
|
<link rel="stylesheet" href="./bundle/app.css">
|
|
7
12
|
<script type="importmap">
|
|
8
13
|
{
|
|
@@ -14,8 +19,80 @@
|
|
|
14
19
|
}
|
|
15
20
|
}
|
|
16
21
|
</script>
|
|
22
|
+
<link rel="preconnect" href="https://fonts.googleapis.com">
|
|
23
|
+
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
|
24
|
+
<link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap">
|
|
17
25
|
<style>
|
|
18
|
-
|
|
26
|
+
/*
|
|
27
|
+
* Portal-parity theme tokens. In production the portal copies its computed
|
|
28
|
+
* :root custom properties onto the app root, so app CSS written against
|
|
29
|
+
* hsl(var(--token)) resolves to the real portal palette. The harness mirrors
|
|
30
|
+
* the portal's light theme here so dev renders and listing screenshots look
|
|
31
|
+
* exactly like the app inside the portal. Keep in sync with
|
|
32
|
+
* portal/src/app/globals.css (:root).
|
|
33
|
+
*/
|
|
34
|
+
:root {
|
|
35
|
+
color-scheme: light;
|
|
36
|
+
--background: 0 0% 100%;
|
|
37
|
+
--foreground: 222.2 84% 4.9%;
|
|
38
|
+
--card: 0 0% 100%;
|
|
39
|
+
--card-foreground: 222.2 84% 4.9%;
|
|
40
|
+
--popover: 0 0% 100%;
|
|
41
|
+
--popover-foreground: 222.2 84% 4.9%;
|
|
42
|
+
--primary: 222.2 47.4% 11.2%;
|
|
43
|
+
--primary-foreground: 210 40% 98%;
|
|
44
|
+
--secondary: 210 40% 96.1%;
|
|
45
|
+
--secondary-foreground: 222.2 47.4% 11.2%;
|
|
46
|
+
--muted: 210 40% 96.1%;
|
|
47
|
+
--muted-foreground: 0 0% 46.9%;
|
|
48
|
+
--accent: 210 40% 96.1%;
|
|
49
|
+
--accent-foreground: 222.2 47.4% 11.2%;
|
|
50
|
+
--destructive: 0 84.2% 60.2%;
|
|
51
|
+
--destructive-foreground: 210 40% 98%;
|
|
52
|
+
--border: 214.3 31.8% 91.4%;
|
|
53
|
+
--input: 214.3 31.8% 91.4%;
|
|
54
|
+
--ring: 222.2 84% 4.9%;
|
|
55
|
+
--radius: 0.5rem;
|
|
56
|
+
--chart-1: 210 100% 49%;
|
|
57
|
+
--chart-2: 173 58% 39%;
|
|
58
|
+
--chart-3: 197 37% 24%;
|
|
59
|
+
--chart-4: 43 74% 66%;
|
|
60
|
+
--chart-5: 27 87% 67%;
|
|
61
|
+
}
|
|
62
|
+
:root.dark {
|
|
63
|
+
color-scheme: dark;
|
|
64
|
+
--background: 0 0% 13%;
|
|
65
|
+
--foreground: 210 40% 98%;
|
|
66
|
+
--card: 0 0% 13%;
|
|
67
|
+
--card-foreground: 210 40% 98%;
|
|
68
|
+
--popover: 0 0% 13%;
|
|
69
|
+
--popover-foreground: 210 40% 98%;
|
|
70
|
+
--primary: 210 40% 98%;
|
|
71
|
+
--primary-foreground: 222.2 47.4% 11.2%;
|
|
72
|
+
--secondary: 0 0% 18%;
|
|
73
|
+
--secondary-foreground: 210 40% 98%;
|
|
74
|
+
--muted: 0 0% 18%;
|
|
75
|
+
--muted-foreground: 0 0% 65.1%;
|
|
76
|
+
--accent: 0 0% 18%;
|
|
77
|
+
--accent-foreground: 210 40% 98%;
|
|
78
|
+
--destructive: 0 84.2% 60.2%;
|
|
79
|
+
--destructive-foreground: 210 40% 98%;
|
|
80
|
+
--border: 0 0% 23.5%;
|
|
81
|
+
--input: 0 0% 23.5%;
|
|
82
|
+
--ring: 212.7 26.8% 83.9%;
|
|
83
|
+
--chart-1: 220 70% 50%;
|
|
84
|
+
--chart-2: 160 60% 45%;
|
|
85
|
+
--chart-3: 30 80% 55%;
|
|
86
|
+
--chart-4: 280 65% 60%;
|
|
87
|
+
--chart-5: 340 75% 55%;
|
|
88
|
+
}
|
|
89
|
+
body {
|
|
90
|
+
margin: 0;
|
|
91
|
+
font-family: 'Inter', system-ui, -apple-system, sans-serif;
|
|
92
|
+
background: hsl(var(--background));
|
|
93
|
+
color: hsl(var(--foreground));
|
|
94
|
+
-webkit-font-smoothing: antialiased;
|
|
95
|
+
}
|
|
19
96
|
#harness-status { position: fixed; top: 0; left: 0; right: 0; padding: 4px 8px; font-size: 11px; background: #111; color: #0f0; font-family: ui-monospace, SFMono-Regular, Menlo, monospace; z-index: 9999; }
|
|
20
97
|
#root { padding-top: 24px; min-height: 100vh; }
|
|
21
98
|
</style>
|
|
@@ -29,6 +106,7 @@
|
|
|
29
106
|
const mode = {{MODE}};
|
|
30
107
|
const apiBase = {{API_BASE}};
|
|
31
108
|
const jwt = {{JWT}};
|
|
109
|
+
const fixtures = {{FIXTURES}};
|
|
32
110
|
const status = document.getElementById('harness-status');
|
|
33
111
|
const setStatus = (msg, color = '#0f0') => {
|
|
34
112
|
status.textContent = `harness: ${msg}`;
|
|
@@ -42,6 +120,7 @@
|
|
|
42
120
|
mounted: false,
|
|
43
121
|
mode,
|
|
44
122
|
route: descriptor.route && descriptor.route.slug,
|
|
123
|
+
scenario: descriptor.context && descriptor.context.screenshotScenario,
|
|
45
124
|
};
|
|
46
125
|
|
|
47
126
|
window.addEventListener('error', (event) => {
|
|
@@ -118,15 +197,48 @@
|
|
|
118
197
|
},
|
|
119
198
|
callTool: async (name, args) => {
|
|
120
199
|
record('callTool', { name, arguments: args || {} });
|
|
200
|
+
if (fixtures && fixtures.tools && Object.prototype.hasOwnProperty.call(fixtures.tools, name)) {
|
|
201
|
+
return structuredClone(fixtures.tools[name]);
|
|
202
|
+
}
|
|
121
203
|
return { ok: true, result: null };
|
|
122
204
|
},
|
|
123
205
|
request: async (path, options) => {
|
|
124
206
|
record('request', { path, options });
|
|
207
|
+
if (fixtures && fixtures.requests && Object.prototype.hasOwnProperty.call(fixtures.requests, path)) {
|
|
208
|
+
return structuredClone(fixtures.requests[path]);
|
|
209
|
+
}
|
|
125
210
|
return null;
|
|
126
211
|
},
|
|
127
212
|
};
|
|
128
213
|
}
|
|
129
214
|
|
|
215
|
+
const delay = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
|
|
216
|
+
|
|
217
|
+
async function applyScenarioActions() {
|
|
218
|
+
const actions = fixtures && fixtures.scenario && Array.isArray(fixtures.scenario.actions)
|
|
219
|
+
? fixtures.scenario.actions
|
|
220
|
+
: [];
|
|
221
|
+
for (const action of actions) {
|
|
222
|
+
if (action && action.type === 'wait') {
|
|
223
|
+
await delay(Math.max(0, Number(action.ms) || 0));
|
|
224
|
+
continue;
|
|
225
|
+
}
|
|
226
|
+
if (action && action.type === 'click' && typeof action.selector === 'string') {
|
|
227
|
+
const deadline = Date.now() + 5000;
|
|
228
|
+
let element = null;
|
|
229
|
+
while (!element && Date.now() < deadline) {
|
|
230
|
+
element = document.querySelector(action.selector);
|
|
231
|
+
if (!element) await delay(50);
|
|
232
|
+
}
|
|
233
|
+
if (!element || typeof element.click !== 'function') {
|
|
234
|
+
throw new Error(`Screenshot scenario could not click ${action.selector}`);
|
|
235
|
+
}
|
|
236
|
+
element.click();
|
|
237
|
+
await delay(Number(action.settle_ms) || 350);
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
|
|
130
242
|
function liveRuntime() {
|
|
131
243
|
return {
|
|
132
244
|
...stubRuntime(),
|
|
@@ -192,11 +304,21 @@
|
|
|
192
304
|
window.__harness.renderStarted = true;
|
|
193
305
|
createRoot(document.getElementById('root')).render(React.createElement(Root));
|
|
194
306
|
|
|
195
|
-
requestAnimationFrame(() => requestAnimationFrame(() => {
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
307
|
+
requestAnimationFrame(() => requestAnimationFrame(async () => {
|
|
308
|
+
try {
|
|
309
|
+
await delay(500);
|
|
310
|
+
await applyScenarioActions();
|
|
311
|
+
window.__harness.mounted = true;
|
|
312
|
+
const errors = window.__harness.errors.length;
|
|
313
|
+
const calls = window.__harness.runtimeCalls.length;
|
|
314
|
+
setStatus(`mounted (errors=${errors}, runtimeCalls=${calls})`, errors ? '#f55' : '#0f0');
|
|
315
|
+
} catch (error) {
|
|
316
|
+
window.__harness.errors.push({
|
|
317
|
+
type: 'scenario',
|
|
318
|
+
message: error && error.message ? error.message : String(error),
|
|
319
|
+
});
|
|
320
|
+
setStatus(`scenario failed: ${error && error.message ? error.message : String(error)}`, '#f55');
|
|
321
|
+
}
|
|
200
322
|
}));
|
|
201
323
|
} catch (error) {
|
|
202
324
|
window.__harness.errors.push({
|
package/template/app/page.tsx
CHANGED
|
@@ -1,47 +1,14 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
|
|
3
|
-
import {
|
|
4
|
-
import { useNotis, useTool } from '@notis/sdk';
|
|
3
|
+
import { getDocumentPreview, useDocuments, useNotis } from '@notis/sdk';
|
|
5
4
|
import { Badge } from '@/components/ui/badge';
|
|
6
5
|
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card';
|
|
7
6
|
|
|
8
|
-
type ItemDocument = {
|
|
9
|
-
id?: string;
|
|
10
|
-
document_id?: string;
|
|
11
|
-
title?: string;
|
|
12
|
-
properties?: Record<string, unknown>;
|
|
13
|
-
};
|
|
14
|
-
|
|
15
|
-
type QueryItemsArgs = {
|
|
16
|
-
database_slug: string;
|
|
17
|
-
query: {
|
|
18
|
-
page_size?: number;
|
|
19
|
-
};
|
|
20
|
-
};
|
|
21
|
-
|
|
22
|
-
type QueryItemsResult = {
|
|
23
|
-
documents?: ItemDocument[];
|
|
24
|
-
};
|
|
25
|
-
|
|
26
7
|
export default function HomePage() {
|
|
27
8
|
const { app, ready } = useNotis();
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
useEffect(() => {
|
|
32
|
-
let cancelled = false;
|
|
33
|
-
queryItems
|
|
34
|
-
.call({ database_slug: 'items', query: { page_size: 25 } })
|
|
35
|
-
.then((result) => {
|
|
36
|
-
if (!cancelled) setDocuments(result.documents || []);
|
|
37
|
-
})
|
|
38
|
-
.catch(() => {
|
|
39
|
-
if (!cancelled) setDocuments([]);
|
|
40
|
-
});
|
|
41
|
-
return () => {
|
|
42
|
-
cancelled = true;
|
|
43
|
-
};
|
|
44
|
-
}, [queryItems.call]);
|
|
9
|
+
// Documents come back normalized: plain property values, camelCase fields,
|
|
10
|
+
// and typed content (contentMarkdown / contentBlocknote / plainText).
|
|
11
|
+
const { documents, loading } = useDocuments('items', { pageSize: 25 });
|
|
45
12
|
|
|
46
13
|
return (
|
|
47
14
|
<main className="notis-app-shell space-y-6">
|
|
@@ -63,7 +30,7 @@ export default function HomePage() {
|
|
|
63
30
|
<CardDescription>Use shadcn surfaces and portal tokens so the app feels native inside Notis.</CardDescription>
|
|
64
31
|
</CardHeader>
|
|
65
32
|
<CardContent>
|
|
66
|
-
{
|
|
33
|
+
{loading ? (
|
|
67
34
|
<p className="text-sm text-muted-foreground">Loading...</p>
|
|
68
35
|
) : documents.length === 0 ? (
|
|
69
36
|
<div className="rounded-xl border border-dashed border-border px-4 py-10 text-center text-sm text-muted-foreground">
|
|
@@ -72,13 +39,16 @@ export default function HomePage() {
|
|
|
72
39
|
) : (
|
|
73
40
|
<div className="space-y-3">
|
|
74
41
|
{documents.map((doc) => (
|
|
75
|
-
<div key={doc.id
|
|
42
|
+
<div key={doc.id} className="rounded-xl border border-border bg-background px-4 py-3">
|
|
76
43
|
<div className="flex items-center justify-between gap-3">
|
|
77
44
|
<p className="font-medium">{doc.title || 'Untitled'}</p>
|
|
78
|
-
{doc.properties
|
|
79
|
-
<Badge variant="outline">{
|
|
45
|
+
{typeof doc.properties.status === 'string' ? (
|
|
46
|
+
<Badge variant="outline">{doc.properties.status}</Badge>
|
|
80
47
|
) : null}
|
|
81
48
|
</div>
|
|
49
|
+
<p className="mt-1 line-clamp-1 text-xs text-muted-foreground">
|
|
50
|
+
{getDocumentPreview(doc)}
|
|
51
|
+
</p>
|
|
82
52
|
</div>
|
|
83
53
|
))}
|
|
84
54
|
</div>
|
package/template/notis.config.ts
CHANGED