소스 검색

text editor for notes

Bernt Christian Egeland 7 달 전
부모
커밋
34e0d26a0b

+ 773 - 5
package-lock.json

@@ -15,6 +15,11 @@
         "@react-pdf/renderer": "^4.3.2",
         "@sendgrid/mail": "^8.1.6",
         "@tanstack/react-query": "^5.90.21",
+        "@tiptap/extension-placeholder": "^3.19.0",
+        "@tiptap/extension-underline": "^3.19.0",
+        "@tiptap/pm": "^3.19.0",
+        "@tiptap/react": "^3.19.0",
+        "@tiptap/starter-kit": "^3.19.0",
         "archiver": "^7.0.1",
         "better-auth": "^1.4.18",
         "bson": "^7.2.0",
@@ -4769,6 +4774,12 @@
         "url": "https://opencollective.com/immer"
       }
     },
+    "node_modules/@remirror/core-constants": {
+      "version": "3.0.0",
+      "resolved": "https://registry.npmjs.org/@remirror/core-constants/-/core-constants-3.0.0.tgz",
+      "integrity": "sha512-42aWfPrimMfDKDi4YegyS7x+/0tlzaqwPQCULLanv3DMIlu96KTJR0fM5isWX2UViOqlGnX6YFgqWepcX+XMNg==",
+      "license": "MIT"
+    },
     "node_modules/@rtsao/scc": {
       "version": "1.1.0",
       "resolved": "https://registry.npmjs.org/@rtsao/scc/-/scc-1.1.0.tgz",
@@ -5752,6 +5763,453 @@
         "react": "^18 || ^19"
       }
     },
