@notis_ai/cli 0.2.1 → 0.2.3

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.
Files changed (117) hide show
  1. package/README.md +106 -170
  2. package/config/notis_app_boundary_rules.json +50 -0
  3. package/dist/scaffolds/notis-database/app/globals.css +44 -0
  4. package/dist/scaffolds/notis-database/app/layout.tsx +6 -0
  5. package/dist/scaffolds/notis-database/app/page.tsx +1091 -0
  6. package/dist/scaffolds/notis-database/components/ui/badge.tsx +28 -0
  7. package/dist/scaffolds/notis-database/components/ui/button.tsx +53 -0
  8. package/dist/scaffolds/notis-database/components/ui/card.tsx +56 -0
  9. package/dist/scaffolds/notis-database/components/ui/table.tsx +120 -0
  10. package/dist/scaffolds/notis-database/components.json +20 -0
  11. package/dist/scaffolds/notis-database/index.html +12 -0
  12. package/dist/scaffolds/notis-database/lib/types.ts +134 -0
  13. package/dist/scaffolds/notis-database/lib/utils.ts +6 -0
  14. package/dist/scaffolds/notis-database/metadata/cover.png +0 -0
  15. package/dist/scaffolds/notis-database/metadata/screenshot-1.png +0 -0
  16. package/dist/scaffolds/notis-database/metadata/screenshot-2.png +0 -0
  17. package/dist/scaffolds/notis-database/metadata/screenshot-3.png +0 -0
  18. package/dist/scaffolds/notis-database/notis.config.ts +27 -0
  19. package/dist/scaffolds/notis-database/package.json +31 -0
  20. package/dist/scaffolds/notis-database/postcss.config.mjs +8 -0
  21. package/dist/scaffolds/notis-database/src/dev-main.tsx +23 -0
  22. package/dist/scaffolds/notis-database/src/mock-runtime.ts +557 -0
  23. package/dist/scaffolds/notis-database/tailwind.config.ts +59 -0
  24. package/dist/scaffolds/notis-database/tsconfig.json +23 -0
  25. package/dist/scaffolds/notis-database/vite.config.ts +22 -0
  26. package/dist/scaffolds/notis-notes/app/globals.css +3 -0
  27. package/dist/scaffolds/notis-notes/app/layout.tsx +6 -0
  28. package/dist/scaffolds/notis-notes/app/page.tsx +2168 -0
  29. package/dist/scaffolds/notis-notes/components/ui/badge.tsx +28 -0
  30. package/dist/scaffolds/notis-notes/components/ui/button.tsx +53 -0
  31. package/dist/scaffolds/notis-notes/components/ui/card.tsx +56 -0
  32. package/dist/scaffolds/notis-notes/components.json +20 -0
  33. package/dist/scaffolds/notis-notes/lib/utils.ts +6 -0
  34. package/dist/scaffolds/notis-notes/metadata/cover.png +0 -0
  35. package/dist/scaffolds/notis-notes/metadata/screenshot-1.png +0 -0
  36. package/dist/scaffolds/notis-notes/metadata/screenshot-2.png +0 -0
  37. package/dist/scaffolds/notis-notes/metadata/screenshot-3.png +0 -0
  38. package/dist/scaffolds/notis-notes/notis.config.ts +36 -0
  39. package/dist/scaffolds/notis-notes/package.json +31 -0
  40. package/dist/scaffolds/notis-notes/postcss.config.mjs +8 -0
  41. package/dist/scaffolds/notis-notes/tailwind.config.ts +58 -0
  42. package/dist/scaffolds/notis-notes/tsconfig.json +23 -0
  43. package/dist/scaffolds/notis-notes/vite.config.ts +10 -0
  44. package/dist/scaffolds/notis-random/README.md +33 -0
  45. package/dist/scaffolds/notis-random/app/globals.css +11 -0
  46. package/dist/scaffolds/notis-random/app/history/page.tsx +66 -0
  47. package/dist/scaffolds/notis-random/app/layout.tsx +7 -0
  48. package/dist/scaffolds/notis-random/app/page.tsx +222 -0
  49. package/dist/scaffolds/notis-random/index.html +12 -0
  50. package/dist/scaffolds/notis-random/lib/notis-tools.ts +109 -0
  51. package/dist/scaffolds/notis-random/lib/rng.ts +42 -0
  52. package/dist/scaffolds/notis-random/lib/roll-record.ts +102 -0
  53. package/dist/scaffolds/notis-random/lib/utils.ts +25 -0
  54. package/dist/scaffolds/notis-random/metadata/cover.png +0 -0
  55. package/dist/scaffolds/notis-random/metadata/screenshot-1.png +0 -0
  56. package/dist/scaffolds/notis-random/metadata/screenshot-2.png +0 -0
  57. package/dist/scaffolds/notis-random/metadata/screenshot-3.png +0 -0
  58. package/dist/scaffolds/notis-random/notis.config.ts +43 -0
  59. package/dist/scaffolds/notis-random/package.json +32 -0
  60. package/dist/scaffolds/notis-random/postcss.config.mjs +6 -0
  61. package/dist/scaffolds/notis-random/src/dev-main.tsx +70 -0
  62. package/dist/scaffolds/notis-random/src/mock-runtime.ts +128 -0
  63. package/dist/scaffolds/notis-random/tailwind.config.ts +43 -0
  64. package/dist/scaffolds/notis-random/tsconfig.json +23 -0
  65. package/dist/scaffolds/notis-random/vite.config.ts +11 -0
  66. package/dist/scaffolds.json +36 -0
  67. package/package.json +8 -3
  68. package/skills/notis-apps/SKILL.md +162 -0
  69. package/skills/notis-apps/cli.md +208 -0
  70. package/skills/notis-cli/SKILL.md +260 -0
  71. package/skills/notis-query/cli.md +39 -0
  72. package/src/cli.js +31 -3
  73. package/src/command-specs/apps.js +248 -56
  74. package/src/command-specs/helpers.js +72 -104
  75. package/src/command-specs/index.js +0 -6
  76. package/src/command-specs/meta.js +14 -13
  77. package/src/command-specs/tools.js +196 -115
  78. package/src/runtime/app-boundary-validator.js +65 -14
  79. package/src/runtime/app-dev-server.js +32 -4
  80. package/src/runtime/app-platform.js +404 -24
  81. package/src/runtime/profiles.js +12 -6
  82. package/src/runtime/transport.js +124 -39
  83. package/template/.harness/index.html.tmpl +1 -50
  84. package/template/app/page.tsx +41 -6
  85. package/template/metadata/cover.png +0 -0
  86. package/template/metadata/screenshot-1.png +0 -0
  87. package/template/metadata/screenshot-2.png +0 -0
  88. package/template/metadata/screenshot-3.png +0 -0
  89. package/template/notis.config.ts +10 -6
  90. package/template/package.json +2 -2
  91. package/template/packages/{notis-sdk → sdk}/package.json +6 -0
  92. package/template/packages/{notis-sdk → sdk}/src/components/MultiSelectActionBar.tsx +2 -1
  93. package/template/packages/{notis-sdk → sdk}/src/components/MultiSelectCheckbox.tsx +2 -2
  94. package/template/packages/{notis-sdk → sdk}/src/components/MultiSelectDragOverlay.tsx +2 -2
  95. package/template/packages/{notis-sdk → sdk}/src/config.ts +1 -1
  96. package/template/packages/{notis-sdk → sdk}/src/hooks/useDatabase.ts +1 -13
  97. package/template/packages/{notis-sdk → sdk}/src/hooks/useMultiSelect.ts +4 -3
  98. package/template/packages/{notis-sdk → sdk}/src/hooks/useNotis.ts +4 -3
  99. package/template/packages/{notis-sdk → sdk}/src/hooks/useNotisNavigation.ts +3 -3
  100. package/template/packages/{notis-sdk → sdk}/src/hooks/useTool.ts +22 -7
  101. package/template/packages/{notis-sdk → sdk}/src/hooks/useUpsertDocument.ts +0 -8
  102. package/template/packages/{notis-sdk → sdk}/src/index.ts +2 -15
  103. package/template/packages/{notis-sdk → sdk}/src/provider.tsx +1 -1
  104. package/template/packages/{notis-sdk → sdk}/src/runtime.ts +5 -26
  105. package/src/command-specs/auth.js +0 -178
  106. package/src/command-specs/db.js +0 -163
  107. package/template/packages/notis-sdk/src/helpers.ts +0 -131
  108. package/template/packages/notis-sdk/src/hooks/useAppState.ts +0 -50
  109. package/template/packages/notis-sdk/src/hooks/useCollectionItem.ts +0 -58
  110. package/template/packages/notis-sdk/src/hooks/useDocument.ts +0 -61
  111. /package/template/packages/{notis-sdk → sdk}/src/hooks/useBackend.ts +0 -0
  112. /package/template/packages/{notis-sdk → sdk}/src/hooks/useTools.ts +0 -0
  113. /package/template/packages/{notis-sdk → sdk}/src/hooks/useTopBarSearch.ts +0 -0
  114. /package/template/packages/{notis-sdk → sdk}/src/styles.css +0 -0
  115. /package/template/packages/{notis-sdk → sdk}/src/ui.ts +0 -0
  116. /package/template/packages/{notis-sdk → sdk}/src/vite.ts +0 -0
  117. /package/template/packages/{notis-sdk → sdk}/tsconfig.json +0 -0
