@notis_ai/cli 0.2.0-beta.102.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 +334 -0
- package/bin/notis.js +2 -0
- package/config/notis_app_boundary_rules.json +50 -0
- package/dist/scaffolds/notis-database/CHANGELOG.md +5 -0
- package/dist/scaffolds/notis-database/app/globals.css +44 -0
- package/dist/scaffolds/notis-database/app/layout.tsx +6 -0
- package/dist/scaffolds/notis-database/app/page.tsx +1091 -0
- package/dist/scaffolds/notis-database/components/ui/badge.tsx +28 -0
- package/dist/scaffolds/notis-database/components/ui/button.tsx +53 -0
- package/dist/scaffolds/notis-database/components/ui/card.tsx +56 -0
- package/dist/scaffolds/notis-database/components/ui/table.tsx +120 -0
- package/dist/scaffolds/notis-database/components.json +20 -0
- package/dist/scaffolds/notis-database/index.html +12 -0
- package/dist/scaffolds/notis-database/lib/types.ts +134 -0
- package/dist/scaffolds/notis-database/lib/utils.ts +6 -0
- package/dist/scaffolds/notis-database/metadata/screenshot-1.png +0 -0
- package/dist/scaffolds/notis-database/notis.config.ts +26 -0
- package/dist/scaffolds/notis-database/package.json +31 -0
- package/dist/scaffolds/notis-database/postcss.config.mjs +8 -0
- package/dist/scaffolds/notis-database/src/dev-main.tsx +23 -0
- package/dist/scaffolds/notis-database/src/mock-runtime.ts +558 -0
- package/dist/scaffolds/notis-database/tailwind.config.ts +59 -0
- package/dist/scaffolds/notis-database/tsconfig.json +23 -0
- package/dist/scaffolds/notis-database/vite.config.ts +22 -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/globals.css +3 -0
- package/dist/scaffolds/notis-notes/app/layout.tsx +6 -0
- package/dist/scaffolds/notis-notes/app/page.tsx +1950 -0
- package/dist/scaffolds/notis-notes/components/ui/badge.tsx +28 -0
- package/dist/scaffolds/notis-notes/components/ui/button.tsx +53 -0
- package/dist/scaffolds/notis-notes/components/ui/card.tsx +56 -0
- package/dist/scaffolds/notis-notes/components.json +20 -0
- package/dist/scaffolds/notis-notes/lib/utils.ts +6 -0
- package/dist/scaffolds/notis-notes/metadata/screenshot-1.png +0 -0
- package/dist/scaffolds/notis-notes/notis.config.ts +35 -0
- package/dist/scaffolds/notis-notes/package.json +31 -0
- package/dist/scaffolds/notis-notes/postcss.config.mjs +8 -0
- package/dist/scaffolds/notis-notes/tailwind.config.ts +58 -0
- package/dist/scaffolds/notis-notes/tsconfig.json +23 -0
- package/dist/scaffolds/notis-notes/vite.config.ts +10 -0
- package/dist/scaffolds/notis-random/CHANGELOG.md +5 -0
- package/dist/scaffolds/notis-random/README.md +33 -0
- package/dist/scaffolds/notis-random/app/globals.css +11 -0
- package/dist/scaffolds/notis-random/app/history/page.tsx +66 -0
- package/dist/scaffolds/notis-random/app/layout.tsx +7 -0
- package/dist/scaffolds/notis-random/app/page.tsx +222 -0
- package/dist/scaffolds/notis-random/index.html +12 -0
- package/dist/scaffolds/notis-random/lib/notis-tools.ts +109 -0
- package/dist/scaffolds/notis-random/lib/rng.ts +42 -0
- package/dist/scaffolds/notis-random/lib/roll-record.ts +102 -0
- package/dist/scaffolds/notis-random/lib/utils.ts +25 -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 +42 -0
- package/dist/scaffolds/notis-random/package.json +32 -0
- package/dist/scaffolds/notis-random/postcss.config.mjs +6 -0
- package/dist/scaffolds/notis-random/src/dev-main.tsx +70 -0
- package/dist/scaffolds/notis-random/src/mock-runtime.ts +129 -0
- package/dist/scaffolds/notis-random/tailwind.config.ts +43 -0
- package/dist/scaffolds/notis-random/tsconfig.json +23 -0
- package/dist/scaffolds/notis-random/vite.config.ts +11 -0
- package/dist/scaffolds.json +47 -0
- package/package.json +45 -0
- package/skills/notis-apps/SKILL.md +219 -0
- package/skills/notis-apps/cli.md +244 -0
- package/skills/notis-cli/SKILL.md +269 -0
- package/skills/notis-query/cli.md +39 -0
- package/src/cli.js +175 -0
- package/src/command-specs/apps.js +1939 -0
- package/src/command-specs/helpers.js +186 -0
- package/src/command-specs/index.js +14 -0
- package/src/command-specs/meta.js +161 -0
- package/src/command-specs/tools.js +499 -0
- package/src/runtime/agent-browser.js +464 -0
- package/src/runtime/app-boundary-validator.js +183 -0
- package/src/runtime/app-changelog.js +79 -0
- package/src/runtime/app-dev-server.js +712 -0
- package/src/runtime/app-dev-sessions.js +251 -0
- package/src/runtime/app-platform.js +1500 -0
- package/src/runtime/assets/store-screenshot-dark.png +0 -0
- package/src/runtime/cli-mode.generated.js +4 -0
- package/src/runtime/cli-mode.js +29 -0
- package/src/runtime/desktop-auth.js +93 -0
- package/src/runtime/errors.js +55 -0
- package/src/runtime/help.js +60 -0
- package/src/runtime/output.js +180 -0
- package/src/runtime/ports.js +15 -0
- package/src/runtime/profiles.js +261 -0
- package/src/runtime/store-screenshot.js +138 -0
- package/src/runtime/transport.js +347 -0
- package/template/.harness/index.html.tmpl +333 -0
- package/template/CHANGELOG.md +5 -0
- package/template/app/globals.css +3 -0
- package/template/app/layout.tsx +6 -0
- package/template/app/page.tsx +60 -0
- package/template/components/ui/badge.tsx +28 -0
- package/template/components/ui/button.tsx +53 -0
- package/template/components/ui/card.tsx +56 -0
- package/template/components.json +20 -0
- package/template/lib/utils.ts +6 -0
- package/template/metadata/screenshot-1.png +0 -0
- package/template/metadata/screenshot-2.png +0 -0
- package/template/metadata/screenshot-3.png +0 -0
- package/template/notis.config.ts +36 -0
- package/template/package-lock.json +4137 -0
- package/template/package.json +32 -0
- package/template/packages/sdk/package.json +36 -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 +278 -0
- package/template/packages/sdk/src/components/MultiSelectCheckbox.tsx +91 -0
- package/template/packages/sdk/src/components/MultiSelectDragOverlay.tsx +39 -0
- package/template/packages/sdk/src/config.ts +145 -0
- package/template/packages/sdk/src/documents.ts +229 -0
- package/template/packages/sdk/src/hooks/useBackend.ts +41 -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 +539 -0
- package/template/packages/sdk/src/hooks/useNotis.ts +34 -0
- package/template/packages/sdk/src/hooks/useNotisNavigation.ts +49 -0
- package/template/packages/sdk/src/hooks/useTool.ts +64 -0
- package/template/packages/sdk/src/hooks/useTools.ts +56 -0
- package/template/packages/sdk/src/hooks/useTopBarSearch.ts +73 -0
- package/template/packages/sdk/src/hooks/useUpsertDocument.ts +95 -0
- package/template/packages/sdk/src/index.ts +83 -0
- package/template/packages/sdk/src/provider.tsx +43 -0
- package/template/packages/sdk/src/runtime.ts +220 -0
- package/template/packages/sdk/src/styles.css +186 -0
- package/template/packages/sdk/src/ui.ts +15 -0
- package/template/packages/sdk/src/vite.ts +56 -0
- package/template/packages/sdk/tsconfig.json +15 -0
- package/template/postcss.config.mjs +8 -0
- package/template/tailwind.config.ts +58 -0
- package/template/tsconfig.json +22 -0
- package/template/vite.config.ts +10 -0
|
@@ -0,0 +1,333 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html>
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="utf-8">
|
|
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>
|
|
11
|
+
<link rel="stylesheet" href="./bundle/app.css">
|
|
12
|
+
<script type="importmap">
|
|
13
|
+
{
|
|
14
|
+
"imports": {
|
|
15
|
+
"react": "https://esm.sh/react@{{REACT_VERSION}}",
|
|
16
|
+
"react-dom": "https://esm.sh/react-dom@{{REACT_VERSION}}",
|
|
17
|
+
"react/jsx-runtime": "https://esm.sh/react@{{REACT_VERSION}}/jsx-runtime",
|
|
18
|
+
"react-dom/client": "https://esm.sh/react-dom@{{REACT_VERSION}}/client"
|
|
19
|
+
}
|
|
20
|
+
}
|
|
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">
|
|
25
|
+
<style>
|
|
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
|
+
}
|
|
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; }
|
|
97
|
+
#root { padding-top: 24px; min-height: 100vh; }
|
|
98
|
+
</style>
|
|
99
|
+
</head>
|
|
100
|
+
<body>
|
|
101
|
+
<div id="harness-status">harness: booting</div>
|
|
102
|
+
<div id="root"></div>
|
|
103
|
+
<script type="module">
|
|
104
|
+
const routeExport = {{ROUTE_EXPORT}};
|
|
105
|
+
const descriptor = {{RUNTIME_DESCRIPTOR}};
|
|
106
|
+
const mode = {{MODE}};
|
|
107
|
+
const apiBase = {{API_BASE}};
|
|
108
|
+
const jwt = {{JWT}};
|
|
109
|
+
const fixtures = {{FIXTURES}};
|
|
110
|
+
const status = document.getElementById('harness-status');
|
|
111
|
+
const setStatus = (msg, color = '#0f0') => {
|
|
112
|
+
status.textContent = `harness: ${msg}`;
|
|
113
|
+
status.style.color = color;
|
|
114
|
+
};
|
|
115
|
+
|
|
116
|
+
window.__harness = {
|
|
117
|
+
errors: [],
|
|
118
|
+
runtimeCalls: [],
|
|
119
|
+
renderStarted: false,
|
|
120
|
+
mounted: false,
|
|
121
|
+
mode,
|
|
122
|
+
route: descriptor.route && descriptor.route.slug,
|
|
123
|
+
scenario: descriptor.context && descriptor.context.screenshotScenario,
|
|
124
|
+
};
|
|
125
|
+
|
|
126
|
+
window.addEventListener('error', (event) => {
|
|
127
|
+
window.__harness.errors.push({
|
|
128
|
+
type: 'window.error',
|
|
129
|
+
message: event.message,
|
|
130
|
+
stack: event.error && event.error.stack,
|
|
131
|
+
});
|
|
132
|
+
});
|
|
133
|
+
window.addEventListener('unhandledrejection', (event) => {
|
|
134
|
+
window.__harness.errors.push({
|
|
135
|
+
type: 'unhandledrejection',
|
|
136
|
+
reason: String((event.reason && event.reason.stack) || event.reason),
|
|
137
|
+
});
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
function record(op, args) {
|
|
141
|
+
window.__harness.runtimeCalls.push({ op, args });
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
async function readJsonOrSnippet(response) {
|
|
145
|
+
const text = await response.text().catch(() => '');
|
|
146
|
+
if (!text) return { payload: null, snippet: null };
|
|
147
|
+
try {
|
|
148
|
+
return { payload: JSON.parse(text), snippet: null };
|
|
149
|
+
} catch {
|
|
150
|
+
const trimmed = text.trim();
|
|
151
|
+
return { payload: null, snippet: trimmed.length > 120 ? `${trimmed.slice(0, 120)}...` : trimmed };
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
async function runtimeQuery(body) {
|
|
156
|
+
if (!apiBase || !jwt || !descriptor.app || !descriptor.app.id) {
|
|
157
|
+
throw new Error('live harness mode requires apiBase, jwt, and app id');
|
|
158
|
+
}
|
|
159
|
+
const response = await fetch(`${String(apiBase).replace(/\/$/, '')}/portal_views/runtime_query`, {
|
|
160
|
+
method: 'POST',
|
|
161
|
+
headers: {
|
|
162
|
+
'Content-Type': 'application/json',
|
|
163
|
+
Authorization: `Bearer ${jwt}`,
|
|
164
|
+
},
|
|
165
|
+
body: JSON.stringify({
|
|
166
|
+
app_id: descriptor.app.id,
|
|
167
|
+
route_slug: descriptor.route && descriptor.route.slug,
|
|
168
|
+
...body,
|
|
169
|
+
}),
|
|
170
|
+
});
|
|
171
|
+
const { payload, snippet } = await readJsonOrSnippet(response);
|
|
172
|
+
if (!response.ok) {
|
|
173
|
+
const message = (payload && (payload.error || payload.message)) || snippet || `status ${response.status}`;
|
|
174
|
+
throw new Error(`Runtime request failed: ${message}`);
|
|
175
|
+
}
|
|
176
|
+
if (!payload) {
|
|
177
|
+
throw new Error(snippet ? `Runtime response was not JSON: ${snippet}` : 'Runtime response was empty');
|
|
178
|
+
}
|
|
179
|
+
if (payload.status === 'error' || payload.error) {
|
|
180
|
+
throw new Error(payload.message || payload.error || 'Runtime request failed');
|
|
181
|
+
}
|
|
182
|
+
return payload;
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
function stubRuntime() {
|
|
186
|
+
return {
|
|
187
|
+
app: descriptor.app,
|
|
188
|
+
route: descriptor.route,
|
|
189
|
+
context: descriptor.context || {},
|
|
190
|
+
navigate: (args) => record('navigate', args),
|
|
191
|
+
registerTopBarSearch: () => {},
|
|
192
|
+
setTopBarSearchValue: () => {},
|
|
193
|
+
setTopBarSearchLoading: () => {},
|
|
194
|
+
listTools: async () => {
|
|
195
|
+
record('listTools', {});
|
|
196
|
+
return descriptor.tools || [];
|
|
197
|
+
},
|
|
198
|
+
callTool: async (name, args) => {
|
|
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
|
+
}
|
|
203
|
+
return { ok: true, result: null };
|
|
204
|
+
},
|
|
205
|
+
request: async (path, options) => {
|
|
206
|
+
record('request', { path, options });
|
|
207
|
+
if (fixtures && fixtures.requests && Object.prototype.hasOwnProperty.call(fixtures.requests, path)) {
|
|
208
|
+
return structuredClone(fixtures.requests[path]);
|
|
209
|
+
}
|
|
210
|
+
return null;
|
|
211
|
+
},
|
|
212
|
+
};
|
|
213
|
+
}
|
|
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
|
+
|
|
242
|
+
function liveRuntime() {
|
|
243
|
+
return {
|
|
244
|
+
...stubRuntime(),
|
|
245
|
+
listTools: async () => {
|
|
246
|
+
record('listTools', {});
|
|
247
|
+
const result = await runtimeQuery({ method: 'tools/list' });
|
|
248
|
+
return result.tools || [];
|
|
249
|
+
},
|
|
250
|
+
callTool: async (name, args) => {
|
|
251
|
+
record('callTool', { name, arguments: args || {} });
|
|
252
|
+
return runtimeQuery({ method: 'tools/call', name, arguments: args || {} });
|
|
253
|
+
},
|
|
254
|
+
request: async (path, options) => {
|
|
255
|
+
record('request', { path, options });
|
|
256
|
+
const response = await fetch(`${String(apiBase).replace(/\/$/, '')}${path}`, {
|
|
257
|
+
method: (options && options.method) || 'GET',
|
|
258
|
+
headers: {
|
|
259
|
+
Authorization: `Bearer ${jwt}`,
|
|
260
|
+
...((options && options.body) ? { 'Content-Type': 'application/json' } : {}),
|
|
261
|
+
...((options && options.headers) || {}),
|
|
262
|
+
},
|
|
263
|
+
body: options && options.body ? JSON.stringify(options.body) : undefined,
|
|
264
|
+
});
|
|
265
|
+
const { payload, snippet } = await readJsonOrSnippet(response);
|
|
266
|
+
if (!response.ok) {
|
|
267
|
+
throw new Error((payload && (payload.error || payload.message)) || snippet || `Request failed with status ${response.status}`);
|
|
268
|
+
}
|
|
269
|
+
return payload;
|
|
270
|
+
},
|
|
271
|
+
};
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
try {
|
|
275
|
+
setStatus('loading react');
|
|
276
|
+
const React = await import('react');
|
|
277
|
+
const { createRoot } = await import('react-dom/client');
|
|
278
|
+
|
|
279
|
+
setStatus('loading bundle');
|
|
280
|
+
const bundle = await import('./bundle/app.js');
|
|
281
|
+
|
|
282
|
+
setStatus('binding runtime context');
|
|
283
|
+
const RuntimeContext = globalThis[Symbol.for('notis.sdk.runtime_context')];
|
|
284
|
+
if (!RuntimeContext) {
|
|
285
|
+
throw new Error('runtime context Symbol not initialized by bundle');
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
const Page = bundle[routeExport];
|
|
289
|
+
if (!Page) {
|
|
290
|
+
throw new Error(`bundle has no ${routeExport} export`);
|
|
291
|
+
}
|
|
292
|
+
const Shell = bundle.__AppShell;
|
|
293
|
+
const runtime = mode === 'live' ? liveRuntime() : stubRuntime();
|
|
294
|
+
const Root = () =>
|
|
295
|
+
React.createElement(
|
|
296
|
+
RuntimeContext.Provider,
|
|
297
|
+
{ value: runtime },
|
|
298
|
+
Shell
|
|
299
|
+
? React.createElement(Shell, null, React.createElement(Page, null))
|
|
300
|
+
: React.createElement(Page, null),
|
|
301
|
+
);
|
|
302
|
+
|
|
303
|
+
setStatus('mounting');
|
|
304
|
+
window.__harness.renderStarted = true;
|
|
305
|
+
createRoot(document.getElementById('root')).render(React.createElement(Root));
|
|
306
|
+
|
|
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
|
+
}
|
|
322
|
+
}));
|
|
323
|
+
} catch (error) {
|
|
324
|
+
window.__harness.errors.push({
|
|
325
|
+
type: 'boot',
|
|
326
|
+
message: error && error.message ? error.message : String(error),
|
|
327
|
+
stack: error && error.stack,
|
|
328
|
+
});
|
|
329
|
+
setStatus(`boot failed: ${error && error.message ? error.message : String(error)}`, '#f55');
|
|
330
|
+
}
|
|
331
|
+
</script>
|
|
332
|
+
</body>
|
|
333
|
+
</html>
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import { getDocumentPreview, useDocuments, useNotis } from '@notis/sdk';
|
|
4
|
+
import { Badge } from '@/components/ui/badge';
|
|
5
|
+
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card';
|
|
6
|
+
|
|
7
|
+
export default function HomePage() {
|
|
8
|
+
const { app, ready } = useNotis();
|
|
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 });
|
|
12
|
+
|
|
13
|
+
return (
|
|
14
|
+
<main className="notis-app-shell space-y-6">
|
|
15
|
+
<Card>
|
|
16
|
+
<CardHeader className="space-y-3">
|
|
17
|
+
<Badge variant="secondary" className="w-fit">Installed app</Badge>
|
|
18
|
+
<div className="space-y-2">
|
|
19
|
+
<CardTitle>{ready ? app?.name : 'Loading...'}</CardTitle>
|
|
20
|
+
<CardDescription>
|
|
21
|
+
{ready ? app?.description : 'Loading app metadata...'}
|
|
22
|
+
</CardDescription>
|
|
23
|
+
</div>
|
|
24
|
+
</CardHeader>
|
|
25
|
+
</Card>
|
|
26
|
+
|
|
27
|
+
<Card>
|
|
28
|
+
<CardHeader>
|
|
29
|
+
<CardTitle>Items</CardTitle>
|
|
30
|
+
<CardDescription>Use shadcn surfaces and portal tokens so the app feels native inside Notis.</CardDescription>
|
|
31
|
+
</CardHeader>
|
|
32
|
+
<CardContent>
|
|
33
|
+
{loading ? (
|
|
34
|
+
<p className="text-sm text-muted-foreground">Loading...</p>
|
|
35
|
+
) : documents.length === 0 ? (
|
|
36
|
+
<div className="rounded-xl border border-dashed border-border px-4 py-10 text-center text-sm text-muted-foreground">
|
|
37
|
+
No items yet. Deploy the app and create some.
|
|
38
|
+
</div>
|
|
39
|
+
) : (
|
|
40
|
+
<div className="space-y-3">
|
|
41
|
+
{documents.map((doc) => (
|
|
42
|
+
<div key={doc.id} className="rounded-xl border border-border bg-background px-4 py-3">
|
|
43
|
+
<div className="flex items-center justify-between gap-3">
|
|
44
|
+
<p className="font-medium">{doc.title || 'Untitled'}</p>
|
|
45
|
+
{typeof doc.properties.status === 'string' ? (
|
|
46
|
+
<Badge variant="outline">{doc.properties.status}</Badge>
|
|
47
|
+
) : null}
|
|
48
|
+
</div>
|
|
49
|
+
<p className="mt-1 line-clamp-1 text-xs text-muted-foreground">
|
|
50
|
+
{getDocumentPreview(doc)}
|
|
51
|
+
</p>
|
|
52
|
+
</div>
|
|
53
|
+
))}
|
|
54
|
+
</div>
|
|
55
|
+
)}
|
|
56
|
+
</CardContent>
|
|
57
|
+
</Card>
|
|
58
|
+
</main>
|
|
59
|
+
);
|
|
60
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
import { cva, type VariantProps } from 'class-variance-authority';
|
|
3
|
+
|
|
4
|
+
import { cn } from '@/lib/utils';
|
|
5
|
+
|
|
6
|
+
const badgeVariants = cva(
|
|
7
|
+
'inline-flex items-center rounded-full border px-2.5 py-0.5 text-xs font-medium transition-colors focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2',
|
|
8
|
+
{
|
|
9
|
+
variants: {
|
|
10
|
+
variant: {
|
|
11
|
+
default: 'border-transparent bg-primary/10 text-primary',
|
|
12
|
+
secondary: 'border-transparent bg-secondary text-secondary-foreground',
|
|
13
|
+
outline: 'text-foreground',
|
|
14
|
+
},
|
|
15
|
+
},
|
|
16
|
+
defaultVariants: {
|
|
17
|
+
variant: 'default',
|
|
18
|
+
},
|
|
19
|
+
},
|
|
20
|
+
);
|
|
21
|
+
|
|
22
|
+
export interface BadgeProps extends React.HTMLAttributes<HTMLDivElement>, VariantProps<typeof badgeVariants> {}
|
|
23
|
+
|
|
24
|
+
function Badge({ className, variant, ...props }: BadgeProps) {
|
|
25
|
+
return <div className={cn(badgeVariants({ variant }), className)} {...props} />;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export { Badge, badgeVariants };
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
import { Slot } from '@radix-ui/react-slot';
|
|
3
|
+
import { cva, type VariantProps } from 'class-variance-authority';
|
|
4
|
+
|
|
5
|
+
import { cn } from '@/lib/utils';
|
|
6
|
+
|
|
7
|
+
const buttonVariants = cva(
|
|
8
|
+
'inline-flex items-center justify-center whitespace-nowrap rounded-md text-sm font-medium ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50',
|
|
9
|
+
{
|
|
10
|
+
variants: {
|
|
11
|
+
variant: {
|
|
12
|
+
default: 'bg-primary text-primary-foreground hover:bg-primary/90',
|
|
13
|
+
destructive: 'bg-destructive text-destructive-foreground hover:bg-destructive/90',
|
|
14
|
+
outline: 'border border-input bg-background hover:bg-accent hover:text-accent-foreground',
|
|
15
|
+
secondary: 'bg-secondary text-secondary-foreground hover:bg-secondary/80',
|
|
16
|
+
ghost: 'hover:bg-accent hover:text-accent-foreground',
|
|
17
|
+
link: 'text-primary underline-offset-4 hover:underline',
|
|
18
|
+
},
|
|
19
|
+
size: {
|
|
20
|
+
default: 'h-10 px-4 py-2',
|
|
21
|
+
sm: 'h-9 rounded-md px-3',
|
|
22
|
+
lg: 'h-11 rounded-md px-8',
|
|
23
|
+
icon: 'h-10 w-10',
|
|
24
|
+
},
|
|
25
|
+
},
|
|
26
|
+
defaultVariants: {
|
|
27
|
+
variant: 'default',
|
|
28
|
+
size: 'default',
|
|
29
|
+
},
|
|
30
|
+
},
|
|
31
|
+
);
|
|
32
|
+
|
|
33
|
+
export interface ButtonProps
|
|
34
|
+
extends React.ButtonHTMLAttributes<HTMLButtonElement>,
|
|
35
|
+
VariantProps<typeof buttonVariants> {
|
|
36
|
+
asChild?: boolean;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(
|
|
40
|
+
({ className, variant, size, asChild = false, ...props }, ref) => {
|
|
41
|
+
const Comp = asChild ? Slot : 'button';
|
|
42
|
+
return (
|
|
43
|
+
<Comp
|
|
44
|
+
className={cn(buttonVariants({ variant, size, className }))}
|
|
45
|
+
ref={ref}
|
|
46
|
+
{...props}
|
|
47
|
+
/>
|
|
48
|
+
);
|
|
49
|
+
},
|
|
50
|
+
);
|
|
51
|
+
Button.displayName = 'Button';
|
|
52
|
+
|
|
53
|
+
export { Button, buttonVariants };
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
|
|
3
|
+
import { cn } from '@/lib/utils';
|
|
4
|
+
|
|
5
|
+
const Card = React.forwardRef<HTMLDivElement, React.HTMLAttributes<HTMLDivElement>>(
|
|
6
|
+
({ className, ...props }, ref) => (
|
|
7
|
+
<div
|
|
8
|
+
ref={ref}
|
|
9
|
+
className={cn('rounded-2xl border bg-card text-card-foreground shadow-sm', className)}
|
|
10
|
+
{...props}
|
|
11
|
+
/>
|
|
12
|
+
),
|
|
13
|
+
);
|
|
14
|
+
Card.displayName = 'Card';
|
|
15
|
+
|
|
16
|
+
const CardHeader = React.forwardRef<HTMLDivElement, React.HTMLAttributes<HTMLDivElement>>(
|
|
17
|
+
({ className, ...props }, ref) => (
|
|
18
|
+
<div ref={ref} className={cn('flex flex-col space-y-1.5 p-6', className)} {...props} />
|
|
19
|
+
),
|
|
20
|
+
);
|
|
21
|
+
CardHeader.displayName = 'CardHeader';
|
|
22
|
+
|
|
23
|
+
const CardTitle = React.forwardRef<HTMLParagraphElement, React.HTMLAttributes<HTMLHeadingElement>>(
|
|
24
|
+
({ className, ...props }, ref) => (
|
|
25
|
+
<h3
|
|
26
|
+
ref={ref}
|
|
27
|
+
className={cn('text-xl font-semibold leading-none tracking-tight', className)}
|
|
28
|
+
{...props}
|
|
29
|
+
/>
|
|
30
|
+
),
|
|
31
|
+
);
|
|
32
|
+
CardTitle.displayName = 'CardTitle';
|
|
33
|
+
|
|
34
|
+
const CardDescription = React.forwardRef<
|
|
35
|
+
HTMLParagraphElement,
|
|
36
|
+
React.HTMLAttributes<HTMLParagraphElement>
|
|
37
|
+
>(({ className, ...props }, ref) => (
|
|
38
|
+
<p ref={ref} className={cn('text-sm text-muted-foreground', className)} {...props} />
|
|
39
|
+
));
|
|
40
|
+
CardDescription.displayName = 'CardDescription';
|
|
41
|
+
|
|
42
|
+
const CardContent = React.forwardRef<HTMLDivElement, React.HTMLAttributes<HTMLDivElement>>(
|
|
43
|
+
({ className, ...props }, ref) => (
|
|
44
|
+
<div ref={ref} className={cn('p-6 pt-0', className)} {...props} />
|
|
45
|
+
),
|
|
46
|
+
);
|
|
47
|
+
CardContent.displayName = 'CardContent';
|
|
48
|
+
|
|
49
|
+
const CardFooter = React.forwardRef<HTMLDivElement, React.HTMLAttributes<HTMLDivElement>>(
|
|
50
|
+
({ className, ...props }, ref) => (
|
|
51
|
+
<div ref={ref} className={cn('flex items-center p-6 pt-0', className)} {...props} />
|
|
52
|
+
),
|
|
53
|
+
);
|
|
54
|
+
CardFooter.displayName = 'CardFooter';
|
|
55
|
+
|
|
56
|
+
export { Card, CardHeader, CardFooter, CardTitle, CardDescription, CardContent };
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://ui.shadcn.com/schema.json",
|
|
3
|
+
"style": "new-york",
|
|
4
|
+
"rsc": false,
|
|
5
|
+
"tsx": true,
|
|
6
|
+
"tailwind": {
|
|
7
|
+
"config": "tailwind.config.ts",
|
|
8
|
+
"css": "app/globals.css",
|
|
9
|
+
"baseColor": "zinc",
|
|
10
|
+
"cssVariables": true,
|
|
11
|
+
"prefix": ""
|
|
12
|
+
},
|
|
13
|
+
"aliases": {
|
|
14
|
+
"components": "@/components",
|
|
15
|
+
"utils": "@/lib/utils",
|
|
16
|
+
"ui": "@/components/ui",
|
|
17
|
+
"lib": "@/lib",
|
|
18
|
+
"hooks": "@/hooks"
|
|
19
|
+
}
|
|
20
|
+
}
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { defineNotisApp } from '@notis/sdk/config';
|
|
2
|
+
|
|
3
|
+
export default defineNotisApp({
|
|
4
|
+
name: 'my-notis-app',
|
|
5
|
+
title: 'My Notis App',
|
|
6
|
+
description: 'A focused Notis app with one route and one database-backed workflow.',
|
|
7
|
+
icon: 'phosphor:squares-four',
|
|
8
|
+
author: { name: 'Notis User' },
|
|
9
|
+
categories: ['Productivity'],
|
|
10
|
+
tagline: 'A clean starting point for a focused workflow.',
|
|
11
|
+
|
|
12
|
+
databases: ['items'],
|
|
13
|
+
|
|
14
|
+
routes: [
|
|
15
|
+
{ path: '/', slug: 'home', name: 'Home', icon: 'phosphor:house', default: true },
|
|
16
|
+
// Example collection tree route:
|
|
17
|
+
// {
|
|
18
|
+
// path: '/notes',
|
|
19
|
+
// slug: 'notes',
|
|
20
|
+
// name: 'Notes',
|
|
21
|
+
// collection: {
|
|
22
|
+
// database: 'notes',
|
|
23
|
+
// titleProperty: 'Name',
|
|
24
|
+
// parentProperty: 'Parent',
|
|
25
|
+
// sidebar: {
|
|
26
|
+
// mode: 'tree',
|
|
27
|
+
// allowCreate: true,
|
|
28
|
+
// },
|
|
29
|
+
// },
|
|
30
|
+
// },
|
|
31
|
+
],
|
|
32
|
+
|
|
33
|
+
tools: [
|
|
34
|
+
'LOCAL_NOTIS_DATABASE_QUERY',
|
|
35
|
+
],
|
|
36
|
+
});
|