+    "node_modules/@tiptap/core": {
+      "version": "3.19.0",
+      "resolved": "https://registry.npmjs.org/@tiptap/core/-/core-3.19.0.tgz",
+      "integrity": "sha512-bpqELwPW+DG8gWiD8iiFtSl4vIBooG5uVJod92Qxn3rA9nFatyXRr4kNbMJmOZ66ezUvmCjXVe/5/G4i5cyzKA==",
+      "license": "MIT",
+      "funding": {
+        "type": "github",
+        "url": "https://github.com/sponsors/ueberdosis"
+      },
+      "peerDependencies": {
+        "@tiptap/pm": "^3.19.0"
+      }
+    },
+    "node_modules/@tiptap/extension-blockquote": {
+      "version": "3.19.0",
+      "resolved": "https://registry.npmjs.org/@tiptap/extension-blockquote/-/extension-blockquote-3.19.0.tgz",
+      "integrity": "sha512-y3UfqY9KD5XwWz3ndiiJ089Ij2QKeiXy/g1/tlAN/F1AaWsnkHEHMLxCP1BIqmMpwsX7rZjMLN7G5Lp7c9682A==",
+      "license": "MIT",
+      "funding": {
+        "type": "github",
+        "url": "https://github.com/sponsors/ueberdosis"
+      },
+      "peerDependencies": {
+        "@tiptap/core": "^3.19.0"
+      }
+    },
+    "node_modules/@tiptap/extension-bold": {
+      "version": "3.19.0",
+      "resolved": "https://registry.npmjs.org/@tiptap/extension-bold/-/extension-bold-3.19.0.tgz",
+      "integrity": "sha512-UZgb1d0XK4J/JRIZ7jW+s4S6KjuEDT2z1PPM6ugcgofgJkWQvRZelCPbmtSFd3kwsD+zr9UPVgTh9YIuGQ8t+Q==",
+      "license": "MIT",
+      "funding": {
+        "type": "github",
+        "url": "https://github.com/sponsors/ueberdosis"
+      },
+      "peerDependencies": {
+        "@tiptap/core": "^3.19.0"
+      }
+    },
+    "node_modules/@tiptap/extension-bubble-menu": {
+      "version": "3.19.0",
+      "resolved": "https://registry.npmjs.org/@tiptap/extension-bubble-menu/-/extension-bubble-menu-3.19.0.tgz",
+      "integrity": "sha512-klNVIYGCdznhFkrRokzGd6cwzoi8J7E5KbuOfZBwFwhMKZhlz/gJfKmYg9TJopeUhrr2Z9yHgWTk8dh/YIJCdQ==",
+      "license": "MIT",
+      "optional": true,
+      "dependencies": {
+        "@floating-ui/dom": "^1.0.0"
+      },
+      "funding": {
+        "type": "github",
+        "url": "https://github.com/sponsors/ueberdosis"
+      },
+      "peerDependencies": {
+        "@tiptap/core": "^3.19.0",
+        "@tiptap/pm": "^3.19.0"
+      }
+    },
+    "node_modules/@tiptap/extension-bullet-list": {
+      "version": "3.19.0",
+      "resolved": "https://registry.npmjs.org/@tiptap/extension-bullet-list/-/extension-bullet-list-3.19.0.tgz",
+      "integrity": "sha512-F9uNnqd0xkJbMmRxVI5RuVxwB9JaCH/xtRqOUNQZnRBt7IdAElCY+Dvb4hMCtiNv+enGM/RFGJuFHR9TxmI7rw==",
+      "license": "MIT",
+      "funding": {
+        "type": "github",
+        "url": "https://github.com/sponsors/ueberdosis"
+      },
+      "peerDependencies": {
+        "@tiptap/extension-list": "^3.19.0"
+      }
+    },
+    "node_modules/@tiptap/extension-code": {
+      "version": "3.19.0",
+      "resolved": "https://registry.npmjs.org/@tiptap/extension-code/-/extension-code-3.19.0.tgz",
+      "integrity": "sha512-2kqqQIXBXj2Or+4qeY3WoE7msK+XaHKL6EKOcKlOP2BW8eYqNTPzNSL+PfBDQ3snA7ljZQkTs/j4GYDj90vR1A==",
+      "license": "MIT",
+      "funding": {
+        "type": "github",
+        "url": "https://github.com/sponsors/ueberdosis"
+      },
+      "peerDependencies": {
+        "@tiptap/core": "^3.19.0"
+      }
+    },
+    "node_modules/@tiptap/extension-code-block": {
+      "version": "3.19.0",
+      "resolved": "https://registry.npmjs.org/@tiptap/extension-code-block/-/extension-code-block-3.19.0.tgz",
+      "integrity": "sha512-b/2qR+tMn8MQb+eaFYgVk4qXnLNkkRYmwELQ8LEtEDQPxa5Vl7J3eu8+4OyoIFhZrNDZvvoEp80kHMCP8sI6rg==",
+      "license": "MIT",
+      "funding": {
+        "type": "github",
+        "url": "https://github.com/sponsors/ueberdosis"
+      },
+      "peerDependencies": {
+        "@tiptap/core": "^3.19.0",
+        "@tiptap/pm": "^3.19.0"
+      }
+    },
+    "node_modules/@tiptap/extension-document": {
+      "version": "3.19.0",
+      "resolved": "https://registry.npmjs.org/@tiptap/extension-document/-/extension-document-3.19.0.tgz",
+      "integrity": "sha512-AOf0kHKSFO0ymjVgYSYDncRXTITdTcrj1tqxVazrmO60KNl1Rc2dAggDvIVTEBy5NvceF0scc7q3sE/5ZtVV7A==",
+      "license": "MIT",
+      "funding": {
+        "type": "github",
+        "url": "https://github.com/sponsors/ueberdosis"
+      },
+      "peerDependencies": {
+        "@tiptap/core": "^3.19.0"
+      }
+    },
+    "node_modules/@tiptap/extension-dropcursor": {
+      "version": "3.19.0",
+      "resolved": "https://registry.npmjs.org/@tiptap/extension-dropcursor/-/extension-dropcursor-3.19.0.tgz",
+      "integrity": "sha512-sf3dEZXiLvsGqVK2maUIzXY6qtYYCvBumag7+VPTMGQ0D4hiZ1X/4ukt4+6VXDg5R2WP1CoIt/QvUetUjWNhbQ==",
+      "license": "MIT",
+      "funding": {
+        "type": "github",
+        "url": "https://github.com/sponsors/ueberdosis"
+      },
+      "peerDependencies": {
+        "@tiptap/extensions": "^3.19.0"
+      }
+    },
+    "node_modules/@tiptap/extension-floating-menu": {
+      "version": "3.19.0",
+      "resolved": "https://registry.npmjs.org/@tiptap/extension-floating-menu/-/extension-floating-menu-3.19.0.tgz",
+      "integrity": "sha512-JaoEkVRkt+Slq3tySlIsxnMnCjS0L5n1CA1hctjLy0iah8edetj3XD5mVv5iKqDzE+LIjF4nwLRRVKJPc8hFBg==",
+      "license": "MIT",
+      "optional": true,
+      "funding": {
+        "type": "github",
+        "url": "https://github.com/sponsors/ueberdosis"
+      },
+      "peerDependencies": {
+        "@floating-ui/dom": "^1.0.0",
+        "@tiptap/core": "^3.19.0",
+        "@tiptap/pm": "^3.19.0"
+      }
+    },
+    "node_modules/@tiptap/extension-gapcursor": {
+      "version": "3.19.0",
+      "resolved": "https://registry.npmjs.org/@tiptap/extension-gapcursor/-/extension-gapcursor-3.19.0.tgz",
+      "integrity": "sha512-w7DACS4oSZaDWjz7gropZHPc9oXqC9yERZTcjWxyORuuIh1JFf0TRYspleK+OK28plK/IftojD/yUDn1MTRhvA==",
+      "license": "MIT",
+      "funding": {
+        "type": "github",
+        "url": "https://github.com/sponsors/ueberdosis"
+      },
+      "peerDependencies": {
+        "@tiptap/extensions": "^3.19.0"
+      }
+    },
+    "node_modules/@tiptap/extension-hard-break": {
+      "version": "3.19.0",
+      "resolved": "https://registry.npmjs.org/@tiptap/extension-hard-break/-/extension-hard-break-3.19.0.tgz",
+      "integrity": "sha512-lAmQraYhPS5hafvCl74xDB5+bLuNwBKIEsVoim35I0sDJj5nTrfhaZgMJ91VamMvT+6FF5f1dvBlxBxAWa8jew==",
+      "license": "MIT",
+      "funding": {
+        "type": "github",
+        "url": "https://github.com/sponsors/ueberdosis"
+      },
+      "peerDependencies": {
+        "@tiptap/core": "^3.19.0"
+      }
+    },
+    "node_modules/@tiptap/extension-heading": {
+      "version": "3.19.0",
+      "resolved": "https://registry.npmjs.org/@tiptap/extension-heading/-/extension-heading-3.19.0.tgz",
+      "integrity": "sha512-uLpLlfyp086WYNOc0ekm1gIZNlEDfmzOhKzB0Hbyi6jDagTS+p9mxUNYeYOn9jPUxpFov43+Wm/4E24oY6B+TQ==",
+      "license": "MIT",
+      "funding": {
+        "type": "github",
+        "url": "https://github.com/sponsors/ueberdosis"
+      },
+      "peerDependencies": {
+        "@tiptap/core": "^3.19.0"
+      }
+    },
+    "node_modules/@tiptap/extension-horizontal-rule": {
+      "version": "3.19.0",
+      "resolved": "https://registry.npmjs.org/@tiptap/extension-horizontal-rule/-/extension-horizontal-rule-3.19.0.tgz",
+      "integrity": "sha512-iqUHmgMGhMgYGwG6L/4JdelVQ5Mstb4qHcgTGd/4dkcUOepILvhdxajPle7OEdf9sRgjQO6uoAU5BVZVC26+ng==",
+      "license": "MIT",
+      "funding": {
+        "type": "github",
+        "url": "https://github.com/sponsors/ueberdosis"
+      },
+      "peerDependencies": {
+        "@tiptap/core": "^3.19.0",
+        "@tiptap/pm": "^3.19.0"
+      }
+    },
+    "node_modules/@tiptap/extension-italic": {
+      "version": "3.19.0",
+      "resolved": "https://registry.npmjs.org/@tiptap/extension-italic/-/extension-italic-3.19.0.tgz",
+      "integrity": "sha512-6GffxOnS/tWyCbDkirWNZITiXRta9wrCmrfa4rh+v32wfaOL1RRQNyqo9qN6Wjyl1R42Js+yXTzTTzZsOaLMYA==",
+      "license": "MIT",
+      "funding": {
+        "type": "github",
+        "url": "https://github.com/sponsors/ueberdosis"
+      },
+      "peerDependencies": {
+        "@tiptap/core": "^3.19.0"
+      }
+    },
+    "node_modules/@tiptap/extension-link": {
+      "version": "3.19.0",
+      "resolved": "https://registry.npmjs.org/@tiptap/extension-link/-/extension-link-3.19.0.tgz",
+      "integrity": "sha512-HEGDJnnCPfr7KWu7Dsq+eRRe/mBCsv6DuI+7fhOCLDJjjKzNgrX2abbo/zG3D/4lCVFaVb+qawgJubgqXR/Smw==",
+      "license": "MIT",
+      "dependencies": {
+        "linkifyjs": "^4.3.2"
+      },
+      "funding": {
+        "type": "github",
+        "url": "https://github.com/sponsors/ueberdosis"
+      },
+      "peerDependencies": {
+        "@tiptap/core": "^3.19.0",
+        "@tiptap/pm": "^3.19.0"
+      }
+    },
+    "node_modules/@tiptap/extension-list": {
+      "version": "3.19.0",
+      "resolved": "https://registry.npmjs.org/@tiptap/extension-list/-/extension-list-3.19.0.tgz",
+      "integrity": "sha512-N6nKbFB2VwMsPlCw67RlAtYSK48TAsAUgjnD+vd3ieSlIufdQnLXDFUP6hFKx9mwoUVUgZGz02RA6bkxOdYyTw==",
+      "license": "MIT",
+      "funding": {
+        "type": "github",
+        "url": "https://github.com/sponsors/ueberdosis"
+      },
+      "peerDependencies": {
+        "@tiptap/core": "^3.19.0",
+        "@tiptap/pm": "^3.19.0"
+      }
+    },
+    "node_modules/@tiptap/extension-list-item": {
+      "version": "3.19.0",
+      "resolved": "https://registry.npmjs.org/@tiptap/extension-list-item/-/extension-list-item-3.19.0.tgz",
+      "integrity": "sha512-VsSKuJz4/Tb6ZmFkXqWpDYkRzmaLTyE6dNSEpNmUpmZ32sMqo58mt11/huADNwfBFB0Ve7siH/VnFNIJYY3xvg==",
+      "license": "MIT",
+      "funding": {
+        "type": "github",
+        "url": "https://github.com/sponsors/ueberdosis"
+      },
+      "peerDependencies": {
+        "@tiptap/extension-list": "^3.19.0"
+      }
+    },
+    "node_modules/@tiptap/extension-list-keymap": {
+      "version": "3.19.0",
+      "resolved": "https://registry.npmjs.org/@tiptap/extension-list-keymap/-/extension-list-keymap-3.19.0.tgz",
+      "integrity": "sha512-bxgmAgA3RzBGA0GyTwS2CC1c+QjkJJq9hC+S6PSOWELGRiTbwDN3MANksFXLjntkTa0N5fOnL27vBHtMStURqw==",
+      "license": "MIT",
+      "funding": {
+        "type": "github",
+        "url": "https://github.com/sponsors/ueberdosis"
+      },
+      "peerDependencies": {
+        "@tiptap/extension-list": "^3.19.0"
+      }
+    },
+    "node_modules/@tiptap/extension-ordered-list": {
+      "version": "3.19.0",
+      "resolved": "https://registry.npmjs.org/@tiptap/extension-ordered-list/-/extension-ordered-list-3.19.0.tgz",
+      "integrity": "sha512-cxGsINquwHYE1kmhAcLNLHAofmoDEG6jbesR5ybl7tU5JwtKVO7S/xZatll2DU1dsDAXWPWEeeMl4e/9svYjCg==",
+      "license": "MIT",
+      "funding": {
+        "type": "github",
+        "url": "https://github.com/sponsors/ueberdosis"
+      },
+      "peerDependencies": {
+        "@tiptap/extension-list": "^3.19.0"
+      }
+    },
+    "node_modules/@tiptap/extension-paragraph": {
+      "version": "3.19.0",
+      "resolved": "https://registry.npmjs.org/@tiptap/extension-paragraph/-/extension-paragraph-3.19.0.tgz",
+      "integrity": "sha512-xWa6gj82l5+AzdYyrSk9P4ynySaDzg/SlR1FarXE5yPXibYzpS95IWaVR0m2Qaz7Rrk+IiYOTGxGRxcHLOelNg==",
+      "license": "MIT",
+      "funding": {
+        "type": "github",
+        "url": "https://github.com/sponsors/ueberdosis"
+      },
+      "peerDependencies": {
+        "@tiptap/core": "^3.19.0"
+      }
+    },
+    "node_modules/@tiptap/extension-placeholder": {
+      "version": "3.19.0",
+      "resolved": "https://registry.npmjs.org/@tiptap/extension-placeholder/-/extension-placeholder-3.19.0.tgz",
+      "integrity": "sha512-i15OfgyI4IDCYAcYSKUMnuZkYuUInfanjf9zquH8J2BETiomf/jZldVCp/QycMJ8DOXZ38fXDc99wOygnSNySg==",
+      "license": "MIT",
+      "funding": {
+        "type": "github",
+        "url": "https://github.com/sponsors/ueberdosis"
+      },
+      "peerDependencies": {
+        "@tiptap/extensions": "^3.19.0"
+      }
+    },
+    "node_modules/@tiptap/extension-strike": {
+      "version": "3.19.0",
+      "resolved": "https://registry.npmjs.org/@tiptap/extension-strike/-/extension-strike-3.19.0.tgz",
+      "integrity": "sha512-xYpabHsv7PccLUBQaP8AYiFCnYbx6P93RHPd0lgNwhdOjYFd931Zy38RyoxPHAgbYVmhf1iyx7lpuLtBnhS5dA==",
+      "license": "MIT",
+      "funding": {
+        "type": "github",
+        "url": "https://github.com/sponsors/ueberdosis"
+      },
+      "peerDependencies": {
+        "@tiptap/core": "^3.19.0"
+      }
+    },
+    "node_modules/@tiptap/extension-text": {
+      "version": "3.19.0",
+      "resolved": "https://registry.npmjs.org/@tiptap/extension-text/-/extension-text-3.19.0.tgz",
+      "integrity": "sha512-K95+SnbZy0h6hNFtfy23n8t/nOcTFEf69In9TSFVVmwn/Nwlke+IfiESAkqbt1/7sKJeegRXYO7WzFEmFl9Q/g==",
+      "license": "MIT",
+      "funding": {
+        "type": "github",
+        "url": "https://github.com/sponsors/ueberdosis"
+      },
+      "peerDependencies": {
+        "@tiptap/core": "^3.19.0"
+      }
+    },
+    "node_modules/@tiptap/extension-underline": {
+      "version": "3.19.0",
+      "resolved": "https://registry.npmjs.org/@tiptap/extension-underline/-/extension-underline-3.19.0.tgz",
+      "integrity": "sha512-800MGEWfG49j10wQzAFiW/ele1HT04MamcL8iyuPNu7ZbjbGN2yknvdrJlRy7hZlzIrVkZMr/1tz62KN33VHIw==",
+      "license": "MIT",
+      "funding": {
+        "type": "github",
+        "url": "https://github.com/sponsors/ueberdosis"
+      },
+      "peerDependencies": {
+        "@tiptap/core": "^3.19.0"
+      }
+    },
+    "node_modules/@tiptap/extensions": {
+      "version": "3.19.0",
+      "resolved": "https://registry.npmjs.org/@tiptap/extensions/-/extensions-3.19.0.tgz",
+      "integrity": "sha512-ZmGUhLbMWaGqnJh2Bry+6V4M6gMpUDYo4D1xNux5Gng/E/eYtc+PMxMZ/6F7tNTAuujLBOQKj6D+4SsSm457jw==",
+      "license": "MIT",
+      "funding": {
+        "type": "github",
+        "url": "https://github.com/sponsors/ueberdosis"
+      },
+      "peerDependencies": {
+        "@tiptap/core": "^3.19.0",
+        "@tiptap/pm": "^3.19.0"
+      }
+    },
+    "node_modules/@tiptap/pm": {
+      "version": "3.19.0",
+      "resolved": "https://registry.npmjs.org/@tiptap/pm/-/pm-3.19.0.tgz",
+      "integrity": "sha512-789zcnM4a8OWzvbD2DL31d0wbSm9BVeO/R7PLQwLIGysDI3qzrcclyZ8yhqOEVuvPitRRwYLq+mY14jz7kY4cw==",
+      "license": "MIT",
+      "dependencies": {
+        "prosemirror-changeset": "^2.3.0",
+        "prosemirror-collab": "^1.3.1",
+        "prosemirror-commands": "^1.6.2",
+        "prosemirror-dropcursor": "^1.8.1",
+        "prosemirror-gapcursor": "^1.3.2",
+        "prosemirror-history": "^1.4.1",
+        "prosemirror-inputrules": "^1.4.0",
+        "prosemirror-keymap": "^1.2.2",
+        "prosemirror-markdown": "^1.13.1",
+        "prosemirror-menu": "^1.2.4",
+        "prosemirror-model": "^1.24.1",
+        "prosemirror-schema-basic": "^1.2.3",
+        "prosemirror-schema-list": "^1.5.0",
+        "prosemirror-state": "^1.4.3",
+        "prosemirror-tables": "^1.6.4",
+        "prosemirror-trailing-node": "^3.0.0",
+        "prosemirror-transform": "^1.10.2",
+        "prosemirror-view": "^1.38.1"
+      },
+      "funding": {
+        "type": "github",
+        "url": "https://github.com/sponsors/ueberdosis"
+      }
+    },
+    "node_modules/@tiptap/react": {
+      "version": "3.19.0",
+      "resolved": "https://registry.npmjs.org/@tiptap/react/-/react-3.19.0.tgz",
+      "integrity": "sha512-GQQMUUXMpNd8tRjc1jDK3tDRXFugJO7C928EqmeBcBzTKDrFIJ3QUoZKEPxUNb6HWhZ2WL7q00fiMzsv4DNSmg==",
+      "license": "MIT",
+      "dependencies": {
+        "@types/use-sync-external-store": "^0.0.6",
+        "fast-equals": "^5.3.3",
+        "use-sync-external-store": "^1.4.0"
+      },
+      "funding": {
+        "type": "github",
+        "url": "https://github.com/sponsors/ueberdosis"
+      },
+      "optionalDependencies": {
+        "@tiptap/extension-bubble-menu": "^3.19.0",
+        "@tiptap/extension-floating-menu": "^3.19.0"
+      },
+      "peerDependencies": {
+        "@tiptap/core": "^3.19.0",
+        "@tiptap/pm": "^3.19.0",
+        "@types/react": "^17.0.0 || ^18.0.0 || ^19.0.0",
+        "@types/react-dom": "^17.0.0 || ^18.0.0 || ^19.0.0",
+        "react": "^17.0.0 || ^18.0.0 || ^19.0.0",
+        "react-dom": "^17.0.0 || ^18.0.0 || ^19.0.0"
+      }
+    },
+    "node_modules/@tiptap/starter-kit": {
+      "version": "3.19.0",
+      "resolved": "https://registry.npmjs.org/@tiptap/starter-kit/-/starter-kit-3.19.0.tgz",
+      "integrity": "sha512-dTCkHEz+Y8ADxX7h+xvl6caAj+3nII/wMB1rTQchSuNKqJTOrzyUsCWm094+IoZmLT738wANE0fRIgziNHs/ug==",
+      "license": "MIT",
+      "dependencies": {
+        "@tiptap/core": "^3.19.0",
+        "@tiptap/extension-blockquote": "^3.19.0",
+        "@tiptap/extension-bold": "^3.19.0",
+        "@tiptap/extension-bullet-list": "^3.19.0",
+        "@tiptap/extension-code": "^3.19.0",
+        "@tiptap/extension-code-block": "^3.19.0",
+        "@tiptap/extension-document": "^3.19.0",
+        "@tiptap/extension-dropcursor": "^3.19.0",
+        "@tiptap/extension-gapcursor": "^3.19.0",
+        "@tiptap/extension-hard-break": "^3.19.0",
+        "@tiptap/extension-heading": "^3.19.0",
+        "@tiptap/extension-horizontal-rule": "^3.19.0",
+        "@tiptap/extension-italic": "^3.19.0",
+        "@tiptap/extension-link": "^3.19.0",
+        "@tiptap/extension-list": "^3.19.0",
+        "@tiptap/extension-list-item": "^3.19.0",
+        "@tiptap/extension-list-keymap": "^3.19.0",
+        "@tiptap/extension-ordered-list": "^3.19.0",
+        "@tiptap/extension-paragraph": "^3.19.0",
+        "@tiptap/extension-strike": "^3.19.0",
+        "@tiptap/extension-text": "^3.19.0",
+        "@tiptap/extension-underline": "^3.19.0",
+        "@tiptap/extensions": "^3.19.0",
+        "@tiptap/pm": "^3.19.0"
+      },
+      "funding": {
+        "type": "github",
+        "url": "https://github.com/sponsors/ueberdosis"
+      }
+    },
     "node_modules/@ts-morph/common": {
       "version": "0.27.0",
       "resolved": "https://registry.npmjs.org/@ts-morph/common/-/common-0.27.0.tgz",
@@ -5926,12 +6384,34 @@
       "dev": true,
       "license": "MIT"
     },
