Permissions Required
diff --git a/apps/desktop/src/services/notes-service.ts b/apps/desktop/src/services/notes-service.ts
new file mode 100644
index 0000000..26a2edf
--- /dev/null
+++ b/apps/desktop/src/services/notes-service.ts
@@ -0,0 +1,193 @@
+import * as cron from "node-cron";
+import {
+ createNote,
+ getNotes,
+ getNoteById,
+ updateNote,
+ deleteNote,
+ saveYjsUpdate as saveYjsUpdateToDB,
+ loadYjsUpdates as loadYjsUpdatesFromDB,
+ getUniqueNoteIds,
+ getYjsUpdatesByNoteId,
+ replaceYjsUpdates,
+} from "../db/notes";
+import * as Y from "yjs";
+import { logger } from "../main/logger";
+
+export interface NoteCreateOptions {
+ title: string;
+ initialContent?: string;
+ icon?: string | null;
+}
+
+export interface NoteUpdateOptions {
+ title?: string;
+ transcriptionId?: number | null;
+ icon?: string | null;
+}
+
+class NotesService {
+ private static instance: NotesService;
+ private compactionTask: cron.ScheduledTask | null = null;
+
+ private constructor() {
+ // Set up cron job for daily compaction
+ this.setupCompactionCron();
+ }
+
+ public static getInstance(): NotesService {
+ if (!NotesService.instance) {
+ NotesService.instance = new NotesService();
+ }
+ return NotesService.instance;
+ }
+
+ async createNote(options: NoteCreateOptions) {
+ // Create the note in the database
+ const note = await createNote({
+ title: options.title,
+ icon: options.icon,
+ });
+
+ // Initialize yjs document with initial content if provided
+ if (options.initialContent) {
+ const ydoc = new Y.Doc();
+ const text = ydoc.getText("content");
+ text.insert(0, options.initialContent);
+
+ // Save initial content as a YJS update
+ const initialUpdate = Y.encodeStateAsUpdate(ydoc);
+ await saveYjsUpdateToDB(note.id, initialUpdate);
+ }
+
+ return note;
+ }
+
+ async getNote(id: number) {
+ const note = await getNoteById(id);
+ return note;
+ }
+
+ async listNotes(options?: {
+ limit?: number;
+ offset?: number;
+ sortBy?: "title" | "updatedAt" | "createdAt";
+ sortOrder?: "asc" | "desc";
+ search?: string;
+ transcriptionId?: number | null;
+ }) {
+ return await getNotes(options);
+ }
+
+ async updateNote(id: number, options: NoteUpdateOptions) {
+ return await updateNote(id, options);
+ }
+
+ async deleteNote(id: number) {
+ const note = await getNoteById(id);
+ if (!note) return null;
+
+ return await deleteNote(id);
+ }
+
+ // Save yjs update to database
+ async saveYjsUpdate(noteId: number, update: Uint8Array) {
+ await saveYjsUpdateToDB(noteId, update);
+ }
+
+ // Load all yjs updates for a note
+ async loadYjsUpdates(noteId: number): Promise {
+ return await loadYjsUpdatesFromDB(noteId);
+ }
+
+ // Compact all note documents
+ async compactAllNotes(): Promise {
+ const startTime = Date.now();
+ logger.main.info("Starting yjs compaction for all notes");
+
+ try {
+ // Get all unique note IDs that have updates
+ const noteIds = await getUniqueNoteIds();
+ logger.main.info(`Found ${noteIds.length} notes to compact`);
+
+ let totalUpdatesBefore = 0;
+ let totalUpdatesAfter = 0;
+
+ for (const noteId of noteIds) {
+ const compactResult = await this.compactNote(noteId);
+ totalUpdatesBefore += compactResult.updatesBefore;
+ totalUpdatesAfter += compactResult.updatesAfter;
+ }
+
+ const duration = Date.now() - startTime;
+ logger.main.info(`Compaction completed in ${duration}ms`, {
+ notesCompacted: noteIds.length,
+ totalUpdatesBefore,
+ totalUpdatesAfter,
+ updatesReduced: totalUpdatesBefore - totalUpdatesAfter,
+ });
+ } catch (error) {
+ logger.main.error("Failed to compact notes:", error);
+ }
+ }
+
+ // Compact a specific note
+ async compactNote(
+ noteId: number,
+ ): Promise<{ updatesBefore: number; updatesAfter: number }> {
+ // Get all updates for this note
+ const updates = await getYjsUpdatesByNoteId(noteId);
+ const updatesBefore = updates.length;
+
+ if (updatesBefore <= 1) {
+ // No need to compact if there's only one update or none
+ return { updatesBefore, updatesAfter: updatesBefore };
+ }
+
+ // Create a new Y.Doc and apply all updates
+ const ydoc = new Y.Doc();
+ for (const update of updates) {
+ const updateArray = new Uint8Array(update.updateData as Buffer);
+ Y.applyUpdate(ydoc, updateArray);
+ }
+
+ // Encode the current state as a single update
+ const stateUpdate = Y.encodeStateAsUpdate(ydoc);
+
+ // Replace all updates with the compacted one
+ await replaceYjsUpdates(noteId, stateUpdate);
+
+ logger.main.debug(
+ `Compacted note ${noteId}: ${updatesBefore} updates -> 1 update`,
+ );
+
+ return { updatesBefore, updatesAfter: 1 };
+ }
+
+ // Set up cron job for scheduled compaction
+ private setupCompactionCron() {
+ // Schedule for daily at 2 AM in production, every 5 minutes in development
+ const schedule =
+ process.env.NODE_ENV === "development" ? "*/5 * * * *" : "0 2 * * *";
+
+ this.compactionTask = cron.schedule(schedule, async () => {
+ logger.main.info(
+ `Running scheduled yjs compaction (schedule: ${schedule})`,
+ );
+ await this.compactAllNotes();
+ });
+
+ logger.main.info(`Yjs compaction cron job scheduled: ${schedule}`);
+ }
+
+ // Clean up resources
+ cleanup() {
+ // Stop the cron job
+ if (this.compactionTask) {
+ this.compactionTask.stop();
+ this.compactionTask = null;
+ }
+ }
+}
+
+export default NotesService;
diff --git a/apps/desktop/src/services/transcription-service.ts b/apps/desktop/src/services/transcription-service.ts
index 284ba5e..c0b7711 100644
--- a/apps/desktop/src/services/transcription-service.ts
+++ b/apps/desktop/src/services/transcription-service.ts
@@ -413,14 +413,13 @@ export class TranscriptionService {
? completionTime - session.recordingStartedAt
: undefined;
- const selectedModel =
- this.modelManagerService.getSelectedModel() || "unknown";
+ const selectedModel = await this.modelManagerService.getSelectedModel();
const audioDurationSeconds =
session.context.sharedData.audioMetadata?.duration;
this.telemetryService.trackTranscriptionCompleted({
session_id: sessionId,
- model_id: selectedModel,
+ model_id: selectedModel!,
model_preloaded: this.modelWasPreloaded,
total_duration_ms: totalDuration || 0,
recording_duration_ms: recordingDuration,
diff --git a/apps/desktop/src/trpc/router.ts b/apps/desktop/src/trpc/router.ts
index 63868b7..cf09cab 100644
--- a/apps/desktop/src/trpc/router.ts
+++ b/apps/desktop/src/trpc/router.ts
@@ -6,6 +6,7 @@ import { settingsRouter } from "./routers/settings";
import { updaterRouter } from "./routers/updater";
import { recordingRouter } from "./routers/recording";
import { widgetRouter } from "./routers/widget";
+import { notesRouter } from "./routers/notes";
import { createRouter, procedure } from "./trpc";
export const router = createRouter({
@@ -53,6 +54,9 @@ export const router = createRouter({
// Widget router
widget: widgetRouter,
+
+ // Notes router
+ notes: notesRouter,
});
export type AppRouter = typeof router;
diff --git a/apps/desktop/src/trpc/routers/notes.ts b/apps/desktop/src/trpc/routers/notes.ts
new file mode 100644
index 0000000..579e21e
--- /dev/null
+++ b/apps/desktop/src/trpc/routers/notes.ts
@@ -0,0 +1,104 @@
+import { z } from "zod";
+import { createRouter, procedure } from "../trpc";
+import NotesService from "../../services/notes-service";
+
+const notesService = NotesService.getInstance();
+
+// Input schemas
+const GetNotesSchema = z.object({
+ limit: z.number().optional().default(50),
+ offset: z.number().optional().default(0),
+ sortBy: z
+ .enum(["title", "updatedAt", "createdAt"])
+ .optional()
+ .default("updatedAt"),
+ sortOrder: z.enum(["asc", "desc"]).optional().default("desc"),
+ search: z.string().optional(),
+ transcriptionId: z.number().nullable().optional(),
+});
+
+const CreateNoteSchema = z.object({
+ title: z.string().min(1),
+ initialContent: z.string().optional(),
+ icon: z.string().nullish(),
+});
+
+const UpdateNoteTitleSchema = z.object({
+ id: z.number(),
+ title: z.string().min(1),
+});
+
+const UpdateNoteIconSchema = z.object({
+ id: z.number(),
+ icon: z.string().nullish(),
+});
+
+export const notesRouter = createRouter({
+ // Get all notes
+ getNotes: procedure.input(GetNotesSchema).query(async ({ input }) => {
+ return await notesService.listNotes({
+ limit: input.limit,
+ offset: input.offset,
+ sortBy: input.sortBy,
+ sortOrder: input.sortOrder,
+ search: input.search,
+ transcriptionId: input.transcriptionId,
+ });
+ }),
+
+ // Get note by ID
+ getNoteById: procedure
+ .input(z.object({ id: z.number() }))
+ .query(async ({ input }) => {
+ const note = await notesService.getNote(input.id);
+ if (!note) {
+ throw new Error("Note not found");
+ }
+ return note;
+ }),
+
+ // Create new note
+ createNote: procedure.input(CreateNoteSchema).mutation(async ({ input }) => {
+ return await notesService.createNote({
+ title: input.title,
+ initialContent: input.initialContent || "",
+ icon: input.icon,
+ });
+ }),
+
+ // Update note title
+ updateNoteTitle: procedure
+ .input(UpdateNoteTitleSchema)
+ .mutation(async ({ input }) => {
+ const updated = await notesService.updateNote(input.id, {
+ title: input.title,
+ });
+ if (!updated) {
+ throw new Error("Failed to update note");
+ }
+ return updated;
+ }),
+
+ updateNoteIcon: procedure
+ .input(UpdateNoteIconSchema)
+ .mutation(async ({ input }) => {
+ const updated = await notesService.updateNote(input.id, {
+ icon: input.icon,
+ });
+ if (!updated) {
+ throw new Error("Failed to update note");
+ }
+ return updated;
+ }),
+
+ // Delete note
+ deleteNote: procedure
+ .input(z.object({ id: z.number() }))
+ .mutation(async ({ input }) => {
+ const deleted = await notesService.deleteNote(input.id);
+ if (!deleted) {
+ throw new Error("Note not found");
+ }
+ return { success: true };
+ }),
+});
diff --git a/apps/desktop/src/types/electron-api.ts b/apps/desktop/src/types/electron-api.ts
index a4357b5..2a1a127 100644
--- a/apps/desktop/src/types/electron-api.ts
+++ b/apps/desktop/src/types/electron-api.ts
@@ -37,4 +37,10 @@ export interface ElectronAPI {
// External link handling
openExternal: (url: string) => Promise;
+
+ // Notes API - Yjs synchronization only
+ notes: {
+ saveYjsUpdate: (noteId: number, update: ArrayBuffer) => Promise;
+ loadYjsUpdates: (noteId: number) => Promise;
+ };
}
diff --git a/apps/desktop/src/utils/meeting-icons.tsx b/apps/desktop/src/utils/meeting-icons.tsx
new file mode 100644
index 0000000..a31025d
--- /dev/null
+++ b/apps/desktop/src/utils/meeting-icons.tsx
@@ -0,0 +1,98 @@
+import React from "react";
+import { Calendar } from "lucide-react";
+
+interface MeetingIconProps {
+ className?: string;
+}
+
+// Helper function to determine meeting platform from URL
+export function getMeetingPlatform(url: string): string {
+ const hostname = new URL(url).hostname.toLowerCase();
+
+ // Remove www. prefix if present
+ const cleanHostname = hostname.replace(/^www\./, "");
+
+ switch (true) {
+ case cleanHostname.includes("zoom.us"):
+ case cleanHostname.includes("zoom.com"):
+ return "zoom";
+
+ case cleanHostname.includes("meet.google.com"):
+ case cleanHostname.includes("meets.google.com"):
+ return "google-meet";
+
+ case cleanHostname.includes("teams.microsoft.com"):
+ case cleanHostname.includes("teams.live.com"):
+ return "microsoft-teams";
+
+ case cleanHostname.includes("discord.com"):
+ case cleanHostname.includes("discord.gg"):
+ return "discord";
+
+ case cleanHostname.includes("webex.com"):
+ return "webex";
+
+ case cleanHostname.includes("gotomeeting.com"):
+ return "gotomeeting";
+
+ case cleanHostname.includes("bluejeans.com"):
+ return "bluejeans";
+
+ case cleanHostname.includes("whereby.com"):
+ return "whereby";
+
+ default:
+ return "default";
+ }
+}
+
+// Component to render the appropriate meeting icon
+export function getMeetingIcon(
+ url: string,
+ props: MeetingIconProps = {},
+): React.ReactElement {
+ const { className = "w-4 h-4" } = props;
+ const platform = getMeetingPlatform(url);
+
+ switch (platform) {
+ case "zoom":
+ return
;
+
+ case "google-meet":
+ return (
+
+ );
+
+ case "discord":
+ return (
+
+ );
+
+ // Add more platforms as needed when you have their icons
+ case "microsoft-teams":
+ case "webex":
+ case "gotomeeting":
+ case "bluejeans":
+ case "whereby":
+ default:
+ // Fallback to calendar icon for unknown platforms
+ return ;
+ }
+}
+
+// Export the platforms for potential use elsewhere
+export const SUPPORTED_PLATFORMS = {
+ ZOOM: "zoom",
+ GOOGLE_MEET: "google-meet",
+ MICROSOFT_TEAMS: "microsoft-teams",
+ DISCORD: "discord",
+ WEBEX: "webex",
+ GOTOMEETING: "gotomeeting",
+ BLUEJEANS: "bluejeans",
+ WHEREBY: "whereby",
+ DEFAULT: "default",
+} as const;
diff --git a/packages/y-libsql/package.json b/packages/y-libsql/package.json
new file mode 100644
index 0000000..6243050
--- /dev/null
+++ b/packages/y-libsql/package.json
@@ -0,0 +1,24 @@
+{
+ "name": "@amical/y-libsql",
+ "version": "0.1.0",
+ "description": "LibSQL persistence provider for Yjs",
+ "main": "dist/index.js",
+ "types": "dist/index.d.ts",
+ "scripts": {
+ "build": "tsc",
+ "dev": "tsc --watch",
+ "clean": "rm -rf dist"
+ },
+ "dependencies": {
+ "@libsql/client": "^0.15.9",
+ "yjs": "^13.6.18",
+ "minimatch": "10.0.3"
+ },
+ "devDependencies": {
+ "@types/node": "^20.14.9",
+ "typescript": "^5.5.2"
+ },
+ "peerDependencies": {
+ "yjs": "^13.6.0"
+ }
+}
diff --git a/packages/y-libsql/src/index.ts b/packages/y-libsql/src/index.ts
new file mode 100644
index 0000000..c5d4e5e
--- /dev/null
+++ b/packages/y-libsql/src/index.ts
@@ -0,0 +1,240 @@
+import * as Y from "yjs";
+import { Client, createClient } from "@libsql/client";
+
+export interface LibSQLPersistenceOptions {
+ url?: string;
+ authToken?: string;
+ client?: Client;
+}
+
+export class LibSQLPersistence {
+ private doc: Y.Doc;
+ private docName: string;
+ private client: Client;
+ private _synced: boolean = false;
+ private _destroyed: boolean = false;
+ private whenSynced: Promise;
+ private _resolveSynced!: () => void;
+ private _storeUpdateHandler: (update: Uint8Array, origin: any) => void;
+ private meta: Map = new Map();
+
+ constructor(
+ docName: string,
+ ydoc: Y.Doc,
+ options: LibSQLPersistenceOptions = {},
+ ) {
+ this.doc = ydoc;
+ this.docName = docName;
+
+ // Initialize client
+ if (options.client) {
+ this.client = options.client;
+ } else if (options.url) {
+ this.client = createClient({
+ url: options.url,
+ authToken: options.authToken,
+ });
+ } else {
+ // Default to local file
+ this.client = createClient({
+ url: "file:local.db",
+ });
+ }
+
+ // Create promise for sync status
+ this.whenSynced = new Promise((resolve) => {
+ this._resolveSynced = resolve;
+ });
+
+ // Bind the update handler
+ this._storeUpdateHandler = this._storeUpdate.bind(this);
+
+ // Initialize the database and load existing data
+ this._initialize();
+ }
+
+ private async _initialize() {
+ try {
+ // Create tables if they don't exist
+ await this._createTables();
+
+ // Load existing updates
+ await this._loadUpdates();
+
+ // Listen for document updates
+ this.doc.on("update", this._storeUpdateHandler);
+
+ // Mark as synced
+ this._synced = true;
+ this._resolveSynced();
+ } catch (error) {
+ console.error("Failed to initialize LibSQLPersistence:", error);
+ throw error;
+ }
+ }
+
+ private async _createTables() {
+ // Create updates table
+ await this.client.execute(`
+ CREATE TABLE IF NOT EXISTS yjs_updates (
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
+ doc_name TEXT NOT NULL,
+ update_data BLOB NOT NULL,
+ created_at INTEGER DEFAULT (unixepoch()),
+ INDEX idx_doc_name (doc_name)
+ )
+ `);
+
+ // Create metadata table
+ await this.client.execute(`
+ CREATE TABLE IF NOT EXISTS yjs_metadata (
+ doc_name TEXT NOT NULL,
+ key TEXT NOT NULL,
+ value TEXT,
+ PRIMARY KEY (doc_name, key)
+ )
+ `);
+ }
+
+ private async _loadUpdates() {
+ // Fetch all updates for this document
+ const result = await this.client.execute({
+ sql: "SELECT update_data FROM yjs_updates WHERE doc_name = ? ORDER BY id",
+ args: [this.docName],
+ });
+
+ if (result.rows.length > 0) {
+ // Apply updates to the document
+ Y.transact(
+ this.doc,
+ () => {
+ for (const row of result.rows) {
+ const updateData = row.update_data;
+ if (updateData instanceof ArrayBuffer) {
+ Y.applyUpdate(this.doc, new Uint8Array(updateData), this);
+ } else if (typeof updateData === "string") {
+ // Handle base64 encoded data
+ const binaryString = atob(updateData);
+ const bytes = new Uint8Array(binaryString.length);
+ for (let i = 0; i < binaryString.length; i++) {
+ bytes[i] = binaryString.charCodeAt(i);
+ }
+ Y.applyUpdate(this.doc, bytes, this);
+ }
+ }
+ },
+ this,
+ );
+ }
+
+ // Load metadata
+ const metaResult = await this.client.execute({
+ sql: "SELECT key, value FROM yjs_metadata WHERE doc_name = ?",
+ args: [this.docName],
+ });
+
+ for (const row of metaResult.rows) {
+ const key = row.key as string;
+ const value = row.value as string;
+ try {
+ this.meta.set(key, JSON.parse(value));
+ } catch {
+ this.meta.set(key, value);
+ }
+ }
+ }
+
+ private async _storeUpdate(update: Uint8Array, origin: any) {
+ // Don't store updates that originated from this provider
+ if (origin === this || this._destroyed) {
+ return;
+ }
+
+ try {
+ // Convert Uint8Array to base64 for storage
+ const base64Update = btoa(String.fromCharCode(...update));
+
+ await this.client.execute({
+ sql: "INSERT INTO yjs_updates (doc_name, update_data) VALUES (?, ?)",
+ args: [this.docName, base64Update],
+ });
+ } catch (error) {
+ console.error("Failed to store update:", error);
+ }
+ }
+
+ async set(key: string, value: any): Promise {
+ this.meta.set(key, value);
+ const jsonValue = JSON.stringify(value);
+
+ await this.client.execute({
+ sql: `
+ INSERT INTO yjs_metadata (doc_name, key, value)
+ VALUES (?, ?, ?)
+ ON CONFLICT(doc_name, key)
+ DO UPDATE SET value = excluded.value
+ `,
+ args: [this.docName, key, jsonValue],
+ });
+ }
+
+ get(key: string): any {
+ return this.meta.get(key);
+ }
+
+ async del(key: string): Promise {
+ this.meta.delete(key);
+
+ await this.client.execute({
+ sql: "DELETE FROM yjs_metadata WHERE doc_name = ? AND key = ?",
+ args: [this.docName, key],
+ });
+ }
+
+ async clearData(): Promise {
+ // Clear all data for this document
+ await this.client.execute({
+ sql: "DELETE FROM yjs_updates WHERE doc_name = ?",
+ args: [this.docName],
+ });
+
+ await this.client.execute({
+ sql: "DELETE FROM yjs_metadata WHERE doc_name = ?",
+ args: [this.docName],
+ });
+
+ this.meta.clear();
+ }
+
+ async compactUpdates(): Promise {
+ // Get the current state as a single update
+ const stateUpdate = Y.encodeStateAsUpdate(this.doc);
+
+ // Clear old updates
+ await this.client.execute({
+ sql: "DELETE FROM yjs_updates WHERE doc_name = ?",
+ args: [this.docName],
+ });
+
+ // Store the compacted update
+ const base64Update = btoa(String.fromCharCode(...stateUpdate));
+ await this.client.execute({
+ sql: "INSERT INTO yjs_updates (doc_name, update_data) VALUES (?, ?)",
+ args: [this.docName, base64Update],
+ });
+ }
+
+ destroy(): void {
+ if (this._destroyed) return;
+
+ this._destroyed = true;
+ this.doc.off("update", this._storeUpdateHandler);
+ }
+
+ get synced(): boolean {
+ return this._synced;
+ }
+}
+
+// Export for convenience
+export { Y };
diff --git a/packages/y-libsql/tsconfig.json b/packages/y-libsql/tsconfig.json
new file mode 100644
index 0000000..31af5e6
--- /dev/null
+++ b/packages/y-libsql/tsconfig.json
@@ -0,0 +1,19 @@
+{
+ "compilerOptions": {
+ "target": "ES2020",
+ "module": "ESNext",
+ "lib": ["ES2020"],
+ "outDir": "./dist",
+ "rootDir": "./src",
+ "strict": true,
+ "esModuleInterop": true,
+ "skipLibCheck": true,
+ "forceConsistentCasingInFileNames": true,
+ "declaration": true,
+ "declarationMap": true,
+ "sourceMap": true,
+ "moduleResolution": "node"
+ },
+ "include": ["src/**/*"],
+ "exclude": ["node_modules", "dist"]
+}
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 570fa79..ab31361 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -35,6 +35,9 @@ importers:
'@amical/types':
specifier: workspace:*
version: link:../../packages/types
+ '@amical/y-libsql':
+ specifier: workspace:*
+ version: link:../../packages/y-libsql
'@dnd-kit/core':
specifier: ^6.3.1
version: 6.3.1(react-dom@19.1.1(react@19.1.1))(react@19.1.1)
@@ -212,6 +215,9 @@ importers:
embla-carousel-react:
specifier: ^8.6.0
version: 8.6.0(react@19.1.1)
+ emoji-picker-react:
+ specifier: ^4.13.3
+ version: 4.13.3(react@19.1.1)
framer-motion:
specifier: ^12.10.5
version: 12.23.12(react-dom@19.1.1(react@19.1.1))(react@19.1.1)
@@ -233,6 +239,9 @@ importers:
next-themes:
specifier: ^0.4.6
version: 0.4.6(react-dom@19.1.1(react@19.1.1))(react@19.1.1)
+ node-cron:
+ specifier: ^4.2.1
+ version: 4.2.1
node-machine-id:
specifier: ^1.1.12
version: 1.1.12
@@ -293,6 +302,9 @@ importers:
workerpool:
specifier: ^9.3.3
version: 9.3.3
+ yjs:
+ specifier: ^13.6.27
+ version: 13.6.27
zod:
specifier: ^3.25.24
version: 3.25.76
@@ -665,6 +677,25 @@ importers:
specifier: 5.8.2
version: 5.8.2
+ packages/y-libsql:
+ dependencies:
+ '@libsql/client':
+ specifier: ^0.15.9
+ version: 0.15.12
+ minimatch:
+ specifier: 10.0.3
+ version: 10.0.3
+ yjs:
+ specifier: ^13.6.18
+ version: 13.6.27
+ devDependencies:
+ '@types/node':
+ specifier: ^20.14.9
+ version: 20.19.11
+ typescript:
+ specifier: ^5.5.2
+ version: 5.8.3
+
packages:
'@ai-sdk/openai@1.3.24':
@@ -3996,6 +4027,9 @@ packages:
'@types/node@18.19.123':
resolution: {integrity: sha512-K7DIaHnh0mzVxreCR9qwgNxp3MH9dltPNIEddW9MYUlcKAzm+3grKNSTe2vCJHI1FaLpvpL5JGJrz1UZDKYvDg==}
+ '@types/node@20.19.11':
+ resolution: {integrity: sha512-uug3FEEGv0r+jrecvUUpbY8lLisvIjg6AAic6a2bSP5OEOLeJsDSnvhCDov7ipFFMXS3orMpzlmi0ZcuGkBbow==}
+
'@types/node@22.15.12':
resolution: {integrity: sha512-K0fpC/ZVeb8G9rm7bH7vI0KAec4XHEhBam616nVJCV51bKzJ6oA3luG4WdKoaztxe70QaNjS/xBmcDLmr4PiGw==}
@@ -5477,6 +5511,12 @@ packages:
resolution: {integrity: sha512-DeWwawk6r5yR9jFgnDKYt4sLS0LmHJJi3ZOnb5/JdbYwj3nW+FxQnHIjhBKz8YLC7oRNPVM9NQ47I3CVx34eqQ==}
engines: {node: '>=12'}
+ emoji-picker-react@4.13.3:
+ resolution: {integrity: sha512-aZaxCI72oUQfvZtYuQ9RaYLEwmH3GVgAr5SEeB97Y7gWL06zJ4VTuSl8rAMVY7GNmd0tf/EQ1W2SDuXTl0q9AA==}
+ engines: {node: '>=10'}
+ peerDependencies:
+ react: '>=16'
+
emoji-regex@8.0.0:
resolution: {integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==}
@@ -6057,6 +6097,9 @@ packages:
fix-dts-default-cjs-exports@1.0.1:
resolution: {integrity: sha512-pVIECanWFC61Hzl2+oOCtoJ3F17kglZC/6N94eRWycFgBH35hHx0Li604ZIzhseh97mf2p0cv7vVrOZGoqhlEg==}
+ flairup@1.0.0:
+ resolution: {integrity: sha512-IKlE+pNvL2R+kVL1kEhUYqRxVqeFnjiIvHWDMLFXNaqyUdFXQM2wte44EfMYJNHkW16X991t2Zg8apKkhv7OBA==}
+
flat-cache@1.3.4:
resolution: {integrity: sha512-VwyB3Lkgacfik2vhqR4uv2rvebqmDvFu4jlN/C1RzWoJEo8I7z4Q404oiqYCkq41mni8EzQnm95emU9seckwtg==}
engines: {node: '>=0.10.0'}
@@ -6903,6 +6946,9 @@ packages:
isexe@2.0.0:
resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==}
+ isomorphic.js@0.2.5:
+ resolution: {integrity: sha512-PIeMbHqMt4DnUP3MA/Flc0HElYjMXArsw1qwJZcm9sqR8mq3l8NYizFMty0pWwE/tzIGH3EKK5+jes5mAr85yw==}
+
istanbul-lib-coverage@3.2.2:
resolution: {integrity: sha512-O8dpsF+r0WV/8MNRKfnmrtCWhuKjxrq2w+jpzBL5UZKTi2LeVWnWOmWRxFlesJONmc+wLAGvKQZEOanko0LFTg==}
engines: {node: '>=8'}
@@ -7209,6 +7255,11 @@ packages:
resolution: {integrity: sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==}
engines: {node: '>= 0.8.0'}
+ lib0@0.2.114:
+ resolution: {integrity: sha512-gcxmNFzA4hv8UYi8j43uPlQ7CGcyMJ2KQb5kZASw6SnAKAf10hK12i2fjrS3Cl/ugZa5Ui6WwIu1/6MIXiHttQ==}
+ engines: {node: '>=16'}
+ hasBin: true
+
libsql@0.5.17:
resolution: {integrity: sha512-RRlj5XQI9+Wq+/5UY8EnugSWfRmHEw4hn3DKlPrkUgZONsge1PwTtHcpStP6MSNi8ohcbsRgEHJaymA33a8cBw==}
cpu: [x64, arm64, wasm32, arm]
@@ -7868,6 +7919,10 @@ packages:
node-api-version@0.2.1:
resolution: {integrity: sha512-2xP/IGGMmmSQpI1+O/k72jF/ykvZ89JeuKX3TLJAYPDVLUalrshrLHkeVcCCZqG/eEa635cr8IBYzgnDvM2O8Q==}
+ node-cron@4.2.1:
+ resolution: {integrity: sha512-lgimEHPE/QDgFlywTd8yTR61ptugX3Qer29efeyWw2rv259HtGBNn1vZVmp8lB9uo9wC0t/AT4iGqXxia+CJFg==}
+ engines: {node: '>=6.0.0'}
+
node-domexception@1.0.0:
resolution: {integrity: sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ==}
engines: {node: '>=10.5.0'}
@@ -10180,6 +10235,10 @@ packages:
yauzl@2.10.0:
resolution: {integrity: sha512-p4a9I6X6nu6IhoGmBqAcbJy1mlC4j27vEPZX9F4L4/vZT3Lyq1VkFHw/V/PUcB9Buo+DG3iHkT0x3Qya58zc3g==}
+ yjs@13.6.27:
+ resolution: {integrity: sha512-OIDwaflOaq4wC6YlPBy2L6ceKeKuF7DeTxx+jPzv1FHn9tCZ0ZwSRnUBxD05E3yed46fv/FWJbvR+Ud7x0L7zw==}
+ engines: {node: '>=16.0.0', npm: '>=8.0.0'}
+
yn@3.1.1:
resolution: {integrity: sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==}
engines: {node: '>=6'}
@@ -14119,6 +14178,10 @@ snapshots:
dependencies:
undici-types: 5.26.5
+ '@types/node@20.19.11':
+ dependencies:
+ undici-types: 6.21.0
+
'@types/node@22.15.12':
dependencies:
undici-types: 6.21.0
@@ -15646,6 +15709,11 @@ snapshots:
emittery@0.13.1: {}
+ emoji-picker-react@4.13.3(react@19.1.1):
+ dependencies:
+ flairup: 1.0.0
+ react: 19.1.1
+
emoji-regex@8.0.0: {}
emoji-regex@9.2.2: {}
@@ -16588,6 +16656,8 @@ snapshots:
mlly: 1.7.4
rollup: 4.47.1
+ flairup@1.0.0: {}
+
flat-cache@1.3.4:
dependencies:
circular-json: 0.3.3
@@ -17624,6 +17694,8 @@ snapshots:
isexe@2.0.0: {}
+ isomorphic.js@0.2.5: {}
+
istanbul-lib-coverage@3.2.2: {}
istanbul-lib-instrument@5.2.1:
@@ -18117,6 +18189,10 @@ snapshots:
prelude-ls: 1.2.1
type-check: 0.4.0
+ lib0@0.2.114:
+ dependencies:
+ isomorphic.js: 0.2.5
+
libsql@0.5.17:
dependencies:
'@neon-rs/load': 0.0.4
@@ -19029,6 +19105,8 @@ snapshots:
dependencies:
semver: 7.7.2
+ node-cron@4.2.1: {}
+
node-domexception@1.0.0: {}
node-fetch-native@1.6.7: {}
@@ -21736,6 +21814,10 @@ snapshots:
buffer-crc32: 0.2.13
fd-slicer: 1.1.0
+ yjs@13.6.27:
+ dependencies:
+ lib0: 0.2.114
+
yn@3.1.1: {}
yocto-queue@0.1.0: {}