@@ -8,6 +8,9 @@ importers:
.:
dependencies:
+ '@pierre/diffs':
+ specifier: ^1.0.9
+ version: 1.0.9(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
'@xterm/addon-fit':
specifier: ^0.11.0
version: 0.11.0
@@ -426,17 +429,53 @@ packages:
resolution: {integrity: sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ==}
engines: {node: '>=18.18'}
+ '@pierre/diffs@1.0.9':
+ resolution: {integrity: sha512-PiRhcAzz0yuifRTe2DmTsKOHQgGf1qgh5W9OVO3/c+DONrjo1PM5tquOxndCAZDTdPxwzGuWc6jPUaDrfu6nDg==}
+ peerDependencies:
+ react: ^18.3.1 || ^19.0.0
+ react-dom: ^18.3.1 || ^19.0.0
+
'@playwright/test@1.57.0':
resolution: {integrity: sha512-6TyEnHgd6SArQO8UO2OMTxshln3QMWBtPGrOCgs3wVEmQmwyuNtB10IZMfmYDE0riwNR1cu4q+pPcxMVtaG3TA==}
engines: {node: '>=18'}
hasBin: true
+ '@shikijs/core@3.21.0':
+ resolution: {integrity: sha512-AXSQu/2n1UIQekY8euBJlvFYZIw0PHY63jUzGbrOma4wPxzznJXTXkri+QcHeBNaFxiiOljKxxJkVSoB3PjbyA==}
+
+ '@shikijs/engine-javascript@3.21.0':
+ resolution: {integrity: sha512-ATwv86xlbmfD9n9gKRiwuPpWgPENAWCLwYCGz9ugTJlsO2kOzhOkvoyV/UD+tJ0uT7YRyD530x6ugNSffmvIiQ==}
+
+ '@shikijs/engine-oniguruma@3.21.0':
+ resolution: {integrity: sha512-OYknTCct6qiwpQDqDdf3iedRdzj6hFlOPv5hMvI+hkWfCKs5mlJ4TXziBG9nyabLwGulrUjHiCq3xCspSzErYQ==}
+
+ '@shikijs/langs@3.21.0':
+ resolution: {integrity: sha512-g6mn5m+Y6GBJ4wxmBYqalK9Sp0CFkUqfNzUy2pJglUginz6ZpWbaWjDB4fbQ/8SHzFjYbtU6Ddlp1pc+PPNDVA==}
+
+ '@shikijs/themes@3.21.0':
+ resolution: {integrity: sha512-BAE4cr9EDiZyYzwIHEk7JTBJ9CzlPuM4PchfcA5ao1dWXb25nv6hYsoDiBq2aZK9E3dlt3WB78uI96UESD+8Mw==}
+
+ '@shikijs/transformers@3.21.0':
+ resolution: {integrity: sha512-CZwvCWWIiRRiFk9/JKzdEooakAP8mQDtBOQ1TKiCaS2E1bYtyBCOkUzS8akO34/7ufICQ29oeSfkb3tT5KtrhA==}
+
+ '@shikijs/types@3.21.0':
+ resolution: {integrity: sha512-zGrWOxZ0/+0ovPY7PvBU2gIS9tmhSUUt30jAcNV0Bq0gb2S98gwfjIs1vxlmH5zM7/4YxLamT6ChlqqAJmPPjA==}
+
+ '@shikijs/vscode-textmate@10.0.2':
+ resolution: {integrity: sha512-83yeghZ2xxin3Nj8z1NMd/NCuca+gsYXswywDy5bHvwlWL8tpTQmzGeUuHd9FC3E/SBEMvzJRwWEOz5gGes9Qg==}
+
'@types/estree@1.0.8':
resolution: {integrity: sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==}
+ '@types/hast@3.0.4':
+ resolution: {integrity: sha512-WPs+bbQw5aCj+x6laNGWLH3wviHtoCv/P3+otBhbOhJgG8qtpdAMlTCxLtsTWA7LH1Oh/bFCHsBn0TPS5m30EQ==}
+
'@types/json-schema@7.0.15':
resolution: {integrity: sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==}
+ '@types/mdast@4.0.4':
+ resolution: {integrity: sha512-kGaNbPh1k7AFzgpud/gMdvIm5xuECykRR+JnWKQno9TAXVa6WIVCGTPvYGekIDL4uwCZQSYbUxNBSb1aUo79oA==}
+
'@types/node@22.19.5':
resolution: {integrity: sha512-HfF8+mYcHPcPypui3w3mvzuIErlNOh2OAG+BCeBZCEwyiD5ls2SiCwEyT47OELtf7M3nHxBdu0FsmzdKxkN52Q==}
@@ -451,6 +490,9 @@ packages:
'@types/react@18.3.27':
resolution: {integrity: sha512-cisd7gxkzjBKU2GgdYrTdtQx1SORymWyaAFhaxQPK9bYO9ot3Y5OikQRvY0VYQtvwjeQnizCINJAenh/V7MK2w==}
+ '@types/unist@3.0.3':
+ resolution: {integrity: sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q==}
+
'@typescript-eslint/eslint-plugin@8.52.0':
resolution: {integrity: sha512-okqtOgqu2qmZJ5iN4TWlgfF171dZmx2FzdOv2K/ixL2LZWDStL8+JgQerI2sa8eAEfoydG9+0V96m7V+P8yE1Q==}
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
@@ -510,6 +552,9 @@ packages:
resolution: {integrity: sha512-ink3/Zofus34nmBsPjow63FP5M7IGff0RKAgqR6+CFpdk22M7aLwC9gOcLGYqr7MczLPzZVERW9hRog3O4n1sQ==}
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
+ '@ungap/structured-clone@1.3.0':
+ resolution: {integrity: sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g==}
+
'@xterm/addon-fit@0.11.0':
resolution: {integrity: sha512-jYcgT6xtVYhnhgxh3QgYDnnNMYTcf8ElbxxFzX0IZo+vabQqSPAjC3c1wJrKB5E19VwQei89QCiZZP86DCPF7g==}
@@ -600,10 +645,19 @@ packages:
resolution: {integrity: sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==}
engines: {node: '>=6'}
+ ccount@2.0.1:
+ resolution: {integrity: sha512-eyrF0jiFpY+3drT6383f1qhkbGsLSifNAjA61IUjZjmLCWjItY6LB9ft9YhoDgwfmclB2zhu51Lc7+95b8NRAg==}
+
chalk@4.1.2:
resolution: {integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==}
engines: {node: '>=10'}
+ character-entities-html4@2.1.0:
+ resolution: {integrity: sha512-1v7fgQRj6hnSwFpq1Eu0ynr/CDEw0rXo2B61qXrLNdHZmPKgb7fqS1a2JwF0rISo9q77jDI8VMEHoApn8qDoZA==}
+
+ character-entities-legacy@3.0.0:
+ resolution: {integrity: sha512-RpPp0asT/6ufRm//AJVwpViZbGM/MkjQFxJccQRHmISF/22NBtsHqAWmL+/pmkPWoIUJdWyeVleTl1wydHATVQ==}
+
color-convert@2.0.1:
resolution: {integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==}
engines: {node: '>=7.0.0'}
@@ -611,6 +665,9 @@ packages:
color-name@1.1.4:
resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==}
+ comma-separated-tokens@2.0.3:
+ resolution: {integrity: sha512-Fu4hJdvzeylCfQPp9SGWidpzrMs7tTrlu6Vb8XGaRGck8QSNZJJp538Wrb60Lax4fPwR64ViY468OIUTbRlGZg==}
+
concat-map@0.0.1:
resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==}
@@ -656,6 +713,17 @@ packages:
resolution: {integrity: sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==}
engines: {node: '>= 0.4'}
+ dequal@2.0.3:
+ resolution: {integrity: sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==}
+ engines: {node: '>=6'}
+
+ devlop@1.1.0:
+ resolution: {integrity: sha512-RWmIqhcFf1lRYBvNmr7qTNuyCt/7/ns2jbpp1+PalgE/rDQcBT0fioSMUpJ93irlUhC5hrg4cYqe6U+0ImW0rA==}
+
+ diff@8.0.2:
+ resolution: {integrity: sha512-sSuxWU5j5SR9QQji/o2qMvqRNYRDOcBTgsJ/DeCf4iSN4gW+gNMXM7wFIP+fdXZxoNiAnHUTGjCr+TSWXdRDKg==}
+ engines: {node: '>=0.3.1'}
+
doctrine@2.1.0:
resolution: {integrity: sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==}
engines: {node: '>=0.10.0'}
@@ -883,6 +951,15 @@ packages:
resolution: {integrity: sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==}
engines: {node: '>= 0.4'}
+ hast-util-to-html@9.0.5:
+ resolution: {integrity: sha512-OguPdidb+fbHQSU4Q4ZiLKnzWo8Wwsf5bZfbvu7//a9oTYoqD/fWpe96NuHkoS9h0ccGOTe0C4NGXdtS0iObOw==}
+
+ hast-util-whitespace@3.0.0:
+ resolution: {integrity: sha512-88JUN06ipLwsnv+dVn+OIYOvAuvBMy/Qoi6O7mQHxdPXpjy+Cd6xRkWwux7DKO+4sYILtLBRIKgsdpS2gQc7qw==}
+
+ html-void-elements@3.0.0:
+ resolution: {integrity: sha512-bEqo66MRXsUGxWHV5IP0PUiAWwoEjba4VCzg0LjFJBpchPaTfyfCKTG6bc5F8ucKec3q5y6qOdGyYTSBEvhCrg==}
+
ignore@5.3.2:
resolution: {integrity: sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==}
engines: {node: '>= 4'}
@@ -1062,10 +1139,31 @@ packages:
resolution: {integrity: sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==}
hasBin: true
+ lru_map@0.4.1:
+ resolution: {integrity: sha512-I+lBvqMMFfqaV8CJCISjI3wbjmwVu/VyOoU7+qtu9d7ioW5klMgsTTiUOUp+DJvfTTzKXoPbyC6YfgkNcyPSOg==}
+
math-intrinsics@1.1.0:
resolution: {integrity: sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==}
engines: {node: '>= 0.4'}
+ mdast-util-to-hast@13.2.1:
+ resolution: {integrity: sha512-cctsq2wp5vTsLIcaymblUriiTcZd0CwWtCbLvrOzYCDZoWyMNV8sZ7krj09FSnsiJi3WVsHLM4k6Dq/yaPyCXA==}
+
+ micromark-util-character@2.1.1:
+ resolution: {integrity: sha512-wv8tdUTJ3thSFFFJKtpYKOYiGP2+v96Hvk4Tu8KpCAsTMs6yi+nVmGh1syvSCsaxz45J6Jbw+9DD6g97+NV67Q==}
+
+ micromark-util-encode@2.0.1:
+ resolution: {integrity: sha512-c3cVx2y4KqUnwopcO9b/SCdo2O67LwJJ/UyqGfbigahfegL9myoEFoDYZgkT7f36T0bLrM9hZTAaAyH+PCAXjw==}
+
+ micromark-util-sanitize-uri@2.0.1:
+ resolution: {integrity: sha512-9N9IomZ/YuGGZZmQec1MbgxtlgougxTodVwDzzEouPKo3qFWvymFHWcnDi2vzV1ff6kas9ucW+o3yzJK9YB1AQ==}
+
+ micromark-util-symbol@2.0.1:
+ resolution: {integrity: sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q==}
+
+ micromark-util-types@2.0.2:
+ resolution: {integrity: sha512-Yw0ECSpJoViF1qTU4DC6NwtC4aWGt1EkzaQB8KPPyCRR8z9TWeV0HbEFGTO+ZY1wB22zmxnJqhPyTpOVCpeHTA==}
+
minimatch@3.1.2:
resolution: {integrity: sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==}
@@ -1110,6 +1208,12 @@ packages:
resolution: {integrity: sha512-gXah6aZrcUxjWg2zR2MwouP2eHlCBzdV4pygudehaKXSGW4v2AsRQUK+lwwXhii6KFZcunEnmSUoYp5CXibxtA==}
engines: {node: '>= 0.4'}
+ oniguruma-parser@0.12.1:
+ resolution: {integrity: sha512-8Unqkvk1RYc6yq2WBYRj4hdnsAxVze8i7iPfQr8e4uSP3tRv0rpZcbGUDvxfQQcdwHt/e9PrMvGCsa8OqG9X3w==}
+
+ oniguruma-to-es@4.3.4:
+ resolution: {integrity: sha512-3VhUGN3w2eYxnTzHn+ikMI+fp/96KoRSVK9/kMTcFqj1NRDh2IhQCKvYxDnWePKRXY/AqH+Fuiyb7VHSzBjHfA==}
+
optionator@0.9.4:
resolution: {integrity: sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==}
engines: {node: '>= 0.8.0'}
@@ -1177,6 +1281,9 @@ packages:
prop-types@15.8.1:
resolution: {integrity: sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==}
+ property-information@7.1.0:
+ resolution: {integrity: sha512-TwEZ+X+yCJmYfL7TPUOcvBZ4QfoT5YenQiJuX//0th53DE6w0xxLEtfK3iyryQFddXuvkIk51EEgrJQ0WJkOmQ==}
+
punycode@2.3.1:
resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==}
engines: {node: '>=6'}
@@ -1200,6 +1307,15 @@ packages:
resolution: {integrity: sha512-00o4I+DVrefhv+nX0ulyi3biSHCPDe+yLv5o/p6d/UVlirijB8E16FtfwSAi4g3tcqrQ4lRAqQSoFEZJehYEcw==}
engines: {node: '>= 0.4'}
+ regex-recursion@6.0.2:
+ resolution: {integrity: sha512-0YCaSCq2VRIebiaUviZNs0cBz1kg5kVS2UKUfNIx8YVs1cN3AV7NTctO5FOKBA+UT2BPJIWZauYHPqJODG50cg==}
+
+ regex-utilities@2.3.0:
+ resolution: {integrity: sha512-8VhliFJAWRaUiVvREIiW2NXXTmHs4vMNnSzuJVhscgmGav3g9VDxLrQndI3dZZVVdp0ZO/5v0xmX516/7M9cng==}
+
+ regex@6.1.0:
+ resolution: {integrity: sha512-6VwtthbV4o/7+OaAF9I5L5V3llLEsoPyq9P1JVXkedTP33c7MfCG0/5NOPcSJn0TzXcG9YUrR0gQSWioew3LDg==}
+
regexp.prototype.flags@1.5.4:
resolution: {integrity: sha512-dYqgNSZbDwkaJ2ceRd9ojCGjBq+mOm9LmtXnAnEGyHhN/5R7iDW2TRw3h+o/jCFxus3P2LfWIIiwowAjANm7IA==}
engines: {node: '>= 0.4'}
@@ -1265,6 +1381,9 @@ packages:
resolution: {integrity: sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==}
engines: {node: '>=8'}
+ shiki@3.21.0:
+ resolution: {integrity: sha512-N65B/3bqL/TI2crrXr+4UivctrAGEjmsib5rPMMPpFp1xAx/w03v8WZ9RDDFYteXoEgY7qZ4HGgl5KBIu1153w==}
+
side-channel-list@1.0.0:
resolution: {integrity: sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==}
engines: {node: '>= 0.4'}
@@ -1281,6 +1400,9 @@ packages:
resolution: {integrity: sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==}
engines: {node: '>= 0.4'}
+ space-separated-tokens@2.0.2:
+ resolution: {integrity: sha512-PEGlAwrG8yXGXRjW32fGbg66JAlOAwbObuqVoJpv/mRgoWDQfgH1wDPvtzWyUSNAXBGSk8h755YDbbcEy3SH2Q==}
+
stop-iteration-iterator@1.1.0:
resolution: {integrity: sha512-eLoXW/DHyl62zxY4SCaIgnRhuMr6ri4juEYARS8E6sCEqzKpOiE521Ucofdx+KnDZl5xmvGYaaKCk5FEOxJCoQ==}
engines: {node: '>= 0.4'}
@@ -1307,6 +1429,9 @@ packages:
string_decoder@1.1.1:
resolution: {integrity: sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==}
+ stringify-entities@4.0.4:
+ resolution: {integrity: sha512-IwfBptatlO+QCJUo19AqvrPNqlVMpW9YEL2LIVY+Rpv2qsjCGxaDLNRgeGsQWJhfItebuJhsGSLjaBbNSQ+ieg==}
+
strip-json-comments@3.1.1:
resolution: {integrity: sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==}
engines: {node: '>=8'}
@@ -1323,6 +1448,9 @@ packages:
resolution: {integrity: sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==}
engines: {node: '>=12.0.0'}
+ trim-lines@3.0.1:
+ resolution: {integrity: sha512-kRj8B+YHZCc9kQYdWfJB2/oUl9rA99qbowYYBtr4ui4mZyAQ2JpvVBd/6U2YloATfqBhBTSMhTpgBHtU0Mf3Rg==}
+
ts-api-utils@2.4.0:
resolution: {integrity: sha512-3TaVTaAv2gTiMB35i3FiGJaRfwb3Pyn/j3m/bfAvGe8FB7CF6u+LMYqYlDh7reQf7UNvoTvdfAqHGmPGOSsPmA==}
engines: {node: '>=18.12'}
@@ -1373,12 +1501,33 @@ packages:
undici-types@6.21.0:
resolution: {integrity: sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==}
+ unist-util-is@6.0.1:
+ resolution: {integrity: sha512-LsiILbtBETkDz8I9p1dQ0uyRUWuaQzd/cuEeS1hoRSyW5E5XGmTzlwY1OrNzzakGowI9Dr/I8HVaw4hTtnxy8g==}
+
+ unist-util-position@5.0.0:
+ resolution: {integrity: sha512-fucsC7HjXvkB5R3kTCO7kUjRdrS0BJt3M/FPxmHMBOm8JQi2BsHAHFsy27E0EolP8rp0NzXsJ+jNPyDWvOJZPA==}
+
+ unist-util-stringify-position@4.0.0:
+ resolution: {integrity: sha512-0ASV06AAoKCDkS2+xw5RXJywruurpbC4JZSm7nr7MOt1ojAzvyyaO+UxZf18j8FCF6kmzCZKcAgN/yu2gm2XgQ==}
+
+ unist-util-visit-parents@6.0.2:
+ resolution: {integrity: sha512-goh1s1TBrqSqukSc8wrjwWhL0hiJxgA8m4kFxGlQ+8FYQ3C/m11FcTs4YYem7V664AhHVvgoQLk890Ssdsr2IQ==}
+
+ unist-util-visit@5.1.0:
+ resolution: {integrity: sha512-m+vIdyeCOpdr/QeQCu2EzxX/ohgS8KbnPDgFni4dQsfSCtpz8UqDyY5GjRru8PDKuYn7Fq19j1CQ+nJSsGKOzg==}
+
uri-js@4.4.1:
resolution: {integrity: sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==}
util-deprecate@1.0.2:
resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==}
+ vfile-message@4.0.3:
+ resolution: {integrity: sha512-QTHzsGd1EhbZs4AsQ20JX1rC3cOlt/IWJruk893DfLRr57lcnOeMaWG4K0JrRta4mIJZKth2Au3mM3u03/JWKw==}
+
+ vfile@6.0.3:
+ resolution: {integrity: sha512-KzIbH/9tXat2u30jf+smMwFCsno4wHVdNmzFyL+T/L3UGqqk6JKfVqOFOZEpZSHADH1k40ab6NUIXZq422ov3Q==}
+
which-boxed-primitive@1.1.1:
resolution: {integrity: sha512-TbX3mj8n0odCBFVlY8AxkqcHASw3L60jIuF8jFP78az3C2YhmGvqbHBpAjTRH2/xqYunrJ9g1jSyjCjpoWzIAA==}
engines: {node: '>= 0.4'}
@@ -1408,6 +1557,9 @@ packages:
resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==}
engines: {node: '>=10'}
+ zwitch@2.0.4:
+ resolution: {integrity: sha512-bXE4cR/kVZhKZX/RjPEflHaKVhUVl85noU3v6b8apfQEc1x4A+zBxjZ4lN8LqGd6WZ3dl98pY4o717VFmoPp+A==}
+
snapshots:
'@esbuild/aix-ppc64@0.19.12':
@@ -1614,14 +1766,72 @@ snapshots:
'@humanwhocodes/retry@0.4.3': {}
+ '@pierre/diffs@1.0.9(react-dom@18.3.1(react@18.3.1))(react@18.3.1)':
+ dependencies:
+ '@shikijs/core': 3.21.0
+ '@shikijs/engine-javascript': 3.21.0
+ '@shikijs/transformers': 3.21.0
+ diff: 8.0.2
+ hast-util-to-html: 9.0.5
+ lru_map: 0.4.1
+ react: 18.3.1
+ react-dom: 18.3.1(react@18.3.1)
+ shiki: 3.21.0
+
'@playwright/test@1.57.0':
dependencies:
playwright: 1.57.0
+ '@shikijs/core@3.21.0':
+ dependencies:
+ '@shikijs/types': 3.21.0
+ '@shikijs/vscode-textmate': 10.0.2
+ '@types/hast': 3.0.4
+ hast-util-to-html: 9.0.5
+
+ '@shikijs/engine-javascript@3.21.0':
+ dependencies:
+ '@shikijs/types': 3.21.0
+ '@shikijs/vscode-textmate': 10.0.2
+ oniguruma-to-es: 4.3.4
+
+ '@shikijs/engine-oniguruma@3.21.0':
+ dependencies:
+ '@shikijs/types': 3.21.0
+ '@shikijs/vscode-textmate': 10.0.2
+
+ '@shikijs/langs@3.21.0':
+ dependencies:
+ '@shikijs/types': 3.21.0
+
+ '@shikijs/themes@3.21.0':
+ dependencies:
+ '@shikijs/types': 3.21.0
+
+ '@shikijs/transformers@3.21.0':
+ dependencies:
+ '@shikijs/core': 3.21.0
+ '@shikijs/types': 3.21.0
+
+ '@shikijs/types@3.21.0':
+ dependencies:
+ '@shikijs/vscode-textmate': 10.0.2
+ '@types/hast': 3.0.4
+
+ '@shikijs/vscode-textmate@10.0.2': {}
+
'@types/estree@1.0.8': {}
+ '@types/hast@3.0.4':
+ dependencies:
+ '@types/unist': 3.0.3
+
'@types/json-schema@7.0.15': {}
+ '@types/mdast@4.0.4':
+ dependencies:
+ '@types/unist': 3.0.3
+
'@types/node@22.19.5':
dependencies:
undici-types: 6.21.0
@@ -1637,6 +1847,8 @@ snapshots:
'@types/prop-types': 15.7.15
csstype: 3.2.3
+ '@types/unist@3.0.3': {}
+
'@typescript-eslint/eslint-plugin@8.52.0(@typescript-eslint/parser@8.52.0(eslint@9.39.2)(typescript@5.9.3))(eslint@9.39.2)(typescript@5.9.3)':
dependencies:
'@eslint-community/regexpp': 4.12.2
@@ -1728,6 +1940,8 @@ snapshots:
'@typescript-eslint/types': 8.52.0
eslint-visitor-keys: 4.2.1
+ '@ungap/structured-clone@1.3.0': {}
+
'@xterm/addon-fit@0.11.0': {}
'@xterm/addon-web-links@0.12.0': {}
@@ -1846,17 +2060,25 @@ snapshots:
callsites@3.1.0: {}
+ ccount@2.0.1: {}
+
chalk@4.1.2:
dependencies:
ansi-styles: 4.3.0
supports-color: 7.2.0
+ character-entities-html4@2.1.0: {}
+
+ character-entities-legacy@3.0.0: {}
+
color-convert@2.0.1:
dependencies:
color-name: 1.1.4
color-name@1.1.4: {}
+ comma-separated-tokens@2.0.3: {}
+
concat-map@0.0.1: {}
core-util-is@1.0.3: {}
@@ -1905,6 +2127,14 @@ snapshots:
has-property-descriptors: 1.0.2
object-keys: 1.1.1
+ dequal@2.0.3: {}
+
+ devlop@1.1.0:
+ dependencies:
+ dequal: 2.0.3
+
+ diff@8.0.2: {}
+
doctrine@2.1.0:
dependencies:
esutils: 2.0.3
@@ -2279,6 +2509,26 @@ snapshots:
dependencies:
function-bind: 1.1.2
+ hast-util-to-html@9.0.5:
+ dependencies:
+ '@types/hast': 3.0.4
+ '@types/unist': 3.0.3
+ ccount: 2.0.1
+ comma-separated-tokens: 2.0.3
+ hast-util-whitespace: 3.0.0
+ html-void-elements: 3.0.0
+ mdast-util-to-hast: 13.2.1
+ property-information: 7.1.0
+ space-separated-tokens: 2.0.2
+ stringify-entities: 4.0.4
+ zwitch: 2.0.4
+
+ hast-util-whitespace@3.0.0:
+ dependencies:
+ '@types/hast': 3.0.4
+
+ html-void-elements@3.0.0: {}
+
ignore@5.3.2: {}
ignore@7.0.5: {}
@@ -2470,8 +2720,39 @@ snapshots:
dependencies:
js-tokens: 4.0.0
+ lru_map@0.4.1: {}
+
math-intrinsics@1.1.0: {}
+ mdast-util-to-hast@13.2.1:
+ dependencies:
+ '@types/hast': 3.0.4
+ '@types/mdast': 4.0.4
+ '@ungap/structured-clone': 1.3.0
+ devlop: 1.1.0
+ micromark-util-sanitize-uri: 2.0.1
+ trim-lines: 3.0.1
+ unist-util-position: 5.0.0
+ unist-util-visit: 5.1.0
+ vfile: 6.0.3
+
+ micromark-util-character@2.1.1:
+ dependencies:
+ micromark-util-symbol: 2.0.1
+ micromark-util-types: 2.0.2
+
+ micromark-util-encode@2.0.1: {}
+
+ micromark-util-sanitize-uri@2.0.1:
+ dependencies:
+ micromark-util-character: 2.1.1
+ micromark-util-encode: 2.0.1
+ micromark-util-symbol: 2.0.1
+
+ micromark-util-symbol@2.0.1: {}
+
+ micromark-util-types@2.0.2: {}
+
minimatch@3.1.2:
dependencies:
brace-expansion: 1.1.12
@@ -2522,6 +2803,14 @@ snapshots:
define-properties: 1.2.1
es-object-atoms: 1.1.1
+ oniguruma-parser@0.12.1: {}
+
+ oniguruma-to-es@4.3.4:
+ dependencies:
+ oniguruma-parser: 0.12.1
+ regex: 6.1.0
+ regex-recursion: 6.0.2
+
optionator@0.9.4:
dependencies:
deep-is: 0.1.4
@@ -2581,6 +2870,8 @@ snapshots:
object-assign: 4.1.1
react-is: 16.13.1
+ property-information@7.1.0: {}
+
punycode@2.3.1: {}
react-dom@18.3.1(react@18.3.1):
@@ -2616,6 +2907,16 @@ snapshots:
get-proto: 1.0.1
which-builtin-type: 1.2.1
+ regex-recursion@6.0.2:
+ dependencies:
+ regex-utilities: 2.3.0
+
+ regex-utilities@2.3.0: {}
+
+ regex@6.1.0:
+ dependencies:
+ regex-utilities: 2.3.0
+
regexp.prototype.flags@1.5.4:
dependencies:
call-bind: 1.0.8
@@ -2694,6 +2995,17 @@ snapshots:
shebang-regex@3.0.0: {}
+ shiki@3.21.0:
+ dependencies:
+ '@shikijs/core': 3.21.0
+ '@shikijs/engine-javascript': 3.21.0
+ '@shikijs/engine-oniguruma': 3.21.0
+ '@shikijs/langs': 3.21.0
+ '@shikijs/themes': 3.21.0
+ '@shikijs/types': 3.21.0
+ '@shikijs/vscode-textmate': 10.0.2
+ '@types/hast': 3.0.4
+
side-channel-list@1.0.0:
dependencies:
es-errors: 1.3.0
@@ -2722,6 +3034,8 @@ snapshots:
side-channel-map: 1.0.1
side-channel-weakmap: 1.0.2
+ space-separated-tokens@2.0.2: {}
+
stop-iteration-iterator@1.1.0:
dependencies:
es-errors: 1.3.0
@@ -2775,6 +3089,11 @@ snapshots:
dependencies:
safe-buffer: 5.1.2
+ stringify-entities@4.0.4:
+ dependencies:
+ character-entities-html4: 2.1.0
+ character-entities-legacy: 3.0.0
+
strip-json-comments@3.1.1: {}
supports-color@7.2.0:
@@ -2788,6 +3107,8 @@ snapshots:
fdir: 6.5.0(picomatch@4.0.3)
picomatch: 4.0.3
+ trim-lines@3.0.1: {}
+
ts-api-utils@2.4.0(typescript@5.9.3):
dependencies:
typescript: 5.9.3
@@ -2858,12 +3179,45 @@ snapshots:
undici-types@6.21.0: {}
+ unist-util-is@6.0.1:
+ dependencies:
+ '@types/unist': 3.0.3
+
+ unist-util-position@5.0.0:
+ dependencies:
+ '@types/unist': 3.0.3
+
+ unist-util-stringify-position@4.0.0:
+ dependencies:
+ '@types/unist': 3.0.3
+
+ unist-util-visit-parents@6.0.2:
+ dependencies:
+ '@types/unist': 3.0.3
+ unist-util-is: 6.0.1
+
+ unist-util-visit@5.1.0:
+ dependencies:
+ '@types/unist': 3.0.3
+ unist-util-is: 6.0.1
+ unist-util-visit-parents: 6.0.2
+
uri-js@4.4.1:
dependencies:
punycode: 2.3.1
util-deprecate@1.0.2: {}
+ vfile-message@4.0.3:
+ dependencies:
+ '@types/unist': 3.0.3
+ unist-util-stringify-position: 4.0.0
+
+ vfile@6.0.3:
+ dependencies:
+ '@types/unist': 3.0.3
+ vfile-message: 4.0.3
+
which-boxed-primitive@1.1.1:
dependencies:
is-bigint: 1.1.0
@@ -2912,3 +3266,5 @@ snapshots:
word-wrap@1.2.5: {}
yocto-queue@0.1.0: {}
+
+ zwitch@2.0.4: {}
@@ -1,21 +1,12 @@
-import React, { useState, useEffect, useRef, useCallback } from "react";
-import type * as Monaco from "monaco-editor";
+import React, { useState, useEffect, useCallback } from "react";
+import { MultiFileDiff } from "@pierre/diffs/react";
+import type { FileContents, SupportedLanguages, ThemeTypes, ThemesType } from "@pierre/diffs";
import { LLMContent } from "../types";
import { isDarkModeActive } from "../services/theme";
-// LocalStorage keys for preferences
-const STORAGE_KEY_MONACO_ENABLED = "shelley-use-monaco-diff";
+// LocalStorage key for side-by-side preference
const STORAGE_KEY_SIDE_BY_SIDE = "shelley-diff-side-by-side";
-// Feature flag for Monaco diff view
-function useMonacoDiff(): boolean {
- try {
- return localStorage.getItem(STORAGE_KEY_MONACO_ENABLED) === "true";
- } catch {
- return false;
- }
-}
-
// Get saved side-by-side preference (default: true for desktop)
function getSideBySidePreference(): boolean {
try {
@@ -59,485 +50,121 @@ interface PatchToolProps {
onCommentTextChange?: (text: string) => void;
}
-// Global Monaco instance - loaded lazily
-let monacoInstance: typeof Monaco | null = null;
-let monacoLoadPromise: Promise<typeof Monaco> | null = null;
-
-function loadMonaco(): Promise<typeof Monaco> {
- if (monacoInstance) {
- return Promise.resolve(monacoInstance);
- }
- if (monacoLoadPromise) {
- return monacoLoadPromise;
- }
-
- monacoLoadPromise = (async () => {
- // Configure Monaco environment for web workers before importing
- const monacoEnv: Monaco.Environment = {
- getWorkerUrl: () => "/editor.worker.js",
- };
- (self as Window).MonacoEnvironment = monacoEnv;
-
- // Load Monaco CSS if not already loaded
- if (!document.querySelector('link[href="/monaco-editor.css"]')) {
- const link = document.createElement("link");
- link.rel = "stylesheet";
- link.href = "/monaco-editor.css";
- document.head.appendChild(link);
- }
-
- // Load Monaco from our local bundle (runtime URL, cast to proper types)
- // eslint-disable-next-line @typescript-eslint/ban-ts-comment
- // @ts-ignore - dynamic runtime URL import
- const monaco = (await import("/monaco-editor.js")) as typeof Monaco;
- monacoInstance = monaco;
- return monacoInstance;
- })();
-
- return monacoLoadPromise;
-}
-
-// Simple diff view component (default)
-function SimpleDiffView({ displayData }: { displayData: PatchDisplayData | null }) {
- // Get diff text from displayData or fall back to empty
- const diff = displayData?.diff || "";
-
- // Parse unified diff to extract lines
- const lines = diff ? diff.split("\n") : [];
-
- return (
- <pre className="patch-tool-diff">
- {lines.map((line, idx) => {
- // Determine line type for styling
- let className = "patch-diff-line";
- if (line.startsWith("+") && !line.startsWith("+++")) {
- className += " patch-diff-addition";
- } else if (line.startsWith("-") && !line.startsWith("---")) {
- className += " patch-diff-deletion";
- } else if (line.startsWith("@@")) {
- className += " patch-diff-hunk";
- } else if (line.startsWith("---") || line.startsWith("+++")) {
- className += " patch-diff-header";
- }
-
- return (
- <div key={idx} className={className}>
- {line || " "}
- </div>
- );
- })}
- </pre>
- );
+// Map file extension to language for syntax highlighting
+function getLanguageFromPath(path: string): SupportedLanguages {
+ const ext = path.split(".").pop()?.toLowerCase() || "";
+ const langMap: Record<string, SupportedLanguages> = {
+ ts: "typescript",
+ tsx: "tsx",
+ js: "javascript",
+ jsx: "jsx",
+ py: "python",
+ rb: "ruby",
+ go: "go",
+ rs: "rust",
+ java: "java",
+ c: "c",
+ cpp: "cpp",
+ h: "c",
+ hpp: "cpp",
+ cs: "csharp",
+ php: "php",
+ swift: "swift",
+ kt: "kotlin",
+ scala: "scala",
+ sh: "bash",
+ bash: "bash",
+ zsh: "bash",
+ fish: "fish",
+ ps1: "powershell",
+ sql: "sql",
+ html: "html",
+ htm: "html",
+ css: "css",
+ scss: "scss",
+ sass: "sass",
+ less: "less",
+ json: "json",
+ xml: "xml",
+ yaml: "yaml",
+ yml: "yaml",
+ toml: "toml",
+ ini: "ini",
+ md: "markdown",
+ markdown: "markdown",
+ txt: "text",
+ dockerfile: "dockerfile",
+ makefile: "makefile",
+ cmake: "cmake",
+ lua: "lua",
+ perl: "perl",
+ r: "r",
+ vue: "vue",
+ svelte: "svelte",
+ astro: "astro",
+ };
+ return langMap[ext] || "text";
}
-// Monaco diff view component (feature-flagged)
-function MonacoDiffView({
+// Diff view component using @pierre/diffs
+function DiffView({
displayData,
- isMobile,
sideBySide,
- onCommentTextChange,
- filename,
}: {
displayData: PatchDisplayData;
- isMobile: boolean;
sideBySide: boolean;
- onCommentTextChange?: (text: string) => void;
- filename: string;
}) {
- const [monacoLoaded, setMonacoLoaded] = useState(false);
- const [isVisible, setIsVisible] = useState(false);
- const [editorHeight, setEditorHeight] = useState<number>(100);
- const [showCommentDialog, setShowCommentDialog] = useState<{
- line: number;
- selectedText?: string;
- } | null>(null);
- const [commentText, setCommentText] = useState("");
-
- const containerRef = useRef<HTMLDivElement>(null);
- const editorContainerRef = useRef<HTMLDivElement>(null);
- const editorRef = useRef<Monaco.editor.IStandaloneDiffEditor | null>(null);
- const monacoRef = useRef<typeof Monaco | null>(null);
- const commentInputRef = useRef<HTMLTextAreaElement>(null);
- const hoverDecorationsRef = useRef<string[]>([]);
- const heightSetRef = useRef(false);
- const modelsRef = useRef<{
- original: Monaco.editor.ITextModel | null;
- modified: Monaco.editor.ITextModel | null;
- }>({
- original: null,
- modified: null,
- });
-
- // Intersection observer for lazy loading
- useEffect(() => {
- const container = containerRef.current;
- if (!container) return;
-
- const observer = new IntersectionObserver(
- (entries) => {
- for (const entry of entries) {
- if (entry.isIntersecting) {
- setIsVisible(true);
- // Once visible, we don't need to observe anymore
- observer.disconnect();
- }
- }
- },
- {
- rootMargin: "100px", // Start loading a bit before it's visible
- threshold: 0,
- },
- );
-
- observer.observe(container);
-
- return () => observer.disconnect();
- }, []);
-
- // Load Monaco only when visible
- useEffect(() => {
- if (!isVisible || monacoLoaded) return;
-
- loadMonaco()
- .then((monaco) => {
- monacoRef.current = monaco;
- setMonacoLoaded(true);
- })
- .catch((err) => {
- console.error("Failed to load Monaco:", err);
- });
- }, [isVisible, monacoLoaded]);
-
- // Update side-by-side mode when prop changes
- useEffect(() => {
- if (editorRef.current) {
- editorRef.current.updateOptions({ renderSideBySide: sideBySide });
- // Reset height flag to allow recalculation after mode change
- heightSetRef.current = false;
- }
- }, [sideBySide]);
-
- // Create Monaco editor when data is ready and visible
- useEffect(() => {
- if (!monacoLoaded || !isVisible || !editorContainerRef.current || !monacoRef.current) {
- return;
- }
-
- const monaco = monacoRef.current;
-
- // Dispose previous editor and models
- if (editorRef.current) {
- editorRef.current.dispose();
- editorRef.current = null;
- }
- if (modelsRef.current.original) {
- modelsRef.current.original.dispose();
- modelsRef.current.original = null;
- }
- if (modelsRef.current.modified) {
- modelsRef.current.modified.dispose();
- modelsRef.current.modified = null;
- }
-
- // Reset height tracking for new editor
- heightSetRef.current = false;
-
- // Get language from file extension
- const ext = "." + (displayData.path.split(".").pop()?.toLowerCase() || "");
- const languages = monaco.languages.getLanguages();
- let language = "plaintext";
- for (const lang of languages) {
- if (lang.extensions?.includes(ext)) {
- language = lang.id;
- break;
- }
- }
+ const [themeType, setThemeType] = useState<ThemeTypes>(isDarkModeActive() ? "dark" : "light");
- // Create models with unique URIs (include timestamp to avoid conflicts)
- const timestamp = Date.now();
- const originalUri = monaco.Uri.file(`patch-original-${timestamp}-${displayData.path}`);
- const modifiedUri = monaco.Uri.file(`patch-modified-${timestamp}-${displayData.path}`);
-
- // Check for and dispose any existing models with these URIs (defensive, shouldn't happen)
- const existingOriginal = monaco.editor.getModel(originalUri);
- if (existingOriginal) existingOriginal.dispose();
- const existingModified = monaco.editor.getModel(modifiedUri);
- if (existingModified) existingModified.dispose();
-
- const originalModel = monaco.editor.createModel(displayData.oldContent, language, originalUri);
- const modifiedModel = monaco.editor.createModel(displayData.newContent, language, modifiedUri);
- modelsRef.current = { original: originalModel, modified: modifiedModel };
-
- // Create diff editor with collapsed unchanged regions
- const diffEditor = monaco.editor.createDiffEditor(editorContainerRef.current, {
- theme: isDarkModeActive() ? "vs-dark" : "vs",
- readOnly: true,
- originalEditable: false,
- automaticLayout: true,
- renderSideBySide: sideBySide,
- enableSplitViewResizing: true,
- renderIndicators: true,
- renderMarginRevertIcon: false,
- lineNumbers: isMobile ? "off" : "on",
- minimap: { enabled: false },
- scrollBeyondLastLine: false,
- wordWrap: "on",
- glyphMargin: !isMobile, // Enable glyph margin for comment indicator
- lineDecorationsWidth: isMobile ? 0 : 10,
- lineNumbersMinChars: isMobile ? 0 : 3,
- quickSuggestions: false,
- suggestOnTriggerCharacters: false,
- lightbulb: { enabled: false },
- codeLens: false,
- contextmenu: false,
- links: false,
- folding: !isMobile,
- // Hide unchanged regions to show only edited sections
- hideUnchangedRegions: {
- enabled: true,
- revealLineCount: 2, // Show 2 lines of context around changes
- minimumLineCount: 3, // Hide regions with 3+ unchanged lines
- contextLineCount: 2, // Context lines to show when expanding
- },
- // Disable scrollbar when content fits
- scrollbar: {
- vertical: "auto",
- horizontal: "auto",
- alwaysConsumeMouseWheel: false,
- },
- });
-
- diffEditor.setModel({
- original: originalModel,
- modified: modifiedModel,
- });
-
- editorRef.current = diffEditor;
-
- // Function to update height - only do this once to avoid scroll disruption
- const updateHeight = () => {
- if (heightSetRef.current) return;
-
- const modifiedEditor = diffEditor.getModifiedEditor();
- const contentHeight = modifiedEditor.getContentHeight();
-
- if (contentHeight > 0) {
- // Add small buffer, no max height - let it expand fully
- const newHeight = Math.max(60, contentHeight + 4);
- heightSetRef.current = true;
- setEditorHeight(newHeight);
- }
- };
-
- // Update height after diff is computed
- // Monaco needs time to compute the diff and layout
- const heightUpdateTimer = setTimeout(updateHeight, 200);
-
- // Also listen for content size change (fires when diff is computed)
- const modifiedEditor = diffEditor.getModifiedEditor();
- const contentSizeDisposable = modifiedEditor.onDidContentSizeChange(() => {
- updateHeight();
- });
-
- // Add click handler for commenting if callback is provided
- if (onCommentTextChange) {
- const openCommentDialog = (lineNumber: number) => {
- const model = modifiedEditor.getModel();
- const selection = modifiedEditor.getSelection();
- let selectedText = "";
-
- if (selection && !selection.isEmpty() && model) {
- selectedText = model.getValueInRange(selection);
- } else if (model) {
- selectedText = model.getLineContent(lineNumber) || "";
- }
-
- setShowCommentDialog({
- line: lineNumber,
- selectedText,
- });
- };
-
- modifiedEditor.onMouseDown((e: Monaco.editor.IEditorMouseEvent) => {
- const isLineClick =
- e.target.type === monaco.editor.MouseTargetType.CONTENT_TEXT ||
- e.target.type === monaco.editor.MouseTargetType.CONTENT_EMPTY;
-
- if (isLineClick) {
- const position = e.target.position;
- if (position) {
- openCommentDialog(position.lineNumber);
- }
- }
- });
-
- // Add hover highlighting with comment indicator
- let lastHoveredLine = -1;
- modifiedEditor.onMouseMove((e: Monaco.editor.IEditorMouseEvent) => {
- const position = e.target.position;
- const lineNumber = position?.lineNumber ?? -1;
-
- if (lineNumber === lastHoveredLine) return;
- lastHoveredLine = lineNumber;
-
- if (lineNumber > 0) {
- hoverDecorationsRef.current = modifiedEditor.deltaDecorations(
- hoverDecorationsRef.current,
- [
- {
- range: new monaco.Range(lineNumber, 1, lineNumber, 1),
- options: {
- isWholeLine: true,
- className: "patch-line-hover",
- glyphMarginClassName: "patch-comment-glyph",
- },
- },
- ],
- );
- } else {
- // Clear decorations when not hovering a line
- hoverDecorationsRef.current = modifiedEditor.deltaDecorations(
- hoverDecorationsRef.current,
- [],
- );
- }
- });
-
- // Clear decorations when mouse leaves editor
- modifiedEditor.onMouseLeave(() => {
- lastHoveredLine = -1;
- hoverDecorationsRef.current = modifiedEditor.deltaDecorations(
- hoverDecorationsRef.current,
- [],
- );
- });
- }
-
- // Cleanup function
- return () => {
- clearTimeout(heightUpdateTimer);
- contentSizeDisposable.dispose();
- if (editorRef.current) {
- editorRef.current.dispose();
- editorRef.current = null;
- }
- if (modelsRef.current.original) {
- modelsRef.current.original.dispose();
- modelsRef.current.original = null;
- }
- if (modelsRef.current.modified) {
- modelsRef.current.modified.dispose();
- modelsRef.current.modified = null;
- }
- };
- }, [monacoLoaded, isVisible, displayData, isMobile, onCommentTextChange, sideBySide]);
-
- // Update Monaco theme when dark mode changes
+ // Listen for theme changes
useEffect(() => {
- if (!monacoRef.current) return;
-
- const updateMonacoTheme = () => {
- const theme = isDarkModeActive() ? "vs-dark" : "vs";
- monacoRef.current?.editor.setTheme(theme);
+ const updateTheme = () => {
+ setThemeType(isDarkModeActive() ? "dark" : "light");
};
const observer = new MutationObserver((mutations) => {
for (const mutation of mutations) {
if (mutation.attributeName === "class") {
- updateMonacoTheme();
+ updateTheme();
}
}
});
observer.observe(document.documentElement, { attributes: true });
-
return () => observer.disconnect();
- }, [monacoLoaded]);
-
- // Focus comment input when dialog opens
- useEffect(() => {
- if (showCommentDialog && commentInputRef.current) {
- setTimeout(() => {
- commentInputRef.current?.focus();
- }, 50);
- }
- }, [showCommentDialog]);
+ }, []);
- // Handle adding a comment
- const handleAddComment = useCallback(() => {
- if (!showCommentDialog || !commentText.trim() || !onCommentTextChange) return;
+ const lang = getLanguageFromPath(displayData.path);
- const line = showCommentDialog.line;
- const codeSnippet = showCommentDialog.selectedText?.split("\n")[0]?.trim() || "";
- const truncatedCode =
- codeSnippet.length > 60 ? codeSnippet.substring(0, 57) + "..." : codeSnippet;
+ const oldFile: FileContents = {
+ name: displayData.path,
+ contents: displayData.oldContent,
+ lang,
+ };
- const commentBlock = `> ${filename}:${line}: ${truncatedCode}\n${commentText}\n\n`;
+ const newFile: FileContents = {
+ name: displayData.path,
+ contents: displayData.newContent,
+ lang,
+ };
- onCommentTextChange(commentBlock);
- setShowCommentDialog(null);
- setCommentText("");
- }, [showCommentDialog, commentText, onCommentTextChange, filename]);
+ const theme: ThemesType = {
+ dark: "github-dark",
+ light: "github-light",
+ };
return (
- <div ref={containerRef} className="patch-tool-monaco-container">
- {/* Monaco editor container */}
- {!isVisible ? (
- <div className="patch-tool-monaco-placeholder" style={{ height: "100px" }}>
- <span>Scroll to load diff...</span>
- </div>
- ) : !monacoLoaded ? (
- <div className="patch-tool-monaco-placeholder" style={{ height: "100px" }}>
- <div className="spinner-small" />
- <span>Loading editor...</span>
- </div>
- ) : (
- <div
- ref={editorContainerRef}
- className="patch-tool-monaco-editor"
- style={{ height: `${editorHeight}px`, width: "100%" }}
- />
- )}
-
- {/* Comment dialog */}
- {showCommentDialog && onCommentTextChange && (
- <div className="patch-tool-comment-dialog">
- <h4>Add Comment (Line {showCommentDialog.line})</h4>
- {showCommentDialog.selectedText && (
- <pre className="patch-tool-selected-text">{showCommentDialog.selectedText}</pre>
- )}
- <textarea
- ref={commentInputRef}
- value={commentText}
- onChange={(e) => setCommentText(e.target.value)}
- placeholder="Enter your comment..."
- className="patch-tool-comment-input"
- autoFocus
- onKeyDown={(e) => {
- if (e.key === "Escape") {
- setShowCommentDialog(null);
- } else if (e.key === "Enter" && (e.ctrlKey || e.metaKey)) {
- handleAddComment();
- }
- }}
- />
- <div className="patch-tool-comment-actions">
- <button
- onClick={() => setShowCommentDialog(null)}
- className="patch-tool-btn patch-tool-btn-secondary"
- >
- Cancel
- </button>
- <button
- onClick={handleAddComment}
- className="patch-tool-btn patch-tool-btn-primary"
- disabled={!commentText.trim()}
- >
- Add Comment
- </button>
- </div>
- </div>
- )}
+ <div className="patch-tool-diffs-container">
+ <MultiFileDiff
+ oldFile={oldFile}
+ newFile={newFile}
+ options={{
+ diffStyle: sideBySide ? "split" : "unified",
+ theme,
+ themeType,
+ disableFileHeader: true,
+ }}
+ />
</div>
);
}
@@ -606,26 +233,21 @@ function DiffModeToggle({ sideBySide, onToggle }: { sideBySide: boolean; onToggl
);
}
-function PatchTool({
- toolInput,
- isRunning,
- toolResult,
- hasError,
- display,
- onCommentTextChange,
-}: PatchToolProps) {
+function PatchTool({ toolInput, isRunning, toolResult, hasError, display }: PatchToolProps) {
// Default to collapsed for errors (since agents typically recover), expanded otherwise
const [isExpanded, setIsExpanded] = useState(!hasError);
const [isMobile, setIsMobile] = useState(window.innerWidth < 768);
const [sideBySide, setSideBySide] = useState(() => !isMobile && getSideBySidePreference());
- // Check feature flag for Monaco diff view
- const useMonaco = useMonacoDiff();
-
// Track viewport size
useEffect(() => {
const handleResize = () => {
- setIsMobile(window.innerWidth < 768);
+ const mobile = window.innerWidth < 768;
+ setIsMobile(mobile);
+ // Force unified view on mobile
+ if (mobile) {
+ setSideBySide(false);
+ }
};
window.addEventListener("resize", handleResize);
return () => window.removeEventListener("resize", handleResize);
@@ -668,9 +290,8 @@ function PatchTool({
// Extract filename from path or diff headers
const filename = displayData?.path || path || "patch";
- // Show toggle only for Monaco view on desktop when expanded and complete
- const showDiffToggle =
- useMonaco && !isMobile && isExpanded && isComplete && !hasError && displayData;
+ // Show toggle only on desktop when expanded and complete with diff data
+ const showDiffToggle = !isMobile && isExpanded && isComplete && !hasError && displayData;
return (
<div
@@ -718,17 +339,7 @@ function PatchTool({
<div className="patch-tool-details">
{isComplete && !hasError && displayData && (
<div className="patch-tool-section">
- {useMonaco ? (
- <MonacoDiffView
- displayData={displayData}
- isMobile={isMobile}
- sideBySide={sideBySide}
- onCommentTextChange={onCommentTextChange}
- filename={filename}
- />
- ) : (
- <SimpleDiffView displayData={displayData} />
- )}
+ <DiffView displayData={displayData} sideBySide={sideBySide} />
</div>
)}