+    "node_modules/@types/linkify-it": {
+      "version": "5.0.0",
+      "resolved": "https://registry.npmjs.org/@types/linkify-it/-/linkify-it-5.0.0.tgz",
+      "integrity": "sha512-sVDA58zAw4eWAffKOaQH5/5j3XeayukzDk+ewSsnv3p4yJEZHCCzMDiZM8e0OUrRvmpGZ85jf4yDHkHsgBNr9Q==",
+      "license": "MIT"
+    },
     "node_modules/@types/luxon": {
       "version": "3.7.1",
       "resolved": "https://registry.npmjs.org/@types/luxon/-/luxon-3.7.1.tgz",
       "integrity": "sha512-H3iskjFIAn5SlJU7OuxUmTEpebK6TKB8rxZShDslBMZJ5u9S//KM1sbdAisiSrqwLQncVjnpi2OK2J51h+4lsg==",
       "license": "MIT"
     },
+    "node_modules/@types/markdown-it": {
+      "version": "14.1.2",
+      "resolved": "https://registry.npmjs.org/@types/markdown-it/-/markdown-it-14.1.2.tgz",
+      "integrity": "sha512-promo4eFwuiW+TfGxhi+0x3czqTYJkG8qB17ZUJiVF10Xm7NLVRSLUsfRTU/6h1e24VvRnXCx+hG7li58lkzog==",
+      "license": "MIT",
+      "dependencies": {
+        "@types/linkify-it": "^5",
+        "@types/mdurl": "^2"
+      }
+    },
+    "node_modules/@types/mdurl": {
+      "version": "2.0.0",
+      "resolved": "https://registry.npmjs.org/@types/mdurl/-/mdurl-2.0.0.tgz",
+      "integrity": "sha512-RGdgjQUZba5p6QEFAVx2OGb8rQDL/cPRG7GiedRzMcJ1tYnUANBncjbSB1NRGwbvjcPeikRABz2nshyPk1bhWg==",
+      "license": "MIT"
+    },
     "node_modules/@types/node": {
       "version": "20.19.33",
       "resolved": "https://registry.npmjs.org/@types/node/-/node-20.19.33.tgz",
@@ -5956,7 +6436,6 @@
       "version": "19.2.14",
       "resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.14.tgz",
       "integrity": "sha512-ilcTH/UniCkMdtexkoCN0bI7pMcJDvmQFPvuPvmEaYA/NSfFTAgdUSLAoVjaRJm7+6PvcM+q1zYOwS4wTYMF9w==",
-      "devOptional": true,
       "license": "MIT",
       "dependencies": {
         "csstype": "^3.2.2"
@@ -5966,7 +6445,6 @@
       "version": "19.2.3",
       "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-19.2.3.tgz",
       "integrity": "sha512-jp2L/eY6fn+KgVVQAOqYItbF0VY/YApe5Mz2F0aykSO8gx31bYCZyvSeYxCHKvzHG5eZjc+zyaS5BrBWya2+kQ==",
-      "devOptional": true,
       "license": "MIT",
       "peerDependencies": {
         "@types/react": "^19.2.0"
@@ -6753,7 +7231,6 @@
       "version": "2.0.1",
       "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz",
       "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==",
-      "dev": true,
       "license": "Python-2.0"
     },
     "node_modules/aria-hidden": {
@@ -7998,6 +8475,12 @@
         "node": ">= 14"
       }
     },
+    "node_modules/crelt": {
+      "version": "1.0.6",
+      "resolved": "https://registry.npmjs.org/crelt/-/crelt-1.0.6.tgz",
+      "integrity": "sha512-VQ2MBenTq1fWZUH9DJNGti7kKv6EeAuYr3cLwxUWhIu1baTaXh4Ib5W2CqHVqib4/MqbYGJqiL3Zb8GJZr3l4g==",
+      "license": "MIT"
+    },
     "node_modules/cron": {
       "version": "4.4.0",
       "resolved": "https://registry.npmjs.org/cron/-/cron-4.4.0.tgz",
@@ -8052,7 +8535,6 @@
       "version": "3.2.3",
       "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz",
       "integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==",
-      "devOptional": true,
       "license": "MIT"
     },
     "node_modules/d3-array": {
@@ -8613,6 +9095,18 @@
         "node": ">=10.13.0"
       }
     },
