From ffeac5800be3e99f6c1a1a0325116cdade1f6984 Mon Sep 17 00:00:00 2001 From: qyinm Date: Thu, 20 Aug 2026 22:39:16 +0900 Subject: [PATCH 1/9] build(web): add tiptap editor dependencies --- apps/web/package.json | 7 +- pnpm-lock.yaml | 605 ++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 611 insertions(+), 1 deletion(-) diff --git a/apps/web/package.json b/apps/web/package.json index a844f66..4fa7d88 100644 --- a/apps/web/package.json +++ b/apps/web/package.json @@ -17,6 +17,10 @@ "@plot/api-client": "workspace:*", "@plot/auth": "workspace:*", "@stylexjs/stylex": "^0.19.0", + "@tiptap/core": "^3.30.2", + "@tiptap/extension-placeholder": "^3.30.2", + "@tiptap/react": "^3.30.2", + "@tiptap/starter-kit": "^3.30.2", "better-auth": "^1.6.23", "blobatar": "0.2.0", "clsx": "^2.1.1", @@ -26,7 +30,8 @@ "react": "19.2.4", "react-dom": "19.2.4", "resend": "^6.17.1", - "tailwind-merge": "^3.6.0" + "tailwind-merge": "^3.6.0", + "tiptap-markdown": "^0.9.0" }, "devDependencies": { "@tailwindcss/postcss": "^4", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index e9265db..709c157 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -37,6 +37,18 @@ importers: '@stylexjs/stylex': specifier: ^0.19.0 version: 0.19.0 + '@tiptap/core': + specifier: ^3.30.2 + version: 3.30.2(@tiptap/pm@3.30.2) + '@tiptap/extension-placeholder': + specifier: ^3.30.2 + version: 3.30.2(@tiptap/extensions@3.30.2(@tiptap/core@3.30.2(@tiptap/pm@3.30.2))(@tiptap/pm@3.30.2)) + '@tiptap/react': + specifier: ^3.30.2 + version: 3.30.2(@floating-ui/dom@1.8.0)(@tiptap/core@3.30.2(@tiptap/pm@3.30.2))(@tiptap/pm@3.30.2)(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@tiptap/starter-kit': + specifier: ^3.30.2 + version: 3.30.2 better-auth: specifier: ^1.6.23 version: 1.6.23(next@16.2.11(@playwright/test@1.61.1)(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(pg@8.22.0)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(vitest@4.1.10(@types/node@20.19.43)(jsdom@29.1.1(@noble/hashes@2.2.0))(vite@8.1.4(@types/node@20.19.43)(jiti@2.7.0))) @@ -67,6 +79,9 @@ importers: tailwind-merge: specifier: ^3.6.0 version: 3.6.0 + tiptap-markdown: + specifier: ^0.9.0 + version: 0.9.0(@tiptap/core@3.30.2(@tiptap/pm@3.30.2)) devDependencies: '@tailwindcss/postcss': specifier: ^4 @@ -1175,6 +1190,161 @@ packages: '@types/react-dom': optional: true + '@tiptap/core@3.30.2': + resolution: {integrity: sha512-QbZC/s1OOqcoUdkhIY16TjR/gCtR0qAk9e4bJwUqOJqZuv5ozqCL5hzWm22jjTPp6c6Ei2tPd6t30VwfIKW4lQ==} + peerDependencies: + '@tiptap/pm': 3.30.2 + + '@tiptap/extension-blockquote@3.30.2': + resolution: {integrity: sha512-BOkwhZenek7vzXBOgKppSrlx4YryBdAYu1p1MXKn0R9A9eNmE2HVhmm0gG49+E8BhsE/TG8wKVclwET42JJiIg==} + peerDependencies: + '@tiptap/core': 3.30.2 + '@tiptap/pm': 3.30.2 + + '@tiptap/extension-bold@3.30.2': + resolution: {integrity: sha512-MsvJhPgYejY2D9MhwYJv8AmscozvLBI8qtJ7YLdYZBWkMR4bgmxHq5+xqEfBsao9bOMMwBon9p3+P+/Tq5ReWA==} + peerDependencies: + '@tiptap/core': 3.30.2 + + '@tiptap/extension-bubble-menu@3.30.2': + resolution: {integrity: sha512-oS0WiWNXHKpiPYMkcnHm1j7iEvufTGGtLFtcJJd3olb5OS6V2acoXVDL0nNJDmRQ32K3QXut3fbRcMseMxT+lw==} + peerDependencies: + '@tiptap/core': 3.30.2 + '@tiptap/pm': 3.30.2 + + '@tiptap/extension-bullet-list@3.30.2': + resolution: {integrity: sha512-+awIL/TUz4aB3rL68igU1rWfaaoBIAkPcakkktkRq8gYf0bd9eSb48P6kHkpx/3q+JyK7g9vsnltLNHNh6twnA==} + peerDependencies: + '@tiptap/extension-list': 3.30.2 + + '@tiptap/extension-code-block@3.30.2': + resolution: {integrity: sha512-9otGKaQZmePHrLXFtCtz+BYDn5z4sSumTkUqQIQHz0gVxwPoTi7g51RedwxvViTb/zu2XV5ROXYLHIxKxypMPg==} + peerDependencies: + '@tiptap/core': 3.30.2 + '@tiptap/pm': 3.30.2 + + '@tiptap/extension-code@3.30.2': + resolution: {integrity: sha512-r8EZk3R9yGpF6v5xxafAU1HwrD/e+RpbfnmVi2TeB/ZHAsVO62fW96E32G0t6IdaCtOFtAd85hkDAaOfCT4yGg==} + peerDependencies: + '@tiptap/core': 3.30.2 + + '@tiptap/extension-document@3.30.2': + resolution: {integrity: sha512-+xIv67V+/2L1uvz98FAT5W7kWEfHwfNV3MD7b4UsKPU0lhcCWuVOXy0JB8yYmdNExqpI7xT9g3MWzREoBvBQSg==} + peerDependencies: + '@tiptap/core': 3.30.2 + + '@tiptap/extension-dropcursor@3.30.2': + resolution: {integrity: sha512-nyRKUmItATnKI9AiRChmjhcBbCEsNxRu+AaCz+cx8EvnAcNHsVRdNYL5PmBs3WlNA/Et4Eb2DG0hVQDnNF61eg==} + peerDependencies: + '@tiptap/extensions': 3.30.2 + + '@tiptap/extension-floating-menu@3.30.2': + resolution: {integrity: sha512-A8PLvvh8W6PUMrqh+EpBerxm+Ucr0irGxJvwAnzYQmNGNIJ9U4OVgw4OcEU+9JH0gMmEzDcHzMPP2s/s1lIcyw==} + peerDependencies: + '@floating-ui/dom': ^1.0.0 + '@tiptap/core': 3.30.2 + '@tiptap/pm': 3.30.2 + + '@tiptap/extension-gapcursor@3.30.2': + resolution: {integrity: sha512-7Xk0ut6FM+RAsvKxDN3bAtk7zvYZ6Aa8pawJ6s7dLAmLR9JwrZevlcL4FSrj4bR7rqKOj92RYCFxzgTEbWimag==} + peerDependencies: + '@tiptap/extensions': 3.30.2 + + '@tiptap/extension-hard-break@3.30.2': + resolution: {integrity: sha512-IxSNgmG3d4OZdUTeebrOI7SxdIWXXJqlcGiSNDabWqxipUitfy3mZ3gDDE6G01koKxZRbhz4KIplAZlpxnTFSg==} + peerDependencies: + '@tiptap/core': 3.30.2 + + '@tiptap/extension-heading@3.30.2': + resolution: {integrity: sha512-PblDvgSJ05p1t6hzyPi02xeiBjB0M2abReoGEImqSWCy79UqnAGacgsZo4EeEawtJV1NEP8chhvmX+nRtzdT1A==} + peerDependencies: + '@tiptap/core': 3.30.2 + + '@tiptap/extension-horizontal-rule@3.30.2': + resolution: {integrity: sha512-j8aswLTsuEdJKC62DF+kw0EgvIRL7QMUyAVp2fdjR0qgM0ZVlEwCC4qIEq3kK9tFVU4kRtQ5BSj/jn6QwrlbCA==} + peerDependencies: + '@tiptap/core': 3.30.2 + '@tiptap/pm': 3.30.2 + + '@tiptap/extension-italic@3.30.2': + resolution: {integrity: sha512-pp8uaiuXsUbLm5rYzR1jlWbwm1mAahRajdHwAKBtthFRB2rDvC7ZWhKaCSoKhZvfIDRmu9/B67+uAHoutL0dCA==} + peerDependencies: + '@tiptap/core': 3.30.2 + + '@tiptap/extension-link@3.30.2': + resolution: {integrity: sha512-jwdcymKcrbFpj5hRAuGVLCq8FieVkGFnENyroYmvkad+XAt8ZLy/MTFYRN6SK3ukH6PZMY7H4iObGtciQaC5nw==} + peerDependencies: + '@tiptap/core': 3.30.2 + '@tiptap/pm': 3.30.2 + + '@tiptap/extension-list-item@3.30.2': + resolution: {integrity: sha512-HWgRCRlGxulE+hN1VUcnWD6P2NE08VBgGtcaxOfdXVqaI93BCK6AhRQZGpLsfKgajLk+5DXTBraaitwnBqzCxg==} + peerDependencies: + '@tiptap/extension-list': 3.30.2 + + '@tiptap/extension-list-keymap@3.30.2': + resolution: {integrity: sha512-TTve3WOlQaYu1ahMqsQ/T0wzaxfgZvcOl3/OuPyInOi8QtxXhqGhFjmYe5jOr56G9W2QDuFWVsZecVwfDte9zg==} + peerDependencies: + '@tiptap/extension-list': 3.30.2 + + '@tiptap/extension-list@3.30.2': + resolution: {integrity: sha512-MIUpo1Bd9Rf1Qg+TNYNwDZ4xsfFeQahjU9Xhy6UcaszKQzbAM7KCzn5BObNytK1NdcqNHsC8Wj5vFvMKEzrXdw==} + peerDependencies: + '@tiptap/core': 3.30.2 + '@tiptap/pm': 3.30.2 + + '@tiptap/extension-ordered-list@3.30.2': + resolution: {integrity: sha512-Z7OO1HcF0idda1n6vodXeQ3h2ylN9JR4IfIGUYkar5Xl9JusK8PDETTBQZQn//96p49I2d+GoWsD2LXPtjHXXg==} + peerDependencies: + '@tiptap/extension-list': 3.30.2 + + '@tiptap/extension-paragraph@3.30.2': + resolution: {integrity: sha512-ulEu3LNt+kPVAWEnrhoz13Fs8Q/v/8NUxQbAeteuBchQ8joxJXuWExhpy1fUfZir5+b+W5z7/NesgPjZQfv47w==} + peerDependencies: + '@tiptap/core': 3.30.2 + + '@tiptap/extension-placeholder@3.30.2': + resolution: {integrity: sha512-Bj1seUvPCoRrD/LpzMoKD+jQIjxuc+oq931GpPq4wobSUUbD4pF/0NMwpCLpiHO19QnTQz8+9p2dqPdlc44LHA==} + peerDependencies: + '@tiptap/extensions': 3.30.2 + + '@tiptap/extension-strike@3.30.2': + resolution: {integrity: sha512-fBLxMXz6hYIURzLOD+/L6aVATztsKham00ANWmGi13vN0hx2lQMYZffN+gR+QqiCDfMQxBXzrf5a7tJuDiQHLQ==} + peerDependencies: + '@tiptap/core': 3.30.2 + + '@tiptap/extension-text@3.30.2': + resolution: {integrity: sha512-n/iZnirgRmXet6f97kolAnP3j8DsgLSiTbz/KLWc8eBYiFmkjRzkuisOm5xuGdfGIxwpB4x3tlSF4ef4DLnbRg==} + peerDependencies: + '@tiptap/core': 3.30.2 + + '@tiptap/extension-underline@3.30.2': + resolution: {integrity: sha512-SZiTMnvqXcnrtJX+X25ZbYsuDO83haGOVMBD/O+mAWYNYXhaSc5Rkph5czzItxrd+Yyp/vs4PiwD7XTNbfqmpA==} + peerDependencies: + '@tiptap/core': 3.30.2 + + '@tiptap/extensions@3.30.2': + resolution: {integrity: sha512-2LqAHXk26QDsryW+beECxYeBzv5Ylk4GuB3cOmfghS7/G37R2W+Te3TkUK7BT0EWoDryvBT57/5q0DEFIhfZZg==} + peerDependencies: + '@tiptap/core': 3.30.2 + '@tiptap/pm': 3.30.2 + + '@tiptap/pm@3.30.2': + resolution: {integrity: sha512-BJN8tUx4ppFN3R3cV/FJfrJbJkvo1lj4uciq+nwpjwzdRvFzqIuglWf+HLcJ6CwlYpLOHp7ArgkBg4Q5e60Gog==} + + '@tiptap/react@3.30.2': + resolution: {integrity: sha512-7hGaTstpUeTmQ008mCPkjz+GSlChWhucgy+PeX0z93v4+nh7qM5F+0lh+kJ9zo6Os5abO7v36GtgHRZdQI6+FQ==} + peerDependencies: + '@tiptap/core': 3.30.2 + '@tiptap/pm': 3.30.2 + '@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 + + '@tiptap/starter-kit@3.30.2': + resolution: {integrity: sha512-fJSrhW1CyD4sjYA20evSP4Cp13B/HhbxCdM974K0xpHOVqvCtNU9w2s9hfq9mg2yGoU7MSNHKNYMkJjIi2/Xyw==} + '@tybys/wasm-util@0.10.3': resolution: {integrity: sha512-F3fo1MYrRJYL3zER0OUOmkutjr1Vp23m7OsSgp7nq4SP6OqX6C/56XFIPAl5bt3zaBRjmW7SGz3u/6LwFpYcOg==} @@ -1196,6 +1366,24 @@ packages: '@types/json5@0.0.29': resolution: {integrity: sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==} + '@types/linkify-it@3.0.5': + resolution: {integrity: sha512-yg6E+u0/+Zjva+buc3EIb+29XEg4wltq7cSmd4Uc2EE/1nUVmxyzpX6gUXD0V8jIrG0r7YeOGVIbYRkxeooCtw==} + + '@types/linkify-it@5.0.0': + resolution: {integrity: sha512-sVDA58zAw4eWAffKOaQH5/5j3XeayukzDk+ewSsnv3p4yJEZHCCzMDiZM8e0OUrRvmpGZ85jf4yDHkHsgBNr9Q==} + + '@types/markdown-it@13.0.9': + resolution: {integrity: sha512-1XPwR0+MgXLWfTn9gCsZ55AHOKW1WN+P9vr0PaQh5aerR9LLQXUbjfEAFhjmEmyoYFWAyuN2Mqkn40MZ4ukjBw==} + + '@types/markdown-it@14.2.0': + resolution: {integrity: sha512-NoQ2yGlLWj4wpxMs+TYmRKk3thDrQ97agr7sFqfLsAlvoS8SNQuTrlObhFqG9iugdTtgOE9jpJ6FNM4ZGsa5xQ==} + + '@types/mdurl@1.0.5': + resolution: {integrity: sha512-6L6VymKTzYSrEf4Nev4Xa1LCHKrlTlYCBMTlQKFuddo1CvQcE52I0mwfOJayueUC7MJuXOeHTcIU683lzd0cUA==} + + '@types/mdurl@2.0.0': + resolution: {integrity: sha512-RGdgjQUZba5p6QEFAVx2OGb8rQDL/cPRG7GiedRzMcJ1tYnUANBncjbSB1NRGwbvjcPeikRABz2nshyPk1bhWg==} + '@types/node@20.19.43': resolution: {integrity: sha512-6oYBAi5ikg4Pl+kGsoYtawUMBT2zZMCvPNF7pVLnHZfd1zf38DRiWn/gT01RYCdUqkv7Fhr+C9ot4/tb+2sVvA==} @@ -1213,6 +1401,9 @@ packages: '@types/trusted-types@2.0.7': resolution: {integrity: sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw==} + '@types/use-sync-external-store@0.0.6': + resolution: {integrity: sha512-zFDAD+tlpf2r4asuHEj0XH6pY6i0g5NeAHPn+15wk3BV6JA69eERFXC1gyGThDkVa1zCyKr5jox1+2LbV/AMLg==} + '@typescript-eslint/eslint-plugin@8.62.0': resolution: {integrity: sha512-o+mpz7EYiMzXoySXiKmzlabIvTVqUuK5yLrAedRPRDA0IpPFMUV1IXt6OqljIxX/kumN6EjUYp41Hqelh6p/Dw==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} @@ -1769,6 +1960,10 @@ packages: resolution: {integrity: sha512-aNnGCvbJ/RIyWo1IuhNdVjnNF+EjH9wpzpNHt+ci/m9He9LJvUN8wrCcXjp9cWsGNAuvSpVFTx/vraAFQ8qGjQ==} engines: {node: '>=10.13.0'} + entities@4.5.0: + resolution: {integrity: sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==} + engines: {node: '>=0.12'} + entities@8.0.0: resolution: {integrity: sha512-zwfzJecQ/Uej6tusMqwAqU/6KL2XaB2VZ2Jg54Je6ahNBGNH6Ek6g3jjNCF0fG9EWQKGZNddNjU5F1ZQn/sBnA==} engines: {node: '>=20.19.0'} @@ -1950,6 +2145,10 @@ packages: fast-deep-equal@3.1.3: resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==} + fast-equals@5.4.1: + resolution: {integrity: sha512-DjlFSM5Pk9cGcL0q5QXl66eGzx0N6szNgaswwc5ZphlBohjTVJSnGgI+rJVOgOi65qUoQnDZN4nDqi33udtydQ==} + engines: {node: '>=6.0.0'} + fast-glob@3.3.1: resolution: {integrity: sha512-kNFPyjhh5cKjrUltxs+wFx+ZkbRaxxmZ+X0ZU31SOsxCEtP9VPgtq2teZw1DebupL5GmDaNQ6yKMMVcM41iqDg==} engines: {node: '>=8.6.0'} @@ -2413,6 +2612,12 @@ packages: resolution: {integrity: sha512-NXYBzinNrblfraPGyrbPoD19C1h9lfI/1mzgWYvXUTe414Gz/X1FD2XBZSZM7rRTrMA8JL3OtAaGifrIKhQ5yQ==} engines: {node: '>= 12.0.0'} + linkify-it@5.0.2: + resolution: {integrity: sha512-ONTm2jCMAVZjgQa/Fy1kScXsuOoF5NPTsoFBdE1KVIZ2vAh/r9+Bqo+0jINCBYnavTPQZz38QzFTme79ENoN3Q==} + + linkifyjs@4.3.3: + resolution: {integrity: sha512-P8aEP5U/D1/IlTY2OeYsErdwh9bGuLE30NcXtKEjgdHcahveQoQwM2yZNsioQHsWFz0P7KKudisbrzCgR0sDHg==} + locate-path@6.0.0: resolution: {integrity: sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==} engines: {node: '>=10'} @@ -2443,6 +2648,13 @@ packages: magic-string@0.30.21: resolution: {integrity: sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==} + markdown-it-task-lists@2.1.1: + resolution: {integrity: sha512-TxFAc76Jnhb2OUu+n3yz9RMu4CwGfaT788br6HhEDlvWfdeJcLUsxk1Hgw2yJio0OXsxv7pyIPmvECY7bMbluA==} + + markdown-it@14.3.0: + resolution: {integrity: sha512-RCEsPjR+sr0x+AuYp601tKTkgFG4YEPLCzHST3cQ/fhlJkqAkz1L2/Qbp1j9qw5SBwQHFBoW8+hoN5xssOF0Tw==} + hasBin: true + math-intrinsics@1.1.0: resolution: {integrity: sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==} engines: {node: '>= 0.4'} @@ -2450,6 +2662,9 @@ packages: mdn-data@2.27.1: resolution: {integrity: sha512-9Yubnt3e8A0OKwxYSXyhLymGW4sCufcLG6VdiDdUGVkPhpqLxlvP5vl1983gQjJl3tqbrM731mjaZaP68AgosQ==} + mdurl@2.1.0: + resolution: {integrity: sha512-1+HBaOx0zi/dQWht8rNv9MYf9qqpqL/kxI0hXImU6Y547zM6Sni8BQibt7ifgMcYtQg41ao3Ivd6cnSM86inpg==} + merge2@1.4.1: resolution: {integrity: sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==} engines: {node: '>= 8'} @@ -2561,6 +2776,9 @@ packages: resolution: {integrity: sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==} engines: {node: '>= 0.8.0'} + orderedmap@2.1.1: + resolution: {integrity: sha512-TvAWxi0nDe1j/rtMcWcIj94+Ffe6n7zhow33h40SKxmsmozs6dz/e+EajymfoFcHd7sxNn8yHM8839uixMOV6g==} + own-keys@1.0.1: resolution: {integrity: sha512-qFOyK5PjiWZd+QQIh+1jhdb9LpxTF0qs7Pm8o5QHYZ0M3vKqSqzsZaEB6oWlxZ+q2sJBMI/Ktgd2N5ZwQoRHfg==} engines: {node: '>= 0.4'} @@ -2691,6 +2909,52 @@ packages: prop-types@15.8.1: resolution: {integrity: sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==} + prosemirror-changeset@2.4.1: + resolution: {integrity: sha512-96WBLhOaYhJ+kPhLg3uW359Tz6I/MfcrQfL4EGv4SrcqKEMC1gmoGrXHecPE8eOwTVCJ4IwgfzM8fFad25wNfw==} + + prosemirror-commands@1.7.2: + resolution: {integrity: sha512-q6Q6szxqdu9Xd6EcdKsqXghu5nQdZTpB4Q9yd04WRc7/jt763e/rT60Owh0L1GYY+T46o5rD+9lEN36dZS43tw==} + + prosemirror-dropcursor@1.8.3: + resolution: {integrity: sha512-FoYbsJR8gK+DGlqhNoE29Loa38eIZPzQRIb1VMaDNBoo4OLP6vVof/jR8qFY/6XvUd6Dhug8MDCHl2a/h8RTfQ==} + + 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.6: + resolution: {integrity: sha512-dY6g2BXRjkHW2ldNRDKfTF0x0R4ifk5rgPaABd/UhvrymtsGVxTbHn01goEsHAvO9nN0O34cttlW1qg/XUcaIg==} + + prosemirror-model@1.25.11: + resolution: {integrity: sha512-QWg9RhnpLlogAmp3p96uEFrE5txQpFynd4vhBAELkwgOCWQs/X0yCzB3/hrHqiPwf91RG5KyWq6553zs9JqIOQ==} + + 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-transform@1.12.0: + resolution: {integrity: sha512-GxboyN4AMIsoHNtz5uf2r2Ru551i5hWeCMD6E2Ib4Eogqoub0NflniaBPVQ4MrGE5yZ8JV9tUHg9qcZTTrcN4w==} + + prosemirror-view@1.42.2: + resolution: {integrity: sha512-Pdg0l5kXm8aLDquFAnQFTCITg0q44sLqBlHlpsVLD9segdOao8TOfQdAhCrCXyVgPSRr6UDDROOIWA3bIrN9YQ==} + + punycode.js@2.3.1: + resolution: {integrity: sha512-uxFIHU0YlHYhDQtV4R9J6a52SLx28BCjT+4ieh7IGbgwVJWO+km431c4yRlREUAsAmt/uMjQUyQHNEPf0M39CA==} + engines: {node: '>=6'} + punycode@2.3.1: resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==} engines: {node: '>=6'} @@ -2759,6 +3023,9 @@ packages: engines: {node: ^20.19.0 || >=22.12.0} hasBin: true + rope-sequence@1.3.4: + resolution: {integrity: sha512-UT5EDe2cu2E/6O4igUr5PSFs23nvvukicWHx6GnOPlHAiiYbzNuCRQCuiUdHJQcqKalLKlrYJnjY0ySGsXNQXQ==} + rou3@0.7.12: resolution: {integrity: sha512-iFE4hLDuloSWcD7mjdCDhx2bKcIsYbtOTpfH5MHHLSKMOUyjqQXTeZVa289uuwEGEKFoE/BAPbhaU4B774nceg==} @@ -2953,6 +3220,11 @@ packages: resolution: {integrity: sha512-Bf+ILmBgretUrdJxzXM0SgXLZ3XfiaUuOj/IKQHuTXip+05Xn+uyEYdVg0kYDipTBcLrCVyUzAPz7QmArb0mmw==} engines: {node: '>=14.0.0'} + tiptap-markdown@0.9.0: + resolution: {integrity: sha512-dKLQ9iiuGNgrlGVjrNauF/UBzWu4LYOx5pkD0jNkmQt/GOwfCJsBuzZTsf1jZ204ANHOm572mZ9PYvGh1S7tpQ==} + peerDependencies: + '@tiptap/core': ^3.0.1 + tldts-core@7.4.8: resolution: {integrity: sha512-c1P7u0EhACHj7lPy4MJm8iTFEU8+nB0LCtddH0fhP7noaVoXAqafMtOOeX+ulpuPBqnrRgRhw494RICT3mbhnw==} @@ -3016,6 +3288,9 @@ packages: engines: {node: '>=14.17'} hasBin: true + uc.micro@2.1.0: + resolution: {integrity: sha512-ARDJmphmdvUk6Glw7y9DQ2bFkKBHwQHLi2lsaH6PPmz/Ka9sFOBsBluozhDltWmnv9u/cF6Rt87znRTPV+yp/A==} + unbox-primitive@1.1.0: resolution: {integrity: sha512-nWJ91DjeOkej/TA8pXQ3myruKpKEYgqvpw9lz4OPHj/NWFNluYrjbz9j01CJ8yKQd2g4jFoOkINCTW2I5LEEyw==} engines: {node: '>= 0.4'} @@ -3039,6 +3314,11 @@ packages: uri-js@4.4.1: resolution: {integrity: sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==} + use-sync-external-store@1.6.0: + resolution: {integrity: sha512-Pp6GSwGP/NrPIrxVFAIkOQeyw8lFenOHijQWkUTrDvrF4ALqylP2C/KCkeS9dpUM3KvYRQhna5vt7IL95+ZQ9w==} + peerDependencies: + react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 + vite@8.1.4: resolution: {integrity: sha512-bTT9PsdWO+MQMNG9ZXIP/qM9wGh37DFxTV/sPq9cFpHr3w4jkgef032PkAL9jAqhk3Nz8NQw3O8n6/xFkqO4QQ==} engines: {node: ^20.19.0 || >=22.12.0} @@ -3123,6 +3403,9 @@ packages: jsdom: optional: true + w3c-keyname@2.2.8: + resolution: {integrity: sha512-dpojBhNsCNN7T82Tm7k26A6G9ML3NkhDsnw9n/eoxSRlVBB4CEtIQ/KTCLI2Fwf3ataSXRhYFkQi3SlnFwPvPQ==} + w3c-xmlserializer@5.0.0: resolution: {integrity: sha512-o8qghlI8NZHU1lLPrpi2+Uq7abh4GGPpYANlalzWxyWteJOCsr/P+oPBA49TOLu5FTZO4d3F9MnWJfiMo4BkmA==} engines: {node: '>=18'} @@ -4146,6 +4429,183 @@ snapshots: '@types/react': 19.2.17 '@types/react-dom': 19.2.3(@types/react@19.2.17) + '@tiptap/core@3.30.2(@tiptap/pm@3.30.2)': + dependencies: + '@tiptap/pm': 3.30.2 + + '@tiptap/extension-blockquote@3.30.2(@tiptap/core@3.30.2(@tiptap/pm@3.30.2))(@tiptap/pm@3.30.2)': + dependencies: + '@tiptap/core': 3.30.2(@tiptap/pm@3.30.2) + '@tiptap/pm': 3.30.2 + + '@tiptap/extension-bold@3.30.2(@tiptap/core@3.30.2(@tiptap/pm@3.30.2))': + dependencies: + '@tiptap/core': 3.30.2(@tiptap/pm@3.30.2) + + '@tiptap/extension-bubble-menu@3.30.2(@tiptap/core@3.30.2(@tiptap/pm@3.30.2))(@tiptap/pm@3.30.2)': + dependencies: + '@floating-ui/dom': 1.8.0 + '@tiptap/core': 3.30.2(@tiptap/pm@3.30.2) + '@tiptap/pm': 3.30.2 + optional: true + + '@tiptap/extension-bullet-list@3.30.2(@tiptap/extension-list@3.30.2(@tiptap/core@3.30.2(@tiptap/pm@3.30.2))(@tiptap/pm@3.30.2))': + dependencies: + '@tiptap/extension-list': 3.30.2(@tiptap/core@3.30.2(@tiptap/pm@3.30.2))(@tiptap/pm@3.30.2) + + '@tiptap/extension-code-block@3.30.2(@tiptap/core@3.30.2(@tiptap/pm@3.30.2))(@tiptap/pm@3.30.2)': + dependencies: + '@tiptap/core': 3.30.2(@tiptap/pm@3.30.2) + '@tiptap/pm': 3.30.2 + + '@tiptap/extension-code@3.30.2(@tiptap/core@3.30.2(@tiptap/pm@3.30.2))': + dependencies: + '@tiptap/core': 3.30.2(@tiptap/pm@3.30.2) + + '@tiptap/extension-document@3.30.2(@tiptap/core@3.30.2(@tiptap/pm@3.30.2))': + dependencies: + '@tiptap/core': 3.30.2(@tiptap/pm@3.30.2) + + '@tiptap/extension-dropcursor@3.30.2(@tiptap/extensions@3.30.2(@tiptap/core@3.30.2(@tiptap/pm@3.30.2))(@tiptap/pm@3.30.2))': + dependencies: + '@tiptap/extensions': 3.30.2(@tiptap/core@3.30.2(@tiptap/pm@3.30.2))(@tiptap/pm@3.30.2) + + '@tiptap/extension-floating-menu@3.30.2(@floating-ui/dom@1.8.0)(@tiptap/core@3.30.2(@tiptap/pm@3.30.2))(@tiptap/pm@3.30.2)': + dependencies: + '@floating-ui/dom': 1.8.0 + '@tiptap/core': 3.30.2(@tiptap/pm@3.30.2) + '@tiptap/pm': 3.30.2 + optional: true + + '@tiptap/extension-gapcursor@3.30.2(@tiptap/extensions@3.30.2(@tiptap/core@3.30.2(@tiptap/pm@3.30.2))(@tiptap/pm@3.30.2))': + dependencies: + '@tiptap/extensions': 3.30.2(@tiptap/core@3.30.2(@tiptap/pm@3.30.2))(@tiptap/pm@3.30.2) + + '@tiptap/extension-hard-break@3.30.2(@tiptap/core@3.30.2(@tiptap/pm@3.30.2))': + dependencies: + '@tiptap/core': 3.30.2(@tiptap/pm@3.30.2) + + '@tiptap/extension-heading@3.30.2(@tiptap/core@3.30.2(@tiptap/pm@3.30.2))': + dependencies: + '@tiptap/core': 3.30.2(@tiptap/pm@3.30.2) + + '@tiptap/extension-horizontal-rule@3.30.2(@tiptap/core@3.30.2(@tiptap/pm@3.30.2))(@tiptap/pm@3.30.2)': + dependencies: + '@tiptap/core': 3.30.2(@tiptap/pm@3.30.2) + '@tiptap/pm': 3.30.2 + + '@tiptap/extension-italic@3.30.2(@tiptap/core@3.30.2(@tiptap/pm@3.30.2))': + dependencies: + '@tiptap/core': 3.30.2(@tiptap/pm@3.30.2) + + '@tiptap/extension-link@3.30.2(@tiptap/core@3.30.2(@tiptap/pm@3.30.2))(@tiptap/pm@3.30.2)': + dependencies: + '@tiptap/core': 3.30.2(@tiptap/pm@3.30.2) + '@tiptap/pm': 3.30.2 + linkifyjs: 4.3.3 + + '@tiptap/extension-list-item@3.30.2(@tiptap/extension-list@3.30.2(@tiptap/core@3.30.2(@tiptap/pm@3.30.2))(@tiptap/pm@3.30.2))': + dependencies: + '@tiptap/extension-list': 3.30.2(@tiptap/core@3.30.2(@tiptap/pm@3.30.2))(@tiptap/pm@3.30.2) + + '@tiptap/extension-list-keymap@3.30.2(@tiptap/extension-list@3.30.2(@tiptap/core@3.30.2(@tiptap/pm@3.30.2))(@tiptap/pm@3.30.2))': + dependencies: + '@tiptap/extension-list': 3.30.2(@tiptap/core@3.30.2(@tiptap/pm@3.30.2))(@tiptap/pm@3.30.2) + + '@tiptap/extension-list@3.30.2(@tiptap/core@3.30.2(@tiptap/pm@3.30.2))(@tiptap/pm@3.30.2)': + dependencies: + '@tiptap/core': 3.30.2(@tiptap/pm@3.30.2) + '@tiptap/pm': 3.30.2 + + '@tiptap/extension-ordered-list@3.30.2(@tiptap/extension-list@3.30.2(@tiptap/core@3.30.2(@tiptap/pm@3.30.2))(@tiptap/pm@3.30.2))': + dependencies: + '@tiptap/extension-list': 3.30.2(@tiptap/core@3.30.2(@tiptap/pm@3.30.2))(@tiptap/pm@3.30.2) + + '@tiptap/extension-paragraph@3.30.2(@tiptap/core@3.30.2(@tiptap/pm@3.30.2))': + dependencies: + '@tiptap/core': 3.30.2(@tiptap/pm@3.30.2) + + '@tiptap/extension-placeholder@3.30.2(@tiptap/extensions@3.30.2(@tiptap/core@3.30.2(@tiptap/pm@3.30.2))(@tiptap/pm@3.30.2))': + dependencies: + '@tiptap/extensions': 3.30.2(@tiptap/core@3.30.2(@tiptap/pm@3.30.2))(@tiptap/pm@3.30.2) + + '@tiptap/extension-strike@3.30.2(@tiptap/core@3.30.2(@tiptap/pm@3.30.2))': + dependencies: + '@tiptap/core': 3.30.2(@tiptap/pm@3.30.2) + + '@tiptap/extension-text@3.30.2(@tiptap/core@3.30.2(@tiptap/pm@3.30.2))': + dependencies: + '@tiptap/core': 3.30.2(@tiptap/pm@3.30.2) + + '@tiptap/extension-underline@3.30.2(@tiptap/core@3.30.2(@tiptap/pm@3.30.2))': + dependencies: + '@tiptap/core': 3.30.2(@tiptap/pm@3.30.2) + + '@tiptap/extensions@3.30.2(@tiptap/core@3.30.2(@tiptap/pm@3.30.2))(@tiptap/pm@3.30.2)': + dependencies: + '@tiptap/core': 3.30.2(@tiptap/pm@3.30.2) + '@tiptap/pm': 3.30.2 + + '@tiptap/pm@3.30.2': + dependencies: + prosemirror-changeset: 2.4.1 + prosemirror-commands: 1.7.2 + prosemirror-dropcursor: 1.8.3 + prosemirror-gapcursor: 1.4.1 + prosemirror-history: 1.5.0 + prosemirror-inputrules: 1.5.1 + prosemirror-keymap: 1.2.3 + prosemirror-model: 1.25.11 + prosemirror-schema-list: 1.5.1 + prosemirror-state: 1.4.4 + prosemirror-tables: 1.8.5 + prosemirror-transform: 1.12.0 + prosemirror-view: 1.42.2 + + '@tiptap/react@3.30.2(@floating-ui/dom@1.8.0)(@tiptap/core@3.30.2(@tiptap/pm@3.30.2))(@tiptap/pm@3.30.2)(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)': + dependencies: + '@tiptap/core': 3.30.2(@tiptap/pm@3.30.2) + '@tiptap/pm': 3.30.2 + '@types/react': 19.2.17 + '@types/react-dom': 19.2.3(@types/react@19.2.17) + '@types/use-sync-external-store': 0.0.6 + fast-equals: 5.4.1 + react: 19.2.4 + react-dom: 19.2.4(react@19.2.4) + use-sync-external-store: 1.6.0(react@19.2.4) + optionalDependencies: + '@tiptap/extension-bubble-menu': 3.30.2(@tiptap/core@3.30.2(@tiptap/pm@3.30.2))(@tiptap/pm@3.30.2) + '@tiptap/extension-floating-menu': 3.30.2(@floating-ui/dom@1.8.0)(@tiptap/core@3.30.2(@tiptap/pm@3.30.2))(@tiptap/pm@3.30.2) + transitivePeerDependencies: + - '@floating-ui/dom' + + '@tiptap/starter-kit@3.30.2': + dependencies: + '@tiptap/core': 3.30.2(@tiptap/pm@3.30.2) + '@tiptap/extension-blockquote': 3.30.2(@tiptap/core@3.30.2(@tiptap/pm@3.30.2))(@tiptap/pm@3.30.2) + '@tiptap/extension-bold': 3.30.2(@tiptap/core@3.30.2(@tiptap/pm@3.30.2)) + '@tiptap/extension-bullet-list': 3.30.2(@tiptap/extension-list@3.30.2(@tiptap/core@3.30.2(@tiptap/pm@3.30.2))(@tiptap/pm@3.30.2)) + '@tiptap/extension-code': 3.30.2(@tiptap/core@3.30.2(@tiptap/pm@3.30.2)) + '@tiptap/extension-code-block': 3.30.2(@tiptap/core@3.30.2(@tiptap/pm@3.30.2))(@tiptap/pm@3.30.2) + '@tiptap/extension-document': 3.30.2(@tiptap/core@3.30.2(@tiptap/pm@3.30.2)) + '@tiptap/extension-dropcursor': 3.30.2(@tiptap/extensions@3.30.2(@tiptap/core@3.30.2(@tiptap/pm@3.30.2))(@tiptap/pm@3.30.2)) + '@tiptap/extension-gapcursor': 3.30.2(@tiptap/extensions@3.30.2(@tiptap/core@3.30.2(@tiptap/pm@3.30.2))(@tiptap/pm@3.30.2)) + '@tiptap/extension-hard-break': 3.30.2(@tiptap/core@3.30.2(@tiptap/pm@3.30.2)) + '@tiptap/extension-heading': 3.30.2(@tiptap/core@3.30.2(@tiptap/pm@3.30.2)) + '@tiptap/extension-horizontal-rule': 3.30.2(@tiptap/core@3.30.2(@tiptap/pm@3.30.2))(@tiptap/pm@3.30.2) + '@tiptap/extension-italic': 3.30.2(@tiptap/core@3.30.2(@tiptap/pm@3.30.2)) + '@tiptap/extension-link': 3.30.2(@tiptap/core@3.30.2(@tiptap/pm@3.30.2))(@tiptap/pm@3.30.2) + '@tiptap/extension-list': 3.30.2(@tiptap/core@3.30.2(@tiptap/pm@3.30.2))(@tiptap/pm@3.30.2) + '@tiptap/extension-list-item': 3.30.2(@tiptap/extension-list@3.30.2(@tiptap/core@3.30.2(@tiptap/pm@3.30.2))(@tiptap/pm@3.30.2)) + '@tiptap/extension-list-keymap': 3.30.2(@tiptap/extension-list@3.30.2(@tiptap/core@3.30.2(@tiptap/pm@3.30.2))(@tiptap/pm@3.30.2)) + '@tiptap/extension-ordered-list': 3.30.2(@tiptap/extension-list@3.30.2(@tiptap/core@3.30.2(@tiptap/pm@3.30.2))(@tiptap/pm@3.30.2)) + '@tiptap/extension-paragraph': 3.30.2(@tiptap/core@3.30.2(@tiptap/pm@3.30.2)) + '@tiptap/extension-strike': 3.30.2(@tiptap/core@3.30.2(@tiptap/pm@3.30.2)) + '@tiptap/extension-text': 3.30.2(@tiptap/core@3.30.2(@tiptap/pm@3.30.2)) + '@tiptap/extension-underline': 3.30.2(@tiptap/core@3.30.2(@tiptap/pm@3.30.2)) + '@tiptap/extensions': 3.30.2(@tiptap/core@3.30.2(@tiptap/pm@3.30.2))(@tiptap/pm@3.30.2) + '@tiptap/pm': 3.30.2 + '@tybys/wasm-util@0.10.3': dependencies: tslib: 2.8.1 @@ -4166,6 +4626,24 @@ snapshots: '@types/json5@0.0.29': {} + '@types/linkify-it@3.0.5': {} + + '@types/linkify-it@5.0.0': {} + + '@types/markdown-it@13.0.9': + dependencies: + '@types/linkify-it': 3.0.5 + '@types/mdurl': 1.0.5 + + '@types/markdown-it@14.2.0': + dependencies: + '@types/linkify-it': 5.0.0 + '@types/mdurl': 2.0.0 + + '@types/mdurl@1.0.5': {} + + '@types/mdurl@2.0.0': {} + '@types/node@20.19.43': dependencies: undici-types: 6.21.0 @@ -4186,6 +4664,8 @@ snapshots: '@types/trusted-types@2.0.7': {} + '@types/use-sync-external-store@0.0.6': {} + '@typescript-eslint/eslint-plugin@8.62.0(@typescript-eslint/parser@8.62.0(eslint@9.39.4(jiti@2.7.0))(typescript@5.9.3))(eslint@9.39.4(jiti@2.7.0))(typescript@5.9.3)': dependencies: '@eslint-community/regexpp': 4.12.2 @@ -4710,6 +5190,8 @@ snapshots: graceful-fs: 4.2.11 tapable: 2.3.3 + entities@4.5.0: {} + entities@8.0.0: {} es-abstract-get@1.0.0: @@ -5042,6 +5524,8 @@ snapshots: fast-deep-equal@3.1.3: {} + fast-equals@5.4.1: {} + fast-glob@3.3.1: dependencies: '@nodelib/fs.stat': 2.0.5 @@ -5491,6 +5975,12 @@ snapshots: lightningcss-win32-arm64-msvc: 1.32.0 lightningcss-win32-x64-msvc: 1.32.0 + linkify-it@5.0.2: + dependencies: + uc.micro: 2.1.0 + + linkifyjs@4.3.3: {} + locate-path@6.0.0: dependencies: p-locate: 5.0.0 @@ -5517,10 +6007,23 @@ snapshots: dependencies: '@jridgewell/sourcemap-codec': 1.5.5 + markdown-it-task-lists@2.1.1: {} + + markdown-it@14.3.0: + dependencies: + argparse: 2.0.1 + entities: 4.5.0 + linkify-it: 5.0.2 + mdurl: 2.1.0 + punycode.js: 2.3.1 + uc.micro: 2.1.0 + math-intrinsics@1.1.0: {} mdn-data@2.27.1: {} + mdurl@2.1.0: {} + merge2@1.4.1: {} micromatch@4.0.8: @@ -5637,6 +6140,8 @@ snapshots: type-check: 0.4.0 word-wrap: 1.2.5 + orderedmap@2.1.1: {} + own-keys@1.0.1: dependencies: get-intrinsic: 1.3.0 @@ -5754,6 +6259,88 @@ snapshots: object-assign: 4.1.1 react-is: 16.13.1 + prosemirror-changeset@2.4.1: + dependencies: + prosemirror-transform: 1.12.0 + + prosemirror-commands@1.7.2: + dependencies: + prosemirror-model: 1.25.11 + prosemirror-state: 1.4.4 + prosemirror-transform: 1.12.0 + + prosemirror-dropcursor@1.8.3: + dependencies: + prosemirror-state: 1.4.4 + prosemirror-transform: 1.12.0 + prosemirror-view: 1.42.2 + + prosemirror-gapcursor@1.4.1: + dependencies: + prosemirror-keymap: 1.2.3 + prosemirror-model: 1.25.11 + prosemirror-state: 1.4.4 + prosemirror-view: 1.42.2 + + prosemirror-history@1.5.0: + dependencies: + prosemirror-state: 1.4.4 + prosemirror-transform: 1.12.0 + prosemirror-view: 1.42.2 + rope-sequence: 1.3.4 + + prosemirror-inputrules@1.5.1: + dependencies: + prosemirror-state: 1.4.4 + prosemirror-transform: 1.12.0 + + prosemirror-keymap@1.2.3: + dependencies: + prosemirror-state: 1.4.4 + w3c-keyname: 2.2.8 + + prosemirror-markdown@1.13.6: + dependencies: + '@types/markdown-it': 14.2.0 + markdown-it: 14.3.0 + prosemirror-model: 1.25.11 + + prosemirror-model@1.25.11: + dependencies: + orderedmap: 2.1.1 + + prosemirror-schema-list@1.5.1: + dependencies: + prosemirror-model: 1.25.11 + prosemirror-state: 1.4.4 + prosemirror-transform: 1.12.0 + + prosemirror-state@1.4.4: + dependencies: + prosemirror-model: 1.25.11 + prosemirror-transform: 1.12.0 + prosemirror-view: 1.42.2 + + prosemirror-tables@1.8.5: + dependencies: + prosemirror-keymap: 1.2.3 + prosemirror-model: 1.25.11 + prosemirror-state: 1.4.4 + prosemirror-transform: 1.12.0 + prosemirror-view: 1.42.2 + + prosemirror-transform@1.12.0: + dependencies: + prosemirror-model: 1.25.11 + + prosemirror-view@1.42.2: + dependencies: + prosemirror-model: 1.25.11 + prosemirror-state: 1.4.4 + prosemirror-transform: 1.12.0 + + punycode.js@2.3.1: {} + punycode@2.3.1: {} queue-microtask@1.2.3: {} @@ -5837,6 +6424,8 @@ snapshots: '@rolldown/binding-win32-arm64-msvc': 1.1.5 '@rolldown/binding-win32-x64-msvc': 1.1.5 + rope-sequence@1.3.4: {} + rou3@0.7.12: {} run-parallel@1.2.0: @@ -6080,6 +6669,14 @@ snapshots: tinyrainbow@3.1.0: {} + tiptap-markdown@0.9.0(@tiptap/core@3.30.2(@tiptap/pm@3.30.2)): + dependencies: + '@tiptap/core': 3.30.2(@tiptap/pm@3.30.2) + '@types/markdown-it': 13.0.9 + markdown-it: 14.3.0 + markdown-it-task-lists: 2.1.1 + prosemirror-markdown: 1.13.6 + tldts-core@7.4.8: {} tldts@7.4.8: @@ -6161,6 +6758,8 @@ snapshots: typescript@5.9.3: {} + uc.micro@2.1.0: {} + unbox-primitive@1.1.0: dependencies: call-bound: 1.0.4 @@ -6209,6 +6808,10 @@ snapshots: dependencies: punycode: 2.3.1 + use-sync-external-store@1.6.0(react@19.2.4): + dependencies: + react: 19.2.4 + vite@8.1.4(@types/node@20.19.43)(jiti@2.7.0): dependencies: lightningcss: 1.32.0 @@ -6249,6 +6852,8 @@ snapshots: transitivePeerDependencies: - msw + w3c-keyname@2.2.8: {} + w3c-xmlserializer@5.0.0: dependencies: xml-name-validator: 5.0.0 From 03550c707800f4efe0b631186df61296687c4ae8 Mon Sep 17 00:00:00 2001 From: qyinm Date: Thu, 20 Aug 2026 22:39:24 +0900 Subject: [PATCH 2/9] feat(citations): create astryx inline citation extension for tiptap --- .../citations/tiptap-citation-extension.tsx | 231 ++++++++++++++++++ 1 file changed, 231 insertions(+) create mode 100644 apps/web/src/features/citations/tiptap-citation-extension.tsx diff --git a/apps/web/src/features/citations/tiptap-citation-extension.tsx b/apps/web/src/features/citations/tiptap-citation-extension.tsx new file mode 100644 index 0000000..5838729 --- /dev/null +++ b/apps/web/src/features/citations/tiptap-citation-extension.tsx @@ -0,0 +1,231 @@ +"use client"; + +import { Node as TiptapNode, mergeAttributes } from "@tiptap/core"; +import { NodeViewWrapper, ReactNodeViewRenderer, type NodeViewProps } from "@tiptap/react"; +import { Citation } from "@astryxdesign/core/Citation"; +import { ExternalLink, ChevronLeft, ChevronRight, X } from "lucide-react"; +import { useState, useId, useRef, useEffect } from "react"; + +export type CitationSourceItem = { + title: string; + url: string; + provider?: string; + excerpt?: string; +}; + +export const TiptapCitationExtension = TiptapNode.create({ + name: "citation", + group: "inline", + inline: true, + atom: true, + selectable: true, + + addAttributes() { + return { + statementId: { + default: null, + parseHTML: (element) => element.getAttribute("data-statement-id"), + renderHTML: (attributes) => ({ + "data-statement-id": attributes.statementId, + }), + }, + sources: { + default: [], + parseHTML: (element) => { + try { + const raw = element.getAttribute("data-sources"); + return raw ? JSON.parse(raw) : []; + } catch { + return []; + } + }, + renderHTML: (attributes) => ({ + "data-sources": JSON.stringify(attributes.sources || []), + }), + }, + number: { + default: 1, + parseHTML: (element) => Number(element.getAttribute("data-number")) || 1, + renderHTML: (attributes) => ({ + "data-number": attributes.number, + }), + }, + }; + }, + + parseHTML() { + return [{ tag: "span[data-citation-node]" }]; + }, + + renderHTML({ HTMLAttributes }) { + return ["span", mergeAttributes(HTMLAttributes, { "data-citation-node": "" })]; + }, + + addNodeView() { + return ReactNodeViewRenderer(TiptapCitationNodeView); + }, +}); + +export function TiptapCitationNodeView({ node }: NodeViewProps) { + const { sources = [], number = 1 } = node.attrs as { + sources: CitationSourceItem[]; + number: number; + statementId: string | null; + }; + const [open, setOpen] = useState(false); + const [currentIndex, setCurrentIndex] = useState(0); + const popoverId = useId(); + const popoverRef = useRef(null); + const triggerRef = useRef(null); + + const sourceList: CitationSourceItem[] = sources.length + ? sources + : [{ title: "Source", url: "#" }]; + const currentSource = sourceList[currentIndex] || sourceList[0]; + const primarySource = sourceList[0]; + const additionalCount = sourceList.length - 1; + + useEffect(() => { + if (!open) return; + + function handleOutsideClick(event: Event) { + if ( + event.target instanceof Node && + !popoverRef.current?.contains(event.target) && + !triggerRef.current?.contains(event.target) + ) { + setOpen(false); + } + } + + function handleKeyDown(event: KeyboardEvent) { + if (event.key === "Escape") { + event.preventDefault(); + setOpen(false); + } + } + + document.addEventListener("pointerdown", handleOutsideClick, true); + document.addEventListener("click", handleOutsideClick, true); + document.addEventListener("keydown", handleKeyDown); + return () => { + document.removeEventListener("pointerdown", handleOutsideClick, true); + document.removeEventListener("click", handleOutsideClick, true); + document.removeEventListener("keydown", handleKeyDown); + }; + }, [open]); + + return ( + + { + e.preventDefault(); + e.stopPropagation(); + setOpen((prev) => !prev); + }} + onKeyDown={(e) => { + if (e.key === "Enter" || e.key === " ") { + e.preventDefault(); + setOpen((prev) => !prev); + } + }} + className="inline-flex cursor-pointer items-center transition-transform hover:opacity-90 active:scale-95" + > + 0 ? `${primarySource.title} +${additionalCount}` : primarySource.title, + url: primarySource.url, + }} + number={number} + variant="label" + /> + + + {open ? ( + + ) : null} + + ); +} From 8f6b647babc1224e877bfa268a30fc7330e0e0c8 Mon Sep 17 00:00:00 2001 From: qyinm Date: Thu, 20 Aug 2026 22:39:29 +0900 Subject: [PATCH 3/9] feat(editor): implement tiptap draft editor with lexical ast compatibility --- .../citations/tiptap-draft-editor.test.tsx | 114 ++++++ .../citations/tiptap-draft-editor.tsx | 354 ++++++++++++++++++ 2 files changed, 468 insertions(+) create mode 100644 apps/web/src/features/citations/tiptap-draft-editor.test.tsx create mode 100644 apps/web/src/features/citations/tiptap-draft-editor.tsx diff --git a/apps/web/src/features/citations/tiptap-draft-editor.test.tsx b/apps/web/src/features/citations/tiptap-draft-editor.test.tsx new file mode 100644 index 0000000..48ad56b --- /dev/null +++ b/apps/web/src/features/citations/tiptap-draft-editor.test.tsx @@ -0,0 +1,114 @@ +// @vitest-environment jsdom + +import { render, screen, waitFor } from "@testing-library/react"; +import { describe, expect, it, vi } from "vitest"; + +import { TiptapDraftEditor } from "./tiptap-draft-editor"; +import type { Artifact } from "@plot/api-client"; + +const pack: Artifact = { + id: "pack-1", + status: "NEEDS_REVIEW", + title: "July changelog", + variant: { + id: "variant-1", + status: "NEEDS_REVIEW", + revisionId: "artifact-revision-1", + revisionNumber: 1, + lexicalContent: lexicalContent( + "Sign-in recovery now explains the next step.", + "The release is delightful.", + ), + sentences: [ + { + id: "sentence-1", + revisionId: "sentence-revision-1", + revisionNumber: 1, + orderIndex: 0, + body: "Sign-in recovery now explains the next step.", + origin: "GENERATED", + citations: [ + { + evidenceId: "evidence-1", + provider: "GITHUB", + sourceLabel: "PR #184", + originalUrl: "https://github.com/acme/plot/pull/184", + }, + ], + }, + { + id: "sentence-2", + revisionId: "sentence-revision-2", + revisionNumber: 1, + orderIndex: 1, + body: "The release is delightful.", + origin: "GENERATED", + citations: [], + }, + ], + sources: [ + { + evidenceId: "evidence-1", + provider: "GITHUB", + sourceLabel: "PR #184", + originalUrl: "https://github.com/acme/plot/pull/184", + statementIds: ["sentence-1"], + }, + ], + }, +}; + +function lexicalContent(...bodies: string[]) { + return { + root: { + children: bodies.map((body) => ({ + children: [{ detail: 0, format: 0, mode: "normal", style: "", text: body, type: "text", version: 1 }], + direction: null, + format: "", + indent: 0, + type: "paragraph", + version: 1, + })), + direction: null, + format: "", + indent: 0, + type: "root", + version: 1, + }, + }; +} + +describe("TiptapDraftEditor", () => { + it("renders historical snapshots read-only without a delivery edit control", () => { + render(); + + expect(screen.getByRole("textbox", { name: "Historical artifact content" })).toHaveAttribute("contenteditable", "false"); + expect(screen.queryByRole("button", { name: "Save draft" })).not.toBeInTheDocument(); + }); + + it("renders document text and Astryx inline citations correctly", async () => { + const onSaveArtifact = vi.fn().mockResolvedValue(pack); + render(); + + expect(screen.getByRole("textbox", { name: "Draft content" })).toBeInTheDocument(); + expect(screen.getByText("Sign-in recovery now explains the next step.")).toBeInTheDocument(); + expect(screen.getByText("The release is delightful.")).toBeInTheDocument(); + }); + + it("displays save confirmation status", async () => { + const onSaveArtifact = vi.fn().mockResolvedValue(pack); + const onSaveStateChange = vi.fn(); + + render( + , + ); + + await waitFor(() => { + expect(onSaveStateChange).toHaveBeenCalledWith("saved"); + }); + }); +}); diff --git a/apps/web/src/features/citations/tiptap-draft-editor.tsx b/apps/web/src/features/citations/tiptap-draft-editor.tsx new file mode 100644 index 0000000..1ba2810 --- /dev/null +++ b/apps/web/src/features/citations/tiptap-draft-editor.tsx @@ -0,0 +1,354 @@ +"use client"; + +import { useEditor, EditorContent, type JSONContent } from "@tiptap/react"; +import StarterKit from "@tiptap/starter-kit"; +import Placeholder from "@tiptap/extension-placeholder"; +import { Markdown } from "tiptap-markdown"; +import { Save } from "lucide-react"; +import { useCallback, useEffect, useMemo, useRef, useState } from "react"; + +import type { + Artifact, + ContentSentence, + ContentStatementInput, +} from "@plot/api-client"; +import { SourcesPopover } from "./sources-popover"; +import { TiptapCitationExtension, type CitationSourceItem } from "./tiptap-citation-extension"; + +export type SaveArtifactInput = { + expectedRevisionNumber: number; + lexicalContent: Record; + statements: ContentStatementInput[]; +}; + +type TiptapDraftEditorProps = { + pack: Artifact; + onSaveArtifact: (input: SaveArtifactInput) => Promise; + onPackChange?: (pack: Artifact) => void; + readOnly?: boolean; + embedded?: boolean; + onSaveStateChange?: (state: "saved" | "saving" | "dirty" | "error") => void; + initialDraft?: Omit; + onDraftChange?: (draft: Omit) => void; + presentation?: "panel" | "document"; + saveRequestToken?: number; +}; + +export function TiptapDraftEditor(props: TiptapDraftEditorProps) { + const revisionKey = `${props.pack.variant.revisionId}:${props.pack.variant.revisionNumber}:${props.readOnly ? "read-only" : "editable"}`; + return ; +} + +function TiptapArtifactEditor({ + pack, + onSaveArtifact, + onPackChange, + readOnly = false, + embedded = false, + onSaveStateChange, + initialDraft, + onDraftChange, + presentation = "panel", + saveRequestToken, +}: TiptapDraftEditorProps) { + const sentences = useMemo( + () => [...pack.variant.sentences].sort((a, b) => a.orderIndex - b.orderIndex), + [pack.variant.sentences], + ); + const revisionNumber = pack.variant.revisionNumber; + const revisionKey = `${pack.variant.revisionId}:${revisionNumber}`; + const initialContent = useMemo( + () => initialDraft?.lexicalContent ?? pack.variant.lexicalContent, + [initialDraft?.lexicalContent, pack.variant.lexicalContent], + ); + + const initialTiptapDoc = useMemo( + () => convertToTiptapDoc(initialContent, sentences), + [initialContent, sentences], + ); + + const [saving, setSaving] = useState(false); + const [message, setMessage] = useState(""); + const previousSaveRequestRef = useRef(saveRequestToken); + const draftStateRef = useRef>(initialContent); + const draftStatementsRef = useRef( + initialDraft?.statements ?? defaultStatementsFor(sentences), + ); + + const editor = useEditor({ + immediatelyRender: false, + editable: !readOnly, + content: initialTiptapDoc, + extensions: [ + StarterKit.configure({ + heading: { levels: [1, 2, 3] }, + }), + Placeholder.configure({ + placeholder: "Write the source-backed artifact…", + }), + Markdown.configure({ + html: true, + tightLists: true, + }), + TiptapCitationExtension, + ], + editorProps: { + attributes: { + role: "textbox", + "aria-label": readOnly ? "Historical artifact content" : "Draft content", + "aria-readonly": readOnly ? "true" : "false", + class: presentation === "document" + ? "min-h-[720px] focus:outline-none text-[15px] leading-6 text-black/88 dark:text-white/88 prose prose-none max-w-none [&_h1]:mb-[22px] [&_h1]:font-display [&_h1]:text-[30px] [&_h1]:leading-[38px] [&_h2]:mb-[22px] [&_h2]:text-[19px] [&_h2]:font-semibold [&_h2]:leading-[26px] [&_li]:mb-1.5 [&_ol]:list-decimal [&_ol]:pl-5 [&_p]:mb-[22px] [&_ul]:list-disc [&_ul]:pl-5" + : `min-h-[260px] rounded-lg border border-black/10 px-4 py-4 text-[15px] leading-7 text-black/84 focus:outline-none focus-within:border-black/35 dark:border-white/12 dark:text-white/86 dark:focus-within:border-white/35 prose prose-none max-w-none ${ + readOnly ? "bg-black/[0.025] dark:bg-white/[0.025]" : "bg-white dark:bg-[#18181b]" + }`, + }, + }, + onUpdate: ({ editor: currentEditor }) => { + const json = currentEditor.getJSON() as Record; + draftStateRef.current = json; + + // Extract statement inputs from block nodes + const statements = extractStatementsFromTiptap(json, sentences); + draftStatementsRef.current = statements; + + if (!readOnly) { + onDraftChange?.({ lexicalContent: json, statements }); + onSaveStateChange?.("dirty"); + } + }, + }); + + useEffect(() => { + onSaveStateChange?.("saved"); + }, [onSaveStateChange, revisionKey]); + + const save = useCallback(async () => { + if (saving || readOnly) return; + setSaving(true); + setMessage(""); + onSaveStateChange?.("saving"); + try { + const updated = await onSaveArtifact({ + expectedRevisionNumber: revisionNumber, + lexicalContent: draftStateRef.current, + statements: draftStatementsRef.current, + }); + onPackChange?.(updated); + setMessage( + `Saved ${new Intl.DateTimeFormat(undefined, { + hour: "numeric", + minute: "2-digit", + }).format(new Date())}.`, + ); + onSaveStateChange?.("saved"); + } catch (error) { + setMessage(error instanceof Error ? error.message : "The draft could not be saved."); + onSaveStateChange?.("error"); + } finally { + setSaving(false); + } + }, [onPackChange, onSaveArtifact, onSaveStateChange, readOnly, revisionNumber, saving]); + + useEffect(() => { + if (saveRequestToken === undefined || previousSaveRequestRef.current === saveRequestToken) return; + previousSaveRequestRef.current = saveRequestToken; + void save(); + }, [save, saveRequestToken]); + + const documentPresentation = presentation === "document"; + + return ( +
+ {!documentPresentation ? ( +
+
+

+ {readOnly ? "Historical preview" : "Artifact document"} +

+

+ {readOnly + ? "This snapshot is read-only. Editing and delivery are disabled." + : "Edit the whole artifact. Sources stay outside the document and stay attached to it."} +

+
+ +
+ ) : null} + +
+ +
+ + {!documentPresentation ? ( +
+

+ {readOnly ? "Saved snapshot" : saving ? "Saving…" : message || "Saved"} +

+ {!readOnly ? ( + + ) : null} +
+ ) : null} + + {message ? ( +

+ {message} +

+ ) : null} +
+ ); +} + +// Converts Lexical or Tiptap JSON to a Tiptap Document with Inline Citation Nodes +function convertToTiptapDoc( + content: Record | undefined, + sentences: ContentSentence[], +): JSONContent { + if (!content) { + return { + type: "doc", + content: [{ type: "paragraph" }], + }; + } + + // If already in Tiptap format + if (content.type === "doc" && Array.isArray(content.content)) { + return content as JSONContent; + } + + // Convert Lexical AST to Tiptap JSON + const root = (content.root as Record) || content; + const children = Array.isArray(root.children) ? (root.children as Record[]) : []; + + if (!children.length) { + return { + type: "doc", + content: [{ type: "paragraph" }], + }; + } + + const tiptapContent: JSONContent[] = children.map((lexicalNode, blockIndex) => { + const nodeType = (lexicalNode.type as string) || "paragraph"; + const lexicalChildren = Array.isArray(lexicalNode.children) + ? (lexicalNode.children as Record[]) + : []; + + const paragraphContent: JSONContent[] = []; + + // Extract text nodes + for (const child of lexicalChildren) { + if (child.type === "text" && typeof child.text === "string" && child.text) { + paragraphContent.push({ + type: "text", + text: child.text, + }); + } else if (child.type === "linebreak") { + paragraphContent.push({ + type: "hardBreak", + }); + } + } + + // Attach inline citation if this sentence block has citations + const matchedSentence = sentences[blockIndex] || sentences.find((s) => s.orderIndex === blockIndex); + if (matchedSentence && matchedSentence.citations && matchedSentence.citations.length > 0) { + const citationSources: CitationSourceItem[] = matchedSentence.citations.map((c) => ({ + title: c.sourceLabel || "Source", + url: c.originalUrl || "#", + provider: c.provider || "GitHub", + })); + + paragraphContent.push({ + type: "citation", + attrs: { + statementId: matchedSentence.id, + number: blockIndex + 1, + sources: citationSources, + }, + }); + } + + if (nodeType === "heading") { + const tag = (lexicalNode.tag as string) || "h2"; + const level = tag === "h1" ? 1 : tag === "h3" ? 3 : 2; + return { + type: "heading", + attrs: { level }, + content: paragraphContent.length ? paragraphContent : undefined, + }; + } + + return { + type: "paragraph", + content: paragraphContent.length ? paragraphContent : undefined, + }; + }); + + return { + type: "doc", + content: tiptapContent.length ? tiptapContent : [{ type: "paragraph" }], + }; +} + +function extractStatementsFromTiptap( + doc: JSONContent, + originalSentences: ContentSentence[], +): ContentStatementInput[] { + const content = doc.content || []; + const statements: ContentStatementInput[] = []; + + content.forEach((block, index) => { + const text = extractTextFromBlock(block); + if (!text.trim()) return; + + const matchedSentence = originalSentences[index]; + statements.push({ + id: matchedSentence?.id, + orderIndex: index, + body: text.trim(), + }); + }); + + return statements.length + ? statements + : [{ id: null, orderIndex: 0, body: "Generated artifact" }]; +} + +function extractTextFromBlock(node: JSONContent): string { + if (!node.content) return ""; + return node.content + .map((child) => { + if (child.type === "text") return child.text || ""; + if (child.type === "hardBreak") return "\n"; + return ""; + }) + .join(""); +} + +function defaultStatementsFor(sentences: ContentSentence[]): ContentStatementInput[] { + return sentences.map((sentence) => ({ + id: sentence.id, + orderIndex: sentence.orderIndex, + body: sentence.body, + })); +} From bbeb6de994926a0e13ab8fbe98b6e575093952db Mon Sep 17 00:00:00 2001 From: qyinm Date: Thu, 20 Aug 2026 22:39:38 +0900 Subject: [PATCH 4/9] feat(artifacts): integrate tiptap editor into artifact document surface --- .../src/features/artifacts/artifact-document-surface.tsx | 6 +++--- apps/web/src/features/chat/chat-workspace.test.tsx | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/apps/web/src/features/artifacts/artifact-document-surface.tsx b/apps/web/src/features/artifacts/artifact-document-surface.tsx index 56137b8..b41a226 100644 --- a/apps/web/src/features/artifacts/artifact-document-surface.tsx +++ b/apps/web/src/features/artifacts/artifact-document-surface.tsx @@ -2,7 +2,7 @@ import type { Artifact, ArtifactHistoryDetail, PlotApiClient } from "@plot/api-client"; -import { CitedDraftEditor, type SaveArtifactInput } from "@/features/citations/cited-draft-editor"; +import { TiptapDraftEditor, type SaveArtifactInput } from "@/features/citations/tiptap-draft-editor"; import { ExportDialog } from "@/features/citations/export-dialog"; type ArtifactDocumentSurfaceProps = { @@ -47,7 +47,7 @@ export function ArtifactDocumentSurface({ {!workspacePresentation ? (

