diff --git a/package.json b/package.json index 2a06680..b97291e 100644 --- a/package.json +++ b/package.json @@ -54,7 +54,6 @@ "react": "^18.3.1", "react-dom": "^18.3.1", "react-resizable-panels": "^2.1.7", - "react-router-dom": "^7.1.3", "react-syntax-highlighter": "^15.6.1", "sonner": "^1.7.2", "tailwind-merge": "^2.6.0", @@ -63,7 +62,6 @@ }, "devDependencies": { "@eslint/js": "^10.0.1", - "@shadcn/ui": "^0.0.4", "@tauri-apps/cli": "^2.2.5", "@testing-library/jest-dom": "^6.6.3", "@testing-library/react": "^16.2.0", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 3f40ad4..c3a4ead 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -107,9 +107,6 @@ importers: react-resizable-panels: specifier: ^2.1.7 version: 2.1.7(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - react-router-dom: - specifier: ^7.1.3 - version: 7.1.3(react-dom@18.3.1(react@18.3.1))(react@18.3.1) react-syntax-highlighter: specifier: ^15.6.1 version: 15.6.1(react@18.3.1) @@ -129,9 +126,6 @@ importers: '@eslint/js': specifier: ^10.0.1 version: 10.0.1(eslint@10.8.0(jiti@1.21.7)) - '@shadcn/ui': - specifier: ^0.0.4 - version: 0.0.4 '@tauri-apps/cli': specifier: ^2.2.5 version: 2.2.5 @@ -1456,10 +1450,6 @@ packages: cpu: [x64] os: [win32] - '@shadcn/ui@0.0.4': - resolution: {integrity: sha512-0dtu/5ApsOZ24qgaZwtif8jVwqol7a4m1x5AxPuM1k5wxhqU7t/qEfBGtaSki1R8VlbTQfCj5PAlO45NKCa7Gg==} - hasBin: true - '@shikijs/core@2.5.0': resolution: {integrity: sha512-uu/8RExTKtavlpH7XqnVYBrfBkUc20ngXiX9NSrBhOVZYv/7XQRKUyhtkeflY5QsxC0GbJThCerruZfsUaSldg==} @@ -1614,9 +1604,6 @@ packages: '@types/babel__traverse@7.20.6': resolution: {integrity: sha512-r1bzfrm0tomOI8g1SzvCaQHo6Lcv6zu0EA+W2kHrt8dyrHQxGzBBL4kdkzIS+jBMV+EYcMAEAqXqYaLJq5rOZg==} - '@types/cookie@0.6.0': - resolution: {integrity: sha512-4Kh9a6B2bQciAhf7FSuMRRkUWecJgJu9nPnx3yzpsfXX/c50REIqpHY4C82bXP90qrLtXtkDxTZosYO3UpOwlA==} - '@types/esrecurse@4.3.1': resolution: {integrity: sha512-xJBAbDifo5hpffDBuHl0Y8ywswbiAp/Wi7Y/GtAgSlZyIABppyurxVueOPE8LUQOxdlgi6Zqce7uoEpqNTeiUw==} @@ -1963,9 +1950,6 @@ packages: resolution: {integrity: sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==} engines: {node: 18 || 20 || >=22} - base64-js@1.5.1: - resolution: {integrity: sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==} - binary-extensions@2.3.0: resolution: {integrity: sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==} engines: {node: '>=8'} @@ -1973,9 +1957,6 @@ packages: birpc@2.9.0: resolution: {integrity: sha512-KrayHS5pBi69Xi9JmvoqrIgYGDkD6mcSe/i6YKi3w5kekCLzrX4+nawcXqrj2tIp50Kw/mT/s3p+GVK0A0sKxw==} - bl@5.1.0: - resolution: {integrity: sha512-tv1ZJHLfTDnXE6tMHv73YgSJaWR2AFuPwMntBe7XL/GBFHnT0CLnsHMogfk5+GzCDC5ZWarSCYaIGATZt9dNsQ==} - brace-expansion@2.0.1: resolution: {integrity: sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==} @@ -1992,9 +1973,6 @@ packages: engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7} hasBin: true - buffer@6.0.3: - resolution: {integrity: sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==} - cac@6.7.14: resolution: {integrity: sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==} engines: {node: '>=8'} @@ -2021,10 +1999,6 @@ packages: resolution: {integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==} engines: {node: '>=10'} - chalk@5.2.0: - resolution: {integrity: sha512-ree3Gqw/nazQAPuJJEy+avdl7QfZMcUvmHIKgEZkGL+xOBzRvup5Hxo6LHuMceSxOabuJLJm5Yp/92R9eMmMvA==} - engines: {node: ^12.17.0 || ^14.13 || >=16.0.0} - character-entities-html4@2.1.0: resolution: {integrity: sha512-1v7fgQRj6hnSwFpq1Eu0ynr/CDEw0rXo2B61qXrLNdHZmPKgb7fqS1a2JwF0rISo9q77jDI8VMEHoApn8qDoZA==} @@ -2051,18 +2025,6 @@ packages: class-variance-authority@0.7.1: resolution: {integrity: sha512-Ka+9Trutv7G8M6WT6SeiRWz792K5qEqIGEGzXKhAE6xOWAY6pPH8U+9IY3oCMv6kqTmLsv7Xh/2w2RigkePMsg==} - cli-cursor@4.0.0: - resolution: {integrity: sha512-VGtlMu3x/4DOtIUwEkRezxUZ2lBacNJCHash0N0WeZDBS+7Ux1dm3XWAgWYxLJFMMdOeXMHXorshEFhbMSGelg==} - engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} - - cli-spinners@2.9.2: - resolution: {integrity: sha512-ywqV+5MmyL4E7ybXgKys4DugZbX0FC6LnwrhjuykIjnK9k8OQacQ7axGKnjDXWNhns0xot3bZI5h55H8yo9cJg==} - engines: {node: '>=6'} - - clone@1.0.4: - resolution: {integrity: sha512-JQHZ2QMW6l3aH/j6xCqQThY/9OH4D/9ls34cgkUBiEeocRTU04tHfKPBsUK1PqZCUQM7GiA0IIXJSuXHI64Kbg==} - engines: {node: '>=0.8'} - clsx@2.1.1: resolution: {integrity: sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==} engines: {node: '>=6'} @@ -2084,10 +2046,6 @@ packages: comma-separated-tokens@2.0.3: resolution: {integrity: sha512-Fu4hJdvzeylCfQPp9SGWidpzrMs7tTrlu6Vb8XGaRGck8QSNZJJp538Wrb60Lax4fPwR64ViY468OIUTbRlGZg==} - commander@10.0.1: - resolution: {integrity: sha512-y4Mg2tXshplEbSGzx7amzPwKKOCGuoSRP/CjEdwwk0FOGlUbq6lKuoyDZTNZkmxHdJtp54hdfY/JUrdL7Xfdug==} - engines: {node: '>=14'} - commander@4.1.1: resolution: {integrity: sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==} engines: {node: '>= 6'} @@ -2095,10 +2053,6 @@ packages: convert-source-map@2.0.0: resolution: {integrity: sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==} - cookie@1.0.2: - resolution: {integrity: sha512-9Kr/j4O16ISv8zBBhJoi4bXOYNTkFLOqSL3UDB0njXxCXNezjeyVrJyGOWtgfs/q2km1gwBcfH8q1yEGoMYunA==} - engines: {node: '>=18'} - copy-anything@4.0.5: resolution: {integrity: sha512-7Vv6asjS4gMOuILabD3l739tsaxFQmC+a7pLZm02zyvs8p977bL3zEgq3yDk5rn9B0PbYgIv++jmHcuUab4RhA==} engines: {node: '>=18'} @@ -2125,10 +2079,6 @@ packages: csstype@3.2.3: resolution: {integrity: sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==} - data-uri-to-buffer@4.0.1: - resolution: {integrity: sha512-0R9ikRb668HB7QDxT1vkpuUBtqc53YyAwMwGeUFKRojY/NWKvdZ+9UYtRfGmhqNbRkTSVpMbmyhXipFFv2cb/A==} - engines: {node: '>= 12'} - data-urls@5.0.0: resolution: {integrity: sha512-ZYP5VBHshaDAiVZxjbRVcFJpc+4xGgT0bK3vzy1HLN8jTO975HEbuYzZJcHoQEY5K1a0z8YayJkyVETa08eNTg==} engines: {node: '>=18'} @@ -2161,9 +2111,6 @@ packages: deep-is@0.1.4: resolution: {integrity: sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==} - defaults@1.0.4: - resolution: {integrity: sha512-eFuaLoy/Rxalv2kr+lqMlUnrDWV+3j4pljOIJgLIhI058IQfWJ7vXhyEIHu+HtC738klGALYxOKDO0bQP3tg8A==} - delayed-stream@1.0.0: resolution: {integrity: sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==} engines: {node: '>=0.4.0'} @@ -2296,10 +2243,6 @@ packages: resolution: {integrity: sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==} engines: {node: '>=0.10.0'} - execa@7.2.0: - resolution: {integrity: sha512-UduyVP7TLB5IcAQl+OzLyLcS/l32W/GLg+AhHJ+ow40FOk2U3SAllPwR44v4vmdFwIWqpdwxxpQbF1n5ta9seA==} - engines: {node: ^14.18.0 || ^16.14.0 || >=18.0.0} - expect-type@1.1.0: resolution: {integrity: sha512-bFi65yM+xZgk+u/KRIpekdSYkTB5W1pEf0Lt8Q8Msh7b+eQ7LXVtIB1Bkm4fvclDEL1b2CZkMhv2mOeF8tMdkA==} engines: {node: '>=12.0.0'} @@ -2332,10 +2275,6 @@ packages: picomatch: optional: true - fetch-blob@3.2.0: - resolution: {integrity: sha512-7yAQpD2UMJzLi1Dqv7qFYnPbaPx7ZfFK6PiIxQ4PfkGPyNyl2Ugx+a/umUonmKqjhM4DnfbMvdX6otXq83soQQ==} - engines: {node: ^12.20 || >= 14.13} - file-entry-cache@8.0.0: resolution: {integrity: sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==} engines: {node: '>=16.0.0'} @@ -2370,17 +2309,9 @@ packages: resolution: {integrity: sha512-wzsgA6WOq+09wrU1tsJ09udeR/YZRaeArL9e1wPbFg3GG2yDnC2ldKpxs4xunpFF9DgqCqOIra3bc1HWrJ37Ww==} engines: {node: '>=0.4.x'} - formdata-polyfill@4.0.10: - resolution: {integrity: sha512-buewHzMvYL29jdeQTVILecSaZKnt/RJWjoZCF5OW60Z67/GmSLBkOFM7qh1PI3zFNtJbaZL5eQu1vLfazOwj4g==} - engines: {node: '>=12.20.0'} - fraction.js@4.3.7: resolution: {integrity: sha512-ZsDfxO51wGAXREY55a7la9LScWpwv9RxIrYABrlvOFBlH/ShPnrtsXeuUIfXKKOVicNxQ+o8JTbJvjS4M89yew==} - fs-extra@11.3.0: - resolution: {integrity: sha512-Z4XaCL6dUDHfP/jT25jJKMmtxvuwbkrD1vNSMFlo9lNLY2c5FHYSQgHPRZUjAB26TpDEoW9HCOgplrdbaPV/ew==} - engines: {node: '>=14.14'} - fsevents@2.3.3: resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==} engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} @@ -2397,10 +2328,6 @@ packages: resolution: {integrity: sha512-FJhYRoDaiatfEkUK8HKlicmu/3SGFD51q3itKDGoSTysQJBnfOcxU5GxnhE1E6soB76MbT0MBtnKJuXyAx+96Q==} engines: {node: '>=6'} - get-stream@6.0.1: - resolution: {integrity: sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==} - engines: {node: '>=10'} - glob-parent@5.1.2: resolution: {integrity: sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==} engines: {node: '>= 6'} @@ -2421,9 +2348,6 @@ packages: resolution: {integrity: sha512-Czmyns5dUsq4seFBR/Kdydhmo8y9kC79hiSkPn0YcGtNnYWnrgt0vjrSjx9tspoDGWm2CMarffRuLjM4xUz8xg==} engines: {node: '>=18'} - graceful-fs@4.2.11: - resolution: {integrity: sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==} - has-flag@4.0.0: resolution: {integrity: sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==} engines: {node: '>=8'} @@ -2477,17 +2401,10 @@ packages: resolution: {integrity: sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==} engines: {node: '>= 14'} - human-signals@4.3.1: - resolution: {integrity: sha512-nZXjEF2nbo7lIw3mgYjItAfgQXog3OjJogSbKa2CQIIvSGWcKgeJnQlNXip6NglNzYH45nSRiEVimMvYL8DDqQ==} - engines: {node: '>=14.18.0'} - iconv-lite@0.6.3: resolution: {integrity: sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==} engines: {node: '>=0.10.0'} - ieee754@1.2.1: - resolution: {integrity: sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==} - ignore@5.3.2: resolution: {integrity: sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==} engines: {node: '>= 4'} @@ -2504,9 +2421,6 @@ packages: resolution: {integrity: sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==} engines: {node: '>=8'} - inherits@2.0.4: - resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==} - is-alphabetical@1.0.4: resolution: {integrity: sha512-DwzsA04LQ10FHTZuL0/grVDk4rFoVH1pjAToYwBrHSxcrBIGQuXrQMtD5U1b0U2XVgKZCTLLP8u2Qxqhy3l2Vg==} @@ -2539,10 +2453,6 @@ packages: is-hexadecimal@1.0.4: resolution: {integrity: sha512-gyPJuv83bHMpocVYoqof5VDiZveEoGoFL8m3BXNb2VW8Xs+rz9kqO8LOQ5DH6EsuvilT1ApazU0pyl+ytbPtlw==} - is-interactive@2.0.0: - resolution: {integrity: sha512-qP1vozQRI+BMOPcjFzrjXuQvdak2pHNUMZoeG2eRbiSqyvbEf/wQtEOTOX1guk6E3t36RkaqiSt8A/6YElNxLQ==} - engines: {node: '>=12'} - is-number@7.0.0: resolution: {integrity: sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==} engines: {node: '>=0.12.0'} @@ -2550,14 +2460,6 @@ packages: is-potential-custom-element-name@1.0.1: resolution: {integrity: sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ==} - is-stream@3.0.0: - resolution: {integrity: sha512-LnQR4bZ9IADDRSkvpqMGvt/tEJWclzklNgSw48V5EAaAeDd6qGvN8ei6k5p0tvxSR171VmGyHuTiAOfxAbr8kA==} - engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} - - is-unicode-supported@1.3.0: - resolution: {integrity: sha512-43r2mRvz+8JRIKnWJ+3j8JtjRKZ6GmjzfaE/qiBJnikNnYv/6bagRJ1kUhNk8R5EX/GkobD+r+sfxCPJsiKBLQ==} - engines: {node: '>=12'} - is-what@5.5.0: resolution: {integrity: sha512-oG7cgbmg5kLYae2N5IVd3jm2s+vldjxJzK1pcu9LfpGuQ93MQSzo0okvRna+7y5ifrD+20FE8FvjusyGaz14fw==} engines: {node: '>=18'} @@ -2619,16 +2521,9 @@ packages: engines: {node: '>=6'} hasBin: true - jsonfile@6.1.0: - resolution: {integrity: sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==} - keyv@4.5.4: resolution: {integrity: sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==} - kleur@3.0.3: - resolution: {integrity: sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w==} - engines: {node: '>=6'} - levn@0.4.1: resolution: {integrity: sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==} engines: {node: '>= 0.8.0'} @@ -2647,10 +2542,6 @@ packages: lodash@4.17.21: resolution: {integrity: sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==} - log-symbols@5.1.0: - resolution: {integrity: sha512-l0x2DvrW294C9uDCoQe1VSU4gf529FkSZ6leBl4TiqZH/e+0R7hSfHQBNut2mNygDgHwvYHfFLn6Oxb3VWj2rA==} - engines: {node: '>=12'} - loose-envify@1.4.0: resolution: {integrity: sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==} hasBin: true @@ -2700,9 +2591,6 @@ packages: mdast-util-to-hast@13.2.1: resolution: {integrity: sha512-cctsq2wp5vTsLIcaymblUriiTcZd0CwWtCbLvrOzYCDZoWyMNV8sZ7krj09FSnsiJi3WVsHLM4k6Dq/yaPyCXA==} - merge-stream@2.0.0: - resolution: {integrity: sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==} - merge2@1.4.1: resolution: {integrity: sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==} engines: {node: '>= 8'} @@ -2734,14 +2622,6 @@ packages: resolution: {integrity: sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==} engines: {node: '>= 0.6'} - mimic-fn@2.1.0: - resolution: {integrity: sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==} - engines: {node: '>=6'} - - mimic-fn@4.0.0: - resolution: {integrity: sha512-vqiC06CuhBTUdZH+RYl8sFrL096vA45Ok5ISO6sE/Mr1jRbGH4Csnhi8f3wKVl7x8mO4Au7Ir9D3Oyv1VYMFJw==} - engines: {node: '>=12'} - min-indent@1.0.1: resolution: {integrity: sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg==} engines: {node: '>=4'} @@ -2786,14 +2666,6 @@ packages: natural-compare@1.4.0: resolution: {integrity: sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==} - node-domexception@1.0.0: - resolution: {integrity: sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ==} - engines: {node: '>=10.5.0'} - - node-fetch@3.3.2: - resolution: {integrity: sha512-dRB78srN/l6gqWulah9SrxeYnxeddIG30+GOqK/9OlLVyLg3HPnr6SqOWTWOXKRwC2eGYCkZ59NNuSgvSrpgOA==} - engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} - node-releases@2.0.19: resolution: {integrity: sha512-xxOWJsBKtzAq7DY0J+DTzuz58K8e7sJbdgwkbMWQe8UYB6ekmsQ45q0M/tJDsGaZmbC+l7n57UV8Hl5tHxO9uw==} @@ -2805,10 +2677,6 @@ packages: resolution: {integrity: sha512-bdok/XvKII3nUpklnV6P2hxtMNrCboOjAcyBuQnWEhO665FwrSNRxU+AqpsyvO6LgGYPspN+lu5CLtw4jPRKNA==} engines: {node: '>=0.10.0'} - npm-run-path@5.3.0: - resolution: {integrity: sha512-ppwTtiJZq0O/ai0z7yfudtBpWIoxM8yE6nHi1X47eFR2EWORqfbu6CnPlNsjeN683eT0qG6H/Pyf9fCcvjnnnQ==} - engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} - nwsapi@2.2.16: resolution: {integrity: sha512-F1I/bimDpj3ncaNDhfyMWuFqmQDBwDB0Fogc2qpL3BWvkQteFD/8BzWuIRl83rq0DXfm8SGt/HFhLXZyljTXcQ==} @@ -2820,14 +2688,6 @@ packages: resolution: {integrity: sha512-RSn9F68PjH9HqtltsSnqYC1XXoWe9Bju5+213R98cNGttag9q9yAOTzdbsqvIa7aNm5WffBZFpWYr2aWrklWAw==} engines: {node: '>= 6'} - onetime@5.1.2: - resolution: {integrity: sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==} - engines: {node: '>=6'} - - onetime@6.0.0: - resolution: {integrity: sha512-1FlR+gjXK7X+AsAHso35MnyN5KqGwJRi/31ft6x0M194ht7S+rWAvd7PHss9xSKMzE0asv1pyIHaJYq+BbacAQ==} - engines: {node: '>=12'} - oniguruma-to-es@3.1.1: resolution: {integrity: sha512-bUH8SDvPkH3ho3dvwJwfonjlQ4R80vjyvrU8YpxuROddv55vAEJrTuCuCVUhhsHbtlD9tGGbaNApGQckXhS8iQ==} @@ -2835,10 +2695,6 @@ packages: resolution: {integrity: sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==} engines: {node: '>= 0.8.0'} - ora@6.3.1: - resolution: {integrity: sha512-ERAyNnZOfqM+Ao3RAvIXkYh5joP220yf59gVe2X/cI6SiCxIdi4c9HZKZD8R6q/RDXEje1THBju6iExiSsgJaQ==} - engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} - p-limit@3.1.0: resolution: {integrity: sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==} engines: {node: '>=10'} @@ -2864,10 +2720,6 @@ packages: resolution: {integrity: sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==} engines: {node: '>=8'} - path-key@4.0.0: - resolution: {integrity: sha512-haREypq7xkM7ErfgIyA0z+Bj4AGKlMSdlQE2jvJo6huWD1EdkKYV+G/T4nq0YEF2vgTT8kqMFKo1uHn950r4SQ==} - engines: {node: '>=12'} - path-parse@1.0.7: resolution: {integrity: sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==} @@ -2968,10 +2820,6 @@ packages: resolution: {integrity: sha512-Kx/1w86q/epKcmte75LNrEoT+lX8pBpavuAbvJWRXar7Hz8jrtF+e3vY751p0R8H9HdArwaCTNDDzHg/ScJK1Q==} engines: {node: '>=6'} - prompts@2.4.2: - resolution: {integrity: sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q==} - engines: {node: '>= 6'} - property-information@5.6.0: resolution: {integrity: sha512-YUHSPk+A30YPv+0Qf8i9Mbfe/C0hdPXk1s1jPVToV8pk8BQtpw10ct89Eo7OWkutrwqvT0eicAxlOg3dOAu8JA==} @@ -3023,23 +2871,6 @@ packages: react: ^16.14.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc react-dom: ^16.14.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc - react-router-dom@7.1.3: - resolution: {integrity: sha512-qQGTE+77hleBzv9SIUIkGRvuFBQGagW+TQKy53UTZAO/3+YFNBYvRsNIZ1GT17yHbc63FylMOdS+m3oUriF1GA==} - engines: {node: '>=20.0.0'} - peerDependencies: - react: '>=18' - react-dom: '>=18' - - react-router@7.1.3: - resolution: {integrity: sha512-EezYymLY6Guk/zLQ2vRA8WvdUhWFEj5fcE3RfWihhxXBW7+cd1LsIiA3lmx+KCmneAGQuyBv820o44L2+TtkSA==} - engines: {node: '>=20.0.0'} - peerDependencies: - react: '>=18' - react-dom: '>=18' - peerDependenciesMeta: - react-dom: - optional: true - react-style-singleton@2.2.3: resolution: {integrity: sha512-b6jSvxvVnyptAiLjbkWLE/lOnR4lfTtDAl+eUC7RZy+QQWc6wRzIV2CE6xBuMmDxc2qIihtDCZD5NPOFl7fRBQ==} engines: {node: '>=10'} @@ -3062,10 +2893,6 @@ packages: read-cache@1.0.0: resolution: {integrity: sha512-Owdv/Ft7IjOgm/i0xvNDZ1LrRANRfew4b2prF3OWMQLxLfu3bS8FVhCsrSCMK4lR56Y9ya+AThoTpDCTxCmpRA==} - readable-stream@3.6.2: - resolution: {integrity: sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==} - engines: {node: '>= 6'} - readdirp@3.6.0: resolution: {integrity: sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==} engines: {node: '>=8.10.0'} @@ -3094,10 +2921,6 @@ packages: engines: {node: '>= 0.4'} hasBin: true - restore-cursor@4.0.0: - resolution: {integrity: sha512-I9fPXU9geO9bHOt9pHHOhOkYerIMsmVaWB0rA2AI9ERh/+x/i7MV5HKBNrg+ljO5eoPVgCcnFuRjJ9uH6I/3eg==} - engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} - reusify@1.0.4: resolution: {integrity: sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==} engines: {iojs: '>=1.0.0', node: '>=0.10.0'} @@ -3116,9 +2939,6 @@ packages: run-parallel@1.2.0: resolution: {integrity: sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==} - safe-buffer@5.2.1: - resolution: {integrity: sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==} - safer-buffer@2.1.2: resolution: {integrity: sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==} @@ -3146,9 +2966,6 @@ packages: engines: {node: '>=10'} hasBin: true - set-cookie-parser@2.7.1: - resolution: {integrity: sha512-IOc8uWeOZgnb3ptbCURJWNjWUPcO3ZnTTdzsurqERrP6nPyv+paC55vJM0LpOlT2ne+Ix+9+CRG1MNLlyZ4GjQ==} - shebang-command@2.0.0: resolution: {integrity: sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==} engines: {node: '>=8'} @@ -3163,16 +2980,10 @@ packages: siginfo@2.0.0: resolution: {integrity: sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==} - signal-exit@3.0.7: - resolution: {integrity: sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==} - signal-exit@4.1.0: resolution: {integrity: sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==} engines: {node: '>=14'} - sisteransi@1.0.5: - resolution: {integrity: sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==} - sonner@1.7.2: resolution: {integrity: sha512-zMbseqjrOzQD1a93lxahm+qMGxWovdMxBlkTbbnZdNqVLt4j+amF9PQxUCL32WfztOFt9t9ADYkejAL3jF9iNA==} peerDependencies: @@ -3202,10 +3013,6 @@ packages: std-env@3.8.0: resolution: {integrity: sha512-Bc3YwwCB+OzldMxOXJIIvC6cPRWr/LxOp48CdQTOkPyk/t4JWWJbrilwBd7RJzKV8QW7tJkcgAmeuLLJugl5/w==} - stdin-discarder@0.1.0: - resolution: {integrity: sha512-xhV7w8S+bUwlPTb4bAOUQhv8/cSS5offJuX8GQGq32ONF0ZtDWKfkdomM3HMRA+LhX6um/FZ0COqlwsjD53LeQ==} - engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} - string-width@4.2.3: resolution: {integrity: sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==} engines: {node: '>=8'} @@ -3214,9 +3021,6 @@ packages: resolution: {integrity: sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==} engines: {node: '>=12'} - string_decoder@1.3.0: - resolution: {integrity: sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==} - stringify-entities@4.0.4: resolution: {integrity: sha512-IwfBptatlO+QCJUo19AqvrPNqlVMpW9YEL2LIVY+Rpv2qsjCGxaDLNRgeGsQWJhfItebuJhsGSLjaBbNSQ+ieg==} @@ -3228,10 +3032,6 @@ packages: resolution: {integrity: sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==} engines: {node: '>=12'} - strip-final-newline@3.0.0: - resolution: {integrity: sha512-dOESqjYr96iWYylGObzd39EuNTa5VJxyvVAEm5Jnh7KGo75V43Hk1odPQkNDyXNmUR6k+gEiDVXnjB8HJ3crXw==} - engines: {node: '>=12'} - strip-indent@3.0.0: resolution: {integrity: sha512-laJTa3Jb+VQpaC6DseHhF7dXVqHTfJPCRDaEbid/drOhgitgYku/letMUqOXFoWV0zIIUbjpdH2t+tYj4bQMRQ==} engines: {node: '>=8'} @@ -3345,9 +3145,6 @@ packages: tslib@2.8.1: resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==} - turbo-stream@2.4.0: - resolution: {integrity: sha512-FHncC10WpBd2eOmGwpmQsWLDoK4cqsA/UT/GqNoaKOQnT8uzhtCbg3EoUDMvqpOSAI0S26mr0rkjzbOO6S3v1g==} - type-check@0.4.0: resolution: {integrity: sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==} engines: {node: '>= 0.8.0'} @@ -3382,10 +3179,6 @@ packages: unist-util-visit@5.1.0: resolution: {integrity: sha512-m+vIdyeCOpdr/QeQCu2EzxX/ohgS8KbnPDgFni4dQsfSCtpz8UqDyY5GjRru8PDKuYn7Fq19j1CQ+nJSsGKOzg==} - universalify@2.0.1: - resolution: {integrity: sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==} - engines: {node: '>= 10.0.0'} - update-browserslist-db@1.1.2: resolution: {integrity: sha512-PPypAm5qvlD7XMZC3BujecnaOxwhrtoFR+Dqkk5Aa/6DssiH0ibKoketaj9w8LP7Bont1rYeoV5plxD7RTEPRg==} hasBin: true @@ -3556,13 +3349,6 @@ packages: resolution: {integrity: sha512-o8qghlI8NZHU1lLPrpi2+Uq7abh4GGPpYANlalzWxyWteJOCsr/P+oPBA49TOLu5FTZO4d3F9MnWJfiMo4BkmA==} engines: {node: '>=18'} - wcwidth@1.0.1: - resolution: {integrity: sha512-XHPEwS0q6TaxcvG85+8EYkbiCux2XtWG2mkc47Ng2A77BQu9+DqIOJldST4HgPkuea7dvKSj5VgX3P1d4rW8Tg==} - - web-streams-polyfill@3.3.3: - resolution: {integrity: sha512-d2JWLCivmZYTSIoge9MsgFCZrt571BikcWGYkjC1khllbTeDlGqZ2D8vD8E/lJa8WGWbb7Plm8/XJYV7IJHZZw==} - engines: {node: '>= 8'} - webidl-conversions@7.0.0: resolution: {integrity: sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g==} engines: {node: '>=12'} @@ -3642,9 +3428,6 @@ packages: peerDependencies: zod: ^3.25.0 || ^4.0.0 - zod@3.24.1: - resolution: {integrity: sha512-muH7gBL9sI1nciMZV67X5fTKKBLtwpZ5VBp1vsOQzj1MhrBZ4wlVCm3gedKZWLp0Oyel8sIGfeiz54Su+OVT+A==} - zod@4.4.3: resolution: {integrity: sha512-ytENFjIJFl2UwYglde2jchW2Hwm4GJFLDiSXWdTrJQBIN9Fcyp7n4DhxJEiWNAJMV1/BqWfW/kkg71UDcHJyTQ==} @@ -4810,17 +4593,6 @@ snapshots: '@rollup/rollup-win32-x64-msvc@4.31.0': optional: true - '@shadcn/ui@0.0.4': - dependencies: - chalk: 5.2.0 - commander: 10.0.1 - execa: 7.2.0 - fs-extra: 11.3.0 - node-fetch: 3.3.2 - ora: 6.3.1 - prompts: 2.4.2 - zod: 3.24.1 - '@shikijs/core@2.5.0': dependencies: '@shikijs/engine-javascript': 2.5.0 @@ -4988,8 +4760,6 @@ snapshots: dependencies: '@babel/types': 7.26.5 - '@types/cookie@0.6.0': {} - '@types/esrecurse@4.3.1': {} '@types/estree@1.0.6': {} @@ -5396,18 +5166,10 @@ snapshots: balanced-match@4.0.4: {} - base64-js@1.5.1: {} - binary-extensions@2.3.0: {} birpc@2.9.0: {} - bl@5.1.0: - dependencies: - buffer: 6.0.3 - inherits: 2.0.4 - readable-stream: 3.6.2 - brace-expansion@2.0.1: dependencies: balanced-match: 1.0.2 @@ -5427,11 +5189,6 @@ snapshots: node-releases: 2.0.19 update-browserslist-db: 1.1.2(browserslist@4.24.4) - buffer@6.0.3: - dependencies: - base64-js: 1.5.1 - ieee754: 1.2.1 - cac@6.7.14: {} camelcase-css@2.0.1: {} @@ -5458,8 +5215,6 @@ snapshots: ansi-styles: 4.3.0 supports-color: 7.2.0 - chalk@5.2.0: {} - character-entities-html4@2.1.0: {} character-entities-legacy@1.1.4: {} @@ -5488,14 +5243,6 @@ snapshots: dependencies: clsx: 2.1.1 - cli-cursor@4.0.0: - dependencies: - restore-cursor: 4.0.0 - - cli-spinners@2.9.2: {} - - clone@1.0.4: {} - clsx@2.1.1: {} color-convert@2.0.1: @@ -5512,14 +5259,10 @@ snapshots: comma-separated-tokens@2.0.3: {} - commander@10.0.1: {} - commander@4.1.1: {} convert-source-map@2.0.0: {} - cookie@1.0.2: {} - copy-anything@4.0.5: dependencies: is-what: 5.5.0 @@ -5543,8 +5286,6 @@ snapshots: csstype@3.2.3: {} - data-uri-to-buffer@4.0.1: {} - data-urls@5.0.0: dependencies: whatwg-mimetype: 4.0.0 @@ -5564,10 +5305,6 @@ snapshots: deep-is@0.1.4: {} - defaults@1.0.4: - dependencies: - clone: 1.0.4 - delayed-stream@1.0.0: {} dequal@2.0.3: {} @@ -5751,18 +5488,6 @@ snapshots: esutils@2.0.3: {} - execa@7.2.0: - dependencies: - cross-spawn: 7.0.6 - get-stream: 6.0.1 - human-signals: 4.3.1 - is-stream: 3.0.0 - merge-stream: 2.0.0 - npm-run-path: 5.3.0 - onetime: 6.0.0 - signal-exit: 3.0.7 - strip-final-newline: 3.0.0 - expect-type@1.1.0: {} fast-deep-equal@3.1.3: {} @@ -5791,11 +5516,6 @@ snapshots: optionalDependencies: picomatch: 4.0.5 - fetch-blob@3.2.0: - dependencies: - node-domexception: 1.0.0 - web-streams-polyfill: 3.3.3 - file-entry-cache@8.0.0: dependencies: flat-cache: 4.0.1 @@ -5833,18 +5553,8 @@ snapshots: format@0.2.2: {} - formdata-polyfill@4.0.10: - dependencies: - fetch-blob: 3.2.0 - fraction.js@4.3.7: {} - fs-extra@11.3.0: - dependencies: - graceful-fs: 4.2.11 - jsonfile: 6.1.0 - universalify: 2.0.1 - fsevents@2.3.3: optional: true @@ -5854,8 +5564,6 @@ snapshots: get-nonce@1.0.1: {} - get-stream@6.0.1: {} - glob-parent@5.1.2: dependencies: is-glob: 4.0.3 @@ -5877,8 +5585,6 @@ snapshots: globals@17.7.0: {} - graceful-fs@4.2.11: {} - has-flag@4.0.0: {} hasown@2.0.2: @@ -5947,14 +5653,10 @@ snapshots: transitivePeerDependencies: - supports-color - human-signals@4.3.1: {} - iconv-lite@0.6.3: dependencies: safer-buffer: 2.1.2 - ieee754@1.2.1: {} - ignore@5.3.2: {} ignore@7.0.6: {} @@ -5963,8 +5665,6 @@ snapshots: indent-string@4.0.0: {} - inherits@2.0.4: {} - is-alphabetical@1.0.4: {} is-alphanumerical@1.0.4: @@ -5992,16 +5692,10 @@ snapshots: is-hexadecimal@1.0.4: {} - is-interactive@2.0.0: {} - is-number@7.0.0: {} is-potential-custom-element-name@1.0.1: {} - is-stream@3.0.0: {} - - is-unicode-supported@1.3.0: {} - is-what@5.5.0: {} isexe@2.0.0: {} @@ -6075,18 +5769,10 @@ snapshots: json5@2.2.3: {} - jsonfile@6.1.0: - dependencies: - universalify: 2.0.1 - optionalDependencies: - graceful-fs: 4.2.11 - keyv@4.5.4: dependencies: json-buffer: 3.0.1 - kleur@3.0.3: {} - levn@0.4.1: dependencies: prelude-ls: 1.2.1 @@ -6102,11 +5788,6 @@ snapshots: lodash@4.17.21: {} - log-symbols@5.1.0: - dependencies: - chalk: 5.2.0 - is-unicode-supported: 1.3.0 - loose-envify@1.4.0: dependencies: js-tokens: 4.0.0 @@ -6164,8 +5845,6 @@ snapshots: unist-util-visit: 5.1.0 vfile: 6.0.3 - merge-stream@2.0.0: {} - merge2@1.4.1: {} micromark-util-character@2.1.1: @@ -6196,10 +5875,6 @@ snapshots: dependencies: mime-db: 1.52.0 - mimic-fn@2.1.0: {} - - mimic-fn@4.0.0: {} - min-indent@1.0.1: {} minimatch@10.2.5: @@ -6235,38 +5910,18 @@ snapshots: natural-compare@1.4.0: {} - node-domexception@1.0.0: {} - - node-fetch@3.3.2: - dependencies: - data-uri-to-buffer: 4.0.1 - fetch-blob: 3.2.0 - formdata-polyfill: 4.0.10 - node-releases@2.0.19: {} normalize-path@3.0.0: {} normalize-range@0.1.2: {} - npm-run-path@5.3.0: - dependencies: - path-key: 4.0.0 - nwsapi@2.2.16: {} object-assign@4.1.1: {} object-hash@3.0.0: {} - onetime@5.1.2: - dependencies: - mimic-fn: 2.1.0 - - onetime@6.0.0: - dependencies: - mimic-fn: 4.0.0 - oniguruma-to-es@3.1.1: dependencies: emoji-regex-xs: 1.0.0 @@ -6282,18 +5937,6 @@ snapshots: type-check: 0.4.0 word-wrap: 1.2.5 - ora@6.3.1: - dependencies: - chalk: 5.2.0 - cli-cursor: 4.0.0 - cli-spinners: 2.9.2 - is-interactive: 2.0.0 - is-unicode-supported: 1.3.0 - log-symbols: 5.1.0 - stdin-discarder: 0.1.0 - strip-ansi: 7.1.0 - wcwidth: 1.0.1 - p-limit@3.1.0: dependencies: yocto-queue: 0.1.0 @@ -6321,8 +5964,6 @@ snapshots: path-key@3.1.1: {} - path-key@4.0.0: {} - path-parse@1.0.7: {} path-scurry@1.11.1: @@ -6403,11 +6044,6 @@ snapshots: prismjs@1.29.0: {} - prompts@2.4.2: - dependencies: - kleur: 3.0.3 - sisteransi: 1.0.5 - property-information@5.6.0: dependencies: xtend: 4.0.2 @@ -6452,22 +6088,6 @@ snapshots: react: 18.3.1 react-dom: 18.3.1(react@18.3.1) - react-router-dom@7.1.3(react-dom@18.3.1(react@18.3.1))(react@18.3.1): - dependencies: - react: 18.3.1 - react-dom: 18.3.1(react@18.3.1) - react-router: 7.1.3(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - - react-router@7.1.3(react-dom@18.3.1(react@18.3.1))(react@18.3.1): - dependencies: - '@types/cookie': 0.6.0 - cookie: 1.0.2 - react: 18.3.1 - set-cookie-parser: 2.7.1 - turbo-stream: 2.4.0 - optionalDependencies: - react-dom: 18.3.1(react@18.3.1) - react-style-singleton@2.2.3(@types/react@18.3.18)(react@18.3.1): dependencies: get-nonce: 1.0.1 @@ -6494,12 +6114,6 @@ snapshots: dependencies: pify: 2.3.0 - readable-stream@3.6.2: - dependencies: - inherits: 2.0.4 - string_decoder: 1.3.0 - util-deprecate: 1.0.2 - readdirp@3.6.0: dependencies: picomatch: 2.3.1 @@ -6533,11 +6147,6 @@ snapshots: path-parse: 1.0.7 supports-preserve-symlinks-flag: 1.0.0 - restore-cursor@4.0.0: - dependencies: - onetime: 5.1.2 - signal-exit: 3.0.7 - reusify@1.0.4: {} rfdc@1.4.1: {} @@ -6573,8 +6182,6 @@ snapshots: dependencies: queue-microtask: 1.2.3 - safe-buffer@5.2.1: {} - safer-buffer@2.1.2: {} saxes@6.0.0: @@ -6593,8 +6200,6 @@ snapshots: semver@7.8.5: {} - set-cookie-parser@2.7.1: {} - shebang-command@2.0.0: dependencies: shebang-regex: 3.0.0 @@ -6614,12 +6219,8 @@ snapshots: siginfo@2.0.0: {} - signal-exit@3.0.7: {} - signal-exit@4.1.0: {} - sisteransi@1.0.5: {} - sonner@1.7.2(react-dom@18.3.1(react@18.3.1))(react@18.3.1): dependencies: react: 18.3.1 @@ -6639,10 +6240,6 @@ snapshots: std-env@3.8.0: {} - stdin-discarder@0.1.0: - dependencies: - bl: 5.1.0 - string-width@4.2.3: dependencies: emoji-regex: 8.0.0 @@ -6655,10 +6252,6 @@ snapshots: emoji-regex: 9.2.2 strip-ansi: 7.1.0 - string_decoder@1.3.0: - dependencies: - safe-buffer: 5.2.1 - stringify-entities@4.0.4: dependencies: character-entities-html4: 2.1.0 @@ -6672,8 +6265,6 @@ snapshots: dependencies: ansi-regex: 6.1.0 - strip-final-newline@3.0.0: {} - strip-indent@3.0.0: dependencies: min-indent: 1.0.1 @@ -6796,8 +6387,6 @@ snapshots: tslib@2.8.1: {} - turbo-stream@2.4.0: {} - type-check@0.4.0: dependencies: prelude-ls: 1.2.1 @@ -6840,8 +6429,6 @@ snapshots: unist-util-is: 6.0.1 unist-util-visit-parents: 6.0.2 - universalify@2.0.1: {} - update-browserslist-db@1.1.2(browserslist@4.24.4): dependencies: browserslist: 4.24.4 @@ -7024,12 +6611,6 @@ snapshots: dependencies: xml-name-validator: 5.0.0 - wcwidth@1.0.1: - dependencies: - defaults: 1.0.4 - - web-streams-polyfill@3.3.3: {} - webidl-conversions@7.0.0: {} whatwg-encoding@3.1.1: @@ -7084,8 +6665,6 @@ snapshots: dependencies: zod: 4.4.3 - zod@3.24.1: {} - zod@4.4.3: {} zustand@5.0.3(@types/react@18.3.18)(react@18.3.1): diff --git a/src-tauri/src/http_client.rs b/src-tauri/src/http_client.rs index d769d81..a2374f9 100644 --- a/src-tauri/src/http_client.rs +++ b/src-tauri/src/http_client.rs @@ -244,11 +244,9 @@ where { let mut total = 0.0; let mut has_value = false; - for value in values { - if let Some(v) = value { - total += v; - has_value = true; - } + for v in values.into_iter().flatten() { + total += v; + has_value = true; } has_value.then_some(total) } @@ -366,7 +364,7 @@ fn perform_curl_request( return true; }; - let line = raw_line.trim_end_matches(|c| c == '\r' || c == '\n'); + let line = raw_line.trim_end_matches(['\r', '\n']); if line.is_empty() { return true; } diff --git a/src-tauri/src/oauth.rs b/src-tauri/src/oauth.rs index 97afa5a..830c584 100644 --- a/src-tauri/src/oauth.rs +++ b/src-tauri/src/oauth.rs @@ -26,6 +26,25 @@ const AUTH_CALLBACK_TIMEOUT_SECS: u64 = 300; /// `expires_in` cannot leave a poll loop running for hours. const DEVICE_CODE_DEFAULT_EXPIRES_SECS: u64 = 900; const DEVICE_CODE_MAX_EXPIRES_SECS: u64 = 1800; + +/// POST a form to an OAuth endpoint with the standard accept header and +/// timeout. Every token/device request in this module sends the same shape; +/// `err_prefix` labels a transport failure ("Token request failed: ..."). +async fn post_oauth_form( + client: &reqwest::Client, + url: &str, + params: &HashMap, + err_prefix: &str, +) -> Result { + client + .post(url) + .header("accept", OAUTH_TOKEN_ACCEPT_HEADER) + .timeout(std::time::Duration::from_secs(30)) + .form(params) + .send() + .await + .map_err(|e| format!("{}: {}", err_prefix, e)) +} const DEVICE_POLL_DEFAULT_INTERVAL_SECS: u64 = 5; #[derive(Debug, Deserialize)] @@ -597,14 +616,7 @@ pub async fn oauth2_token_exchange( } let client = client_wrapper.get_or_init_client()?; - let res = client - .post(&token_url) - .header("accept", OAUTH_TOKEN_ACCEPT_HEADER) - .timeout(std::time::Duration::from_secs(30)) - .form(¶ms) - .send() - .await - .map_err(|e| format!("Token request failed: {}", e))?; + let res = post_oauth_form(&client, &token_url, ¶ms, "Token request failed").await?; if !res.status().is_success() { return Err(oauth_http_error("Token request failed", res).await); @@ -758,14 +770,7 @@ pub async fn oauth2_auth_code_flow( } let client = client_wrapper.get_or_init_client()?; - let res = client - .post(&token_url) - .header("accept", OAUTH_TOKEN_ACCEPT_HEADER) - .timeout(std::time::Duration::from_secs(30)) - .form(¶ms) - .send() - .await - .map_err(|e| format!("Token exchange failed: {}", e))?; + let res = post_oauth_form(&client, &token_url, ¶ms, "Token exchange failed").await?; if !res.status().is_success() { return Err(oauth_http_error("Token exchange failed", res).await); @@ -841,14 +846,13 @@ pub async fn oauth2_device_flow( params.insert("client_secret".to_string(), secret.clone()); } - let res = client - .post(&device_auth_url) - .header("accept", OAUTH_TOKEN_ACCEPT_HEADER) - .timeout(std::time::Duration::from_secs(30)) - .form(¶ms) - .send() - .await - .map_err(|e| format!("Device authorization request failed: {}", e))?; + let res = post_oauth_form( + &client, + &device_auth_url, + ¶ms, + "Device authorization request failed", + ) + .await?; if !res.status().is_success() { return Err(oauth_http_error("Device authorization request failed", res).await); @@ -914,14 +918,8 @@ pub async fn oauth2_device_flow( poll_params.insert("client_secret".to_string(), secret.clone()); } - let res = client - .post(&token_url) - .header("accept", OAUTH_TOKEN_ACCEPT_HEADER) - .timeout(std::time::Duration::from_secs(30)) - .form(&poll_params) - .send() - .await - .map_err(|e| format!("Token request failed: {}", e))?; + let res = + post_oauth_form(&client, &token_url, &poll_params, "Token request failed").await?; let status = res.status(); let (content_type, body) = read_response_body(res).await?; @@ -1090,14 +1088,7 @@ pub async fn oauth2_refresh( insert_optional_param(&mut params, "client_secret", options.client_secret); let client = client_wrapper.get_or_init_client()?; - let res = client - .post(&token_url) - .header("accept", OAUTH_TOKEN_ACCEPT_HEADER) - .timeout(std::time::Duration::from_secs(30)) - .form(¶ms) - .send() - .await - .map_err(|e| format!("Refresh token request failed: {}", e))?; + let res = post_oauth_form(&client, &token_url, ¶ms, "Refresh token request failed").await?; if !res.status().is_success() { return Err(oauth_http_error("Refresh failed", res).await); diff --git a/src/App.tsx b/src/App.tsx index 778edd7..b465a7d 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -67,6 +67,9 @@ function App() { setActiveTab, addTab, closeTab, + closeOtherTabs, + closeTabsToRight, + duplicateTab, updateTab, startEditing, stopEditing, @@ -106,6 +109,51 @@ function App() { return () => window.removeEventListener("keydown", handleKeyDown) }, [togglePalette]) + // Ref mirrors so the tab-shortcut listener stays stable across renders. + const tabsRef = useRef(tabs) + tabsRef.current = tabs + const activeTabRef = useRef(activeTab) + activeTabRef.current = activeTab + + // Tab management shortcuts: Ctrl+T new, Ctrl+W close, Ctrl(+Shift)+Tab + // cycle, Ctrl+1–9 jump (9 = last, as in every browser). + useEffect(() => { + const handleKeyDown = (e: KeyboardEvent) => { + if (!(e.ctrlKey || e.metaKey)) return + const currentTabs = tabsRef.current + const current = activeTabRef.current + + if (e.key === "Tab") { + e.preventDefault() + const index = currentTabs.findIndex((tab) => tab.id === current) + if (index === -1) return + const nextIndex = e.shiftKey + ? (index - 1 + currentTabs.length) % currentTabs.length + : (index + 1) % currentTabs.length + setActiveTab(currentTabs[nextIndex].id) + return + } + if (e.shiftKey) return + + const key = e.key.toLowerCase() + if (key === "t") { + e.preventDefault() + addTab() + } else if (key === "w") { + e.preventDefault() + closeTab(current) + } else if (e.key >= "1" && e.key <= "9") { + const index = e.key === "9" ? currentTabs.length - 1 : Number(e.key) - 1 + if (index < currentTabs.length) { + e.preventDefault() + setActiveTab(currentTabs[index].id) + } + } + } + window.addEventListener("keydown", handleKeyDown) + return () => window.removeEventListener("keydown", handleKeyDown) + }, [addTab, closeTab, setActiveTab]) + // Mirror the theme class onto so portaled content (tooltips, menus, // dialogs) inherits theme tokens instead of falling back to :root defaults. useEffect(() => { @@ -155,24 +203,22 @@ function App() { const requestUrl = overrides.url ?? tab?.rawUrl if (!tab || !requestUrl?.trim()) return + const urlUpdates = overrides.url === undefined ? {} : { + rawUrl: overrides.url, + url: overrides.url, + // A user-set tab name survives URL edits. + ...(tab.nameEdited ? {} : { name: getRequestNameFromUrl(overrides.url) }), + } const requestTab = { ...tab, ...(overrides.body === undefined ? {} : { body: overrides.body }), - ...(overrides.url === undefined ? {} : { - rawUrl: overrides.url, - url: overrides.url, - name: getRequestNameFromUrl(overrides.url), - }), + ...urlUpdates, } updateTab(tabId, { loading: true, response: null, ...(overrides.body === undefined ? {} : { body: overrides.body }), - ...(overrides.url === undefined ? {} : { - rawUrl: overrides.url, - url: overrides.url, - name: getRequestNameFromUrl(overrides.url), - }), + ...urlUpdates, }) const response = await sendRequest(requestTab) updateTab(tabId, { loading: false, response: response || null }) @@ -300,6 +346,9 @@ function App() { onTabChange={setActiveTab} onAddTab={addTab} onCloseTab={closeTab} + onCloseOtherTabs={closeOtherTabs} + onCloseTabsToRight={closeTabsToRight} + onDuplicateTab={duplicateTab} onStartEditing={startEditing} onStopEditing={stopEditing} /> @@ -318,6 +367,7 @@ function App() { body={currentTab.body} contentType={currentTab.contentType} auth={currentTab.auth} + tabName={currentTab.name} cookies={currentTab.cookies} response={currentTab.response} testScripts={currentTab.testScripts} @@ -330,7 +380,8 @@ function App() { updateTab(currentTab.id, { rawUrl, url: rawUrl, - name: getRequestNameFromUrl(rawUrl) + // A user-set tab name survives URL edits. + ...(currentTab.nameEdited ? {} : { name: getRequestNameFromUrl(rawUrl) }) }) }} onParamsChange={(params) => updateTab(currentTab.id, { params })} diff --git a/src/components/CollapsibleJSON.tsx b/src/components/CollapsibleJSON.tsx index 3cfc0e8..58e0f42 100644 --- a/src/components/CollapsibleJSON.tsx +++ b/src/components/CollapsibleJSON.tsx @@ -7,7 +7,8 @@ import { ChevronRight, ChevronDown } from "lucide-react" const CHILDREN_PAGE_SIZE = 100 interface CollapsibleJSONProps { - data: any + /** Any parsed JSON value — object, array, or primitive. */ + data: unknown level?: number isExpanded?: boolean maxAutoExpandDepth?: number diff --git a/src/components/CollectionRunner.tsx b/src/components/CollectionRunner.tsx index d519672..118341a 100644 --- a/src/components/CollectionRunner.tsx +++ b/src/components/CollectionRunner.tsx @@ -5,14 +5,14 @@ import { Progress } from "@/components/ui/progress" import { ScrollArea } from "@/components/ui/scroll-area" import { Card } from "@/components/ui/card" import { Collection, SavedRequest, Response, TestResult } from "@/types" -import { applyAuthToHeaders } from "@/utils/authHeaders" import { resolveRequestAuth } from "@/utils/collectionAuth" +import { buildSendRequestOptions } from "@/utils/requestBuilder" +import { methodTextColors as methodColors } from "@/utils/methodColors" import { useCollectionStore } from "@/store/collections" import { useEnvironmentStore } from "@/store/environments" import { useSettingsStore } from "@/store/settings" import { runTests } from "@/utils/testRunner" import { invoke } from "@tauri-apps/api/core" -import { runPreRequestScripts } from "@/utils/preRequestRunner" import { applyExtractionRules } from "@/utils/responseExtraction" import { Dialog, @@ -74,114 +74,20 @@ export function CollectionRunner({ open, onOpenChange }: CollectionRunnerProps) const selectedCollection = collections.find((c) => c.id === selectedCollectionId) - const substituteVariables = useCallback( - (text: string): string => { - return text.replace(/\{\{([^}]+)\}\}/g, (match, key) => { - const value = getVariable(key.trim()) - return value !== undefined ? value : match - }) - }, - [getVariable] - ) - const runRequest = useCallback( async (request: SavedRequest, collection?: Collection): Promise => { const startTime = performance.now() try { - // Build headers - const headerRecord: Record = {} - request.headers.forEach((header) => { - if (header.enabled && header.key) { - headerRecord[substituteVariables(header.key)] = substituteVariables(header.value) - } + // Auth falls back to the collection's where the request does + // not carry its own — an imported spec relies on that. + const { options, method } = await buildSendRequestOptions(request, { + auth: resolveRequestAuth(request, collection), + getVariable, + setVariable, + globalNetwork, }) - // Apply auth, falling back to the collection's where the request - // does not carry its own — an imported spec relies on that. - let url = substituteVariables(request.rawUrl || request.url) - url = applyAuthToHeaders( - resolveRequestAuth(request, collection), - headerRecord, - url, - substituteVariables - ) - - // Cookie header - const cookieHeader = request.cookies - .map( - (c) => - `${encodeURIComponent(substituteVariables(c.name))}=${encodeURIComponent( - substituteVariables(c.value) - )}` - ) - .join("; ") - if (cookieHeader) headerRecord["Cookie"] = cookieHeader - - const body = - request.body && request.method !== "GET" && request.method !== "HEAD" - ? substituteVariables(request.body) - : undefined - - let method = request.method - let runtimeUrl = url - let runtimeBody = body - const runtimeHeaders = { ...headerRecord } - - if (request.preRequestScripts && request.preRequestScripts.length > 0) { - const runtime = await runPreRequestScripts({ - scripts: request.preRequestScripts, - request: { - method, - url: runtimeUrl, - headers: runtimeHeaders, - body: runtimeBody, - }, - getVariable, - setVariable, - substituteVariables, - }) - - method = runtime.method - runtimeUrl = runtime.url - runtimeBody = runtime.body - Object.keys(runtimeHeaders).forEach((key) => { - delete runtimeHeaders[key] - }) - Object.assign(runtimeHeaders, runtime.headers) - } - - const nc = request.networkConfig - const options: Record = { - method, - url: runtimeUrl, - headers: runtimeHeaders, - body: runtimeBody, - content_type: - runtimeBody && method !== "GET" && method !== "HEAD" - ? request.contentType - : undefined, - cookies: request.cookies.map((c) => ({ - ...c, - name: substituteVariables(c.name), - value: substituteVariables(c.value), - })), - timeout: (nc?.timeout ?? globalNetwork.timeout) || undefined, - connect_timeout: (nc?.connectTimeout ?? globalNetwork.connectTimeout) || undefined, - ssl_verification: nc?.sslVerification ?? globalNetwork.sslVerification, - proxy: (nc?.proxy ?? globalNetwork.proxy) || undefined, - } - - if (request.contentType === "multipart/form-data" && request.formDataEntries) { - options.form_data = request.formDataEntries.map((entry) => ({ - ...entry, - key: substituteVariables(entry.key), - value: entry.type === "text" ? substituteVariables(entry.value) : entry.value, - fileName: entry.fileName ? substituteVariables(entry.fileName) : entry.fileName, - })) - options.content_type = "multipart/form-data" - } - const responseData = await invoke<{ status: number status_text: string @@ -248,7 +154,7 @@ export function CollectionRunner({ open, onOpenChange }: CollectionRunnerProps) } } }, - [activeEnvironmentId, getVariable, setVariable, substituteVariables, globalNetwork] + [activeEnvironmentId, getVariable, setVariable, globalNetwork] ) const runCollection = useCallback(async () => { @@ -293,15 +199,6 @@ export function CollectionRunner({ open, onOpenChange }: CollectionRunnerProps) const progress = totalRequests > 0 ? (currentIndex / totalRequests) * 100 : 0 // Method colors - const methodColors: Record = { - GET: "text-sky-400", - POST: "text-emerald-400", - PUT: "text-amber-400", - DELETE: "text-rose-400", - PATCH: "text-orange-400", - HEAD: "text-violet-400", - OPTIONS: "text-cyan-400", - } return ( diff --git a/src/components/CollectionsPanel.tsx b/src/components/CollectionsPanel.tsx index b7df6e0..06298ab 100644 --- a/src/components/CollectionsPanel.tsx +++ b/src/components/CollectionsPanel.tsx @@ -57,7 +57,7 @@ export const CollectionsPanel = forwardRef(null) const themeClass = useThemeClass() - const { width, isDragging, setIsDragging } = useResizablePanel(600, 450) + const { width, isDragging, setIsDragging } = useResizablePanel(600, 450, 0.9, "litepost:collectionsPanelWidth") const shouldLogImportErrors = typeof import.meta !== "undefined" && Boolean(import.meta.env?.DEV) && @@ -82,11 +82,12 @@ export const CollectionsPanel = forwardRef { if (!currentRequest) return - const { id, loading, response, isEditing, activeSession, ...requestData } = currentRequest - addRequest(collectionId, { - ...requestData, - name: getRequestNameFromUrl(requestData.url) - }) + const { id, loading, response, isEditing, ...requestData } = currentRequest + // Keep the tab's name (it may be user-set); fall back to a URL-derived one. + const name = requestData.name?.trim() || getRequestNameFromUrl(requestData.url) + addRequest(collectionId, { ...requestData, name }) + const collectionName = collections.find((c) => c.id === collectionId)?.name + toast.success(`Saved "${name}"${collectionName ? ` to ${collectionName}` : ''}`) } const handleSelectRequest = (request: Tab) => { @@ -122,28 +123,25 @@ export const CollectionsPanel = forwardRef { - const blob = new Blob([exportCollections()], { type: 'application/json' }) + const downloadJson = (content: string, fileName: string) => { + const blob = new Blob([content], { type: 'application/json' }) const url = URL.createObjectURL(blob) const a = document.createElement('a') a.href = url - a.download = 'litepost-collections.json' + a.download = fileName document.body.appendChild(a) a.click() document.body.removeChild(a) URL.revokeObjectURL(url) + toast.success(`Exported ${fileName}`) + } + + const handleExport = () => { + downloadJson(exportCollections(), 'litepost-collections.json') } const handleExportPostman = () => { - const blob = new Blob([exportToPostman()], { type: 'application/json' }) - const url = URL.createObjectURL(blob) - const a = document.createElement('a') - a.href = url - a.download = 'postman-collections.json' - document.body.appendChild(a) - a.click() - document.body.removeChild(a) - URL.revokeObjectURL(url) + downloadJson(exportToPostman(), 'postman-collections.json') } const handleImport = (event: React.ChangeEvent) => { @@ -260,6 +258,9 @@ export const CollectionsPanel = forwardRef {/* Resize Handle */}
{ e.preventDefault(); setIsDragging(true); }} > diff --git a/src/components/CommandPalette.tsx b/src/components/CommandPalette.tsx index 100e1a9..33514ca 100644 --- a/src/components/CommandPalette.tsx +++ b/src/components/CommandPalette.tsx @@ -7,15 +7,7 @@ import { useUiStore } from "@/store/ui" import { cn } from "@/lib/utils" import { Beaker, Check, Clock, Folder, History, Plus, Search, Settings, Terminal, Zap } from "lucide-react" -const methodColors: Record = { - GET: "text-sky-400", - POST: "text-emerald-400", - PUT: "text-amber-400", - PATCH: "text-orange-400", - DELETE: "text-rose-400", - HEAD: "text-violet-400", - OPTIONS: "text-cyan-400", -} +import { methodTextColors as methodColors } from "@/utils/methodColors" interface PaletteResult { kind: "history" | "saved" | "environment" | "action" @@ -263,7 +255,9 @@ export function CommandPalette({ "flex-1 min-w-0 truncate text-foreground/90", (result.kind === "history" || result.kind === "saved") && "font-mono" )}>{result.label} - {result.sub && {result.sub}} + {/* Capped: an unbounded shrink-0 sub (e.g. a long collection + name) would force the whole row wider than the dialog. */} + {result.sub && {result.sub}}
) diff --git a/src/components/CopyButton.tsx b/src/components/CopyButton.tsx index 64ac997..8a02dc9 100644 --- a/src/components/CopyButton.tsx +++ b/src/components/CopyButton.tsx @@ -6,13 +6,22 @@ import { toast } from "sonner" interface CopyButtonProps { content: string className?: string + /** Accessible name; defaults to "Copy to clipboard". */ + label?: string } -export function CopyButton({ content, className = "" }: CopyButtonProps) { +export function CopyButton({ content, className = "", label = "Copy to clipboard" }: CopyButtonProps) { const [copied, setCopied] = useState(false) const copy = async () => { - await navigator.clipboard.writeText(content) + try { + await navigator.clipboard.writeText(content) + } catch { + // Clipboard access can be denied (permissions, non-secure context) — + // surface it instead of silently doing nothing. + toast.error("Couldn't copy to clipboard") + return + } setCopied(true) toast.success("Copied to clipboard") setTimeout(() => setCopied(false), 2000) @@ -24,6 +33,8 @@ export function CopyButton({ content, className = "" }: CopyButtonProps) { size="icon" className={`h-8 w-8 ${className}`} onClick={copy} + title={label} + aria-label={label} > {copied ? ( @@ -32,4 +43,4 @@ export function CopyButton({ content, className = "" }: CopyButtonProps) { )} ) -} \ No newline at end of file +} diff --git a/src/components/CurlImportModal.tsx b/src/components/CurlImportModal.tsx index d9655e4..971ce0e 100644 --- a/src/components/CurlImportModal.tsx +++ b/src/components/CurlImportModal.tsx @@ -12,6 +12,7 @@ import { Textarea } from "@/components/ui/textarea" import { Badge } from "@/components/ui/badge" import { parseCurlCommand, isCurlCommand } from "@/utils/curlParser" import { toast } from "sonner" +import { methodOutlineColors as methodColors } from "@/utils/methodColors" import { Terminal, ArrowRight, AlertCircle } from "lucide-react" import { useThemeClass } from "@/hooks/useThemeClass" @@ -71,19 +72,15 @@ export function CurlImportModal({ open, onOpenChange, onImport }: CurlImportModa }, [preview, onImport, onOpenChange]) // Method color mapping (matching RequestUrlBar) - const methodColors: Record = { - GET: "bg-sky-500/15 text-sky-400 border-sky-500/30", - POST: "bg-emerald-500/15 text-emerald-400 border-emerald-500/30", - PUT: "bg-amber-500/15 text-amber-400 border-amber-500/30", - DELETE: "bg-rose-500/15 text-rose-400 border-rose-500/30", - PATCH: "bg-orange-500/15 text-orange-400 border-orange-500/30", - HEAD: "bg-violet-500/15 text-violet-400 border-violet-500/30", - OPTIONS: "bg-cyan-500/15 text-cyan-400 border-cyan-500/30", - } return ( - + { + if ((e.ctrlKey || e.metaKey) && e.key === "Enter") handleImport() + }} + > diff --git a/src/components/EnvironmentManager.tsx b/src/components/EnvironmentManager.tsx index 0b72c79..3869f4b 100644 --- a/src/components/EnvironmentManager.tsx +++ b/src/components/EnvironmentManager.tsx @@ -1,4 +1,15 @@ -import { PlusCircle, Trash2 } from "lucide-react" +import { Globe, PlusCircle, Trash2 } from "lucide-react" +import { + AlertDialog, + AlertDialogAction, + AlertDialogCancel, + AlertDialogContent, + AlertDialogDescription, + AlertDialogFooter, + AlertDialogHeader, + AlertDialogTitle, + AlertDialogTrigger, +} from "@/components/ui/alert-dialog" import { Button } from "@/components/ui/button" import { Input } from "@/components/ui/input" import { Label } from "@/components/ui/label" @@ -76,22 +87,56 @@ export function EnvironmentManager() { + {environments.length === 0 && ( +
+ +

No environments yet

+

+ Create one to use {'{{variables}}'} like base URLs and tokens across requests. +

+
+ )} + {environments.map((env, index) => (
handleUpdateName(env.id, e.target.value)} + aria-label={`Environment name ${env.name}`} className="flex-1 bg-background text-foreground" /> - + + + + + + + Delete Environment + + This will permanently delete "{env.name}" and its {Object.keys(env.variables).length} variable{Object.keys(env.variables).length === 1 ? '' : 's'}. + {env.id === activeEnvironmentId && ' It is the active environment — any {{variable}} references will stop resolving.'} + + + + Cancel + deleteEnvironment(env.id)} + className="bg-destructive text-destructive-foreground hover:bg-destructive/90 rounded-xl transition-all shadow-lg shadow-destructive/20" + > + Delete + + + +
( ({ open, onOpenChange }, _ref) => { const themeClass = useThemeClass() - const { width, isDragging, setIsDragging } = useResizablePanel(600, 400) + const { width, isDragging, setIsDragging } = useResizablePanel(600, 400, 0.9, "litepost:environmentPanelWidth") return ( @@ -29,6 +29,9 @@ export const EnvironmentPanel = forwardRef {/* Resize Handle */}
{ e.preventDefault(); setIsDragging(true); }} > diff --git a/src/components/FormDataEditor.tsx b/src/components/FormDataEditor.tsx index c32d4cf..be01935 100644 --- a/src/components/FormDataEditor.tsx +++ b/src/components/FormDataEditor.tsx @@ -12,6 +12,7 @@ import { } from "@/components/ui/select" import { Plus, Trash2, File, Upload, X } from "lucide-react" import { useThemeClass } from "@/hooks/useThemeClass" +import { formatBytes } from "@/utils/format" export interface FormDataEntry { id: string @@ -30,11 +31,6 @@ interface FormDataEditorProps { onEntriesChange: (entries: FormDataEntry[]) => void } -function formatFileSize(bytes: number): string { - if (bytes < 1024) return `${bytes} B` - if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB` - return `${(bytes / (1024 * 1024)).toFixed(1)} MB` -} export function FormDataEditor({ entries, onEntriesChange }: FormDataEditorProps) { const themeClass = useThemeClass() @@ -197,7 +193,7 @@ export function FormDataEditor({ entries, onEntriesChange }: FormDataEditorProps {entry.fileName} {entry.fileSize && ( - ({formatFileSize(entry.fileSize)}) + ({formatBytes(entry.fileSize)}) )} diff --git a/src/components/GraphQLEditor.tsx b/src/components/GraphQLEditor.tsx index e7d2e2d..5ecc250 100644 --- a/src/components/GraphQLEditor.tsx +++ b/src/components/GraphQLEditor.tsx @@ -1,5 +1,7 @@ import { useCallback, useRef, useEffect, useState, useMemo } from "react" -import { basicAuthValue } from "@/utils/base64" +import { applyAuthToHeaders } from "@/utils/authHeaders" +import { substituteVariables as substitute } from "@/utils/variables" +import { useEnvironmentStore } from "@/store/environments" import Editor, { OnMount, loader } from "@monaco-editor/react" import type { editor as MonacoEditor, IDisposable } from "monaco-editor" import { Button } from "@/components/ui/button" @@ -46,6 +48,8 @@ interface GraphQLEditorProps { onQueryChange: (query: string) => void onVariablesChange: (variables: string) => void onOperationNameChange: (name: string) => void + /** Ctrl/Cmd+Enter inside either editor sends the request. */ + onSend?: () => void } // Simple Monaco theme config for the GraphQL editor - uses the same bg as body editor @@ -68,8 +72,11 @@ export function GraphQLEditor({ onQueryChange, onVariablesChange, onOperationNameChange, + onSend, }: GraphQLEditorProps) { const { color: themeColor } = useThemeStore() + const onSendRef = useRef(onSend) + onSendRef.current = onSend const [activeSection, setActiveSection] = useState<"query" | "variables">("query") const queryEditorRef = useRef(null) const varsEditorRef = useRef(null) @@ -77,6 +84,7 @@ export function GraphQLEditor({ const completionDisposableRef = useRef(null) const { getSchema, isLoading, getError, setSchema, setLoading, setError } = useGraphQLSchemaStore() + const { getVariable } = useEnvironmentStore() const schema = getSchema(url) const loading = isLoading(url) const schemaError = getError(url) @@ -153,18 +161,27 @@ export function GraphQLEditor({ }, } + // Monaco swallows Ctrl+Enter, so the send shortcut must be an editor command. + const registerSendCommand = useCallback((editor: MonacoEditor.IStandaloneCodeEditor) => { + editor.addCommand(monaco.KeyMod.CtrlCmd | monaco.KeyCode.Enter, () => { + onSendRef.current?.() + }) + }, []) + const handleQueryMount: OnMount = useCallback( (editor) => { queryEditorRef.current = editor + registerSendCommand(editor) }, - [] + [registerSendCommand] ) const handleVarsMount: OnMount = useCallback( (editor) => { varsEditorRef.current = editor + registerSendCommand(editor) }, - [] + [registerSendCommand] ) // Sync external query changes @@ -202,26 +219,19 @@ export function GraphQLEditor({ setError(url, null) try { - // Build headers for introspection request + // Build headers for introspection request, resolving {{variables}} + // and applying auth the same way a normal send does. + const substituteVariables = (text: string) => substitute(text, getVariable) const headerRecord: Record = { 'Content-Type': 'application/json', } headers.forEach(h => { if (h.enabled && h.key) { - headerRecord[h.key] = h.value + headerRecord[substituteVariables(h.key)] = substituteVariables(h.value) } }) - // Apply auth - if (auth.type === 'basic' && auth.username) { - headerRecord['Authorization'] = basicAuthValue(auth.username, auth.password || '') - } else if (auth.type === 'bearer' && auth.token) { - headerRecord['Authorization'] = `Bearer ${auth.token}` - } else if (auth.type === 'api-key' && auth.key && auth.value && auth.addTo === 'header') { - headerRecord[auth.key] = auth.value - } else if (auth.type === 'oauth2' && auth.oauth2?.accessToken) { - headerRecord['Authorization'] = `${auth.oauth2.tokenType || 'Bearer'} ${auth.oauth2.accessToken}` - } + const requestUrl = applyAuthToHeaders(auth, headerRecord, substituteVariables(url), substituteVariables) const response = await invoke<{ status: number @@ -229,7 +239,7 @@ export function GraphQLEditor({ }>('send_request', { options: { method: 'POST', - url, + url: requestUrl, headers: headerRecord, body: JSON.stringify({ query: INTROSPECTION_QUERY }), content_type: 'application/json', @@ -249,7 +259,7 @@ export function GraphQLEditor({ } finally { setLoading(url, false) } - }, [url, loading, headers, auth, setLoading, setError, setSchema]) + }, [url, loading, headers, auth, getVariable, setLoading, setError, setSchema]) const userTypeCount = schema ? Array.from(schema.types.values()).filter(t => t.name && !t.name.startsWith('__')).length diff --git a/src/components/HistoryPanel.tsx b/src/components/HistoryPanel.tsx index f29ccfc..03b96d5 100644 --- a/src/components/HistoryPanel.tsx +++ b/src/components/HistoryPanel.tsx @@ -108,15 +108,7 @@ import { AlertDialogTrigger, } from "@/components/ui/alert-dialog" -const methodColors: Record = { - GET: "bg-sky-500/12 text-sky-400 ring-sky-500/20", - POST: "bg-emerald-500/12 text-emerald-400 ring-emerald-500/20", - PUT: "bg-amber-500/12 text-amber-400 ring-amber-500/20", - PATCH: "bg-orange-500/12 text-orange-400 ring-orange-500/20", - DELETE: "bg-rose-500/12 text-rose-400 ring-rose-500/20", - HEAD: "bg-violet-500/12 text-violet-400 ring-violet-500/20", - OPTIONS: "bg-cyan-500/12 text-cyan-400 ring-cyan-500/20" -} +import { methodBadgeColors as methodColors } from "@/utils/methodColors" interface HistoryPanelProps { history: HistoryItem[] @@ -174,6 +166,8 @@ export const HistoryPanel = memo(function HistoryPanel({ history, onSelect, onRe