+    "node_modules/entities": {
+      "version": "4.5.0",
+      "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz",
+      "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==",
+      "license": "BSD-2-Clause",
+      "engines": {
+        "node": ">=0.12"
+      },
+      "funding": {
+        "url": "https://github.com/fb55/entities?sponsor=1"
+      }
+    },
     "node_modules/env-paths": {
       "version": "2.2.1",
       "resolved": "https://registry.npmjs.org/env-paths/-/env-paths-2.2.1.tgz",
@@ -8837,7 +9331,6 @@
       "version": "4.0.0",
       "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz",
       "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==",
-      "dev": true,
       "license": "MIT",
       "engines": {
         "node": ">=10"
@@ -9474,6 +9967,15 @@
       "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==",
       "license": "MIT"
     },
+    "node_modules/fast-equals": {
+      "version": "5.4.0",
+      "resolved": "https://registry.npmjs.org/fast-equals/-/fast-equals-5.4.0.tgz",
+      "integrity": "sha512-jt2DW/aNFNwke7AUd+Z+e6pz39KO5rzdbbFCg2sGafS4mk13MI7Z8O5z9cADNn5lhGODIgLwug6TZO2ctf7kcw==",
+      "license": "MIT",
+      "engines": {
+        "node": ">=6.0.0"
+      }
+    },
     "node_modules/fast-fifo": {
       "version": "1.3.2",
       "resolved": "https://registry.npmjs.org/fast-fifo/-/fast-fifo-1.3.2.tgz",
@@ -11772,6 +12274,21 @@
       "dev": true,
       "license": "MIT"
     },
+    "node_modules/linkify-it": {
+      "version": "5.0.0",
+      "resolved": "https://registry.npmjs.org/linkify-it/-/linkify-it-5.0.0.tgz",
+      "integrity": "sha512-5aHCbzQRADcdP+ATqnDuhhJ/MRIqDkZX5pyjFHRRysS8vZ5AbqGEoFIb6pYHPZ+L/OC2Lc+xT8uHVVR5CAK/wQ==",
+      "license": "MIT",
+      "dependencies": {
+        "uc.micro": "^2.0.0"
+      }
+    },
+    "node_modules/linkifyjs": {
+      "version": "4.3.2",
+      "resolved": "https://registry.npmjs.org/linkifyjs/-/linkifyjs-4.3.2.tgz",
+      "integrity": "sha512-NT1CJtq3hHIreOianA8aSXn6Cw0JzYOuDQbOrSPe7gqFnCpKP++MQe3ODgO3oh2GJFORkAAdqredOa60z63GbA==",
+      "license": "MIT"
+    },
     "node_modules/locate-path": {
       "version": "6.0.0",
       "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz",
@@ -11933,6 +12450,23 @@
         "node": ">=18.0.0"
       }
     },
+    "node_modules/markdown-it": {
+      "version": "14.1.1",
+      "resolved": "https://registry.npmjs.org/markdown-it/-/markdown-it-14.1.1.tgz",
+      "integrity": "sha512-BuU2qnTti9YKgK5N+IeMubp14ZUKUUw7yeJbkjtosvHiP0AZ5c8IAgEMk79D0eC8F23r4Ac/q8cAIFdm2FtyoA==",
+      "license": "MIT",
+      "dependencies": {
+        "argparse": "^2.0.1",
+        "entities": "^4.4.0",
+        "linkify-it": "^5.0.0",
+        "mdurl": "^2.0.0",
+        "punycode.js": "^2.3.1",
+        "uc.micro": "^2.1.0"
+      },
+      "bin": {
+        "markdown-it": "bin/markdown-it.mjs"
+      }
+    },
     "node_modules/math-intrinsics": {
       "version": "1.1.0",
       "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz",
@@ -11942,6 +12476,12 @@
         "node": ">= 0.4"
       }
     },
+    "node_modules/mdurl": {
+      "version": "2.0.0",
+      "resolved": "https://registry.npmjs.org/mdurl/-/mdurl-2.0.0.tgz",
+      "integrity": "sha512-Lf+9+2r+Tdp5wXDXC4PcIBjTDtq4UKjCPMQhKIuzpJNW0b96kVqSwW0bT7FhRSfmAiFYgP+SCRvdrDozfh0U5w==",
+      "license": "MIT"
+    },
     "node_modules/media-engine": {
       "version": "1.0.3",
       "resolved": "https://registry.npmjs.org/media-engine/-/media-engine-1.0.3.tgz",
@@ -12739,6 +13279,12 @@
         "url": "https://github.com/chalk/chalk?sponsor=1"
       }
     },
+    "node_modules/orderedmap": {
+      "version": "2.1.1",
+      "resolved": "https://registry.npmjs.org/orderedmap/-/orderedmap-2.1.1.tgz",
+      "integrity": "sha512-TvAWxi0nDe1j/rtMcWcIj94+Ffe6n7zhow33h40SKxmsmozs6dz/e+EajymfoFcHd7sxNn8yHM8839uixMOV6g==",
+      "license": "MIT"
+    },
     "node_modules/outvariant": {
       "version": "1.4.3",
       "resolved": "https://registry.npmjs.org/outvariant/-/outvariant-1.4.3.tgz",
@@ -13196,6 +13742,201 @@
         "react-is": "^16.13.1"
       }
     },
