deepspace 0.3.9 → 0.3.11

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 (120) hide show
  1. package/dist/cli.js +96 -18
  2. package/dist/cli.js.map +1 -1
  3. package/dist/index.d.ts +160 -1
  4. package/dist/index.js +283 -126
  5. package/dist/index.js.map +1 -1
  6. package/dist/server.d.ts +172 -2
  7. package/dist/server.js +581 -1
  8. package/dist/server.js.map +1 -1
  9. package/dist/worker.d.ts +252 -2
  10. package/dist/worker.js +588 -1
  11. package/dist/worker.js.map +1 -1
  12. package/features/admin-page/feature.json +29 -0
  13. package/features/admin-page/src/AdminPage.tsx +292 -0
  14. package/features/admin-page/src/admin-schema.ts +20 -0
  15. package/features/ai-chat/feature.json +41 -0
  16. package/features/ai-chat/src/AiChatPage.tsx +692 -0
  17. package/features/ai-chat/src/ChatPanel.tsx +1098 -0
  18. package/features/ai-chat/src/ai-chat-schema.ts +11 -0
  19. package/features/canvas/feature.json +45 -0
  20. package/features/canvas/src/CanvasEditorPage.tsx +87 -0
  21. package/features/canvas/src/CanvasListPage.tsx +202 -0
  22. package/features/canvas/src/canvas-schema.ts +33 -0
  23. package/features/canvas/src/components/CanvasToolbar.tsx +170 -0
  24. package/features/canvas/src/components/CanvasView.tsx +369 -0
  25. package/features/canvas/src/components/ShapeRenderer.tsx +114 -0
  26. package/features/cron/feature.json +33 -0
  27. package/features/cron/src/CronLogPage.tsx +100 -0
  28. package/features/cron/src/cron.ts +25 -0
  29. package/features/docs/feature.json +96 -0
  30. package/features/docs/src/DocsEditorPage.tsx +1012 -0
  31. package/features/docs/src/DocsListPage.tsx +951 -0
  32. package/features/docs/src/DocsOutlinePanel.tsx +68 -0
  33. package/features/docs/src/DocsPresence.tsx +138 -0
  34. package/features/docs/src/InviteDialog.tsx +336 -0
  35. package/features/docs/src/LibrarySidebar.tsx +464 -0
  36. package/features/docs/src/docs-favorites.ts +18 -0
  37. package/features/docs/src/docs-library-types.ts +15 -0
  38. package/features/docs/src/docs-schema.ts +80 -0
  39. package/features/docs/src/docs-ui.css +309 -0
  40. package/features/docs/src/editor/DocEditorSurface.tsx +257 -0
  41. package/features/docs/src/editor/DocsTiptapToolbar.tsx +560 -0
  42. package/features/docs/src/editor/useDocEditor.ts +206 -0
  43. package/features/docs/src/use-yjs-room-with-awareness.ts +285 -0
  44. package/features/file-attachments/feature.json +42 -0
  45. package/features/file-attachments/src/AttachmentsPage.tsx +864 -0
  46. package/features/file-attachments/src/attachments-constants.ts +9 -0
  47. package/features/file-attachments/src/attachments-schema.ts +45 -0
  48. package/features/file-attachments/src/useFilePreview.ts +471 -0
  49. package/features/file-attachments/src/useMimeTypeDetection.ts +138 -0
  50. package/features/file-manager/feature.json +24 -0
  51. package/features/file-manager/src/FileManagerPage.tsx +330 -0
  52. package/features/integration-test/feature.json +18 -0
  53. package/features/integration-test/src/IntegrationTestPage.tsx +423 -0
  54. package/features/integration-test/src/integration.spec.ts +42 -0
  55. package/features/items/feature.json +33 -0
  56. package/features/items/src/ItemsPage.tsx +325 -0
  57. package/features/items/src/items-constants.ts +10 -0
  58. package/features/items/src/items-schema.ts +27 -0
  59. package/features/kanban/feature.json +18 -0
  60. package/features/kanban/src/Kanban.tsx +283 -0
  61. package/features/landing/feature.json +62 -0
  62. package/features/landing/src/LandingPage.tsx +627 -0
  63. package/features/landing/src/primitives.tsx +389 -0
  64. package/features/landing/src/sections/FAQSection.tsx +110 -0
  65. package/features/landing/src/sections/FeaturesGridSection.tsx +311 -0
  66. package/features/landing/src/sections/HowItWorksSection.tsx +62 -0
  67. package/features/landing/src/sections/LogoCloudSection.tsx +58 -0
  68. package/features/landing/src/sections/ShowcaseSection.tsx +63 -0
  69. package/features/landing/src/sections/StatsSection.tsx +31 -0
  70. package/features/landing/src/sections/TeamSection.tsx +86 -0
  71. package/features/landing/src/sections/TestimonialsSection.tsx +68 -0
  72. package/features/landing/src/sections/VideoSection.tsx +71 -0
  73. package/features/leaderboard/feature.json +33 -0
  74. package/features/leaderboard/src/LeaderboardPage.tsx +372 -0
  75. package/features/leaderboard/src/leaderboard-constants.ts +21 -0
  76. package/features/leaderboard/src/leaderboard-schema.ts +36 -0
  77. package/features/messaging/feature.json +111 -0
  78. package/features/messaging/src/ChatMultiPage.tsx +300 -0
  79. package/features/messaging/src/ChatPage.tsx +75 -0
  80. package/features/messaging/src/components/chat/ChatHeader.tsx +76 -0
  81. package/features/messaging/src/components/chat/MessageActionSheet.tsx +168 -0
  82. package/features/messaging/src/components/chat/MessageInput.tsx +65 -0
  83. package/features/messaging/src/components/chat/MessageItem.tsx +352 -0
  84. package/features/messaging/src/components/chat/MessageList.tsx +248 -0
  85. package/features/messaging/src/components/chat/ReactionPicker.tsx +2 -0
  86. package/features/messaging/src/components/chat/ThreadPanel.tsx +107 -0
  87. package/features/messaging/src/components/chat/UserProfilePopover.tsx +158 -0
  88. package/features/messaging/src/components/chat-multi/AddMemberModal.tsx +188 -0
  89. package/features/messaging/src/components/chat-multi/BrowseGroupsModal.tsx +163 -0
  90. package/features/messaging/src/components/chat-multi/ChannelListItem.tsx +147 -0
  91. package/features/messaging/src/components/chat-multi/ChannelSettingsPanel.tsx +344 -0
  92. package/features/messaging/src/components/chat-multi/ChannelSidebar.tsx +460 -0
  93. package/features/messaging/src/components/chat-multi/CreateGroupModal.tsx +190 -0
  94. package/features/messaging/src/components/chat-multi/InvitationList.tsx +149 -0
  95. package/features/messaging/src/components/chat-multi/NewMessageModal.tsx +159 -0
  96. package/features/messaging/src/hooks/useChatChannel.ts +64 -0
  97. package/features/messaging/src/hooks/useLongPress.ts +74 -0
  98. package/features/messaging/src/hooks/useMultiChannel.ts +142 -0
  99. package/features/messaging/src/messaging-schema.ts +25 -0
  100. package/features/presence-test/feature.json +21 -0
  101. package/features/presence-test/src/PresenceTestPage.tsx +120 -0
  102. package/features/sidebar/feature.json +19 -0
  103. package/features/sidebar/src/AppSidebar.tsx +558 -0
  104. package/features/tasks/feature.json +33 -0
  105. package/features/tasks/src/TasksPage.tsx +598 -0
  106. package/features/tasks/src/tasks-constants.ts +43 -0
  107. package/features/tasks/src/tasks-schema.ts +56 -0
  108. package/features/testing/feature.json +49 -0
  109. package/features/testing/src/TestPage.tsx +145 -0
  110. package/features/testing/src/cpu-burn-action.ts +51 -0
  111. package/features/testing/src/subrequest-burn-action.ts +53 -0
  112. package/features/testing/src/test-schema.ts +23 -0
  113. package/features/topbar/feature.json +18 -0
  114. package/features/topbar/src/Topbar.tsx +353 -0
  115. package/features/tree/feature.json +26 -0
  116. package/features/tree/src/TreeSidebar.tsx +607 -0
  117. package/features/tree/src/tree-constants.ts +68 -0
  118. package/features/tree/src/tree-hooks.ts +258 -0
  119. package/package.json +4 -2
  120. package/scripts/add-feature.cjs +654 -0