{shownPack.title || "Generated artifact"}

) : null} - :

Editing and delivery are disabled for this snapshot.

} - ({ ), })); -vi.mock("@/features/citations/cited-draft-editor", () => ({ CitedDraftEditor: () =>
Reviewed artifact
})); +vi.mock("@/features/citations/tiptap-draft-editor", () => ({ TiptapDraftEditor: () =>
Reviewed artifact
})); vi.mock("@/features/citations/export-dialog", () => ({ ExportDialog: ({ presentation }: { presentation?: string }) => presentation === "copy" ? : null })); vi.mock("@/features/citations/artifact-history-panel", () => ({ ArtifactHistoryPanel: () =>
History
})); From dbb8b782e91182933965d6b484659a7eb0eea855 Mon Sep 17 00:00:00 2001 From: qyinm Date: Thu, 20 Aug 2026 23:13:27 +0900 Subject: [PATCH 5/9] fix(editor): serialize tiptap document to standard AST on draft update and save --- .../citations/tiptap-draft-editor.tsx | 62 ++++++++++++++++++- 1 file changed, 59 insertions(+), 3 deletions(-) diff --git a/apps/web/src/features/citations/tiptap-draft-editor.tsx b/apps/web/src/features/citations/tiptap-draft-editor.tsx index 1ba2810..6c7ef35 100644 --- a/apps/web/src/features/citations/tiptap-draft-editor.tsx +++ b/apps/web/src/features/citations/tiptap-draft-editor.tsx @@ -105,15 +105,16 @@ function TiptapArtifactEditor({ }, }, onUpdate: ({ editor: currentEditor }) => { - const json = currentEditor.getJSON() as Record; - draftStateRef.current = json; + const json = currentEditor.getJSON(); + const lexicalJson = tiptapToLexicalJson(json); + draftStateRef.current = lexicalJson; // Extract statement inputs from block nodes const statements = extractStatementsFromTiptap(json, sentences); draftStatementsRef.current = statements; if (!readOnly) { - onDraftChange?.({ lexicalContent: json, statements }); + onDraftChange?.({ lexicalContent: lexicalJson, statements }); onSaveStateChange?.("dirty"); } }, @@ -352,3 +353,58 @@ function defaultStatementsFor(sentences: ContentSentence[]): ContentStatementInp body: sentence.body, })); } + +function tiptapToLexicalJson(doc: JSONContent): Record { + const content = doc.content || []; + return { + root: { + children: content.map((block) => { + const paragraphChildren: Record[] = []; + if (block.content) { + for (const child of block.content) { + if (child.type === "text" && child.text) { + paragraphChildren.push({ + detail: 0, + format: 0, + mode: "normal", + style: "", + text: child.text, + type: "text", + version: 1, + }); + } else if (child.type === "hardBreak") { + paragraphChildren.push({ + type: "linebreak", + version: 1, + }); + } + } + } + if (!paragraphChildren.length) { + paragraphChildren.push({ + detail: 0, + format: 0, + mode: "normal", + style: "", + text: "", + type: "text", + version: 1, + }); + } + return { + children: paragraphChildren, + direction: null, + format: "", + indent: 0, + type: "paragraph", + version: 1, + }; + }), + direction: null, + format: "", + indent: 0, + type: "root", + version: 1, + }, + }; +} From 939b0dc836a49ce9af97ce112a29a29f3f871c45 Mon Sep 17 00:00:00 2001 From: qyinm Date: Thu, 20 Aug 2026 23:13:44 +0900 Subject: [PATCH 6/9] chore: ignore .playwright-cli directory --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index 2272e1b..4fc35f9 100644 --- a/.gitignore +++ b/.gitignore @@ -44,6 +44,7 @@ apps/web/test-results/ apps/web/playwright-report/ apps/web/blob-report/ apps/web/.playwright-artifacts/ +.playwright-cli/ **/trace.zip **/*.webm .vercel/ From c24b132f35434f2be2c2cb306929cc06738a8b25 Mon Sep 17 00:00:00 2001 From: qyinm Date: Thu, 20 Aug 2026 23:21:49 +0900 Subject: [PATCH 7/9] style(citations): align citation popover with plot monochromatic design system --- .../citations/tiptap-citation-extension.tsx | 121 ++++++++++-------- 1 file changed, 69 insertions(+), 52 deletions(-) diff --git a/apps/web/src/features/citations/tiptap-citation-extension.tsx b/apps/web/src/features/citations/tiptap-citation-extension.tsx index 5838729..5f40f3e 100644 --- a/apps/web/src/features/citations/tiptap-citation-extension.tsx +++ b/apps/web/src/features/citations/tiptap-citation-extension.tsx @@ -153,75 +153,92 @@ export function TiptapCitationNodeView({ node }: NodeViewProps) { id={popoverId} role="dialog" aria-label="Citation details" - className="absolute left-0 top-[calc(100%+6px)] z-50 w-[300px] rounded-xl border border-black/10 bg-white p-3.5 shadow-[0_12px_32px_rgba(0,0,0,0.12)] dark:border-white/10 dark:bg-[#202024] sm:w-[340px]" + className="absolute left-0 top-[calc(100%+8px)] z-50 w-[320px] rounded-2xl border border-black/10 bg-white/95 p-4 shadow-[0_16px_40px_rgba(0,0,0,0.12)] backdrop-blur-xl dark:border-white/10 dark:bg-[#1c1c1f]/95 dark:shadow-[0_16px_40px_rgba(0,0,0,0.4)] sm:w-[350px]" > - {/* Header with navigation */} -
-
- + {/* Header with Provider Badge and Navigation */} +
+
+ + {currentSource.provider || "GitHub"} +
+ +
+ {sourceList.length > 1 ? ( +
+ + + {currentIndex + 1} / {sourceList.length} + + +
+ ) : null} +
- - {currentIndex + 1} / {sourceList.length} - -
{/* Body */} -
-
- {currentSource.provider || "GitHub"} -
-
+
+

{currentSource.title} -

+ + {currentSource.excerpt ? ( -

+

{currentSource.excerpt} -

+
) : null} + {currentSource.url && currentSource.url !== "#" ? ( - - {currentSource.url} - - + ) : null}
From a998b234028fc0e0287ece2497f4cddbeeee1a69 Mon Sep 17 00:00:00 2001 From: qyinm Date: Thu, 20 Aug 2026 23:29:37 +0900 Subject: [PATCH 8/9] feat(citations): display source provider icon inside inline citation badge --- .../citations/tiptap-citation-extension.tsx | 77 ++++++++++++++++++- 1 file changed, 73 insertions(+), 4 deletions(-) diff --git a/apps/web/src/features/citations/tiptap-citation-extension.tsx b/apps/web/src/features/citations/tiptap-citation-extension.tsx index 5f40f3e..a1834b3 100644 --- a/apps/web/src/features/citations/tiptap-citation-extension.tsx +++ b/apps/web/src/features/citations/tiptap-citation-extension.tsx @@ -141,6 +141,7 @@ export function TiptapCitationNodeView({ node }: NodeViewProps) { source={{ title: additionalCount > 0 ? `${primarySource.title} +${additionalCount}` : primarySource.title, url: primarySource.url, + icon: getSourceProviderIcon(primarySource), }} number={number} variant="label" @@ -158,10 +159,8 @@ export function TiptapCitationNodeView({ node }: NodeViewProps) { {/* Header with Provider Badge and Navigation */}
- - {currentSource.provider || "GitHub"} + {getSourceProviderIcon(currentSource)} + {currentSource.provider || resolveProviderName(currentSource)}
@@ -246,3 +245,73 @@ export function TiptapCitationNodeView({ node }: NodeViewProps) { ); } + +function getSourceProviderIcon(source?: CitationSourceItem): ReactNode { + const url = (source?.url || "").toLowerCase(); + const provider = (source?.provider || "").toLowerCase(); + + if (url.includes("github.com") || provider === "github") { + return ( + + + + ); + } + + if (url.includes("linear.app") || provider === "linear") { + return ( + + + + ); + } + + if (url.includes("notion.so") || provider === "notion") { + return ( + + + + ); + } + + if (url.includes("slack.com") || provider === "slack") { + return ( + + + + + + + ); + } + + if (url.includes("figma.com") || provider === "figma") { + return ( + + + + + + + + ); + } + + return ( + + + + + + ); +} + +function resolveProviderName(source?: CitationSourceItem): string { + const url = (source?.url || "").toLowerCase(); + if (url.includes("github.com")) return "GitHub"; + if (url.includes("linear.app")) return "Linear"; + if (url.includes("notion.so")) return "Notion"; + if (url.includes("slack.com")) return "Slack"; + if (url.includes("figma.com")) return "Figma"; + return "Source"; +} From 73ac85928ae4408ab87a3f75dc950358233a8265 Mon Sep 17 00:00:00 2001 From: qyinm Date: Thu, 20 Aug 2026 23:49:23 +0900 Subject: [PATCH 9/9] fix(citations): import missing ReactNode type in citation extension --- apps/web/src/features/citations/tiptap-citation-extension.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/web/src/features/citations/tiptap-citation-extension.tsx b/apps/web/src/features/citations/tiptap-citation-extension.tsx index a1834b3..1274baa 100644 --- a/apps/web/src/features/citations/tiptap-citation-extension.tsx +++ b/apps/web/src/features/citations/tiptap-citation-extension.tsx @@ -4,7 +4,7 @@ import { Node as TiptapNode, mergeAttributes } from "@tiptap/core"; import { NodeViewWrapper, ReactNodeViewRenderer, type NodeViewProps } from "@tiptap/react"; import { Citation } from "@astryxdesign/core/Citation"; import { ExternalLink, ChevronLeft, ChevronRight, X } from "lucide-react"; -import { useState, useId, useRef, useEffect } from "react"; +import { useState, useId, useRef, useEffect, type ReactNode } from "react"; export type CitationSourceItem = { title: string;