+    "node_modules/prosemirror-changeset": {
+      "version": "2.4.0",
+      "resolved": "https://registry.npmjs.org/prosemirror-changeset/-/prosemirror-changeset-2.4.0.tgz",
+      "integrity": "sha512-LvqH2v7Q2SF6yxatuPP2e8vSUKS/L+xAU7dPDC4RMyHMhZoGDfBC74mYuyYF4gLqOEG758wajtyhNnsTkuhvng==",
+      "license": "MIT",
+      "dependencies": {
+        "prosemirror-transform": "^1.0.0"
+      }
+    },
+    "node_modules/prosemirror-collab": {
+      "version": "1.3.1",
+      "resolved": "https://registry.npmjs.org/prosemirror-collab/-/prosemirror-collab-1.3.1.tgz",
+      "integrity": "sha512-4SnynYR9TTYaQVXd/ieUvsVV4PDMBzrq2xPUWutHivDuOshZXqQ5rGbZM84HEaXKbLdItse7weMGOUdDVcLKEQ==",
+      "license": "MIT",
+      "dependencies": {
+        "prosemirror-state": "^1.0.0"
+      }
+    },
+    "node_modules/prosemirror-commands": {
+      "version": "1.7.1",
+      "resolved": "https://registry.npmjs.org/prosemirror-commands/-/prosemirror-commands-1.7.1.tgz",
+      "integrity": "sha512-rT7qZnQtx5c0/y/KlYaGvtG411S97UaL6gdp6RIZ23DLHanMYLyfGBV5DtSnZdthQql7W+lEVbpSfwtO8T+L2w==",
+      "license": "MIT",
+      "dependencies": {
+        "prosemirror-model": "^1.0.0",
+        "prosemirror-state": "^1.0.0",
+        "prosemirror-transform": "^1.10.2"
+      }
+    },
+    "node_modules/prosemirror-dropcursor": {
+      "version": "1.8.2",
+      "resolved": "https://registry.npmjs.org/prosemirror-dropcursor/-/prosemirror-dropcursor-1.8.2.tgz",
+      "integrity": "sha512-CCk6Gyx9+Tt2sbYk5NK0nB1ukHi2ryaRgadV/LvyNuO3ena1payM2z6Cg0vO1ebK8cxbzo41ku2DE5Axj1Zuiw==",
+      "license": "MIT",
+      "dependencies": {
+        "prosemirror-state": "^1.0.0",
+        "prosemirror-transform": "^1.1.0",
+        "prosemirror-view": "^1.1.0"
+      }
+    },
+    "node_modules/prosemirror-gapcursor": {
+      "version": "1.4.0",
+      "resolved": "https://registry.npmjs.org/prosemirror-gapcursor/-/prosemirror-gapcursor-1.4.0.tgz",
+      "integrity": "sha512-z00qvurSdCEWUIulij/isHaqu4uLS8r/Fi61IbjdIPJEonQgggbJsLnstW7Lgdk4zQ68/yr6B6bf7sJXowIgdQ==",
+      "license": "MIT",
+      "dependencies": {
+        "prosemirror-keymap": "^1.0.0",
+        "prosemirror-model": "^1.0.0",
+        "prosemirror-state": "^1.0.0",
+        "prosemirror-view": "^1.0.0"
+      }
+    },
+    "node_modules/prosemirror-history": {
+      "version": "1.5.0",
+      "resolved": "https://registry.npmjs.org/prosemirror-history/-/prosemirror-history-1.5.0.tgz",
+      "integrity": "sha512-zlzTiH01eKA55UAf1MEjtssJeHnGxO0j4K4Dpx+gnmX9n+SHNlDqI2oO1Kv1iPN5B1dm5fsljCfqKF9nFL6HRg==",
+      "license": "MIT",
+      "dependencies": {
+        "prosemirror-state": "^1.2.2",
+        "prosemirror-transform": "^1.0.0",
+        "prosemirror-view": "^1.31.0",
+        "rope-sequence": "^1.3.0"
+      }
+    },
+    "node_modules/prosemirror-inputrules": {
+      "version": "1.5.1",
+      "resolved": "https://registry.npmjs.org/prosemirror-inputrules/-/prosemirror-inputrules-1.5.1.tgz",
+      "integrity": "sha512-7wj4uMjKaXWAQ1CDgxNzNtR9AlsuwzHfdFH1ygEHA2KHF2DOEaXl1CJfNPAKCg9qNEh4rum975QLaCiQPyY6Fw==",
+      "license": "MIT",
+      "dependencies": {
+        "prosemirror-state": "^1.0.0",
+        "prosemirror-transform": "^1.0.0"
+      }
+    },
+    "node_modules/prosemirror-keymap": {
+      "version": "1.2.3",
+      "resolved": "https://registry.npmjs.org/prosemirror-keymap/-/prosemirror-keymap-1.2.3.tgz",
+      "integrity": "sha512-4HucRlpiLd1IPQQXNqeo81BGtkY8Ai5smHhKW9jjPKRc2wQIxksg7Hl1tTI2IfT2B/LgX6bfYvXxEpJl7aKYKw==",
+      "license": "MIT",
+      "dependencies": {
+        "prosemirror-state": "^1.0.0",
+        "w3c-keyname": "^2.2.0"
+      }
+    },
+    "node_modules/prosemirror-markdown": {
+      "version": "1.13.4",
+      "resolved": "https://registry.npmjs.org/prosemirror-markdown/-/prosemirror-markdown-1.13.4.tgz",
+      "integrity": "sha512-D98dm4cQ3Hs6EmjK500TdAOew4Z03EV71ajEFiWra3Upr7diytJsjF4mPV2dW+eK5uNectiRj0xFxYI9NLXDbw==",
+      "license": "MIT",
+      "dependencies": {
+        "@types/markdown-it": "^14.0.0",
+        "markdown-it": "^14.0.0",
+        "prosemirror-model": "^1.25.0"
+      }
+    },
+    "node_modules/prosemirror-menu": {
+      "version": "1.3.0",
+      "resolved": "https://registry.npmjs.org/prosemirror-menu/-/prosemirror-menu-1.3.0.tgz",
+      "integrity": "sha512-TImyPXCHPcDsSka2/lwJ6WjTASr4re/qWq1yoTTuLOqfXucwF6VcRa2LWCkM/EyTD1UO3CUwiH8qURJoWJRxwg==",
+      "license": "MIT",
+      "dependencies": {
+        "crelt": "^1.0.0",
+        "prosemirror-commands": "^1.0.0",
+        "prosemirror-history": "^1.0.0",
+        "prosemirror-state": "^1.0.0"
+      }
+    },
+    "node_modules/prosemirror-model": {
+      "version": "1.25.4",
+      "resolved": "https://registry.npmjs.org/prosemirror-model/-/prosemirror-model-1.25.4.tgz",
+      "integrity": "sha512-PIM7E43PBxKce8OQeezAs9j4TP+5yDpZVbuurd1h5phUxEKIu+G2a+EUZzIC5nS1mJktDJWzbqS23n1tsAf5QA==",
+      "license": "MIT",
+      "dependencies": {
+        "orderedmap": "^2.0.0"
+      }
+    },
+    "node_modules/prosemirror-schema-basic": {
+      "version": "1.2.4",
+      "resolved": "https://registry.npmjs.org/prosemirror-schema-basic/-/prosemirror-schema-basic-1.2.4.tgz",
+      "integrity": "sha512-ELxP4TlX3yr2v5rM7Sb70SqStq5NvI15c0j9j/gjsrO5vaw+fnnpovCLEGIcpeGfifkuqJwl4fon6b+KdrODYQ==",
+      "license": "MIT",
+      "dependencies": {
+        "prosemirror-model": "^1.25.0"
+      }
+    },
+    "node_modules/prosemirror-schema-list": {
+      "version": "1.5.1",
+      "resolved": "https://registry.npmjs.org/prosemirror-schema-list/-/prosemirror-schema-list-1.5.1.tgz",
+      "integrity": "sha512-927lFx/uwyQaGwJxLWCZRkjXG0p48KpMj6ueoYiu4JX05GGuGcgzAy62dfiV8eFZftgyBUvLx76RsMe20fJl+Q==",
+      "license": "MIT",
+      "dependencies": {
+        "prosemirror-model": "^1.0.0",
+        "prosemirror-state": "^1.0.0",
+        "prosemirror-transform": "^1.7.3"
+      }
+    },
+    "node_modules/prosemirror-state": {
+      "version": "1.4.4",
+      "resolved": "https://registry.npmjs.org/prosemirror-state/-/prosemirror-state-1.4.4.tgz",
+      "integrity": "sha512-6jiYHH2CIGbCfnxdHbXZ12gySFY/fz/ulZE333G6bPqIZ4F+TXo9ifiR86nAHpWnfoNjOb3o5ESi7J8Uz1jXHw==",
+      "license": "MIT",
+      "dependencies": {
+        "prosemirror-model": "^1.0.0",
+        "prosemirror-transform": "^1.0.0",
+        "prosemirror-view": "^1.27.0"
+      }
+    },
+    "node_modules/prosemirror-tables": {
+      "version": "1.8.5",
+      "resolved": "https://registry.npmjs.org/prosemirror-tables/-/prosemirror-tables-1.8.5.tgz",
+      "integrity": "sha512-V/0cDCsHKHe/tfWkeCmthNUcEp1IVO3p6vwN8XtwE9PZQLAZJigbw3QoraAdfJPir4NKJtNvOB8oYGKRl+t0Dw==",
+      "license": "MIT",
+      "dependencies": {
+        "prosemirror-keymap": "^1.2.3",
+        "prosemirror-model": "^1.25.4",
+        "prosemirror-state": "^1.4.4",
+        "prosemirror-transform": "^1.10.5",
+        "prosemirror-view": "^1.41.4"
+      }
+    },
+    "node_modules/prosemirror-trailing-node": {
+      "version": "3.0.0",
+      "resolved": "https://registry.npmjs.org/prosemirror-trailing-node/-/prosemirror-trailing-node-3.0.0.tgz",
+      "integrity": "sha512-xiun5/3q0w5eRnGYfNlW1uU9W6x5MoFKWwq/0TIRgt09lv7Hcser2QYV8t4muXbEr+Fwo0geYn79Xs4GKywrRQ==",
+      "license": "MIT",
+      "dependencies": {
+        "@remirror/core-constants": "3.0.0",
+        "escape-string-regexp": "^4.0.0"
+      },
+      "peerDependencies": {
+        "prosemirror-model": "^1.22.1",
+        "prosemirror-state": "^1.4.2",
+        "prosemirror-view": "^1.33.8"
+      }
+    },
+    "node_modules/prosemirror-transform": {
+      "version": "1.11.0",
+      "resolved": "https://registry.npmjs.org/prosemirror-transform/-/prosemirror-transform-1.11.0.tgz",
+      "integrity": "sha512-4I7Ce4KpygXb9bkiPS3hTEk4dSHorfRw8uI0pE8IhxlK2GXsqv5tIA7JUSxtSu7u8APVOTtbUBxTmnHIxVkIJw==",
+      "license": "MIT",
+      "dependencies": {
+        "prosemirror-model": "^1.21.0"
+      }
+    },
+    "node_modules/prosemirror-view": {
+      "version": "1.41.6",
+      "resolved": "https://registry.npmjs.org/prosemirror-view/-/prosemirror-view-1.41.6.tgz",
+      "integrity": "sha512-mxpcDG4hNQa/CPtzxjdlir5bJFDlm0/x5nGBbStB2BWX+XOQ9M8ekEG+ojqB5BcVu2Rc80/jssCMZzSstJuSYg==",
+      "license": "MIT",
+      "dependencies": {
+        "prosemirror-model": "^1.20.0",
+        "prosemirror-state": "^1.0.0",
+        "prosemirror-transform": "^1.1.0"
+      }
+    },
     "node_modules/proxy-addr": {
       "version": "2.0.7",
       "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz",
@@ -13226,6 +13967,15 @@
         "node": ">=6"
       }
     },