@@ -0,0 +1,56 @@
1
+ /**
2
+ * Tasks/Challenges Feature - Schema
3
+ *
4
+ * A claimable task system demonstrating:
5
+ * - 'unclaimed-or-own' permission for claiming unclaimed items
6
+ * - writableFields for restricting which fields users can update
7
+ * - timestampTrigger for automatic timestamp fields
8
+ * - Admin-only grading workflow
9
+ */
10
+
11
+ import type { CollectionSchema } from 'deepspace/worker'
12
+
13
+ export const challengesSchema: CollectionSchema = {
14
+ name: 'challenges',
15
+ columns: [
16
+ { name: 'title', storage: 'text', interpretation: 'plain', required: true },
17
+ { name: 'description', storage: 'text', interpretation: 'plain', required: true },
18
+ { name: 'difficulty', storage: 'text', interpretation: 'plain', default: 'medium' },
19
+ { name: 'points', storage: 'number', interpretation: 'plain', default: 10 },
20
+ // Claim fields
21
+ { name: 'claimedById', storage: 'text', interpretation: 'plain' },
22
+ { name: 'claimedAt', storage: 'text', interpretation: 'plain', timestampTrigger: { field: 'claimedById' } },
23
+ // Submission fields
24
+ { name: 'submitted', storage: 'number', interpretation: { kind: 'boolean' }, default: false },
25
+ { name: 'submissionUrl', storage: 'text', interpretation: 'plain' },
26
+ { name: 'submissionNotes', storage: 'text', interpretation: 'plain' },
27
+ { name: 'submittedAt', storage: 'text', interpretation: 'plain', timestampTrigger: { field: 'submitted', value: true } },
28
+ // Grading fields (admin only)
29
+ { name: 'grade', storage: 'text', interpretation: 'plain' },
30
+ { name: 'feedback', storage: 'text', interpretation: 'plain' },
31
+ { name: 'gradedById', storage: 'text', interpretation: 'plain' },
32
+ { name: 'gradedAt', storage: 'text', interpretation: 'plain', timestampTrigger: { field: 'grade' } },
33
+ // Creator
34
+ { name: 'createdById', storage: 'text', interpretation: 'plain', required: true, userBound: true, immutable: true },
35
+ ],
36
+ ownerField: 'claimedById',
37
+ permissions: {
38
+ viewer: {
39
+ read: true,
40
+ create: false,
41
+ update: false,
42
+ delete: false,
43
+ },
44
+ member: {
45
+ read: true,
46
+ create: true,
47
+ update: 'unclaimed-or-own',
48
+ delete: 'own',
49
+ writableFields: [
50
+ 'claimedById',
51
+ 'submitted', 'submissionUrl', 'submissionNotes',
52
+ ],
53
+ },
54
+ admin: { read: true, create: true, update: true, delete: true },
55
+ },
56
+ }
@@ -0,0 +1,49 @@
1
+ {
2
+ "id": "testing",
3
+ "name": "Testing",
4
+ "category": "data",
5
+ "description": "E2E test page exercising RBAC and CRUD against RecordRoom",
6
+ "details": "Test infrastructure page used by Playwright e2e tests. Displays current role, creates/reads/updates/deletes test items, and verifies RBAC permission enforcement. Also installs cpu-burn and subrequest-burn server actions that intentionally exceed the dispatch worker's per-invocation limits — used by the e2e dispatch-limits spec to assert denial-of-wallet protection fires.",
7
+ "files": [
8
+ {
9
+ "src": "src/TestPage.tsx",
10
+ "dest": "src/pages/test.tsx"
11
+ },
12
+ {
13
+ "src": "src/test-schema.ts",
14
+ "dest": "src/schemas/test-schema.ts"
15
+ },
16
+ {
17
+ "src": "src/cpu-burn-action.ts",
18
+ "dest": "src/actions/cpu-burn.ts"
19
+ },
20
+ {
21
+ "src": "src/subrequest-burn-action.ts",
22
+ "dest": "src/actions/subrequest-burn.ts"
23
+ }
24
+ ],
25
+ "schema": {
26
+ "exportName": "testItemsSchema",
27
+ "importPath": "./schemas/test-schema",
28
+ "spreadOperator": false
29
+ },
30
+ "actions": [
31
+ {
32
+ "exportName": "cpuBurnAction",
33
+ "registerAs": "cpu-burn",
34
+ "importPath": "./cpu-burn"
35
+ },
36
+ {
37
+ "exportName": "subrequestBurnAction",
38
+ "registerAs": "subrequest-burn",
39
+ "importPath": "./subrequest-burn"
40
+ }
41
+ ],
42
+ "route": {
43
+ "path": "/test",
44
+ "protected": false
45
+ },
46
+ "requires": [],
47
+ "instructions": [],
48
+ "patterns": []
49
+ }
@@ -0,0 +1,145 @@
1
+ /**
2
+ * TestPage — exercises RBAC and CRUD against the RecordRoom.
3
+ *
4
+ * Used by Playwright e2e tests to verify real data flow.
5
+ * Displays current role, creates/reads/updates/deletes items.
6
+ */
7
+
8
+ import { useState } from 'react'
9
+ import { useQuery } from 'deepspace'
10
+ import { useMutations } from 'deepspace'
11
+ import { useUser } from 'deepspace'
12
+ import { useRecordContext } from 'deepspace'
13
+ import { useAuth } from 'deepspace'
14
+
15
+ interface ItemData {
16
+ title: string
17
+ description: string
18
+ status: string
19
+ createdBy: string
20
+ }
21
+
22
+ export default function TestPage() {
23
+ const { isSignedIn } = useAuth()
24
+ const { user } = useUser()
25
+ const { roomRole } = useRecordContext()
26
+ const { records: items, status } = useQuery<ItemData>('test-items')
27
+ const isLoading = status === 'loading'
28
+ const { createConfirmed, putConfirmed, removeConfirmed } = useMutations<ItemData>('test-items')
29
+
30
+ const [lastResult, setLastResult] = useState<string>('')
31
+ const [lastError, setLastError] = useState<string>('')
32
+
33
+ const clearStatus = () => { setLastResult(''); setLastError('') }
34
+
35
+ const tryCreate = async () => {
36
+ clearStatus()
37
+ try {
38
+ const id = await createConfirmed({ title: 'Test Item', description: 'Created by test', status: 'draft', createdBy: user?.id ?? 'anonymous' })
39
+ setLastResult(`created:${id}`)
40
+ } catch (e: any) {
41
+ setLastError(e.message ?? String(e))
42
+ }
43
+ }
44
+
45
+ const tryCreatePublished = async () => {
46
+ clearStatus()
47
+ try {
48
+ const id = await createConfirmed({ title: 'Public Item', description: 'Visible to anonymous', status: 'published', createdBy: user?.id ?? 'anonymous' })
49
+ setLastResult(`created:${id}`)
50
+ } catch (e: any) {
51
+ setLastError(e.message ?? String(e))
52
+ }
53
+ }
54
+
55
+ const tryUpdate = async (recordId: string) => {
56
+ clearStatus()
57
+ try {
58
+ await putConfirmed(recordId, { title: 'Updated Title', description: 'Updated by test', status: 'draft', createdBy: user?.id ?? 'anonymous' })
59
+ setLastResult(`updated:${recordId}`)
60
+ } catch (e: any) {
61
+ setLastError(e.message ?? String(e))
62
+ }
63
+ }
64
+
65
+ const tryDelete = async (recordId: string) => {
66
+ clearStatus()
67
+ try {
68
+ await removeConfirmed(recordId)
69
+ setLastResult(`deleted:${recordId}`)
70
+ } catch (e: any) {
71
+ setLastError(e.message ?? String(e))
72
+ }
73
+ }
74
+
75
+ return (
76
+ <div className="mx-auto max-w-3xl p-8" data-testid="test-page">
77
+ <div className="mb-8 rounded-lg border border-border bg-card p-4">
78
+ <h2 className="text-lg font-semibold text-foreground mb-2">Connection Status</h2>
79
+ <div className="space-y-1 text-sm">
80
+ <div>Signed in: <span data-testid="test-signed-in">{String(isSignedIn)}</span></div>
81
+ <div>User ID: <span data-testid="test-user-id">{user?.id ?? 'none'}</span></div>
82
+ <div>Role: <span data-testid="test-user-role">{roomRole ?? 'connecting'}</span></div>
83
+ <div>User Name: <span data-testid="test-user-name">{user?.name ?? 'Anonymous'}</span></div>
84
+ </div>
85
+ </div>
86
+
87
+ <div className="mb-8 rounded-lg border border-border bg-card p-4">
88
+ <h2 className="text-lg font-semibold text-foreground mb-2">Actions</h2>
89
+ <div className="flex flex-wrap gap-2">
90
+ <button data-testid="test-create-item" onClick={tryCreate}
91
+ className="rounded bg-primary px-3 py-1.5 text-sm text-primary-foreground hover:opacity-90">
92
+ Create Draft Item
93
+ </button>
94
+ <button data-testid="test-create-published" onClick={tryCreatePublished}
95
+ className="rounded bg-primary px-3 py-1.5 text-sm text-primary-foreground hover:opacity-90">
96
+ Create Published Item
97
+ </button>
98
+ </div>
99
+ <div className="mt-3 min-h-[2.25rem]">
100
+ {lastResult && (
101
+ <div data-testid="test-last-result" className="rounded bg-success/20 px-3 py-2 text-sm text-success">
102
+ {lastResult}
103
+ </div>
104
+ )}
105
+ {lastError && (
106
+ <div data-testid="test-last-error" className="rounded bg-destructive/20 px-3 py-2 text-sm text-destructive">
107
+ {lastError}
108
+ </div>
109
+ )}
110
+ </div>
111
+ </div>
112
+
113
+ <div className="rounded-lg border border-border bg-card p-4">
114
+ <h2 className="text-lg font-semibold text-foreground mb-2">
115
+ Items {isLoading ? '(loading...)' : `(${items?.length ?? 0})`}
116
+ </h2>
117
+ <div data-testid="test-items-list" className="space-y-2">
118
+ {items?.map((item) => (
119
+ <div key={item.recordId} data-testid={`test-item-${item.recordId}`}
120
+ className="flex items-center justify-between rounded border border-border px-3 py-2">
121
+ <div className="text-sm">
122
+ <span className="font-medium text-foreground">{item.data.title}</span>
123
+ <span className="ml-2 text-muted-foreground">({item.data.status})</span>
124
+ <span className="ml-2 text-muted-foreground text-xs">by {item.data.createdBy}</span>
125
+ </div>
126
+ <div className="flex gap-1">
127
+ <button data-testid={`test-update-${item.recordId}`} onClick={() => tryUpdate(item.recordId)}
128
+ className="rounded bg-secondary px-2 py-1 text-xs text-secondary-foreground hover:opacity-90">
129
+ Update
130
+ </button>
131
+ <button data-testid={`test-delete-${item.recordId}`} onClick={() => tryDelete(item.recordId)}
132
+ className="rounded bg-destructive/20 px-2 py-1 text-xs text-destructive hover:opacity-90">
133
+ Delete
134
+ </button>
135
+ </div>
136
+ </div>
137
+ ))}
138
+ {!isLoading && (!items || items.length === 0) && (
139
+ <div data-testid="test-items-empty" className="text-sm text-muted-foreground">No items</div>
140
+ )}
141
+ </div>
142
+ </div>
143
+ </div>
144
+ )
145
+ }
@@ -0,0 +1,51 @@
1
+ /**
2
+ * cpu-burn — server action that deliberately exceeds the dispatcher's
3
+ * CPU limit so the e2e `testing.spec.ts` can assert that
4
+ * `DEFAULT_LIMITS.cpuMs` on the dispatch worker actually fires.
5
+ *
6
+ * Runs a tight loop of hash computations until the runtime kills it. In
7
+ * production this would be a denial-of-wallet event; in the testing
8
+ * feature it's the whole point — we want the kill.
9
+ *
10
+ * The action returns only if CF *didn't* enforce the limit, which is
11
+ * itself a test failure signal (hence the misleading `success: true`
12
+ * — the spec asserts on the response status / outcome, not the body).
13
+ */
14
+
15
+ import type { ActionHandler } from 'deepspace/worker'
16
+
17
+ interface OwnerEnv {
18
+ OWNER_USER_ID?: string
19
+ }
20
+
21
+ export const cpuBurnAction: ActionHandler<OwnerEnv> = async (ctx) => {
22
+ // Owner-only: the whole point of this action is to deliberately blow
23
+ // the dispatcher's CPU cap, which burns real CF compute. Gate to the
24
+ // app owner so a random authenticated user can't weaponize every
25
+ // deployed app that installed the testing feature.
26
+ const ownerId = ctx.env.OWNER_USER_ID
27
+ if (ownerId && ctx.userId !== ownerId) {
28
+ return { success: false, error: 'Forbidden: owner only' }
29
+ }
30
+
31
+ // Keep the loop tight and work-generating so v8 can't eliminate it as
32
+ // dead code. Each iteration hashes a different value so the work
33
+ // isn't constant-foldable.
34
+ const encoder = new TextEncoder()
35
+ let counter = 0n
36
+ const deadline = Date.now() + 15_000 // safety: stop after 15s if CF didn't
37
+
38
+ while (Date.now() < deadline) {
39
+ const bytes = encoder.encode(`cpu-burn-${counter}`)
40
+ await crypto.subtle.digest('SHA-256', bytes)
41
+ counter += 1n
42
+ }
43
+
44
+ // If we got here, the dispatcher's limit did NOT fire — which is the
45
+ // thing the spec is checking against. Return a marker so the failure
46
+ // is diagnosable if the response ever makes it back.
47
+ return {
48
+ success: true,
49
+ data: { iterations: counter.toString(), note: 'limit-did-not-fire' },
50
+ }
51
+ }
@@ -0,0 +1,53 @@
1
+ /**
2
+ * subrequest-burn — server action that fires more outbound fetches than
3
+ * the dispatcher's `DEFAULT_LIMITS.subRequests` allows, so the e2e
4
+ * `testing.spec.ts` can assert the subRequests cap actually enforces.
5
+ *
6
+ * Hits the platform-worker health endpoint 60 times (cap is 50). The
7
+ * 51st subrequest should fail inside the user worker.
8
+ *
9
+ * Platform-worker is an internal service binding — no public network
10
+ * egress, no third-party rate-limit headaches.
11
+ */
12
+
13
+ import type { ActionHandler } from 'deepspace/worker'
14
+
15
+ interface OwnerEnv {
16
+ OWNER_USER_ID?: string
17
+ }
18
+
19
+ export const subrequestBurnAction: ActionHandler<OwnerEnv> = async (ctx) => {
20
+ // Owner-only — same reasoning as cpu-burn. Without this guard any
21
+ // authenticated user could force our dispatcher to emit 50 outbound
22
+ // fetches on command.
23
+ const ownerId = ctx.env.OWNER_USER_ID
24
+ if (ownerId && ctx.userId !== ownerId) {
25
+ return { success: false, error: 'Forbidden: owner only' }
26
+ }
27
+
28
+ // Target is an arbitrary URL — the test passes `https://example.com`
29
+ // but callers could point at an internal endpoint too. Fixed fallback
30
+ // so the spec works even when params aren't supplied.
31
+ const target = (ctx.params.target as string | undefined) ?? 'https://example.com'
32
+ const count = Math.max(1, Math.min(60, (ctx.params.count as number | undefined) ?? 60))
33
+
34
+ let completed = 0
35
+ let lastError: string | null = null
36
+ try {
37
+ for (let i = 0; i < count; i++) {
38
+ const res = await fetch(`${target}?i=${i}`, { method: 'GET' })
39
+ // Drain the body so the subrequest actually completes.
40
+ await res.arrayBuffer()
41
+ completed++
42
+ }
43
+ } catch (err) {
44
+ lastError = err instanceof Error ? err.message : String(err)
45
+ }
46
+
47
+ // If we completed all N subrequests, the cap didn't fire. Surface
48
+ // that as data so the spec can diagnose.
49
+ return {
50
+ success: completed === count,
51
+ data: { attempted: count, completed, lastError },
52
+ }
53
+ }
@@ -0,0 +1,23 @@
1
+ /**
2
+ * Test Items Schema — exercises RBAC and CRUD for e2e testing.
3
+ */
4
+
5
+ import type { CollectionSchema } from 'deepspace/worker'
6
+ import { ROLE_ANONYMOUS } from 'deepspace/worker'
7
+
8
+ export const testItemsSchema: CollectionSchema = {
9
+ name: 'test-items',
10
+ columns: [
11
+ { name: 'title', storage: 'text', interpretation: 'plain' },
12
+ { name: 'description', storage: 'text', interpretation: 'plain' },
13
+ { name: 'status', storage: 'text', interpretation: { kind: 'select', options: ['draft', 'published', 'archived'] } },
14
+ { name: 'createdBy', storage: 'text', interpretation: 'plain' },
15
+ ],
16
+ ownerField: 'createdBy',
17
+ permissions: {
18
+ [ROLE_ANONYMOUS]: { read: 'published', create: false, update: false, delete: false },
19
+ member: { read: true, create: true, update: 'own', delete: 'own' },
20
+ admin: { read: true, create: true, update: true, delete: true },
21
+ },
22
+ visibilityField: { field: 'status', value: 'published' },
23
+ }
@@ -0,0 +1,18 @@
1
+ {
2
+ "id": "topbar",
3
+ "name": "Topbar",
4
+ "category": "nav",
5
+ "description": "Horizontal top navigation bar with search, user menu, and notifications",
6
+ "details": "Configurable top navigation bar component with logo/brand area, horizontal nav links with active state, optional search input, user avatar dropdown menu, and notification bell with badge count.",
7
+ "files": [
8
+ {
9
+ "src": "src/Topbar.tsx",
10
+ "dest": "src/components/topbar/Topbar.tsx"
11
+ }
12
+ ],
13
+ "requires": [],
14
+ "instructions": [
15
+ "Import Topbar into your layout and configure navItems, logo, and user props"
16
+ ],
17
+ "patterns": []
18
+ }