diff --git a/docs/documents.md b/docs/documents.md new file mode 100644 index 00000000000..8272d98e1dd --- /dev/null +++ b/docs/documents.md @@ -0,0 +1,190 @@ +# Documents + +Notion-like document management for Paperclip. Every document is created from an issue — agents edit docs by commenting on the source issue. + +## How It Works + +1. **Create an issue** for the document (e.g. "Write Q1 health report") +2. **Assign the issue** to an agent +3. **Create a document** from that issue (UI → Documents → New Document → select issue) +4. The agent works on the doc by **commenting on the source issue** +5. Comments appear in the **issue sidebar** on the doc detail page in real time + +This means agents don't need to learn a new workflow — they already know how to work with issues and comments. + +## Data Model + +### Documents + +| Field | Type | Description | +|-------|------|-------------| +| id | uuid | Primary key | +| companyId | uuid | Company scope (required) | +| projectId | uuid | Optional project association | +| issueId | uuid | **Required** — the source issue that created this doc | +| title | text | Document title | +| content | jsonb | Tiptap/Novel JSON document format | +| createdByAgentId | uuid | Agent that created the doc (nullable) | +| createdByUserId | text | User that created the doc (nullable) | +| createdAt | timestamp | Creation time | +| updatedAt | timestamp | Last update time | + +### Document Links (many-to-many) + +- **document_goals** — link documents to goals (`documentId`, `goalId`, unique constraint) +- **document_issues** — link documents to issues (`documentId`, `issueId`, unique constraint) + +When a document is created, the source issue is automatically linked via `document_issues`. + +## API + +### List documents +``` +GET /api/companies/:companyId/documents +``` +Query params: `?projectId=X`, `?goalId=Y`, `?issueId=Z` + +### Create document +``` +POST /api/companies/:companyId/documents +{ + "title": "Q1 Health Report", + "issueId": "uuid-of-source-issue", // required + "projectId": "uuid-of-project", // optional + "content": {} // optional, tiptap JSON +} +``` +The source issue is automatically linked via `document_issues`. + +### Get document +``` +GET /api/documents/:id +``` +Returns the document with linked goals and issues. + +### Update document +``` +PATCH /api/documents/:id +{ + "title": "Updated Title", // optional + "content": { ... }, // optional, tiptap JSON + "projectId": "uuid-or-null" // optional +} +``` + +### Delete document +``` +DELETE /api/documents/:id +``` + +### Link/unlink goals +``` +POST /api/documents/:id/goals +{ "goalId": "uuid" } + +DELETE /api/documents/:id/goals/:goalId +``` + +### Link/unlink issues +``` +POST /api/documents/:id/issues +{ "issueId": "uuid" } + +DELETE /api/documents/:id/issues/:issueId +``` + +## UI + +### Document List (`/documents`) +- Grouped by project +- Shows title, last updated date +- "New Document" button opens an issue picker — select which issue this doc is for + +### Document Detail (`/documents/:id`) +- **Left side:** Novel.sh rich text editor with auto-save + - Editable title + - Project selector dropdown + - Goal linker (multi-select tags) +- **Right side:** Issue sidebar + - Source issue title, identifier, status, priority, assignee + - Issue description + - Live comments feed (polls every 15s) + - Agents respond to the issue → comments appear here in real time + +### Linked Documents +On issue detail and goal detail pages, a "Linked Documents" section shows all documents linked to that issue/goal. + +### Get document by issue +``` +GET /api/issues/:issueId/document +``` +Returns the document linked to a specific issue. Useful for agents to find their doc from their assigned task. + +## Agent Workflow + +Agents interact with documents in two ways: + +### 1. Direct document editing (primary) +Agents can read and write document content directly via the API: + +```bash +# Find the document for your assigned issue +GET /api/issues/{issueId}/document +Authorization: Bearer $PAPERCLIP_API_KEY + +# Read the current content +GET /api/documents/{docId} +Authorization: Bearer $PAPERCLIP_API_KEY + +# Update the document content directly +PATCH /api/documents/{docId} +Authorization: Bearer $PAPERCLIP_API_KEY +{ "content": { "type": "doc", "content": [...tiptap JSON...] } } + +# Update just the title +PATCH /api/documents/{docId} +Authorization: Bearer $PAPERCLIP_API_KEY +{ "title": "March Finance Report — Final" } +``` + +### 2. Comment-based collaboration +Agents can also discuss changes via the source issue's comment thread: + +1. Board creates issue: "Write the family finance summary for March" +2. Board creates document from that issue +3. Agent is assigned the issue +4. Agent fetches the doc via `GET /api/issues/{issueId}/document` +5. Agent edits the content via `PATCH /api/documents/{docId}` +6. Agent posts a comment on the issue: "Updated the doc with March expenses" +7. Board sees the comment in the doc sidebar, reviews the changes +8. Board comments "Add the Polymarket PnL section" +9. Agent wakes up, reads the comment, edits the doc again + +### Agent document editing flow (recommended) + +``` +1. GET /api/issues/{issueId}/document → get the doc +2. Read doc.content (tiptap JSON) → understand current state +3. Modify the content → make your edits +4. PATCH /api/documents/{docId} { content } → save changes +5. POST /api/issues/{issueId}/comments → tell the board what you changed +``` + +### Creating documents (agents) + +```bash +POST /api/companies/{companyId}/documents +Authorization: Bearer $PAPERCLIP_API_KEY +{ + "title": "March Finance Report", + "issueId": "{issueId}" +} +``` + +## Editor + +Uses [Novel.sh](https://novel.sh/) — a Notion-style WYSIWYG editor built on Tiptap. Supports: +- Headings, paragraphs, lists +- Bold, italic, code +- Slash commands +- Auto-save (debounced PATCH on every change) diff --git a/packages/db/src/migrations/0028_documents.sql b/packages/db/src/migrations/0028_documents.sql new file mode 100644 index 00000000000..4f3c441726c --- /dev/null +++ b/packages/db/src/migrations/0028_documents.sql @@ -0,0 +1,90 @@ +CREATE TABLE IF NOT EXISTS "documents" ( + "id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL, + "company_id" uuid NOT NULL, + "project_id" uuid, + "issue_id" uuid NOT NULL, + "title" text NOT NULL, + "content" jsonb DEFAULT '{}'::jsonb, + "created_by_agent_id" uuid, + "created_by_user_id" text, + "created_at" timestamp with time zone DEFAULT now() NOT NULL, + "updated_at" timestamp with time zone DEFAULT now() NOT NULL +); +--> statement-breakpoint +CREATE TABLE IF NOT EXISTS "document_goals" ( + "id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL, + "document_id" uuid NOT NULL, + "goal_id" uuid NOT NULL +); +--> statement-breakpoint +CREATE TABLE IF NOT EXISTS "document_issues" ( + "id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL, + "document_id" uuid NOT NULL, + "issue_id" uuid NOT NULL +); +--> statement-breakpoint +DO $$ BEGIN + ALTER TABLE "documents" ADD CONSTRAINT "documents_company_id_companies_id_fk" FOREIGN KEY ("company_id") REFERENCES "public"."companies"("id") ON DELETE no action ON UPDATE no action; +EXCEPTION + WHEN duplicate_object THEN null; +END $$; +--> statement-breakpoint +DO $$ BEGIN + ALTER TABLE "documents" ADD CONSTRAINT "documents_project_id_projects_id_fk" FOREIGN KEY ("project_id") REFERENCES "public"."projects"("id") ON DELETE no action ON UPDATE no action; +EXCEPTION + WHEN duplicate_object THEN null; +END $$; +--> statement-breakpoint +DO $$ BEGIN + ALTER TABLE "documents" ADD CONSTRAINT "documents_issue_id_issues_id_fk" FOREIGN KEY ("issue_id") REFERENCES "public"."issues"("id") ON DELETE no action ON UPDATE no action; +EXCEPTION + WHEN duplicate_object THEN null; +END $$; +--> statement-breakpoint +DO $$ BEGIN + ALTER TABLE "documents" ADD CONSTRAINT "documents_created_by_agent_id_agents_id_fk" FOREIGN KEY ("created_by_agent_id") REFERENCES "public"."agents"("id") ON DELETE no action ON UPDATE no action; +EXCEPTION + WHEN duplicate_object THEN null; +END $$; +--> statement-breakpoint +DO $$ BEGIN + ALTER TABLE "document_goals" ADD CONSTRAINT "document_goals_document_id_documents_id_fk" FOREIGN KEY ("document_id") REFERENCES "public"."documents"("id") ON DELETE cascade ON UPDATE no action; +EXCEPTION + WHEN duplicate_object THEN null; +END $$; +--> statement-breakpoint +DO $$ BEGIN + ALTER TABLE "document_goals" ADD CONSTRAINT "document_goals_goal_id_goals_id_fk" FOREIGN KEY ("goal_id") REFERENCES "public"."goals"("id") ON DELETE cascade ON UPDATE no action; +EXCEPTION + WHEN duplicate_object THEN null; +END $$; +--> statement-breakpoint +DO $$ BEGIN + ALTER TABLE "document_issues" ADD CONSTRAINT "document_issues_document_id_documents_id_fk" FOREIGN KEY ("document_id") REFERENCES "public"."documents"("id") ON DELETE cascade ON UPDATE no action; +EXCEPTION + WHEN duplicate_object THEN null; +END $$; +--> statement-breakpoint +DO $$ BEGIN + ALTER TABLE "document_issues" ADD CONSTRAINT "document_issues_issue_id_issues_id_fk" FOREIGN KEY ("issue_id") REFERENCES "public"."issues"("id") ON DELETE cascade ON UPDATE no action; +EXCEPTION + WHEN duplicate_object THEN null; +END $$; +--> statement-breakpoint +CREATE INDEX IF NOT EXISTS "documents_company_idx" ON "documents" USING btree ("company_id"); +--> statement-breakpoint +CREATE INDEX IF NOT EXISTS "documents_project_idx" ON "documents" USING btree ("project_id"); +--> statement-breakpoint +CREATE INDEX IF NOT EXISTS "documents_issue_idx" ON "documents" USING btree ("issue_id"); +--> statement-breakpoint +CREATE INDEX IF NOT EXISTS "document_goals_document_idx" ON "document_goals" USING btree ("document_id"); +--> statement-breakpoint +CREATE INDEX IF NOT EXISTS "document_goals_goal_idx" ON "document_goals" USING btree ("goal_id"); +--> statement-breakpoint +CREATE UNIQUE INDEX IF NOT EXISTS "document_goals_unique" ON "document_goals" USING btree ("document_id","goal_id"); +--> statement-breakpoint +CREATE INDEX IF NOT EXISTS "document_issues_document_idx" ON "document_issues" USING btree ("document_id"); +--> statement-breakpoint +CREATE INDEX IF NOT EXISTS "document_issues_issue_idx" ON "document_issues" USING btree ("issue_id"); +--> statement-breakpoint +CREATE UNIQUE INDEX IF NOT EXISTS "document_issues_unique" ON "document_issues" USING btree ("document_id","issue_id"); diff --git a/packages/db/src/migrations/meta/_journal.json b/packages/db/src/migrations/meta/_journal.json index 80a1dfbd1de..95be2a16b76 100644 --- a/packages/db/src/migrations/meta/_journal.json +++ b/packages/db/src/migrations/meta/_journal.json @@ -197,6 +197,13 @@ "when": 1773150731736, "tag": "0027_tranquil_tenebrous", "breakpoints": true + }, + { + "idx": 28, + "version": "7", + "when": 1774410300000, + "tag": "0028_documents", + "breakpoints": true } ] } \ No newline at end of file diff --git a/packages/db/src/schema/document_goals.ts b/packages/db/src/schema/document_goals.ts new file mode 100644 index 00000000000..f9ad08ce335 --- /dev/null +++ b/packages/db/src/schema/document_goals.ts @@ -0,0 +1,22 @@ +import { + pgTable, + uuid, + index, + uniqueIndex, +} from "drizzle-orm/pg-core"; +import { documents } from "./documents.js"; +import { goals } from "./goals.js"; + +export const documentGoals = pgTable( + "document_goals", + { + id: uuid("id").primaryKey().defaultRandom(), + documentId: uuid("document_id").notNull().references(() => documents.id, { onDelete: "cascade" }), + goalId: uuid("goal_id").notNull().references(() => goals.id, { onDelete: "cascade" }), + }, + (table) => ({ + documentIdx: index("document_goals_document_idx").on(table.documentId), + goalIdx: index("document_goals_goal_idx").on(table.goalId), + unique: uniqueIndex("document_goals_unique").on(table.documentId, table.goalId), + }), +); diff --git a/packages/db/src/schema/document_issues.ts b/packages/db/src/schema/document_issues.ts new file mode 100644 index 00000000000..150a5f0b0fc --- /dev/null +++ b/packages/db/src/schema/document_issues.ts @@ -0,0 +1,22 @@ +import { + pgTable, + uuid, + index, + uniqueIndex, +} from "drizzle-orm/pg-core"; +import { documents } from "./documents.js"; +import { issues } from "./issues.js"; + +export const documentIssues = pgTable( + "document_issues", + { + id: uuid("id").primaryKey().defaultRandom(), + documentId: uuid("document_id").notNull().references(() => documents.id, { onDelete: "cascade" }), + issueId: uuid("issue_id").notNull().references(() => issues.id, { onDelete: "cascade" }), + }, + (table) => ({ + documentIdx: index("document_issues_document_idx").on(table.documentId), + issueIdx: index("document_issues_issue_idx").on(table.issueId), + unique: uniqueIndex("document_issues_unique").on(table.documentId, table.issueId), + }), +); diff --git a/packages/db/src/schema/documents.ts b/packages/db/src/schema/documents.ts new file mode 100644 index 00000000000..4cda230e43b --- /dev/null +++ b/packages/db/src/schema/documents.ts @@ -0,0 +1,33 @@ +import { + pgTable, + uuid, + text, + timestamp, + jsonb, + index, +} from "drizzle-orm/pg-core"; +import { agents } from "./agents.js"; +import { companies } from "./companies.js"; +import { issues } from "./issues.js"; +import { projects } from "./projects.js"; + +export const documents = pgTable( + "documents", + { + id: uuid("id").primaryKey().defaultRandom(), + companyId: uuid("company_id").notNull().references(() => companies.id), + projectId: uuid("project_id").references(() => projects.id), + issueId: uuid("issue_id").notNull().references(() => issues.id), + title: text("title").notNull(), + content: jsonb("content").default({}), + createdByAgentId: uuid("created_by_agent_id").references(() => agents.id), + createdByUserId: text("created_by_user_id"), + createdAt: timestamp("created_at", { withTimezone: true }).notNull().defaultNow(), + updatedAt: timestamp("updated_at", { withTimezone: true }).notNull().defaultNow(), + }, + (table) => ({ + companyIdx: index("documents_company_idx").on(table.companyId), + projectIdx: index("documents_project_idx").on(table.projectId), + issueIdx: index("documents_issue_idx").on(table.issueId), + }), +); diff --git a/packages/db/src/schema/index.ts b/packages/db/src/schema/index.ts index 3416ea9a046..8347200a012 100644 --- a/packages/db/src/schema/index.ts +++ b/packages/db/src/schema/index.ts @@ -32,3 +32,6 @@ export { approvalComments } from "./approval_comments.js"; export { activityLog } from "./activity_log.js"; export { companySecrets } from "./company_secrets.js"; export { companySecretVersions } from "./company_secret_versions.js"; +export { documents } from "./documents.js"; +export { documentGoals } from "./document_goals.js"; +export { documentIssues } from "./document_issues.js"; diff --git a/packages/shared/src/index.ts b/packages/shared/src/index.ts index 1a222f2778e..cebf80443e9 100644 --- a/packages/shared/src/index.ts +++ b/packages/shared/src/index.ts @@ -63,6 +63,13 @@ export { type PermissionKey, } from "./constants.js"; +export type { + Document, + DocumentGoalLink, + DocumentIssueLink, + DocumentWithLinks, +} from "./types/index.js"; + export type { Company, Agent, @@ -183,6 +190,14 @@ export { updateGoalSchema, type CreateGoal, type UpdateGoal, + createDocumentSchema, + updateDocumentSchema, + linkDocumentGoalSchema, + linkDocumentIssueSchema, + type CreateDocument, + type UpdateDocument, + type LinkDocumentGoal, + type LinkDocumentIssue, createApprovalSchema, resolveApprovalSchema, requestApprovalRevisionSchema, diff --git a/packages/shared/src/types/document.ts b/packages/shared/src/types/document.ts new file mode 100644 index 00000000000..cd841b103ca --- /dev/null +++ b/packages/shared/src/types/document.ts @@ -0,0 +1,29 @@ +export interface Document { + id: string; + companyId: string; + projectId: string | null; + issueId: string; + title: string; + content: Record; + createdByAgentId: string | null; + createdByUserId: string | null; + createdAt: Date; + updatedAt: Date; +} + +export interface DocumentGoalLink { + id: string; + documentId: string; + goalId: string; +} + +export interface DocumentIssueLink { + id: string; + documentId: string; + issueId: string; +} + +export interface DocumentWithLinks extends Document { + goals: DocumentGoalLink[]; + issues: DocumentIssueLink[]; +} diff --git a/packages/shared/src/types/index.ts b/packages/shared/src/types/index.ts index 07862c581e3..5b220ac8152 100644 --- a/packages/shared/src/types/index.ts +++ b/packages/shared/src/types/index.ts @@ -1,4 +1,5 @@ export type { Company } from "./company.js"; +export type { Document, DocumentGoalLink, DocumentIssueLink, DocumentWithLinks } from "./document.js"; export type { Agent, AgentPermissions, diff --git a/packages/shared/src/validators/document.ts b/packages/shared/src/validators/document.ts new file mode 100644 index 00000000000..a718ae5a1ec --- /dev/null +++ b/packages/shared/src/validators/document.ts @@ -0,0 +1,30 @@ +import { z } from "zod"; + +export const createDocumentSchema = z.object({ + title: z.string().min(1), + issueId: z.string().uuid(), + content: z.record(z.unknown()).optional(), + projectId: z.string().uuid().optional().nullable(), +}); + +export type CreateDocument = z.infer; + +export const updateDocumentSchema = z.object({ + title: z.string().min(1).optional(), + content: z.record(z.unknown()).optional(), + projectId: z.string().uuid().optional().nullable(), +}); + +export type UpdateDocument = z.infer; + +export const linkDocumentGoalSchema = z.object({ + goalId: z.string().uuid(), +}); + +export type LinkDocumentGoal = z.infer; + +export const linkDocumentIssueSchema = z.object({ + issueId: z.string().uuid(), +}); + +export type LinkDocumentIssue = z.infer; diff --git a/packages/shared/src/validators/index.ts b/packages/shared/src/validators/index.ts index ad74a1e8700..d14cce295c5 100644 --- a/packages/shared/src/validators/index.ts +++ b/packages/shared/src/validators/index.ts @@ -83,6 +83,17 @@ export { type UpdateGoal, } from "./goal.js"; +export { + createDocumentSchema, + updateDocumentSchema, + linkDocumentGoalSchema, + linkDocumentIssueSchema, + type CreateDocument, + type UpdateDocument, + type LinkDocumentGoal, + type LinkDocumentIssue, +} from "./document.js"; + export { createApprovalSchema, resolveApprovalSchema, diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index f6820f52ec3..89a6f06dbd8 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -433,6 +433,9 @@ importers: mermaid: specifier: ^11.12.0 version: 11.12.3 + novel: + specifier: ^1.0.2 + version: 1.0.2(@babel/core@7.29.0)(@babel/template@7.28.6)(@tiptap/extension-code-block@2.27.2(@tiptap/core@2.27.2(@tiptap/pm@2.27.2))(@tiptap/pm@2.27.2))(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(highlight.js@11.11.1)(lowlight@3.3.0)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) radix-ui: specifier: ^1.4.3 version: 1.4.3(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) @@ -766,6 +769,9 @@ packages: '@braintree/sanitize-url@7.1.2': resolution: {integrity: sha512-jigsZK+sMF/cuiB7sERuo9V7N9jx+dhmHHnQyDSVdpZwVutaBu7WvNYqMDLSgFgfB30n452TP3vjDAvFC973mA==} + '@cfcs/core@0.0.6': + resolution: {integrity: sha512-FxfJMwoLB8MEMConeXUCqtMGqxdtePQxRBOiGip9ULcYYam3WfCgoY6xdnMaSkYvRvmosp5iuG+TiPofm65+Pw==} + '@changesets/apply-release-plan@7.1.0': resolution: {integrity: sha512-yq8ML3YS7koKQ/9bk1PqO0HMzApIFNwjlwCnwFEXMzNe8NpzeeYYKCmnhWJGkN8g7E51MnWaSbqRcTcdIxUgnQ==} @@ -947,6 +953,9 @@ packages: react: ^16.8.0 || ^17 || ^18 || ^19 react-dom: ^16.8.0 || ^17 || ^18 || ^19 + '@daybrush/utils@1.13.0': + resolution: {integrity: sha512-ALK12C6SQNNHw1enXK+UO8bdyQ+jaWNQ1Af7Z3FNxeAwjYhQT7do+TRE4RASAJ3ObaS2+TJ7TXR3oz2Gzbw0PQ==} + '@dnd-kit/accessibility@3.1.1': resolution: {integrity: sha512-2P+YgaXF+gRsIihwwY1gCsQSYnu9Zyj2py8kY5fFvUM1qm2WA2u639R6YNVfU4GWr+ZM5mqEsfHZZLoRONbemw==} peerDependencies: @@ -972,6 +981,18 @@ packages: '@drizzle-team/brocli@0.10.2': resolution: {integrity: sha512-z33Il7l5dKjUgGULTqBsQBQwckHh5AbIuxhdsIxDDiZAzBOrZO6q9ogcWC65kU382AfynTfgNumVcNIjuIua6w==} + '@egjs/agent@2.4.4': + resolution: {integrity: sha512-cvAPSlUILhBBOakn2krdPnOGv5hAZq92f1YHxYcfu0p7uarix2C6Ia3AVizpS1SGRZGiEkIS5E+IVTLg1I2Iog==} + + '@egjs/children-differ@1.0.1': + resolution: {integrity: sha512-DRvyqMf+CPCOzAopQKHtW+X8iN6Hy6SFol+/7zCUiE5y4P/OB8JP8FtU4NxtZwtafvSL4faD5KoQYPj3JHzPFQ==} + + '@egjs/component@3.0.5': + resolution: {integrity: sha512-cLcGizTrrUNA2EYE3MBmEDt2tQv1joVP1Q3oDisZ5nw0MZDx2kcgEXM+/kZpfa/PAkFvYVhRUZwytIQWoN3V/w==} + + '@egjs/list-differ@1.0.1': + resolution: {integrity: sha512-OTFTDQcWS+1ZREOdCWuk5hCBgYO4OsD30lXcOCyVOAjXMhgL5rBRDnt/otb6Nz8CzU0L/igdcaQBDLWc4t9gvg==} + '@electric-sql/pglite@0.3.15': resolution: {integrity: sha512-Cj++n1Mekf9ETfdc16TlDi+cDDQF0W7EcbyRHYOAeZdsAe8M/FJg18itDTSwyHfar2WIezawM9o0EKaRGVKygQ==} @@ -1716,6 +1737,9 @@ packages: engines: {node: '>=18'} hasBin: true + '@popperjs/core@2.11.8': + resolution: {integrity: sha512-P1st0aksCrn9sGZhp8GMYwBnQsbvAWsZAX44oXNNvLHGqAOcoVxmjZiohstwQ7SqKnbR47akdNi+uleWD8+g6A==} + '@radix-ui/colors@3.0.0': resolution: {integrity: sha512-FUOsGBkHrYJwCSEtWRCIfQbZG7q1e6DgxCIOe1SUQzDe/7rXXeA47s8yCn6fuTNQAj1Zq4oTFi9Yjp3wzElcxg==} @@ -2433,6 +2457,9 @@ packages: peerDependencies: react: '>=16.8' + '@remirror/core-constants@3.0.0': + resolution: {integrity: sha512-42aWfPrimMfDKDi4YegyS7x+/0tlzaqwPQCULLanv3DMIlu96KTJR0fM5isWX2UViOqlGnX6YFgqWepcX+XMNg==} + '@rolldown/pluginutils@1.0.0-beta.27': resolution: {integrity: sha512-+d0F4MKMCbeVUJwG96uQ4SgAznZNSq93I3V+9NHA4OpvqG8mRCpGdKmK8l/dl02h2CCDHwW2FqilnTyDcAnqjA==} @@ -2561,6 +2588,15 @@ packages: cpu: [x64] os: [win32] + '@scena/dragscroll@1.4.0': + resolution: {integrity: sha512-3O8daaZD9VXA9CP3dra6xcgt/qrm0mg0xJCwiX6druCteQ9FFsXffkF8PrqxY4Z4VJ58fFKEa0RlKqbsi/XnRA==} + + '@scena/event-emitter@1.0.5': + resolution: {integrity: sha512-AzY4OTb0+7ynefmWFQ6hxDdk0CySAq/D4efljfhtRHCOP7MBF9zUfhKG3TJiroVjASqVgkRJFdenS8ArZo6Olg==} + + '@scena/matrix@1.1.1': + resolution: {integrity: sha512-JVKBhN0tm2Srl+Yt+Ywqu0oLgLcdemDQlD1OxmN9jaCTwaFPZ7tY8n6dhVgMEaR9qcR7r+kAlMXnSfNyYdE+Vg==} + '@smithy/abort-controller@4.2.8': resolution: {integrity: sha512-peuVfkYHAmS5ybKxWcfraK7WBBP0J+rkfUcbHJJKQ4ir3UAUNQI+Y4Vt/PqSzGqgloJ5O1dk7+WzNL8wcCSXbw==} engines: {node: '>=18.0.0'} @@ -2783,6 +2819,9 @@ packages: '@stitches/core@1.2.8': resolution: {integrity: sha512-Gfkvwk9o9kE9r9XNBmJRfV8zONvXThnm1tcuojL04Uy5uRyqg93DC83lDebl0rocZCfKSjUv+fWYtMQmEDJldg==} + '@swc/helpers@0.5.19': + resolution: {integrity: sha512-QamiFeIK3txNjgUTNppE6MiG3p7TdninpZu0E0PbqVh1a9FNLT2FRhisaa4NcaX52XVhA5l7Pk58Ft7Sqi/2sA==} + '@tailwindcss/node@4.1.18': resolution: {integrity: sha512-DoR7U1P7iYhw16qJ49fgXUlry1t4CpXeErJHnQ44JgTSKMaZUdf17cfn5mHchfJ4KRBZRFA/Coo+MUF5+gOaCQ==} @@ -2886,6 +2925,207 @@ packages: peerDependencies: react: ^18 || ^19 + '@tiptap/core@2.27.2': + resolution: {integrity: sha512-ABL1N6eoxzDzC1bYvkMbvyexHacszsKdVPYqhl5GwHLOvpZcv9VE9QaKwDILTyz5voCA0lGcAAXZp+qnXOk5lQ==} + peerDependencies: + '@tiptap/pm': ^2.7.0 + + '@tiptap/extension-blockquote@2.27.2': + resolution: {integrity: sha512-oIGZgiAeA4tG3YxbTDfrmENL4/CIwGuP3THtHsNhwRqwsl9SfMk58Ucopi2GXTQSdYXpRJ0ahE6nPqB5D6j/Zw==} + peerDependencies: + '@tiptap/core': ^2.7.0 + + '@tiptap/extension-bold@2.27.2': + resolution: {integrity: sha512-bR7J5IwjCGQ0s3CIxyMvOCnMFMzIvsc5OVZKscTN5UkXzFsaY6muUAIqtKxayBUucjtUskm5qZowJITCeCb1/A==} + peerDependencies: + '@tiptap/core': ^2.7.0 + + '@tiptap/extension-bubble-menu@2.27.2': + resolution: {integrity: sha512-VkwlCOcr0abTBGzjPXklJ92FCowG7InU8+Od9FyApdLNmn0utRYGRhw0Zno6VgE9EYr1JY4BRnuSa5f9wlR72w==} + peerDependencies: + '@tiptap/core': ^2.7.0 + '@tiptap/pm': ^2.7.0 + + '@tiptap/extension-bullet-list@2.27.2': + resolution: {integrity: sha512-gmFuKi97u5f8uFc/GQs+zmezjiulZmFiDYTh3trVoLRoc2SAHOjGEB7qxdx7dsqmMN7gwiAWAEVurLKIi1lnnw==} + peerDependencies: + '@tiptap/core': ^2.7.0 + + '@tiptap/extension-character-count@2.27.2': + resolution: {integrity: sha512-EcQRIvbLbMDDzo7uFqXYgh1CfgedS9sYX4BllktY2OlXLPdNpwo9t8WMK/a7soESNv0Le3WZ5pNvnNhv7Z2YdA==} + peerDependencies: + '@tiptap/core': ^2.7.0 + '@tiptap/pm': ^2.7.0 + + '@tiptap/extension-code-block-lowlight@2.27.2': + resolution: {integrity: sha512-v6NKStBbQ/XCc1NnCi3ObsL1DsxadSIBtUQNA/B+urkPgn5LEy72HAGlf0xwjRaNkAGSaTASLKmc84L5q5zlGQ==} + peerDependencies: + '@tiptap/core': ^2.7.0 + '@tiptap/extension-code-block': ^2.7.0 + '@tiptap/pm': ^2.7.0 + highlight.js: ^11 + lowlight: ^2 || ^3 + + '@tiptap/extension-code-block@2.27.2': + resolution: {integrity: sha512-KgvdQHS4jXr79aU3wZOGBIZYYl9vCB7uDEuRFV4so2rYrfmiYMw3T8bTnlNEEGe4RUeAms1i4fdwwvQp9nR1Dw==} + peerDependencies: + '@tiptap/core': ^2.7.0 + '@tiptap/pm': ^2.7.0 + + '@tiptap/extension-code@2.27.2': + resolution: {integrity: sha512-7X9AgwqiIGXoZX7uvdHQsGsjILnN/JaEVtqfXZnPECzKGaWHeK/Ao4sYvIIIffsyZJA8k5DC7ny2/0sAgr2TuA==} + peerDependencies: + '@tiptap/core': ^2.7.0 + + '@tiptap/extension-color@2.27.2': + resolution: {integrity: sha512-sOKCP8/2V3sRM3FdWgMe1lFE5ewsWNCRafiVoujS1+TTHGCj4jw6W+LiumBUk7cRI8kXW/rqGWVC4RVdknYUCA==} + peerDependencies: + '@tiptap/core': ^2.7.0 + '@tiptap/extension-text-style': ^2.7.0 + + '@tiptap/extension-document@2.27.2': + resolution: {integrity: sha512-CFhAYsPnyYnosDC4639sCJnBUnYH4Cat9qH5NZWHVvdgtDwu8GZgZn2eSzaKSYXWH1vJ9DSlCK+7UyC3SNXIBA==} + peerDependencies: + '@tiptap/core': ^2.7.0 + + '@tiptap/extension-dropcursor@2.27.2': + resolution: {integrity: sha512-oEu/OrktNoQXq1x29NnH/GOIzQZm8ieTQl3FK27nxfBPA89cNoH4mFEUmBL5/OFIENIjiYG3qWpg6voIqzswNw==} + peerDependencies: + '@tiptap/core': ^2.7.0 + '@tiptap/pm': ^2.7.0 + + '@tiptap/extension-floating-menu@2.27.2': + resolution: {integrity: sha512-GUN6gPIGXS7ngRJOwdSmtBRBDt9Kt9CM/9pSwKebhLJ+honFoNA+Y6IpVyDvvDMdVNgBchiJLs6qA5H97gAePQ==} + peerDependencies: + '@tiptap/core': ^2.7.0 + '@tiptap/pm': ^2.7.0 + + '@tiptap/extension-gapcursor@2.27.2': + resolution: {integrity: sha512-/c9VF1HBxj+AP54XGVgCmD9bEGYc5w5OofYCFQgM7l7PB1J00A4vOke0oPkHJnqnOOyPlFaxO/7N6l3XwFcnKA==} + peerDependencies: + '@tiptap/core': ^2.7.0 + '@tiptap/pm': ^2.7.0 + + '@tiptap/extension-hard-break@2.27.2': + resolution: {integrity: sha512-kSRVGKlCYK6AGR0h8xRkk0WOFGXHIIndod3GKgWU49APuIGDiXd8sziXsSlniUsWmqgDmDXcNnSzPcV7AQ8YNg==} + peerDependencies: + '@tiptap/core': ^2.7.0 + + '@tiptap/extension-heading@2.27.2': + resolution: {integrity: sha512-iM3yeRWuuQR/IRQ1djwNooJGfn9Jts9zF43qZIUf+U2NY8IlvdNsk2wTOdBgh6E0CamrStPxYGuln3ZS4fuglw==} + peerDependencies: + '@tiptap/core': ^2.7.0 + + '@tiptap/extension-highlight@2.27.2': + resolution: {integrity: sha512-ZjlktDdMjruMJFAVz0TbQf0v92Jqkc7Ri1iZJqBXuLid+r+GxUzl2CVAV7qq5yagkGQgvAG+WGsMk880HgR3MA==} + peerDependencies: + '@tiptap/core': ^2.7.0 + + '@tiptap/extension-history@2.27.2': + resolution: {integrity: sha512-+hSyqERoFNTWPiZx4/FCyZ/0eFqB9fuMdTB4AC/q9iwu3RNWAQtlsJg5230bf/qmyO6bZxRUc0k8p4hrV6ybAw==} + peerDependencies: + '@tiptap/core': ^2.7.0 + '@tiptap/pm': ^2.7.0 + + '@tiptap/extension-horizontal-rule@2.27.2': + resolution: {integrity: sha512-WGWUSgX+jCsbtf9Y9OCUUgRZYuwjVoieW5n6mAUohJ9/6gc6sGIOrUpBShf+HHo6WD+gtQjRd+PssmX3NPWMpg==} + peerDependencies: + '@tiptap/core': ^2.7.0 + '@tiptap/pm': ^2.7.0 + + '@tiptap/extension-image@2.27.2': + resolution: {integrity: sha512-5zL/BY41FIt72azVrCrv3n+2YJ/JyO8wxCcA4Dk1eXIobcgVyIdo4rG39gCqIOiqziAsqnqoj12QHTBtHsJ6mQ==} + peerDependencies: + '@tiptap/core': ^2.7.0 + + '@tiptap/extension-italic@2.27.2': + resolution: {integrity: sha512-1OFsw2SZqfaqx5Fa5v90iNlPRcqyt+lVSjBwTDzuPxTPFY4Q0mL89mKgkq2gVHYNCiaRkXvFLDxaSvBWbmthgg==} + peerDependencies: + '@tiptap/core': ^2.7.0 + + '@tiptap/extension-link@2.27.2': + resolution: {integrity: sha512-bnP61qkr0Kj9Cgnop1hxn2zbOCBzNtmawxr92bVTOE31fJv6FhtCnQiD6tuPQVGMYhcmAj7eihtvuEMFfqEPcQ==} + peerDependencies: + '@tiptap/core': ^2.7.0 + '@tiptap/pm': ^2.7.0 + + '@tiptap/extension-list-item@2.27.2': + resolution: {integrity: sha512-eJNee7IEGXMnmygM5SdMGDC8m/lMWmwNGf9fPCK6xk0NxuQRgmZHL6uApKcdH6gyNcRPHCqvTTkhEP7pbny/fg==} + peerDependencies: + '@tiptap/core': ^2.7.0 + + '@tiptap/extension-ordered-list@2.27.2': + resolution: {integrity: sha512-M7A4tLGJcLPYdLC4CI2Gwl8LOrENQW59u3cMVa+KkwG1hzSJyPsbDpa1DI6oXPC2WtYiTf22zrbq3gVvH+KA2w==} + peerDependencies: + '@tiptap/core': ^2.7.0 + + '@tiptap/extension-paragraph@2.27.2': + resolution: {integrity: sha512-elYVn2wHJJ+zB9LESENWOAfI4TNT0jqEN34sMA/hCtA4im1ZG2DdLHwkHIshj/c4H0dzQhmsS/YmNC5Vbqab/A==} + peerDependencies: + '@tiptap/core': ^2.7.0 + + '@tiptap/extension-placeholder@2.27.2': + resolution: {integrity: sha512-IjsgSVYJRjpAKmIoapU0E2R4E2FPY3kpvU7/1i7PUYisylqejSJxmtJPGYw0FOMQY9oxnEEvfZHMBA610tqKpg==} + peerDependencies: + '@tiptap/core': ^2.7.0 + '@tiptap/pm': ^2.7.0 + + '@tiptap/extension-strike@2.27.2': + resolution: {integrity: sha512-HHIjhafLhS2lHgfAsCwC1okqMsQzR4/mkGDm4M583Yftyjri1TNA7lzhzXWRFWiiMfJxKtdjHjUAQaHuteRTZw==} + peerDependencies: + '@tiptap/core': ^2.7.0 + + '@tiptap/extension-task-item@2.27.2': + resolution: {integrity: sha512-ZBSqj/dygB/Rp5K9qOxRVwASTZCmKVoTq8C59KvMgD/aFjJxhq/w2dZaWkCUEXEep+NmvJqo0kfeAEMY5UDnGg==} + peerDependencies: + '@tiptap/core': ^2.7.0 + '@tiptap/pm': ^2.7.0 + + '@tiptap/extension-task-list@2.27.2': + resolution: {integrity: sha512-5nupAewdzZ9F3599oAcaK0WkDH04wdACAVBPM4zG7InlIpkbho3txB7zWmm64OxfhCMIMGKiXY1q0bw9i0QBGQ==} + peerDependencies: + '@tiptap/core': ^2.7.0 + + '@tiptap/extension-text-style@2.27.2': + resolution: {integrity: sha512-Omk+uxjJLyEY69KStpCw5fA9asvV+MGcAX2HOxyISDFoLaL49TMrNjhGAuz09P1L1b0KGXo4ml7Q3v/Lfy4WPA==} + peerDependencies: + '@tiptap/core': ^2.7.0 + + '@tiptap/extension-text@2.27.2': + resolution: {integrity: sha512-Xk7nYcigljAY0GO9hAQpZ65ZCxqOqaAlTPDFcKerXmlkQZP/8ndx95OgUb1Xf63kmPOh3xypurGS2is3v0MXSA==} + peerDependencies: + '@tiptap/core': ^2.7.0 + + '@tiptap/extension-underline@2.27.2': + resolution: {integrity: sha512-gPOsbAcw1S07ezpAISwoO8f0RxpjcSH7VsHEFDVuXm4ODE32nhvSinvHQjv2icRLOXev+bnA7oIBu7Oy859gWQ==} + peerDependencies: + '@tiptap/core': ^2.7.0 + + '@tiptap/extension-youtube@2.27.2': + resolution: {integrity: sha512-3l/tfJ8wO8/tALo1tpAfN7TTJQQ00V52XaYamjQPVzPGelm/ECCfSCGQ4oRv8gbyzjUbZkNpkSV1Bj2V7QcGDg==} + peerDependencies: + '@tiptap/core': ^2.7.0 + + '@tiptap/pm@2.27.2': + resolution: {integrity: sha512-kaEg7BfiJPDQMKbjVIzEPO3wlcA+pZb2tlcK9gPrdDnEFaec2QTF1sXz2ak2IIb2curvnIrQ4yrfHgLlVA72wA==} + + '@tiptap/react@2.27.2': + resolution: {integrity: sha512-0EAs8Cpkfbvben1PZ34JN2Nd79Dhioynm2jML27DBbf1VWPk+FFWFGTMLUT0bu+Np5iVxio8fqV9t0mc4D6thA==} + peerDependencies: + '@tiptap/core': ^2.7.0 + '@tiptap/pm': ^2.7.0 + react: ^17.0.0 || ^18.0.0 || ^19.0.0 + react-dom: ^17.0.0 || ^18.0.0 || ^19.0.0 + + '@tiptap/starter-kit@2.27.2': + resolution: {integrity: sha512-bb0gJvPoDuyRUQ/iuN52j1//EtWWttw+RXAv1uJxfR0uKf8X7uAqzaOOgwjknoCIDC97+1YHwpGdnRjpDkOBxw==} + + '@tiptap/suggestion@2.27.2': + resolution: {integrity: sha512-dQyvCIg0hcAVeh4fCIVCxogvbp+bF+GpbUb8sNlgnGrmHXnapGxzkvrlHnvneXZxLk/j7CxmBPKJNnm4Pbx4zw==} + peerDependencies: + '@tiptap/core': ^2.7.0 + '@tiptap/pm': ^2.7.0 + '@types/babel__core@7.20.5': resolution: {integrity: sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==} @@ -3030,9 +3270,18 @@ packages: '@types/http-errors@2.0.5': resolution: {integrity: sha512-r8Tayk8HJnX0FztbZN7oVqGccWgw98T/0neJphO91KkmOzug1KkofZURD4UaD5uH8AqcFLfdPErnBod0u71/qg==} + '@types/linkify-it@5.0.0': + resolution: {integrity: sha512-sVDA58zAw4eWAffKOaQH5/5j3XeayukzDk+ewSsnv3p4yJEZHCCzMDiZM8e0OUrRvmpGZ85jf4yDHkHsgBNr9Q==} + + '@types/markdown-it@14.1.2': + resolution: {integrity: sha512-promo4eFwuiW+TfGxhi+0x3czqTYJkG8qB17ZUJiVF10Xm7NLVRSLUsfRTU/6h1e24VvRnXCx+hG7li58lkzog==} + '@types/mdast@4.0.4': resolution: {integrity: sha512-kGaNbPh1k7AFzgpud/gMdvIm5xuECykRR+JnWKQno9TAXVa6WIVCGTPvYGekIDL4uwCZQSYbUxNBSb1aUo79oA==} + '@types/mdurl@2.0.0': + resolution: {integrity: sha512-RGdgjQUZba5p6QEFAVx2OGb8rQDL/cPRG7GiedRzMcJ1tYnUANBncjbSB1NRGwbvjcPeikRABz2nshyPk1bhWg==} + '@types/methods@1.1.4': resolution: {integrity: sha512-ymXWVrDiCxTBE3+RIrrP533E70eA+9qu7zdWoHuOmGujkYtzf4HQF96b8nwHLqhuf4ykX61IGRIB38CC6/sImQ==} @@ -3089,6 +3338,9 @@ packages: '@types/unist@3.0.3': resolution: {integrity: sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q==} + '@types/use-sync-external-store@0.0.6': + resolution: {integrity: sha512-zFDAD+tlpf2r4asuHEj0XH6pY6i0g5NeAHPn+15wk3BV6JA69eERFXC1gyGThDkVa1zCyKr5jox1+2LbV/AMLg==} + '@types/ws@8.18.1': resolution: {integrity: sha512-ThVF6DCVhA8kUGy+aazFQ4kXQ7E1Ty7A3ypFOe0IcJV8O/M511G99AW24irKrW56Wt44yG9+ij8FaqoBGkuBXg==} @@ -3470,6 +3722,12 @@ packages: resolution: {integrity: sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==} engines: {node: '>= 8'} + css-styled@1.0.8: + resolution: {integrity: sha512-tCpP7kLRI8dI95rCh3Syl7I+v7PP+2JYOzWkl0bUEoSbJM+u8ITbutjlQVf0NC2/g4ULROJPi16sfwDIO8/84g==} + + css-to-mat@1.1.1: + resolution: {integrity: sha512-kvpxFYZb27jRd2vium35G7q5XZ2WJ9rWjDUMNT36M3Hc41qCrLXFM5iEKMGXcrPsKfXEN+8l/riB4QzwwwiEyQ==} + cssesc@3.0.0: resolution: {integrity: sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==} engines: {node: '>=4'} @@ -3862,6 +4120,10 @@ packages: resolution: {integrity: sha512-rRqJg/6gd538VHvR3PSrdRBb/1Vy2YfzHqzvbhGIQpDRKIa4FgV/54b5Q1xYSxOOwKvjXweS26E0Q+nAMwp2pQ==} engines: {node: '>=8.6'} + entities@4.5.0: + resolution: {integrity: sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==} + engines: {node: '>=0.12'} + es-define-property@1.0.1: resolution: {integrity: sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==} engines: {node: '>= 0.4'} @@ -3922,6 +4184,10 @@ packages: escape-html@1.0.3: resolution: {integrity: sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==} + escape-string-regexp@4.0.0: + resolution: {integrity: sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==} + engines: {node: '>=10'} + escape-string-regexp@5.0.0: resolution: {integrity: sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw==} engines: {node: '>=12'} @@ -3971,6 +4237,9 @@ packages: fast-copy@4.0.2: resolution: {integrity: sha512-ybA6PDXIXOXivLJK/z9e+Otk7ve13I4ckBvGO5I2RRmBU1gMHLVDJYEuJYhGwez7YNlYji2M2DvVU+a9mSFDlw==} + fast-deep-equal@3.1.3: + resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==} + fast-glob@3.3.3: resolution: {integrity: sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==} engines: {node: '>=8.6.0'} @@ -4025,6 +4294,9 @@ packages: resolution: {integrity: sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==} engines: {node: '>= 0.6'} + framework-utils@1.1.0: + resolution: {integrity: sha512-KAfqli5PwpFJ8o3psRNs8svpMGyCSAe8nmGcjQ0zZBWN2H6dZDnq+ABp3N3hdUmFeMrLtjOCTXD4yplUJIWceg==} + fresh@2.0.0: resolution: {integrity: sha512-Rx/WycZ60HOaqLKAi6cHRKKI7zxWbJ31MhntmtwMoaTeF7XFH9hhBp8vITaMidfljRQ6eYWCKkaTK+ykVJHP2A==} engines: {node: '>= 0.8'} @@ -4054,6 +4326,9 @@ packages: resolution: {integrity: sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==} engines: {node: '>=6.9.0'} + gesto@1.19.4: + resolution: {integrity: sha512-hfr/0dWwh0Bnbb88s3QVJd1ZRJeOWcgHPPwmiH6NnafDYvhTsxg+SLYu+q/oPNh9JS3V+nlr6fNs8kvPAtcRDQ==} + get-caller-file@2.0.5: resolution: {integrity: sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==} engines: {node: 6.* || 8.* || >= 10.*} @@ -4112,6 +4387,10 @@ packages: help-me@5.0.0: resolution: {integrity: sha512-7xgomUX6ADmcYzFik0HzAxh/73YlKR9bmFzf51CZwR+b6YtzU2m0u49hQCqV6SvlqIqsaxovfwdvbnsw3b/zpg==} + highlight.js@11.11.1: + resolution: {integrity: sha512-Xwwo44whKBVCYoliBQwaPvtd/2tYFkRQtXDWj1nackaV2JPXx3L0+Jvd8/qCJ2p+ML0/XVkJ2q+Mr+UVdpJK5w==} + engines: {node: '>=12.0.0'} + html-url-attributes@3.0.1: resolution: {integrity: sha512-ol6UPyBWqsrO6EJySPz2O7ZSr856WDrEzM5zMqp+FJJLGMW35cLYmmZnl0vztAZxRUoNZJFTCohfjuIJ8I4QBQ==} @@ -4229,6 +4508,24 @@ packages: jose@6.1.3: resolution: {integrity: sha512-0TpaTfihd4QMNwrz/ob2Bp7X04yuxJkjRGi4aKmOqwhov54i6u79oCv7T+C7lo70MKH6BesI3vscD1yb/yzKXQ==} + jotai@2.18.1: + resolution: {integrity: sha512-e0NOzK+yRFwHo7DOp0DS0Ycq74KMEAObDWFGmfEL28PD9nLqBTt3/Ug7jf9ca72x0gC9LQZG9zH+0ISICmy3iA==} + engines: {node: '>=12.20.0'} + peerDependencies: + '@babel/core': '>=7.0.0' + '@babel/template': '>=7.0.0' + '@types/react': '>=17.0.0' + react: '>=17.0.0' + peerDependenciesMeta: + '@babel/core': + optional: true + '@babel/template': + optional: true + '@types/react': + optional: true + react: + optional: true + joycon@3.1.1: resolution: {integrity: sha512-34wB/Y7MW7bzjKRjUKTa46I2Z7eV62Rkhva+KkopW7Qvv/OSWBqvkSY7vusOPrNuZcUG3tApvdVgNB8POj3SPw==} engines: {node: '>=10'} @@ -4264,6 +4561,12 @@ packages: resolution: {integrity: sha512-TIGjO2cCGYono+uUzgkE7RFF329mLLWGuHUlSr6cwIVj9O8f0VQZ783rsanmJpFUo32vvtj7XT04NGRPh+SZFg==} hasBin: true + keycode@2.2.1: + resolution: {integrity: sha512-Rdgz9Hl9Iv4QKi8b0OlCRQEzp4AgVxyCtz5S/+VIHezDmrDhkp2N2TqBWOLz0/gbeREXOOiI9/4b8BY9uw2vFg==} + + keycon@1.4.0: + resolution: {integrity: sha512-p1NAIxiRMH3jYfTeXRs2uWbVJ1WpEjpi8ktzUyBJsX7/wn2qu2VRXktneBLNtKNxJmlUYxRi9gOJt1DuthXR7A==} + khroma@2.1.0: resolution: {integrity: sha512-Ls993zuzfayK269Svk9hzpeGUKob/sIgZzyHYdjQoAdQetRKpOLj+k/QQQ/6Qi0Yz65mlROrfd+Ev+1+7dz9Kw==} @@ -4363,6 +4666,12 @@ packages: resolution: {integrity: sha512-utfs7Pr5uJyyvDETitgsaqSyjCb2qNRAtuqUeWIAKztsOYdcACf2KtARYXg2pSvhkt+9NfoaNY7fxjl6nuMjIQ==} engines: {node: '>= 12.0.0'} + linkify-it@5.0.0: + resolution: {integrity: sha512-5aHCbzQRADcdP+ATqnDuhhJ/MRIqDkZX5pyjFHRRysS8vZ5AbqGEoFIb6pYHPZ+L/OC2Lc+xT8uHVVR5CAK/wQ==} + + linkifyjs@4.3.2: + resolution: {integrity: sha512-NT1CJtq3hHIreOianA8aSXn6Cw0JzYOuDQbOrSPe7gqFnCpKP++MQe3ODgO3oh2GJFORkAAdqredOa60z63GbA==} + locate-path@5.0.0: resolution: {integrity: sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==} engines: {node: '>=8'} @@ -4383,6 +4692,9 @@ packages: loupe@3.2.1: resolution: {integrity: sha512-CdzqowRJCeLU72bHvWqwRBBlLcMEtIvGrlvef74kMnV2AolS9Y8xUv1I0U/MNAWMhBlKIoyuEgoJ0t/bbwHbLQ==} + lowlight@3.3.0: + resolution: {integrity: sha512-0JNhgFoPvP6U6lE/UdVsSq99tn6DhjjpAj5MxG49ewd2mOBVtwWYIT8ClyABhq198aXXODMU6Ox8DrGy/CpTZQ==} + lru-cache@5.1.1: resolution: {integrity: sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==} @@ -4398,6 +4710,10 @@ packages: magic-string@0.30.21: resolution: {integrity: sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==} + markdown-it@14.1.1: + resolution: {integrity: sha512-BuU2qnTti9YKgK5N+IeMubp14ZUKUUw7yeJbkjtosvHiP0AZ5c8IAgEMk79D0eC8F23r4Ac/q8cAIFdm2FtyoA==} + hasBin: true + markdown-table@3.0.4: resolution: {integrity: sha512-wiYz4+JrLyb/DqW2hkFJxP7Vd7JuTDm77fvbM8VfEQdmSMqcImWeeRbHwZjBjIFki/VaMK2BhFi7oUUZeM5bqw==} @@ -4467,6 +4783,9 @@ packages: mdast-util-to-string@4.0.0: resolution: {integrity: sha512-0H44vDimn51F0YwvxSJSm0eCDOJTRlmN0R1yBh4HLj9wiV1Dn0QoXGbvFAWj2hSItVTlCmBF1hqKlIyUBVFLPg==} + mdurl@2.0.0: + resolution: {integrity: sha512-Lf+9+2r+Tdp5wXDXC4PcIBjTDtq4UKjCPMQhKIuzpJNW0b96kVqSwW0bT7FhRSfmAiFYgP+SCRvdrDozfh0U5w==} + media-typer@0.3.0: resolution: {integrity: sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==} engines: {node: '>= 0.6'} @@ -4669,6 +4988,11 @@ packages: node-releases@2.0.27: resolution: {integrity: sha512-nmh3lCkYZ3grZvqcCH+fjmQ7X+H0OeZgP40OierEaAptX4XofMh5kwNbWh7lBduUzCcV/8kZ+NDLCwm2iorIlA==} + novel@1.0.2: + resolution: {integrity: sha512-lyMtoBsRCqgrQaNhlc8Ngpp+npJEQjPoGBLcnYlEr8mEf+lXZV7/m6CbEpGRfma+HZQVlU3YJOs4gCmzbLG+ow==} + peerDependencies: + react: '>=18' + object-assign@4.1.1: resolution: {integrity: sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==} engines: {node: '>=0.10.0'} @@ -4692,12 +5016,18 @@ packages: resolution: {integrity: sha512-smsWv2LzFjP03xmvFoJ331ss6h+jixfA4UUV/Bsiyuu4YJPfN+FIQGOIiv4w9/+MoHkfkJ22UIaQWRVFRfH6Vw==} engines: {node: '>=20'} + orderedmap@2.1.1: + resolution: {integrity: sha512-TvAWxi0nDe1j/rtMcWcIj94+Ffe6n7zhow33h40SKxmsmozs6dz/e+EajymfoFcHd7sxNn8yHM8839uixMOV6g==} + outdent@0.5.0: resolution: {integrity: sha512-/jHxFIzoMXdqPzTaCpFzAAWhpkSjZPF4Vsn6jAfNpmbH/ymsmd7Qc6VE9BGn0L6YMj6uwpQLxCECpus4ukKS9Q==} outvariant@1.4.0: resolution: {integrity: sha512-AlWY719RF02ujitly7Kk/0QlV+pXGFDHrHf9O2OKqyqgBieaPOIeuSkL8sRK6j2WK+/ZAURq2kZsY0d8JapUiw==} + overlap-area@1.1.0: + resolution: {integrity: sha512-3dlJgJCaVeXH0/eZjYVJvQiLVVrPO4U1ZGqlATtx6QGO3b5eNM6+JgUKa7oStBTdYuGTk7gVoABCW6Tp+dhRdw==} + p-filter@2.1.0: resolution: {integrity: sha512-ZBxxZ5sL2HghephhpGAQdoskxplTwr7ICaehZwLIlfL6acuVgZPm8yBNuRAFBGEqtD/hmUeq9eqLg2ys9Xr/yw==} engines: {node: '>=8'} @@ -4894,6 +5224,64 @@ packages: property-information@7.1.0: resolution: {integrity: sha512-TwEZ+X+yCJmYfL7TPUOcvBZ4QfoT5YenQiJuX//0th53DE6w0xxLEtfK3iyryQFddXuvkIk51EEgrJQ0WJkOmQ==} + prosemirror-changeset@2.4.0: + resolution: {integrity: sha512-LvqH2v7Q2SF6yxatuPP2e8vSUKS/L+xAU7dPDC4RMyHMhZoGDfBC74mYuyYF4gLqOEG758wajtyhNnsTkuhvng==} + + prosemirror-collab@1.3.1: + resolution: {integrity: sha512-4SnynYR9TTYaQVXd/ieUvsVV4PDMBzrq2xPUWutHivDuOshZXqQ5rGbZM84HEaXKbLdItse7weMGOUdDVcLKEQ==} + + prosemirror-commands@1.7.1: + resolution: {integrity: sha512-rT7qZnQtx5c0/y/KlYaGvtG411S97UaL6gdp6RIZ23DLHanMYLyfGBV5DtSnZdthQql7W+lEVbpSfwtO8T+L2w==} + + prosemirror-dropcursor@1.8.2: + resolution: {integrity: sha512-CCk6Gyx9+Tt2sbYk5NK0nB1ukHi2ryaRgadV/LvyNuO3ena1payM2z6Cg0vO1ebK8cxbzo41ku2DE5Axj1Zuiw==} + + prosemirror-gapcursor@1.4.1: + resolution: {integrity: sha512-pMdYaEnjNMSwl11yjEGtgTmLkR08m/Vl+Jj443167p9eB3HVQKhYCc4gmHVDsLPODfZfjr/MmirsdyZziXbQKw==} + + prosemirror-history@1.5.0: + resolution: {integrity: sha512-zlzTiH01eKA55UAf1MEjtssJeHnGxO0j4K4Dpx+gnmX9n+SHNlDqI2oO1Kv1iPN5B1dm5fsljCfqKF9nFL6HRg==} + + prosemirror-inputrules@1.5.1: + resolution: {integrity: sha512-7wj4uMjKaXWAQ1CDgxNzNtR9AlsuwzHfdFH1ygEHA2KHF2DOEaXl1CJfNPAKCg9qNEh4rum975QLaCiQPyY6Fw==} + + prosemirror-keymap@1.2.3: + resolution: {integrity: sha512-4HucRlpiLd1IPQQXNqeo81BGtkY8Ai5smHhKW9jjPKRc2wQIxksg7Hl1tTI2IfT2B/LgX6bfYvXxEpJl7aKYKw==} + + prosemirror-markdown@1.13.4: + resolution: {integrity: sha512-D98dm4cQ3Hs6EmjK500TdAOew4Z03EV71ajEFiWra3Upr7diytJsjF4mPV2dW+eK5uNectiRj0xFxYI9NLXDbw==} + + prosemirror-menu@1.3.0: + resolution: {integrity: sha512-TImyPXCHPcDsSka2/lwJ6WjTASr4re/qWq1yoTTuLOqfXucwF6VcRa2LWCkM/EyTD1UO3CUwiH8qURJoWJRxwg==} + + prosemirror-model@1.25.4: + resolution: {integrity: sha512-PIM7E43PBxKce8OQeezAs9j4TP+5yDpZVbuurd1h5phUxEKIu+G2a+EUZzIC5nS1mJktDJWzbqS23n1tsAf5QA==} + + prosemirror-schema-basic@1.2.4: + resolution: {integrity: sha512-ELxP4TlX3yr2v5rM7Sb70SqStq5NvI15c0j9j/gjsrO5vaw+fnnpovCLEGIcpeGfifkuqJwl4fon6b+KdrODYQ==} + + prosemirror-schema-list@1.5.1: + resolution: {integrity: sha512-927lFx/uwyQaGwJxLWCZRkjXG0p48KpMj6ueoYiu4JX05GGuGcgzAy62dfiV8eFZftgyBUvLx76RsMe20fJl+Q==} + + prosemirror-state@1.4.4: + resolution: {integrity: sha512-6jiYHH2CIGbCfnxdHbXZ12gySFY/fz/ulZE333G6bPqIZ4F+TXo9ifiR86nAHpWnfoNjOb3o5ESi7J8Uz1jXHw==} + + prosemirror-tables@1.8.5: + resolution: {integrity: sha512-V/0cDCsHKHe/tfWkeCmthNUcEp1IVO3p6vwN8XtwE9PZQLAZJigbw3QoraAdfJPir4NKJtNvOB8oYGKRl+t0Dw==} + + prosemirror-trailing-node@3.0.0: + resolution: {integrity: sha512-xiun5/3q0w5eRnGYfNlW1uU9W6x5MoFKWwq/0TIRgt09lv7Hcser2QYV8t4muXbEr+Fwo0geYn79Xs4GKywrRQ==} + peerDependencies: + prosemirror-model: ^1.22.1 + prosemirror-state: ^1.4.2 + prosemirror-view: ^1.33.8 + + prosemirror-transform@1.11.0: + resolution: {integrity: sha512-4I7Ce4KpygXb9bkiPS3hTEk4dSHorfRw8uI0pE8IhxlK2GXsqv5tIA7JUSxtSu7u8APVOTtbUBxTmnHIxVkIJw==} + + prosemirror-view@1.41.7: + resolution: {integrity: sha512-jUwKNCEIGiqdvhlS91/2QAg21e4dfU5bH2iwmSDQeosXJgKF7smG0YSplOWK0cjSNgIqXe7VXqo7EIfUFJdt3w==} + proxy-addr@2.0.7: resolution: {integrity: sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==} engines: {node: '>= 0.10'} @@ -4901,6 +5289,10 @@ packages: pump@3.0.3: resolution: {integrity: sha512-todwxLMY7/heScKmntwQG8CXVkWUOdYxIvY2s0VWAAMh/nd8SoYiRaKjlr7+iCs984f2P8zvrfWcDDYVb73NfA==} + punycode.js@2.3.1: + resolution: {integrity: sha512-uxFIHU0YlHYhDQtV4R9J6a52SLx28BCjT+4ieh7IGbgwVJWO+km431c4yRlREUAsAmt/uMjQUyQHNEPf0M39CA==} + engines: {node: '>=6'} + qs@6.15.0: resolution: {integrity: sha512-mAZTtNCeetKMH+pSjrb76NAM8V9a05I9aBZOHztWy/UqcJdQYNsf59vrRKWnojAT9Y+GbIvoTBC++CPHqpDBhQ==} engines: {node: '>=0.6'} @@ -4935,6 +5327,9 @@ packages: resolution: {integrity: sha512-K5zQjDllxWkf7Z5xJdV0/B0WTNqx6vxG70zJE4N0kBs4LovmEYWJzQGxC9bS9RAKu3bgM40lrd5zoLJ12MQ5BA==} engines: {node: '>= 0.10'} + react-css-styled@1.1.9: + resolution: {integrity: sha512-M7fJZ3IWFaIHcZEkoFOnkjdiUFmwd8d+gTh2bpqMOcnxy/0Gsykw4dsL4QBiKsxcGow6tETUa4NAUcmJF+/nfw==} + react-devtools-inline@4.4.0: resolution: {integrity: sha512-ES0GolSrKO8wsKbsEkVeiR/ZAaHQTY4zDh1UW8DImVmm8oaGLl3ijJDvSGe+qDRKPZdPRnDtWWnSvvrgxXdThQ==} @@ -4967,6 +5362,15 @@ packages: '@types/react': '>=18' react: '>=18' + react-markdown@9.1.0: + resolution: {integrity: sha512-xaijuJB0kzGiUdG7nc2MOMDUDBWPyGAjZtUrow9XxUeua8IqeP+VlIfAZ3bphpcLTnSZXz6z9jcVC/TCwbfgdw==} + peerDependencies: + '@types/react': '>=18' + react: '>=18' + + react-moveable@0.56.0: + resolution: {integrity: sha512-FmJNmIOsOA36mdxbrc/huiE4wuXSRlmon/o+/OrfNhSiYYYL0AV5oObtPluEhb2Yr/7EfYWBHTxF5aWAvjg1SA==} + react-refresh@0.17.0: resolution: {integrity: sha512-z6F7K9bV85EfseRCp2bzrpyQ0Gkw1uLoCel9XBVWPg/TjRj94SkJzUTGfOa4bs7iJvBWtQG0Wq7wnI0syw3EBQ==} engines: {node: '>=0.10.0'} @@ -5008,6 +5412,9 @@ packages: react-dom: optional: true + react-selecto@1.26.3: + resolution: {integrity: sha512-Ubik7kWSnZyQEBNro+1k38hZaI1tJarE+5aD/qsqCOA1uUBSjgKVBy3EWRzGIbdmVex7DcxznFZLec/6KZNvwQ==} + react-style-singleton@2.2.3: resolution: {integrity: sha512-b6jSvxvVnyptAiLjbkWLE/lOnR4lfTtDAl+eUC7RZy+QQWc6wRzIV2CE6xBuMmDxc2qIihtDCZD5NPOFl7fRBQ==} engines: {node: '>=10'} @@ -5018,6 +5425,12 @@ packages: '@types/react': optional: true + react-tweet@3.3.0: + resolution: {integrity: sha512-gSIG2169ZK7UH6rBzuU+j1xnQbH3IlOTLEkuGrRiJJTMgETik+h+26yHyyVKrLkzwrOaYPk4K3OtEKycqKgNLw==} + peerDependencies: + react: ^18.0.0 || ^19.0.0 + react-dom: ^18.0.0 || ^19.0.0 + react@19.2.4: resolution: {integrity: sha512-9nfp2hYpCwOjAN+8TZFGhtWEwgvWHXqESH8qT89AT/lWklpLON22Lc8pEtnpsZz7VmawabSU0gCjnj8aC0euHQ==} engines: {node: '>=0.10.0'} @@ -5065,6 +5478,9 @@ packages: engines: {node: '>=18.0.0', npm: '>=8.0.0'} hasBin: true + rope-sequence@1.3.4: + resolution: {integrity: sha512-UT5EDe2cu2E/6O4igUr5PSFs23nvvukicWHx6GnOPlHAiiYbzNuCRQCuiUdHJQcqKalLKlrYJnjY0ySGsXNQXQ==} + rou3@0.7.12: resolution: {integrity: sha512-iFE4hLDuloSWcD7mjdCDhx2bKcIsYbtOTpfH5MHHLSKMOUyjqQXTeZVa289uuwEGEKFoE/BAPbhaU4B774nceg==} @@ -5105,6 +5521,9 @@ packages: secure-json-parse@4.1.0: resolution: {integrity: sha512-l4KnYfEyqYJxDwlNVyRfO2E4NTHfMKAWdUuA8J0yve2Dz/E/PdBepY03RvyJpssIpRFwJoCD55wA+mEDs6ByWA==} + selecto@1.26.3: + resolution: {integrity: sha512-gZHgqMy5uyB6/2YDjv3Qqaf7bd2hTDOpPdxXlrez4R3/L0GiEWDCFaUfrflomgqdb3SxHF2IXY0Jw0EamZi7cw==} + semver@6.3.1: resolution: {integrity: sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==} hasBin: true @@ -5257,6 +5676,11 @@ packages: resolution: {integrity: sha512-oK8WG9diS3DlhdUkcFn4tkNIiIbBx9lI2ClF8K+b2/m8Eyv47LSawxUzZQSNKUrVb2KsqeTDCcjAAVPYaSLVTA==} engines: {node: '>=14.18.0'} + swr@2.4.1: + resolution: {integrity: sha512-2CC6CiKQtEwaEeNiqWTAw9PGykW8SR5zZX8MZk6TeAvEAnVS7Visz8WzphqgtQ8v2xz/4Q5K+j+SeMaKXeeQIA==} + peerDependencies: + react: ^16.11.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 + tabbable@6.4.0: resolution: {integrity: sha512-05PUHKSNE8ou2dwIxTngl4EzcnsCDZGJ/iCLtDflR/SHB/ny14rXc+qU5P4mG9JkusiV7EivzY9Mhm55AzAvCg==} @@ -5303,6 +5727,12 @@ packages: resolution: {integrity: sha512-azl+t0z7pw/z958Gy9svOTuzqIk6xq+NSheJzn5MMWtWTFywIacg2wUlzKFGtt3cthx0r2SxMK0yzJOR0IES7Q==} engines: {node: '>=14.0.0'} + tippy.js@6.3.7: + resolution: {integrity: sha512-E1d3oP2emgJ9dRQZdf3Kkn0qJgI6ZLpyS5z6ZkY1DF3kaQaBsGZsndEpHwx+eC+tYM41HaSNvNtLx8tU57FzTQ==} + + tiptap-extension-global-drag-handle@0.1.18: + resolution: {integrity: sha512-jwFuy1K8DP3a4bFy76Hpc63w1Sil0B7uZ3mvhQomVvUFCU787Lg2FowNhn7NFzeyok761qY2VG+PZ/FDthWUdg==} + to-regex-range@5.0.1: resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==} engines: {node: '>=8.0'} @@ -5329,6 +5759,9 @@ packages: engines: {node: '>=18.0.0'} hasBin: true + tunnel-rat@0.1.2: + resolution: {integrity: sha512-lR5VHmkPhzdhrM092lI2nACsLO4QubF0/yoOhzX7c+wIpbN1GjHNzCc91QlpxBi+cnx8vVJ+Ur6vL5cEoQPFpQ==} + type-is@1.6.18: resolution: {integrity: sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==} engines: {node: '>= 0.6'} @@ -5348,6 +5781,9 @@ packages: engines: {node: '>=14.17'} hasBin: true + uc.micro@2.1.0: + resolution: {integrity: sha512-ARDJmphmdvUk6Glw7y9DQ2bFkKBHwQHLi2lsaH6PPmz/Ka9sFOBsBluozhDltWmnv9u/cF6Rt87znRTPV+yp/A==} + ufo@1.6.3: resolution: {integrity: sha512-yDJTmhydvl5lJzBmy/hyOAA0d+aqCBuwl818haVdYCRrWV84o7YyeVm4QlVHStqNrrJSTb6jKuFAVqAFsr+K3Q==} @@ -5624,6 +6060,21 @@ packages: zod@4.3.6: resolution: {integrity: sha512-rftlrkhHZOcjDwkGlnUtZZkvaPHCsDATp4pGpuOOMDaTdDDXF91wuVDJoWoPsKX/3YPQ5fHuF3STjcYyKr+Qhg==} + zustand@4.5.7: + resolution: {integrity: sha512-CHOUy7mu3lbD6o6LJLfllpjkzhHXSBlX8B9+qPddUsIfeF5S/UZ5q0kmCsnRqT1UHFQZchNFDDzMbQsuesHWlw==} + engines: {node: '>=12.7.0'} + peerDependencies: + '@types/react': '>=16.8' + immer: '>=9.0.6' + react: '>=16.8' + peerDependenciesMeta: + '@types/react': + optional: true + immer: + optional: true + react: + optional: true + zwitch@2.0.4: resolution: {integrity: sha512-bXE4cR/kVZhKZX/RjPEflHaKVhUVl85noU3v6b8apfQEc1x4A+zBxjZ4lN8LqGd6WZ3dl98pY4o717VFmoPp+A==} @@ -6264,6 +6715,10 @@ snapshots: '@braintree/sanitize-url@7.1.2': {} + '@cfcs/core@0.0.6': + dependencies: + '@egjs/component': 3.0.5 + '@changesets/apply-release-plan@7.1.0': dependencies: '@changesets/config': 3.1.3 @@ -6729,6 +7184,8 @@ snapshots: react-dom: 19.2.4(react@19.2.4) react-is: 17.0.2 + '@daybrush/utils@1.13.0': {} + '@dnd-kit/accessibility@3.1.1(react@19.2.4)': dependencies: react: 19.2.4 @@ -6756,6 +7213,16 @@ snapshots: '@drizzle-team/brocli@0.10.2': {} + '@egjs/agent@2.4.4': {} + + '@egjs/children-differ@1.0.1': + dependencies: + '@egjs/list-differ': 1.0.1 + + '@egjs/component@3.0.5': {} + + '@egjs/list-differ@1.0.1': {} + '@electric-sql/pglite@0.3.15': optional: true @@ -7440,6 +7907,8 @@ snapshots: dependencies: playwright: 1.58.2 + '@popperjs/core@2.11.8': {} + '@radix-ui/colors@3.0.0': {} '@radix-ui/number@1.1.1': {} @@ -8210,6 +8679,8 @@ snapshots: dependencies: react: 19.2.4 + '@remirror/core-constants@3.0.0': {} + '@rolldown/pluginutils@1.0.0-beta.27': {} '@rollup/rollup-android-arm-eabi@4.57.1': @@ -8287,6 +8758,19 @@ snapshots: '@rollup/rollup-win32-x64-msvc@4.57.1': optional: true + '@scena/dragscroll@1.4.0': + dependencies: + '@daybrush/utils': 1.13.0 + '@scena/event-emitter': 1.0.5 + + '@scena/event-emitter@1.0.5': + dependencies: + '@daybrush/utils': 1.13.0 + + '@scena/matrix@1.1.1': + dependencies: + '@daybrush/utils': 1.13.0 + '@smithy/abort-controller@4.2.8': dependencies: '@smithy/types': 4.12.0 @@ -8629,6 +9113,10 @@ snapshots: '@stitches/core@1.2.8': {} + '@swc/helpers@0.5.19': + dependencies: + tslib: 2.8.1 + '@tailwindcss/node@4.1.18': dependencies: '@jridgewell/remapping': 2.3.5 @@ -8709,6 +9197,219 @@ snapshots: '@tanstack/query-core': 5.90.20 react: 19.2.4 + '@tiptap/core@2.27.2(@tiptap/pm@2.27.2)': + dependencies: + '@tiptap/pm': 2.27.2 + + '@tiptap/extension-blockquote@2.27.2(@tiptap/core@2.27.2(@tiptap/pm@2.27.2))': + dependencies: + '@tiptap/core': 2.27.2(@tiptap/pm@2.27.2) + + '@tiptap/extension-bold@2.27.2(@tiptap/core@2.27.2(@tiptap/pm@2.27.2))': + dependencies: + '@tiptap/core': 2.27.2(@tiptap/pm@2.27.2) + + '@tiptap/extension-bubble-menu@2.27.2(@tiptap/core@2.27.2(@tiptap/pm@2.27.2))(@tiptap/pm@2.27.2)': + dependencies: + '@tiptap/core': 2.27.2(@tiptap/pm@2.27.2) + '@tiptap/pm': 2.27.2 + tippy.js: 6.3.7 + + '@tiptap/extension-bullet-list@2.27.2(@tiptap/core@2.27.2(@tiptap/pm@2.27.2))': + dependencies: + '@tiptap/core': 2.27.2(@tiptap/pm@2.27.2) + + '@tiptap/extension-character-count@2.27.2(@tiptap/core@2.27.2(@tiptap/pm@2.27.2))(@tiptap/pm@2.27.2)': + dependencies: + '@tiptap/core': 2.27.2(@tiptap/pm@2.27.2) + '@tiptap/pm': 2.27.2 + + '@tiptap/extension-code-block-lowlight@2.27.2(@tiptap/core@2.27.2(@tiptap/pm@2.27.2))(@tiptap/extension-code-block@2.27.2(@tiptap/core@2.27.2(@tiptap/pm@2.27.2))(@tiptap/pm@2.27.2))(@tiptap/pm@2.27.2)(highlight.js@11.11.1)(lowlight@3.3.0)': + dependencies: + '@tiptap/core': 2.27.2(@tiptap/pm@2.27.2) + '@tiptap/extension-code-block': 2.27.2(@tiptap/core@2.27.2(@tiptap/pm@2.27.2))(@tiptap/pm@2.27.2) + '@tiptap/pm': 2.27.2 + highlight.js: 11.11.1 + lowlight: 3.3.0 + + '@tiptap/extension-code-block@2.27.2(@tiptap/core@2.27.2(@tiptap/pm@2.27.2))(@tiptap/pm@2.27.2)': + dependencies: + '@tiptap/core': 2.27.2(@tiptap/pm@2.27.2) + '@tiptap/pm': 2.27.2 + + '@tiptap/extension-code@2.27.2(@tiptap/core@2.27.2(@tiptap/pm@2.27.2))': + dependencies: + '@tiptap/core': 2.27.2(@tiptap/pm@2.27.2) + + '@tiptap/extension-color@2.27.2(@tiptap/core@2.27.2(@tiptap/pm@2.27.2))(@tiptap/extension-text-style@2.27.2(@tiptap/core@2.27.2(@tiptap/pm@2.27.2)))': + dependencies: + '@tiptap/core': 2.27.2(@tiptap/pm@2.27.2) + '@tiptap/extension-text-style': 2.27.2(@tiptap/core@2.27.2(@tiptap/pm@2.27.2)) + + '@tiptap/extension-document@2.27.2(@tiptap/core@2.27.2(@tiptap/pm@2.27.2))': + dependencies: + '@tiptap/core': 2.27.2(@tiptap/pm@2.27.2) + + '@tiptap/extension-dropcursor@2.27.2(@tiptap/core@2.27.2(@tiptap/pm@2.27.2))(@tiptap/pm@2.27.2)': + dependencies: + '@tiptap/core': 2.27.2(@tiptap/pm@2.27.2) + '@tiptap/pm': 2.27.2 + + '@tiptap/extension-floating-menu@2.27.2(@tiptap/core@2.27.2(@tiptap/pm@2.27.2))(@tiptap/pm@2.27.2)': + dependencies: + '@tiptap/core': 2.27.2(@tiptap/pm@2.27.2) + '@tiptap/pm': 2.27.2 + tippy.js: 6.3.7 + + '@tiptap/extension-gapcursor@2.27.2(@tiptap/core@2.27.2(@tiptap/pm@2.27.2))(@tiptap/pm@2.27.2)': + dependencies: + '@tiptap/core': 2.27.2(@tiptap/pm@2.27.2) + '@tiptap/pm': 2.27.2 + + '@tiptap/extension-hard-break@2.27.2(@tiptap/core@2.27.2(@tiptap/pm@2.27.2))': + dependencies: + '@tiptap/core': 2.27.2(@tiptap/pm@2.27.2) + + '@tiptap/extension-heading@2.27.2(@tiptap/core@2.27.2(@tiptap/pm@2.27.2))': + dependencies: + '@tiptap/core': 2.27.2(@tiptap/pm@2.27.2) + + '@tiptap/extension-highlight@2.27.2(@tiptap/core@2.27.2(@tiptap/pm@2.27.2))': + dependencies: + '@tiptap/core': 2.27.2(@tiptap/pm@2.27.2) + + '@tiptap/extension-history@2.27.2(@tiptap/core@2.27.2(@tiptap/pm@2.27.2))(@tiptap/pm@2.27.2)': + dependencies: + '@tiptap/core': 2.27.2(@tiptap/pm@2.27.2) + '@tiptap/pm': 2.27.2 + + '@tiptap/extension-horizontal-rule@2.27.2(@tiptap/core@2.27.2(@tiptap/pm@2.27.2))(@tiptap/pm@2.27.2)': + dependencies: + '@tiptap/core': 2.27.2(@tiptap/pm@2.27.2) + '@tiptap/pm': 2.27.2 + + '@tiptap/extension-image@2.27.2(@tiptap/core@2.27.2(@tiptap/pm@2.27.2))': + dependencies: + '@tiptap/core': 2.27.2(@tiptap/pm@2.27.2) + + '@tiptap/extension-italic@2.27.2(@tiptap/core@2.27.2(@tiptap/pm@2.27.2))': + dependencies: + '@tiptap/core': 2.27.2(@tiptap/pm@2.27.2) + + '@tiptap/extension-link@2.27.2(@tiptap/core@2.27.2(@tiptap/pm@2.27.2))(@tiptap/pm@2.27.2)': + dependencies: + '@tiptap/core': 2.27.2(@tiptap/pm@2.27.2) + '@tiptap/pm': 2.27.2 + linkifyjs: 4.3.2 + + '@tiptap/extension-list-item@2.27.2(@tiptap/core@2.27.2(@tiptap/pm@2.27.2))': + dependencies: + '@tiptap/core': 2.27.2(@tiptap/pm@2.27.2) + + '@tiptap/extension-ordered-list@2.27.2(@tiptap/core@2.27.2(@tiptap/pm@2.27.2))': + dependencies: + '@tiptap/core': 2.27.2(@tiptap/pm@2.27.2) + + '@tiptap/extension-paragraph@2.27.2(@tiptap/core@2.27.2(@tiptap/pm@2.27.2))': + dependencies: + '@tiptap/core': 2.27.2(@tiptap/pm@2.27.2) + + '@tiptap/extension-placeholder@2.27.2(@tiptap/core@2.27.2(@tiptap/pm@2.27.2))(@tiptap/pm@2.27.2)': + dependencies: + '@tiptap/core': 2.27.2(@tiptap/pm@2.27.2) + '@tiptap/pm': 2.27.2 + + '@tiptap/extension-strike@2.27.2(@tiptap/core@2.27.2(@tiptap/pm@2.27.2))': + dependencies: + '@tiptap/core': 2.27.2(@tiptap/pm@2.27.2) + + '@tiptap/extension-task-item@2.27.2(@tiptap/core@2.27.2(@tiptap/pm@2.27.2))(@tiptap/pm@2.27.2)': + dependencies: + '@tiptap/core': 2.27.2(@tiptap/pm@2.27.2) + '@tiptap/pm': 2.27.2 + + '@tiptap/extension-task-list@2.27.2(@tiptap/core@2.27.2(@tiptap/pm@2.27.2))': + dependencies: + '@tiptap/core': 2.27.2(@tiptap/pm@2.27.2) + + '@tiptap/extension-text-style@2.27.2(@tiptap/core@2.27.2(@tiptap/pm@2.27.2))': + dependencies: + '@tiptap/core': 2.27.2(@tiptap/pm@2.27.2) + + '@tiptap/extension-text@2.27.2(@tiptap/core@2.27.2(@tiptap/pm@2.27.2))': + dependencies: + '@tiptap/core': 2.27.2(@tiptap/pm@2.27.2) + + '@tiptap/extension-underline@2.27.2(@tiptap/core@2.27.2(@tiptap/pm@2.27.2))': + dependencies: + '@tiptap/core': 2.27.2(@tiptap/pm@2.27.2) + + '@tiptap/extension-youtube@2.27.2(@tiptap/core@2.27.2(@tiptap/pm@2.27.2))': + dependencies: + '@tiptap/core': 2.27.2(@tiptap/pm@2.27.2) + + '@tiptap/pm@2.27.2': + dependencies: + prosemirror-changeset: 2.4.0 + prosemirror-collab: 1.3.1 + prosemirror-commands: 1.7.1 + prosemirror-dropcursor: 1.8.2 + prosemirror-gapcursor: 1.4.1 + prosemirror-history: 1.5.0 + prosemirror-inputrules: 1.5.1 + prosemirror-keymap: 1.2.3 + prosemirror-markdown: 1.13.4 + prosemirror-menu: 1.3.0 + prosemirror-model: 1.25.4 + prosemirror-schema-basic: 1.2.4 + prosemirror-schema-list: 1.5.1 + prosemirror-state: 1.4.4 + prosemirror-tables: 1.8.5 + prosemirror-trailing-node: 3.0.0(prosemirror-model@1.25.4)(prosemirror-state@1.4.4)(prosemirror-view@1.41.7) + prosemirror-transform: 1.11.0 + prosemirror-view: 1.41.7 + + '@tiptap/react@2.27.2(@tiptap/core@2.27.2(@tiptap/pm@2.27.2))(@tiptap/pm@2.27.2)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)': + dependencies: + '@tiptap/core': 2.27.2(@tiptap/pm@2.27.2) + '@tiptap/extension-bubble-menu': 2.27.2(@tiptap/core@2.27.2(@tiptap/pm@2.27.2))(@tiptap/pm@2.27.2) + '@tiptap/extension-floating-menu': 2.27.2(@tiptap/core@2.27.2(@tiptap/pm@2.27.2))(@tiptap/pm@2.27.2) + '@tiptap/pm': 2.27.2 + '@types/use-sync-external-store': 0.0.6 + fast-deep-equal: 3.1.3 + react: 19.2.4 + react-dom: 19.2.4(react@19.2.4) + use-sync-external-store: 1.6.0(react@19.2.4) + + '@tiptap/starter-kit@2.27.2': + dependencies: + '@tiptap/core': 2.27.2(@tiptap/pm@2.27.2) + '@tiptap/extension-blockquote': 2.27.2(@tiptap/core@2.27.2(@tiptap/pm@2.27.2)) + '@tiptap/extension-bold': 2.27.2(@tiptap/core@2.27.2(@tiptap/pm@2.27.2)) + '@tiptap/extension-bullet-list': 2.27.2(@tiptap/core@2.27.2(@tiptap/pm@2.27.2)) + '@tiptap/extension-code': 2.27.2(@tiptap/core@2.27.2(@tiptap/pm@2.27.2)) + '@tiptap/extension-code-block': 2.27.2(@tiptap/core@2.27.2(@tiptap/pm@2.27.2))(@tiptap/pm@2.27.2) + '@tiptap/extension-document': 2.27.2(@tiptap/core@2.27.2(@tiptap/pm@2.27.2)) + '@tiptap/extension-dropcursor': 2.27.2(@tiptap/core@2.27.2(@tiptap/pm@2.27.2))(@tiptap/pm@2.27.2) + '@tiptap/extension-gapcursor': 2.27.2(@tiptap/core@2.27.2(@tiptap/pm@2.27.2))(@tiptap/pm@2.27.2) + '@tiptap/extension-hard-break': 2.27.2(@tiptap/core@2.27.2(@tiptap/pm@2.27.2)) + '@tiptap/extension-heading': 2.27.2(@tiptap/core@2.27.2(@tiptap/pm@2.27.2)) + '@tiptap/extension-history': 2.27.2(@tiptap/core@2.27.2(@tiptap/pm@2.27.2))(@tiptap/pm@2.27.2) + '@tiptap/extension-horizontal-rule': 2.27.2(@tiptap/core@2.27.2(@tiptap/pm@2.27.2))(@tiptap/pm@2.27.2) + '@tiptap/extension-italic': 2.27.2(@tiptap/core@2.27.2(@tiptap/pm@2.27.2)) + '@tiptap/extension-list-item': 2.27.2(@tiptap/core@2.27.2(@tiptap/pm@2.27.2)) + '@tiptap/extension-ordered-list': 2.27.2(@tiptap/core@2.27.2(@tiptap/pm@2.27.2)) + '@tiptap/extension-paragraph': 2.27.2(@tiptap/core@2.27.2(@tiptap/pm@2.27.2)) + '@tiptap/extension-strike': 2.27.2(@tiptap/core@2.27.2(@tiptap/pm@2.27.2)) + '@tiptap/extension-text': 2.27.2(@tiptap/core@2.27.2(@tiptap/pm@2.27.2)) + '@tiptap/extension-text-style': 2.27.2(@tiptap/core@2.27.2(@tiptap/pm@2.27.2)) + '@tiptap/pm': 2.27.2 + + '@tiptap/suggestion@2.27.2(@tiptap/core@2.27.2(@tiptap/pm@2.27.2))(@tiptap/pm@2.27.2)': + dependencies: + '@tiptap/core': 2.27.2(@tiptap/pm@2.27.2) + '@tiptap/pm': 2.27.2 + '@types/babel__core@7.20.5': dependencies: '@babel/parser': 7.29.0 @@ -8896,10 +9597,19 @@ snapshots: '@types/http-errors@2.0.5': {} + '@types/linkify-it@5.0.0': {} + + '@types/markdown-it@14.1.2': + dependencies: + '@types/linkify-it': 5.0.0 + '@types/mdurl': 2.0.0 + '@types/mdast@4.0.4': dependencies: '@types/unist': 3.0.3 + '@types/mdurl@2.0.0': {} + '@types/methods@1.1.4': {} '@types/ms@2.1.0': {} @@ -8962,6 +9672,8 @@ snapshots: '@types/unist@3.0.3': {} + '@types/use-sync-external-store@0.0.6': {} + '@types/ws@8.18.1': dependencies: '@types/node': 25.2.3 @@ -9310,6 +10022,15 @@ snapshots: shebang-command: 2.0.0 which: 2.0.2 + css-styled@1.0.8: + dependencies: + '@daybrush/utils': 1.13.0 + + css-to-mat@1.1.1: + dependencies: + '@daybrush/utils': 1.13.0 + '@scena/matrix': 1.1.1 + cssesc@3.0.0: {} csstype@3.2.3: {} @@ -9640,6 +10361,8 @@ snapshots: ansi-colors: 4.1.3 strip-ansi: 6.0.1 + entities@4.5.0: {} + es-define-property@1.0.1: {} es-errors@1.3.0: {} @@ -9771,6 +10494,8 @@ snapshots: escape-html@1.0.3: {} + escape-string-regexp@4.0.0: {} + escape-string-regexp@5.0.0: {} esniff@2.0.1: @@ -9845,6 +10570,8 @@ snapshots: fast-copy@4.0.2: {} + fast-deep-equal@3.1.3: {} + fast-glob@3.3.3: dependencies: '@nodelib/fs.stat': 2.0.5 @@ -9909,6 +10636,8 @@ snapshots: forwarded@0.2.0: {} + framework-utils@1.1.0: {} + fresh@2.0.0: {} fs-extra@7.0.1: @@ -9933,6 +10662,11 @@ snapshots: gensync@1.0.0-beta.2: {} + gesto@1.19.4: + dependencies: + '@daybrush/utils': 1.13.0 + '@scena/event-emitter': 1.0.5 + get-caller-file@2.0.5: {} get-intrinsic@1.3.0: @@ -10014,6 +10748,8 @@ snapshots: help-me@5.0.0: {} + highlight.js@11.11.1: {} + html-url-attributes@3.0.1: {} http-errors@2.0.1: @@ -10099,6 +10835,13 @@ snapshots: jose@6.1.3: {} + jotai@2.18.1(@babel/core@7.29.0)(@babel/template@7.28.6)(@types/react@19.2.14)(react@19.2.4): + optionalDependencies: + '@babel/core': 7.29.0 + '@babel/template': 7.28.6 + '@types/react': 19.2.14 + react: 19.2.4 + joycon@3.1.1: {} js-tokens@4.0.0: {} @@ -10126,6 +10869,15 @@ snapshots: dependencies: commander: 8.3.0 + keycode@2.2.1: {} + + keycon@1.4.0: + dependencies: + '@cfcs/core': 0.0.6 + '@daybrush/utils': 1.13.0 + '@scena/event-emitter': 1.0.5 + keycode: 2.2.1 + khroma@2.1.0: {} kleur@4.1.5: {} @@ -10199,6 +10951,12 @@ snapshots: lightningcss-win32-arm64-msvc: 1.30.2 lightningcss-win32-x64-msvc: 1.30.2 + linkify-it@5.0.0: + dependencies: + uc.micro: 2.1.0 + + linkifyjs@4.3.2: {} + locate-path@5.0.0: dependencies: p-locate: 4.1.0 @@ -10215,6 +10973,12 @@ snapshots: loupe@3.2.1: {} + lowlight@3.3.0: + dependencies: + '@types/hast': 3.0.4 + devlop: 1.1.0 + highlight.js: 11.11.1 + lru-cache@5.1.1: dependencies: yallist: 3.1.1 @@ -10229,6 +10993,15 @@ snapshots: dependencies: '@jridgewell/sourcemap-codec': 1.5.5 + markdown-it@14.1.1: + dependencies: + argparse: 2.0.1 + entities: 4.5.0 + linkify-it: 5.0.0 + mdurl: 2.0.0 + punycode.js: 2.3.1 + uc.micro: 2.1.0 + markdown-table@3.0.4: {} marked@16.4.2: {} @@ -10427,6 +11200,8 @@ snapshots: dependencies: '@types/mdast': 4.0.4 + mdurl@2.0.0: {} + media-typer@0.3.0: {} media-typer@1.1.0: {} @@ -10806,6 +11581,50 @@ snapshots: node-releases@2.0.27: {} + novel@1.0.2(@babel/core@7.29.0)(@babel/template@7.28.6)(@tiptap/extension-code-block@2.27.2(@tiptap/core@2.27.2(@tiptap/pm@2.27.2))(@tiptap/pm@2.27.2))(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(highlight.js@11.11.1)(lowlight@3.3.0)(react-dom@19.2.4(react@19.2.4))(react@19.2.4): + dependencies: + '@radix-ui/react-slot': 1.2.4(@types/react@19.2.14)(react@19.2.4) + '@tiptap/core': 2.27.2(@tiptap/pm@2.27.2) + '@tiptap/extension-character-count': 2.27.2(@tiptap/core@2.27.2(@tiptap/pm@2.27.2))(@tiptap/pm@2.27.2) + '@tiptap/extension-code-block-lowlight': 2.27.2(@tiptap/core@2.27.2(@tiptap/pm@2.27.2))(@tiptap/extension-code-block@2.27.2(@tiptap/core@2.27.2(@tiptap/pm@2.27.2))(@tiptap/pm@2.27.2))(@tiptap/pm@2.27.2)(highlight.js@11.11.1)(lowlight@3.3.0) + '@tiptap/extension-color': 2.27.2(@tiptap/core@2.27.2(@tiptap/pm@2.27.2))(@tiptap/extension-text-style@2.27.2(@tiptap/core@2.27.2(@tiptap/pm@2.27.2))) + '@tiptap/extension-highlight': 2.27.2(@tiptap/core@2.27.2(@tiptap/pm@2.27.2)) + '@tiptap/extension-horizontal-rule': 2.27.2(@tiptap/core@2.27.2(@tiptap/pm@2.27.2))(@tiptap/pm@2.27.2) + '@tiptap/extension-image': 2.27.2(@tiptap/core@2.27.2(@tiptap/pm@2.27.2)) + '@tiptap/extension-link': 2.27.2(@tiptap/core@2.27.2(@tiptap/pm@2.27.2))(@tiptap/pm@2.27.2) + '@tiptap/extension-placeholder': 2.27.2(@tiptap/core@2.27.2(@tiptap/pm@2.27.2))(@tiptap/pm@2.27.2) + '@tiptap/extension-task-item': 2.27.2(@tiptap/core@2.27.2(@tiptap/pm@2.27.2))(@tiptap/pm@2.27.2) + '@tiptap/extension-task-list': 2.27.2(@tiptap/core@2.27.2(@tiptap/pm@2.27.2)) + '@tiptap/extension-text-style': 2.27.2(@tiptap/core@2.27.2(@tiptap/pm@2.27.2)) + '@tiptap/extension-underline': 2.27.2(@tiptap/core@2.27.2(@tiptap/pm@2.27.2)) + '@tiptap/extension-youtube': 2.27.2(@tiptap/core@2.27.2(@tiptap/pm@2.27.2)) + '@tiptap/pm': 2.27.2 + '@tiptap/react': 2.27.2(@tiptap/core@2.27.2(@tiptap/pm@2.27.2))(@tiptap/pm@2.27.2)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@tiptap/starter-kit': 2.27.2 + '@tiptap/suggestion': 2.27.2(@tiptap/core@2.27.2(@tiptap/pm@2.27.2))(@tiptap/pm@2.27.2) + '@types/node': 22.19.11 + cmdk: 1.1.1(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + jotai: 2.18.1(@babel/core@7.29.0)(@babel/template@7.28.6)(@types/react@19.2.14)(react@19.2.4) + katex: 0.16.37 + react: 19.2.4 + react-markdown: 9.1.0(@types/react@19.2.14)(react@19.2.4) + react-moveable: 0.56.0 + react-tweet: 3.3.0(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + tippy.js: 6.3.7 + tiptap-extension-global-drag-handle: 0.1.18 + tunnel-rat: 0.1.2(@types/react@19.2.14)(react@19.2.4) + transitivePeerDependencies: + - '@babel/core' + - '@babel/template' + - '@tiptap/extension-code-block' + - '@types/react' + - '@types/react-dom' + - highlight.js + - immer + - lowlight + - react-dom + - supports-color + object-assign@4.1.1: {} object-inspect@1.13.4: {} @@ -10829,10 +11648,16 @@ snapshots: powershell-utils: 0.1.0 wsl-utils: 0.3.1 + orderedmap@2.1.1: {} + outdent@0.5.0: {} outvariant@1.4.0: {} + overlap-area@1.1.0: + dependencies: + '@daybrush/utils': 1.13.0 + p-filter@2.1.0: dependencies: p-map: 2.1.0 @@ -11031,6 +11856,109 @@ snapshots: property-information@7.1.0: {} + prosemirror-changeset@2.4.0: + dependencies: + prosemirror-transform: 1.11.0 + + prosemirror-collab@1.3.1: + dependencies: + prosemirror-state: 1.4.4 + + prosemirror-commands@1.7.1: + dependencies: + prosemirror-model: 1.25.4 + prosemirror-state: 1.4.4 + prosemirror-transform: 1.11.0 + + prosemirror-dropcursor@1.8.2: + dependencies: + prosemirror-state: 1.4.4 + prosemirror-transform: 1.11.0 + prosemirror-view: 1.41.7 + + prosemirror-gapcursor@1.4.1: + dependencies: + prosemirror-keymap: 1.2.3 + prosemirror-model: 1.25.4 + prosemirror-state: 1.4.4 + prosemirror-view: 1.41.7 + + prosemirror-history@1.5.0: + dependencies: + prosemirror-state: 1.4.4 + prosemirror-transform: 1.11.0 + prosemirror-view: 1.41.7 + rope-sequence: 1.3.4 + + prosemirror-inputrules@1.5.1: + dependencies: + prosemirror-state: 1.4.4 + prosemirror-transform: 1.11.0 + + prosemirror-keymap@1.2.3: + dependencies: + prosemirror-state: 1.4.4 + w3c-keyname: 2.2.8 + + prosemirror-markdown@1.13.4: + dependencies: + '@types/markdown-it': 14.1.2 + markdown-it: 14.1.1 + prosemirror-model: 1.25.4 + + prosemirror-menu@1.3.0: + dependencies: + crelt: 1.0.6 + prosemirror-commands: 1.7.1 + prosemirror-history: 1.5.0 + prosemirror-state: 1.4.4 + + prosemirror-model@1.25.4: + dependencies: + orderedmap: 2.1.1 + + prosemirror-schema-basic@1.2.4: + dependencies: + prosemirror-model: 1.25.4 + + prosemirror-schema-list@1.5.1: + dependencies: + prosemirror-model: 1.25.4 + prosemirror-state: 1.4.4 + prosemirror-transform: 1.11.0 + + prosemirror-state@1.4.4: + dependencies: + prosemirror-model: 1.25.4 + prosemirror-transform: 1.11.0 + prosemirror-view: 1.41.7 + + prosemirror-tables@1.8.5: + dependencies: + prosemirror-keymap: 1.2.3 + prosemirror-model: 1.25.4 + prosemirror-state: 1.4.4 + prosemirror-transform: 1.11.0 + prosemirror-view: 1.41.7 + + prosemirror-trailing-node@3.0.0(prosemirror-model@1.25.4)(prosemirror-state@1.4.4)(prosemirror-view@1.41.7): + dependencies: + '@remirror/core-constants': 3.0.0 + escape-string-regexp: 4.0.0 + prosemirror-model: 1.25.4 + prosemirror-state: 1.4.4 + prosemirror-view: 1.41.7 + + prosemirror-transform@1.11.0: + dependencies: + prosemirror-model: 1.25.4 + + prosemirror-view@1.41.7: + dependencies: + prosemirror-model: 1.25.4 + prosemirror-state: 1.4.4 + prosemirror-transform: 1.11.0 + proxy-addr@2.0.7: dependencies: forwarded: 0.2.0 @@ -11041,6 +11969,8 @@ snapshots: end-of-stream: 1.4.5 once: 1.4.0 + punycode.js@2.3.1: {} + qs@6.15.0: dependencies: side-channel: 1.1.0 @@ -11123,6 +12053,11 @@ snapshots: iconv-lite: 0.7.2 unpipe: 1.0.0 + react-css-styled@1.1.9: + dependencies: + css-styled: 1.0.8 + framework-utils: 1.1.0 + react-devtools-inline@4.4.0: dependencies: es6-symbol: 3.1.4 @@ -11163,6 +12098,40 @@ snapshots: transitivePeerDependencies: - supports-color + react-markdown@9.1.0(@types/react@19.2.14)(react@19.2.4): + dependencies: + '@types/hast': 3.0.4 + '@types/mdast': 4.0.4 + '@types/react': 19.2.14 + devlop: 1.1.0 + hast-util-to-jsx-runtime: 2.3.6 + html-url-attributes: 3.0.1 + mdast-util-to-hast: 13.2.1 + react: 19.2.4 + remark-parse: 11.0.0 + remark-rehype: 11.1.2 + unified: 11.0.5 + unist-util-visit: 5.1.0 + vfile: 6.0.3 + transitivePeerDependencies: + - supports-color + + react-moveable@0.56.0: + dependencies: + '@daybrush/utils': 1.13.0 + '@egjs/agent': 2.4.4 + '@egjs/children-differ': 1.0.1 + '@egjs/list-differ': 1.0.1 + '@scena/dragscroll': 1.4.0 + '@scena/event-emitter': 1.0.5 + '@scena/matrix': 1.1.1 + css-to-mat: 1.1.1 + framework-utils: 1.1.0 + gesto: 1.19.4 + overlap-area: 1.1.0 + react-css-styled: 1.1.9 + react-selecto: 1.26.3 + react-refresh@0.17.0: {} react-remove-scroll-bar@2.3.8(@types/react@19.2.14)(react@19.2.4): @@ -11198,6 +12167,10 @@ snapshots: optionalDependencies: react-dom: 19.2.4(react@19.2.4) + react-selecto@1.26.3: + dependencies: + selecto: 1.26.3 + react-style-singleton@2.2.3(@types/react@19.2.14)(react@19.2.4): dependencies: get-nonce: 1.0.1 @@ -11206,6 +12179,14 @@ snapshots: optionalDependencies: '@types/react': 19.2.14 + react-tweet@3.3.0(react-dom@19.2.4(react@19.2.4))(react@19.2.4): + dependencies: + '@swc/helpers': 0.5.19 + clsx: 2.1.1 + react: 19.2.4 + react-dom: 19.2.4(react@19.2.4) + swr: 2.4.1(react@19.2.4) + react@19.2.4: {} read-yaml-file@1.1.0: @@ -11296,6 +12277,8 @@ snapshots: '@rollup/rollup-win32-x64-msvc': 4.57.1 fsevents: 2.3.3 + rope-sequence@1.3.4: {} + rou3@0.7.12: {} roughjs@4.6.6: @@ -11337,6 +12320,19 @@ snapshots: secure-json-parse@4.1.0: {} + selecto@1.26.3: + dependencies: + '@daybrush/utils': 1.13.0 + '@egjs/children-differ': 1.0.1 + '@scena/dragscroll': 1.4.0 + '@scena/event-emitter': 1.0.5 + css-styled: 1.0.8 + css-to-mat: 1.1.1 + framework-utils: 1.1.0 + gesto: 1.19.4 + keycon: 1.4.0 + overlap-area: 1.1.0 + semver@6.3.1: {} semver@7.7.4: {} @@ -11510,6 +12506,12 @@ snapshots: transitivePeerDependencies: - supports-color + swr@2.4.1(react@19.2.4): + dependencies: + dequal: 2.0.3 + react: 19.2.4 + use-sync-external-store: 1.6.0(react@19.2.4) + tabbable@6.4.0: {} tailwind-merge@3.4.1: {} @@ -11541,6 +12543,12 @@ snapshots: tinyspy@4.0.4: {} + tippy.js@6.3.7: + dependencies: + '@popperjs/core': 2.11.8 + + tiptap-extension-global-drag-handle@0.1.18: {} + to-regex-range@5.0.1: dependencies: is-number: 7.0.0 @@ -11562,6 +12570,14 @@ snapshots: optionalDependencies: fsevents: 2.3.3 + tunnel-rat@0.1.2(@types/react@19.2.14)(react@19.2.4): + dependencies: + zustand: 4.5.7(@types/react@19.2.14)(react@19.2.4) + transitivePeerDependencies: + - '@types/react' + - immer + - react + type-is@1.6.18: dependencies: media-typer: 0.3.0 @@ -11579,6 +12595,8 @@ snapshots: typescript@5.9.3: {} + uc.micro@2.1.0: {} + ufo@1.6.3: {} undici-types@6.21.0: {} @@ -11913,4 +12931,11 @@ snapshots: zod@4.3.6: {} + zustand@4.5.7(@types/react@19.2.14)(react@19.2.4): + dependencies: + use-sync-external-store: 1.6.0(react@19.2.4) + optionalDependencies: + '@types/react': 19.2.14 + react: 19.2.4 + zwitch@2.0.4: {} diff --git a/server/src/app.ts b/server/src/app.ts index 6871552a572..d58f4e65ac8 100644 --- a/server/src/app.ts +++ b/server/src/app.ts @@ -15,6 +15,7 @@ import { agentRoutes } from "./routes/agents.js"; import { projectRoutes } from "./routes/projects.js"; import { issueRoutes } from "./routes/issues.js"; import { goalRoutes } from "./routes/goals.js"; +import { documentRoutes } from "./routes/documents.js"; import { approvalRoutes } from "./routes/approvals.js"; import { secretRoutes } from "./routes/secrets.js"; import { costRoutes } from "./routes/costs.js"; @@ -108,6 +109,7 @@ export async function createApp( api.use(projectRoutes(db)); api.use(issueRoutes(db, opts.storageService)); api.use(goalRoutes(db)); + api.use(documentRoutes(db)); api.use(approvalRoutes(db)); api.use(secretRoutes(db)); api.use(costRoutes(db)); diff --git a/server/src/routes/documents.ts b/server/src/routes/documents.ts new file mode 100644 index 00000000000..4d600c8cc98 --- /dev/null +++ b/server/src/routes/documents.ts @@ -0,0 +1,213 @@ +import { Router } from "express"; +import { eq } from "drizzle-orm"; +import type { Db } from "@paperclipai/db"; +import { documents } from "@paperclipai/db"; +import { + createDocumentSchema, + updateDocumentSchema, + linkDocumentGoalSchema, + linkDocumentIssueSchema, +} from "@paperclipai/shared"; +import { validate } from "../middleware/validate.js"; +import { documentService, logActivity } from "../services/index.js"; +import { assertCompanyAccess, getActorInfo } from "./authz.js"; + +export function documentRoutes(db: Db) { + const router = Router(); + const svc = documentService(db); + + // List documents for a company + router.get("/companies/:companyId/documents", async (req, res) => { + const companyId = req.params.companyId as string; + assertCompanyAccess(req, companyId); + const filters: { projectId?: string; goalId?: string; issueId?: string } = {}; + if (req.query.projectId) filters.projectId = req.query.projectId as string; + if (req.query.goalId) filters.goalId = req.query.goalId as string; + if (req.query.issueId) filters.issueId = req.query.issueId as string; + const result = await svc.list(companyId, filters); + res.json(result); + }); + + // Create document + router.post( + "/companies/:companyId/documents", + validate(createDocumentSchema), + async (req, res) => { + const companyId = req.params.companyId as string; + assertCompanyAccess(req, companyId); + const actor = getActorInfo(req); + const doc = await svc.create(companyId, { + ...req.body, + createdByAgentId: actor.agentId ?? null, + createdByUserId: actor.actorType === "user" ? actor.actorId : null, + }); + await logActivity(db, { + companyId, + actorType: actor.actorType, + actorId: actor.actorId, + agentId: actor.agentId, + action: "document.created", + entityType: "document", + entityId: doc.id, + details: { title: doc.title }, + }); + res.status(201).json(doc); + }, + ); + + // Get document by source issue ID (for agents to find their doc from their task) + router.get("/issues/:issueId/document", async (req, res) => { + const issueId = req.params.issueId as string; + const docs = await db + .select() + .from(documents) + .where(eq(documents.issueId, issueId)) + .then((rows) => rows[0] ?? null); + if (!docs) { + res.status(404).json({ error: "No document found for this issue" }); + return; + } + assertCompanyAccess(req, docs.companyId); + const full = await svc.getById(docs.id); + res.json(full); + }); + + // Get document with links + router.get("/documents/:id", async (req, res) => { + const id = req.params.id as string; + const doc = await svc.getById(id); + if (!doc) { + res.status(404).json({ error: "Document not found" }); + return; + } + assertCompanyAccess(req, doc.companyId); + res.json(doc); + }); + + // Update document + router.patch("/documents/:id", validate(updateDocumentSchema), async (req, res) => { + const id = req.params.id as string; + const existing = await svc.getById(id); + if (!existing) { + res.status(404).json({ error: "Document not found" }); + return; + } + assertCompanyAccess(req, existing.companyId); + const doc = await svc.update(id, req.body); + if (!doc) { + res.status(404).json({ error: "Document not found" }); + return; + } + const actor = getActorInfo(req); + await logActivity(db, { + companyId: doc.companyId, + actorType: actor.actorType, + actorId: actor.actorId, + agentId: actor.agentId, + action: "document.updated", + entityType: "document", + entityId: doc.id, + details: req.body, + }); + res.json(doc); + }); + + // Delete document + router.delete("/documents/:id", async (req, res) => { + const id = req.params.id as string; + const existing = await svc.getById(id); + if (!existing) { + res.status(404).json({ error: "Document not found" }); + return; + } + assertCompanyAccess(req, existing.companyId); + const doc = await svc.remove(id); + if (!doc) { + res.status(404).json({ error: "Document not found" }); + return; + } + const actor = getActorInfo(req); + await logActivity(db, { + companyId: doc.companyId, + actorType: actor.actorType, + actorId: actor.actorId, + agentId: actor.agentId, + action: "document.deleted", + entityType: "document", + entityId: doc.id, + }); + res.json(doc); + }); + + // Link goal + router.post( + "/documents/:id/goals", + validate(linkDocumentGoalSchema), + async (req, res) => { + const id = req.params.id as string; + const existing = await svc.getById(id); + if (!existing) { + res.status(404).json({ error: "Document not found" }); + return; + } + assertCompanyAccess(req, existing.companyId); + const link = await svc.linkGoal(id, req.body.goalId); + res.status(201).json(link); + }, + ); + + // Unlink goal + router.delete("/documents/:id/goals/:goalId", async (req, res) => { + const id = req.params.id as string; + const goalId = req.params.goalId as string; + const existing = await svc.getById(id); + if (!existing) { + res.status(404).json({ error: "Document not found" }); + return; + } + assertCompanyAccess(req, existing.companyId); + const link = await svc.unlinkGoal(id, goalId); + if (!link) { + res.status(404).json({ error: "Link not found" }); + return; + } + res.json(link); + }); + + // Link issue + router.post( + "/documents/:id/issues", + validate(linkDocumentIssueSchema), + async (req, res) => { + const id = req.params.id as string; + const existing = await svc.getById(id); + if (!existing) { + res.status(404).json({ error: "Document not found" }); + return; + } + assertCompanyAccess(req, existing.companyId); + const link = await svc.linkIssue(id, req.body.issueId); + res.status(201).json(link); + }, + ); + + // Unlink issue + router.delete("/documents/:id/issues/:issueId", async (req, res) => { + const id = req.params.id as string; + const issueId = req.params.issueId as string; + const existing = await svc.getById(id); + if (!existing) { + res.status(404).json({ error: "Document not found" }); + return; + } + assertCompanyAccess(req, existing.companyId); + const link = await svc.unlinkIssue(id, issueId); + if (!link) { + res.status(404).json({ error: "Link not found" }); + return; + } + res.json(link); + }); + + return router; +} diff --git a/server/src/services/documents.ts b/server/src/services/documents.ts new file mode 100644 index 00000000000..c93d15f84e6 --- /dev/null +++ b/server/src/services/documents.ts @@ -0,0 +1,166 @@ +import { and, eq, inArray } from "drizzle-orm"; +import type { Db } from "@paperclipai/db"; +import { documents, documentGoals, documentIssues } from "@paperclipai/db"; + +export function documentService(db: Db) { + return { + list: async ( + companyId: string, + filters?: { projectId?: string; goalId?: string; issueId?: string }, + ) => { + if (filters?.goalId) { + const links = await db + .select({ documentId: documentGoals.documentId }) + .from(documentGoals) + .where(eq(documentGoals.goalId, filters.goalId)); + const ids = links.map((l) => l.documentId); + if (ids.length === 0) return []; + return db + .select() + .from(documents) + .where(and(eq(documents.companyId, companyId), inArray(documents.id, ids))); + } + if (filters?.issueId) { + const links = await db + .select({ documentId: documentIssues.documentId }) + .from(documentIssues) + .where(eq(documentIssues.issueId, filters.issueId)); + const ids = links.map((l) => l.documentId); + if (ids.length === 0) return []; + return db + .select() + .from(documents) + .where(and(eq(documents.companyId, companyId), inArray(documents.id, ids))); + } + const conditions = [eq(documents.companyId, companyId)]; + if (filters?.projectId) { + conditions.push(eq(documents.projectId, filters.projectId)); + } + return db + .select() + .from(documents) + .where(and(...conditions)); + }, + + getById: async (id: string) => { + const doc = await db + .select() + .from(documents) + .where(eq(documents.id, id)) + .then((rows) => rows[0] ?? null); + if (!doc) return null; + + const goals = await db + .select() + .from(documentGoals) + .where(eq(documentGoals.documentId, id)); + const issues = await db + .select() + .from(documentIssues) + .where(eq(documentIssues.documentId, id)); + + return { ...doc, goals, issues }; + }, + + create: async ( + companyId: string, + data: { + title: string; + issueId: string; + content?: Record; + projectId?: string | null; + createdByAgentId?: string | null; + createdByUserId?: string | null; + }, + ) => { + const doc = await db + .insert(documents) + .values({ ...data, companyId }) + .returning() + .then((rows) => rows[0]); + // Auto-link the creating issue + if (doc) { + await db + .insert(documentIssues) + .values({ documentId: doc.id, issueId: data.issueId }) + .onConflictDoNothing(); + } + return doc; + }, + + update: (id: string, data: Partial) => + db + .update(documents) + .set({ ...data, updatedAt: new Date() }) + .where(eq(documents.id, id)) + .returning() + .then((rows) => rows[0] ?? null), + + remove: (id: string) => + db + .delete(documents) + .where(eq(documents.id, id)) + .returning() + .then((rows) => rows[0] ?? null), + + linkGoal: (documentId: string, goalId: string) => + db + .insert(documentGoals) + .values({ documentId, goalId }) + .onConflictDoNothing() + .returning() + .then((rows) => rows[0] ?? null), + + unlinkGoal: (documentId: string, goalId: string) => + db + .delete(documentGoals) + .where( + and( + eq(documentGoals.documentId, documentId), + eq(documentGoals.goalId, goalId), + ), + ) + .returning() + .then((rows) => rows[0] ?? null), + + linkIssue: (documentId: string, issueId: string) => + db + .insert(documentIssues) + .values({ documentId, issueId }) + .onConflictDoNothing() + .returning() + .then((rows) => rows[0] ?? null), + + unlinkIssue: (documentId: string, issueId: string) => + db + .delete(documentIssues) + .where( + and( + eq(documentIssues.documentId, documentId), + eq(documentIssues.issueId, issueId), + ), + ) + .returning() + .then((rows) => rows[0] ?? null), + + listByGoal: async (goalId: string) => { + const links = await db + .select({ documentId: documentGoals.documentId }) + .from(documentGoals) + .where(eq(documentGoals.goalId, goalId)); + const ids = links.map((l) => l.documentId); + if (ids.length === 0) return []; + return db.select().from(documents).where(inArray(documents.id, ids)); + }, + + listByIssue: async (issueId: string) => { + const links = await db + .select({ documentId: documentIssues.documentId }) + .from(documentIssues) + .where(eq(documentIssues.issueId, issueId)); + const ids = links.map((l) => l.documentId); + if (ids.length === 0) return []; + return db.select().from(documents).where(inArray(documents.id, ids)); + }, + }; +} diff --git a/server/src/services/index.ts b/server/src/services/index.ts index 99a950c543f..c3df16fe533 100644 --- a/server/src/services/index.ts +++ b/server/src/services/index.ts @@ -5,6 +5,7 @@ export { projectService } from "./projects.js"; export { issueService, type IssueFilters } from "./issues.js"; export { issueApprovalService } from "./issue-approvals.js"; export { goalService } from "./goals.js"; +export { documentService } from "./documents.js"; export { activityService, type ActivityFilters } from "./activity.js"; export { approvalService } from "./approvals.js"; export { secretService } from "./secrets.js"; diff --git a/ui/package.json b/ui/package.json index 5ce15553376..22f22da463f 100644 --- a/ui/package.json +++ b/ui/package.json @@ -18,9 +18,9 @@ "@paperclipai/adapter-codex-local": "workspace:*", "@paperclipai/adapter-cursor-local": "workspace:*", "@paperclipai/adapter-gemini-local": "workspace:*", + "@paperclipai/adapter-openclaw-gateway": "workspace:*", "@paperclipai/adapter-opencode-local": "workspace:*", "@paperclipai/adapter-pi-local": "workspace:*", - "@paperclipai/adapter-openclaw-gateway": "workspace:*", "@paperclipai/adapter-utils": "workspace:*", "@paperclipai/shared": "workspace:*", "@radix-ui/react-slot": "^1.2.4", @@ -31,6 +31,7 @@ "cmdk": "^1.1.1", "lucide-react": "^0.574.0", "mermaid": "^11.12.0", + "novel": "^1.0.2", "radix-ui": "^1.4.3", "react": "^19.0.0", "react-dom": "^19.0.0", diff --git a/ui/src/App.tsx b/ui/src/App.tsx index 1cfdd9dfdde..ef7d4e6c1f2 100644 --- a/ui/src/App.tsx +++ b/ui/src/App.tsx @@ -16,6 +16,8 @@ import { Issues } from "./pages/Issues"; import { IssueDetail } from "./pages/IssueDetail"; import { Goals } from "./pages/Goals"; import { GoalDetail } from "./pages/GoalDetail"; +import { Documents } from "./pages/Documents"; +import { DocumentDetail } from "./pages/DocumentDetail"; import { Approvals } from "./pages/Approvals"; import { ApprovalDetail } from "./pages/ApprovalDetail"; import { Costs } from "./pages/Costs"; @@ -138,6 +140,8 @@ function boardRoutes() { } /> } /> } /> + } /> + } /> } /> } /> } /> diff --git a/ui/src/api/documents.ts b/ui/src/api/documents.ts new file mode 100644 index 00000000000..15e3a708c53 --- /dev/null +++ b/ui/src/api/documents.ts @@ -0,0 +1,27 @@ +import type { Document, DocumentWithLinks, DocumentGoalLink, DocumentIssueLink } from "@paperclipai/shared"; +import { api } from "./client"; + +export const documentsApi = { + list: (companyId: string, filters?: { projectId?: string; goalId?: string; issueId?: string }) => { + const params = new URLSearchParams(); + if (filters?.projectId) params.set("projectId", filters.projectId); + if (filters?.goalId) params.set("goalId", filters.goalId); + if (filters?.issueId) params.set("issueId", filters.issueId); + const qs = params.toString(); + return api.get(`/companies/${companyId}/documents${qs ? `?${qs}` : ""}`); + }, + get: (id: string) => api.get(`/documents/${id}`), + create: (companyId: string, data: { title: string; issueId: string; content?: Record; projectId?: string | null }) => + api.post(`/companies/${companyId}/documents`, data), + update: (id: string, data: { title?: string; content?: Record; projectId?: string | null }) => + api.patch(`/documents/${id}`, data), + remove: (id: string) => api.delete(`/documents/${id}`), + linkGoal: (id: string, goalId: string) => + api.post(`/documents/${id}/goals`, { goalId }), + unlinkGoal: (id: string, goalId: string) => + api.delete(`/documents/${id}/goals/${goalId}`), + linkIssue: (id: string, issueId: string) => + api.post(`/documents/${id}/issues`, { issueId }), + unlinkIssue: (id: string, issueId: string) => + api.delete(`/documents/${id}/issues/${issueId}`), +}; diff --git a/ui/src/components/DocumentEditor.tsx b/ui/src/components/DocumentEditor.tsx new file mode 100644 index 00000000000..21273c140e5 --- /dev/null +++ b/ui/src/components/DocumentEditor.tsx @@ -0,0 +1,43 @@ +import { useCallback, useRef } from "react"; +import { + EditorRoot, + EditorContent, + type JSONContent, +} from "novel"; + +interface DocumentEditorProps { + initialContent?: Record; + onUpdate?: (content: JSONContent) => void; +} + +export function DocumentEditor({ initialContent, onUpdate }: DocumentEditorProps) { + const debounceTimer = useRef | null>(null); + + const handleUpdate = useCallback( + ({ editor }: { editor: { getJSON: () => JSONContent } }) => { + if (!onUpdate) return; + if (debounceTimer.current) clearTimeout(debounceTimer.current); + debounceTimer.current = setTimeout(() => { + onUpdate(editor.getJSON()); + }, 500); + }, + [onUpdate], + ); + + return ( +
+ + + +
+ ); +} diff --git a/ui/src/components/LinkedDocuments.tsx b/ui/src/components/LinkedDocuments.tsx new file mode 100644 index 00000000000..1f05947aaeb --- /dev/null +++ b/ui/src/components/LinkedDocuments.tsx @@ -0,0 +1,49 @@ +import { useQuery } from "@tanstack/react-query"; +import { useNavigate } from "@/lib/router"; +import { documentsApi } from "../api/documents"; +import { queryKeys } from "../lib/queryKeys"; +import { FileText } from "lucide-react"; +import type { Document } from "@paperclipai/shared"; + +interface LinkedDocumentsProps { + companyId: string; + goalId?: string; + issueId?: string; +} + +export function LinkedDocuments({ companyId, goalId, issueId }: LinkedDocumentsProps) { + const navigate = useNavigate(); + + const queryKey = goalId + ? queryKeys.documents.byGoal(goalId) + : queryKeys.documents.byIssue(issueId!); + + const { data: documents } = useQuery({ + queryKey, + queryFn: () => + documentsApi.list(companyId, goalId ? { goalId } : { issueId }), + enabled: !!(goalId || issueId), + }); + + if (!documents || documents.length === 0) return null; + + return ( +
+

+ Linked Documents +

+
+ {documents.map((doc) => ( + + ))} +
+
+ ); +} diff --git a/ui/src/components/Sidebar.tsx b/ui/src/components/Sidebar.tsx index 0cc46d87550..20e7883390b 100644 --- a/ui/src/components/Sidebar.tsx +++ b/ui/src/components/Sidebar.tsx @@ -2,6 +2,7 @@ import { Inbox, CircleDot, Target, + FileText, LayoutDashboard, DollarSign, History, @@ -85,6 +86,7 @@ export function Sidebar() { + diff --git a/ui/src/lib/queryKeys.ts b/ui/src/lib/queryKeys.ts index c500afdc5f4..0a1b460356e 100644 --- a/ui/src/lib/queryKeys.ts +++ b/ui/src/lib/queryKeys.ts @@ -41,6 +41,12 @@ export const queryKeys = { list: (companyId: string) => ["goals", companyId] as const, detail: (id: string) => ["goals", "detail", id] as const, }, + documents: { + list: (companyId: string) => ["documents", companyId] as const, + detail: (id: string) => ["documents", "detail", id] as const, + byGoal: (goalId: string) => ["documents", "by-goal", goalId] as const, + byIssue: (issueId: string) => ["documents", "by-issue", issueId] as const, + }, approvals: { list: (companyId: string, status?: string) => ["approvals", companyId, status] as const, diff --git a/ui/src/pages/DocumentDetail.tsx b/ui/src/pages/DocumentDetail.tsx new file mode 100644 index 00000000000..396dcf70f73 --- /dev/null +++ b/ui/src/pages/DocumentDetail.tsx @@ -0,0 +1,257 @@ +import { useEffect, useCallback, useState } from "react"; +import { useParams } from "@/lib/router"; +import { useQuery, useMutation, useQueryClient } from "@tanstack/react-query"; +import { documentsApi } from "../api/documents"; +import { projectsApi } from "../api/projects"; +import { goalsApi } from "../api/goals"; +import { issuesApi } from "../api/issues"; +import { useCompany } from "../context/CompanyContext"; +import { useBreadcrumbs } from "../context/BreadcrumbContext"; +import { queryKeys } from "../lib/queryKeys"; +import { DocumentEditor } from "../components/DocumentEditor"; +import { PageSkeleton } from "../components/PageSkeleton"; +import { InlineEditor } from "../components/InlineEditor"; +import { Badge } from "@/components/ui/badge"; +import { X, MessageSquare, FileText } from "lucide-react"; +import type { JSONContent } from "novel"; +import type { Goal, Project } from "@paperclipai/shared"; + +export function DocumentDetail() { + const { documentId } = useParams<{ documentId: string }>(); + const { selectedCompanyId, setSelectedCompanyId } = useCompany(); + const { setBreadcrumbs } = useBreadcrumbs(); + const queryClient = useQueryClient(); + const [saving, setSaving] = useState(false); + + const { data: doc, isLoading, error } = useQuery({ + queryKey: queryKeys.documents.detail(documentId!), + queryFn: () => documentsApi.get(documentId!), + enabled: !!documentId, + }); + + const resolvedCompanyId = doc?.companyId ?? selectedCompanyId; + + const { data: projects } = useQuery({ + queryKey: queryKeys.projects.list(resolvedCompanyId!), + queryFn: () => projectsApi.list(resolvedCompanyId!), + enabled: !!resolvedCompanyId, + }); + + const { data: allGoals } = useQuery({ + queryKey: queryKeys.goals.list(resolvedCompanyId!), + queryFn: () => goalsApi.list(resolvedCompanyId!), + enabled: !!resolvedCompanyId, + }); + + // Fetch the source issue (the one that created this doc) + const { data: sourceIssue } = useQuery({ + queryKey: queryKeys.issues.detail(doc?.issueId ?? ""), + queryFn: () => issuesApi.get(doc!.issueId), + enabled: !!doc?.issueId, + }); + + // Fetch comments for the source issue + const { data: issueComments } = useQuery({ + queryKey: [...queryKeys.issues.detail(doc?.issueId ?? ""), "comments"], + queryFn: () => issuesApi.listComments(doc!.issueId), + enabled: !!doc?.issueId, + refetchInterval: 15000, // poll every 15s for new comments + }); + + useEffect(() => { + if (!doc?.companyId || doc.companyId === selectedCompanyId) return; + setSelectedCompanyId(doc.companyId, { source: "route_sync" }); + }, [doc?.companyId, selectedCompanyId, setSelectedCompanyId]); + + useEffect(() => { + setBreadcrumbs([ + { label: "Documents", to: "/documents" }, + { label: doc?.title ?? "Document" }, + ]); + }, [setBreadcrumbs, doc?.title]); + + const updateDoc = useMutation({ + mutationFn: (data: { title?: string; content?: Record; projectId?: string | null }) => + documentsApi.update(documentId!, data), + onSuccess: () => { + queryClient.invalidateQueries({ queryKey: queryKeys.documents.detail(documentId!) }); + if (resolvedCompanyId) { + queryClient.invalidateQueries({ queryKey: queryKeys.documents.list(resolvedCompanyId) }); + } + setSaving(false); + }, + }); + + const linkGoal = useMutation({ + mutationFn: (goalId: string) => documentsApi.linkGoal(documentId!, goalId), + onSuccess: () => + queryClient.invalidateQueries({ queryKey: queryKeys.documents.detail(documentId!) }), + }); + + const unlinkGoal = useMutation({ + mutationFn: (goalId: string) => documentsApi.unlinkGoal(documentId!, goalId), + onSuccess: () => + queryClient.invalidateQueries({ queryKey: queryKeys.documents.detail(documentId!) }), + }); + + const handleContentUpdate = useCallback( + (content: JSONContent) => { + setSaving(true); + updateDoc.mutate({ content: content as Record }); + }, + [updateDoc], + ); + + if (isLoading) return ; + if (error) return

{error.message}

; + if (!doc) return null; + + const linkedGoalIds = new Set(doc.goals?.map((g: any) => g.goalId) ?? []); + const unlinkedGoals = (allGoals ?? []).filter((g: any) => !linkedGoalIds.has(g.id)); + + return ( +
+ {/* Main editor area */} +
+ {/* Title */} + updateDoc.mutate({ title })} + className="text-2xl font-bold" + /> + + {/* Metadata bar */} +
+
+ Project: + +
+ + {/* Linked Goals inline */} +
+ Goals: + {doc.goals?.map((link: any) => { + const goal = (allGoals ?? []).find((g: any) => g.id === link.goalId); + return ( + + {goal?.title ?? link.goalId.slice(0, 8)} + + + ); + })} + {unlinkedGoals.length > 0 && ( + + )} +
+ + {saving && Saving...} +
+ + {/* Editor */} + } + onUpdate={handleContentUpdate} + /> +
+ + {/* Issue sidebar */} + {sourceIssue && ( +
+ {/* Issue header */} +
+
+ + Source Issue +
+

{sourceIssue.title}

+ {sourceIssue.identifier && ( + + {sourceIssue.identifier} + + )} +
+ + {/* Issue properties */} +
+
+ Status + {sourceIssue.status} +
+
+ Priority + {sourceIssue.priority} +
+ {sourceIssue.assigneeAgentId && ( +
+ Assignee + {sourceIssue.assigneeAgentId.slice(0, 8)}... +
+ )} +
+ + {/* Issue description */} + {sourceIssue.description && ( +
+

Description

+

+ {sourceIssue.description.slice(0, 500)} + {sourceIssue.description.length > 500 && "..."} +

+
+ )} + + {/* Comments section */} +
+
+ + Comments ({(issueComments as any)?.length ?? 0}) +
+
+ {(issueComments as any[])?.map((comment: any) => ( +
+
+ + {comment.authorAgentId ? `Agent ${comment.authorAgentId.slice(0, 8)}` : "Board"} + + · + {new Date(comment.createdAt).toLocaleString()} +
+

+ {comment.body.slice(0, 300)} + {comment.body.length > 300 && "..."} +

+
+ ))} + {(!issueComments || (issueComments as any[])?.length === 0) && ( +

No comments yet. Comment on the issue to request AI edits.

+ )} +
+
+
+ )} +
+ ); +} diff --git a/ui/src/pages/Documents.tsx b/ui/src/pages/Documents.tsx new file mode 100644 index 00000000000..3317bbfbac6 --- /dev/null +++ b/ui/src/pages/Documents.tsx @@ -0,0 +1,183 @@ +import { useEffect, useState } from "react"; +import { useQuery } from "@tanstack/react-query"; +import { useNavigate } from "@/lib/router"; +import { documentsApi } from "../api/documents"; +import { projectsApi } from "../api/projects"; +import { issuesApi } from "../api/issues"; +import { useCompany } from "../context/CompanyContext"; +import { useBreadcrumbs } from "../context/BreadcrumbContext"; +import { queryKeys } from "../lib/queryKeys"; +import { EmptyState } from "../components/EmptyState"; +import { PageSkeleton } from "../components/PageSkeleton"; +import { Button } from "@/components/ui/button"; +import { FileText, Plus } from "lucide-react"; +import type { Document, Project } from "@paperclipai/shared"; + +function formatDate(date: Date | string) { + return new Date(date).toLocaleDateString(undefined, { + month: "short", + day: "numeric", + year: "numeric", + }); +} + +function GroupedDocuments({ + documents, + projects, + onDocClick, +}: { + documents: Document[]; + projects: Project[]; + onDocClick: (id: string) => void; +}) { + const projectMap = new Map(projects.map((p) => [p.id, p])); + const grouped = new Map(); + for (const doc of documents) { + const key = doc.projectId; + if (!grouped.has(key)) grouped.set(key, []); + grouped.get(key)!.push(doc); + } + + const sortedGroups = Array.from(grouped.entries()).sort(([a], [b]) => { + if (a === null) return 1; + if (b === null) return -1; + return 0; + }); + + return ( +
+ {sortedGroups.map(([projectId, docs]) => { + const project = projectId ? projectMap.get(projectId) : null; + return ( +
+

+ {project ? project.name : "No Project"} +

+
+ {docs.map((doc) => ( + + ))} +
+
+ ); + })} +
+ ); +} + +export function Documents() { + const { selectedCompanyId } = useCompany(); + const { setBreadcrumbs } = useBreadcrumbs(); + const navigate = useNavigate(); + const [showIssuePicker, setShowIssuePicker] = useState(false); + + useEffect(() => { + setBreadcrumbs([{ label: "Documents" }]); + }, [setBreadcrumbs]); + + const { data: documents, isLoading, error } = useQuery({ + queryKey: queryKeys.documents.list(selectedCompanyId!), + queryFn: () => documentsApi.list(selectedCompanyId!), + enabled: !!selectedCompanyId, + }); + + const { data: projects } = useQuery({ + queryKey: queryKeys.projects.list(selectedCompanyId!), + queryFn: () => projectsApi.list(selectedCompanyId!), + enabled: !!selectedCompanyId, + }); + + const { data: issues } = useQuery({ + queryKey: queryKeys.issues.list(selectedCompanyId!), + queryFn: () => issuesApi.list(selectedCompanyId!), + enabled: !!selectedCompanyId && showIssuePicker, + }); + + const handleNew = () => setShowIssuePicker(true); + + const handleCreateWithIssue = async (issueId: string, issueTitle: string) => { + if (!selectedCompanyId) return; + const doc = await documentsApi.create(selectedCompanyId, { + title: `Doc: ${issueTitle}`, + issueId, + }); + setShowIssuePicker(false); + navigate(`/documents/${doc.id}`); + }; + + if (!selectedCompanyId) { + return ; + } + + if (isLoading) { + return ; + } + + return ( +
+ {error &&

{error.message}

} + + {/* Issue picker modal */} + {showIssuePicker && ( +
setShowIssuePicker(false)}> +
e.stopPropagation()}> +

Select an issue for this document

+

Documents are created from issues. Comment on the issue to request AI edits.

+
+ {(issues as any[])?.map((issue: any) => ( + + ))} + {(!issues || (issues as any[])?.length === 0) && ( +

No issues found. Create an issue first.

+ )} +
+
+
+ )} + + {documents && documents.length === 0 && ( + + )} + + {documents && documents.length > 0 && ( + <> +
+ +
+ navigate(`/documents/${id}`)} + /> + + )} +
+ ); +} diff --git a/ui/src/pages/GoalDetail.tsx b/ui/src/pages/GoalDetail.tsx index 3eed0ba5e5f..6d32ef84735 100644 --- a/ui/src/pages/GoalDetail.tsx +++ b/ui/src/pages/GoalDetail.tsx @@ -16,6 +16,7 @@ import { InlineEditor } from "../components/InlineEditor"; import { EntityRow } from "../components/EntityRow"; import { PageSkeleton } from "../components/PageSkeleton"; import { projectUrl } from "../lib/utils"; +import { LinkedDocuments } from "../components/LinkedDocuments"; import { Button } from "@/components/ui/button"; import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"; import { Plus } from "lucide-react"; @@ -191,6 +192,10 @@ export function GoalDetail() { )} + + {resolvedCompanyId && goalId && ( + + )} ); } diff --git a/ui/src/pages/IssueDetail.tsx b/ui/src/pages/IssueDetail.tsx index bb152e175a0..6b5e6d25c9a 100644 --- a/ui/src/pages/IssueDetail.tsx +++ b/ui/src/pages/IssueDetail.tsx @@ -45,6 +45,7 @@ import { Trash2, } from "lucide-react"; import type { ActivityEvent } from "@paperclipai/shared"; +import { LinkedDocuments } from "../components/LinkedDocuments"; import type { Agent, IssueAttachment } from "@paperclipai/shared"; type CommentReassignment = { @@ -935,6 +936,9 @@ export function IssueDetail() { + {selectedCompanyId && issueId && ( + + )} );