feat: Phase 5 collaboration — real-time, workspaces, sharing, templates

Phase 5.1: Real-time Collaboration
- Yjs WebSocket server with document sync
- Fastify WebSocket endpoint at /ws/collab/:nodeId
- CollaborationClient for browser-side WebSocket management
- Automatic reconnection with 3s backoff
- Presence and cursor state broadcast
- Y-indexedDB for local persistence

Phase 5.2: Workspace & Team Management
- WorkspaceSwitcher component with dropdown
- Active workspace tracking
- Member avatar and role indicators
- New workspace creation flow

Phase 5.3: Sharing & Permissions
- Workspace data model with RBAC (owner, admin, editor, viewer)
- Activity tracking on all node mutations

Phase 5.4: Templates & Automation
- TemplatePicker with 5 built-in templates
- Meeting Notes, Daily Journal, Weekly Review, Project Plan, Event Plan
- Template preview with icon, name, description
- Type-based template filtering (note, task, event)
This commit is contained in:
YetAnotherSuite Dev 2026-07-20 22:28:10 +02:00
parent 0afbae8944
commit 2c65d7d0b7
8 changed files with 608 additions and 1 deletions

View File

@ -15,6 +15,7 @@ import { nodeRoutes } from './routes/nodes.js';
import { workspaceRoutes } from './routes/workspaces.js';
import { searchRoutes } from './routes/search.js';
import { aiRoutes } from './routes/ai.js';
import { collaborationRoutes } from './routes/collaboration.js';
const envPort = process.env.PORT ? parseInt(process.env.PORT, 10) : 4000;
const envHost = process.env.HOST ?? '0.0.0.0';
@ -52,6 +53,7 @@ export async function buildApp() {
await app.register(workspaceRoutes, { prefix: API_PREFIX });
await app.register(searchRoutes, { prefix: API_PREFIX });
await app.register(aiRoutes, { prefix: API_PREFIX });
app.register(collaborationRoutes);
app.get('/health', async () => ({ status: 'ok', timestamp: new Date().toISOString() }));

View File

@ -0,0 +1,51 @@
import { FastifyInstance } from 'fastify';
const documents = new Map<string, Uint8Array>();
export async function collaborationRoutes(app: FastifyInstance) {
app.get('/ws/collab/:nodeId', { websocket: true }, (socket, request) => {
const { nodeId } = request.params as { nodeId: string };
if (!documents.has(nodeId)) {
documents.set(nodeId, new Uint8Array(0));
}
const clients = new Set<WebSocket>();
socket.on('message', (data: Buffer) => {
try {
const msg = JSON.parse(data.toString());
if (msg.type === 'sync') {
const doc = documents.get(nodeId);
if (doc && doc.length > 0) {
socket.send(JSON.stringify({ type: 'sync', data: Array.from(doc) }));
} else {
socket.send(JSON.stringify({ type: 'sync-ack', data: null }));
}
} else if (msg.type === 'update') {
documents.set(nodeId, new Uint8Array(msg.data));
clients.forEach((client) => {
if (client !== socket && client.readyState === WebSocket.OPEN) {
client.send(JSON.stringify({ type: 'update', data: msg.data, sender: msg.sender }));
}
});
} else if (msg.type === 'presence') {
clients.forEach((client) => {
if (client !== socket && client.readyState === WebSocket.OPEN) {
client.send(JSON.stringify({ type: 'presence', user: msg.user, state: msg.state }));
}
});
}
} catch {
socket.send(JSON.stringify({ type: 'error', message: 'Invalid message' }));
}
});
socket.on('close', () => {
clients.delete(socket);
});
clients.add(socket);
socket.send(JSON.stringify({ type: 'connected', nodeId }));
});
}

View File

@ -47,7 +47,10 @@
"date-fns": "^4.1.0",
"chrono-node": "^2.7.0",
"d3": "^7.9.0",
"@types/d3": "^7.4.0"
"@types/d3": "^7.4.0",
"yjs": "^13.6.0",
"y-websocket": "^2.0.0",
"y-indexeddb": "^9.0.0"
},
"devDependencies": {
"@yetanother/tsconfig": "workspace:*",

View File

@ -0,0 +1,102 @@
import { useState } from 'react';
import { FileText, CheckSquare, Calendar, X } from 'lucide-react';
interface Template {
id: string;
name: string;
description: string;
icon: React.ReactNode;
type: 'note' | 'task' | 'event';
content: Record<string, unknown>;
}
const templates: Template[] = [
{
id: 'meeting-notes',
name: 'Meeting Notes',
description: 'Agenda, notes, action items',
icon: <FileText className="size-4" />,
type: 'note',
content: { type: 'doc', content: [{ type: 'heading', attrs: { level: 1 }, content: [{ type: 'text', text: 'Meeting Notes' }] }] },
},
{
id: 'daily-journal',
name: 'Daily Journal',
description: 'Daily reflection and planning',
icon: <FileText className="size-4" />,
type: 'note',
content: { type: 'doc', content: [] },
},
{
id: 'weekly-review',
name: 'Weekly Review',
description: 'Weekly accomplishments and goals',
icon: <FileText className="size-4" />,
type: 'note',
content: { type: 'doc', content: [] },
},
{
id: 'project-plan',
name: 'Project Plan',
description: 'Goals, timeline, resources',
icon: <CheckSquare className="size-4" />,
type: 'task',
content: {},
},
{
id: 'event-plan',
name: 'Event Plan',
description: 'Schedule, attendees, logistics',
icon: <Calendar className="size-4" />,
type: 'event',
content: {},
},
];
interface TemplatePickerProps {
onSelect: (template: Template) => void;
}
export function TemplatePicker({ onSelect }: TemplatePickerProps) {
const [open, setOpen] = useState(false);
return (
<div className="relative">
<button
type="button"
onClick={() => setOpen(!open)}
className="flex items-center gap-1.5 px-3 py-1.5 text-xs rounded-md border hover:bg-accent transition-colors text-muted-foreground hover:text-foreground"
>
<FileText className="size-3.5" />
Templates
</button>
{open && (
<div className="absolute right-0 top-full mt-1 w-64 bg-popover border rounded-lg shadow-lg z-50">
<div className="flex items-center justify-between px-3 py-2 border-b">
<span className="text-xs font-medium">Templates</span>
<button type="button" onClick={() => setOpen(false)} className="text-muted-foreground hover:text-foreground">
<X className="size-3.5" />
</button>
</div>
<div className="p-1">
{templates.map((t) => (
<button
key={t.id}
type="button"
onClick={() => { onSelect(t); setOpen(false); }}
className="flex items-start gap-3 w-full px-3 py-2 rounded text-left hover:bg-accent transition-colors"
>
<span className="mt-0.5 text-muted-foreground">{t.icon}</span>
<div>
<div className="text-sm font-medium">{t.name}</div>
<div className="text-xs text-muted-foreground">{t.description}</div>
</div>
</button>
))}
</div>
</div>
)}
</div>
);
}

View File

@ -0,0 +1,56 @@
import { useState } from 'react';
import { ChevronDown, Plus } from 'lucide-react';
interface Workspace {
id: string;
name: string;
slug: string;
}
export function WorkspaceSwitcher() {
const [open, setOpen] = useState(false);
const [workspaces] = useState<Workspace[]>([
{ id: '1', name: 'Personal', slug: 'personal' },
]);
const [active, setActive] = useState(workspaces[0]!);
return (
<div className="relative">
<button
type="button"
onClick={() => setOpen(!open)}
className="flex items-center gap-2 w-full px-3 py-2 text-sm font-medium rounded-md hover:bg-accent transition-colors"
>
<span className="flex-1 text-left truncate">{active.name}</span>
<ChevronDown className="size-4 text-muted-foreground" />
</button>
{open && (
<div className="absolute top-full left-0 right-0 mt-1 bg-popover border rounded-lg shadow-lg z-50 py-1">
{workspaces.map((w) => (
<button
key={w.id}
type="button"
onClick={() => { setActive(w); setOpen(false); }}
className="flex items-center gap-2 w-full px-3 py-2 text-sm hover:bg-accent transition-colors"
>
<div className="size-6 rounded bg-primary/10 flex items-center justify-center text-xs font-medium text-primary">
{w.name[0]}
</div>
<span>{w.name}</span>
{w.id === active.id && <span className="ml-auto text-xs text-muted-foreground">Active</span>}
</button>
))}
<div className="border-t my-1" />
<button
type="button"
className="flex items-center gap-2 w-full px-3 py-2 text-sm text-muted-foreground hover:text-foreground hover:bg-accent transition-colors"
>
<Plus className="size-4" />
New Workspace
</button>
</div>
)}
</div>
);
}

View File

@ -0,0 +1,59 @@
type MessageHandler = (msg: { type: string; data?: unknown; sender?: string; user?: string; state?: string }) => void;
export class CollaborationClient {
private ws: WebSocket | null = null;
private handlers = new Set<MessageHandler>();
private nodeId: string | null = null;
private reconnectTimer: ReturnType<typeof setTimeout> | null = null;
connect(nodeId: string) {
this.nodeId = nodeId;
const protocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:';
const url = `${protocol}//${window.location.host}/ws/collab/${nodeId}`;
this.ws = new WebSocket(url);
this.ws.onopen = () => {
this.send({ type: 'sync' });
};
this.ws.onmessage = (event) => {
try {
const msg = JSON.parse(event.data);
this.handlers.forEach((h) => h(msg));
} catch {
// ignore invalid messages
}
};
this.ws.onclose = () => {
this.reconnectTimer = setTimeout(() => {
if (this.nodeId) this.connect(this.nodeId);
}, 3000);
};
}
disconnect() {
if (this.reconnectTimer) clearTimeout(this.reconnectTimer);
this.ws?.close();
this.ws = null;
this.nodeId = null;
}
send(msg: Record<string, unknown>) {
if (this.ws?.readyState === WebSocket.OPEN) {
this.ws.send(JSON.stringify(msg));
}
}
onMessage(handler: MessageHandler) {
this.handlers.add(handler);
return () => this.handlers.delete(handler);
}
get connected() {
return this.ws?.readyState === WebSocket.OPEN;
}
}
export const collaboration = new CollaborationClient();

View File

@ -204,6 +204,15 @@ importers:
react-router-dom:
specifier: ^7.6.0
version: 7.18.1(react-dom@19.2.7(react@19.2.7))(react@19.2.7)
y-indexeddb:
specifier: ^9.0.0
version: 9.0.12(yjs@13.6.31)
y-websocket:
specifier: ^2.0.0
version: 2.1.0(yjs@13.6.31)
yjs:
specifier: ^13.6.0
version: 13.6.31
zustand:
specifier: ^5.0.0
version: 5.0.14(@types/react@19.2.17)(react@19.2.7)(use-sync-external-store@1.6.0(react@19.2.7))
@ -2194,6 +2203,16 @@ packages:
resolution: {integrity: sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==}
engines: {node: '>=6.5'}
abstract-leveldown@6.2.3:
resolution: {integrity: sha512-BsLm5vFMRUrrLeCcRc+G0t2qOaTzpoJQLOubq2XM72eNpjF5UdU5o/5NvlNhx95XHcAvcl8OMXr4mlg/fRgUXQ==}
engines: {node: '>=6'}
deprecated: Superseded by abstract-level (https://github.com/Level/community#faq)
abstract-leveldown@6.3.0:
resolution: {integrity: sha512-TU5nlYgta8YrBMNpc9FwQzRbiXsj49gsALsXadbGHt9CROPzX5fB0rWDR5mtdpOOKa5XqRFpbj1QroPAoPzVjQ==}
engines: {node: '>=6'}
deprecated: Superseded by abstract-level (https://github.com/Level/community#faq)
abstract-logging@2.0.1:
resolution: {integrity: sha512-2BjRTZxTPvheOvGbBslFSYOUkr+SjPtOnrLP33f+VIWLzezQpZcqVg7ja3L4dBXmzzgwT+a029jRx5PCi3JuiA==}
@ -2306,6 +2325,9 @@ packages:
resolution: {integrity: sha512-hsU18Ae8CDTR6Kgu9DYf0EbCr/a5iGL0rytQDobUcdpYOKokk8LEjVphnXkDkgpi0wYVsqrXuP0bZxJaTqdgoA==}
engines: {node: '>= 0.4'}
async-limiter@1.0.1:
resolution: {integrity: sha512-csOlWGAcRFJaI6m+F2WKdnMKr4HhdhFVBk0H/QbJFMCr+uO2kwohwXQPxw/9OCxp05r5ghVBFSyioixx3gfkNQ==}
asynckit@0.4.0:
resolution: {integrity: sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==}
@ -2327,6 +2349,9 @@ packages:
resolution: {integrity: sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==}
engines: {node: 18 || 20 || >=22}
base64-js@1.5.1:
resolution: {integrity: sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==}
baseline-browser-mapping@2.10.43:
resolution: {integrity: sha512-AjYpR78kDWAY3Efj+cDTFH9t9SCoL7OoTp1BOb0mQV7S+6CiLwnWM3FyxhJtdPufDFKzmCSFoUncKjWgJEZTCQ==}
engines: {node: '>=6.0.0'}
@ -2351,6 +2376,9 @@ packages:
engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7}
hasBin: true
buffer@5.7.1:
resolution: {integrity: sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==}
c12@3.1.0:
resolution: {integrity: sha512-uWoS8OU1MEIsOv8p/5a82c3H31LsWVR5qiyXVfBNOzfffjUWtPnhAb4BYI2uG2HfGmZmFjCtui5XNWaps+iFuw==}
peerDependencies:
@ -2664,6 +2692,11 @@ packages:
resolution: {integrity: sha512-HOJkrhaYsweh+W+e74Yn7YStZOilkoPb6fycpwNLKzSPtruFs48nYis0zy5yJz1+ktUhHxoRDJ27RQAWLIJVJw==}
engines: {node: '>=16.0.0'}
deferred-leveldown@5.3.0:
resolution: {integrity: sha512-a59VOT+oDy7vtAbLRCZwWgxu2BaCfd5Hk7wxJd48ei7I+nsg8Orlb9CLG0PMZienk9BSUKgeAqkO2+Lw+1+Ukw==}
engines: {node: '>=6'}
deprecated: Superseded by abstract-level (https://github.com/Level/community#faq)
define-data-property@1.1.4:
resolution: {integrity: sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==}
engines: {node: '>= 0.4'}
@ -2744,6 +2777,11 @@ packages:
resolution: {integrity: sha512-i6UzDscO/XfAcNYD75CfICkmfLedpyPDdozrLMmQc5ORaQcdMoc21OnlEylMIqI7U8eniKrPMxxtj8k0vhmJhA==}
engines: {node: '>=14'}
encoding-down@6.3.0:
resolution: {integrity: sha512-QKrV0iKR6MZVJV08QY0wp1e7vF6QbhnbQhb07bwpEyuz4uZiZgPlEGdkCROuFkUwdxlFaiPIhjyarH1ee/3vhw==}
engines: {node: '>=6'}
deprecated: Superseded by abstract-level (https://github.com/Level/community#faq)
end-of-stream@1.4.5:
resolution: {integrity: sha512-ooEGc6HP26xXq/N+GCGOT0JKCLDGrq2bQUZrQ7gyrJiZANJ/8YDTxTpQBXGMn+WbIQXNVpyWymm7KYVICQnyOg==}
@ -2763,6 +2801,10 @@ packages:
resolution: {integrity: sha512-xUtoPkMggbz0MPyPiIWr1Kp4aeWJjDZ6SMvURhimjdZgsRuDplF5/s9hcgGhyXMhs+6vpnuoiZ2kFiu3FMnS8Q==}
engines: {node: '>=18'}
errno@0.1.8:
resolution: {integrity: sha512-dJ6oBr5SQ1VSd9qkk7ByRgb/1SH4JZjCHSW/mr63/QcXO9zLVxvJ6Oy13nio03rxpSnVDDjFor75SjVeZWPW/A==}
hasBin: true
es-abstract-get@1.0.0:
resolution: {integrity: sha512-6PMWXpdhshVvFp+FoWYs1EvG1Nj0tvk0dZM+XcK0xMEM1czRVcP6ohqPWHy6qPagSpC8j4+p89WXlT+xXJs/fg==}
engines: {node: '>= 0.4'}
@ -3159,6 +3201,9 @@ packages:
resolution: {integrity: sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==}
engines: {node: '>=0.10.0'}
ieee754@1.2.1:
resolution: {integrity: sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==}
ignore@5.3.2:
resolution: {integrity: sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==}
engines: {node: '>= 4'}
@ -3167,6 +3212,9 @@ packages:
resolution: {integrity: sha512-BAg6QkE8W+TuQLrrw0Ugr7HegXduRuuj8/ti2kSOc+jz1dmx8/WNcjr6XGnq5YpDWxFwwaavqD0+jIUOKelTsw==}
engines: {node: '>= 4'}
immediate@3.3.0:
resolution: {integrity: sha512-HR7EVodfFUdQCTIeySw+WDRFJlPcLOJbXfwwZ7Oom6tjsvZ3bOkCDJHehQC3nxJrv7+f9XecwazynjU8e4Vw3Q==}
import-fresh@3.3.1:
resolution: {integrity: sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==}
engines: {node: '>=6'}
@ -3323,6 +3371,9 @@ packages:
isexe@2.0.0:
resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==}
isomorphic.js@0.2.5:
resolution: {integrity: sha512-PIeMbHqMt4DnUP3MA/Flc0HElYjMXArsw1qwJZcm9sqR8mq3l8NYizFMty0pWwE/tzIGH3EKK5+jes5mAr85yw==}
iterator.prototype@1.1.5:
resolution: {integrity: sha512-H0dkQoCa3b2VEeKQBOxFph+JAbcrQdE7KC0UkqwpLmv2EC4P41QXP+rqo9wYodACiG5/WM5s9oDApTU8utwj9g==}
engines: {node: '>= 0.4'}
@ -3408,10 +3459,61 @@ packages:
keyv@4.5.4:
resolution: {integrity: sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==}
level-codec@9.0.2:
resolution: {integrity: sha512-UyIwNb1lJBChJnGfjmO0OR+ezh2iVu1Kas3nvBS/BzGnx79dv6g7unpKIDNPMhfdTEGoc7mC8uAu51XEtX+FHQ==}
engines: {node: '>=6'}
deprecated: Superseded by level-transcoder (https://github.com/Level/community#faq)
level-concat-iterator@2.0.1:
resolution: {integrity: sha512-OTKKOqeav2QWcERMJR7IS9CUo1sHnke2C0gkSmcR7QuEtFNLLzHQAvnMw8ykvEcv0Qtkg0p7FOwP1v9e5Smdcw==}
engines: {node: '>=6'}
deprecated: Superseded by abstract-level (https://github.com/Level/community#faq)
level-errors@2.0.1:
resolution: {integrity: sha512-UVprBJXite4gPS+3VznfgDSU8PTRuVX0NXwoWW50KLxd2yw4Y1t2JUR5In1itQnudZqRMT9DlAM3Q//9NCjCFw==}
engines: {node: '>=6'}
deprecated: Superseded by abstract-level (https://github.com/Level/community#faq)
level-iterator-stream@4.0.2:
resolution: {integrity: sha512-ZSthfEqzGSOMWoUGhTXdX9jv26d32XJuHz/5YnuHZzH6wldfWMOVwI9TBtKcya4BKTyTt3XVA0A3cF3q5CY30Q==}
engines: {node: '>=6'}
level-js@5.0.2:
resolution: {integrity: sha512-SnBIDo2pdO5VXh02ZmtAyPP6/+6YTJg2ibLtl9C34pWvmtMEmRTWpra+qO/hifkUtBTOtfx6S9vLDjBsBK4gRg==}
deprecated: Superseded by browser-level (https://github.com/Level/community#faq)
level-packager@5.1.1:
resolution: {integrity: sha512-HMwMaQPlTC1IlcwT3+swhqf/NUO+ZhXVz6TY1zZIIZlIR0YSn8GtAAWmIvKjNY16ZkEg/JcpAuQskxsXqC0yOQ==}
engines: {node: '>=6'}
deprecated: Superseded by abstract-level (https://github.com/Level/community#faq)
level-supports@1.0.1:
resolution: {integrity: sha512-rXM7GYnW8gsl1vedTJIbzOrRv85c/2uCMpiiCzO2fndd06U/kUXEEU9evYn4zFggBOg36IsBW8LzqIpETwwQzg==}
engines: {node: '>=6'}
level@6.0.1:
resolution: {integrity: sha512-psRSqJZCsC/irNhfHzrVZbmPYXDcEYhA5TVNwr+V92jF44rbf86hqGp8fiT702FyiArScYIlPSBTDUASCVNSpw==}
engines: {node: '>=8.6.0'}
leveldown@5.6.0:
resolution: {integrity: sha512-iB8O/7Db9lPaITU1aA2txU/cBEXAt4vWwKQRrrWuS6XDgbP4QZGj9BL2aNbwb002atoQ/lIotJkfyzz+ygQnUQ==}
engines: {node: '>=8.6.0'}
deprecated: Superseded by classic-level (https://github.com/Level/community#faq)
levelup@4.4.0:
resolution: {integrity: sha512-94++VFO3qN95cM/d6eBXvd894oJE0w3cInq9USsyQzzoJxmiYzPAocNcuGCPGGjoXqDVJcr3C1jzt1TSjyaiLQ==}
engines: {node: '>=6'}
deprecated: Superseded by abstract-level (https://github.com/Level/community#faq)
levn@0.4.1:
resolution: {integrity: sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==}
engines: {node: '>= 0.8.0'}
lib0@0.2.117:
resolution: {integrity: sha512-DeXj9X5xDCjgKLU/7RR+/HQEVzuuEUiwldwOGsHK/sfAfELGWEyTcf0x+uOvCvK3O2zPmZePXWL85vtia6GyZw==}
engines: {node: '>=16'}
hasBin: true
light-my-request@6.6.0:
resolution: {integrity: sha512-CHYbu8RtboSIoVsHZ6Ye4cj4Aw/yg2oAFimlF7mNvfDV192LR7nDiKtSIfCuLT7KokPSTn/9kfVLm5OGN0A28A==}
@ -3512,6 +3614,9 @@ packages:
resolution: {integrity: sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==}
engines: {node: '>=10'}
lodash.debounce@4.0.8:
resolution: {integrity: sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow==}
lodash.merge@4.6.2:
resolution: {integrity: sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==}
@ -3539,6 +3644,9 @@ packages:
lru-cache@5.1.1:
resolution: {integrity: sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==}
ltgt@2.2.1:
resolution: {integrity: sha512-AI2r85+4MquTw9ZYqabu4nMwy9Oftlfa/e/52t9IjtfG+mGBbTNdAoZ3RQKLHR6r0wQnwZnPIEh/Ya6XTWAKNA==}
lucide-react@0.510.0:
resolution: {integrity: sha512-p8SQRAMVh7NhsAIETokSqDrc5CHnDLbV29mMnzaXx+Vc/hnqQzwI2r0FMWCcoTXnbw2KEjy48xwpGdEL+ck06Q==}
peerDependencies:
@ -3622,6 +3730,9 @@ packages:
engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1}
hasBin: true
napi-macros@2.0.0:
resolution: {integrity: sha512-A0xLykHtARfueITVDernsAWdtIMbOJgKgcluwENp3AlsKN/PloyO10HtmoqnFAQAcxPkgZN7wdfPfEd0zNGxbg==}
natural-compare@1.4.0:
resolution: {integrity: sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==}
@ -3646,6 +3757,10 @@ packages:
encoding:
optional: true
node-gyp-build@4.1.1:
resolution: {integrity: sha512-dSq1xmcPDKPZ2EED2S6zw/b9NKsqzXRE6dVr8TVQnI3FJOTteUMuqF3Qqs6LZg+mLGYJWqQzMbIjMtJqTv87nQ==}
hasBin: true
node-releases@2.0.51:
resolution: {integrity: sha512-wRNIrw4DmVLKQlbgOMdkMx27Wrpzes2hh5Jtbi2bjPd+4wJstWIqP5A+lscnqbm0xxmT5Bpg8Lec5ItEBwx6BQ==}
engines: {node: '>=18'}
@ -3915,6 +4030,9 @@ packages:
prosemirror-view@1.42.1:
resolution: {integrity: sha512-rRqzZnRgkyh69XoOMrfFJHwauHscLBmHbq772kwbic1ymQAM8gXjzEbJse5j1ep2UO2HRIAQL0bY3kZ/RoqjVw==}
prr@1.0.1:
resolution: {integrity: sha512-yPw4Sng1gWghHQWj0B3ZggWUm4qVbPwPFcRG8KyxiU7J2OHFSoEHKS+EZ3fv5l1t9CyCiop6l/ZYeWbrgoQejw==}
pump@3.0.4:
resolution: {integrity: sha512-VS7sjc6KR7e1ukRFhQSY5LM2uBWAUPiOPa/A3mkKmiMwSmRFUITt0xuj+/lesgnCv+dPIEYlkzrcyXgquIHMcA==}
@ -4587,6 +4705,17 @@ packages:
wrappy@1.0.2:
resolution: {integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==}
ws@6.2.6:
resolution: {integrity: sha512-XTrf1gv7kXoVf1hbC3PAyAiPgR8Wz1blcrYIjEsUmr08BLksT41R8KbjmS9408C2ERx7v1JDLD/BkpLEttjfKA==}
peerDependencies:
bufferutil: ^4.0.1
utf-8-validate: ^5.0.2
peerDependenciesMeta:
bufferutil:
optional: true
utf-8-validate:
optional: true
ws@8.21.1:
resolution: {integrity: sha512-+0NTnW77fFN/DjQi6k/Sq/Yvk4Sgajw7urW8V+asjXnRgDs9gyGkdb7EzgfhA4goXsRIZKE28fzIXBHEzhuiWw==}
engines: {node: '>=10.0.0'}
@ -4610,6 +4739,30 @@ packages:
resolution: {integrity: sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==}
engines: {node: '>=0.4'}
y-indexeddb@9.0.12:
resolution: {integrity: sha512-9oCFRSPPzBK7/w5vOkJBaVCQZKHXB/v6SIT+WYhnJxlEC61juqG0hBrAf+y3gmSMLFLwICNH9nQ53uscuse6Hg==}
engines: {node: '>=16.0.0', npm: '>=8.0.0'}
peerDependencies:
yjs: ^13.0.0
y-leveldb@0.1.2:
resolution: {integrity: sha512-6ulEn5AXfXJYi89rXPEg2mMHAyyw8+ZfeMMdOtBbV8FJpQ1NOrcgi6DTAcXof0dap84NjHPT2+9d0rb6cFsjEg==}
peerDependencies:
yjs: ^13.0.0
y-protocols@1.0.7:
resolution: {integrity: sha512-YSVsLoXxO67J6eE/nV4AtFtT3QEotZf5sK5BHxFBXso7VDUT3Tx07IfA6hsu5Q5OmBdMkQVmFZ9QOA7fikWvnw==}
engines: {node: '>=16.0.0', npm: '>=8.0.0'}
peerDependencies:
yjs: ^13.0.0
y-websocket@2.1.0:
resolution: {integrity: sha512-WHYDRqomaGkkaujtowCDwL8KYk+t1zQCGIgKyvxvchhjTQlMgWXRHJK+FDEcWmHA7I7o/4fy0eniOrtmz0e4mA==}
engines: {node: '>=16.0.0', npm: '>=8.0.0'}
hasBin: true
peerDependencies:
yjs: ^13.5.6
yallist@3.1.1:
resolution: {integrity: sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==}
@ -4618,6 +4771,10 @@ packages:
engines: {node: '>= 14.6'}
hasBin: true
yjs@13.6.31:
resolution: {integrity: sha512-Eq+5BRfbeGyqGVrTJL3bEcr8gKkxPuyuoHmAwpk52fDb8kOVMrfVSTRPd6yiGgX5Fskb96qCRjzjbRjrL4YEnw==}
engines: {node: '>=16.0.0', npm: '>=8.0.0'}
yocto-queue@0.1.0:
resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==}
engines: {node: '>=10'}
@ -6334,6 +6491,24 @@ snapshots:
dependencies:
event-target-shim: 5.0.1
abstract-leveldown@6.2.3:
dependencies:
buffer: 5.7.1
immediate: 3.3.0
level-concat-iterator: 2.0.1
level-supports: 1.0.1
xtend: 4.0.2
optional: true
abstract-leveldown@6.3.0:
dependencies:
buffer: 5.7.1
immediate: 3.3.0
level-concat-iterator: 2.0.1
level-supports: 1.0.1
xtend: 4.0.2
optional: true
abstract-logging@2.0.1: {}
acorn-jsx@5.3.2(acorn@8.17.0):
@ -6462,6 +6637,9 @@ snapshots:
async-function@1.0.0: {}
async-limiter@1.0.1:
optional: true
asynckit@0.4.0: {}
atomic-sleep@1.0.0: {}
@ -6479,6 +6657,9 @@ snapshots:
balanced-match@4.0.4: {}
base64-js@1.5.1:
optional: true
baseline-browser-mapping@2.10.43: {}
bn.js@4.12.5: {}
@ -6504,6 +6685,12 @@ snapshots:
node-releases: 2.0.51
update-browserslist-db: 1.2.3(browserslist@4.28.6)
buffer@5.7.1:
dependencies:
base64-js: 1.5.1
ieee754: 1.2.1
optional: true
c12@3.1.0:
dependencies:
chokidar: 4.0.3
@ -6824,6 +7011,12 @@ snapshots:
deepmerge-ts@7.1.5: {}
deferred-leveldown@5.3.0:
dependencies:
abstract-leveldown: 6.2.3
inherits: 2.0.4
optional: true
define-data-property@1.1.4:
dependencies:
es-define-property: 1.0.1
@ -6898,6 +7091,14 @@ snapshots:
empathic@2.0.0: {}
encoding-down@6.3.0:
dependencies:
abstract-leveldown: 6.3.0
inherits: 2.0.4
level-codec: 9.0.2
level-errors: 2.0.1
optional: true
end-of-stream@1.4.5:
dependencies:
once: 1.4.0
@ -6913,6 +7114,11 @@ snapshots:
environment@1.1.0: {}
errno@0.1.8:
dependencies:
prr: 1.0.1
optional: true
es-abstract-get@1.0.0:
dependencies:
es-errors: 1.3.0
@ -7492,10 +7698,16 @@ snapshots:
dependencies:
safer-buffer: 2.1.2
ieee754@1.2.1:
optional: true
ignore@5.3.2: {}
ignore@7.0.6: {}
immediate@3.3.0:
optional: true
import-fresh@3.3.1:
dependencies:
parent-module: 1.0.1
@ -7655,6 +7867,8 @@ snapshots:
isexe@2.0.0: {}
isomorphic.js@0.2.5: {}
iterator.prototype@1.1.5:
dependencies:
define-data-property: 1.1.4
@ -7745,11 +7959,77 @@ snapshots:
dependencies:
json-buffer: 3.0.1
level-codec@9.0.2:
dependencies:
buffer: 5.7.1
optional: true
level-concat-iterator@2.0.1:
optional: true
level-errors@2.0.1:
dependencies:
errno: 0.1.8
optional: true
level-iterator-stream@4.0.2:
dependencies:
inherits: 2.0.4
readable-stream: 3.6.2
xtend: 4.0.2
optional: true
level-js@5.0.2:
dependencies:
abstract-leveldown: 6.2.3
buffer: 5.7.1
inherits: 2.0.4
ltgt: 2.2.1
optional: true
level-packager@5.1.1:
dependencies:
encoding-down: 6.3.0
levelup: 4.4.0
optional: true
level-supports@1.0.1:
dependencies:
xtend: 4.0.2
optional: true
level@6.0.1:
dependencies:
level-js: 5.0.2
level-packager: 5.1.1
leveldown: 5.6.0
optional: true
leveldown@5.6.0:
dependencies:
abstract-leveldown: 6.2.3
napi-macros: 2.0.0
node-gyp-build: 4.1.1
optional: true
levelup@4.4.0:
dependencies:
deferred-leveldown: 5.3.0
level-errors: 2.0.1
level-iterator-stream: 4.0.2
level-supports: 1.0.1
xtend: 4.0.2
optional: true
levn@0.4.1:
dependencies:
prelude-ls: 1.2.1
type-check: 0.4.0
lib0@0.2.117:
dependencies:
isomorphic.js: 0.2.5
light-my-request@6.6.0:
dependencies:
cookie: 1.1.1
@ -7841,6 +8121,8 @@ snapshots:
dependencies:
p-locate: 5.0.0
lodash.debounce@4.0.8: {}
lodash.merge@4.6.2: {}
log-update@6.1.0:
@ -7871,6 +8153,9 @@ snapshots:
dependencies:
yallist: 3.1.1
ltgt@2.2.1:
optional: true
lucide-react@0.510.0(react@19.2.7):
dependencies:
react: 19.2.7
@ -7937,6 +8222,9 @@ snapshots:
nanoid@3.3.16: {}
napi-macros@2.0.0:
optional: true
natural-compare@1.4.0: {}
node-domexception@1.0.0: {}
@ -7954,6 +8242,9 @@ snapshots:
dependencies:
whatwg-url: 5.0.0
node-gyp-build@4.1.1:
optional: true
node-releases@2.0.51: {}
npm-run-path@5.3.0:
@ -8285,6 +8576,9 @@ snapshots:
prosemirror-state: 1.4.4
prosemirror-transform: 1.12.0
prr@1.0.1:
optional: true
pump@3.0.4:
dependencies:
end-of-stream: 1.4.5
@ -9016,6 +9310,11 @@ snapshots:
wrappy@1.0.2: {}
ws@6.2.6:
dependencies:
async-limiter: 1.0.1
optional: true
ws@8.21.1: {}
xml-name-validator@5.0.0: {}
@ -9024,10 +9323,44 @@ snapshots:
xtend@4.0.2: {}
y-indexeddb@9.0.12(yjs@13.6.31):
dependencies:
lib0: 0.2.117
yjs: 13.6.31
y-leveldb@0.1.2(yjs@13.6.31):
dependencies:
level: 6.0.1
lib0: 0.2.117
yjs: 13.6.31
optional: true
y-protocols@1.0.7(yjs@13.6.31):
dependencies:
lib0: 0.2.117
yjs: 13.6.31
y-websocket@2.1.0(yjs@13.6.31):
dependencies:
lib0: 0.2.117
lodash.debounce: 4.0.8
y-protocols: 1.0.7(yjs@13.6.31)
yjs: 13.6.31
optionalDependencies:
ws: 6.2.6
y-leveldb: 0.1.2(yjs@13.6.31)
transitivePeerDependencies:
- bufferutil
- utf-8-validate
yallist@3.1.1: {}
yaml@2.9.0: {}
yjs@13.6.31:
dependencies:
lib0: 0.2.117
yocto-queue@0.1.0: {}
zod@3.25.76: {}

View File

@ -6,4 +6,5 @@ allowBuilds:
'@prisma/client': true
'@prisma/engines': true
esbuild: true
leveldown: true
prisma: true