diff --git a/package.json b/package.json index 44b42b4..707f060 100644 --- a/package.json +++ b/package.json @@ -17,14 +17,21 @@ "dependencies": { "@fontsource/dm-mono": "^5.2.7", "@fontsource/dm-sans": "^5.2.8", + "@radix-ui/react-collapsible": "^1.1.12", "@tailwindcss/vite": "^4.2.4", "@tanstack/react-query": "^5.74.4", "@tanstack/react-router": "^1.120.3", + "class-variance-authority": "^0.7.1", + "clsx": "^2.1.1", "framer-motion": "^12.38.0", "lucide-react": "^1.11.0", + "luxon": "^3.7.2", "openapi-fetch": "^0.13.5", "react": "^19.2.5", "react-dom": "^19.2.5", + "react-markdown": "^10.1.0", + "remark-gfm": "^4.0.1", + "tailwind-merge": "^3.5.0", "tailwindcss": "^4.2.4", "zod": "^3.24.3" }, @@ -34,6 +41,7 @@ "@testing-library/jest-dom": "^6.6.3", "@testing-library/react": "^16.3.0", "@testing-library/user-event": "^14.6.1", + "@types/luxon": "^3.7.1", "@types/node": "^25.6.0", "@types/react": "^19.2.14", "@types/react-dom": "^19.2.3", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index b0d1020..ddb0bf9 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -16,6 +16,9 @@ importers: '@fontsource/dm-sans': specifier: ^5.2.8 version: 5.2.8 + '@radix-ui/react-collapsible': + specifier: ^1.1.12 + version: 1.1.12(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.5(react@19.2.5))(react@19.2.5) '@tailwindcss/vite': specifier: ^4.2.4 version: 4.2.4(vite@8.0.10(@types/node@25.6.0)(esbuild@0.27.7)(jiti@2.6.1)(tsx@4.21.0)) @@ -25,12 +28,21 @@ importers: '@tanstack/react-router': specifier: ^1.120.3 version: 1.168.23(react-dom@19.2.5(react@19.2.5))(react@19.2.5) + class-variance-authority: + specifier: ^0.7.1 + version: 0.7.1 + clsx: + specifier: ^2.1.1 + version: 2.1.1 framer-motion: specifier: ^12.38.0 version: 12.38.0(react-dom@19.2.5(react@19.2.5))(react@19.2.5) lucide-react: specifier: ^1.11.0 version: 1.11.0(react@19.2.5) + luxon: + specifier: ^3.7.2 + version: 3.7.2 openapi-fetch: specifier: ^0.13.5 version: 0.13.8 @@ -40,6 +52,15 @@ importers: react-dom: specifier: ^19.2.5 version: 19.2.5(react@19.2.5) + react-markdown: + specifier: ^10.1.0 + version: 10.1.0(@types/react@19.2.14)(react@19.2.5) + remark-gfm: + specifier: ^4.0.1 + version: 4.0.1 + tailwind-merge: + specifier: ^3.5.0 + version: 3.5.0 tailwindcss: specifier: ^4.2.4 version: 4.2.4 @@ -62,6 +83,9 @@ importers: '@testing-library/user-event': specifier: ^14.6.1 version: 14.6.1(@testing-library/dom@10.4.1) + '@types/luxon': + specifier: ^3.7.1 + version: 3.7.1 '@types/node': specifier: ^25.6.0 version: 25.6.0 @@ -88,7 +112,7 @@ importers: version: 8.0.10(@types/node@25.6.0)(esbuild@0.27.7)(jiti@2.6.1)(tsx@4.21.0) vitest: specifier: ^3.2.3 - version: 3.2.4(@types/node@25.6.0)(jiti@2.6.1)(jsdom@26.1.0)(lightningcss@1.32.0)(tsx@4.21.0) + version: 3.2.4(@types/debug@4.1.13)(@types/node@25.6.0)(jiti@2.6.1)(jsdom@26.1.0)(lightningcss@1.32.0)(tsx@4.21.0) packages: @@ -466,6 +490,111 @@ packages: '@oxc-project/types@0.127.0': resolution: {integrity: sha512-aIYXQBo4lCbO4z0R3FHeucQHpF46l2LbMdxRvqvuRuW2OxdnSkcng5B8+K12spgLDj93rtN3+J2Vac/TIO+ciQ==} + '@radix-ui/primitive@1.1.3': + resolution: {integrity: sha512-JTF99U/6XIjCBo0wqkU5sK10glYe27MRRsfwoiq5zzOEZLHU3A3KCMa5X/azekYRCJ0HlwI0crAXS/5dEHTzDg==} + + '@radix-ui/react-collapsible@1.1.12': + resolution: {integrity: sha512-Uu+mSh4agx2ib1uIGPP4/CKNULyajb3p92LsVXmH2EHVMTfZWpll88XJ0j4W0z3f8NK1eYl1+Mf/szHPmcHzyA==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-compose-refs@1.1.2': + resolution: {integrity: sha512-z4eqJvfiNnFMHIIvXP3CY57y2WJs5g2v3X0zm9mEJkrkNv4rDxu+sg9Jh8EkXyeqBkB7SOcboo9dMVqhyrACIg==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + '@radix-ui/react-context@1.1.2': + resolution: {integrity: sha512-jCi/QKUM2r1Ju5a3J64TH2A5SpKAgh0LpknyqdQ4m6DCV0xJ2HG1xARRwNGPQfi1SLdLWZ1OJz6F4OMBBNiGJA==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + '@radix-ui/react-id@1.1.1': + resolution: {integrity: sha512-kGkGegYIdQsOb4XjsfM97rXsiHaBwco+hFI66oO4s9LU+PLAC5oJ7khdOVFxkhsmlbpUqDAvXw11CluXP+jkHg==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + '@radix-ui/react-presence@1.1.5': + resolution: {integrity: sha512-/jfEwNDdQVBCNvjkGit4h6pMOzq8bHkopq458dPt2lMjx+eBQUohZNG9A7DtO/O5ukSbxuaNGXMjHicgwy6rQQ==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-primitive@2.1.3': + resolution: {integrity: sha512-m9gTwRkhy2lvCPe6QJp4d3G1TYEUHn/FzJUtq9MjH46an1wJU+GdoGC5VLof8RX8Ft/DlpshApkhswDLZzHIcQ==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-slot@1.2.3': + resolution: {integrity: sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + '@radix-ui/react-use-controllable-state@1.2.2': + resolution: {integrity: sha512-BjasUjixPFdS+NKkypcyyN5Pmg83Olst0+c6vGov0diwTEo6mgdqVR6hxcEgFuh4QrAs7Rc+9KuGJ9TVCj0Zzg==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + '@radix-ui/react-use-effect-event@0.0.2': + resolution: {integrity: sha512-Qp8WbZOBe+blgpuUT+lw2xheLP8q0oatc9UpmiemEICxGvFLYmHm9QowVZGHtJlGbS6A6yJ3iViad/2cVjnOiA==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + '@radix-ui/react-use-layout-effect@1.1.1': + resolution: {integrity: sha512-RbJRS4UWQFkzHTTwVymMTUv8EqYhOp8dOOviLj2ugtTiXRaRQS7GLGxZTLL1jWhMeoSCf5zmcZkqTl9IiYfXcQ==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@redocly/ajv@8.11.2': resolution: {integrity: sha512-io1JpnwtIcvojV7QKDUSIuMN/ikdOUd1ReEnUnMKGfDVridQZ31J0MmIuqwuRjWDZfmvr+Q0MqCcfHM2gTivOg==} @@ -915,12 +1044,30 @@ packages: '@types/chai@5.2.3': resolution: {integrity: sha512-Mw558oeA9fFbv65/y4mHtXDs9bPnFMZAL/jxdPFUpOHHIXX91mcgEHbS5Lahr+pwZFR8A7GQleRWeI6cGFC2UA==} + '@types/debug@4.1.13': + resolution: {integrity: sha512-KSVgmQmzMwPlmtljOomayoR89W4FynCAi3E8PPs7vmDVPe84hT+vGPKkJfThkmXs0x0jAaa9U8uW8bbfyS2fWw==} + '@types/deep-eql@4.0.2': resolution: {integrity: sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw==} + '@types/estree-jsx@1.0.5': + resolution: {integrity: sha512-52CcUVNFyfb1A2ALocQw/Dd1BQFNmSdkuC3BkZ6iqhdMfQz7JWOFRuJFloOzjk+6WijU56m9oKXFAXc7o3Towg==} + '@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/luxon@3.7.1': + resolution: {integrity: sha512-H3iskjFIAn5SlJU7OuxUmTEpebK6TKB8rxZShDslBMZJ5u9S//KM1sbdAisiSrqwLQncVjnpi2OK2J51h+4lsg==} + + '@types/mdast@4.0.4': + resolution: {integrity: sha512-kGaNbPh1k7AFzgpud/gMdvIm5xuECykRR+JnWKQno9TAXVa6WIVCGTPvYGekIDL4uwCZQSYbUxNBSb1aUo79oA==} + + '@types/ms@2.1.0': + resolution: {integrity: sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA==} + '@types/node@25.6.0': resolution: {integrity: sha512-+qIYRKdNYJwY3vRCZMdJbPLJAtGjQBudzZzdzwQYkEPQd+PJGixUL5QfvCLDaULoLv+RhT3LDkwEfKaAkgSmNQ==} @@ -932,6 +1079,15 @@ packages: '@types/react@19.2.14': resolution: {integrity: sha512-ilcTH/UniCkMdtexkoCN0bI7pMcJDvmQFPvuPvmEaYA/NSfFTAgdUSLAoVjaRJm7+6PvcM+q1zYOwS4wTYMF9w==} + '@types/unist@2.0.11': + resolution: {integrity: sha512-CmBKiL6NNo/OqgmMn95Fk9Whlp2mtvIv+KNpQKN2F4SjvrEesubTRWGYSg+BnWZOnlCaSTU1sMpsBOzgbYhnsA==} + + '@types/unist@3.0.3': + resolution: {integrity: sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q==} + + '@ungap/structured-clone@1.3.0': + resolution: {integrity: sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g==} + '@vitejs/plugin-react@6.0.1': resolution: {integrity: sha512-l9X/E3cDb+xY3SWzlG1MOGt2usfEHGMNIaegaUGFsLkb3RCn/k8/TOXBcab+OndDI4TBtktT8/9BwwW8Vi9KUQ==} engines: {node: ^20.19.0 || >=22.12.0} @@ -1020,6 +1176,9 @@ packages: babel-dead-code-elimination@1.0.12: resolution: {integrity: sha512-GERT7L2TiYcYDtYk1IpD+ASAYXjKbLTDPhBtYj7X1NuRMDTMtAx9kyBenub1Ev41lo91OHCKdmP+egTDmfQ7Ig==} + bail@2.0.2: + resolution: {integrity: sha512-0xO6mYd7JB2YesxDKplafRpsiOzPt9V02ddPCLbY1xYGPOX24NTyN50qnUxgCPcSoYMhKpAuBTjQoRZCAkUDRw==} + balanced-match@1.0.2: resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==} @@ -1051,6 +1210,9 @@ packages: caniuse-lite@1.0.30001790: resolution: {integrity: sha512-bOoxfJPyYo+ds6W0YfptaCWbFnJYjh2Y1Eow5lRv+vI2u8ganPZqNm1JwNh0t2ELQCqIWg4B3dWEusgAmsoyOw==} + ccount@2.0.1: + resolution: {integrity: sha512-eyrF0jiFpY+3drT6383f1qhkbGsLSifNAjA61IUjZjmLCWjItY6LB9ft9YhoDgwfmclB2zhu51Lc7+95b8NRAg==} + chai@5.3.3: resolution: {integrity: sha512-4zNhdJD/iOjSH0A05ea+Ke6MU5mmpQcbQsSOkgdaUMJ9zTlDTD/GYlwohmIE2u0gaxHYiVHEn1Fw9mZ/ktJWgw==} engines: {node: '>=18'} @@ -1058,6 +1220,18 @@ packages: change-case@5.4.4: resolution: {integrity: sha512-HRQyTk2/YPEkt9TnUPbOpr64Uw3KOicFWPVBb+xiHvd6eBx/qPr9xqfBFDT8P2vWsvvz4jbEkfDe71W3VyNu2w==} + 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==} + + character-entities@2.0.2: + resolution: {integrity: sha512-shx7oQ0Awen/BRIdkjkvz54PnEEI/EjwXDSIZp86/KKdbafHh1Df/RYGBhn4hbe2+uKC9FnT5UCEdyPz3ai9hQ==} + + character-reference-invalid@2.0.1: + resolution: {integrity: sha512-iBZ4F4wRbyORVsu0jPV7gXkOsGYjGHPmAyv+HiHG8gi5PtC9KI2j1+v8/tlibRvjoWX027ypmG/n0HtO5t7unw==} + check-error@2.1.3: resolution: {integrity: sha512-PAJdDJusoxnwm1VwW07VWwUN1sl7smmC3OKggvndJFadxxDRyFJBX/ggnu/KE4kQAB7a3Dp8f/YXC1FlUprWmA==} engines: {node: '>= 16'} @@ -1066,9 +1240,19 @@ packages: resolution: {integrity: sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==} engines: {node: '>= 8.10.0'} + class-variance-authority@0.7.1: + resolution: {integrity: sha512-Ka+9Trutv7G8M6WT6SeiRWz792K5qEqIGEGzXKhAE6xOWAY6pPH8U+9IY3oCMv6kqTmLsv7Xh/2w2RigkePMsg==} + + clsx@2.1.1: + resolution: {integrity: sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==} + engines: {node: '>=6'} + colorette@1.4.0: resolution: {integrity: sha512-Y2oEozpomLn7Q3HFP7dpww7AtMJplbM9lGZP6RDfHqmbeRjiwRg4n6VM6j4KLmRke85uWEI7JqF17f3pqdRA0g==} + comma-separated-tokens@2.0.3: + resolution: {integrity: sha512-Fu4hJdvzeylCfQPp9SGWidpzrMs7tTrlu6Vb8XGaRGck8QSNZJJp538Wrb60Lax4fPwR64ViY468OIUTbRlGZg==} + convert-source-map@2.0.0: resolution: {integrity: sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==} @@ -1101,6 +1285,9 @@ packages: decimal.js@10.6.0: resolution: {integrity: sha512-YpgQiITW3JXGntzdUmyUR1V812Hn8T1YVXhCu+wO3OpS4eU9l4YdD3qjyiKdV6mvV29zapkMeD390UVEf2lkUg==} + decode-named-character-reference@1.3.0: + resolution: {integrity: sha512-GtpQYB283KrPp6nRw50q3U9/VfOutZOe103qlN7BPP6Ad27xYnOIWv4lPzo8HCAL+mMZofJ9KEy30fq6MfaK6Q==} + deep-eql@5.0.2: resolution: {integrity: sha512-h5k/5U50IJJFpzfL6nO9jaaumfjO/f2NjK/oYB2Djzm4p9L+3T9qWpZqZ2hAbLPuuYq9wrU08WQyBTL5GbPk5Q==} engines: {node: '>=6'} @@ -1113,6 +1300,9 @@ packages: resolution: {integrity: sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==} engines: {node: '>=8'} + devlop@1.1.0: + resolution: {integrity: sha512-RWmIqhcFf1lRYBvNmr7qTNuyCt/7/ns2jbpp1+PalgE/rDQcBT0fioSMUpJ93irlUhC5hrg4cYqe6U+0ImW0rA==} + diff@8.0.4: resolution: {integrity: sha512-DPi0FmjiSU5EvQV0++GFDOJ9ASQUVFh5kD+OzOnYdi7n3Wpm9hWWGfB/O2blfHcMVTL5WkQXSnRiK9makhrcnw==} engines: {node: '>=0.3.1'} @@ -1146,6 +1336,13 @@ packages: resolution: {integrity: sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==} engines: {node: '>=6'} + escape-string-regexp@5.0.0: + resolution: {integrity: sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw==} + engines: {node: '>=12'} + + estree-util-is-identifier-name@3.0.0: + resolution: {integrity: sha512-hFtqIDZTIUZ9BXLb8y4pYGyk6+wekIivNVTcmvk8NoOh+VeRn5y6cEHzbURrWbfp1fIqdVipilzj+lfaadNZmg==} + estree-walker@3.0.3: resolution: {integrity: sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==} @@ -1153,6 +1350,9 @@ packages: resolution: {integrity: sha512-knvyeauYhqjOYvQ66MznSMs83wmHrCycNEN6Ao+2AeYEfxUIkuiVxdEa1qlGEPK+We3n0THiDciYSsCcgW/DoA==} engines: {node: '>=12.0.0'} + extend@3.0.2: + resolution: {integrity: sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==} + fast-deep-equal@3.1.3: resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==} @@ -1202,10 +1402,19 @@ packages: graceful-fs@4.2.11: resolution: {integrity: sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==} + hast-util-to-jsx-runtime@2.3.6: + resolution: {integrity: sha512-zl6s8LwNyo1P9uw+XJGvZtdFF1GdAkOg8ujOw+4Pyb76874fLps4ueHXDhXWdk6YHQ6OgUtinliG7RsYvCbbBg==} + + hast-util-whitespace@3.0.0: + resolution: {integrity: sha512-88JUN06ipLwsnv+dVn+OIYOvAuvBMy/Qoi6O7mQHxdPXpjy+Cd6xRkWwux7DKO+4sYILtLBRIKgsdpS2gQc7qw==} + html-encoding-sniffer@4.0.0: resolution: {integrity: sha512-Y22oTqIU4uuPgEemfz7NDJz6OeKf12Lsu+QC+s3BVpda64lTiMYCyGwg5ki4vFxkMwQdeZDl2adZoqUgdFuTgQ==} engines: {node: '>=18'} + html-url-attributes@3.0.1: + resolution: {integrity: sha512-ol6UPyBWqsrO6EJySPz2O7ZSr856WDrEzM5zMqp+FJJLGMW35cLYmmZnl0vztAZxRUoNZJFTCohfjuIJ8I4QBQ==} + http-proxy-agent@7.0.2: resolution: {integrity: sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig==} engines: {node: '>= 14'} @@ -1226,10 +1435,22 @@ packages: resolution: {integrity: sha512-Yg7+ztRkqslMAS2iFaU+Oa4KTSidr63OsFGlOrJoW981kIYO3CGCS3wA95P1mUi/IVSJkn0D479KTJpVpvFNuw==} engines: {node: '>=18'} + inline-style-parser@0.2.7: + resolution: {integrity: sha512-Nb2ctOyNR8DqQoR0OwRG95uNWIC0C1lCgf5Naz5H6Ji72KZ8OcFZLz2P5sNgwlyoJ8Yif11oMuYs5pBQa86csA==} + + is-alphabetical@2.0.1: + resolution: {integrity: sha512-FWyyY60MeTNyeSRpkM2Iry0G9hpr7/9kD40mD/cGQEuilcZYS4okz8SN2Q6rLCJ8gbCt6fN+rC+6tMGS99LaxQ==} + + is-alphanumerical@2.0.1: + resolution: {integrity: sha512-hmbYhX/9MUMF5uh7tOXyK/n0ZvWpad5caBA17GsC6vyuCqaWliRG5K1qS9inmUhEMaOBIW7/whAnSwveW/LtZw==} + is-binary-path@2.1.0: resolution: {integrity: sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==} engines: {node: '>=8'} + is-decimal@2.0.1: + resolution: {integrity: sha512-AAB9hiomQs5DXWcRB1rqsxGUstbRroFOPPVAomNk/3XHR5JyEZChOyTWe2oayKnsSsr/kcGqF+z6yuH6HHpN0A==} + is-extglob@2.1.1: resolution: {integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==} engines: {node: '>=0.10.0'} @@ -1238,10 +1459,17 @@ packages: resolution: {integrity: sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==} engines: {node: '>=0.10.0'} + is-hexadecimal@2.0.1: + resolution: {integrity: sha512-DgZQp241c8oO6cA1SbTEWiXeoxV42vlcJxgH+B3hi1AiqqKruZR3ZGF8In3fj4+/y/7rHvlOZLZtgJ/4ttYGZg==} + is-number@7.0.0: resolution: {integrity: sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==} engines: {node: '>=0.12.0'} + is-plain-obj@4.1.0: + resolution: {integrity: sha512-+Pgi+vMuUNkJyExiMBt5IlFoMyKnr5zhJ4Uspz58WOhBF5QoIZkFyNHIbBAtHwzVAgk5RtndVNsDRN61/mmDqg==} + engines: {node: '>=12'} + is-potential-custom-element-name@1.0.1: resolution: {integrity: sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ==} @@ -1363,6 +1591,9 @@ packages: resolution: {integrity: sha512-NXYBzinNrblfraPGyrbPoD19C1h9lfI/1mzgWYvXUTe414Gz/X1FD2XBZSZM7rRTrMA8JL3OtAaGifrIKhQ5yQ==} engines: {node: '>= 12.0.0'} + longest-streak@3.1.0: + resolution: {integrity: sha512-9Ri+o0JYgehTaVBBDoMqIl8GXtbWg711O3srftcHhZ0dqnETqLaoIK0x17fUw9rFSlK/0NlsKe0Ahhyl5pXE2g==} + loupe@3.2.1: resolution: {integrity: sha512-CdzqowRJCeLU72bHvWqwRBBlLcMEtIvGrlvef74kMnV2AolS9Y8xUv1I0U/MNAWMhBlKIoyuEgoJ0t/bbwHbLQ==} @@ -1377,6 +1608,10 @@ packages: peerDependencies: react: ^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0 + luxon@3.7.2: + resolution: {integrity: sha512-vtEhXh/gNjI9Yg1u4jX/0YVPMvxzHuGgCm6tC5kZyb08yjGWGnqAjGJvcXbqQR2P3MyMEFnRbpcdFS6PBcLqew==} + engines: {node: '>=12'} + lz-string@1.5.0: resolution: {integrity: sha512-h5bgJWpxJNswbU7qCrV0tIKQCaS3blPDrqKWx+QxzuzL1zGUzij9XCWLrSLsJPu5t+eWA/ycetzYAO5IOMcWAQ==} hasBin: true @@ -1384,6 +1619,138 @@ packages: magic-string@0.30.21: resolution: {integrity: sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==} + markdown-table@3.0.4: + resolution: {integrity: sha512-wiYz4+JrLyb/DqW2hkFJxP7Vd7JuTDm77fvbM8VfEQdmSMqcImWeeRbHwZjBjIFki/VaMK2BhFi7oUUZeM5bqw==} + + mdast-util-find-and-replace@3.0.2: + resolution: {integrity: sha512-Tmd1Vg/m3Xz43afeNxDIhWRtFZgM2VLyaf4vSTYwudTyeuTneoL3qtWMA5jeLyz/O1vDJmmV4QuScFCA2tBPwg==} + + mdast-util-from-markdown@2.0.3: + resolution: {integrity: sha512-W4mAWTvSlKvf8L6J+VN9yLSqQ9AOAAvHuoDAmPkz4dHf553m5gVj2ejadHJhoJmcmxEnOv6Pa8XJhpxE93kb8Q==} + + mdast-util-gfm-autolink-literal@2.0.1: + resolution: {integrity: sha512-5HVP2MKaP6L+G6YaxPNjuL0BPrq9orG3TsrZ9YXbA3vDw/ACI4MEsnoDpn6ZNm7GnZgtAcONJyPhOP8tNJQavQ==} + + mdast-util-gfm-footnote@2.1.0: + resolution: {integrity: sha512-sqpDWlsHn7Ac9GNZQMeUzPQSMzR6Wv0WKRNvQRg0KqHh02fpTz69Qc1QSseNX29bhz1ROIyNyxExfawVKTm1GQ==} + + mdast-util-gfm-strikethrough@2.0.0: + resolution: {integrity: sha512-mKKb915TF+OC5ptj5bJ7WFRPdYtuHv0yTRxK2tJvi+BDqbkiG7h7u/9SI89nRAYcmap2xHQL9D+QG/6wSrTtXg==} + + mdast-util-gfm-table@2.0.0: + resolution: {integrity: sha512-78UEvebzz/rJIxLvE7ZtDd/vIQ0RHv+3Mh5DR96p7cS7HsBhYIICDBCu8csTNWNO6tBWfqXPWekRuj2FNOGOZg==} + + mdast-util-gfm-task-list-item@2.0.0: + resolution: {integrity: sha512-IrtvNvjxC1o06taBAVJznEnkiHxLFTzgonUdy8hzFVeDun0uTjxxrRGVaNFqkU1wJR3RBPEfsxmU6jDWPofrTQ==} + + mdast-util-gfm@3.1.0: + resolution: {integrity: sha512-0ulfdQOM3ysHhCJ1p06l0b0VKlhU0wuQs3thxZQagjcjPrlFRqY215uZGHHJan9GEAXd9MbfPjFJz+qMkVR6zQ==} + + mdast-util-mdx-expression@2.0.1: + resolution: {integrity: sha512-J6f+9hUp+ldTZqKRSg7Vw5V6MqjATc+3E4gf3CFNcuZNWD8XdyI6zQ8GqH7f8169MM6P7hMBRDVGnn7oHB9kXQ==} + + mdast-util-mdx-jsx@3.2.0: + resolution: {integrity: sha512-lj/z8v0r6ZtsN/cGNNtemmmfoLAFZnjMbNyLzBafjzikOM+glrjNHPlf6lQDOTccj9n5b0PPihEBbhneMyGs1Q==} + + mdast-util-mdxjs-esm@2.0.1: + resolution: {integrity: sha512-EcmOpxsZ96CvlP03NghtH1EsLtr0n9Tm4lPUJUBccV9RwUOneqSycg19n5HGzCf+10LozMRSObtVr3ee1WoHtg==} + + mdast-util-phrasing@4.1.0: + resolution: {integrity: sha512-TqICwyvJJpBwvGAMZjj4J2n0X8QWp21b9l0o7eXyVJ25YNWYbJDVIyD1bZXE6WtV6RmKJVYmQAKWa0zWOABz2w==} + + mdast-util-to-hast@13.2.1: + resolution: {integrity: sha512-cctsq2wp5vTsLIcaymblUriiTcZd0CwWtCbLvrOzYCDZoWyMNV8sZ7krj09FSnsiJi3WVsHLM4k6Dq/yaPyCXA==} + + mdast-util-to-markdown@2.1.2: + resolution: {integrity: sha512-xj68wMTvGXVOKonmog6LwyJKrYXZPvlwabaryTjLh9LuvovB/KAH+kvi8Gjj+7rJjsFi23nkUxRQv1KqSroMqA==} + + mdast-util-to-string@4.0.0: + resolution: {integrity: sha512-0H44vDimn51F0YwvxSJSm0eCDOJTRlmN0R1yBh4HLj9wiV1Dn0QoXGbvFAWj2hSItVTlCmBF1hqKlIyUBVFLPg==} + + micromark-core-commonmark@2.0.3: + resolution: {integrity: sha512-RDBrHEMSxVFLg6xvnXmb1Ayr2WzLAWjeSATAoxwKYJV94TeNavgoIdA0a9ytzDSVzBy2YKFK+emCPOEibLeCrg==} + + micromark-extension-gfm-autolink-literal@2.1.0: + resolution: {integrity: sha512-oOg7knzhicgQ3t4QCjCWgTmfNhvQbDDnJeVu9v81r7NltNCVmhPy1fJRX27pISafdjL+SVc4d3l48Gb6pbRypw==} + + micromark-extension-gfm-footnote@2.1.0: + resolution: {integrity: sha512-/yPhxI1ntnDNsiHtzLKYnE3vf9JZ6cAisqVDauhp4CEHxlb4uoOTxOCJ+9s51bIB8U1N1FJ1RXOKTIlD5B/gqw==} + + micromark-extension-gfm-strikethrough@2.1.0: + resolution: {integrity: sha512-ADVjpOOkjz1hhkZLlBiYA9cR2Anf8F4HqZUO6e5eDcPQd0Txw5fxLzzxnEkSkfnD0wziSGiv7sYhk/ktvbf1uw==} + + micromark-extension-gfm-table@2.1.1: + resolution: {integrity: sha512-t2OU/dXXioARrC6yWfJ4hqB7rct14e8f7m0cbI5hUmDyyIlwv5vEtooptH8INkbLzOatzKuVbQmAYcbWoyz6Dg==} + + micromark-extension-gfm-tagfilter@2.0.0: + resolution: {integrity: sha512-xHlTOmuCSotIA8TW1mDIM6X2O1SiX5P9IuDtqGonFhEK0qgRI4yeC6vMxEV2dgyr2TiD+2PQ10o+cOhdVAcwfg==} + + micromark-extension-gfm-task-list-item@2.1.0: + resolution: {integrity: sha512-qIBZhqxqI6fjLDYFTBIa4eivDMnP+OZqsNwmQ3xNLE4Cxwc+zfQEfbs6tzAo2Hjq+bh6q5F+Z8/cksrLFYWQQw==} + + micromark-extension-gfm@3.0.0: + resolution: {integrity: sha512-vsKArQsicm7t0z2GugkCKtZehqUm31oeGBV/KVSorWSy8ZlNAv7ytjFhvaryUiCUJYqs+NoE6AFhpQvBTM6Q4w==} + + micromark-factory-destination@2.0.1: + resolution: {integrity: sha512-Xe6rDdJlkmbFRExpTOmRj9N3MaWmbAgdpSrBQvCFqhezUn4AHqJHbaEnfbVYYiexVSs//tqOdY/DxhjdCiJnIA==} + + micromark-factory-label@2.0.1: + resolution: {integrity: sha512-VFMekyQExqIW7xIChcXn4ok29YE3rnuyveW3wZQWWqF4Nv9Wk5rgJ99KzPvHjkmPXF93FXIbBp6YdW3t71/7Vg==} + + micromark-factory-space@2.0.1: + resolution: {integrity: sha512-zRkxjtBxxLd2Sc0d+fbnEunsTj46SWXgXciZmHq0kDYGnck/ZSGj9/wULTV95uoeYiK5hRXP2mJ98Uo4cq/LQg==} + + micromark-factory-title@2.0.1: + resolution: {integrity: sha512-5bZ+3CjhAd9eChYTHsjy6TGxpOFSKgKKJPJxr293jTbfry2KDoWkhBb6TcPVB4NmzaPhMs1Frm9AZH7OD4Cjzw==} + + micromark-factory-whitespace@2.0.1: + resolution: {integrity: sha512-Ob0nuZ3PKt/n0hORHyvoD9uZhr+Za8sFoP+OnMcnWK5lngSzALgQYKMr9RJVOWLqQYuyn6ulqGWSXdwf6F80lQ==} + + micromark-util-character@2.1.1: + resolution: {integrity: sha512-wv8tdUTJ3thSFFFJKtpYKOYiGP2+v96Hvk4Tu8KpCAsTMs6yi+nVmGh1syvSCsaxz45J6Jbw+9DD6g97+NV67Q==} + + micromark-util-chunked@2.0.1: + resolution: {integrity: sha512-QUNFEOPELfmvv+4xiNg2sRYeS/P84pTW0TCgP5zc9FpXetHY0ab7SxKyAQCNCc1eK0459uoLI1y5oO5Vc1dbhA==} + + micromark-util-classify-character@2.0.1: + resolution: {integrity: sha512-K0kHzM6afW/MbeWYWLjoHQv1sgg2Q9EccHEDzSkxiP/EaagNzCm7T/WMKZ3rjMbvIpvBiZgwR3dKMygtA4mG1Q==} + + micromark-util-combine-extensions@2.0.1: + resolution: {integrity: sha512-OnAnH8Ujmy59JcyZw8JSbK9cGpdVY44NKgSM7E9Eh7DiLS2E9RNQf0dONaGDzEG9yjEl5hcqeIsj4hfRkLH/Bg==} + + micromark-util-decode-numeric-character-reference@2.0.2: + resolution: {integrity: sha512-ccUbYk6CwVdkmCQMyr64dXz42EfHGkPQlBj5p7YVGzq8I7CtjXZJrubAYezf7Rp+bjPseiROqe7G6foFd+lEuw==} + + micromark-util-decode-string@2.0.1: + resolution: {integrity: sha512-nDV/77Fj6eH1ynwscYTOsbK7rR//Uj0bZXBwJZRfaLEJ1iGBR6kIfNmlNqaqJf649EP0F3NWNdeJi03elllNUQ==} + + micromark-util-encode@2.0.1: + resolution: {integrity: sha512-c3cVx2y4KqUnwopcO9b/SCdo2O67LwJJ/UyqGfbigahfegL9myoEFoDYZgkT7f36T0bLrM9hZTAaAyH+PCAXjw==} + + micromark-util-html-tag-name@2.0.1: + resolution: {integrity: sha512-2cNEiYDhCWKI+Gs9T0Tiysk136SnR13hhO8yW6BGNyhOC4qYFnwF1nKfD3HFAIXA5c45RrIG1ub11GiXeYd1xA==} + + micromark-util-normalize-identifier@2.0.1: + resolution: {integrity: sha512-sxPqmo70LyARJs0w2UclACPUUEqltCkJ6PhKdMIDuJ3gSf/Q+/GIe3WKl0Ijb/GyH9lOpUkRAO2wp0GVkLvS9Q==} + + micromark-util-resolve-all@2.0.1: + resolution: {integrity: sha512-VdQyxFWFT2/FGJgwQnJYbe1jjQoNTS4RjglmSjTUlpUMa95Htx9NHeYW4rGDJzbjvCsl9eLjMQwGeElsqmzcHg==} + + micromark-util-sanitize-uri@2.0.1: + resolution: {integrity: sha512-9N9IomZ/YuGGZZmQec1MbgxtlgougxTodVwDzzEouPKo3qFWvymFHWcnDi2vzV1ff6kas9ucW+o3yzJK9YB1AQ==} + + micromark-util-subtokenize@2.1.0: + resolution: {integrity: sha512-XQLu552iSctvnEcgXw6+Sx75GflAPNED1qx7eBJ+wydBb2KCbRZe+NwvIEEMM83uml1+2WSXpBAcp9IUCgCYWA==} + + micromark-util-symbol@2.0.1: + resolution: {integrity: sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q==} + + micromark-util-types@2.0.2: + resolution: {integrity: sha512-Yw0ECSpJoViF1qTU4DC6NwtC4aWGt1EkzaQB8KPPyCRR8z9TWeV0HbEFGTO+ZY1wB22zmxnJqhPyTpOVCpeHTA==} + + micromark@4.0.2: + resolution: {integrity: sha512-zpe98Q6kvavpCr1NPVSCMebCKfD7CA2NqZ+rykeNhONIJBpc1tFKt9hucLGwha3jNTNI8lHpctWJWoimVF4PfA==} + min-indent@1.0.1: resolution: {integrity: sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg==} engines: {node: '>=4'} @@ -1428,6 +1795,9 @@ packages: peerDependencies: typescript: ^5.x + parse-entities@4.0.2: + resolution: {integrity: sha512-GG2AQYWoLgL877gQIKeRPGO1xF9+eG1ujIb5soS5gPvLQ1y2o8FL90w2QWNdf9I361Mpp7726c+lj3U0qK1uGw==} + parse-json@8.3.0: resolution: {integrity: sha512-ybiGyvspI+fAoRQbIPRddCcSTV9/LsJbf0e/S85VLowVGzRmokfneg2kwVW/KU5rOXrPSbF1qAKPMgNTqqROQQ==} engines: {node: '>=18'} @@ -1470,6 +1840,9 @@ packages: resolution: {integrity: sha512-Qb1gy5OrP5+zDf2Bvnzdl3jsTf1qXVMazbvCoKhtKqVs4/YK4ozX4gKQJJVyNe+cajNPn0KoC0MC3FUmaHWEmQ==} engines: {node: ^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0} + 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'} @@ -1482,6 +1855,12 @@ packages: react-is@17.0.2: resolution: {integrity: sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==} + react-markdown@10.1.0: + resolution: {integrity: sha512-qKxVopLT/TyA6BX3Ue5NwabOsAzm0Q7kAPwq6L+wWDwisYs7R8vZ0nRXqq6rkueboxpkjvLGU9fWifiX/ZZFxQ==} + peerDependencies: + '@types/react': '>=18' + react: '>=18' + react@19.2.5: resolution: {integrity: sha512-llUJLzz1zTUBrskt2pwZgLq59AemifIftw4aB7JxOqf1HY2FDaGDxgwpAPVzHU1kdWabH7FauP4i1oEeer2WCA==} engines: {node: '>=0.10.0'} @@ -1494,6 +1873,18 @@ packages: resolution: {integrity: sha512-6tDA8g98We0zd0GvVeMT9arEOnTw9qM03L9cJXaCjrip1OO764RDBLBfrB4cwzNGDj5OA5ioymC9GkizgWJDUg==} engines: {node: '>=8'} + remark-gfm@4.0.1: + resolution: {integrity: sha512-1quofZ2RQ9EWdeN34S79+KExV1764+wCUGop5CPL1WGdD0ocPpu91lzPGbwWMECpEpd42kJGQwzRfyov9j4yNg==} + + remark-parse@11.0.0: + resolution: {integrity: sha512-FCxlKLNGknS5ba/1lmpYijMUzX2esxW5xQqjWxw2eHFfS2MSdaHVINFmhjo+qN1WhZhNimq0dZATN9pH0IDrpA==} + + remark-rehype@11.1.2: + resolution: {integrity: sha512-Dh7l57ianaEoIpzbp0PC9UKAdCSVklD8E5Rpw7ETfbTl3FqcOOgq5q2LVDhgGCkaBv7p24JXikPdvhhmHvKMsw==} + + remark-stringify@11.0.0: + resolution: {integrity: sha512-1OSmLd3awB/t8qdoEOMazZkNsfVTeY4fTsgzcQFdXNq8ToTN4ZGwrMnlda4K6smTFKD+GRV6O48i6Z4iKgPPpw==} + require-from-string@2.0.2: resolution: {integrity: sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==} engines: {node: '>=0.10.0'} @@ -1545,12 +1936,18 @@ packages: resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==} engines: {node: '>=0.10.0'} + space-separated-tokens@2.0.2: + resolution: {integrity: sha512-PEGlAwrG8yXGXRjW32fGbg66JAlOAwbObuqVoJpv/mRgoWDQfgH1wDPvtzWyUSNAXBGSk8h755YDbbcEy3SH2Q==} + stackback@0.0.2: resolution: {integrity: sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==} std-env@3.10.0: resolution: {integrity: sha512-5GS12FdOZNliM5mAOxFRg7Ir0pWz8MdpYm6AY6VPkGpbA7ZzmbzNcBJQ0GPvvyWgcY7QAhCgf9Uy89I03faLkg==} + stringify-entities@4.0.4: + resolution: {integrity: sha512-IwfBptatlO+QCJUo19AqvrPNqlVMpW9YEL2LIVY+Rpv2qsjCGxaDLNRgeGsQWJhfItebuJhsGSLjaBbNSQ+ieg==} + strip-indent@3.0.0: resolution: {integrity: sha512-laJTa3Jb+VQpaC6DseHhF7dXVqHTfJPCRDaEbid/drOhgitgYku/letMUqOXFoWV0zIIUbjpdH2t+tYj4bQMRQ==} engines: {node: '>=8'} @@ -1558,6 +1955,12 @@ packages: strip-literal@3.1.0: resolution: {integrity: sha512-8r3mkIM/2+PpjHoOtiAW8Rg3jJLHaV7xPwG+YRGrv6FP0wwk/toTpATxWYOW0BKdWwl82VT2tFYi5DlROa0Mxg==} + style-to-js@1.1.21: + resolution: {integrity: sha512-RjQetxJrrUJLQPHbLku6U/ocGtzyjbJMP9lCNK7Ag0CNh690nSH8woqWH9u16nMjYBAok+i7JO1NP2pOy8IsPQ==} + + style-to-object@1.0.14: + resolution: {integrity: sha512-LIN7rULI0jBscWQYaSswptyderlarFkjQ+t79nzty8tcIAceVomEVlLzH5VP4Cmsv6MtKhs7qaAiwlcp+Mgaxw==} + supports-color@10.2.2: resolution: {integrity: sha512-SS+jx45GF1QjgEXQx4NJZV9ImqmO2NPz5FNsIHrsDjh2YsHnawpan7SNQ1o8NuhrbHZy9AZhIoCUiCeaW/C80g==} engines: {node: '>=18'} @@ -1565,6 +1968,9 @@ packages: symbol-tree@3.2.4: resolution: {integrity: sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==} + tailwind-merge@3.5.0: + resolution: {integrity: sha512-I8K9wewnVDkL1NTGoqWmVEIlUcB9gFriAEkXkfCjX5ib8ezGxtR3xD7iZIxrfArjEsH7F1CHD4RFUtxefdqV/A==} + tailwindcss@4.2.4: resolution: {integrity: sha512-HhKppgO81FQof5m6TEnuBWCZGgfRAWbaeOaGT00KOy/Pf/j6oUihdvBpA7ltCeAvZpFhW3j0PTclkxsd4IXYDA==} @@ -1613,6 +2019,12 @@ packages: resolution: {integrity: sha512-hdF5ZgjTqgAntKkklYw0R03MG2x/bSzTtkxmIRw/sTNV8YXsCJ1tfLAX23lhxhHJlEf3CRCOCGGWw3vI3GaSPw==} engines: {node: '>=18'} + trim-lines@3.0.1: + resolution: {integrity: sha512-kRj8B+YHZCc9kQYdWfJB2/oUl9rA99qbowYYBtr4ui4mZyAQ2JpvVBd/6U2YloATfqBhBTSMhTpgBHtU0Mf3Rg==} + + trough@2.2.0: + resolution: {integrity: sha512-tmMpK00BjZiUyVyvrBK7knerNgmgvcV/KLVyuma/SC+TQN167GrMRciANTz09+k3zW8L8t60jWO1GpfkZdjTaw==} + tslib@2.8.1: resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==} @@ -1633,6 +2045,24 @@ packages: undici-types@7.19.2: resolution: {integrity: sha512-qYVnV5OEm2AW8cJMCpdV20CDyaN3g0AjDlOGf1OW4iaDEx8MwdtChUp4zu4H0VP3nDRF/8RKWH+IPp9uW0YGZg==} + unified@11.0.5: + resolution: {integrity: sha512-xKvGhPWw3k84Qjh8bI3ZeJjqnyadK+GEFtazSfZv/rKeTkTjOJho6mFqh2SM96iIcZokxiOpg78GazTSg8+KHA==} + + 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==} + unplugin@2.3.11: resolution: {integrity: sha512-5uKD0nqiYVzlmCRs01Fhs2BdkEgBS3SAVP6ndrBsuK42iC2+JHyxM05Rm9G8+5mkmRtzMZGY8Ct5+mliZxU/Ww==} engines: {node: '>=18.12.0'} @@ -1651,6 +2081,12 @@ packages: peerDependencies: react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 + vfile-message@4.0.3: + resolution: {integrity: sha512-QTHzsGd1EhbZs4AsQ20JX1rC3cOlt/IWJruk893DfLRr57lcnOeMaWG4K0JrRta4mIJZKth2Au3mM3u03/JWKw==} + + vfile@6.0.3: + resolution: {integrity: sha512-KzIbH/9tXat2u30jf+smMwFCsno4wHVdNmzFyL+T/L3UGqqk6JKfVqOFOZEpZSHADH1k40ab6NUIXZq422ov3Q==} + vite-node@3.2.4: resolution: {integrity: sha512-EbKSKh+bh1E1IFxeO0pg1n4dvoOTt0UDiXMd/qn++r98+jPO1xtJilvXldeuQ8giIB5IkpjCgMleHMNEsGH6pg==} engines: {node: ^18.0.0 || ^20.0.0 || >=22.0.0} @@ -1828,6 +2264,9 @@ packages: zod@3.25.76: resolution: {integrity: sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==} + zwitch@2.0.4: + resolution: {integrity: sha512-bXE4cR/kVZhKZX/RjPEflHaKVhUVl85noU3v6b8apfQEc1x4A+zBxjZ4lN8LqGd6WZ3dl98pY4o717VFmoPp+A==} + snapshots: '@adobe/css-tools@4.4.4': {} @@ -2135,6 +2574,90 @@ snapshots: '@oxc-project/types@0.127.0': {} + '@radix-ui/primitive@1.1.3': {} + + '@radix-ui/react-collapsible@1.1.12(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.5(react@19.2.5))(react@19.2.5)': + dependencies: + '@radix-ui/primitive': 1.1.3 + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.14)(react@19.2.5) + '@radix-ui/react-context': 1.1.2(@types/react@19.2.14)(react@19.2.5) + '@radix-ui/react-id': 1.1.1(@types/react@19.2.14)(react@19.2.5) + '@radix-ui/react-presence': 1.1.5(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.5(react@19.2.5))(react@19.2.5) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.5(react@19.2.5))(react@19.2.5) + '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.2.14)(react@19.2.5) + '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.2.14)(react@19.2.5) + react: 19.2.5 + react-dom: 19.2.5(react@19.2.5) + optionalDependencies: + '@types/react': 19.2.14 + '@types/react-dom': 19.2.3(@types/react@19.2.14) + + '@radix-ui/react-compose-refs@1.1.2(@types/react@19.2.14)(react@19.2.5)': + dependencies: + react: 19.2.5 + optionalDependencies: + '@types/react': 19.2.14 + + '@radix-ui/react-context@1.1.2(@types/react@19.2.14)(react@19.2.5)': + dependencies: + react: 19.2.5 + optionalDependencies: + '@types/react': 19.2.14 + + '@radix-ui/react-id@1.1.1(@types/react@19.2.14)(react@19.2.5)': + dependencies: + '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.2.14)(react@19.2.5) + react: 19.2.5 + optionalDependencies: + '@types/react': 19.2.14 + + '@radix-ui/react-presence@1.1.5(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.5(react@19.2.5))(react@19.2.5)': + dependencies: + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.14)(react@19.2.5) + '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.2.14)(react@19.2.5) + react: 19.2.5 + react-dom: 19.2.5(react@19.2.5) + optionalDependencies: + '@types/react': 19.2.14 + '@types/react-dom': 19.2.3(@types/react@19.2.14) + + '@radix-ui/react-primitive@2.1.3(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.5(react@19.2.5))(react@19.2.5)': + dependencies: + '@radix-ui/react-slot': 1.2.3(@types/react@19.2.14)(react@19.2.5) + react: 19.2.5 + react-dom: 19.2.5(react@19.2.5) + optionalDependencies: + '@types/react': 19.2.14 + '@types/react-dom': 19.2.3(@types/react@19.2.14) + + '@radix-ui/react-slot@1.2.3(@types/react@19.2.14)(react@19.2.5)': + dependencies: + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.14)(react@19.2.5) + react: 19.2.5 + optionalDependencies: + '@types/react': 19.2.14 + + '@radix-ui/react-use-controllable-state@1.2.2(@types/react@19.2.14)(react@19.2.5)': + dependencies: + '@radix-ui/react-use-effect-event': 0.0.2(@types/react@19.2.14)(react@19.2.5) + '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.2.14)(react@19.2.5) + react: 19.2.5 + optionalDependencies: + '@types/react': 19.2.14 + + '@radix-ui/react-use-effect-event@0.0.2(@types/react@19.2.14)(react@19.2.5)': + dependencies: + '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.2.14)(react@19.2.5) + react: 19.2.5 + optionalDependencies: + '@types/react': 19.2.14 + + '@radix-ui/react-use-layout-effect@1.1.1(@types/react@19.2.14)(react@19.2.5)': + dependencies: + react: 19.2.5 + optionalDependencies: + '@types/react': 19.2.14 + '@redocly/ajv@8.11.2': dependencies: fast-deep-equal: 3.1.3 @@ -2484,10 +3007,30 @@ snapshots: '@types/deep-eql': 4.0.2 assertion-error: 2.0.1 + '@types/debug@4.1.13': + dependencies: + '@types/ms': 2.1.0 + '@types/deep-eql@4.0.2': {} + '@types/estree-jsx@1.0.5': + dependencies: + '@types/estree': 1.0.8 + '@types/estree@1.0.8': {} + '@types/hast@3.0.4': + dependencies: + '@types/unist': 3.0.3 + + '@types/luxon@3.7.1': {} + + '@types/mdast@4.0.4': + dependencies: + '@types/unist': 3.0.3 + + '@types/ms@2.1.0': {} + '@types/node@25.6.0': dependencies: undici-types: 7.19.2 @@ -2500,6 +3043,12 @@ snapshots: dependencies: csstype: 3.2.3 + '@types/unist@2.0.11': {} + + '@types/unist@3.0.3': {} + + '@ungap/structured-clone@1.3.0': {} + '@vitejs/plugin-react@6.0.1(vite@8.0.10(@types/node@25.6.0)(esbuild@0.27.7)(jiti@2.6.1)(tsx@4.21.0))': dependencies: '@rolldown/pluginutils': 1.0.0-rc.7 @@ -2583,6 +3132,8 @@ snapshots: transitivePeerDependencies: - supports-color + bail@2.0.2: {} + balanced-match@1.0.2: {} baseline-browser-mapping@2.10.21: {} @@ -2609,6 +3160,8 @@ snapshots: caniuse-lite@1.0.30001790: {} + ccount@2.0.1: {} + chai@5.3.3: dependencies: assertion-error: 2.0.1 @@ -2619,6 +3172,14 @@ snapshots: change-case@5.4.4: {} + character-entities-html4@2.1.0: {} + + character-entities-legacy@3.0.0: {} + + character-entities@2.0.2: {} + + character-reference-invalid@2.0.1: {} + check-error@2.1.3: {} chokidar@3.6.0: @@ -2633,8 +3194,16 @@ snapshots: optionalDependencies: fsevents: 2.3.3 + class-variance-authority@0.7.1: + dependencies: + clsx: 2.1.1 + + clsx@2.1.1: {} + colorette@1.4.0: {} + comma-separated-tokens@2.0.3: {} + convert-source-map@2.0.0: {} cookie-es@3.1.1: {} @@ -2661,12 +3230,20 @@ snapshots: decimal.js@10.6.0: {} + decode-named-character-reference@1.3.0: + dependencies: + character-entities: 2.0.2 + deep-eql@5.0.2: {} dequal@2.0.3: {} detect-libc@2.1.2: {} + devlop@1.1.0: + dependencies: + dequal: 2.0.3 + diff@8.0.4: {} dom-accessibility-api@0.5.16: {} @@ -2715,12 +3292,18 @@ snapshots: escalade@3.2.0: {} + escape-string-regexp@5.0.0: {} + + estree-util-is-identifier-name@3.0.0: {} + estree-walker@3.0.3: dependencies: '@types/estree': 1.0.8 expect-type@1.3.0: {} + extend@3.0.2: {} + fast-deep-equal@3.1.3: {} fdir@6.5.0(picomatch@4.0.4): @@ -2755,10 +3338,36 @@ snapshots: graceful-fs@4.2.11: {} + hast-util-to-jsx-runtime@2.3.6: + dependencies: + '@types/estree': 1.0.8 + '@types/hast': 3.0.4 + '@types/unist': 3.0.3 + comma-separated-tokens: 2.0.3 + devlop: 1.1.0 + estree-util-is-identifier-name: 3.0.0 + hast-util-whitespace: 3.0.0 + mdast-util-mdx-expression: 2.0.1 + mdast-util-mdx-jsx: 3.2.0 + mdast-util-mdxjs-esm: 2.0.1 + property-information: 7.1.0 + space-separated-tokens: 2.0.2 + style-to-js: 1.1.21 + unist-util-position: 5.0.0 + vfile-message: 4.0.3 + transitivePeerDependencies: + - supports-color + + hast-util-whitespace@3.0.0: + dependencies: + '@types/hast': 3.0.4 + html-encoding-sniffer@4.0.0: dependencies: whatwg-encoding: 3.1.1 + html-url-attributes@3.0.1: {} + http-proxy-agent@7.0.2: dependencies: agent-base: 7.1.4 @@ -2781,18 +3390,33 @@ snapshots: index-to-position@1.2.0: {} + inline-style-parser@0.2.7: {} + + is-alphabetical@2.0.1: {} + + is-alphanumerical@2.0.1: + dependencies: + is-alphabetical: 2.0.1 + is-decimal: 2.0.1 + is-binary-path@2.1.0: dependencies: binary-extensions: 2.3.0 + is-decimal@2.0.1: {} + is-extglob@2.1.1: {} is-glob@4.0.3: dependencies: is-extglob: 2.1.1 + is-hexadecimal@2.0.1: {} + is-number@7.0.0: {} + is-plain-obj@4.1.0: {} + is-potential-custom-element-name@1.0.1: {} isbot@5.1.39: {} @@ -2891,6 +3515,8 @@ snapshots: lightningcss-win32-arm64-msvc: 1.32.0 lightningcss-win32-x64-msvc: 1.32.0 + longest-streak@3.1.0: {} + loupe@3.2.1: {} lru-cache@10.4.3: {} @@ -2903,12 +3529,360 @@ snapshots: dependencies: react: 19.2.5 + luxon@3.7.2: {} + lz-string@1.5.0: {} magic-string@0.30.21: dependencies: '@jridgewell/sourcemap-codec': 1.5.5 + markdown-table@3.0.4: {} + + mdast-util-find-and-replace@3.0.2: + dependencies: + '@types/mdast': 4.0.4 + escape-string-regexp: 5.0.0 + unist-util-is: 6.0.1 + unist-util-visit-parents: 6.0.2 + + mdast-util-from-markdown@2.0.3: + dependencies: + '@types/mdast': 4.0.4 + '@types/unist': 3.0.3 + decode-named-character-reference: 1.3.0 + devlop: 1.1.0 + mdast-util-to-string: 4.0.0 + micromark: 4.0.2 + micromark-util-decode-numeric-character-reference: 2.0.2 + micromark-util-decode-string: 2.0.1 + micromark-util-normalize-identifier: 2.0.1 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 + unist-util-stringify-position: 4.0.0 + transitivePeerDependencies: + - supports-color + + mdast-util-gfm-autolink-literal@2.0.1: + dependencies: + '@types/mdast': 4.0.4 + ccount: 2.0.1 + devlop: 1.1.0 + mdast-util-find-and-replace: 3.0.2 + micromark-util-character: 2.1.1 + + mdast-util-gfm-footnote@2.1.0: + dependencies: + '@types/mdast': 4.0.4 + devlop: 1.1.0 + mdast-util-from-markdown: 2.0.3 + mdast-util-to-markdown: 2.1.2 + micromark-util-normalize-identifier: 2.0.1 + transitivePeerDependencies: + - supports-color + + mdast-util-gfm-strikethrough@2.0.0: + dependencies: + '@types/mdast': 4.0.4 + mdast-util-from-markdown: 2.0.3 + mdast-util-to-markdown: 2.1.2 + transitivePeerDependencies: + - supports-color + + mdast-util-gfm-table@2.0.0: + dependencies: + '@types/mdast': 4.0.4 + devlop: 1.1.0 + markdown-table: 3.0.4 + mdast-util-from-markdown: 2.0.3 + mdast-util-to-markdown: 2.1.2 + transitivePeerDependencies: + - supports-color + + mdast-util-gfm-task-list-item@2.0.0: + dependencies: + '@types/mdast': 4.0.4 + devlop: 1.1.0 + mdast-util-from-markdown: 2.0.3 + mdast-util-to-markdown: 2.1.2 + transitivePeerDependencies: + - supports-color + + mdast-util-gfm@3.1.0: + dependencies: + mdast-util-from-markdown: 2.0.3 + mdast-util-gfm-autolink-literal: 2.0.1 + mdast-util-gfm-footnote: 2.1.0 + mdast-util-gfm-strikethrough: 2.0.0 + mdast-util-gfm-table: 2.0.0 + mdast-util-gfm-task-list-item: 2.0.0 + mdast-util-to-markdown: 2.1.2 + transitivePeerDependencies: + - supports-color + + mdast-util-mdx-expression@2.0.1: + dependencies: + '@types/estree-jsx': 1.0.5 + '@types/hast': 3.0.4 + '@types/mdast': 4.0.4 + devlop: 1.1.0 + mdast-util-from-markdown: 2.0.3 + mdast-util-to-markdown: 2.1.2 + transitivePeerDependencies: + - supports-color + + mdast-util-mdx-jsx@3.2.0: + dependencies: + '@types/estree-jsx': 1.0.5 + '@types/hast': 3.0.4 + '@types/mdast': 4.0.4 + '@types/unist': 3.0.3 + ccount: 2.0.1 + devlop: 1.1.0 + mdast-util-from-markdown: 2.0.3 + mdast-util-to-markdown: 2.1.2 + parse-entities: 4.0.2 + stringify-entities: 4.0.4 + unist-util-stringify-position: 4.0.0 + vfile-message: 4.0.3 + transitivePeerDependencies: + - supports-color + + mdast-util-mdxjs-esm@2.0.1: + dependencies: + '@types/estree-jsx': 1.0.5 + '@types/hast': 3.0.4 + '@types/mdast': 4.0.4 + devlop: 1.1.0 + mdast-util-from-markdown: 2.0.3 + mdast-util-to-markdown: 2.1.2 + transitivePeerDependencies: + - supports-color + + mdast-util-phrasing@4.1.0: + dependencies: + '@types/mdast': 4.0.4 + unist-util-is: 6.0.1 + + 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 + + mdast-util-to-markdown@2.1.2: + dependencies: + '@types/mdast': 4.0.4 + '@types/unist': 3.0.3 + longest-streak: 3.1.0 + mdast-util-phrasing: 4.1.0 + mdast-util-to-string: 4.0.0 + micromark-util-classify-character: 2.0.1 + micromark-util-decode-string: 2.0.1 + unist-util-visit: 5.1.0 + zwitch: 2.0.4 + + mdast-util-to-string@4.0.0: + dependencies: + '@types/mdast': 4.0.4 + + micromark-core-commonmark@2.0.3: + dependencies: + decode-named-character-reference: 1.3.0 + devlop: 1.1.0 + micromark-factory-destination: 2.0.1 + micromark-factory-label: 2.0.1 + micromark-factory-space: 2.0.1 + micromark-factory-title: 2.0.1 + micromark-factory-whitespace: 2.0.1 + micromark-util-character: 2.1.1 + micromark-util-chunked: 2.0.1 + micromark-util-classify-character: 2.0.1 + micromark-util-html-tag-name: 2.0.1 + micromark-util-normalize-identifier: 2.0.1 + micromark-util-resolve-all: 2.0.1 + micromark-util-subtokenize: 2.1.0 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 + + micromark-extension-gfm-autolink-literal@2.1.0: + dependencies: + micromark-util-character: 2.1.1 + micromark-util-sanitize-uri: 2.0.1 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 + + micromark-extension-gfm-footnote@2.1.0: + dependencies: + devlop: 1.1.0 + micromark-core-commonmark: 2.0.3 + micromark-factory-space: 2.0.1 + micromark-util-character: 2.1.1 + micromark-util-normalize-identifier: 2.0.1 + micromark-util-sanitize-uri: 2.0.1 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 + + micromark-extension-gfm-strikethrough@2.1.0: + dependencies: + devlop: 1.1.0 + micromark-util-chunked: 2.0.1 + micromark-util-classify-character: 2.0.1 + micromark-util-resolve-all: 2.0.1 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 + + micromark-extension-gfm-table@2.1.1: + dependencies: + devlop: 1.1.0 + micromark-factory-space: 2.0.1 + micromark-util-character: 2.1.1 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 + + micromark-extension-gfm-tagfilter@2.0.0: + dependencies: + micromark-util-types: 2.0.2 + + micromark-extension-gfm-task-list-item@2.1.0: + dependencies: + devlop: 1.1.0 + micromark-factory-space: 2.0.1 + micromark-util-character: 2.1.1 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 + + micromark-extension-gfm@3.0.0: + dependencies: + micromark-extension-gfm-autolink-literal: 2.1.0 + micromark-extension-gfm-footnote: 2.1.0 + micromark-extension-gfm-strikethrough: 2.1.0 + micromark-extension-gfm-table: 2.1.1 + micromark-extension-gfm-tagfilter: 2.0.0 + micromark-extension-gfm-task-list-item: 2.1.0 + micromark-util-combine-extensions: 2.0.1 + micromark-util-types: 2.0.2 + + micromark-factory-destination@2.0.1: + dependencies: + micromark-util-character: 2.1.1 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 + + micromark-factory-label@2.0.1: + dependencies: + devlop: 1.1.0 + micromark-util-character: 2.1.1 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 + + micromark-factory-space@2.0.1: + dependencies: + micromark-util-character: 2.1.1 + micromark-util-types: 2.0.2 + + micromark-factory-title@2.0.1: + dependencies: + micromark-factory-space: 2.0.1 + micromark-util-character: 2.1.1 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 + + micromark-factory-whitespace@2.0.1: + dependencies: + micromark-factory-space: 2.0.1 + micromark-util-character: 2.1.1 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 + + micromark-util-character@2.1.1: + dependencies: + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 + + micromark-util-chunked@2.0.1: + dependencies: + micromark-util-symbol: 2.0.1 + + micromark-util-classify-character@2.0.1: + dependencies: + micromark-util-character: 2.1.1 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 + + micromark-util-combine-extensions@2.0.1: + dependencies: + micromark-util-chunked: 2.0.1 + micromark-util-types: 2.0.2 + + micromark-util-decode-numeric-character-reference@2.0.2: + dependencies: + micromark-util-symbol: 2.0.1 + + micromark-util-decode-string@2.0.1: + dependencies: + decode-named-character-reference: 1.3.0 + micromark-util-character: 2.1.1 + micromark-util-decode-numeric-character-reference: 2.0.2 + micromark-util-symbol: 2.0.1 + + micromark-util-encode@2.0.1: {} + + micromark-util-html-tag-name@2.0.1: {} + + micromark-util-normalize-identifier@2.0.1: + dependencies: + micromark-util-symbol: 2.0.1 + + micromark-util-resolve-all@2.0.1: + dependencies: + micromark-util-types: 2.0.2 + + 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-subtokenize@2.1.0: + dependencies: + devlop: 1.1.0 + micromark-util-chunked: 2.0.1 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 + + micromark-util-symbol@2.0.1: {} + + micromark-util-types@2.0.2: {} + + micromark@4.0.2: + dependencies: + '@types/debug': 4.1.13 + debug: 4.4.3(supports-color@10.2.2) + decode-named-character-reference: 1.3.0 + devlop: 1.1.0 + micromark-core-commonmark: 2.0.3 + micromark-factory-space: 2.0.1 + micromark-util-character: 2.1.1 + micromark-util-chunked: 2.0.1 + micromark-util-combine-extensions: 2.0.1 + micromark-util-decode-numeric-character-reference: 2.0.2 + micromark-util-encode: 2.0.1 + micromark-util-normalize-identifier: 2.0.1 + micromark-util-resolve-all: 2.0.1 + micromark-util-sanitize-uri: 2.0.1 + micromark-util-subtokenize: 2.1.0 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 + transitivePeerDependencies: + - supports-color + min-indent@1.0.1: {} minimatch@5.1.9: @@ -2947,6 +3921,16 @@ snapshots: typescript: 6.0.3 yargs-parser: 21.1.1 + parse-entities@4.0.2: + dependencies: + '@types/unist': 2.0.11 + character-entities-legacy: 3.0.0 + character-reference-invalid: 2.0.1 + decode-named-character-reference: 1.3.0 + is-alphanumerical: 2.0.1 + is-decimal: 2.0.1 + is-hexadecimal: 2.0.1 + parse-json@8.3.0: dependencies: '@babel/code-frame': 7.29.0 @@ -2983,6 +3967,8 @@ snapshots: ansi-styles: 5.2.0 react-is: 17.0.2 + property-information@7.1.0: {} + punycode@2.3.1: {} react-dom@19.2.5(react@19.2.5): @@ -2992,6 +3978,24 @@ snapshots: react-is@17.0.2: {} + react-markdown@10.1.0(@types/react@19.2.14)(react@19.2.5): + dependencies: + '@types/hast': 3.0.4 + '@types/mdast': 4.0.4 + '@types/react': 19.2.14 + devlop: 1.1.0 + hast-util-to-jsx-runtime: 2.3.6 + html-url-attributes: 3.0.1 + mdast-util-to-hast: 13.2.1 + react: 19.2.5 + remark-parse: 11.0.0 + remark-rehype: 11.1.2 + unified: 11.0.5 + unist-util-visit: 5.1.0 + vfile: 6.0.3 + transitivePeerDependencies: + - supports-color + react@19.2.5: {} readdirp@3.6.0: @@ -3003,6 +4007,40 @@ snapshots: indent-string: 4.0.0 strip-indent: 3.0.0 + remark-gfm@4.0.1: + dependencies: + '@types/mdast': 4.0.4 + mdast-util-gfm: 3.1.0 + micromark-extension-gfm: 3.0.0 + remark-parse: 11.0.0 + remark-stringify: 11.0.0 + unified: 11.0.5 + transitivePeerDependencies: + - supports-color + + remark-parse@11.0.0: + dependencies: + '@types/mdast': 4.0.4 + mdast-util-from-markdown: 2.0.3 + micromark-util-types: 2.0.2 + unified: 11.0.5 + transitivePeerDependencies: + - supports-color + + remark-rehype@11.1.2: + dependencies: + '@types/hast': 3.0.4 + '@types/mdast': 4.0.4 + mdast-util-to-hast: 13.2.1 + unified: 11.0.5 + vfile: 6.0.3 + + remark-stringify@11.0.0: + dependencies: + '@types/mdast': 4.0.4 + mdast-util-to-markdown: 2.1.2 + unified: 11.0.5 + require-from-string@2.0.2: {} resolve-pkg-maps@1.0.0: {} @@ -3081,10 +4119,17 @@ snapshots: source-map-js@1.2.1: {} + space-separated-tokens@2.0.2: {} + stackback@0.0.2: {} std-env@3.10.0: {} + stringify-entities@4.0.4: + dependencies: + character-entities-html4: 2.1.0 + character-entities-legacy: 3.0.0 + strip-indent@3.0.0: dependencies: min-indent: 1.0.1 @@ -3093,10 +4138,20 @@ snapshots: dependencies: js-tokens: 9.0.1 + style-to-js@1.1.21: + dependencies: + style-to-object: 1.0.14 + + style-to-object@1.0.14: + dependencies: + inline-style-parser: 0.2.7 + supports-color@10.2.2: {} symbol-tree@3.2.4: {} + tailwind-merge@3.5.0: {} + tailwindcss@4.2.4: {} tapable@2.3.3: {} @@ -3134,6 +4189,10 @@ snapshots: dependencies: punycode: 2.3.1 + trim-lines@3.0.1: {} + + trough@2.2.0: {} + tslib@2.8.1: {} tsx@4.21.0: @@ -3149,6 +4208,39 @@ snapshots: undici-types@7.19.2: {} + unified@11.0.5: + dependencies: + '@types/unist': 3.0.3 + bail: 2.0.2 + devlop: 1.1.0 + extend: 3.0.2 + is-plain-obj: 4.1.0 + trough: 2.2.0 + vfile: 6.0.3 + + 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 + unplugin@2.3.11: dependencies: '@jridgewell/remapping': 2.3.5 @@ -3168,6 +4260,16 @@ snapshots: dependencies: react: 19.2.5 + 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 + vite-node@3.2.4(@types/node@25.6.0)(jiti@2.6.1)(lightningcss@1.32.0)(tsx@4.21.0): dependencies: cac: 6.7.14 @@ -3218,7 +4320,7 @@ snapshots: jiti: 2.6.1 tsx: 4.21.0 - vitest@3.2.4(@types/node@25.6.0)(jiti@2.6.1)(jsdom@26.1.0)(lightningcss@1.32.0)(tsx@4.21.0): + vitest@3.2.4(@types/debug@4.1.13)(@types/node@25.6.0)(jiti@2.6.1)(jsdom@26.1.0)(lightningcss@1.32.0)(tsx@4.21.0): dependencies: '@types/chai': 5.2.3 '@vitest/expect': 3.2.4 @@ -3244,6 +4346,7 @@ snapshots: vite-node: 3.2.4(@types/node@25.6.0)(jiti@2.6.1)(lightningcss@1.32.0)(tsx@4.21.0) why-is-node-running: 2.3.0 optionalDependencies: + '@types/debug': 4.1.13 '@types/node': 25.6.0 jsdom: 26.1.0 transitivePeerDependencies: @@ -3297,3 +4400,5 @@ snapshots: yargs-parser@21.1.1: {} zod@3.25.76: {} + + zwitch@2.0.4: {} diff --git a/src/components/conclusions/ConclusionBrowser.tsx b/src/components/conclusions/ConclusionBrowser.tsx index aca6bbc..5300763 100644 --- a/src/components/conclusions/ConclusionBrowser.tsx +++ b/src/components/conclusions/ConclusionBrowser.tsx @@ -1,21 +1,21 @@ -import { useState } from "react"; -import { Link, useParams } from "@tanstack/react-router"; -import { motion, AnimatePresence } from "framer-motion"; -import { z } from "zod"; -import { Lightbulb, Search, X, Clock, ArrowLeft, Eye, Plus, Trash2 } from "lucide-react"; import { useConclusions, - useQueryConclusions, useCreateConclusion, useDeleteConclusion, + useQueryConclusions, } from "@/api/queries"; -import { PageLoader } from "@/components/shared/LoadingSpinner"; -import { ErrorAlert } from "@/components/shared/ErrorAlert"; -import { Pagination } from "@/components/shared/Pagination"; -import { EmptyState } from "@/components/shared/EmptyState"; -import { FormModal } from "@/components/shared/FormModal"; -import { ConfirmDialog } from "@/components/shared/ConfirmDialog"; import type { components } from "@/api/schema.d.ts"; +import { ConfirmDialog } from "@/components/shared/ConfirmDialog"; +import { EmptyState } from "@/components/shared/EmptyState"; +import { ErrorAlert } from "@/components/shared/ErrorAlert"; +import { FormModal } from "@/components/shared/FormModal"; +import { PageLoader } from "@/components/shared/LoadingSpinner"; +import { Pagination } from "@/components/shared/Pagination"; +import { Link, useParams } from "@tanstack/react-router"; +import { AnimatePresence, motion } from "framer-motion"; +import { ArrowLeft, Clock, Eye, Lightbulb, Plus, Search, Trash2, X } from "lucide-react"; +import { useState } from "react"; +import { z } from "zod"; type Conclusion = components["schemas"]["Conclusion"]; @@ -58,7 +58,9 @@ export function ConclusionBrowser() { const total = (data as { total?: number } | undefined)?.total ?? 0; const displayedConclusions: Conclusion[] = activeSearch - ? Array.isArray(searchResults) ? searchResults : [] + ? Array.isArray(searchResults) + ? searchResults + : [] : conclusions; function handleSearch(e: React.SyntheticEvent) { @@ -144,7 +146,10 @@ export function ConclusionBrowser() { initial={{ opacity: 0, scale: 0.8 }} animate={{ opacity: 1, scale: 1 }} exit={{ opacity: 0, scale: 0.8 }} - onClick={() => { setActiveSearch(""); setSearchQuery(""); }} + onClick={() => { + setActiveSearch(""); + setSearchQuery(""); + }} className="px-3 py-2.5 rounded-xl text-sm transition-all" style={{ background: "var(--surface)", @@ -182,8 +187,8 @@ export function ConclusionBrowser() { className="text-xs font-mono mb-3" style={{ color: "var(--text-4)" }} > - {displayedConclusions.length} result{displayedConclusions.length !== 1 ? "s" : ""}{" "} - for “{activeSearch}” + {displayedConclusions.length} result{displayedConclusions.length !== 1 ? "s" : ""} for + “{activeSearch}” )}
@@ -201,7 +206,10 @@ export function ConclusionBrowser() { }} >
-

+

{c.content}

@@ -357,13 +397,21 @@ function CreateConclusionModal({ className="theme-input w-full text-sm px-3 py-2 rounded-lg" />
- {error &&

{error}

} + {error && ( +

+ {error} +

+ )}
@@ -371,7 +419,11 @@ function CreateConclusionModal({ type="submit" disabled={loading} className="px-3 py-1.5 text-sm rounded-lg font-medium disabled:opacity-50" - style={{ background: "var(--accent-dim)", border: "1px solid var(--accent-border)", color: "var(--accent-text)" }} + style={{ + background: "var(--accent-dim)", + border: "1px solid var(--accent-border)", + color: "var(--accent-text)", + }} > {loading ? "Creating..." : "Create"} diff --git a/src/components/dashboard/Dashboard.tsx b/src/components/dashboard/Dashboard.tsx index a9c80e3..3c86b77 100644 --- a/src/components/dashboard/Dashboard.tsx +++ b/src/components/dashboard/Dashboard.tsx @@ -1,63 +1,158 @@ -import { useState } from "react"; +import { useQueueStatus, useWorkspaces } from "@/api/queries"; +import type { components } from "@/api/schema.d.ts"; +import { ErrorAlert } from "@/components/shared/ErrorAlert"; +import { PageLoader } from "@/components/shared/LoadingSpinner"; +import { COLOR } from "@/lib/constants"; +import { formatCount } from "@/lib/utils"; import { Link } from "@tanstack/react-router"; import { motion } from "framer-motion"; -import { Boxes, Activity, LayoutDashboard } from "lucide-react"; -import { useWorkspaces, useQueueStatus } from "@/api/queries"; -import { PageLoader } from "@/components/shared/LoadingSpinner"; -import { ErrorAlert } from "@/components/shared/ErrorAlert"; +import { Activity, Boxes, ChevronRight, CircleDot, LayoutDashboard } from "lucide-react"; +import { useState } from "react"; -function QueueCard({ workspaceId }: { workspaceId: string }) { +type QueueStatus = components["schemas"]["QueueStatus"]; + +// ─── Per-workspace queue row ───────────────────────────────────────────────── + +function WorkspaceQueueRow({ workspaceId }: { workspaceId: string }) { const { data, isLoading } = useQueueStatus(workspaceId); - if (isLoading) - return ( -
- -
- ); - if (!data) return null; - - const pending = data.pending_work_units; + const pending = data?.pending_work_units ?? 0; + const active = data?.in_progress_work_units ?? 0; + const done = data?.completed_work_units ?? 0; + const total = data?.total_work_units ?? 0; + const isActive = active > 0 || pending > 0; return ( -
-
-
- -

Queue Status

-
- + + - {pending === 0 ? "Idle" : "Active"} - -
-
- {(["total_work_units", "completed_work_units", "in_progress_work_units", "pending_work_units"] as const).map((key) => ( -
- - {key.replace(/_work_units$/, "").replace(/_/g, " ")} - - - {data[key]} + + {workspaceId} + + + + + + + {isLoading ? ( + + … + + ) : ( +
+ {isActive ? ( + + + + ) : ( + + )} + + {isActive ? `${formatCount(pending + active)} pending` : "Idle"}
- ))} -
+ )} + + + {( + [ + { val: total, color: "var(--text-2)" }, + { val: done, color: COLOR.success }, + { val: active, color: COLOR.warning }, + { val: pending, color: "var(--text-3)" }, + ] as Array<{ val: number; color: string }> + ).map(({ val, color }, i) => ( + + {isLoading ? "—" : formatCount(val)} + + ))} + + ); +} + +// ─── Aggregate banner ───────────────────────────────────────────────────────── +// Each workspace row already called useQueueStatus — TanStack Query deduplicates +// the fetches so calling the same hooks here just reads from cache. + +function GlobalQueueBanner({ workspaces }: { workspaces: Array<{ id: string }> }) { + const statuses = workspaces.map((ws) => { + // biome-ignore lint/correctness/useHookAtTopLevel: intentional map over stable list + const { data } = useQueueStatus(ws.id); + return data as QueueStatus | undefined; + }); + + const totalPending = statuses.reduce((s, d) => s + (d?.pending_work_units ?? 0), 0); + const totalActive = statuses.reduce((s, d) => s + (d?.in_progress_work_units ?? 0), 0); + const totalDone = statuses.reduce((s, d) => s + (d?.completed_work_units ?? 0), 0); + const allLoaded = statuses.every((d) => d !== undefined); + + return ( +
+ {( + [ + { label: "Workspaces", value: workspaces.length, color: "var(--text-1)", always: true }, + { label: "Total done", value: totalDone, color: COLOR.success, always: false }, + { label: "Active", value: totalActive, color: COLOR.warning, always: false }, + { + label: "Pending", + value: totalPending, + color: totalPending > 0 ? COLOR.warning : "var(--text-3)", + always: false, + }, + ] as Array<{ label: string; value: number; color: string; always: boolean }> + ).map(({ label, value, color, always }) => ( +
+
+ {allLoaded || always ? formatCount(value) : "—"} +
+
+ {label} +
+
+ ))}
); } +// ─── Main dashboard ─────────────────────────────────────────────────────────── + export function Dashboard() { const [page] = useState(1); - const { data, isLoading, error } = useWorkspaces(page, 6); + const { data, isLoading, error } = useWorkspaces(page, 50); - const workspaces = (data as { items?: Array<{ id: string; created_at?: string }> } | undefined)?.items ?? []; + const workspaces = ( + data as { items?: Array<{ id: string; created_at?: string }> } | undefined + )?.items ?? []; const total = (data as { total?: number } | undefined)?.total ?? 0; return ( @@ -68,10 +163,29 @@ export function Dashboard() { className="mb-8" >
- -

+ +

Dashboard

+ {total > 0 && ( + + {total} workspace{total !== 1 ? "s" : ""} + + )}

Overview of your Honcho instance @@ -81,98 +195,90 @@ export function Dashboard() { {isLoading && } - {!isLoading && ( + {!isLoading && workspaces.length > 0 && (

- {/* Stat row */} + {/* Aggregate stat row */} - {[ - { label: "Workspaces", value: total, icon: Boxes }, - ].map((stat) => { - const Icon = stat.icon; - return ( -
- -
- {stat.value} -
-
- {stat.label} -
-
- ); - })} +
-
- {/* Workspace list */} - +
-
-
- -

- Recent Workspaces -

-
- - View all → - -
+ +

+ Queue Status +

+ + all workspaces · updates every 10s + +
- {workspaces.length === 0 ? ( -

No workspaces found.

- ) : ( -
+
+ + + + {["Workspace", "Status", "Total", "Done", "Active", "Pending"].map((h) => ( + + ))} + + + {workspaces.map((ws) => ( - - - {ws.id} - - - → - - + ))} - - )} - + +
+ {h} +
+
+ - {/* Queue for first workspace */} - {workspaces[0] && ( - workspaces.length && ( +

+ Showing {workspaces.length} of {total} workspaces.{" "} + - - - )} -

+ View all → + +

+ )} +
+ )} + + {!isLoading && workspaces.length === 0 && ( +
+ +

+ No workspaces found. +

)}
diff --git a/src/components/peers/PeerDetail.tsx b/src/components/peers/PeerDetail.tsx index f94bf2e..5760086 100644 --- a/src/components/peers/PeerDetail.tsx +++ b/src/components/peers/PeerDetail.tsx @@ -1,19 +1,21 @@ -import { useState } from "react"; -import { Link, useNavigate, useParams } from "@tanstack/react-router"; -import { motion, AnimatePresence } from "framer-motion"; -import { User, MessageCircle, Search, Save, X } from "lucide-react"; import { usePeer, usePeerCard, usePeerContext, usePeerRepresentation, - useSetPeerCard, useSearchPeer, + useSetPeerCard, } from "@/api/queries"; -import { PageLoader } from "@/components/shared/LoadingSpinner"; +import { Badge } from "@/components/shared/Badge"; import { ErrorAlert } from "@/components/shared/ErrorAlert"; import { JsonViewer } from "@/components/shared/JsonViewer"; -import { Badge } from "@/components/shared/Badge"; +import { PageLoader } from "@/components/shared/LoadingSpinner"; +import { MarkdownRenderer } from "@/components/shared/MarkdownRenderer"; +import { PeerCardViewer } from "@/components/shared/PeerCardViewer"; +import { Link, useNavigate, useParams } from "@tanstack/react-router"; +import { AnimatePresence, motion } from "framer-motion"; +import { MessageCircle, Save, Search, User, X } from "lucide-react"; +import { useState } from "react"; type Tab = "context" | "card" | "representation" | "metadata" | "search"; @@ -28,7 +30,10 @@ export function PeerDetail() { const { data: peer, isLoading, error } = usePeer(workspaceId, peerId); const { data: card, isLoading: cardLoading } = usePeerCard(workspaceId, peerId); const { data: context, isLoading: contextLoading } = usePeerContext(workspaceId, peerId); - const { data: representation, isLoading: repLoading } = usePeerRepresentation(workspaceId, peerId); + const { data: representation, isLoading: repLoading } = usePeerRepresentation( + workspaceId, + peerId, + ); const setPeerCard = useSetPeerCard(workspaceId, peerId); const searchPeer = useSearchPeer(workspaceId, peerId); @@ -36,12 +41,11 @@ export function PeerDetail() { const [cardDraft, setCardDraft] = useState(null); const [searchQuery, setSearchQuery] = useState(""); - const cardLines: string[] = - Array.isArray((card as { peer_card?: unknown })?.peer_card) - ? ((card as { peer_card: string[] }).peer_card) - : typeof card === "string" - ? [card] - : []; + const cardLines: string[] = Array.isArray((card as { peer_card?: unknown })?.peer_card) + ? (card as { peer_card: string[] }).peer_card + : typeof card === "string" + ? [card] + : []; const tabs: Array<{ id: Tab; label: string }> = [ { id: "context", label: "Context" }, @@ -55,13 +59,23 @@ export function PeerDetail() {
- Workspaces + + Workspaces + / - + {workspaceId} / - + Peers
@@ -77,7 +91,9 @@ export function PeerDetail() { {peerId}
-

Peer identity & memory

+

+ Peer identity & memory +

@@ -167,7 +199,11 @@ export function PeerDetail() { }} disabled={setPeerCard.isPending} className="flex items-center gap-1 text-xs px-2 py-1 rounded-lg disabled:opacity-50" - style={{ background: "var(--accent-dim)", border: "1px solid var(--accent-border)", color: "var(--accent-text)" }} + style={{ + background: "var(--accent-dim)", + border: "1px solid var(--accent-border)", + color: "var(--accent-text)", + }} > Save @@ -175,7 +211,11 @@ export function PeerDetail() { @@ -196,34 +236,32 @@ export function PeerDetail() { /> ) : ( - {cardLines.length > 0 ? ( -

- {cardLines.join("\n")} -

- ) : ( -

No card set.

- )} +
)} - ) - )} + ))} - {tab === "representation" && ( - repLoading ? : ( + {tab === "representation" && + (repLoading ? ( + + ) : ( <> -

Memory Representation

- {representation && typeof (representation as { representation?: unknown }).representation === "string" ? ( -

- {(representation as { representation: string }).representation} -

+

+ Memory Representation +

+ {representation && + typeof (representation as { representation?: unknown }).representation === + "string" ? ( + ) : ( )} - ) - )} + ))} {tab === "search" && ( <> @@ -249,21 +287,44 @@ export function PeerDetail() { type="submit" disabled={searchPeer.isPending} className="px-3 py-2 text-sm rounded-lg font-medium" - style={{ background: "var(--accent-dim)", border: "1px solid var(--accent-border)", color: "var(--accent-text)" }} + style={{ + background: "var(--accent-dim)", + border: "1px solid var(--accent-border)", + color: "var(--accent-text)", + }} > {searchPeer.isPending ? "…" : "Search"} {searchPeer.data && (
- {(searchPeer.data as Array<{ id: string; content: string; peer_id?: string; created_at?: string }>).length === 0 ? ( -

No results.

+ {( + searchPeer.data as Array<{ + id: string; + content: string; + peer_id?: string; + created_at?: string; + }> + ).length === 0 ? ( +

+ No results. +

) : ( - (searchPeer.data as Array<{ id: string; content: string; peer_id?: string; created_at?: string }>).map((r) => ( + ( + searchPeer.data as Array<{ + id: string; + content: string; + peer_id?: string; + created_at?: string; + }> + ).map((r) => (
{r.peer_id ?? peerId} @@ -273,7 +334,12 @@ export function PeerDetail() { )}
-

{r.content}

+

+ {r.content} +

)) )} @@ -284,7 +350,9 @@ export function PeerDetail() { {tab === "metadata" && ( <> -

Peer Metadata

+

+ Peer Metadata +

)} diff --git a/src/components/peers/PeerList.tsx b/src/components/peers/PeerList.tsx index 7dc7fb7..e280ae8 100644 --- a/src/components/peers/PeerList.tsx +++ b/src/components/peers/PeerList.tsx @@ -1,13 +1,13 @@ -import { useState } from "react"; -import { Link, useNavigate, useParams } from "@tanstack/react-router"; -import { motion, type Variants } from "framer-motion"; -import { Users, ChevronRight, Clock, ArrowLeft } from "lucide-react"; import { usePeers } from "@/api/queries"; -import { PageLoader } from "@/components/shared/LoadingSpinner"; -import { ErrorAlert } from "@/components/shared/ErrorAlert"; -import { Pagination } from "@/components/shared/Pagination"; -import { EmptyState } from "@/components/shared/EmptyState"; import type { components } from "@/api/schema.d.ts"; +import { EmptyState } from "@/components/shared/EmptyState"; +import { ErrorAlert } from "@/components/shared/ErrorAlert"; +import { PageLoader } from "@/components/shared/LoadingSpinner"; +import { Pagination } from "@/components/shared/Pagination"; +import { Link, useNavigate, useParams } from "@tanstack/react-router"; +import { type Variants, motion } from "framer-motion"; +import { ArrowLeft, ChevronRight, Clock, Eye, Users } from "lucide-react"; +import { useState } from "react"; type Peer = components["schemas"]["Peer"]; @@ -32,11 +32,7 @@ export function PeerList() { return (
- +
- {peer.created_at && ( -
- -

- {new Date(peer.created_at).toLocaleString()} -

-
- )} +
+ {(peer.configuration as { observe_me?: boolean } | null)?.observe_me && ( +
+ + + observed + +
+ )} + {peer.created_at && ( +
+ +

+ {new Date(peer.created_at).toLocaleString()} +

+
+ )} +
))} diff --git a/src/components/sessions/SessionDetail.tsx b/src/components/sessions/SessionDetail.tsx index a2896b3..39858d4 100644 --- a/src/components/sessions/SessionDetail.tsx +++ b/src/components/sessions/SessionDetail.tsx @@ -1,27 +1,29 @@ -import { useState } from "react"; -import { Link, useParams, useNavigate } from "@tanstack/react-router"; -import { motion, AnimatePresence } from "framer-motion"; -import { MessageSquare, Trash2, Copy, Search, Users, X } from "lucide-react"; import { - useSessionMessages, - useSessionSummaries, - useSessionContext, - useSessionPeers, - useDeleteSession, - useCloneSession, - useSearchSession, - useRemovePeersFromSession, - usePeers, useAddPeersToSession, + useCloneSession, + useDeleteSession, + usePeers, + useRemovePeersFromSession, + useSearchSession, + useSessionContext, + useSessionMessages, + useSessionPeers, + useSessionSummaries, } from "@/api/queries"; +import type { components } from "@/api/schema.d.ts"; +import { Badge } from "@/components/shared/Badge"; +import { ConfirmDialog } from "@/components/shared/ConfirmDialog"; +import { JsonViewer } from "@/components/shared/JsonViewer"; import { PageLoader } from "@/components/shared/LoadingSpinner"; import { Pagination } from "@/components/shared/Pagination"; -import { Badge } from "@/components/shared/Badge"; -import { JsonViewer } from "@/components/shared/JsonViewer"; -import { ConfirmDialog } from "@/components/shared/ConfirmDialog"; -import type { components } from "@/api/schema.d.ts"; +import { Link, useNavigate, useParams } from "@tanstack/react-router"; +import { AnimatePresence, motion } from "framer-motion"; +import { AlignLeft, Clock, Copy, MessageSquare, Search, Trash2, Users, X } from "lucide-react"; +import { useState } from "react"; type Message = components["schemas"]["Message"]; +type SessionSummaries = components["schemas"]["SessionSummaries"]; +type Summary = components["schemas"]["Summary"]; type Tab = "messages" | "summaries" | "context" | "peers"; export function SessionDetail() { @@ -37,8 +39,15 @@ export function SessionDetail() { const [searchActive, setSearchActive] = useState(false); const [confirmDelete, setConfirmDelete] = useState(false); - const { data: msgData, isLoading: msgsLoading } = useSessionMessages(workspaceId, sessionId, page); - const { data: summaries, isLoading: summariesLoading } = useSessionSummaries(workspaceId, sessionId); + const { data: msgData, isLoading: msgsLoading } = useSessionMessages( + workspaceId, + sessionId, + page, + ); + const { data: summaries, isLoading: summariesLoading } = useSessionSummaries( + workspaceId, + sessionId, + ); const { data: context, isLoading: contextLoading } = useSessionContext(workspaceId, sessionId); const { data: sessionPeers, isLoading: peersLoading } = useSessionPeers(workspaceId, sessionId); const { data: allPeers } = usePeers(workspaceId, 1, 100); @@ -52,11 +61,11 @@ export function SessionDetail() { const messages: Message[] = (msgData as { items?: Message[] } | undefined)?.items ?? []; const totalPages = (msgData as { pages?: number } | undefined)?.pages ?? 1; - const memberPeerIds = new Set( - (sessionPeers as Array<{ id?: string; peer_id?: string }> | undefined)?.map( - (p) => p.id ?? p.peer_id ?? "", - ) ?? [], - ); + const sessionPeerItems = ( + sessionPeers as { items?: Array<{ id?: string; peer_id?: string }> } | undefined + )?.items ?? []; + + const memberPeerIds = new Set(sessionPeerItems.map((p) => p.id ?? p.peer_id ?? "")); const availablePeers = ( (allPeers as { items?: Array<{ id: string }> } | undefined)?.items ?? [] @@ -71,7 +80,10 @@ export function SessionDetail() { const handleDelete = async () => { await deleteSession.mutateAsync(sessionId); - navigate({ to: "/workspaces/$workspaceId/sessions" as never, params: { workspaceId } as never }); + navigate({ + to: "/workspaces/$workspaceId/sessions" as never, + params: { workspaceId } as never, + }); }; const handleClone = async () => { @@ -88,19 +100,34 @@ export function SessionDetail() {
- + {workspaceId} / - + Sessions
- -

+ +

{sessionId}

@@ -141,7 +168,9 @@ export function SessionDetail() {
-

Session detail

+

+ Session detail +

{/* Inline search bar */} @@ -171,19 +200,32 @@ export function SessionDetail() { type="submit" disabled={searchSession.isPending} className="px-3 py-2 text-sm rounded-lg font-medium" - style={{ background: "var(--accent-dim)", border: "1px solid var(--accent-border)", color: "var(--accent-text)" }} + style={{ + background: "var(--accent-dim)", + border: "1px solid var(--accent-border)", + color: "var(--accent-text)", + }} > {searchSession.isPending ? "…" : "Search"} {searchSession.data && (
- {(searchSession.data as Array<{ id: string; content: string; peer_id?: string }>).length === 0 ? ( -

No results.

+ {(searchSession.data as Array<{ id: string; content: string; peer_id?: string }>) + .length === 0 ? ( +

+ No results. +

) : ( - (searchSession.data as Array<{ id: string; content: string; peer_id?: string }>).map((r) => ( -
- {r.peer_id && {r.peer_id}} + ( + searchSession.data as Array<{ id: string; content: string; peer_id?: string }> + ).map((r) => ( +
+ {r.peer_id && {r.peer_id}}

{r.content}

)) @@ -227,15 +269,23 @@ export function SessionDetail() { transition={{ duration: 0.2 }} className="rounded-xl p-5 theme-card" > - {tab === "messages" && ( - msgsLoading ? : ( + {tab === "messages" && + (msgsLoading ? ( + + ) : (
{messages.length === 0 ? ( -

No messages.

+

+ No messages. +

) : (
{messages.map((msg) => ( -
+
{msg.peer_id ?? "system"} @@ -251,7 +301,10 @@ export function SessionDetail() { )}
-

+

{msg.content}

@@ -260,45 +313,45 @@ export function SessionDetail() { )}
- ) - )} + ))} - {tab === "summaries" && ( - summariesLoading ? : ( - <> -

Session Summaries

- - - ) - )} + {tab === "summaries" && + (summariesLoading ? : )} - {tab === "context" && ( - contextLoading ? : ( + {tab === "context" && + (contextLoading ? ( + + ) : ( <> -

Session Context

+

+ Session Context +

{typeof context === "string" ? ( -

+

{context}

) : ( )} - ) - )} + ))} - {tab === "peers" && ( - peersLoading ? : ( + {tab === "peers" && + (peersLoading ? ( + + ) : ( | undefined} + members={sessionPeerItems} available={availablePeers} onRemove={(id) => removePeers.mutate([id])} onAdd={(id) => addPeers.mutate({ [id]: {} })} removing={removePeers.isPending} adding={addPeers.isPending} /> - ) - )} + ))}
@@ -340,7 +393,9 @@ function SessionPeersTab({ Session members ({list.length}) {list.length === 0 ? ( -

No peers in this session.

+

+ No peers in this session. +

) : (
{list.map((p) => { @@ -351,7 +406,9 @@ function SessionPeersTab({ className="flex items-center justify-between py-1.5 px-3 rounded-lg" style={{ background: "var(--surface)", border: "1px solid var(--border)" }} > - {id} + + {id} +
); } + +function SummaryCard({ label, summary }: { label: string; summary: Summary }) { + return ( +
+
+
+ + + {label} + +
+
+ {summary.token_count != null && ( + + {summary.token_count} tok + + )} + {summary.created_at && ( +
+ + + {new Date(summary.created_at).toLocaleString()} + +
+ )} +
+
+

+ {summary.content} +

+
+ ); +} + +function SummariesDisplay({ summaries }: { summaries: unknown }) { + const data = summaries as SessionSummaries | null | undefined; + + if (!data || (!data.short_summary && !data.long_summary)) { + return ( + <> +

+ Session Summaries +

+

+ No summaries available yet. +

+ + ); + } + + return ( + <> +

+ Session Summaries +

+
+ {data.short_summary && } + {data.long_summary && } +
+ + ); +} diff --git a/src/components/sessions/SessionList.tsx b/src/components/sessions/SessionList.tsx index 27614f0..8e3972c 100644 --- a/src/components/sessions/SessionList.tsx +++ b/src/components/sessions/SessionList.tsx @@ -1,13 +1,13 @@ -import { useState } from "react"; -import { Link, useNavigate, useParams } from "@tanstack/react-router"; -import { motion, type Variants } from "framer-motion"; -import { MessageSquare, ChevronRight, Clock, CircleDot, ArrowLeft } from "lucide-react"; import { useSessions } from "@/api/queries"; -import { PageLoader } from "@/components/shared/LoadingSpinner"; -import { ErrorAlert } from "@/components/shared/ErrorAlert"; -import { Pagination } from "@/components/shared/Pagination"; -import { EmptyState } from "@/components/shared/EmptyState"; import type { components } from "@/api/schema.d.ts"; +import { EmptyState } from "@/components/shared/EmptyState"; +import { ErrorAlert } from "@/components/shared/ErrorAlert"; +import { PageLoader } from "@/components/shared/LoadingSpinner"; +import { Pagination } from "@/components/shared/Pagination"; +import { Link, useNavigate, useParams } from "@tanstack/react-router"; +import { type Variants, motion } from "framer-motion"; +import { ArrowLeft, ChevronRight, CircleDot, Clock, MessageSquare } from "lucide-react"; +import { useState } from "react"; type Session = components["schemas"]["Session"]; @@ -32,11 +32,7 @@ export function SessionList() { return (
- +
- + {session.id}
@@ -113,11 +112,17 @@ export function SessionList() {
- + - Active + + Active +
)}
- {session.created_at && ( -
- -

- {new Date(session.created_at).toLocaleString()} -

-
- )} +
+ {session.created_at && ( +
+ +

+ {new Date(session.created_at).toLocaleString()} +

+
+ )} + {(session.metadata as Record | null)?.source && ( + + {(session.metadata as Record).source} + + )} +
))}
diff --git a/src/components/shared/MarkdownRenderer.tsx b/src/components/shared/MarkdownRenderer.tsx new file mode 100644 index 0000000..7ee4ef0 --- /dev/null +++ b/src/components/shared/MarkdownRenderer.tsx @@ -0,0 +1,149 @@ +import { TimestampChip } from "@/components/shared/TimestampChip"; +import ReactMarkdown, { type Components } from "react-markdown"; +import remarkGfm from "remark-gfm"; + +// [2026-04-24 18:18:48] rest of line +const TIMESTAMP_LINE_RE = /^\[(\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2})\]\s*(.*)/s; + +function flattenChildren(children: React.ReactNode): string { + if (typeof children === "string") return children; + if (Array.isArray(children)) return children.map(flattenChildren).join(""); + if (children && typeof children === "object" && "props" in (children as object)) { + return flattenChildren((children as { props: { children?: React.ReactNode } }).props.children); + } + return ""; +} + +/** Paragraph renderer: detects blocks of timestamp lines and renders them specially. */ +function Paragraph({ children }: { children?: React.ReactNode }) { + const text = flattenChildren(children); + const lines = text.split("\n").filter(Boolean); + + // If every line in this paragraph matches the timestamp pattern, render as timestamp list + if (lines.length > 0 && lines.every((l) => TIMESTAMP_LINE_RE.test(l))) { + return ( +
+ {lines.map((line, i) => { + const m = TIMESTAMP_LINE_RE.exec(line)!; + return ( +
+ + + {m[2]} + +
+ ); + })} +
+ ); + } + + return ( +

+ {children} +

+ ); +} + +const COMPONENTS: Components = { + h1: ({ children }) => ( +

+ {children} +

+ ), + h2: ({ children }) => ( +

+ {children} +

+ ), + h3: ({ children }) => ( +

+ {children} +

+ ), + p: Paragraph, + ul: ({ children }) => ( +
    + {children} +
+ ), + ol: ({ children }) => ( +
    + {children} +
+ ), + li: ({ children }) =>
  • {children}
  • , + code: ({ children, className }) => { + const isBlock = className?.includes("language-"); + if (isBlock) { + return ( +
    +					{children}
    +				
    + ); + } + return ( + + {children} + + ); + }, + blockquote: ({ children }) => ( +
    + {children} +
    + ), + hr: () => ( +
    + ), + strong: ({ children }) => ( + + {children} + + ), +}; + +interface Props { + content: string; +} + +export function MarkdownRenderer({ content }: Props) { + return ( + + {content} + + ); +} diff --git a/src/components/shared/PeerCardViewer.tsx b/src/components/shared/PeerCardViewer.tsx new file mode 100644 index 0000000..79c7870 --- /dev/null +++ b/src/components/shared/PeerCardViewer.tsx @@ -0,0 +1,239 @@ +import { Collapsible, CollapsibleContent, CollapsibleTrigger } from "@/components/ui/collapsible"; +import { cn } from "@/lib/utils"; +import { ChevronDown } from "lucide-react"; +import { useState } from "react"; + +interface Props { + lines: string[]; +} + +// ALL_CAPS_WORD: — no lowercase letters in key +const CAPS_RE = /^([A-Z][A-Z0-9_]+):\s*([\s\S]*)/; +// Title Case Word: — starts capital, must contain at least one lowercase +const TITLE_RE = /^([A-Z][a-zA-Z0-9][a-zA-Z0-9 ]*):\s*([\s\S]*)/; + +type ParsedLine = + | { kind: "fact"; text: string } + | { kind: "caps"; key: string; value: string } + | { kind: "title"; key: string; value: string }; + +const PALETTE: Array<{ bg: string; text: string; border: string; dot: string }> = [ + { bg: "rgba(52,211,153,0.08)", text: "#34d399", border: "rgba(52,211,153,0.25)", dot: "#34d399" }, + { bg: "rgba(245,158,11,0.08)", text: "#f59e0b", border: "rgba(245,158,11,0.25)", dot: "#f59e0b" }, + { bg: "rgba(14,165,233,0.08)", text: "#38bdf8", border: "rgba(14,165,233,0.25)", dot: "#38bdf8" }, + { bg: "rgba(236,72,153,0.08)", text: "#f472b6", border: "rgba(236,72,153,0.25)", dot: "#f472b6" }, + { bg: "rgba(168,85,247,0.08)", text: "#c084fc", border: "rgba(168,85,247,0.25)", dot: "#c084fc" }, + { bg: "rgba(239,68,68,0.08)", text: "#f87171", border: "rgba(239,68,68,0.25)", dot: "#f87171" }, + { bg: "rgba(34,197,94,0.08)", text: "#4ade80", border: "rgba(34,197,94,0.25)", dot: "#4ade80" }, + { bg: "rgba(251,146,60,0.08)", text: "#fb923c", border: "rgba(251,146,60,0.25)", dot: "#fb923c" }, +]; + +function hashPalette(word: string): number { + let h = 5381; + for (let i = 0; i < word.length; i++) h = ((h * 33) ^ word.charCodeAt(i)) >>> 0; + return h % PALETTE.length; +} + +function toLabel(key: string): string { + const s = key.toLowerCase().replace(/_/g, " "); + return s.charAt(0).toUpperCase() + s.slice(1); +} + +function parseLine(line: string): ParsedLine { + const caps = CAPS_RE.exec(line); + if (caps) return { kind: "caps", key: caps[1], value: caps[2].trim() }; + const title = TITLE_RE.exec(line); + if (title && /[a-z]/.test(title[1])) { + return { kind: "title", key: title[1], value: title[2].trim() }; + } + return { kind: "fact", text: line }; +} + +interface CapsGroup { + key: string; + items: string[]; +} + +interface Parsed { + titlePairs: Array<{ key: string; value: string }>; + facts: string[]; + capsGroups: CapsGroup[]; +} + +function parse(lines: string[]): Parsed { + const titlePairs: Array<{ key: string; value: string }> = []; + const facts: string[] = []; + const capsMap = new Map(); + const capsOrder: string[] = []; + + for (const line of lines) { + const p = parseLine(line); + if (p.kind === "title") { + titlePairs.push({ key: p.key, value: p.value }); + } else if (p.kind === "caps") { + if (!capsMap.has(p.key)) { + capsMap.set(p.key, []); + capsOrder.push(p.key); + } + capsMap.get(p.key)!.push(p.value); + } else { + facts.push(p.text); + } + } + + return { + titlePairs, + facts, + capsGroups: capsOrder.map((k) => ({ key: k, items: capsMap.get(k)! })), + }; +} + +// ─── Metadata table (Title Case: pairs) ────────────────────────────────────── + +function MetadataCard({ pairs }: { pairs: Array<{ key: string; value: string }> }) { + if (pairs.length === 0) return null; + return ( +
    +
    + {pairs.map(({ key, value }, i) => ( +
    +
    + {key} +
    +
    + {value || } +
    +
    + ))} +
    +
    + ); +} + +// ─── Collapsible section (ALL_CAPS: groups + Facts) ─────────────────────────── + +interface SectionStyle { + bg: string; + text: string; + border: string; +} + +const FACTS_STYLE: SectionStyle = { + bg: "rgba(99,102,241,0.08)", + text: "#a5b4fc", + border: "rgba(99,102,241,0.2)", +}; + +function CollapsibleSection({ + label, + count, + style, + children, +}: { + label: string; + count: number; + style: SectionStyle; + children: React.ReactNode; +}) { + const [open, setOpen] = useState(true); + + return ( + +
    + + + + {children} +
    +
    + ); +} + +function ItemList({ items }: { items: string[] }) { + return ( +
      + {items.map((item, i) => ( +
    • 0 ? "1px solid var(--border)" : "1px solid var(--border)", + }} + > + {item} +
    • + ))} +
    + ); +} + +// ─── Export ─────────────────────────────────────────────────────────────────── + +export function PeerCardViewer({ lines }: Props) { + if (!lines || lines.length === 0) { + return ( +

    + No card set. +

    + ); + } + + const { titlePairs, facts, capsGroups } = parse(lines); + + return ( +
    + + + {facts.length > 0 && ( + + + + )} + + {capsGroups.map((g) => { + const p = PALETTE[hashPalette(g.key)]; + return ( + + + + ); + })} +
    + ); +} diff --git a/src/components/shared/TimestampChip.tsx b/src/components/shared/TimestampChip.tsx new file mode 100644 index 0000000..171d934 --- /dev/null +++ b/src/components/shared/TimestampChip.tsx @@ -0,0 +1,62 @@ +import { cn } from "@/lib/utils"; +import { DateTime } from "luxon"; + +interface Props { + /** ISO-like string: "2026-04-24 18:18:48" or any Luxon-parseable string */ + value: string; + className?: string; +} + +function parseTimestamp(value: string): DateTime { + // Try bracket format first: "2026-04-24 18:18:48" + const dt = DateTime.fromFormat(value, "yyyy-MM-dd HH:mm:ss"); + if (dt.isValid) return dt; + // Fall back to ISO + return DateTime.fromISO(value); +} + +function formatDisplay(dt: DateTime): string { + const now = DateTime.now(); + const diffMs = Math.abs(now.diff(dt, "milliseconds").milliseconds); + + // Same calendar day → show time only + if (dt.hasSame(now, "day")) return dt.toFormat("HH:mm:ss"); + // Within the past year → month + day + time + if (diffMs < 365 * 24 * 3600 * 1000) return dt.toFormat("MMM d HH:mm"); + // Older → full date + return dt.toFormat("yyyy-MM-dd HH:mm"); +} + +export function TimestampChip({ value, className }: Props) { + const dt = parseTimestamp(value); + if (!dt.isValid) { + return ( + + {value} + + ); + } + + const display = formatDisplay(dt); + const full = dt.toFormat("yyyy-MM-dd HH:mm:ss ZZZZ"); + const relative = dt.toRelative() ?? ""; + + return ( + + ); +} diff --git a/src/components/ui/badge.tsx b/src/components/ui/badge.tsx new file mode 100644 index 0000000..9745913 --- /dev/null +++ b/src/components/ui/badge.tsx @@ -0,0 +1,29 @@ +import { cn } from "@/lib/utils"; +import { type VariantProps, cva } from "class-variance-authority"; +import type { HTMLAttributes } from "react"; + +const badgeVariants = cva( + "inline-flex items-center gap-1 rounded-md border px-2 py-0.5 text-xs font-medium transition-colors", + { + variants: { + variant: { + default: "border-transparent bg-primary/15 text-primary", + secondary: "border-transparent bg-secondary text-muted-foreground", + outline: "border-border text-muted-foreground", + destructive: "border-transparent bg-red-500/15 text-red-400", + success: "border-transparent bg-emerald-500/15 text-emerald-400", + warning: "border-transparent bg-amber-500/15 text-amber-400", + blue: "border-transparent bg-sky-500/15 text-sky-400", + }, + }, + defaultVariants: { variant: "default" }, + }, +); + +interface BadgeProps extends HTMLAttributes, VariantProps {} + +export function Badge({ className, variant, ...props }: BadgeProps) { + return ; +} + +export { badgeVariants }; diff --git a/src/components/ui/card.tsx b/src/components/ui/card.tsx new file mode 100644 index 0000000..ddc43e0 --- /dev/null +++ b/src/components/ui/card.tsx @@ -0,0 +1,25 @@ +import { cn } from "@/lib/utils"; +import type { HTMLAttributes } from "react"; + +export function Card({ className, ...props }: HTMLAttributes) { + return ( +
    + ); +} + +export function CardHeader({ className, ...props }: HTMLAttributes) { + return
    ; +} + +export function CardTitle({ className, ...props }: HTMLAttributes) { + return ( +

    + ); +} + +export function CardContent({ className, ...props }: HTMLAttributes) { + return
    ; +} diff --git a/src/components/ui/collapsible.tsx b/src/components/ui/collapsible.tsx new file mode 100644 index 0000000..bd09ade --- /dev/null +++ b/src/components/ui/collapsible.tsx @@ -0,0 +1,9 @@ +"use client"; + +import * as CollapsiblePrimitive from "@radix-ui/react-collapsible"; + +const Collapsible = CollapsiblePrimitive.Root; +const CollapsibleTrigger = CollapsiblePrimitive.CollapsibleTrigger; +const CollapsibleContent = CollapsiblePrimitive.CollapsibleContent; + +export { Collapsible, CollapsibleTrigger, CollapsibleContent }; diff --git a/src/components/workspaces/WorkspaceDetail.tsx b/src/components/workspaces/WorkspaceDetail.tsx index 2a839ce..39feec8 100644 --- a/src/components/workspaces/WorkspaceDetail.tsx +++ b/src/components/workspaces/WorkspaceDetail.tsx @@ -1,24 +1,50 @@ -import { useState } from "react"; -import { Link, useParams, useNavigate } from "@tanstack/react-router"; -import { motion } from "framer-motion"; -import { Boxes, Users, MessageSquare, Lightbulb, ArrowLeft, CircleDot, Trash2, Zap, Webhook } from "lucide-react"; -import { - useWorkspace, - useQueueStatus, - useDeleteWorkspace, - useScheduleDream, -} from "@/api/queries"; -import { PageLoader } from "@/components/shared/LoadingSpinner"; +import { useDeleteWorkspace, useQueueStatus, useScheduleDream, useWorkspace } from "@/api/queries"; +import { ConfirmDialog } from "@/components/shared/ConfirmDialog"; import { ErrorAlert } from "@/components/shared/ErrorAlert"; import { JsonViewer } from "@/components/shared/JsonViewer"; -import { ConfirmDialog } from "@/components/shared/ConfirmDialog"; +import { PageLoader } from "@/components/shared/LoadingSpinner"; import { ScheduleDreamModal } from "@/components/workspaces/ScheduleDreamModal"; +import { Link, useNavigate, useParams } from "@tanstack/react-router"; +import { AnimatePresence, motion } from "framer-motion"; +import { + ArrowLeft, + Boxes, + ChevronDown, + CircleDot, + Lightbulb, + MessageSquare, + Trash2, + Users, + Webhook, + Zap, +} from "lucide-react"; +import { useState } from "react"; const NAV_SECTIONS = [ - { label: "Peers", icon: Users, to: "peers" as const, description: "Browse peer identities and memory" }, - { label: "Sessions", icon: MessageSquare, to: "sessions" as const, description: "View conversation sessions" }, - { label: "Conclusions", icon: Lightbulb, to: "conclusions" as const, description: "Browse memory conclusions" }, - { label: "Webhooks", icon: Webhook, to: "webhooks" as const, description: "Manage event webhooks" }, + { + label: "Peers", + icon: Users, + to: "peers" as const, + description: "Browse peer identities and memory", + }, + { + label: "Sessions", + icon: MessageSquare, + to: "sessions" as const, + description: "View conversation sessions", + }, + { + label: "Conclusions", + icon: Lightbulb, + to: "conclusions" as const, + description: "Browse memory conclusions", + }, + { + label: "Webhooks", + icon: Webhook, + to: "webhooks" as const, + description: "Manage event webhooks", + }, ] as const; export function WorkspaceDetail() { @@ -33,6 +59,7 @@ export function WorkspaceDetail() { const [confirmDelete, setConfirmDelete] = useState(false); const [dreamOpen, setDreamOpen] = useState(false); + const [sessionsExpanded, setSessionsExpanded] = useState(false); const handleDelete = async () => { await deleteWorkspace.mutateAsync(workspaceId); @@ -52,7 +79,11 @@ export function WorkspaceDetail() {
    - +

    -

    Workspace overview

    +

    + Workspace overview +

    @@ -112,7 +145,11 @@ export function WorkspaceDetail() { params={{ workspaceId } as never} className="block rounded-xl p-5 group transition-all theme-card" > - +

    {s.label}

    @@ -139,24 +176,47 @@ export function WorkspaceDetail() {

    {queue.pending_work_units > 0 ? ( - - + + ) : ( - + )} 0 ? "#f59e0b" : "#34d399" }} > - {queue.pending_work_units === 0 ? "Idle" : `${queue.pending_work_units} pending`} + {queue.pending_work_units === 0 + ? "Idle" + : `${queue.pending_work_units} pending`}
    -
    - {(["total_work_units", "completed_work_units", "in_progress_work_units", "pending_work_units"] as const).map((key) => ( +
    + {( + [ + "total_work_units", + "completed_work_units", + "in_progress_work_units", + "pending_work_units", + ] as const + ).map((key) => (
    -
    +
    {queue[key]}
    @@ -165,6 +225,108 @@ export function WorkspaceDetail() {
    ))}
    + + {/* Per-session breakdown */} + {queue.sessions && Object.keys(queue.sessions).length > 0 && ( +
    + + + {sessionsExpanded && ( + +
    + + + + {["Session", "Total", "Done", "Active", "Pending"].map((h) => ( + + ))} + + + + {Object.entries(queue.sessions).map(([sid, s], i) => ( + 0 ? "1px solid var(--border)" : undefined, + }} + > + + + + + + + ))} + +
    + {h} +
    + + {sid} + + + {s.total_work_units} + + {s.completed_work_units} + + {s.in_progress_work_units} + + {s.pending_work_units} +
    +
    +
    + )} +
    +
    + )} )} diff --git a/src/index.css b/src/index.css index 9b58ea0..f33d569 100644 --- a/src/index.css +++ b/src/index.css @@ -5,50 +5,70 @@ @import "@fontsource/dm-sans/500.css"; @import "@fontsource/dm-sans/600.css"; +/* ─── Tailwind v4 theme bridge ─── */ +@theme inline { + --color-background: var(--bg); + --color-foreground: var(--text-1); + --color-card: var(--bg-2); + --color-card-foreground: var(--text-1); + --color-muted: var(--bg-3); + --color-muted-foreground: var(--text-3); + --color-primary: var(--accent); + --color-primary-foreground: #ffffff; + --color-secondary: var(--bg-3); + --color-secondary-foreground: var(--text-2); + --color-border: var(--border); + --color-ring: var(--accent); + --radius-sm: 6px; + --radius-md: 8px; + --radius-lg: 12px; + --radius-xl: 16px; +} + /* ─── Theme tokens ─── */ :root, [data-theme="dark"] { - --bg: #0c0c10; - --bg-2: #111118; - --bg-3: #16161f; - --surface: rgba(255, 255, 255, 0.02); - --border: rgba(255, 255, 255, 0.06); - --border-2: rgba(255, 255, 255, 0.10); - --text-1: #e4e4f0; - --text-2: rgba(148, 163, 184, 0.75); - --text-3: rgba(148, 163, 184, 0.40); - --text-4: rgba(148, 163, 184, 0.25); - --accent: #6366f1; - --accent-dim: rgba(99, 102, 241, 0.12); - --accent-border: rgba(99, 102, 241, 0.30); - --accent-text: #a5b4fc; - --sidebar-bg: linear-gradient(180deg, #111118 0%, #0e0e15 100%); - --grid-line: rgba(99, 102, 241, 0.03); - --glow: rgba(79, 70, 229, 0.08); - --scrollbar: rgba(99, 102, 241, 0.2); - --card-hover: rgba(99, 102, 241, 0.06); + --bg: #0c0c10; + --bg-2: #111118; + --bg-3: #1a1a24; + --surface: rgba(255, 255, 255, 0.03); + --border: rgba(255, 255, 255, 0.08); + --border-2: rgba(255, 255, 255, 0.13); + --text-1: #e8e8f4; + --text-2: #94a3b8; + --text-3: #64748b; + --text-4: #475569; + --accent: #6366f1; + --accent-dim: rgba(99, 102, 241, 0.15); + --accent-border: rgba(99, 102, 241, 0.35); + --accent-text: #a5b4fc; + --sidebar-bg: linear-gradient(180deg, #111118 0%, #0e0e15 100%); + --grid-line: rgba(99, 102, 241, 0.03); + --glow: rgba(79, 70, 229, 0.08); + --scrollbar: rgba(99, 102, 241, 0.2); + --card-hover: rgba(99, 102, 241, 0.06); } [data-theme="light"] { - --bg: #f8f8fc; - --bg-2: #ffffff; - --bg-3: #f0f0f8; - --surface: rgba(0, 0, 0, 0.02); - --border: rgba(0, 0, 0, 0.07); - --border-2: rgba(0, 0, 0, 0.12); - --text-1: #1a1a2e; - --text-2: rgba(30, 30, 60, 0.65); - --text-3: rgba(30, 30, 60, 0.40); - --text-4: rgba(30, 30, 60, 0.25); - --accent: #4f46e5; - --accent-dim: rgba(79, 70, 229, 0.08); + --bg: #f8f8fc; + --bg-2: #ffffff; + --bg-3: #f0f0f8; + --surface: rgba(0, 0, 0, 0.02); + --border: rgba(0, 0, 0, 0.08); + --border-2: rgba(0, 0, 0, 0.14); + --text-1: #1a1a2e; + --text-2: #374151; + --text-3: #6b7280; + --text-4: #9ca3af; + --accent: #4f46e5; + --accent-dim: rgba(79, 70, 229, 0.08); --accent-border: rgba(79, 70, 229, 0.25); - --accent-text: #4f46e5; - --sidebar-bg: linear-gradient(180deg, #ffffff 0%, #f4f4fc 100%); - --grid-line: rgba(79, 70, 229, 0.04); - --glow: rgba(79, 70, 229, 0.06); - --scrollbar: rgba(79, 70, 229, 0.2); - --card-hover: rgba(79, 70, 229, 0.04); + --accent-text: #4f46e5; + --sidebar-bg: linear-gradient(180deg, #ffffff 0%, #f4f4fc 100%); + --grid-line: rgba(79, 70, 229, 0.04); + --glow: rgba(79, 70, 229, 0.06); + --scrollbar: rgba(79, 70, 229, 0.2); + --card-hover: rgba(79, 70, 229, 0.04); } /* ─── Base ─── */ diff --git a/src/lib/constants.ts b/src/lib/constants.ts new file mode 100644 index 0000000..f6ad2da --- /dev/null +++ b/src/lib/constants.ts @@ -0,0 +1,28 @@ +// Semantic color tokens for inline styles. +// CSS variables (var(--text-1) etc.) handle theme-aware colors. +// These constants are for fixed semantic states that don't invert with theme. + +export const COLOR = { + // Status + success: "#34d399", + successDim: "rgba(52,211,153,0.08)", + successBorder: "rgba(52,211,153,0.2)", + + warning: "#f59e0b", + warningDim: "rgba(245,158,11,0.08)", + warningBorder: "rgba(245,158,11,0.2)", + + destructive: "#f87171", + destructiveDim: "rgba(239,68,68,0.08)", + destructiveBorder: "rgba(239,68,68,0.2)", + + // Accent (indigo — matches --accent CSS var) + accent: "#6366f1", + accentText: "#818cf8", + accentSoft: "#c7d2fe", + accentDim: "rgba(99,102,241,0.08)", + accentDimHover: "rgba(99,102,241,0.06)", + accentSubtle: "rgba(99,102,241,0.1)", + accentBorder: "rgba(99,102,241,0.2)", + accentBorderStrong: "rgba(99,102,241,0.15)", +} as const; diff --git a/src/lib/utils.ts b/src/lib/utils.ts new file mode 100644 index 0000000..f08f69a --- /dev/null +++ b/src/lib/utils.ts @@ -0,0 +1,16 @@ +import { type ClassValue, clsx } from "clsx"; +import { twMerge } from "tailwind-merge"; + +export function cn(...inputs: ClassValue[]) { + return twMerge(clsx(inputs)); +} + +const _compact = new Intl.NumberFormat(undefined, { + notation: "compact", + maximumFractionDigits: 1, +}); + +export function formatCount(n: number): string { + if (n < 1_000) return String(n); + return _compact.format(n); +}