+    "node_modules/punycode.js": {
+      "version": "2.3.1",
+      "resolved": "https://registry.npmjs.org/punycode.js/-/punycode.js-2.3.1.tgz",
+      "integrity": "sha512-uxFIHU0YlHYhDQtV4R9J6a52SLx28BCjT+4ieh7IGbgwVJWO+km431c4yRlREUAsAmt/uMjQUyQHNEPf0M39CA==",
+      "license": "MIT",
+      "engines": {
+        "node": ">=6"
+      }
+    },
     "node_modules/pure-rand": {
       "version": "6.1.0",
       "resolved": "https://registry.npmjs.org/pure-rand/-/pure-rand-6.1.0.tgz",
@@ -13869,6 +14619,12 @@
         "node": ">=0.10.0"
       }
     },
+    "node_modules/rope-sequence": {
+      "version": "1.3.4",
+      "resolved": "https://registry.npmjs.org/rope-sequence/-/rope-sequence-1.3.4.tgz",
+      "integrity": "sha512-UT5EDe2cu2E/6O4igUr5PSFs23nvvukicWHx6GnOPlHAiiYbzNuCRQCuiUdHJQcqKalLKlrYJnjY0ySGsXNQXQ==",
+      "license": "MIT"
+    },
     "node_modules/rou3": {
       "version": "0.7.12",
       "resolved": "https://registry.npmjs.org/rou3/-/rou3-0.7.12.tgz",
@@ -15352,6 +16108,12 @@
         "typescript": ">=4.8.4 <6.0.0"
       }
     },
+    "node_modules/uc.micro": {
+      "version": "2.1.0",
+      "resolved": "https://registry.npmjs.org/uc.micro/-/uc.micro-2.1.0.tgz",
+      "integrity": "sha512-ARDJmphmdvUk6Glw7y9DQ2bFkKBHwQHLi2lsaH6PPmz/Ka9sFOBsBluozhDltWmnv9u/cF6Rt87znRTPV+yp/A==",
+      "license": "MIT"
+    },
     "node_modules/unbox-primitive": {
       "version": "1.1.0",
       "resolved": "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.1.0.tgz",
@@ -15656,6 +16418,12 @@
         "node": ">= 6"
       }
     },
+    "node_modules/w3c-keyname": {
+      "version": "2.2.8",
+      "resolved": "https://registry.npmjs.org/w3c-keyname/-/w3c-keyname-2.2.8.tgz",
+      "integrity": "sha512-dpojBhNsCNN7T82Tm7k26A6G9ML3NkhDsnw9n/eoxSRlVBB4CEtIQ/KTCLI2Fwf3ataSXRhYFkQi3SlnFwPvPQ==",
+      "license": "MIT"
+    },
     "node_modules/web-streams-polyfill": {
       "version": "3.3.3",
       "resolved": "https://registry.npmjs.org/web-streams-polyfill/-/web-streams-polyfill-3.3.3.tgz",

+ 5 - 0
package.json

@@ -20,6 +20,11 @@
     "@react-pdf/renderer": "^4.3.2",
     "@sendgrid/mail": "^8.1.6",
     "@tanstack/react-query": "^5.90.21",
+    "@tiptap/extension-placeholder": "^3.19.0",
+    "@tiptap/extension-underline": "^3.19.0",
+    "@tiptap/pm": "^3.19.0",
+    "@tiptap/react": "^3.19.0",
+    "@tiptap/starter-kit": "^3.19.0",
     "archiver": "^7.0.1",
     "better-auth": "^1.4.18",
     "bson": "^7.2.0",

+ 78 - 0
src/app/globals.css

@@ -175,3 +175,81 @@
     linear-gradient(90deg, oklch(0.22 0.015 70 / 0.3) 1px, transparent 1px);
   background-size: 40px 40px;
 }
+
+/* Tiptap editor styles */
+.tiptap-content {
+  &:focus {
+    outline: none;
+  }
+  > * + * {
+    margin-top: 0.5em;
+  }
+  h2 {
+    font-size: 1.25rem;
+    font-weight: 600;
+    line-height: 1.4;
+  }
+  h3 {
+    font-size: 1.1rem;
+    font-weight: 600;
+    line-height: 1.4;
+  }
+  ul {
+    list-style: disc;
+    padding-left: 1.25rem;
+  }
+  ol {
+    list-style: decimal;
+    padding-left: 1.25rem;
+  }
+  li {
+    margin-top: 0.15em;
+  }
+  blockquote {
+    border-left: 3px solid var(--border);
+    padding-left: 0.75rem;
+    color: var(--muted-foreground);
+    font-style: italic;
+  }
+  p.is-editor-empty:first-child::before {
+    content: attr(data-placeholder);
+    float: left;
+    color: var(--muted-foreground);
+    pointer-events: none;
+    height: 0;
+  }
+}
+
+/* Notes display (rendered HTML) */
+.notes-content {
+  h2 {
+    font-size: 1.25rem;
+    font-weight: 600;
+    line-height: 1.4;
+  }
+  h3 {
+    font-size: 1.1rem;
+    font-weight: 600;
+    line-height: 1.4;
+  }
+  ul {
+    list-style: disc;
+    padding-left: 1.25rem;
+  }
+  ol {
+    list-style: decimal;
+    padding-left: 1.25rem;
+  }
+  li {
+    margin-top: 0.15em;
+  }
+  blockquote {
+    border-left: 3px solid var(--border);
+    padding-left: 0.75rem;
+    color: var(--muted-foreground);
+    font-style: italic;
+  }
+  > * + * {
+    margin-top: 0.5em;
+  }
+}

+ 7 - 2
src/features/vehicles/Components/service-detail/ServiceDetailClient.tsx