@@ -0,0 +1,557 @@
1
+ import type { NotisRuntime } from '@notis/sdk';
2
+ import type {
3
+ DatabaseCatalogRow,
4
+ DatabaseDetail,
5
+ DatabaseDocument,
6
+ DatabaseProperty,
7
+ } from '@/lib/types';
8
+
9
+ type DatabaseSeed = {
10
+ catalog: DatabaseCatalogRow;
11
+ detail: DatabaseDetail;
12
+ };
13
+
14
+ const SCHEMA = (
15
+ title_property_id: string,
16
+ properties: DatabaseProperty[],
17
+ ): DatabaseDetail['schema'] => ({
18
+ schema_version: 1,
19
+ value_encoding_version: 1,
20
+ coercion_version: 1,
21
+ title_property_id,
22
+ properties,
23
+ });
24
+
25
+ function makeSeed(): DatabaseSeed[] {
26
+ const customersDetail: DatabaseDetail = {
27
+ id: 'db_customers',
28
+ slug: 'customers',
29
+ name: 'Customers',
30
+ description:
31
+ 'Every person or company we sell to. The source of truth for billing identity, plan tier, and account ownership. Linked from Deals, Invoices, and Tasks.',
32
+ icon: 'phosphor:users',
33
+ cover: null,
34
+ owner_app_id: 'app_sales_crm',
35
+ owner_app_name: 'Sales CRM',
36
+ content_type: null,
37
+ view_config: null,
38
+ total_documents: 4210,
39
+ successful_documents: 4210,
40
+ created_at: '2025-12-01T00:00:00Z',
41
+ updated_at: '2026-04-20T00:00:00Z',
42
+ schema: SCHEMA('prop_customers_name', [
43
+ {
44
+ id: 'prop_customers_name',
45
+ name: 'name',
46
+ type: 'title',
47
+ description:
48
+ 'Display name shown across the workspace. Used as the canonical title in views and search.',
49
+ order: 0,
50
+ storage_family: 'title',
51
+ },
52
+ {
53
+ id: 'prop_customers_email',
54
+ name: 'email',
55
+ type: 'email',
56
+ description:
57
+ 'Primary contact email. Used to dedupe records when syncing from external CRMs.',
58
+ order: 1,
59
+ storage_family: 'text',
60
+ },
61
+ {
62
+ id: 'prop_customers_plan',
63
+ name: 'plan',
64
+ type: 'select',
65
+ description:
66
+ 'Subscription tier. Drives feature gating and upgrade prompts in the product.',
67
+ order: 2,
68
+ storage_family: 'select',
69
+ options: [
70
+ { id: 'plan_free', name: 'Free', color: 'purple', order: 0 },
71
+ { id: 'plan_pro', name: 'Pro', color: 'blue', order: 1 },
72
+ { id: 'plan_ent', name: 'Enterprise', color: 'yellow', order: 2 },
73
+ ],
74
+ },
75
+ {
76
+ id: 'prop_customers_owner',
77
+ name: 'owner',
78
+ type: 'people',
79
+ description:
80
+ 'Account manager assigned to this customer. Routes follow-ups and reminders.',
81
+ order: 3,
82
+ storage_family: 'people',
83
+ },
84
+ {
85
+ id: 'prop_customers_active_deals',
86
+ name: 'active_deals',
87
+ type: 'relation',
88
+ description:
89
+ 'All deals where this customer is on the buying side, regardless of stage.',
90
+ order: 4,
91
+ storage_family: 'relation',
92
+ relation_target: {
93
+ database_id: 'db_deals',
94
+ database_slug: 'deals',
95
+ database_name: 'Deals',
96
+ },
97
+ },
98
+ ]),
99
+ };
100
+
101
+ const dealsDetail: DatabaseDetail = {
102
+ id: 'db_deals',
103
+ slug: 'deals',
104
+ name: 'Deals',
105
+ description:
106
+ 'Pipeline records for every active and closed opportunity. Drives forecasting and revenue analytics.',
107
+ icon: 'phosphor:currency-dollar',
108
+ cover: null,
109
+ owner_app_id: 'app_sales_crm',
110
+ owner_app_name: 'Sales CRM',
111
+ content_type: null,
112
+ view_config: null,
113
+ total_documents: 1120,
114
+ successful_documents: 1120,
115
+ created_at: '2025-12-01T00:00:00Z',
116
+ updated_at: '2026-04-25T00:00:00Z',
117
+ schema: SCHEMA('prop_deals_name', [
118
+ {
119
+ id: 'prop_deals_name',
120
+ name: 'name',
121
+ type: 'title',
122
+ description: 'Short label for the deal, usually customer + product line.',
123
+ order: 0,
124
+ storage_family: 'title',
125
+ },
126
+ {
127
+ id: 'prop_deals_amount',
128
+ name: 'amount',
129
+ type: 'number',
130
+ description: 'Annualized contract value in USD.',
131
+ order: 1,
132
+ storage_family: 'number',
133
+ format: 'dollar',
134
+ },
135
+ {
136
+ id: 'prop_deals_stage',
137
+ name: 'stage',
138
+ type: 'status',
139
+ description: 'Pipeline stage. Drives forecast probability.',
140
+ order: 2,
141
+ storage_family: 'status',
142
+ options: [
143
+ { id: 'stage_lead', name: 'Lead', color: 'gray', order: 0 },
144
+ { id: 'stage_qualified', name: 'Qualified', color: 'blue', order: 1 },
145
+ { id: 'stage_won', name: 'Won', color: 'green', order: 2 },
146
+ { id: 'stage_lost', name: 'Lost', color: 'red', order: 3 },
147
+ ],
148
+ },
149
+ {
150
+ id: 'prop_deals_customer',
151
+ name: 'customer',
152
+ type: 'relation',
153
+ description: 'Customer the deal belongs to.',
154
+ order: 3,
155
+ storage_family: 'relation',
156
+ relation_target: {
157
+ database_id: 'db_customers',
158
+ database_slug: 'customers',
159
+ database_name: 'Customers',
160
+ },
161
+ },
162
+ ]),
163
+ };
164
+
165
+ const invoicesDetail: DatabaseDetail = {
166
+ id: 'db_invoices',
167
+ slug: 'invoices',
168
+ name: 'Invoices',
169
+ description: 'Issued invoices and their payment status.',
170
+ icon: 'phosphor:file-text',
171
+ cover: null,
172
+ owner_app_id: 'app_sales_crm',
173
+ owner_app_name: 'Sales CRM',
174
+ content_type: null,
175
+ view_config: null,
176
+ total_documents: 890,
177
+ successful_documents: 890,
178
+ created_at: '2025-12-01T00:00:00Z',
179
+ updated_at: '2026-04-25T00:00:00Z',
180
+ schema: SCHEMA('prop_invoices_name', [
181
+ {
182
+ id: 'prop_invoices_name',
183
+ name: 'invoice',
184
+ type: 'title',
185
+ description: 'Invoice number used externally.',
186
+ order: 0,
187
+ storage_family: 'title',
188
+ },
189
+ {
190
+ id: 'prop_invoices_amount',
191
+ name: 'amount',
192
+ type: 'number',
193
+ description: 'Line-item total in USD.',
194
+ order: 1,
195
+ storage_family: 'number',
196
+ format: 'dollar',
197
+ },
198
+ {
199
+ id: 'prop_invoices_due',
200
+ name: 'due_date',
201
+ type: 'date',
202
+ description: 'When payment is expected.',
203
+ order: 2,
204
+ storage_family: 'date',
205
+ },
206
+ ]),
207
+ };
208
+
209
+ const tasksDetail: DatabaseDetail = {
210
+ id: 'db_tasks',
211
+ slug: 'tasks',
212
+ name: 'Tasks',
213
+ description:
214
+ 'Things to do across the workspace. Captures both personal todos and account follow-ups.',
215
+ icon: 'phosphor:check-square',
216
+ cover: null,
217
+ owner_app_id: 'app_tasks_notes',
218
+ owner_app_name: 'Tasks & Notes',
219
+ content_type: null,
220
+ view_config: null,
221
+ total_documents: 785,
222
+ successful_documents: 785,
223
+ created_at: '2025-12-01T00:00:00Z',
224
+ updated_at: '2026-04-29T00:00:00Z',
225
+ schema: SCHEMA('prop_tasks_title', [
226
+ {
227
+ id: 'prop_tasks_title',
228
+ name: 'task',
229
+ type: 'title',
230
+ description: 'Short imperative description of the work.',
231
+ order: 0,
232
+ storage_family: 'title',
233
+ },
234
+ {
235
+ id: 'prop_tasks_status',
236
+ name: 'status',
237
+ type: 'status',
238
+ description: 'Workflow state. Defaults to Not started.',
239
+ order: 1,
240
+ storage_family: 'status',
241
+ options: [
242
+ { id: 'task_not_started', name: 'Not started', color: 'gray', order: 0 },
243
+ { id: 'task_in_progress', name: 'In progress', color: 'blue', order: 1 },
244
+ { id: 'task_done', name: 'Done', color: 'green', order: 2 },
245
+ ],
246
+ },
247
+ {
248
+ id: 'prop_tasks_priority',
249
+ name: 'priority',
250
+ type: 'select',
251
+ description: 'Lightweight triage label.',
252
+ order: 2,
253
+ storage_family: 'select',
254
+ options: [
255
+ { id: 'pri_low', name: 'Low', color: 'gray', order: 0 },
256
+ { id: 'pri_med', name: 'Medium', color: 'yellow', order: 1 },
257
+ { id: 'pri_high', name: 'High', color: 'red', order: 2 },
258
+ ],
259
+ },
260
+ {
261
+ id: 'prop_tasks_due',
262
+ name: 'due_date',
263
+ type: 'date',
264
+ description: 'Optional deadline.',
265
+ order: 3,
266
+ storage_family: 'date',
267
+ },
268
+ {
269
+ id: 'prop_tasks_notes',
270
+ name: 'notes',
271
+ type: 'rich_text',
272
+ description: 'Free-form context for the task.',
273
+ order: 4,
274
+ storage_family: 'text',
275
+ },
276
+ ]),
277
+ };
278
+
279
+ const notesDetail: DatabaseDetail = {
280
+ id: 'db_notes',
281
+ slug: 'notes',
282
+ name: 'Notes',
283
+ description: 'Long-form notes captured from conversations and meetings.',
284
+ icon: 'phosphor:note-pencil',
285
+ cover: null,
286
+ owner_app_id: 'app_tasks_notes',
287
+ owner_app_name: 'Tasks & Notes',
288
+ content_type: null,
289
+ view_config: null,
290
+ total_documents: 2304,
291
+ successful_documents: 2304,
292
+ created_at: '2025-12-01T00:00:00Z',
293
+ updated_at: '2026-04-28T00:00:00Z',
294
+ schema: SCHEMA('prop_notes_title', [
295
+ {
296
+ id: 'prop_notes_title',
297
+ name: 'title',
298
+ type: 'title',
299
+ description: 'Short note title.',
300
+ order: 0,
301
+ storage_family: 'title',
302
+ },
303
+ {
304
+ id: 'prop_notes_tags',
305
+ name: 'tags',
306
+ type: 'multi_select',
307
+ description: 'Lightweight labels.',
308
+ order: 1,
309
+ storage_family: 'multi_select',
310
+ options: [
311
+ { id: 'tag_idea', name: 'idea', color: 'blue', order: 0 },
312
+ { id: 'tag_meeting', name: 'meeting', color: 'green', order: 1 },
313
+ { id: 'tag_followup', name: 'followup', color: 'orange', order: 2 },
314
+ ],
315
+ },
316
+ {
317
+ id: 'prop_notes_content',
318
+ name: 'content',
319
+ type: 'rich_text',
320
+ description: 'The note body.',
321
+ order: 2,
322
+ storage_family: 'text',
323
+ },
324
+ ]),
325
+ };
326
+
327
+ const usersDetail: DatabaseDetail = {
328
+ id: 'db_users',
329
+ slug: 'users',
330
+ name: 'Users',
331
+ description: 'People with access to the Notis workspace.',
332
+ icon: 'phosphor:user',
333
+ cover: null,
334
+ owner_app_id: 'app_identity',
335
+ owner_app_name: 'Identity',
336
+ content_type: null,
337
+ view_config: null,
338
+ total_documents: 64,
339
+ successful_documents: 64,
340
+ created_at: '2025-12-01T00:00:00Z',
341
+ updated_at: '2026-04-15T00:00:00Z',
342
+ schema: SCHEMA('prop_users_name', [
343
+ {
344
+ id: 'prop_users_name',
345
+ name: 'name',
346
+ type: 'title',
347
+ description: 'Full name.',
348
+ order: 0,
349
+ storage_family: 'title',
350
+ },
351
+ {
352
+ id: 'prop_users_email',
353
+ name: 'email',
354
+ type: 'email',
355
+ description: 'Login email.',
356
+ order: 1,
357
+ storage_family: 'text',
358
+ },
359
+ {
360
+ id: 'prop_users_role',
361
+ name: 'role',
362
+ type: 'select',
363
+ description: 'Permission tier.',
364
+ order: 2,
365
+ storage_family: 'select',
366
+ options: [
367
+ { id: 'role_admin', name: 'Admin', color: 'red', order: 0 },
368
+ { id: 'role_member', name: 'Member', color: 'blue', order: 1 },
369
+ { id: 'role_guest', name: 'Guest', color: 'gray', order: 2 },
370
+ ],
371
+ },
372
+ ]),
373
+ };
374
+
375
+ const all: DatabaseDetail[] = [
376
+ customersDetail,
377
+ dealsDetail,
378
+ invoicesDetail,
379
+ tasksDetail,
380
+ notesDetail,
381
+ usersDetail,
382
+ ];
383
+
384
+ return all.map((detail) => ({
385
+ detail,
386
+ catalog: {
387
+ id: detail.id,
388
+ slug: detail.slug,
389
+ name: detail.name,
390
+ description: detail.description,
391
+ icon: detail.icon,
392
+ owner_app_id: detail.owner_app_id,
393
+ owner_app_name: detail.owner_app_name,
394
+ total_documents: detail.total_documents,
395
+ successful_documents: detail.successful_documents,
396
+ updated_at: detail.updated_at,
397
+ },
398
+ }));
399
+ }
400
+
401
+ const SEED = makeSeed();
402
+
403
+ function mockPropertyValue(property: DatabaseProperty, index: number): Record<string, unknown> {
404
+ switch (property.type) {
405
+ case 'title':
406
+ return {
407
+ type: 'title',
408
+ title: [{ type: 'text', text: { content: `${property.name ?? 'Title'} ${index + 1}` } }],
409
+ rich_text: [{ type: 'text', text: { content: `${property.name ?? 'Title'} ${index + 1}` } }],
410
+ };
411
+ case 'rich_text':
412
+ return {
413
+ type: 'rich_text',
414
+ rich_text: [{ type: 'text', text: { content: `Metadata value ${index + 1}` } }],
415
+ };
416
+ case 'number':
417
+ return { type: 'number', number: (index + 1) * 10 };
418
+ case 'select':
419
+ case 'status': {
420
+ const option = property.options?.[index % Math.max(property.options.length, 1)];
421
+ return {
422
+ type: property.type,
423
+ [property.type]: option
424
+ ? { id: option.id, name: option.name, color: option.color }
425
+ : null,
426
+ };
427
+ }
428
+ case 'multi_select': {
429
+ const options = property.options?.slice(0, 2) ?? [];
430
+ return {
431
+ type: 'multi_select',
432
+ multi_select: options.map((option) => ({
433
+ id: option.id,
434
+ name: option.name,
435
+ color: option.color,
436
+ })),
437
+ };
438
+ }
439
+ case 'checkbox':
440
+ return { type: 'checkbox', checkbox: index % 2 === 0 };
441
+ case 'date':
442
+ return { type: 'date', date: { start: `2026-04-${String(20 + index).padStart(2, '0')}`, end: null } };
443
+ case 'relation':
444
+ return { type: 'relation', relation: [{ id: `linked-${index + 1}` }] };
445
+ case 'people':
446
+ return { type: 'people', people: [{ id: `person-${index + 1}` }] };
447
+ case 'files':
448
+ return { type: 'files', files: [{ name: `file-${index + 1}.pdf` }] };
449
+ case 'email':
450
+ return { type: 'email', email: `person${index + 1}@example.com` };
451
+ case 'phone_number':
452
+ return { type: 'phone_number', phone_number: `+1 555 010${index}` };
453
+ case 'url':
454
+ return { type: 'url', url: `https://example.com/${index + 1}` };
455
+ default:
456
+ return { type: property.type, [property.type]: `Value ${index + 1}` };
457
+ }
458
+ }
459
+
460
+ function mockDocuments(detail: DatabaseDetail): DatabaseDocument[] {
461
+ const properties = detail.schema?.properties ?? [];
462
+ return [0, 1, 2].map((index) => {
463
+ const title = `${detail.name ?? 'Database'} record ${index + 1}`;
464
+ return {
465
+ id: `${detail.id}-doc-${index + 1}`,
466
+ title,
467
+ properties: Object.fromEntries(
468
+ properties.map((property) => [
469
+ property.name ?? property.id,
470
+ property.type === 'title'
471
+ ? {
472
+ type: 'title',
473
+ title: [{ type: 'text', text: { content: title } }],
474
+ rich_text: [{ type: 'text', text: { content: title } }],
475
+ }
476
+ : mockPropertyValue(property, index),
477
+ ]),
478
+ ),
479
+ property_states: {},
480
+ url: null,
481
+ created_time: `2026-04-${String(20 + index).padStart(2, '0')}T10:00:00Z`,
482
+ last_edited_time: `2026-04-${String(23 + index).padStart(2, '0')}T15:30:00Z`,
483
+ icon: null,
484
+ cover: null,
485
+ };
486
+ });
487
+ }
488
+
489
+ export function installMockRuntime(): NotisRuntime {
490
+ return {
491
+ app: {
492
+ id: 'dev-notis-database',
493
+ name: 'Notis Database',
494
+ icon: 'phosphor:database',
495
+ description: 'Dev preview — seeded catalog data.',
496
+ },
497
+ route: {
498
+ slug: 'catalog',
499
+ path: '/',
500
+ name: 'Catalog',
501
+ icon: 'phosphor:database',
502
+ parentSlug: null,
503
+ default: true,
504
+ collection: null,
505
+ },
506
+ context: {},
507
+ listTools: async () => [
508
+ { name: 'LOCAL_NOTIS_DATABASE_LIST_DATABASES', inputSchema: { type: 'object', properties: {} } },
509
+ { name: 'LOCAL_NOTIS_DATABASE_GET_DATABASE', inputSchema: { type: 'object', properties: {} } },
510
+ { name: 'LOCAL_NOTIS_DATABASE_QUERY', inputSchema: { type: 'object', properties: {} } },
511
+ ],
512
+ callTool: async <TResult = unknown>(
513
+ name: string,
514
+ args?: Record<string, unknown>,
515
+ ): Promise<TResult> => {
516
+ if (name === 'LOCAL_NOTIS_DATABASE_LIST_DATABASES') {
517
+ return {
518
+ databases: SEED.map((s) => s.catalog),
519
+ total_count: SEED.length,
520
+ } as TResult;
521
+ }
522
+ if (name === 'LOCAL_NOTIS_DATABASE_GET_DATABASE') {
523
+ const request = args ?? {};
524
+ const id = typeof request.database_id === 'string' ? request.database_id : null;
525
+ const slug = typeof request.database_slug === 'string' ? request.database_slug : null;
526
+ const match = SEED.find(
527
+ (s) => (id && s.detail.id === id) || (slug && s.detail.slug === slug),
528
+ );
529
+ if (!match) {
530
+ return { status: 'error', message: 'Database not found' } as TResult;
531
+ }
532
+ return { database: match.detail } as TResult;
533
+ }
534
+ if (name === 'LOCAL_NOTIS_DATABASE_QUERY') {
535
+ const request = args ?? {};
536
+ const slug = typeof request.database_slug === 'string' ? request.database_slug : null;
537
+ const match = SEED.find((s) => slug && s.detail.slug === slug);
538
+ if (!match) {
539
+ return { status: 'error', message: 'Database not found' } as TResult;
540
+ }
541
+ const documents = mockDocuments(match.detail);
542
+ return {
543
+ status: 'success',
544
+ documents,
545
+ results_count: documents.length,
546
+ total_matching: documents.length,
547
+ has_more: false,
548
+ next_offset: null,
549
+ } as TResult;
550
+ }
551
+ throw new Error(`Tool not available in dev preview: ${name}`);
552
+ },
553
+ request: async () => {
554
+ throw new Error('Backend requests unavailable in dev preview.');
555
+ },
556
+ };
557
+ }
@@ -0,0 +1,59 @@
1
+ import type { Config } from 'tailwindcss';
2
+ import tailwindAnimate from 'tailwindcss-animate';
3
+
4
+ const config: Config = {
5
+ darkMode: ['class'],
6
+ content: [
7
+ './app/**/*.{ts,tsx}',
8
+ './components/**/*.{ts,tsx}',
9
+ './lib/**/*.{ts,tsx}',
10
+ './src/**/*.{ts,tsx}',
11
+ ],
12
+ theme: {
13
+ extend: {
14
+ colors: {
15
+ border: 'hsl(var(--border))',
16
+ input: 'hsl(var(--input))',
17
+ ring: 'hsl(var(--ring))',
18
+ background: 'hsl(var(--background))',
19
+ foreground: 'hsl(var(--foreground))',
20
+ primary: {
21
+ DEFAULT: 'hsl(var(--primary))',
22
+ foreground: 'hsl(var(--primary-foreground))',
23
+ },
24
+ secondary: {
25
+ DEFAULT: 'hsl(var(--secondary))',
26
+ foreground: 'hsl(var(--secondary-foreground))',
27
+ },
28
+ destructive: {
29
+ DEFAULT: 'hsl(var(--destructive))',
30
+ foreground: 'hsl(var(--destructive-foreground))',
31
+ },
32
+ muted: {
33
+ DEFAULT: 'hsl(var(--muted))',
34
+ foreground: 'hsl(var(--muted-foreground))',
35
+ },
36
+ accent: {
37
+ DEFAULT: 'hsl(var(--accent))',
38
+ foreground: 'hsl(var(--accent-foreground))',
39
+ },
40
+ popover: {
41
+ DEFAULT: 'hsl(var(--popover))',
42
+ foreground: 'hsl(var(--popover-foreground))',
43
+ },
44
+ card: {
45
+ DEFAULT: 'hsl(var(--card))',
46
+ foreground: 'hsl(var(--card-foreground))',
47
+ },
48
+ },
49
+ borderRadius: {
50
+ lg: 'var(--radius)',
51
+ md: 'calc(var(--radius) - 2px)',
52
+ sm: 'calc(var(--radius) - 4px)',
53
+ },
54
+ },
55
+ },
56
+ plugins: [tailwindAnimate],
57
+ };
58
+
59
+ export default config;
@@ -0,0 +1,23 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ES2017",
4
+ "lib": ["dom", "dom.iterable", "esnext"],
5
+ "allowJs": true,
6
+ "skipLibCheck": true,
7
+ "strict": true,
8
+ "noEmit": true,
9
+ "esModuleInterop": true,
10
+ "module": "esnext",
11
+ "moduleResolution": "bundler",
12
+ "preserveSymlinks": true,
13
+ "resolveJsonModule": true,
14
+ "isolatedModules": true,
15
+ "jsx": "react-jsx",
16
+ "types": ["vite/client", "node"],
17
+ "paths": {
18
+ "@/*": ["./*"]
19
+ }
20
+ },
21
+ "include": ["**/*.ts", "**/*.tsx"],
22
+ "exclude": ["node_modules"]
23
+ }
@@ -0,0 +1,22 @@
1
+ import { notisViteConfig } from '@notis/sdk/vite';
2
+ import react from '@vitejs/plugin-react';
3
+ import { defineConfig, type UserConfig } from 'vite';
4
+ import appConfig from './notis.config';
5
+
6
+ // Two-mode config: dev (`vite`) serves the mock-runtime preview from
7
+ // `index.html` + `src/dev-main.tsx`, while production (`vite build`)
8
+ // emits the Notis portal bundle from `.notis/_entry.tsx`.
9
+ export default defineConfig(({ command }): UserConfig => {
10
+ if (command === 'serve') {
11
+ return {
12
+ plugins: [react()],
13
+ resolve: { alias: { '@': __dirname } },
14
+ };
15
+ }
16
+
17
+ const libConfig = notisViteConfig(appConfig) as unknown as UserConfig;
18
+ return {
19
+ ...libConfig,
20
+ plugins: [react(), ...((libConfig.plugins as UserConfig['plugins']) ?? [])],
21
+ };
22
+ });
@@ -0,0 +1,3 @@
1
+ @tailwind base;
2
+ @tailwind components;
3
+ @tailwind utilities;
@@ -0,0 +1,6 @@
1
+ import '@notis/sdk/styles.css';
2
+ import './globals.css';
3
+
4
+ export default function AppShell({ children }: { children: React.ReactNode }) {
5
+ return children;
6
+ }