@@ -18,6 +18,7 @@ import { InvoiceSummary } from "./InvoiceSummary";
 import { PaymentsSection } from "./PaymentsSection";
 import { ServiceSidebar } from "./ServiceSidebar";
 import { ServiceAttachments } from "./ServiceAttachments";
+import { ServiceNotesDisplay } from "./ServiceNotesDisplay";
 import { ImageCarousel } from "./ImageCarousel";
 import { ShareDialog } from "./ShareDialog";
 import { ServiceDetailContent } from "./ServiceDetailContent";
@@ -162,14 +163,18 @@ export function ServiceDetailClient({
               onDeletePayment={handleDeletePayment} paymentLoading={paymentLoading}
               deletingPayment={deletingPayment}
             />
+            <ServiceNotesDisplay
+              invoiceNotes={record.invoiceNotes}
+              diagnosticNotes={record.diagnosticNotes}
+              description={record.description}
+            />
           </>
         }
         rightColumn={
           <>
             <ServiceSidebar
               recordId={record.id} vehicle={record.vehicle} vehicleName={vehicleName}
-              distUnit={distUnit} mileage={record.mileage} invoiceNotes={record.invoiceNotes}
-              diagnosticNotes={record.diagnosticNotes} description={record.description}
+              distUnit={distUnit} mileage={record.mileage}
             />
             <ServiceAttachments
               attachments={record.attachments || []} imageAttachments={imageAttachments}

+ 62 - 0
src/features/vehicles/Components/service-detail/ServiceNotesDisplay.tsx

@@ -0,0 +1,62 @@
+'use client'
+
+import { FileText, Globe, Lock } from 'lucide-react'
+import { Separator } from '@/components/ui/separator'
+
+interface ServiceNotesDisplayProps {
+  invoiceNotes: string | null
+  diagnosticNotes: string | null
+  description: string | null
+}
+
+export function ServiceNotesDisplay({
+  invoiceNotes,
+  diagnosticNotes,
+  description,
+}: ServiceNotesDisplayProps) {
+  const hasPublic = !!invoiceNotes
+  const hasInternal = !!diagnosticNotes || !!description
+
+  if (!hasPublic && !hasInternal) return null
+
+  return (
+    <div className="rounded-lg border p-3">
+      <h3 className="mb-2 flex items-center gap-2 text-sm font-semibold">
+        <FileText className="h-3.5 w-3.5" />
+        Notes
+      </h3>
+      <div className="space-y-3">
+        {hasPublic && (
+          <div>
+            <span className="mb-1 flex items-center gap-1 text-xs font-medium text-muted-foreground">
+              <Globe className="h-3 w-3" />
+              Public
+            </span>
+            <div
+              className="notes-content text-sm"
+              dangerouslySetInnerHTML={{ __html: invoiceNotes! }}
+            />
+          </div>
+        )}
+        {hasPublic && hasInternal && <Separator />}
+        {hasInternal && (
+          <div>
+            <span className="mb-1 flex items-center gap-1 text-xs font-medium text-muted-foreground">
+              <Lock className="h-3 w-3" />
+              Internal
+            </span>
+            {diagnosticNotes && (
+              <div
+                className="notes-content text-sm"
+                dangerouslySetInnerHTML={{ __html: diagnosticNotes }}
+              />
+            )}
+            {description && !diagnosticNotes && (
+              <p className="whitespace-pre-wrap text-sm">{description}</p>
+            )}
+          </div>
+        )}
+      </div>
+    </div>
+  )
+}

+ 0 - 37
src/features/vehicles/Components/service-detail/ServiceSidebar.tsx

@@ -1,7 +1,6 @@
 "use client";
 
 import Link from "next/link";
-import { Separator } from "@/components/ui/separator";
 import { Building2, Car, Mail, MapPin, Phone, Users } from "lucide-react";
 import { CustomFieldsDisplay } from "@/features/custom-fields/Components/CustomFieldsDisplay";
 import type { Vehicle } from "./types";
@@ -12,9 +11,6 @@ interface ServiceSidebarProps {
   vehicleName: string;
   distUnit: string;
   mileage: number | null;
-  invoiceNotes: string | null;
-  diagnosticNotes: string | null;
-  description: string | null;
 }
 
 export function ServiceSidebar({
@@ -23,9 +19,6 @@ export function ServiceSidebar({
   vehicleName,
   distUnit,
   mileage,
-  invoiceNotes,
-  diagnosticNotes,
-  description,
 }: ServiceSidebarProps) {
   return (
     <>
@@ -93,38 +86,8 @@ export function ServiceSidebar({
         </div>
       )}
 
-      {/* Notes (invoice + diagnostic combined) */}
-      {(invoiceNotes || diagnosticNotes) && (
-        <div className="rounded-lg border p-3">
-          <h3 className="mb-2 text-sm font-semibold">Notes</h3>
-          <div className="space-y-2">
-            {invoiceNotes && (
-              <div>
-                <span className="text-xs font-medium text-muted-foreground">Invoice</span>
-                <p className="whitespace-pre-wrap text-sm">{invoiceNotes}</p>
-              </div>
-            )}
-            {invoiceNotes && diagnosticNotes && <Separator />}
-            {diagnosticNotes && (
-              <div>
-                <span className="text-xs font-medium text-muted-foreground">Diagnostic</span>
-                <p className="whitespace-pre-wrap text-sm">{diagnosticNotes}</p>
-              </div>
-            )}
-          </div>
-        </div>
-      )}
-
       {/* Custom Fields */}
       <CustomFieldsDisplay entityId={recordId} entityType="service_record" />
-
-      {/* Additional Notes */}
-      {description && (
-        <div className="rounded-lg border p-3">
-          <h3 className="mb-1 text-sm font-semibold">Additional Notes</h3>
-          <p className="whitespace-pre-wrap text-sm">{description}</p>
-        </div>
-      )}
     </>
   );
 }

+ 60 - 32
src/features/vehicles/Components/service-edit/NotesSection.tsx

@@ -1,48 +1,76 @@
 'use client'
 
-import { Label } from '@/components/ui/label'
-import { Textarea } from '@/components/ui/textarea'
+import { useState } from 'react'
+import {
+  Select,
+  SelectContent,
+  SelectItem,
+  SelectTrigger,
+  SelectValue,
+} from '@/components/ui/select'
+import { FileText } from 'lucide-react'
+import { RichTextEditor } from './RichTextEditor'
 import type { InitialData } from './form-types'
 
 interface NotesSectionProps {
   initialData: InitialData
+  onNotesChange: (field: 'invoiceNotes' | 'diagnosticNotes' | 'description', value: string) => void
 }
 
-export function NotesSection({ initialData }: NotesSectionProps) {
+export function NotesSection({ initialData, onNotesChange }: NotesSectionProps) {
+  const [noteType, setNoteType] = useState<'public' | 'internal'>('public')
+  const [publicNotes, setPublicNotes] = useState(initialData.invoiceNotes || '')
+  const [internalNotes, setInternalNotes] = useState(initialData.diagnosticNotes || '')
+
+  const handlePublicChange = (html: string) => {
+    setPublicNotes(html)
+    onNotesChange('invoiceNotes', html)
+  }
+
+  const handleInternalChange = (html: string) => {
+    setInternalNotes(html)
+    onNotesChange('diagnosticNotes', html)
+  }
+
   return (
     <div className="rounded-lg border p-3 space-y-3">
-      <h3 className="text-sm font-semibold">Notes</h3>
-
-      <div className="space-y-1">
-        <Label className="text-xs">Diagnostic Notes</Label>
-        <Textarea
-          name="diagnosticNotes"
-          placeholder="Diagnostic findings, observations, recommendations..."
-          rows={3}
-          defaultValue={initialData.diagnosticNotes}
-        />
+      <div className="flex items-center justify-between">
+        <h3 className="flex items-center gap-2 text-sm font-semibold">
+          <FileText className="h-3.5 w-3.5" />
+          Notes
+        </h3>
+        <Select value={noteType} onValueChange={(v) => setNoteType(v as 'public' | 'internal')}>
+          <SelectTrigger className="h-7 w-[120px] text-xs">
+            <SelectValue />
+          </SelectTrigger>
+          <SelectContent>
+            <SelectItem value="public">Public</SelectItem>
+            <SelectItem value="internal">Internal</SelectItem>
+          </SelectContent>
+        </Select>
       </div>
 
-      <div className="space-y-1">
-        <Label className="text-xs">Invoice Notes</Label>
-        <Textarea
-          name="invoiceNotes"
-          placeholder="Notes for the customer..."
-          rows={2}
-          defaultValue={initialData.invoiceNotes}
-        />
-        <p className="text-xs text-muted-foreground">Shown on the invoice</p>
-      </div>
+      {noteType === 'public' && (
+        <div className="space-y-1">
+          <RichTextEditor
+            content={publicNotes}
+            onChange={handlePublicChange}
+            placeholder="Notes visible on the invoice..."
+          />
+          <p className="text-xs text-muted-foreground">Shown on the invoice and shared documents</p>
+        </div>
+      )}
 
-      <div className="space-y-1">
-        <Label className="text-xs">Internal Notes</Label>
-        <Textarea
-          name="description"
-          placeholder="Internal notes (not shown on invoice)..."
-          rows={2}
-          defaultValue={initialData.description}
-        />
-      </div>
+      {noteType === 'internal' && (
+        <div className="space-y-1">
+          <RichTextEditor
+            content={internalNotes}
+            onChange={handleInternalChange}
+            placeholder="Internal notes (not shown on invoice)..."
+          />
+          <p className="text-xs text-muted-foreground">Only visible to your team</p>
+        </div>
+      )}
     </div>
   )
 }

+ 171 - 0
src/features/vehicles/Components/service-edit/RichTextEditor.tsx

@@ -0,0 +1,171 @@
+'use client'
+
+import { useEditor, EditorContent } from '@tiptap/react'
+import StarterKit from '@tiptap/starter-kit'
+import Underline from '@tiptap/extension-underline'
+import Placeholder from '@tiptap/extension-placeholder'
+import { Button } from '@/components/ui/button'
+import {
+  Bold,
+  Italic,
+  Underline as UnderlineIcon,
+  List,
+  ListOrdered,
+  Heading2,
+  Heading3,
+  Quote,
+  Undo,
+  Redo,
+} from 'lucide-react'
+import { cn } from '@/lib/utils'
+
+interface RichTextEditorProps {
+  content: string
+  onChange: (html: string) => void
+  placeholder?: string
+}
+
+export function RichTextEditor({ content, onChange, placeholder }: RichTextEditorProps) {
+  const editor = useEditor({
+    immediatelyRender: false,
+    extensions: [
+      StarterKit.configure({
+        heading: { levels: [2, 3] },
+      }),
+      Underline,
+      Placeholder.configure({
+        placeholder: placeholder || 'Write something...',
+      }),
+    ],
+    content,
+    onUpdate: ({ editor }) => {
+      onChange(editor.getHTML())
+    },
+    editorProps: {
+      attributes: {
+        class: 'tiptap-content min-h-[120px] px-3 py-2 text-sm outline-none',
+      },
+    },
+  })
+
+  if (!editor) return null
+
+  return (
+    <div className="overflow-hidden rounded-md border border-input bg-background">
+      <div className="flex flex-wrap items-center gap-0.5 border-b bg-muted/30 px-1 py-1">
+        <ToolbarButton
+          active={editor.isActive('bold')}
+          onClick={() => editor.chain().focus().toggleBold().run()}
+          title="Bold"
+        >
+          <Bold className="h-3.5 w-3.5" />
+        </ToolbarButton>
+        <ToolbarButton
+          active={editor.isActive('italic')}
+          onClick={() => editor.chain().focus().toggleItalic().run()}
+          title="Italic"
+        >
+          <Italic className="h-3.5 w-3.5" />
+        </ToolbarButton>
+        <ToolbarButton
+          active={editor.isActive('underline')}
+          onClick={() => editor.chain().focus().toggleUnderline().run()}
+          title="Underline"
+        >
+          <UnderlineIcon className="h-3.5 w-3.5" />
+        </ToolbarButton>
+
+        <div className="mx-0.5 h-4 w-px bg-border" />
+
+        <ToolbarButton
+          active={editor.isActive('heading', { level: 2 })}
+          onClick={() => editor.chain().focus().toggleHeading({ level: 2 }).run()}
+          title="Heading"
+        >
+          <Heading2 className="h-3.5 w-3.5" />
+        </ToolbarButton>
+        <ToolbarButton
+          active={editor.isActive('heading', { level: 3 })}
+          onClick={() => editor.chain().focus().toggleHeading({ level: 3 }).run()}
+          title="Subheading"
+        >
+          <Heading3 className="h-3.5 w-3.5" />
+        </ToolbarButton>
+
+        <div className="mx-0.5 h-4 w-px bg-border" />
+
+        <ToolbarButton
+          active={editor.isActive('bulletList')}
+          onClick={() => editor.chain().focus().toggleBulletList().run()}
+          title="Bullet List"
+        >
+          <List className="h-3.5 w-3.5" />
+        </ToolbarButton>
+        <ToolbarButton
+          active={editor.isActive('orderedList')}
+          onClick={() => editor.chain().focus().toggleOrderedList().run()}
+          title="Numbered List"
+        >
+          <ListOrdered className="h-3.5 w-3.5" />
+        </ToolbarButton>
+        <ToolbarButton
+          active={editor.isActive('blockquote')}
+          onClick={() => editor.chain().focus().toggleBlockquote().run()}
+          title="Quote"
+        >
+          <Quote className="h-3.5 w-3.5" />
+        </ToolbarButton>
+
+        <div className="mx-0.5 h-4 w-px bg-border" />
+
+        <ToolbarButton
+          onClick={() => editor.chain().focus().undo().run()}
+          disabled={!editor.can().undo()}
+          title="Undo"
+        >
+          <Undo className="h-3.5 w-3.5" />
+        </ToolbarButton>
+        <ToolbarButton
+          onClick={() => editor.chain().focus().redo().run()}
+          disabled={!editor.can().redo()}
+          title="Redo"
+        >
+          <Redo className="h-3.5 w-3.5" />
+        </ToolbarButton>
+      </div>
+      <EditorContent editor={editor} />
+    </div>
+  )
+}
+
+function ToolbarButton({
+  children,
+  active,
+  disabled,
+  onClick,
+  title,
+}: {
+  children: React.ReactNode
+  active?: boolean
+  disabled?: boolean
+  onClick: () => void
+  title: string
+}) {
+  return (
+    <Button
+      type="button"
+      variant="ghost"
+      size="icon"
+      className={cn(
+        'h-7 w-7',
+        active && 'bg-accent text-accent-foreground',
+        disabled && 'opacity-40'
+      )}
+      disabled={disabled}
+      onClick={onClick}
+      title={title}
+    >
+      {children}
+    </Button>
+  )
+}

+ 17 - 5
src/features/vehicles/Components/service-edit/ServiceRecordForm.tsx

@@ -1,6 +1,6 @@
 'use client'
 
-import { useState, useCallback } from 'react'
+import { useState, useCallback, useRef } from 'react'
 import { useRouter } from 'next/navigation'
 import { toast } from 'sonner'
 import { useGlassModal } from '@/components/glass-modal'
@@ -43,6 +43,18 @@ export function ServiceRecordForm({
   const [discountType, setDiscountType] = useState<string>(initialData.discountType || 'none')
   const [discountValue, setDiscountValue] = useState(initialData.discountValue ?? 0)
   const [showInventoryPicker, setShowInventoryPicker] = useState(false)
+  const notesRef = useRef({
+    invoiceNotes: initialData.invoiceNotes || '',
+    diagnosticNotes: initialData.diagnosticNotes || '',
+    description: initialData.description || '',
+  })
+
+  const handleNotesChange = useCallback(
+    (field: 'invoiceNotes' | 'diagnosticNotes' | 'description', value: string) => {
+      notesRef.current[field] = value
+    },
+    []
+  )
 
   const partsSubtotal = partItems.reduce((sum, p) => sum + p.total, 0)
   const laborSubtotal = laborItems.reduce((sum, l) => sum + l.total, 0)
@@ -95,15 +107,15 @@ export function ServiceRecordForm({
       id: initialData.id,
       vehicleId: selectedVehicleId,
       title: formData.get('title') as string,
-      description: (formData.get('description') as string) || undefined,
+      description: notesRef.current.description || undefined,
       type,
       status,
       cost: totalAmount,
       mileage: Number(formData.get('mileage')) || undefined,
       serviceDate: (formData.get('serviceDate') as string) || new Date().toISOString(),
       techName: techName || undefined,
-      diagnosticNotes: (formData.get('diagnosticNotes') as string) || undefined,
-      invoiceNotes: (formData.get('invoiceNotes') as string) || undefined,
+      diagnosticNotes: notesRef.current.diagnosticNotes || undefined,
+      invoiceNotes: notesRef.current.invoiceNotes || undefined,
       invoiceNumber: (formData.get('invoiceNumber') as string) || undefined,
       partItems: partItems.filter((p) => p.name),
       laborItems: laborItems.filter((l) => l.description),
@@ -151,6 +163,7 @@ export function ServiceRecordForm({
         currencyCode={currencyCode}
         defaultLaborRate={defaultLaborRate}
       />
+      <NotesSection initialData={initialData} onNotesChange={handleNotesChange} />
     </div>
   )
 
@@ -191,7 +204,6 @@ export function ServiceRecordForm({
         totalAmount={totalAmount}
         currencyCode={currencyCode}
       />
-      <NotesSection initialData={initialData} />
     </div>
   )