feat: full shadcn/ui component system with consistent typography
New components: - ui/button.tsx — Button with primary/accent/surface/ghost/destructive variants - ui/input.tsx — Input + Textarea with focus ring and CSS var theming - ui/label.tsx — Radix Label with peer-disabled support - ui/separator.tsx — Radix Separator - ui/tooltip.tsx — Radix Tooltip with themed content - ui/dialog.tsx — Radix Dialog replacing custom modal implementations - ui/table.tsx — Table/TableHeader/TableBody/TableRow/TableHead/TableCell - ui/typography.tsx — PageTitle/SectionHeading/Body/Muted/Caption/MonoCaption Wired throughout all components: - ConfirmDialog + FormModal migrated to Radix Dialog (focus trap, Escape, ARIA) - All raw <button> → Button, <input>/<textarea> → Input/Textarea - All repeated text patterns → typography components - All hardcoded hex/rgba strings → COLOR constants
This commit is contained in:
@@ -18,6 +18,11 @@
|
||||
"@fontsource/dm-mono": "^5.2.7",
|
||||
"@fontsource/dm-sans": "^5.2.8",
|
||||
"@radix-ui/react-collapsible": "^1.1.12",
|
||||
"@radix-ui/react-dialog": "^1.1.15",
|
||||
"@radix-ui/react-label": "^2.1.8",
|
||||
"@radix-ui/react-separator": "^1.1.8",
|
||||
"@radix-ui/react-slot": "^1.2.4",
|
||||
"@radix-ui/react-tooltip": "^1.2.8",
|
||||
"@tailwindcss/vite": "^4.2.4",
|
||||
"@tanstack/react-query": "^5.74.4",
|
||||
"@tanstack/react-router": "^1.120.3",
|
||||
|
||||
539
pnpm-lock.yaml
generated
539
pnpm-lock.yaml
generated
@@ -19,6 +19,21 @@ importers:
|
||||
'@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)
|
||||
'@radix-ui/react-dialog':
|
||||
specifier: ^1.1.15
|
||||
version: 1.1.15(@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-label':
|
||||
specifier: ^2.1.8
|
||||
version: 2.1.8(@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-separator':
|
||||
specifier: ^1.1.8
|
||||
version: 1.1.8(@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-slot':
|
||||
specifier: ^1.2.4
|
||||
version: 1.2.4(@types/react@19.2.14)(react@19.2.5)
|
||||
'@radix-ui/react-tooltip':
|
||||
specifier: ^1.2.8
|
||||
version: 1.2.8(@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))
|
||||
@@ -459,6 +474,21 @@ packages:
|
||||
cpu: [x64]
|
||||
os: [win32]
|
||||
|
||||
'@floating-ui/core@1.7.5':
|
||||
resolution: {integrity: sha512-1Ih4WTWyw0+lKyFMcBHGbb5U5FtuHJuujoyyr5zTaWS5EYMeT6Jb2AuDeftsCsEuchO+mM2ij5+q9crhydzLhQ==}
|
||||
|
||||
'@floating-ui/dom@1.7.6':
|
||||
resolution: {integrity: sha512-9gZSAI5XM36880PPMm//9dfiEngYoC6Am2izES1FF406YFsjvyBMmeJ2g4SAju3xWwtuynNRFL2s9hgxpLI5SQ==}
|
||||
|
||||
'@floating-ui/react-dom@2.1.8':
|
||||
resolution: {integrity: sha512-cC52bHwM/n/CxS87FH0yWdngEZrjdtLW/qVruo68qg+prK7ZQ4YGdut2GyDVpoGeAYe/h899rVeOVm6Oi40k2A==}
|
||||
peerDependencies:
|
||||
react: '>=16.8.0'
|
||||
react-dom: '>=16.8.0'
|
||||
|
||||
'@floating-ui/utils@0.2.11':
|
||||
resolution: {integrity: sha512-RiB/yIh78pcIxl6lLMG0CgBXAZ2Y0eVHqMPYugu+9U0AeT6YBeiJpf7lbdJNIugFP5SIjwNRgo4DhR1Qxi26Gg==}
|
||||
|
||||
'@fontsource/dm-mono@5.2.7':
|
||||
resolution: {integrity: sha512-Ma1az2atTVgQWuOWwjuxx26p/6A6CU9HBNKq1CFV6YKpKhpswnf9ry9Ql4+T6bTZzkdtSfS6tjJvqZOljVzIFQ==}
|
||||
|
||||
@@ -493,6 +523,19 @@ packages:
|
||||
'@radix-ui/primitive@1.1.3':
|
||||
resolution: {integrity: sha512-JTF99U/6XIjCBo0wqkU5sK10glYe27MRRsfwoiq5zzOEZLHU3A3KCMa5X/azekYRCJ0HlwI0crAXS/5dEHTzDg==}
|
||||
|
||||
'@radix-ui/react-arrow@1.1.7':
|
||||
resolution: {integrity: sha512-F+M1tLhO+mlQaOWspE8Wstg+z6PwxwRd8oQ8IXceWz92kfAmalTRf0EjrouQeo7QssEPfCn05B4Ihs1K9WQ/7w==}
|
||||
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-collapsible@1.1.12':
|
||||
resolution: {integrity: sha512-Uu+mSh4agx2ib1uIGPP4/CKNULyajb3p92LsVXmH2EHVMTfZWpll88XJ0j4W0z3f8NK1eYl1+Mf/szHPmcHzyA==}
|
||||
peerDependencies:
|
||||
@@ -524,6 +567,54 @@ packages:
|
||||
'@types/react':
|
||||
optional: true
|
||||
|
||||
'@radix-ui/react-dialog@1.1.15':
|
||||
resolution: {integrity: sha512-TCglVRtzlffRNxRMEyR36DGBLJpeusFcgMVD9PZEzAKnUs1lKCgX5u9BmC2Yg+LL9MgZDugFFs1Vl+Jp4t/PGw==}
|
||||
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-dismissable-layer@1.1.11':
|
||||
resolution: {integrity: sha512-Nqcp+t5cTB8BinFkZgXiMJniQH0PsUt2k51FUhbdfeKvc4ACcG2uQniY/8+h1Yv6Kza4Q7lD7PQV0z0oicE0Mg==}
|
||||
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-focus-guards@1.1.3':
|
||||
resolution: {integrity: sha512-0rFg/Rj2Q62NCm62jZw0QX7a3sz6QCQU0LpZdNrJX8byRGaGVTqbrW9jAoIAHyMQqsNpeZ81YgSizOt5WXq0Pw==}
|
||||
peerDependencies:
|
||||
'@types/react': '*'
|
||||
react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
|
||||
peerDependenciesMeta:
|
||||
'@types/react':
|
||||
optional: true
|
||||
|
||||
'@radix-ui/react-focus-scope@1.1.7':
|
||||
resolution: {integrity: sha512-t2ODlkXBQyn7jkl6TNaw/MtVEVvIGelJDCG41Okq/KwUsJBwQ4XVZsHAVUkK4mBv3ewiAS3PGuUWuY2BoK4ZUw==}
|
||||
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-id@1.1.1':
|
||||
resolution: {integrity: sha512-kGkGegYIdQsOb4XjsfM97rXsiHaBwco+hFI66oO4s9LU+PLAC5oJ7khdOVFxkhsmlbpUqDAvXw11CluXP+jkHg==}
|
||||
peerDependencies:
|
||||
@@ -533,6 +624,45 @@ packages:
|
||||
'@types/react':
|
||||
optional: true
|
||||
|
||||
'@radix-ui/react-label@2.1.8':
|
||||
resolution: {integrity: sha512-FmXs37I6hSBVDlO4y764TNz1rLgKwjJMQ0EGte6F3Cb3f4bIuHB/iLa/8I9VKkmOy+gNHq8rql3j686ACVV21A==}
|
||||
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-popper@1.2.8':
|
||||
resolution: {integrity: sha512-0NJQ4LFFUuWkE7Oxf0htBKS6zLkkjBH+hM1uk7Ng705ReR8m/uelduy1DBo0PyBXPKVnBA6YBlU94MBGXrSBCw==}
|
||||
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-portal@1.1.9':
|
||||
resolution: {integrity: sha512-bpIxvq03if6UNwXZ+HTK71JLh4APvnXntDc6XOX8UVq4XQOVl7lwok0AvIl+b8zgCw3fSaVTZMpAPPagXbKmHQ==}
|
||||
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-presence@1.1.5':
|
||||
resolution: {integrity: sha512-/jfEwNDdQVBCNvjkGit4h6pMOzq8bHkopq458dPt2lMjx+eBQUohZNG9A7DtO/O5ukSbxuaNGXMjHicgwy6rQQ==}
|
||||
peerDependencies:
|
||||
@@ -559,6 +689,32 @@ packages:
|
||||
'@types/react-dom':
|
||||
optional: true
|
||||
|
||||
'@radix-ui/react-primitive@2.1.4':
|
||||
resolution: {integrity: sha512-9hQc4+GNVtJAIEPEqlYqW5RiYdrr8ea5XQ0ZOnD6fgru+83kqT15mq2OCcbe8KnjRZl5vF3ks69AKz3kh1jrhg==}
|
||||
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-separator@1.1.8':
|
||||
resolution: {integrity: sha512-sDvqVY4itsKwwSMEe0jtKgfTh+72Sy3gPmQpjqcQneqQ4PFmr/1I0YA+2/puilhggCe2gJcx5EBAYFkWkdpa5g==}
|
||||
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:
|
||||
@@ -568,6 +724,37 @@ packages:
|
||||
'@types/react':
|
||||
optional: true
|
||||
|
||||
'@radix-ui/react-slot@1.2.4':
|
||||
resolution: {integrity: sha512-Jl+bCv8HxKnlTLVrcDE8zTMJ09R9/ukw4qBs/oZClOfoQk/cOTbDn+NceXfV7j09YPVQUryJPHurafcSg6EVKA==}
|
||||
peerDependencies:
|
||||
'@types/react': '*'
|
||||
react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
|
||||
peerDependenciesMeta:
|
||||
'@types/react':
|
||||
optional: true
|
||||
|
||||
'@radix-ui/react-tooltip@1.2.8':
|
||||
resolution: {integrity: sha512-tY7sVt1yL9ozIxvmbtN5qtmH2krXcBCfjEiCgKGLqunJHvgvZG2Pcl2oQ3kbcZARb1BGEHdkLzcYGO8ynVlieg==}
|
||||
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-use-callback-ref@1.1.1':
|
||||
resolution: {integrity: sha512-FkBMwD+qbGQeMu1cOHnuGB6x4yzPjho8ap5WtbEJ26umhgqVXbhekKUQO+hZEL1vU92a3wHwdp0HAcqAUF5iDg==}
|
||||
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:
|
||||
@@ -586,6 +773,15 @@ packages:
|
||||
'@types/react':
|
||||
optional: true
|
||||
|
||||
'@radix-ui/react-use-escape-keydown@1.1.1':
|
||||
resolution: {integrity: sha512-Il0+boE7w/XebUHyBjroE+DbByORGR9KKmITzbR7MyQ4akpORYP/ZmbhAr0DG7RmmBqoOnZdy2QlvajJ2QA59g==}
|
||||
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:
|
||||
@@ -595,6 +791,40 @@ packages:
|
||||
'@types/react':
|
||||
optional: true
|
||||
|
||||
'@radix-ui/react-use-rect@1.1.1':
|
||||
resolution: {integrity: sha512-QTYuDesS0VtuHNNvMh+CjlKJ4LJickCMUAqjlE3+j8w+RlRpwyX3apEQKGFzbZGdo7XNG1tXa+bQqIE7HIXT2w==}
|
||||
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-size@1.1.1':
|
||||
resolution: {integrity: sha512-ewrXRDTAqAXlkl6t/fkXWNAhFX9I+CkKlw6zjEwk86RSPKwZr3xpBRso655aqYafwtnbpHLj6toFzmd6xdVptQ==}
|
||||
peerDependencies:
|
||||
'@types/react': '*'
|
||||
react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
|
||||
peerDependenciesMeta:
|
||||
'@types/react':
|
||||
optional: true
|
||||
|
||||
'@radix-ui/react-visually-hidden@1.2.3':
|
||||
resolution: {integrity: sha512-pzJq12tEaaIhqjbzpCuv/OypJY/BPavOofm+dbab+MHLajy277+1lLm6JFcGgF5eskJ6mquGirhXY2GD/8u8Ug==}
|
||||
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/rect@1.1.1':
|
||||
resolution: {integrity: sha512-HPwpGIzkl28mWyZqG52jiqDJ12waP11Pa1lGoiyUkIEuMLBP0oeK/C89esbXrxsky5we7dfd8U58nm0SgAWpVw==}
|
||||
|
||||
'@redocly/ajv@8.11.2':
|
||||
resolution: {integrity: sha512-io1JpnwtIcvojV7QKDUSIuMN/ikdOUd1ReEnUnMKGfDVridQZ31J0MmIuqwuRjWDZfmvr+Q0MqCcfHM2gTivOg==}
|
||||
|
||||
@@ -1162,6 +1392,10 @@ packages:
|
||||
argparse@2.0.1:
|
||||
resolution: {integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==}
|
||||
|
||||
aria-hidden@1.2.6:
|
||||
resolution: {integrity: sha512-ik3ZgC9dY/lYVVM++OISsaYDeg1tb0VtP5uL3ouh1koGOaUMDPpbFIei4JkFimWUFPn90sbMNMXQAIVOlnYKJA==}
|
||||
engines: {node: '>=10'}
|
||||
|
||||
aria-query@5.3.0:
|
||||
resolution: {integrity: sha512-b0P0sZPKtyu8HkeRAfCq0IfURZK+SuwMjY1UXGBU27wpAiTwQAIlq56IbIO+ytk/JjS1fMR14ee5WBBfKi5J6A==}
|
||||
|
||||
@@ -1300,6 +1534,9 @@ packages:
|
||||
resolution: {integrity: sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==}
|
||||
engines: {node: '>=8'}
|
||||
|
||||
detect-node-es@1.1.0:
|
||||
resolution: {integrity: sha512-ypdmJU/TbBby2Dxibuv7ZLW3Bs1QEmM7nHjEANfohJLvE0XVujisn1qPJcZxg+qDucsr+bP6fLD1rPS3AhJ7EQ==}
|
||||
|
||||
devlop@1.1.0:
|
||||
resolution: {integrity: sha512-RWmIqhcFf1lRYBvNmr7qTNuyCt/7/ns2jbpp1+PalgE/rDQcBT0fioSMUpJ93irlUhC5hrg4cYqe6U+0ImW0rA==}
|
||||
|
||||
@@ -1392,6 +1629,10 @@ packages:
|
||||
resolution: {integrity: sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==}
|
||||
engines: {node: '>=6.9.0'}
|
||||
|
||||
get-nonce@1.0.1:
|
||||
resolution: {integrity: sha512-FJhYRoDaiatfEkUK8HKlicmu/3SGFD51q3itKDGoSTysQJBnfOcxU5GxnhE1E6soB76MbT0MBtnKJuXyAx+96Q==}
|
||||
engines: {node: '>=6'}
|
||||
|
||||
get-tsconfig@4.14.0:
|
||||
resolution: {integrity: sha512-yTb+8DXzDREzgvYmh6s9vHsSVCHeC0G3PI5bEXNBHtmshPnO+S5O7qgLEOn0I5QvMy6kpZN8K1NKGyilLb93wA==}
|
||||
|
||||
@@ -1861,6 +2102,36 @@ packages:
|
||||
'@types/react': '>=18'
|
||||
react: '>=18'
|
||||
|
||||
react-remove-scroll-bar@2.3.8:
|
||||
resolution: {integrity: sha512-9r+yi9+mgU33AKcj6IbT9oRCO78WriSj6t/cF8DWBZJ9aOGPOTEDvdUDz1FwKim7QXWwmHqtdHnRJfhAxEG46Q==}
|
||||
engines: {node: '>=10'}
|
||||
peerDependencies:
|
||||
'@types/react': '*'
|
||||
react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0
|
||||
peerDependenciesMeta:
|
||||
'@types/react':
|
||||
optional: true
|
||||
|
||||
react-remove-scroll@2.7.2:
|
||||
resolution: {integrity: sha512-Iqb9NjCCTt6Hf+vOdNIZGdTiH1QSqr27H/Ek9sv/a97gfueI/5h1s3yRi1nngzMUaOOToin5dI1dXKdXiF+u0Q==}
|
||||
engines: {node: '>=10'}
|
||||
peerDependencies:
|
||||
'@types/react': '*'
|
||||
react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc
|
||||
peerDependenciesMeta:
|
||||
'@types/react':
|
||||
optional: true
|
||||
|
||||
react-style-singleton@2.2.3:
|
||||
resolution: {integrity: sha512-b6jSvxvVnyptAiLjbkWLE/lOnR4lfTtDAl+eUC7RZy+QQWc6wRzIV2CE6xBuMmDxc2qIihtDCZD5NPOFl7fRBQ==}
|
||||
engines: {node: '>=10'}
|
||||
peerDependencies:
|
||||
'@types/react': '*'
|
||||
react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc
|
||||
peerDependenciesMeta:
|
||||
'@types/react':
|
||||
optional: true
|
||||
|
||||
react@19.2.5:
|
||||
resolution: {integrity: sha512-llUJLzz1zTUBrskt2pwZgLq59AemifIftw4aB7JxOqf1HY2FDaGDxgwpAPVzHU1kdWabH7FauP4i1oEeer2WCA==}
|
||||
engines: {node: '>=0.10.0'}
|
||||
@@ -2076,6 +2347,26 @@ packages:
|
||||
uri-js-replace@1.0.1:
|
||||
resolution: {integrity: sha512-W+C9NWNLFOoBI2QWDp4UT9pv65r2w5Cx+3sTYFvtMdDBxkKt1syCqsUdSFAChbEe1uK5TfS04wt/nGwmaeIQ0g==}
|
||||
|
||||
use-callback-ref@1.3.3:
|
||||
resolution: {integrity: sha512-jQL3lRnocaFtu3V00JToYz/4QkNWswxijDaCVNZRiRTO3HQDLsdu1ZtmIUvV4yPp+rvWm5j0y0TG/S61cuijTg==}
|
||||
engines: {node: '>=10'}
|
||||
peerDependencies:
|
||||
'@types/react': '*'
|
||||
react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc
|
||||
peerDependenciesMeta:
|
||||
'@types/react':
|
||||
optional: true
|
||||
|
||||
use-sidecar@1.1.3:
|
||||
resolution: {integrity: sha512-Fedw0aZvkhynoPYlA5WXrMCAMm+nSWdZt6lzJQ7Ok8S6Q+VsHmHpRWndVRJ8Be0ZbkfPc5LRYH+5XrzXcEeLRQ==}
|
||||
engines: {node: '>=10'}
|
||||
peerDependencies:
|
||||
'@types/react': '*'
|
||||
react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc
|
||||
peerDependenciesMeta:
|
||||
'@types/react':
|
||||
optional: true
|
||||
|
||||
use-sync-external-store@1.6.0:
|
||||
resolution: {integrity: sha512-Pp6GSwGP/NrPIrxVFAIkOQeyw8lFenOHijQWkUTrDvrF4ALqylP2C/KCkeS9dpUM3KvYRQhna5vt7IL95+ZQ9w==}
|
||||
peerDependencies:
|
||||
@@ -2542,6 +2833,23 @@ snapshots:
|
||||
'@esbuild/win32-x64@0.27.7':
|
||||
optional: true
|
||||
|
||||
'@floating-ui/core@1.7.5':
|
||||
dependencies:
|
||||
'@floating-ui/utils': 0.2.11
|
||||
|
||||
'@floating-ui/dom@1.7.6':
|
||||
dependencies:
|
||||
'@floating-ui/core': 1.7.5
|
||||
'@floating-ui/utils': 0.2.11
|
||||
|
||||
'@floating-ui/react-dom@2.1.8(react-dom@19.2.5(react@19.2.5))(react@19.2.5)':
|
||||
dependencies:
|
||||
'@floating-ui/dom': 1.7.6
|
||||
react: 19.2.5
|
||||
react-dom: 19.2.5(react@19.2.5)
|
||||
|
||||
'@floating-ui/utils@0.2.11': {}
|
||||
|
||||
'@fontsource/dm-mono@5.2.7': {}
|
||||
|
||||
'@fontsource/dm-sans@5.2.8': {}
|
||||
@@ -2576,6 +2884,15 @@ snapshots:
|
||||
|
||||
'@radix-ui/primitive@1.1.3': {}
|
||||
|
||||
'@radix-ui/react-arrow@1.1.7(@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-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)
|
||||
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-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
|
||||
@@ -2604,6 +2921,58 @@ snapshots:
|
||||
optionalDependencies:
|
||||
'@types/react': 19.2.14
|
||||
|
||||
'@radix-ui/react-dialog@1.1.15(@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-dismissable-layer': 1.1.11(@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-focus-guards': 1.1.3(@types/react@19.2.14)(react@19.2.5)
|
||||
'@radix-ui/react-focus-scope': 1.1.7(@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-id': 1.1.1(@types/react@19.2.14)(react@19.2.5)
|
||||
'@radix-ui/react-portal': 1.1.9(@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-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-slot': 1.2.3(@types/react@19.2.14)(react@19.2.5)
|
||||
'@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.2.14)(react@19.2.5)
|
||||
aria-hidden: 1.2.6
|
||||
react: 19.2.5
|
||||
react-dom: 19.2.5(react@19.2.5)
|
||||
react-remove-scroll: 2.7.2(@types/react@19.2.14)(react@19.2.5)
|
||||
optionalDependencies:
|
||||
'@types/react': 19.2.14
|
||||
'@types/react-dom': 19.2.3(@types/react@19.2.14)
|
||||
|
||||
'@radix-ui/react-dismissable-layer@1.1.11(@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-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-callback-ref': 1.1.1(@types/react@19.2.14)(react@19.2.5)
|
||||
'@radix-ui/react-use-escape-keydown': 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-focus-guards@1.1.3(@types/react@19.2.14)(react@19.2.5)':
|
||||
dependencies:
|
||||
react: 19.2.5
|
||||
optionalDependencies:
|
||||
'@types/react': 19.2.14
|
||||
|
||||
'@radix-ui/react-focus-scope@1.1.7(@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-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-callback-ref': 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-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)
|
||||
@@ -2611,6 +2980,43 @@ snapshots:
|
||||
optionalDependencies:
|
||||
'@types/react': 19.2.14
|
||||
|
||||
'@radix-ui/react-label@2.1.8(@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-primitive': 2.1.4(@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)
|
||||
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-popper@1.2.8(@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:
|
||||
'@floating-ui/react-dom': 2.1.8(react-dom@19.2.5(react@19.2.5))(react@19.2.5)
|
||||
'@radix-ui/react-arrow': 1.1.7(@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-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-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-callback-ref': 1.1.1(@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)
|
||||
'@radix-ui/react-use-rect': 1.1.1(@types/react@19.2.14)(react@19.2.5)
|
||||
'@radix-ui/react-use-size': 1.1.1(@types/react@19.2.14)(react@19.2.5)
|
||||
'@radix-ui/rect': 1.1.1
|
||||
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-portal@1.1.9(@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-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-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-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)
|
||||
@@ -2630,6 +3036,24 @@ snapshots:
|
||||
'@types/react': 19.2.14
|
||||
'@types/react-dom': 19.2.3(@types/react@19.2.14)
|
||||
|
||||
'@radix-ui/react-primitive@2.1.4(@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.4(@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-separator@1.1.8(@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-primitive': 2.1.4(@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)
|
||||
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)
|
||||
@@ -2637,6 +3061,39 @@ snapshots:
|
||||
optionalDependencies:
|
||||
'@types/react': 19.2.14
|
||||
|
||||
'@radix-ui/react-slot@1.2.4(@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-tooltip@1.2.8(@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-dismissable-layer': 1.1.11(@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-id': 1.1.1(@types/react@19.2.14)(react@19.2.5)
|
||||
'@radix-ui/react-popper': 1.2.8(@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-portal': 1.1.9(@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-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-slot': 1.2.3(@types/react@19.2.14)(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-visually-hidden': 1.2.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)
|
||||
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-use-callback-ref@1.1.1(@types/react@19.2.14)(react@19.2.5)':
|
||||
dependencies:
|
||||
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)
|
||||
@@ -2652,12 +3109,44 @@ snapshots:
|
||||
optionalDependencies:
|
||||
'@types/react': 19.2.14
|
||||
|
||||
'@radix-ui/react-use-escape-keydown@1.1.1(@types/react@19.2.14)(react@19.2.5)':
|
||||
dependencies:
|
||||
'@radix-ui/react-use-callback-ref': 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
|
||||
|
||||
'@radix-ui/react-use-rect@1.1.1(@types/react@19.2.14)(react@19.2.5)':
|
||||
dependencies:
|
||||
'@radix-ui/rect': 1.1.1
|
||||
react: 19.2.5
|
||||
optionalDependencies:
|
||||
'@types/react': 19.2.14
|
||||
|
||||
'@radix-ui/react-use-size@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-visually-hidden@1.2.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-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)
|
||||
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/rect@1.1.1': {}
|
||||
|
||||
'@redocly/ajv@8.11.2':
|
||||
dependencies:
|
||||
fast-deep-equal: 3.1.3
|
||||
@@ -3115,6 +3604,10 @@ snapshots:
|
||||
|
||||
argparse@2.0.1: {}
|
||||
|
||||
aria-hidden@1.2.6:
|
||||
dependencies:
|
||||
tslib: 2.8.1
|
||||
|
||||
aria-query@5.3.0:
|
||||
dependencies:
|
||||
dequal: 2.0.3
|
||||
@@ -3240,6 +3733,8 @@ snapshots:
|
||||
|
||||
detect-libc@2.1.2: {}
|
||||
|
||||
detect-node-es@1.1.0: {}
|
||||
|
||||
devlop@1.1.0:
|
||||
dependencies:
|
||||
dequal: 2.0.3
|
||||
@@ -3328,6 +3823,8 @@ snapshots:
|
||||
|
||||
gensync@1.0.0-beta.2: {}
|
||||
|
||||
get-nonce@1.0.1: {}
|
||||
|
||||
get-tsconfig@4.14.0:
|
||||
dependencies:
|
||||
resolve-pkg-maps: 1.0.0
|
||||
@@ -3996,6 +4493,33 @@ snapshots:
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
react-remove-scroll-bar@2.3.8(@types/react@19.2.14)(react@19.2.5):
|
||||
dependencies:
|
||||
react: 19.2.5
|
||||
react-style-singleton: 2.2.3(@types/react@19.2.14)(react@19.2.5)
|
||||
tslib: 2.8.1
|
||||
optionalDependencies:
|
||||
'@types/react': 19.2.14
|
||||
|
||||
react-remove-scroll@2.7.2(@types/react@19.2.14)(react@19.2.5):
|
||||
dependencies:
|
||||
react: 19.2.5
|
||||
react-remove-scroll-bar: 2.3.8(@types/react@19.2.14)(react@19.2.5)
|
||||
react-style-singleton: 2.2.3(@types/react@19.2.14)(react@19.2.5)
|
||||
tslib: 2.8.1
|
||||
use-callback-ref: 1.3.3(@types/react@19.2.14)(react@19.2.5)
|
||||
use-sidecar: 1.1.3(@types/react@19.2.14)(react@19.2.5)
|
||||
optionalDependencies:
|
||||
'@types/react': 19.2.14
|
||||
|
||||
react-style-singleton@2.2.3(@types/react@19.2.14)(react@19.2.5):
|
||||
dependencies:
|
||||
get-nonce: 1.0.1
|
||||
react: 19.2.5
|
||||
tslib: 2.8.1
|
||||
optionalDependencies:
|
||||
'@types/react': 19.2.14
|
||||
|
||||
react@19.2.5: {}
|
||||
|
||||
readdirp@3.6.0:
|
||||
@@ -4256,6 +4780,21 @@ snapshots:
|
||||
|
||||
uri-js-replace@1.0.1: {}
|
||||
|
||||
use-callback-ref@1.3.3(@types/react@19.2.14)(react@19.2.5):
|
||||
dependencies:
|
||||
react: 19.2.5
|
||||
tslib: 2.8.1
|
||||
optionalDependencies:
|
||||
'@types/react': 19.2.14
|
||||
|
||||
use-sidecar@1.1.3(@types/react@19.2.14)(react@19.2.5):
|
||||
dependencies:
|
||||
detect-node-es: 1.1.0
|
||||
react: 19.2.5
|
||||
tslib: 2.8.1
|
||||
optionalDependencies:
|
||||
'@types/react': 19.2.14
|
||||
|
||||
use-sync-external-store@1.6.0(react@19.2.5):
|
||||
dependencies:
|
||||
react: 19.2.5
|
||||
|
||||
@@ -11,6 +11,11 @@ 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 { Button } from "@/components/ui/button";
|
||||
import { Input, Textarea } from "@/components/ui/input";
|
||||
import { Label } from "@/components/ui/label";
|
||||
import { PageTitle, Body, Muted, Caption, MonoCaption } from "@/components/ui/typography";
|
||||
import { COLOR } from "@/lib/constants";
|
||||
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";
|
||||
@@ -83,9 +88,7 @@ export function ConclusionBrowser() {
|
||||
</Link>
|
||||
<div className="flex items-center gap-2 mb-1">
|
||||
<Lightbulb className="w-5 h-5" style={{ color: "var(--accent)" }} strokeWidth={1.5} />
|
||||
<h1 className="text-xl font-semibold tracking-tight" style={{ color: "var(--text-1)" }}>
|
||||
Conclusions
|
||||
</h1>
|
||||
<PageTitle>Conclusions</PageTitle>
|
||||
{total > 0 && !activeSearch && (
|
||||
<span
|
||||
className="ml-auto text-xs font-mono px-2 py-0.5 rounded-full"
|
||||
@@ -98,22 +101,17 @@ export function ConclusionBrowser() {
|
||||
{total}
|
||||
</span>
|
||||
)}
|
||||
<button
|
||||
<Button
|
||||
variant="accent"
|
||||
size="sm"
|
||||
onClick={() => setCreateOpen(true)}
|
||||
className="ml-auto flex items-center gap-1.5 px-3 py-1.5 rounded-lg text-xs font-medium transition-colors"
|
||||
style={{
|
||||
background: "var(--accent-dim)",
|
||||
border: "1px solid var(--accent-border)",
|
||||
color: "var(--accent-text)",
|
||||
}}
|
||||
className="ml-auto"
|
||||
>
|
||||
<Plus className="w-3.5 h-3.5" strokeWidth={2} />
|
||||
New
|
||||
</button>
|
||||
</Button>
|
||||
</div>
|
||||
<p className="text-sm mt-0.5" style={{ color: "var(--text-3)" }}>
|
||||
Distilled memory observations about peers
|
||||
</p>
|
||||
<Muted className="mt-0.5">Distilled memory observations about peers</Muted>
|
||||
</motion.div>
|
||||
|
||||
{/* Search */}
|
||||
@@ -124,41 +122,36 @@ export function ConclusionBrowser() {
|
||||
style={{ color: "var(--text-4)" }}
|
||||
strokeWidth={1.5}
|
||||
/>
|
||||
<input
|
||||
<Input
|
||||
type="text"
|
||||
value={searchQuery}
|
||||
onChange={(e) => setSearchQuery(e.target.value)}
|
||||
placeholder="Semantic search across conclusions..."
|
||||
className="theme-input w-full rounded-xl pl-9 pr-4 py-2.5 text-sm font-mono"
|
||||
className="rounded-xl pl-9 pr-4 py-2.5 font-mono"
|
||||
/>
|
||||
</div>
|
||||
<button
|
||||
type="submit"
|
||||
className="px-4 py-2.5 rounded-xl text-sm font-medium transition-all"
|
||||
style={{ background: "var(--accent)", color: "#fff" }}
|
||||
>
|
||||
<Button type="submit" variant="primary" className="rounded-xl">
|
||||
Search
|
||||
</button>
|
||||
</Button>
|
||||
<AnimatePresence>
|
||||
{activeSearch && (
|
||||
<motion.button
|
||||
type="button"
|
||||
<motion.div
|
||||
initial={{ opacity: 0, scale: 0.8 }}
|
||||
animate={{ opacity: 1, scale: 1 }}
|
||||
exit={{ opacity: 0, scale: 0.8 }}
|
||||
onClick={() => {
|
||||
setActiveSearch("");
|
||||
setSearchQuery("");
|
||||
}}
|
||||
className="px-3 py-2.5 rounded-xl text-sm transition-all"
|
||||
style={{
|
||||
background: "var(--surface)",
|
||||
border: "1px solid var(--border)",
|
||||
color: "var(--text-3)",
|
||||
}}
|
||||
>
|
||||
<X className="w-4 h-4" strokeWidth={1.5} />
|
||||
</motion.button>
|
||||
<Button
|
||||
type="button"
|
||||
variant="surface"
|
||||
onClick={() => {
|
||||
setActiveSearch("");
|
||||
setSearchQuery("");
|
||||
}}
|
||||
className="rounded-xl"
|
||||
>
|
||||
<X className="w-4 h-4" strokeWidth={1.5} />
|
||||
</Button>
|
||||
</motion.div>
|
||||
)}
|
||||
</AnimatePresence>
|
||||
</form>
|
||||
@@ -206,19 +199,16 @@ export function ConclusionBrowser() {
|
||||
}}
|
||||
>
|
||||
<div className="flex items-start justify-between gap-3">
|
||||
<p
|
||||
className="text-sm leading-relaxed whitespace-pre-wrap flex-1"
|
||||
style={{ color: "var(--text-2)" }}
|
||||
>
|
||||
{c.content}
|
||||
</p>
|
||||
<button
|
||||
<Body className="whitespace-pre-wrap flex-1">{c.content}</Body>
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
onClick={() => setDeleteTarget(c.id)}
|
||||
className="opacity-0 group-hover:opacity-100 p-1.5 rounded-lg transition-all flex-shrink-0"
|
||||
style={{ color: "var(--text-4)" }}
|
||||
className="opacity-0 group-hover:opacity-100 flex-shrink-0"
|
||||
aria-label="Delete conclusion"
|
||||
>
|
||||
<Trash2 className="w-3.5 h-3.5" strokeWidth={1.5} />
|
||||
</button>
|
||||
</Button>
|
||||
</div>
|
||||
<div
|
||||
className="flex items-center gap-3 mt-4 pt-3"
|
||||
@@ -226,18 +216,12 @@ export function ConclusionBrowser() {
|
||||
>
|
||||
<div className="flex items-center gap-1.5">
|
||||
<Eye className="w-3 h-3" style={{ color: "var(--text-4)" }} strokeWidth={1.5} />
|
||||
<span className="text-xs font-mono" style={{ color: "var(--text-4)" }}>
|
||||
{c.observer_id}
|
||||
</span>
|
||||
<MonoCaption>{c.observer_id}</MonoCaption>
|
||||
</div>
|
||||
{c.observed_id && (
|
||||
<div className="flex items-center gap-1">
|
||||
<span className="text-xs" style={{ color: "var(--text-4)" }}>
|
||||
→
|
||||
</span>
|
||||
<span className="text-xs font-mono" style={{ color: "var(--text-4)" }}>
|
||||
{c.observed_id}
|
||||
</span>
|
||||
<Caption>→</Caption>
|
||||
<MonoCaption>{c.observed_id}</MonoCaption>
|
||||
</div>
|
||||
)}
|
||||
{c.session_id && (
|
||||
@@ -258,9 +242,7 @@ export function ConclusionBrowser() {
|
||||
style={{ color: "var(--text-4)" }}
|
||||
strokeWidth={1.5}
|
||||
/>
|
||||
<span className="text-xs font-mono" style={{ color: "var(--text-4)" }}>
|
||||
{new Date(c.created_at).toLocaleString()}
|
||||
</span>
|
||||
<MonoCaption>{new Date(c.created_at).toLocaleString()}</MonoCaption>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
@@ -352,81 +334,71 @@ function CreateConclusionModal({
|
||||
<form onSubmit={handleSubmit} className="space-y-3">
|
||||
{(["observer_id", "observed_id"] as const).map((field) => (
|
||||
<div key={field}>
|
||||
<label className="block text-xs font-medium mb-1" style={{ color: "var(--text-2)" }}>
|
||||
<Label className="mb-1">
|
||||
{field === "observer_id" ? "Observer peer ID" : "Observed peer ID"}{" "}
|
||||
<span style={{ color: "#f87171" }}>*</span>
|
||||
</label>
|
||||
<input
|
||||
<span style={{ color: COLOR.destructive }}>*</span>
|
||||
</Label>
|
||||
<Input
|
||||
value={fields[field]}
|
||||
onChange={set(field)}
|
||||
placeholder="peer_id"
|
||||
className="theme-input w-full text-sm px-3 py-2 rounded-lg"
|
||||
/>
|
||||
{validationErrors[field] && (
|
||||
<p className="text-xs mt-1" style={{ color: "#f87171" }}>
|
||||
<p className="text-xs mt-1" style={{ color: COLOR.destructive }}>
|
||||
{validationErrors[field]}
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
))}
|
||||
<div>
|
||||
<label className="block text-xs font-medium mb-1" style={{ color: "var(--text-2)" }}>
|
||||
Content <span style={{ color: "#f87171" }}>*</span>
|
||||
</label>
|
||||
<textarea
|
||||
<Label className="mb-1">
|
||||
Content <span style={{ color: COLOR.destructive }}>*</span>
|
||||
</Label>
|
||||
<Textarea
|
||||
value={fields.content}
|
||||
onChange={set("content")}
|
||||
rows={4}
|
||||
placeholder="The conclusion content…"
|
||||
className="theme-input w-full text-sm px-3 py-2 rounded-lg resize-y"
|
||||
className="resize-y"
|
||||
/>
|
||||
{validationErrors.content && (
|
||||
<p className="text-xs mt-1" style={{ color: "#f87171" }}>
|
||||
<p className="text-xs mt-1" style={{ color: COLOR.destructive }}>
|
||||
{validationErrors.content}
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
<div>
|
||||
<label className="block text-xs font-medium mb-1" style={{ color: "var(--text-2)" }}>
|
||||
<Label className="mb-1">
|
||||
Session ID <span style={{ color: "var(--text-4)" }}>(optional)</span>
|
||||
</label>
|
||||
<input
|
||||
</Label>
|
||||
<Input
|
||||
value={fields.session_id}
|
||||
onChange={set("session_id")}
|
||||
placeholder="session_id"
|
||||
className="theme-input w-full text-sm px-3 py-2 rounded-lg"
|
||||
/>
|
||||
</div>
|
||||
{error && (
|
||||
<p className="text-xs" style={{ color: "#f87171" }}>
|
||||
<p className="text-xs" style={{ color: COLOR.destructive }}>
|
||||
{error}
|
||||
</p>
|
||||
)}
|
||||
<div className="flex justify-end gap-2 pt-2">
|
||||
<button
|
||||
<Button
|
||||
type="button"
|
||||
variant="surface"
|
||||
size="sm"
|
||||
onClick={onClose}
|
||||
className="px-3 py-1.5 text-sm rounded-lg"
|
||||
style={{
|
||||
background: "var(--surface)",
|
||||
border: "1px solid var(--border)",
|
||||
color: "var(--text-2)",
|
||||
}}
|
||||
>
|
||||
Cancel
|
||||
</button>
|
||||
<button
|
||||
</Button>
|
||||
<Button
|
||||
type="submit"
|
||||
variant="accent"
|
||||
size="sm"
|
||||
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)",
|
||||
}}
|
||||
>
|
||||
{loading ? "Creating..." : "Create"}
|
||||
</button>
|
||||
</Button>
|
||||
</div>
|
||||
</form>
|
||||
</FormModal>
|
||||
|
||||
@@ -12,6 +12,9 @@ import { JsonViewer } from "@/components/shared/JsonViewer";
|
||||
import { PageLoader } from "@/components/shared/LoadingSpinner";
|
||||
import { MarkdownRenderer } from "@/components/shared/MarkdownRenderer";
|
||||
import { PeerCardViewer } from "@/components/shared/PeerCardViewer";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Input, Textarea } from "@/components/ui/input";
|
||||
import { PageTitle, SectionHeading, Body, Muted, Caption } from "@/components/ui/typography";
|
||||
import { Link, useNavigate, useParams } from "@tanstack/react-router";
|
||||
import { AnimatePresence, motion } from "framer-motion";
|
||||
import { MessageCircle, Save, Search, User, X } from "lucide-react";
|
||||
@@ -84,30 +87,25 @@ export function PeerDetail() {
|
||||
<div>
|
||||
<div className="flex items-center gap-2 mb-1">
|
||||
<User className="w-5 h-5" style={{ color: "var(--accent)" }} strokeWidth={1.5} />
|
||||
<h1
|
||||
className="text-xl font-semibold font-mono break-all tracking-tight"
|
||||
style={{ color: "var(--text-1)" }}
|
||||
>
|
||||
<PageTitle className="font-mono break-all">
|
||||
{peerId}
|
||||
</h1>
|
||||
</PageTitle>
|
||||
</div>
|
||||
<p className="text-sm" style={{ color: "var(--text-2)" }}>
|
||||
Peer identity & memory
|
||||
</p>
|
||||
<Body className="leading-none">Peer identity & memory</Body>
|
||||
</div>
|
||||
<button
|
||||
<Button
|
||||
variant="primary"
|
||||
onClick={() =>
|
||||
navigate({
|
||||
to: "/workspaces/$workspaceId/peers/$peerId/chat",
|
||||
params: { workspaceId, peerId } as never,
|
||||
})
|
||||
}
|
||||
className="shrink-0 flex items-center gap-2 px-4 py-2 rounded-xl text-sm font-medium transition-all"
|
||||
style={{ background: "var(--accent)", color: "#fff" }}
|
||||
className="shrink-0 rounded-xl"
|
||||
>
|
||||
<MessageCircle className="w-4 h-4" strokeWidth={1.5} />
|
||||
Chat
|
||||
</button>
|
||||
</Button>
|
||||
</div>
|
||||
</motion.div>
|
||||
|
||||
@@ -153,16 +151,9 @@ export function PeerDetail() {
|
||||
<PageLoader />
|
||||
) : (
|
||||
<>
|
||||
<h2 className="text-sm font-medium mb-3" style={{ color: "var(--text-1)" }}>
|
||||
Peer Context
|
||||
</h2>
|
||||
<SectionHeading>Peer Context</SectionHeading>
|
||||
{typeof context === "string" ? (
|
||||
<p
|
||||
className="text-sm whitespace-pre-wrap leading-relaxed"
|
||||
style={{ color: "var(--text-2)" }}
|
||||
>
|
||||
{context}
|
||||
</p>
|
||||
<Body className="whitespace-pre-wrap">{context}</Body>
|
||||
) : (
|
||||
<JsonViewer data={context} />
|
||||
)}
|
||||
@@ -175,65 +166,50 @@ export function PeerDetail() {
|
||||
) : (
|
||||
<>
|
||||
<div className="flex items-center justify-between mb-3">
|
||||
<h2 className="text-sm font-medium" style={{ color: "var(--text-1)" }}>
|
||||
Peer Card
|
||||
</h2>
|
||||
<SectionHeading className="mb-0">Peer Card</SectionHeading>
|
||||
{cardDraft === null ? (
|
||||
<button
|
||||
<Button
|
||||
variant="accent"
|
||||
size="sm"
|
||||
onClick={() => setCardDraft(cardLines.join("\n"))}
|
||||
className="text-xs px-2 py-1 rounded-lg transition-colors"
|
||||
style={{
|
||||
background: "var(--accent-dim)",
|
||||
border: "1px solid var(--accent-border)",
|
||||
color: "var(--accent-text)",
|
||||
}}
|
||||
>
|
||||
Edit
|
||||
</button>
|
||||
</Button>
|
||||
) : (
|
||||
<div className="flex gap-1.5">
|
||||
<button
|
||||
<Button
|
||||
variant="accent"
|
||||
size="sm"
|
||||
onClick={() => {
|
||||
setPeerCard.mutate(cardDraft.split("\n").filter(Boolean));
|
||||
setCardDraft(null);
|
||||
}}
|
||||
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)",
|
||||
}}
|
||||
>
|
||||
<Save className="w-3 h-3" strokeWidth={2} />
|
||||
Save
|
||||
</button>
|
||||
<button
|
||||
</Button>
|
||||
<Button
|
||||
variant="surface"
|
||||
size="sm"
|
||||
onClick={() => setCardDraft(null)}
|
||||
className="flex items-center gap-1 text-xs px-2 py-1 rounded-lg"
|
||||
style={{
|
||||
background: "var(--surface)",
|
||||
border: "1px solid var(--border)",
|
||||
color: "var(--text-3)",
|
||||
}}
|
||||
>
|
||||
<X className="w-3 h-3" strokeWidth={2} />
|
||||
</button>
|
||||
</Button>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
<AnimatePresence mode="wait">
|
||||
{cardDraft !== null ? (
|
||||
<motion.textarea
|
||||
key="edit"
|
||||
initial={{ opacity: 0 }}
|
||||
animate={{ opacity: 1 }}
|
||||
value={cardDraft}
|
||||
onChange={(e) => setCardDraft(e.target.value)}
|
||||
rows={8}
|
||||
className="theme-input w-full text-sm px-3 py-2 rounded-lg font-mono resize-y"
|
||||
style={{ minHeight: "8rem" }}
|
||||
/>
|
||||
<motion.div key="edit" initial={{ opacity: 0 }} animate={{ opacity: 1 }}>
|
||||
<Textarea
|
||||
value={cardDraft}
|
||||
onChange={(e) => setCardDraft(e.target.value)}
|
||||
rows={8}
|
||||
className="font-mono resize-y"
|
||||
style={{ minHeight: "8rem" }}
|
||||
/>
|
||||
</motion.div>
|
||||
) : (
|
||||
<motion.div key="view" initial={{ opacity: 0 }} animate={{ opacity: 1 }}>
|
||||
<PeerCardViewer lines={cardLines} />
|
||||
@@ -248,9 +224,7 @@ export function PeerDetail() {
|
||||
<PageLoader />
|
||||
) : (
|
||||
<>
|
||||
<h2 className="text-sm font-medium mb-3" style={{ color: "var(--text-1)" }}>
|
||||
Memory Representation
|
||||
</h2>
|
||||
<SectionHeading>Memory Representation</SectionHeading>
|
||||
{representation &&
|
||||
typeof (representation as { representation?: unknown }).representation ===
|
||||
"string" ? (
|
||||
@@ -265,10 +239,10 @@ export function PeerDetail() {
|
||||
|
||||
{tab === "search" && (
|
||||
<>
|
||||
<h2 className="text-sm font-medium mb-3" style={{ color: "var(--text-1)" }}>
|
||||
<SectionHeading>
|
||||
<Search className="w-3.5 h-3.5 inline mr-1.5" strokeWidth={2} />
|
||||
Search peer messages
|
||||
</h2>
|
||||
</SectionHeading>
|
||||
<form
|
||||
onSubmit={(e) => {
|
||||
e.preventDefault();
|
||||
@@ -276,25 +250,20 @@ export function PeerDetail() {
|
||||
}}
|
||||
className="flex gap-2 mb-4"
|
||||
>
|
||||
<input
|
||||
<Input
|
||||
autoFocus
|
||||
value={searchQuery}
|
||||
onChange={(e) => setSearchQuery(e.target.value)}
|
||||
placeholder="Semantic search across this peer's messages…"
|
||||
className="theme-input flex-1 text-sm px-3 py-2 rounded-lg"
|
||||
className="flex-1"
|
||||
/>
|
||||
<button
|
||||
<Button
|
||||
type="submit"
|
||||
variant="accent"
|
||||
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)",
|
||||
}}
|
||||
>
|
||||
{searchPeer.isPending ? "…" : "Search"}
|
||||
</button>
|
||||
</Button>
|
||||
</form>
|
||||
{searchPeer.data && (
|
||||
<div className="space-y-3">
|
||||
@@ -306,9 +275,7 @@ export function PeerDetail() {
|
||||
created_at?: string;
|
||||
}>
|
||||
).length === 0 ? (
|
||||
<p className="text-sm" style={{ color: "var(--text-3)" }}>
|
||||
No results.
|
||||
</p>
|
||||
<Muted>No results.</Muted>
|
||||
) : (
|
||||
(
|
||||
searchPeer.data as Array<{
|
||||
@@ -329,17 +296,10 @@ export function PeerDetail() {
|
||||
<div className="flex items-center gap-2 mb-1.5">
|
||||
<Badge variant="blue">{r.peer_id ?? peerId}</Badge>
|
||||
{r.created_at && (
|
||||
<span className="text-xs" style={{ color: "var(--text-4)" }}>
|
||||
{new Date(r.created_at).toLocaleString()}
|
||||
</span>
|
||||
<Caption>{new Date(r.created_at).toLocaleString()}</Caption>
|
||||
)}
|
||||
</div>
|
||||
<p
|
||||
className="text-sm whitespace-pre-wrap"
|
||||
style={{ color: "var(--text-2)" }}
|
||||
>
|
||||
{r.content}
|
||||
</p>
|
||||
<Body className="whitespace-pre-wrap">{r.content}</Body>
|
||||
</div>
|
||||
))
|
||||
)}
|
||||
@@ -350,9 +310,7 @@ export function PeerDetail() {
|
||||
|
||||
{tab === "metadata" && (
|
||||
<>
|
||||
<h2 className="text-sm font-medium mb-3" style={{ color: "var(--text-1)" }}>
|
||||
Peer Metadata
|
||||
</h2>
|
||||
<SectionHeading>Peer Metadata</SectionHeading>
|
||||
<JsonViewer data={peer.metadata} maxHeight="400px" />
|
||||
</>
|
||||
)}
|
||||
|
||||
@@ -4,6 +4,8 @@ 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 { PageTitle, MonoCaption } from "@/components/ui/typography";
|
||||
import { COLOR } from "@/lib/constants";
|
||||
import { Link, useNavigate, useParams } from "@tanstack/react-router";
|
||||
import { type Variants, motion } from "framer-motion";
|
||||
import { ArrowLeft, ChevronRight, Clock, Eye, Users } from "lucide-react";
|
||||
@@ -44,25 +46,21 @@ export function PeerList() {
|
||||
</Link>
|
||||
<div className="flex items-center gap-2 mb-1">
|
||||
<Users className="w-5 h-5" style={{ color: "#6366f1" }} strokeWidth={1.5} />
|
||||
<h1 className="text-xl font-semibold tracking-tight" style={{ color: "#e4e4f0" }}>
|
||||
Peers
|
||||
</h1>
|
||||
<PageTitle>Peers</PageTitle>
|
||||
{total > 0 && (
|
||||
<span
|
||||
className="ml-auto text-xs font-mono px-2 py-0.5 rounded-full"
|
||||
style={{
|
||||
background: "rgba(99,102,241,0.1)",
|
||||
color: "#818cf8",
|
||||
border: "1px solid rgba(99,102,241,0.2)",
|
||||
background: COLOR.accentSubtle,
|
||||
color: COLOR.accentText,
|
||||
border: `1px solid ${COLOR.accentBorder}`,
|
||||
}}
|
||||
>
|
||||
{total}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
<p className="text-xs font-mono mt-0.5" style={{ color: "rgba(148,163,184,0.4)" }}>
|
||||
{workspaceId}
|
||||
</p>
|
||||
<MonoCaption className="mt-0.5" as="p">{workspaceId}</MonoCaption>
|
||||
</motion.div>
|
||||
|
||||
<ErrorAlert error={error instanceof Error ? error : null} />
|
||||
@@ -120,8 +118,8 @@ export function PeerList() {
|
||||
<div className="flex items-center gap-2 flex-wrap">
|
||||
{(peer.configuration as { observe_me?: boolean } | null)?.observe_me && (
|
||||
<div className="flex items-center gap-1">
|
||||
<Eye className="w-3 h-3" style={{ color: "#818cf8" }} strokeWidth={1.5} />
|
||||
<span className="text-xs" style={{ color: "#818cf8" }}>
|
||||
<Eye className="w-3 h-3" style={{ color: COLOR.accentText }} strokeWidth={1.5} />
|
||||
<span className="text-xs" style={{ color: COLOR.accentText }}>
|
||||
observed
|
||||
</span>
|
||||
</div>
|
||||
@@ -133,9 +131,7 @@ export function PeerList() {
|
||||
style={{ color: "rgba(148,163,184,0.3)" }}
|
||||
strokeWidth={1.5}
|
||||
/>
|
||||
<p className="text-xs font-mono" style={{ color: "rgba(148,163,184,0.3)" }}>
|
||||
{new Date(peer.created_at).toLocaleString()}
|
||||
</p>
|
||||
<MonoCaption>{new Date(peer.created_at).toLocaleString()}</MonoCaption>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
@@ -16,6 +16,9 @@ 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 { Button } from "@/components/ui/button";
|
||||
import { Input } from "@/components/ui/input";
|
||||
import { PageTitle, SectionHeading, Body, Muted, Caption, MonoCaption } from "@/components/ui/typography";
|
||||
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";
|
||||
@@ -124,53 +127,39 @@ export function SessionDetail() {
|
||||
style={{ color: "var(--accent)" }}
|
||||
strokeWidth={1.5}
|
||||
/>
|
||||
<h1
|
||||
className="text-xl font-semibold font-mono break-all tracking-tight"
|
||||
style={{ color: "var(--text-1)" }}
|
||||
>
|
||||
<PageTitle className="font-mono break-all">
|
||||
{sessionId}
|
||||
</h1>
|
||||
</PageTitle>
|
||||
</div>
|
||||
<div className="flex items-center gap-2 flex-shrink-0">
|
||||
<button
|
||||
<Button
|
||||
variant={searchActive ? "accent" : "surface"}
|
||||
size="icon"
|
||||
onClick={() => setSearchActive((v) => !v)}
|
||||
className="p-1.5 rounded-lg transition-colors"
|
||||
style={{
|
||||
background: searchActive ? "var(--accent-dim)" : "var(--surface)",
|
||||
border: `1px solid ${searchActive ? "var(--accent-border)" : "var(--border)"}`,
|
||||
color: searchActive ? "var(--accent-text)" : "var(--text-3)",
|
||||
}}
|
||||
aria-label="Search session"
|
||||
>
|
||||
<Search className="w-3.5 h-3.5" strokeWidth={2} />
|
||||
</button>
|
||||
<button
|
||||
</Button>
|
||||
<Button
|
||||
variant="surface"
|
||||
size="icon"
|
||||
onClick={handleClone}
|
||||
disabled={cloneSession.isPending}
|
||||
className="p-1.5 rounded-lg transition-colors disabled:opacity-50"
|
||||
style={{
|
||||
background: "var(--surface)",
|
||||
border: "1px solid var(--border)",
|
||||
color: "var(--text-3)",
|
||||
}}
|
||||
aria-label="Clone session"
|
||||
>
|
||||
<Copy className="w-3.5 h-3.5" strokeWidth={2} />
|
||||
</button>
|
||||
<button
|
||||
</Button>
|
||||
<Button
|
||||
variant="destructive"
|
||||
size="icon"
|
||||
onClick={() => setConfirmDelete(true)}
|
||||
className="p-1.5 rounded-lg transition-colors"
|
||||
style={{
|
||||
background: "rgba(239,68,68,0.08)",
|
||||
border: "1px solid rgba(239,68,68,0.2)",
|
||||
color: "#f87171",
|
||||
}}
|
||||
aria-label="Delete session"
|
||||
>
|
||||
<Trash2 className="w-3.5 h-3.5" strokeWidth={2} />
|
||||
</button>
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
<p className="text-sm" style={{ color: "var(--text-2)" }}>
|
||||
Session detail
|
||||
</p>
|
||||
<Body className="leading-none">Session detail</Body>
|
||||
</motion.div>
|
||||
|
||||
{/* Inline search bar */}
|
||||
@@ -189,33 +178,26 @@ export function SessionDetail() {
|
||||
}}
|
||||
className="flex gap-2"
|
||||
>
|
||||
<input
|
||||
<Input
|
||||
autoFocus
|
||||
value={searchQuery}
|
||||
onChange={(e) => setSearchQuery(e.target.value)}
|
||||
placeholder="Search within this session…"
|
||||
className="theme-input flex-1 text-sm px-3 py-2 rounded-lg"
|
||||
className="flex-1"
|
||||
/>
|
||||
<button
|
||||
<Button
|
||||
type="submit"
|
||||
variant="accent"
|
||||
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)",
|
||||
}}
|
||||
>
|
||||
{searchSession.isPending ? "…" : "Search"}
|
||||
</button>
|
||||
</Button>
|
||||
</form>
|
||||
{searchSession.data && (
|
||||
<div className="mt-3 rounded-xl p-4 theme-card space-y-2">
|
||||
{(searchSession.data as Array<{ id: string; content: string; peer_id?: string }>)
|
||||
.length === 0 ? (
|
||||
<p className="text-sm" style={{ color: "var(--text-3)" }}>
|
||||
No results.
|
||||
</p>
|
||||
<Muted>No results.</Muted>
|
||||
) : (
|
||||
(
|
||||
searchSession.data as Array<{ id: string; content: string; peer_id?: string }>
|
||||
@@ -275,9 +257,7 @@ export function SessionDetail() {
|
||||
) : (
|
||||
<div>
|
||||
{messages.length === 0 ? (
|
||||
<p className="text-sm" style={{ color: "var(--text-3)" }}>
|
||||
No messages.
|
||||
</p>
|
||||
<Muted>No messages.</Muted>
|
||||
) : (
|
||||
<div className="space-y-4">
|
||||
{messages.map((msg) => (
|
||||
@@ -291,22 +271,13 @@ export function SessionDetail() {
|
||||
{msg.peer_id ?? "system"}
|
||||
</Badge>
|
||||
{msg.token_count != null && (
|
||||
<span className="text-xs" style={{ color: "var(--text-4)" }}>
|
||||
{msg.token_count} tokens
|
||||
</span>
|
||||
<Caption>{msg.token_count} tokens</Caption>
|
||||
)}
|
||||
{msg.created_at && (
|
||||
<span className="text-xs" style={{ color: "var(--text-4)" }}>
|
||||
{new Date(msg.created_at).toLocaleString()}
|
||||
</span>
|
||||
<Caption>{new Date(msg.created_at).toLocaleString()}</Caption>
|
||||
)}
|
||||
</div>
|
||||
<p
|
||||
className="text-sm whitespace-pre-wrap leading-relaxed"
|
||||
style={{ color: "var(--text-2)" }}
|
||||
>
|
||||
{msg.content}
|
||||
</p>
|
||||
<Body className="whitespace-pre-wrap">{msg.content}</Body>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
@@ -323,16 +294,9 @@ export function SessionDetail() {
|
||||
<PageLoader />
|
||||
) : (
|
||||
<>
|
||||
<h2 className="text-sm font-medium mb-3" style={{ color: "var(--text-1)" }}>
|
||||
Session Context
|
||||
</h2>
|
||||
<SectionHeading>Session Context</SectionHeading>
|
||||
{typeof context === "string" ? (
|
||||
<p
|
||||
className="text-sm whitespace-pre-wrap leading-relaxed"
|
||||
style={{ color: "var(--text-2)" }}
|
||||
>
|
||||
{context}
|
||||
</p>
|
||||
<Body className="whitespace-pre-wrap">{context}</Body>
|
||||
) : (
|
||||
<JsonViewer data={context} maxHeight="500px" />
|
||||
)}
|
||||
@@ -388,14 +352,12 @@ function SessionPeersTab({
|
||||
return (
|
||||
<div className="space-y-4">
|
||||
<div>
|
||||
<h2 className="text-sm font-medium mb-2" style={{ color: "var(--text-1)" }}>
|
||||
<SectionHeading className="mb-2">
|
||||
<Users className="w-3.5 h-3.5 inline mr-1.5" strokeWidth={2} />
|
||||
Session members ({list.length})
|
||||
</h2>
|
||||
</SectionHeading>
|
||||
{list.length === 0 ? (
|
||||
<p className="text-sm" style={{ color: "var(--text-3)" }}>
|
||||
No peers in this session.
|
||||
</p>
|
||||
<Muted>No peers in this session.</Muted>
|
||||
) : (
|
||||
<div className="space-y-1">
|
||||
{list.map((p) => {
|
||||
@@ -409,14 +371,15 @@ function SessionPeersTab({
|
||||
<span className="text-xs font-mono" style={{ color: "var(--accent-text)" }}>
|
||||
{id}
|
||||
</span>
|
||||
<button
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
onClick={() => onRemove(id)}
|
||||
disabled={removing}
|
||||
className="p-1 rounded transition-colors disabled:opacity-40"
|
||||
style={{ color: "var(--text-4)" }}
|
||||
aria-label={`Remove ${id}`}
|
||||
>
|
||||
<X className="w-3 h-3" strokeWidth={2} />
|
||||
</button>
|
||||
</Button>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
@@ -426,9 +389,7 @@ function SessionPeersTab({
|
||||
|
||||
{available.length > 0 && (
|
||||
<div>
|
||||
<h2 className="text-sm font-medium mb-2" style={{ color: "var(--text-2)" }}>
|
||||
Add peer
|
||||
</h2>
|
||||
<SectionHeading className="mb-2">Add peer</SectionHeading>
|
||||
<div className="space-y-1 max-h-48 overflow-auto">
|
||||
{available.map((p) => (
|
||||
<button
|
||||
@@ -467,23 +428,17 @@ function SummaryCard({ label, summary }: { label: string; summary: Summary }) {
|
||||
</div>
|
||||
<div className="flex items-center gap-3">
|
||||
{summary.token_count != null && (
|
||||
<span className="text-xs font-mono" style={{ color: "var(--text-4)" }}>
|
||||
{summary.token_count} tok
|
||||
</span>
|
||||
<MonoCaption>{summary.token_count} tok</MonoCaption>
|
||||
)}
|
||||
{summary.created_at && (
|
||||
<div className="flex items-center gap-1">
|
||||
<Clock className="w-3 h-3" style={{ color: "var(--text-4)" }} strokeWidth={1.5} />
|
||||
<span className="text-xs font-mono" style={{ color: "var(--text-4)" }}>
|
||||
{new Date(summary.created_at).toLocaleString()}
|
||||
</span>
|
||||
<MonoCaption>{new Date(summary.created_at).toLocaleString()}</MonoCaption>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
<p className="text-sm leading-relaxed whitespace-pre-wrap" style={{ color: "var(--text-2)" }}>
|
||||
{summary.content}
|
||||
</p>
|
||||
<Body className="whitespace-pre-wrap">{summary.content}</Body>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -494,21 +449,15 @@ function SummariesDisplay({ summaries }: { summaries: unknown }) {
|
||||
if (!data || (!data.short_summary && !data.long_summary)) {
|
||||
return (
|
||||
<>
|
||||
<h2 className="text-sm font-medium mb-3" style={{ color: "var(--text-1)" }}>
|
||||
Session Summaries
|
||||
</h2>
|
||||
<p className="text-sm" style={{ color: "var(--text-4)" }}>
|
||||
No summaries available yet.
|
||||
</p>
|
||||
<SectionHeading>Session Summaries</SectionHeading>
|
||||
<Caption as="p">No summaries available yet.</Caption>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<h2 className="text-sm font-medium mb-3" style={{ color: "var(--text-1)" }}>
|
||||
Session Summaries
|
||||
</h2>
|
||||
<SectionHeading>Session Summaries</SectionHeading>
|
||||
<div className="space-y-3">
|
||||
{data.short_summary && <SummaryCard label="Short summary" summary={data.short_summary} />}
|
||||
{data.long_summary && <SummaryCard label="Long summary" summary={data.long_summary} />}
|
||||
|
||||
@@ -4,6 +4,8 @@ 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 { PageTitle, MonoCaption } from "@/components/ui/typography";
|
||||
import { COLOR } from "@/lib/constants";
|
||||
import { Link, useNavigate, useParams } from "@tanstack/react-router";
|
||||
import { type Variants, motion } from "framer-motion";
|
||||
import { ArrowLeft, ChevronRight, CircleDot, Clock, MessageSquare } from "lucide-react";
|
||||
@@ -44,25 +46,21 @@ export function SessionList() {
|
||||
</Link>
|
||||
<div className="flex items-center gap-2 mb-1">
|
||||
<MessageSquare className="w-5 h-5" style={{ color: "#6366f1" }} strokeWidth={1.5} />
|
||||
<h1 className="text-xl font-semibold tracking-tight" style={{ color: "#e4e4f0" }}>
|
||||
Sessions
|
||||
</h1>
|
||||
<PageTitle>Sessions</PageTitle>
|
||||
{total > 0 && (
|
||||
<span
|
||||
className="ml-auto text-xs font-mono px-2 py-0.5 rounded-full"
|
||||
style={{
|
||||
background: "rgba(99,102,241,0.1)",
|
||||
color: "#818cf8",
|
||||
border: "1px solid rgba(99,102,241,0.2)",
|
||||
background: COLOR.accentSubtle,
|
||||
color: COLOR.accentText,
|
||||
border: `1px solid ${COLOR.accentBorder}`,
|
||||
}}
|
||||
>
|
||||
{total}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
<p className="text-xs font-mono mt-0.5" style={{ color: "rgba(148,163,184,0.4)" }}>
|
||||
{workspaceId}
|
||||
</p>
|
||||
<MonoCaption className="mt-0.5" as="p">{workspaceId}</MonoCaption>
|
||||
</motion.div>
|
||||
|
||||
<ErrorAlert error={error instanceof Error ? error : null} />
|
||||
@@ -116,11 +114,11 @@ export function SessionList() {
|
||||
>
|
||||
<CircleDot
|
||||
className="w-3 h-3"
|
||||
style={{ color: "#34d399" }}
|
||||
style={{ color: COLOR.success }}
|
||||
strokeWidth={2}
|
||||
/>
|
||||
</motion.div>
|
||||
<span className="text-xs" style={{ color: "#34d399" }}>
|
||||
<span className="text-xs" style={{ color: COLOR.success }}>
|
||||
Active
|
||||
</span>
|
||||
</div>
|
||||
@@ -140,17 +138,15 @@ export function SessionList() {
|
||||
style={{ color: "rgba(148,163,184,0.3)" }}
|
||||
strokeWidth={1.5}
|
||||
/>
|
||||
<p className="text-xs font-mono" style={{ color: "rgba(148,163,184,0.3)" }}>
|
||||
{new Date(session.created_at).toLocaleString()}
|
||||
</p>
|
||||
<MonoCaption>{new Date(session.created_at).toLocaleString()}</MonoCaption>
|
||||
</div>
|
||||
)}
|
||||
{(session.metadata as Record<string, string> | null)?.source && (
|
||||
<span
|
||||
className="text-xs font-mono px-1.5 py-0.5 rounded"
|
||||
style={{
|
||||
background: "rgba(99,102,241,0.08)",
|
||||
border: "1px solid rgba(99,102,241,0.15)",
|
||||
background: COLOR.accentDim,
|
||||
border: `1px solid ${COLOR.accentBorderStrong}`,
|
||||
color: "rgba(148,163,184,0.6)",
|
||||
}}
|
||||
>
|
||||
|
||||
@@ -9,16 +9,21 @@ import {
|
||||
type Config,
|
||||
type HealthStatus,
|
||||
} from "@/lib/config";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Input, Textarea } from "@/components/ui/input";
|
||||
import { Label } from "@/components/ui/label";
|
||||
import { Muted } from "@/components/ui/typography";
|
||||
import { COLOR } from "@/lib/constants";
|
||||
|
||||
interface SettingsFormProps {
|
||||
onSaved?: () => void;
|
||||
}
|
||||
|
||||
const statusConfig = {
|
||||
ok: { icon: CheckCircle, color: "#34d399", label: "Connected" },
|
||||
"auth-required": { icon: AlertCircle, color: "#f59e0b", label: "Auth required" },
|
||||
unreachable: { icon: WifiOff, color: "#f87171", label: "Unreachable" },
|
||||
checking: { icon: Loader, color: "#818cf8", label: "Checking..." },
|
||||
ok: { icon: CheckCircle, color: COLOR.success, label: "Connected" },
|
||||
"auth-required": { icon: AlertCircle, color: COLOR.warning, label: "Auth required" },
|
||||
unreachable: { icon: WifiOff, color: COLOR.destructive, label: "Unreachable" },
|
||||
checking: { icon: Loader, color: COLOR.accentText, label: "Checking..." },
|
||||
};
|
||||
|
||||
export function SettingsForm({ onSaved }: SettingsFormProps) {
|
||||
@@ -37,7 +42,6 @@ export function SettingsForm({ onSaved }: SettingsFormProps) {
|
||||
setHealth(result);
|
||||
setChecking(false);
|
||||
|
||||
// Auto-show token field if auth is required
|
||||
if (result.status === "auth-required" && !token) {
|
||||
document.getElementById("honcho-token")?.focus();
|
||||
}
|
||||
@@ -77,37 +81,23 @@ export function SettingsForm({ onSaved }: SettingsFormProps) {
|
||||
>
|
||||
{/* Base URL */}
|
||||
<div>
|
||||
<label
|
||||
className="block text-sm font-medium mb-1.5"
|
||||
style={{ color: "var(--text-1)" }}
|
||||
>
|
||||
<Label className="mb-1.5 text-sm">
|
||||
Honcho Base URL
|
||||
</label>
|
||||
</Label>
|
||||
<div className="flex gap-2">
|
||||
<input
|
||||
<Input
|
||||
type="url"
|
||||
value={baseUrl}
|
||||
onChange={(e) => { setBaseUrl(e.target.value); setHealth(null); }}
|
||||
placeholder="http://localhost:8000"
|
||||
className="flex-1 px-3 py-2 text-sm font-mono rounded-xl outline-none transition-all"
|
||||
style={{
|
||||
background: "var(--surface)",
|
||||
border: "1px solid var(--border-2)",
|
||||
color: "var(--text-1)",
|
||||
}}
|
||||
onFocus={(e) => { e.target.style.borderColor = "var(--accent)"; }}
|
||||
onBlur={(e) => { e.target.style.borderColor = "var(--border-2)"; }}
|
||||
className="flex-1 font-mono rounded-xl"
|
||||
/>
|
||||
<button
|
||||
<Button
|
||||
type="button"
|
||||
variant="accent"
|
||||
onClick={handleTest}
|
||||
disabled={checking || !baseUrl}
|
||||
className="px-3 py-2 rounded-xl text-sm font-medium flex items-center gap-1.5 transition-all disabled:opacity-40"
|
||||
style={{
|
||||
background: "var(--accent-dim)",
|
||||
border: "1px solid var(--accent-border)",
|
||||
color: "var(--accent-text)",
|
||||
}}
|
||||
className="rounded-xl"
|
||||
>
|
||||
{checking ? (
|
||||
<motion.div animate={{ rotate: 360 }} transition={{ duration: 1, repeat: Infinity, ease: "linear" }}>
|
||||
@@ -117,14 +107,14 @@ export function SettingsForm({ onSaved }: SettingsFormProps) {
|
||||
<Wifi className="w-4 h-4" strokeWidth={1.5} />
|
||||
)}
|
||||
<span className="hidden sm:block">Test</span>
|
||||
</button>
|
||||
</Button>
|
||||
</div>
|
||||
{errors.baseUrl && (
|
||||
<p className="text-xs mt-1" style={{ color: "#f87171" }}>{errors.baseUrl}</p>
|
||||
<p className="text-xs mt-1" style={{ color: COLOR.destructive }}>{errors.baseUrl}</p>
|
||||
)}
|
||||
<p className="text-xs mt-1.5" style={{ color: "var(--text-3)" }}>
|
||||
<Muted className="text-xs mt-1.5">
|
||||
URL of your self-hosted Honcho instance
|
||||
</p>
|
||||
</Muted>
|
||||
</div>
|
||||
|
||||
{/* Health status */}
|
||||
@@ -154,9 +144,9 @@ export function SettingsForm({ onSaved }: SettingsFormProps) {
|
||||
<p className="text-sm font-medium" style={{ color: statusConfig[health.status].color }}>
|
||||
{statusConfig[health.status].label}
|
||||
</p>
|
||||
<p className="text-xs mt-0.5" style={{ color: "var(--text-3)" }}>
|
||||
<Muted className="text-xs mt-0.5">
|
||||
{health.message}
|
||||
</p>
|
||||
</Muted>
|
||||
</div>
|
||||
</div>
|
||||
</motion.div>
|
||||
@@ -165,10 +155,9 @@ export function SettingsForm({ onSaved }: SettingsFormProps) {
|
||||
|
||||
{/* Token */}
|
||||
<div>
|
||||
<label
|
||||
<Label
|
||||
htmlFor="honcho-token"
|
||||
className="flex items-center gap-1.5 text-sm font-medium mb-1.5"
|
||||
style={{ color: "var(--text-1)" }}
|
||||
className="flex items-center gap-1.5 mb-1.5 text-sm"
|
||||
>
|
||||
{token ? (
|
||||
<Lock className="w-3.5 h-3.5" style={{ color: "var(--accent)" }} strokeWidth={1.5} />
|
||||
@@ -186,44 +175,35 @@ export function SettingsForm({ onSaved }: SettingsFormProps) {
|
||||
>
|
||||
optional
|
||||
</span>
|
||||
</label>
|
||||
<textarea
|
||||
</Label>
|
||||
<Textarea
|
||||
id="honcho-token"
|
||||
value={token}
|
||||
onChange={(e) => setToken(e.target.value)}
|
||||
rows={2}
|
||||
placeholder="eyJ... (required only if your instance has auth enabled)"
|
||||
className="w-full px-3 py-2.5 text-sm rounded-xl font-mono resize-none outline-none transition-all"
|
||||
style={{
|
||||
background: "var(--surface)",
|
||||
border: "1px solid var(--border-2)",
|
||||
color: "var(--text-1)",
|
||||
}}
|
||||
onFocus={(e) => { e.target.style.borderColor = "var(--accent)"; }}
|
||||
onBlur={(e) => { e.target.style.borderColor = "var(--border-2)"; }}
|
||||
className="font-mono rounded-xl"
|
||||
/>
|
||||
{health?.status === "auth-required" && !token && (
|
||||
<motion.p
|
||||
initial={{ opacity: 0, y: -4 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
className="text-xs mt-1"
|
||||
style={{ color: "#f59e0b" }}
|
||||
style={{ color: COLOR.warning }}
|
||||
>
|
||||
This instance requires an API token to proceed
|
||||
</motion.p>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<button
|
||||
<Button
|
||||
type="submit"
|
||||
className="w-full py-2.5 px-4 rounded-xl text-sm font-medium transition-all"
|
||||
style={{
|
||||
background: saved ? "#059669" : "var(--accent)",
|
||||
color: "#fff",
|
||||
}}
|
||||
variant="primary"
|
||||
className="w-full py-2.5 px-4 rounded-xl"
|
||||
style={saved ? { background: "#059669" } : undefined}
|
||||
>
|
||||
{saved ? "✓ Saved" : "Save Connection"}
|
||||
</button>
|
||||
</Button>
|
||||
</form>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,6 +1,13 @@
|
||||
import { useEffect, useRef } from "react";
|
||||
import { motion, AnimatePresence } from "framer-motion";
|
||||
import { AlertTriangle, X } from "lucide-react";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import {
|
||||
Dialog,
|
||||
DialogContent,
|
||||
DialogDescription,
|
||||
DialogFooter,
|
||||
DialogTitle,
|
||||
} from "@/components/ui/dialog";
|
||||
import { COLOR } from "@/lib/constants";
|
||||
import { AlertTriangle } from "lucide-react";
|
||||
|
||||
interface ConfirmDialogProps {
|
||||
open: boolean;
|
||||
@@ -23,102 +30,41 @@ export function ConfirmDialog({
|
||||
danger = true,
|
||||
loading = false,
|
||||
}: ConfirmDialogProps) {
|
||||
const cancelRef = useRef<HTMLButtonElement>(null);
|
||||
|
||||
useEffect(() => {
|
||||
if (open) cancelRef.current?.focus();
|
||||
}, [open]);
|
||||
|
||||
useEffect(() => {
|
||||
const handler = (e: KeyboardEvent) => {
|
||||
if (e.key === "Escape" && open) onCancel();
|
||||
};
|
||||
window.addEventListener("keydown", handler);
|
||||
return () => window.removeEventListener("keydown", handler);
|
||||
}, [open, onCancel]);
|
||||
|
||||
return (
|
||||
<AnimatePresence>
|
||||
{open && (
|
||||
<motion.div
|
||||
initial={{ opacity: 0 }}
|
||||
animate={{ opacity: 1 }}
|
||||
exit={{ opacity: 0 }}
|
||||
className="fixed inset-0 z-50 flex items-center justify-center p-4"
|
||||
style={{ background: "rgba(0,0,0,0.6)", backdropFilter: "blur(4px)" }}
|
||||
onClick={(e) => e.target === e.currentTarget && onCancel()}
|
||||
>
|
||||
<motion.div
|
||||
initial={{ opacity: 0, scale: 0.95, y: 8 }}
|
||||
animate={{ opacity: 1, scale: 1, y: 0 }}
|
||||
exit={{ opacity: 0, scale: 0.95, y: 8 }}
|
||||
transition={{ type: "spring", stiffness: 300, damping: 28 }}
|
||||
className="w-full max-w-sm rounded-2xl p-6 relative"
|
||||
style={{
|
||||
background: "var(--bg-2)",
|
||||
border: "1px solid var(--border-2)",
|
||||
boxShadow: "0 24px 64px rgba(0,0,0,0.4)",
|
||||
}}
|
||||
>
|
||||
<button
|
||||
onClick={onCancel}
|
||||
className="absolute top-4 right-4 p-1 rounded-lg transition-colors"
|
||||
style={{ color: "var(--text-4)" }}
|
||||
<Dialog open={open} onOpenChange={(o) => !o && onCancel()}>
|
||||
<DialogContent className="max-w-sm">
|
||||
<div className="flex items-start gap-3 mb-4">
|
||||
{danger && (
|
||||
<div
|
||||
className="w-9 h-9 rounded-xl flex items-center justify-center flex-shrink-0 mt-0.5"
|
||||
style={{ background: COLOR.destructiveDim }}
|
||||
>
|
||||
<X className="w-4 h-4" />
|
||||
</button>
|
||||
|
||||
<div className="flex items-start gap-3 mb-4">
|
||||
{danger && (
|
||||
<div
|
||||
className="w-9 h-9 rounded-xl flex items-center justify-center flex-shrink-0 mt-0.5"
|
||||
style={{ background: "rgba(239,68,68,0.1)" }}
|
||||
>
|
||||
<AlertTriangle className="w-4 h-4" style={{ color: "#f87171" }} strokeWidth={2} />
|
||||
</div>
|
||||
)}
|
||||
<div>
|
||||
<h3
|
||||
className="text-sm font-semibold mb-1"
|
||||
style={{ color: "var(--text-1)" }}
|
||||
>
|
||||
{title}
|
||||
</h3>
|
||||
<p className="text-sm" style={{ color: "var(--text-3)" }}>
|
||||
{description}
|
||||
</p>
|
||||
</div>
|
||||
<AlertTriangle
|
||||
className="w-4 h-4"
|
||||
style={{ color: COLOR.destructive }}
|
||||
strokeWidth={2}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="flex gap-2 justify-end mt-6">
|
||||
<button
|
||||
ref={cancelRef}
|
||||
onClick={onCancel}
|
||||
className="px-3 py-1.5 text-sm rounded-lg transition-colors"
|
||||
style={{
|
||||
background: "var(--surface)",
|
||||
border: "1px solid var(--border)",
|
||||
color: "var(--text-2)",
|
||||
}}
|
||||
>
|
||||
Cancel
|
||||
</button>
|
||||
<button
|
||||
onClick={onConfirm}
|
||||
disabled={loading}
|
||||
className="px-3 py-1.5 text-sm rounded-lg font-medium transition-colors disabled:opacity-50"
|
||||
style={
|
||||
danger
|
||||
? { background: "rgba(239,68,68,0.15)", color: "#f87171", border: "1px solid rgba(239,68,68,0.3)" }
|
||||
: { background: "var(--accent-dim)", color: "var(--accent-text)", border: "1px solid var(--accent-border)" }
|
||||
}
|
||||
>
|
||||
{loading ? "..." : confirmLabel}
|
||||
</button>
|
||||
</div>
|
||||
</motion.div>
|
||||
</motion.div>
|
||||
)}
|
||||
</AnimatePresence>
|
||||
)}
|
||||
<div>
|
||||
<DialogTitle>{title}</DialogTitle>
|
||||
<DialogDescription className="mt-1">{description}</DialogDescription>
|
||||
</div>
|
||||
</div>
|
||||
<DialogFooter>
|
||||
<Button variant="surface" size="sm" onClick={onCancel}>
|
||||
Cancel
|
||||
</Button>
|
||||
<Button
|
||||
variant={danger ? "destructive" : "accent"}
|
||||
size="sm"
|
||||
onClick={onConfirm}
|
||||
disabled={loading}
|
||||
>
|
||||
{loading ? "..." : confirmLabel}
|
||||
</Button>
|
||||
</DialogFooter>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
import { motion } from "framer-motion";
|
||||
import type { LucideIcon } from "lucide-react";
|
||||
import { Body, Caption } from "@/components/ui/typography";
|
||||
import { COLOR } from "@/lib/constants";
|
||||
|
||||
interface EmptyStateProps {
|
||||
icon?: LucideIcon;
|
||||
@@ -20,16 +22,16 @@ export function EmptyState({ icon: Icon, title, description, action }: EmptyStat
|
||||
<div
|
||||
className="w-12 h-12 rounded-xl flex items-center justify-center mb-4"
|
||||
style={{
|
||||
background: "rgba(99,102,241,0.08)",
|
||||
border: "1px solid rgba(99,102,241,0.15)",
|
||||
background: COLOR.accentSubtle,
|
||||
border: `1px solid ${COLOR.accentBorderStrong}`,
|
||||
}}
|
||||
>
|
||||
<Icon className="w-5 h-5" style={{ color: "rgba(99,102,241,0.6)" }} strokeWidth={1.5} />
|
||||
</div>
|
||||
)}
|
||||
<p className="text-zinc-300 font-medium text-sm">{title}</p>
|
||||
<Body className="font-medium">{title}</Body>
|
||||
{description && (
|
||||
<p className="text-zinc-600 text-xs mt-1.5 max-w-xs leading-relaxed">{description}</p>
|
||||
<Caption className="mt-1.5 max-w-xs leading-relaxed">{description}</Caption>
|
||||
)}
|
||||
{action && <div className="mt-4">{action}</div>}
|
||||
</motion.div>
|
||||
|
||||
@@ -1,6 +1,10 @@
|
||||
import { useEffect } from "react";
|
||||
import { motion, AnimatePresence } from "framer-motion";
|
||||
import { X } from "lucide-react";
|
||||
import {
|
||||
Dialog,
|
||||
DialogContent,
|
||||
DialogHeader,
|
||||
DialogTitle,
|
||||
} from "@/components/ui/dialog";
|
||||
import { cn } from "@/lib/utils";
|
||||
|
||||
interface FormModalProps {
|
||||
open: boolean;
|
||||
@@ -17,56 +21,14 @@ export function FormModal({
|
||||
children,
|
||||
maxWidth = "max-w-md",
|
||||
}: FormModalProps) {
|
||||
useEffect(() => {
|
||||
const handler = (e: KeyboardEvent) => {
|
||||
if (e.key === "Escape" && open) onClose();
|
||||
};
|
||||
window.addEventListener("keydown", handler);
|
||||
return () => window.removeEventListener("keydown", handler);
|
||||
}, [open, onClose]);
|
||||
|
||||
return (
|
||||
<AnimatePresence>
|
||||
{open && (
|
||||
<motion.div
|
||||
initial={{ opacity: 0 }}
|
||||
animate={{ opacity: 1 }}
|
||||
exit={{ opacity: 0 }}
|
||||
className="fixed inset-0 z-50 flex items-center justify-center p-4"
|
||||
style={{ background: "rgba(0,0,0,0.6)", backdropFilter: "blur(4px)" }}
|
||||
onClick={(e) => e.target === e.currentTarget && onClose()}
|
||||
>
|
||||
<motion.div
|
||||
initial={{ opacity: 0, scale: 0.95, y: 12 }}
|
||||
animate={{ opacity: 1, scale: 1, y: 0 }}
|
||||
exit={{ opacity: 0, scale: 0.95, y: 12 }}
|
||||
transition={{ type: "spring", stiffness: 300, damping: 28 }}
|
||||
className={`w-full ${maxWidth} rounded-2xl relative`}
|
||||
style={{
|
||||
background: "var(--bg-2)",
|
||||
border: "1px solid var(--border-2)",
|
||||
boxShadow: "0 24px 64px rgba(0,0,0,0.4)",
|
||||
}}
|
||||
>
|
||||
<div
|
||||
className="flex items-center justify-between px-5 py-4"
|
||||
style={{ borderBottom: "1px solid var(--border)" }}
|
||||
>
|
||||
<h3 className="text-sm font-semibold" style={{ color: "var(--text-1)" }}>
|
||||
{title}
|
||||
</h3>
|
||||
<button
|
||||
onClick={onClose}
|
||||
className="p-1 rounded-lg transition-colors"
|
||||
style={{ color: "var(--text-4)" }}
|
||||
>
|
||||
<X className="w-4 h-4" />
|
||||
</button>
|
||||
</div>
|
||||
<div className="p-5">{children}</div>
|
||||
</motion.div>
|
||||
</motion.div>
|
||||
)}
|
||||
</AnimatePresence>
|
||||
<Dialog open={open} onOpenChange={(o) => !o && onClose()}>
|
||||
<DialogContent className={cn("p-0", maxWidth)}>
|
||||
<DialogHeader className="px-5 py-4 mb-0">
|
||||
<DialogTitle>{title}</DialogTitle>
|
||||
</DialogHeader>
|
||||
<div className="px-5 pb-5">{children}</div>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,3 +1,6 @@
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { MonoCaption } from "@/components/ui/typography";
|
||||
|
||||
interface PaginationProps {
|
||||
page: number;
|
||||
totalPages: number;
|
||||
@@ -9,33 +12,23 @@ export function Pagination({ page, totalPages, onPageChange }: PaginationProps)
|
||||
|
||||
return (
|
||||
<div className="flex items-center gap-2 mt-6">
|
||||
<button
|
||||
<Button
|
||||
variant="surface"
|
||||
size="sm"
|
||||
onClick={() => onPageChange(page - 1)}
|
||||
disabled={page <= 1}
|
||||
className="px-3 py-1.5 text-sm rounded-lg disabled:opacity-30 transition-colors"
|
||||
style={{
|
||||
background: "var(--surface)",
|
||||
border: "1px solid var(--border)",
|
||||
color: "var(--text-2)",
|
||||
}}
|
||||
>
|
||||
Previous
|
||||
</button>
|
||||
<span className="text-xs font-mono px-2" style={{ color: "var(--text-3)" }}>
|
||||
{page} / {totalPages}
|
||||
</span>
|
||||
<button
|
||||
</Button>
|
||||
<MonoCaption className="px-2">{page} / {totalPages}</MonoCaption>
|
||||
<Button
|
||||
variant="surface"
|
||||
size="sm"
|
||||
onClick={() => onPageChange(page + 1)}
|
||||
disabled={page >= totalPages}
|
||||
className="px-3 py-1.5 text-sm rounded-lg disabled:opacity-30 transition-colors"
|
||||
style={{
|
||||
background: "var(--surface)",
|
||||
border: "1px solid var(--border)",
|
||||
color: "var(--text-2)",
|
||||
}}
|
||||
>
|
||||
Next
|
||||
</button>
|
||||
</Button>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
70
src/components/ui/button.tsx
Normal file
70
src/components/ui/button.tsx
Normal file
@@ -0,0 +1,70 @@
|
||||
import { cn } from "@/lib/utils";
|
||||
import { Slot } from "@radix-ui/react-slot";
|
||||
import { type VariantProps, cva } from "class-variance-authority";
|
||||
import { forwardRef } from "react";
|
||||
|
||||
const buttonVariants = cva(
|
||||
[
|
||||
"inline-flex items-center justify-center gap-1.5 rounded-lg font-medium transition-all",
|
||||
"focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-offset-2 focus-visible:ring-offset-[var(--bg-1)]",
|
||||
"disabled:opacity-50 disabled:pointer-events-none",
|
||||
],
|
||||
{
|
||||
variants: {
|
||||
variant: {
|
||||
primary: [
|
||||
"text-white",
|
||||
"[background:var(--accent)]",
|
||||
"focus-visible:ring-[var(--accent)]",
|
||||
],
|
||||
accent: [
|
||||
"[background:var(--accent-dim)] [color:var(--accent-text)]",
|
||||
"[border:1px_solid_var(--accent-border)]",
|
||||
"focus-visible:ring-[var(--accent)]",
|
||||
],
|
||||
surface: [
|
||||
"[background:var(--surface)] [color:var(--text-2)]",
|
||||
"[border:1px_solid_var(--border)]",
|
||||
"focus-visible:ring-[var(--border)]",
|
||||
],
|
||||
ghost: [
|
||||
"[color:var(--text-3)]",
|
||||
"hover:[background:var(--surface)]",
|
||||
"focus-visible:ring-[var(--border)]",
|
||||
],
|
||||
destructive: [
|
||||
"bg-[rgba(239,68,68,0.08)] text-[#f87171]",
|
||||
"border border-[rgba(239,68,68,0.2)]",
|
||||
"focus-visible:ring-[#f87171]",
|
||||
],
|
||||
},
|
||||
size: {
|
||||
default: "px-4 py-2 text-sm",
|
||||
sm: "px-3 py-1.5 text-xs",
|
||||
icon: "p-1.5 text-sm",
|
||||
},
|
||||
},
|
||||
defaultVariants: {
|
||||
variant: "accent",
|
||||
size: "default",
|
||||
},
|
||||
},
|
||||
);
|
||||
|
||||
export interface ButtonProps
|
||||
extends React.ButtonHTMLAttributes<HTMLButtonElement>,
|
||||
VariantProps<typeof buttonVariants> {
|
||||
asChild?: boolean;
|
||||
}
|
||||
|
||||
export const Button = forwardRef<HTMLButtonElement, ButtonProps>(
|
||||
({ className, variant, size, asChild = false, ...props }, ref) => {
|
||||
const Comp = asChild ? Slot : "button";
|
||||
return (
|
||||
<Comp ref={ref} className={cn(buttonVariants({ variant, size }), className)} {...props} />
|
||||
);
|
||||
},
|
||||
);
|
||||
Button.displayName = "Button";
|
||||
|
||||
export { buttonVariants };
|
||||
97
src/components/ui/dialog.tsx
Normal file
97
src/components/ui/dialog.tsx
Normal file
@@ -0,0 +1,97 @@
|
||||
import { cn } from "@/lib/utils";
|
||||
import * as DialogPrimitive from "@radix-ui/react-dialog";
|
||||
import { X } from "lucide-react";
|
||||
import { forwardRef } from "react";
|
||||
|
||||
export const Dialog = DialogPrimitive.Root;
|
||||
export const DialogTrigger = DialogPrimitive.Trigger;
|
||||
export const DialogPortal = DialogPrimitive.Portal;
|
||||
export const DialogClose = DialogPrimitive.Close;
|
||||
|
||||
export const DialogOverlay = forwardRef<
|
||||
React.ComponentRef<typeof DialogPrimitive.Overlay>,
|
||||
React.ComponentPropsWithoutRef<typeof DialogPrimitive.Overlay>
|
||||
>(({ className, ...props }, ref) => (
|
||||
<DialogPrimitive.Overlay
|
||||
ref={ref}
|
||||
className={cn(
|
||||
"fixed inset-0 z-50 backdrop-blur-sm",
|
||||
"bg-black/60",
|
||||
"data-[state=open]:animate-in data-[state=closed]:animate-out",
|
||||
"data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0",
|
||||
className,
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
));
|
||||
DialogOverlay.displayName = DialogPrimitive.Overlay.displayName;
|
||||
|
||||
export const DialogContent = forwardRef<
|
||||
React.ComponentRef<typeof DialogPrimitive.Content>,
|
||||
React.ComponentPropsWithoutRef<typeof DialogPrimitive.Content>
|
||||
>(({ className, children, ...props }, ref) => (
|
||||
<DialogPortal>
|
||||
<DialogOverlay />
|
||||
<DialogPrimitive.Content
|
||||
ref={ref}
|
||||
className={cn(
|
||||
"fixed left-1/2 top-1/2 z-50 w-full max-w-md -translate-x-1/2 -translate-y-1/2",
|
||||
"rounded-2xl p-6 shadow-2xl",
|
||||
"[background:var(--bg-2)] [border:1px_solid_var(--border-2)]",
|
||||
"data-[state=open]:animate-in data-[state=closed]:animate-out",
|
||||
"data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0",
|
||||
"data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95",
|
||||
"data-[state=closed]:slide-out-to-top-[2%] data-[state=open]:slide-in-from-top-[2%]",
|
||||
className,
|
||||
)}
|
||||
{...props}
|
||||
>
|
||||
{children}
|
||||
<DialogPrimitive.Close
|
||||
className={cn(
|
||||
"absolute right-4 top-4 rounded-lg p-1 transition-colors",
|
||||
"[color:var(--text-4)] hover:[color:var(--text-2)]",
|
||||
"focus:outline-none focus:ring-2 focus:ring-[var(--accent)] focus:ring-offset-2 focus:ring-offset-[var(--bg-2)]",
|
||||
)}
|
||||
>
|
||||
<X className="h-4 w-4" />
|
||||
<span className="sr-only">Close</span>
|
||||
</DialogPrimitive.Close>
|
||||
</DialogPrimitive.Content>
|
||||
</DialogPortal>
|
||||
));
|
||||
DialogContent.displayName = DialogPrimitive.Content.displayName;
|
||||
|
||||
export function DialogHeader({ className, ...props }: React.HTMLAttributes<HTMLDivElement>) {
|
||||
return <div className={cn("flex flex-col gap-1.5 pb-4 mb-4 [border-bottom:1px_solid_var(--border)]", className)} {...props} />;
|
||||
}
|
||||
|
||||
export function DialogFooter({ className, ...props }: React.HTMLAttributes<HTMLDivElement>) {
|
||||
return <div className={cn("flex justify-end gap-2 pt-4 mt-4", className)} {...props} />;
|
||||
}
|
||||
|
||||
export const DialogTitle = forwardRef<
|
||||
React.ComponentRef<typeof DialogPrimitive.Title>,
|
||||
React.ComponentPropsWithoutRef<typeof DialogPrimitive.Title>
|
||||
>(({ className, ...props }, ref) => (
|
||||
<DialogPrimitive.Title
|
||||
ref={ref}
|
||||
className={cn("text-sm font-semibold", className)}
|
||||
style={{ color: "var(--text-1)" }}
|
||||
{...props}
|
||||
/>
|
||||
));
|
||||
DialogTitle.displayName = DialogPrimitive.Title.displayName;
|
||||
|
||||
export const DialogDescription = forwardRef<
|
||||
React.ComponentRef<typeof DialogPrimitive.Description>,
|
||||
React.ComponentPropsWithoutRef<typeof DialogPrimitive.Description>
|
||||
>(({ className, ...props }, ref) => (
|
||||
<DialogPrimitive.Description
|
||||
ref={ref}
|
||||
className={cn("text-sm", className)}
|
||||
style={{ color: "var(--text-3)" }}
|
||||
{...props}
|
||||
/>
|
||||
));
|
||||
DialogDescription.displayName = DialogPrimitive.Description.displayName;
|
||||
42
src/components/ui/input.tsx
Normal file
42
src/components/ui/input.tsx
Normal file
@@ -0,0 +1,42 @@
|
||||
import { cn } from "@/lib/utils";
|
||||
import { forwardRef } from "react";
|
||||
|
||||
export type InputProps = React.InputHTMLAttributes<HTMLInputElement>;
|
||||
|
||||
export const Input = forwardRef<HTMLInputElement, InputProps>(({ className, ...props }, ref) => (
|
||||
<input
|
||||
ref={ref}
|
||||
className={cn(
|
||||
"flex w-full rounded-lg px-3 py-2 text-sm transition-all outline-none",
|
||||
"[background:var(--surface)] [color:var(--text-1)]",
|
||||
"[border:1px_solid_var(--border-2)]",
|
||||
"placeholder:[color:var(--text-4)]",
|
||||
"focus:[border-color:var(--accent)]",
|
||||
"disabled:opacity-50 disabled:cursor-not-allowed",
|
||||
className,
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
));
|
||||
Input.displayName = "Input";
|
||||
|
||||
export type TextareaProps = React.TextareaHTMLAttributes<HTMLTextAreaElement>;
|
||||
|
||||
export const Textarea = forwardRef<HTMLTextAreaElement, TextareaProps>(
|
||||
({ className, ...props }, ref) => (
|
||||
<textarea
|
||||
ref={ref}
|
||||
className={cn(
|
||||
"flex w-full rounded-lg px-3 py-2 text-sm transition-all outline-none resize-none",
|
||||
"[background:var(--surface)] [color:var(--text-1)]",
|
||||
"[border:1px_solid_var(--border-2)]",
|
||||
"placeholder:[color:var(--text-4)]",
|
||||
"focus:[border-color:var(--accent)]",
|
||||
"disabled:opacity-50 disabled:cursor-not-allowed",
|
||||
className,
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
),
|
||||
);
|
||||
Textarea.displayName = "Textarea";
|
||||
20
src/components/ui/label.tsx
Normal file
20
src/components/ui/label.tsx
Normal file
@@ -0,0 +1,20 @@
|
||||
import { cn } from "@/lib/utils";
|
||||
import * as LabelPrimitive from "@radix-ui/react-label";
|
||||
import { forwardRef } from "react";
|
||||
|
||||
export const Label = forwardRef<
|
||||
React.ComponentRef<typeof LabelPrimitive.Root>,
|
||||
React.ComponentPropsWithoutRef<typeof LabelPrimitive.Root>
|
||||
>(({ className, ...props }, ref) => (
|
||||
<LabelPrimitive.Root
|
||||
ref={ref}
|
||||
className={cn(
|
||||
"block text-xs font-medium leading-none",
|
||||
"peer-disabled:cursor-not-allowed peer-disabled:opacity-70",
|
||||
className,
|
||||
)}
|
||||
style={{ color: "var(--text-2)" }}
|
||||
{...props}
|
||||
/>
|
||||
));
|
||||
Label.displayName = LabelPrimitive.Root.displayName;
|
||||
21
src/components/ui/separator.tsx
Normal file
21
src/components/ui/separator.tsx
Normal file
@@ -0,0 +1,21 @@
|
||||
import { cn } from "@/lib/utils";
|
||||
import * as SeparatorPrimitive from "@radix-ui/react-separator";
|
||||
import { forwardRef } from "react";
|
||||
|
||||
export const Separator = forwardRef<
|
||||
React.ComponentRef<typeof SeparatorPrimitive.Root>,
|
||||
React.ComponentPropsWithoutRef<typeof SeparatorPrimitive.Root>
|
||||
>(({ className, orientation = "horizontal", decorative = true, ...props }, ref) => (
|
||||
<SeparatorPrimitive.Root
|
||||
ref={ref}
|
||||
decorative={decorative}
|
||||
orientation={orientation}
|
||||
className={cn(
|
||||
"shrink-0 [background:var(--border)]",
|
||||
orientation === "horizontal" ? "h-px w-full" : "h-full w-px",
|
||||
className,
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
));
|
||||
Separator.displayName = SeparatorPrimitive.Root.displayName;
|
||||
80
src/components/ui/table.tsx
Normal file
80
src/components/ui/table.tsx
Normal file
@@ -0,0 +1,80 @@
|
||||
import { cn } from "@/lib/utils";
|
||||
import { forwardRef } from "react";
|
||||
|
||||
export const Table = forwardRef<HTMLTableElement, React.HTMLAttributes<HTMLTableElement>>(
|
||||
({ className, ...props }, ref) => (
|
||||
<div className="w-full overflow-x-auto">
|
||||
<table ref={ref} className={cn("w-full text-xs caption-bottom", className)} {...props} />
|
||||
</div>
|
||||
),
|
||||
);
|
||||
Table.displayName = "Table";
|
||||
|
||||
export const TableHeader = forwardRef<
|
||||
HTMLTableSectionElement,
|
||||
React.HTMLAttributes<HTMLTableSectionElement>
|
||||
>(({ className, ...props }, ref) => (
|
||||
<thead
|
||||
ref={ref}
|
||||
className={cn("[&_tr]:border-b [&_tr]:[border-color:var(--border)]", className)}
|
||||
{...props}
|
||||
/>
|
||||
));
|
||||
TableHeader.displayName = "TableHeader";
|
||||
|
||||
export const TableBody = forwardRef<
|
||||
HTMLTableSectionElement,
|
||||
React.HTMLAttributes<HTMLTableSectionElement>
|
||||
>(({ className, ...props }, ref) => (
|
||||
<tbody
|
||||
ref={ref}
|
||||
className={cn("[&_tr:last-child]:border-0", className)}
|
||||
{...props}
|
||||
/>
|
||||
));
|
||||
TableBody.displayName = "TableBody";
|
||||
|
||||
export const TableRow = forwardRef<HTMLTableRowElement, React.HTMLAttributes<HTMLTableRowElement>>(
|
||||
({ className, ...props }, ref) => (
|
||||
<tr
|
||||
ref={ref}
|
||||
className={cn(
|
||||
"border-b transition-colors [border-color:var(--border)]",
|
||||
"hover:[background:var(--surface)]",
|
||||
"data-[state=selected]:[background:var(--surface)]",
|
||||
className,
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
),
|
||||
);
|
||||
TableRow.displayName = "TableRow";
|
||||
|
||||
export const TableHead = forwardRef<
|
||||
HTMLTableCellElement,
|
||||
React.ThHTMLAttributes<HTMLTableCellElement>
|
||||
>(({ className, ...props }, ref) => (
|
||||
<th
|
||||
ref={ref}
|
||||
className={cn(
|
||||
"h-9 px-4 text-left align-middle font-medium [background:var(--bg-3)]",
|
||||
"[color:var(--text-3)]",
|
||||
"[&:has([role=checkbox])]:pr-0",
|
||||
className,
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
));
|
||||
TableHead.displayName = "TableHead";
|
||||
|
||||
export const TableCell = forwardRef<
|
||||
HTMLTableCellElement,
|
||||
React.TdHTMLAttributes<HTMLTableCellElement>
|
||||
>(({ className, ...props }, ref) => (
|
||||
<td
|
||||
ref={ref}
|
||||
className={cn("px-4 py-2.5 align-middle [&:has([role=checkbox])]:pr-0", className)}
|
||||
{...props}
|
||||
/>
|
||||
));
|
||||
TableCell.displayName = "TableCell";
|
||||
31
src/components/ui/tooltip.tsx
Normal file
31
src/components/ui/tooltip.tsx
Normal file
@@ -0,0 +1,31 @@
|
||||
import { cn } from "@/lib/utils";
|
||||
import * as TooltipPrimitive from "@radix-ui/react-tooltip";
|
||||
|
||||
export const TooltipProvider = TooltipPrimitive.Provider;
|
||||
export const Tooltip = TooltipPrimitive.Root;
|
||||
export const TooltipTrigger = TooltipPrimitive.Trigger;
|
||||
|
||||
export function TooltipContent({
|
||||
className,
|
||||
sideOffset = 4,
|
||||
...props
|
||||
}: React.ComponentPropsWithoutRef<typeof TooltipPrimitive.Content>) {
|
||||
return (
|
||||
<TooltipPrimitive.Portal>
|
||||
<TooltipPrimitive.Content
|
||||
sideOffset={sideOffset}
|
||||
className={cn(
|
||||
"z-50 overflow-hidden rounded-lg px-2.5 py-1.5 text-xs font-medium",
|
||||
"[background:var(--bg-3)] [color:var(--text-1)]",
|
||||
"[border:1px_solid_var(--border)]",
|
||||
"shadow-md",
|
||||
"animate-in fade-in-0 zoom-in-95",
|
||||
"data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95",
|
||||
"data-[side=bottom]:slide-in-from-top-2 data-[side=top]:slide-in-from-bottom-2",
|
||||
className,
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
</TooltipPrimitive.Portal>
|
||||
);
|
||||
}
|
||||
110
src/components/ui/typography.tsx
Normal file
110
src/components/ui/typography.tsx
Normal file
@@ -0,0 +1,110 @@
|
||||
import { cn } from "@/lib/utils";
|
||||
|
||||
type AsChild<T extends React.ElementType> = {
|
||||
as?: T;
|
||||
className?: string;
|
||||
children?: React.ReactNode;
|
||||
};
|
||||
|
||||
type Props<T extends React.ElementType> = AsChild<T> &
|
||||
Omit<React.ComponentPropsWithoutRef<T>, keyof AsChild<T>>;
|
||||
|
||||
export function PageTitle<T extends React.ElementType = "h1">({
|
||||
as,
|
||||
className,
|
||||
children,
|
||||
...rest
|
||||
}: Props<T>) {
|
||||
const Tag = (as ?? "h1") as React.ElementType;
|
||||
return (
|
||||
<Tag
|
||||
className={cn("text-xl font-semibold tracking-tight", className)}
|
||||
style={{ color: "var(--text-1)" }}
|
||||
{...rest}
|
||||
>
|
||||
{children}
|
||||
</Tag>
|
||||
);
|
||||
}
|
||||
|
||||
export function SectionHeading<T extends React.ElementType = "h2">({
|
||||
as,
|
||||
className,
|
||||
children,
|
||||
...rest
|
||||
}: Props<T>) {
|
||||
const Tag = (as ?? "h2") as React.ElementType;
|
||||
return (
|
||||
<Tag
|
||||
className={cn("text-sm font-medium mb-3", className)}
|
||||
style={{ color: "var(--text-1)" }}
|
||||
{...rest}
|
||||
>
|
||||
{children}
|
||||
</Tag>
|
||||
);
|
||||
}
|
||||
|
||||
export function Body<T extends React.ElementType = "p">({
|
||||
as,
|
||||
className,
|
||||
children,
|
||||
...rest
|
||||
}: Props<T>) {
|
||||
const Tag = (as ?? "p") as React.ElementType;
|
||||
return (
|
||||
<Tag
|
||||
className={cn("text-sm leading-relaxed", className)}
|
||||
style={{ color: "var(--text-2)" }}
|
||||
{...rest}
|
||||
>
|
||||
{children}
|
||||
</Tag>
|
||||
);
|
||||
}
|
||||
|
||||
export function Muted<T extends React.ElementType = "p">({
|
||||
as,
|
||||
className,
|
||||
children,
|
||||
...rest
|
||||
}: Props<T>) {
|
||||
const Tag = (as ?? "p") as React.ElementType;
|
||||
return (
|
||||
<Tag className={cn("text-sm", className)} style={{ color: "var(--text-3)" }} {...rest}>
|
||||
{children}
|
||||
</Tag>
|
||||
);
|
||||
}
|
||||
|
||||
export function Caption<T extends React.ElementType = "span">({
|
||||
as,
|
||||
className,
|
||||
children,
|
||||
...rest
|
||||
}: Props<T>) {
|
||||
const Tag = (as ?? "span") as React.ElementType;
|
||||
return (
|
||||
<Tag className={cn("text-xs", className)} style={{ color: "var(--text-4)" }} {...rest}>
|
||||
{children}
|
||||
</Tag>
|
||||
);
|
||||
}
|
||||
|
||||
export function MonoCaption<T extends React.ElementType = "span">({
|
||||
as,
|
||||
className,
|
||||
children,
|
||||
...rest
|
||||
}: Props<T>) {
|
||||
const Tag = (as ?? "span") as React.ElementType;
|
||||
return (
|
||||
<Tag
|
||||
className={cn("text-xs font-mono", className)}
|
||||
style={{ color: "var(--text-4)" }}
|
||||
{...rest}
|
||||
>
|
||||
{children}
|
||||
</Tag>
|
||||
);
|
||||
}
|
||||
@@ -2,6 +2,11 @@ import { useState } from "react";
|
||||
import { z } from "zod";
|
||||
import type { UseMutationResult } from "@tanstack/react-query";
|
||||
import { FormModal } from "@/components/shared/FormModal";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Input } from "@/components/ui/input";
|
||||
import { Label } from "@/components/ui/label";
|
||||
import { Caption } from "@/components/ui/typography";
|
||||
import { COLOR } from "@/lib/constants";
|
||||
|
||||
const schema = z.object({
|
||||
observer: z.string().min(1, "Observer peer ID is required"),
|
||||
@@ -54,61 +59,58 @@ export function ScheduleDreamModal({ open, onClose, mutation }: Props) {
|
||||
<FormModal open={open} title="Schedule Dream" onClose={() => { reset(); onClose(); }}>
|
||||
<form onSubmit={handleSubmit} className="space-y-4">
|
||||
<div>
|
||||
<label className="block text-xs font-medium mb-1.5" style={{ color: "var(--text-2)" }}>
|
||||
Observer peer ID <span style={{ color: "#f87171" }}>*</span>
|
||||
</label>
|
||||
<input
|
||||
<Label className="mb-1.5">
|
||||
Observer peer ID <span style={{ color: COLOR.destructive }}>*</span>
|
||||
</Label>
|
||||
<Input
|
||||
value={observer}
|
||||
onChange={(e) => { setObserver(e.target.value); setValidationError(""); }}
|
||||
placeholder="peer_id"
|
||||
className="theme-input w-full text-sm px-3 py-2 rounded-lg"
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<label className="block text-xs font-medium mb-1.5" style={{ color: "var(--text-2)" }}>
|
||||
Observed peer ID <span style={{ color: "var(--text-4)" }}>(optional, defaults to observer)</span>
|
||||
</label>
|
||||
<input
|
||||
<Label className="mb-1.5">
|
||||
Observed peer ID <Caption as="span"> (optional, defaults to observer)</Caption>
|
||||
</Label>
|
||||
<Input
|
||||
value={observed}
|
||||
onChange={(e) => setObserved(e.target.value)}
|
||||
placeholder="peer_id"
|
||||
className="theme-input w-full text-sm px-3 py-2 rounded-lg"
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<label className="block text-xs font-medium mb-1.5" style={{ color: "var(--text-2)" }}>
|
||||
Session ID <span style={{ color: "var(--text-4)" }}>(optional)</span>
|
||||
</label>
|
||||
<input
|
||||
<Label className="mb-1.5">
|
||||
Session ID <Caption as="span"> (optional)</Caption>
|
||||
</Label>
|
||||
<Input
|
||||
value={sessionId}
|
||||
onChange={(e) => setSessionId(e.target.value)}
|
||||
placeholder="session_id"
|
||||
className="theme-input w-full text-sm px-3 py-2 rounded-lg"
|
||||
/>
|
||||
</div>
|
||||
{validationError && (
|
||||
<p className="text-xs" style={{ color: "#f87171" }}>{validationError}</p>
|
||||
<Caption as="p" style={{ color: COLOR.destructive }}>{validationError}</Caption>
|
||||
)}
|
||||
{mutation.error && (
|
||||
<p className="text-xs" style={{ color: "#f87171" }}>{mutation.error.message}</p>
|
||||
<Caption as="p" style={{ color: COLOR.destructive }}>{mutation.error.message}</Caption>
|
||||
)}
|
||||
<div className="flex justify-end gap-2 pt-2">
|
||||
<button
|
||||
<Button
|
||||
type="button"
|
||||
variant="surface"
|
||||
size="sm"
|
||||
onClick={() => { reset(); onClose(); }}
|
||||
className="px-3 py-1.5 text-sm rounded-lg"
|
||||
style={{ background: "var(--surface)", border: "1px solid var(--border)", color: "var(--text-2)" }}
|
||||
>
|
||||
Cancel
|
||||
</button>
|
||||
<button
|
||||
</Button>
|
||||
<Button
|
||||
type="submit"
|
||||
variant="accent"
|
||||
size="sm"
|
||||
disabled={mutation.isPending}
|
||||
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)" }}
|
||||
>
|
||||
{mutation.isPending ? "Scheduling..." : "Schedule"}
|
||||
</button>
|
||||
</Button>
|
||||
</div>
|
||||
</form>
|
||||
</FormModal>
|
||||
|
||||
@@ -7,6 +7,10 @@ import { useWebhooks, useCreateWebhook, useDeleteWebhook, useTestWebhook } from
|
||||
import { PageLoader } from "@/components/shared/LoadingSpinner";
|
||||
import { ErrorAlert } from "@/components/shared/ErrorAlert";
|
||||
import { ConfirmDialog } from "@/components/shared/ConfirmDialog";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Input } from "@/components/ui/input";
|
||||
import { PageTitle, SectionHeading, Body, Muted } from "@/components/ui/typography";
|
||||
import { COLOR } from "@/lib/constants";
|
||||
|
||||
const urlSchema = z.string().url("Must be a valid URL");
|
||||
|
||||
@@ -60,27 +64,19 @@ export function WebhookManager({ workspaceId }: Props) {
|
||||
<div className="flex items-center justify-between mb-1">
|
||||
<div className="flex items-center gap-2">
|
||||
<Webhook className="w-5 h-5" style={{ color: "var(--accent)" }} strokeWidth={1.5} />
|
||||
<h1 className="text-xl font-semibold tracking-tight" style={{ color: "var(--text-1)" }}>
|
||||
Webhooks
|
||||
</h1>
|
||||
<PageTitle>Webhooks</PageTitle>
|
||||
</div>
|
||||
<button
|
||||
<Button
|
||||
variant="accent"
|
||||
size="sm"
|
||||
onClick={handleTest}
|
||||
disabled={testWebhook.isPending}
|
||||
className="flex items-center gap-1.5 px-3 py-1.5 rounded-lg text-xs font-medium transition-colors disabled:opacity-50"
|
||||
style={{
|
||||
background: "var(--accent-dim)",
|
||||
border: "1px solid var(--accent-border)",
|
||||
color: "var(--accent-text)",
|
||||
}}
|
||||
>
|
||||
<Zap className="w-3.5 h-3.5" strokeWidth={2} />
|
||||
{testWebhook.isPending ? "Firing..." : "Test emit"}
|
||||
</button>
|
||||
</Button>
|
||||
</div>
|
||||
<p className="text-sm" style={{ color: "var(--text-2)" }}>
|
||||
Event webhook endpoints for this workspace
|
||||
</p>
|
||||
<Body className="leading-none">Event webhook endpoints for this workspace</Body>
|
||||
</motion.div>
|
||||
|
||||
<div className="mt-8 space-y-4">
|
||||
@@ -93,34 +89,28 @@ export function WebhookManager({ workspaceId }: Props) {
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
className="rounded-xl p-5 theme-card"
|
||||
>
|
||||
<h2 className="text-sm font-medium mb-3" style={{ color: "var(--text-1)" }}>
|
||||
<SectionHeading>
|
||||
<Plus className="w-3.5 h-3.5 inline mr-1.5" strokeWidth={2} />
|
||||
Add endpoint
|
||||
</h2>
|
||||
</SectionHeading>
|
||||
<form onSubmit={handleCreate} className="flex gap-2">
|
||||
<div className="flex-1">
|
||||
<input
|
||||
<Input
|
||||
value={url}
|
||||
onChange={(e) => { setUrl(e.target.value); setUrlError(""); }}
|
||||
placeholder="https://your-server.com/webhook"
|
||||
className="theme-input w-full text-sm px-3 py-2 rounded-lg"
|
||||
/>
|
||||
{urlError && (
|
||||
<p className="text-xs mt-1" style={{ color: "#f87171" }}>{urlError}</p>
|
||||
<p className="text-xs mt-1" style={{ color: COLOR.destructive }}>{urlError}</p>
|
||||
)}
|
||||
</div>
|
||||
<button
|
||||
<Button
|
||||
type="submit"
|
||||
variant="accent"
|
||||
disabled={createWebhook.isPending}
|
||||
className="px-3 py-2 text-sm rounded-lg font-medium disabled:opacity-50"
|
||||
style={{
|
||||
background: "var(--accent-dim)",
|
||||
border: "1px solid var(--accent-border)",
|
||||
color: "var(--accent-text)",
|
||||
}}
|
||||
>
|
||||
{createWebhook.isPending ? "Adding..." : "Add"}
|
||||
</button>
|
||||
</Button>
|
||||
</form>
|
||||
</motion.div>
|
||||
|
||||
@@ -133,9 +123,9 @@ export function WebhookManager({ workspaceId }: Props) {
|
||||
exit={{ opacity: 0 }}
|
||||
className="rounded-xl p-4 text-xs font-mono overflow-auto"
|
||||
style={{
|
||||
background: "rgba(52,211,153,0.06)",
|
||||
border: "1px solid rgba(52,211,153,0.2)",
|
||||
color: "#34d399",
|
||||
background: COLOR.successDim,
|
||||
border: `1px solid ${COLOR.successBorder}`,
|
||||
color: COLOR.success,
|
||||
}}
|
||||
>
|
||||
{testResult}
|
||||
@@ -158,9 +148,7 @@ export function WebhookManager({ workspaceId }: Props) {
|
||||
style={{ color: "var(--text-3)" }}
|
||||
strokeWidth={1.5}
|
||||
/>
|
||||
<p className="text-sm" style={{ color: "var(--text-3)" }}>
|
||||
No webhook endpoints yet.
|
||||
</p>
|
||||
<Muted>No webhook endpoints yet.</Muted>
|
||||
</div>
|
||||
) : (
|
||||
<div className="divide-y" style={{ borderColor: "var(--border)" }}>
|
||||
@@ -197,13 +185,14 @@ export function WebhookManager({ workspaceId }: Props) {
|
||||
{(wh as { id: string }).id}
|
||||
</span>
|
||||
</div>
|
||||
<button
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
onClick={() => setDeleteTarget((wh as { id: string }).id)}
|
||||
className="p-1.5 rounded-lg transition-colors flex-shrink-0"
|
||||
style={{ color: "var(--text-4)" }}
|
||||
aria-label="Delete webhook"
|
||||
>
|
||||
<Trash2 className="w-3.5 h-3.5" strokeWidth={1.5} />
|
||||
</button>
|
||||
</Button>
|
||||
</motion.div>
|
||||
))}
|
||||
</div>
|
||||
|
||||
@@ -4,6 +4,9 @@ import { ErrorAlert } from "@/components/shared/ErrorAlert";
|
||||
import { JsonViewer } from "@/components/shared/JsonViewer";
|
||||
import { PageLoader } from "@/components/shared/LoadingSpinner";
|
||||
import { ScheduleDreamModal } from "@/components/workspaces/ScheduleDreamModal";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { PageTitle, SectionHeading, Body, Caption } from "@/components/ui/typography";
|
||||
import { COLOR } from "@/lib/constants";
|
||||
import { Link, useNavigate, useParams } from "@tanstack/react-router";
|
||||
import { AnimatePresence, motion } from "framer-motion";
|
||||
import {
|
||||
@@ -84,43 +87,30 @@ export function WorkspaceDetail() {
|
||||
style={{ color: "var(--accent)" }}
|
||||
strokeWidth={1.5}
|
||||
/>
|
||||
<h1
|
||||
className="text-xl font-semibold font-mono break-all tracking-tight"
|
||||
style={{ color: "var(--text-1)" }}
|
||||
>
|
||||
<PageTitle className="font-mono break-all">
|
||||
{workspaceId}
|
||||
</h1>
|
||||
</PageTitle>
|
||||
</div>
|
||||
<div className="flex items-center gap-2 flex-shrink-0">
|
||||
<button
|
||||
<Button
|
||||
variant="accent"
|
||||
size="sm"
|
||||
onClick={() => setDreamOpen(true)}
|
||||
className="flex items-center gap-1.5 px-3 py-1.5 rounded-lg text-xs font-medium transition-colors"
|
||||
style={{
|
||||
background: "var(--accent-dim)",
|
||||
border: "1px solid var(--accent-border)",
|
||||
color: "var(--accent-text)",
|
||||
}}
|
||||
>
|
||||
<Zap className="w-3.5 h-3.5" strokeWidth={2} />
|
||||
Schedule Dream
|
||||
</button>
|
||||
<button
|
||||
</Button>
|
||||
<Button
|
||||
variant="destructive"
|
||||
size="sm"
|
||||
onClick={() => setConfirmDelete(true)}
|
||||
className="flex items-center gap-1.5 px-3 py-1.5 rounded-lg text-xs font-medium transition-colors"
|
||||
style={{
|
||||
background: "rgba(239,68,68,0.08)",
|
||||
border: "1px solid rgba(239,68,68,0.2)",
|
||||
color: "#f87171",
|
||||
}}
|
||||
>
|
||||
<Trash2 className="w-3.5 h-3.5" strokeWidth={2} />
|
||||
Delete
|
||||
</button>
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
<p className="text-sm" style={{ color: "var(--text-2)" }}>
|
||||
Workspace overview
|
||||
</p>
|
||||
<Body className="leading-none">Workspace overview</Body>
|
||||
</motion.div>
|
||||
|
||||
<div className="mt-8">
|
||||
@@ -150,12 +140,8 @@ export function WorkspaceDetail() {
|
||||
style={{ color: "var(--accent)" }}
|
||||
strokeWidth={1.5}
|
||||
/>
|
||||
<h2 className="text-sm font-medium mb-0.5" style={{ color: "var(--text-1)" }}>
|
||||
{s.label}
|
||||
</h2>
|
||||
<p className="text-xs" style={{ color: "var(--text-3)" }}>
|
||||
{s.description}
|
||||
</p>
|
||||
<SectionHeading className="mb-0.5">{s.label}</SectionHeading>
|
||||
<Caption as="p">{s.description}</Caption>
|
||||
</Link>
|
||||
</motion.div>
|
||||
);
|
||||
@@ -171,9 +157,7 @@ export function WorkspaceDetail() {
|
||||
className="rounded-xl p-5 theme-card"
|
||||
>
|
||||
<div className="flex items-center justify-between mb-4">
|
||||
<h2 className="text-sm font-medium" style={{ color: "var(--text-1)" }}>
|
||||
Queue Status
|
||||
</h2>
|
||||
<SectionHeading className="mb-0">Queue Status</SectionHeading>
|
||||
<div className="flex items-center gap-1.5">
|
||||
{queue.pending_work_units > 0 ? (
|
||||
<motion.div
|
||||
@@ -182,20 +166,20 @@ export function WorkspaceDetail() {
|
||||
>
|
||||
<CircleDot
|
||||
className="w-3.5 h-3.5"
|
||||
style={{ color: "#f59e0b" }}
|
||||
style={{ color: COLOR.warning }}
|
||||
strokeWidth={2}
|
||||
/>
|
||||
</motion.div>
|
||||
) : (
|
||||
<CircleDot
|
||||
className="w-3.5 h-3.5"
|
||||
style={{ color: "#34d399" }}
|
||||
style={{ color: COLOR.success }}
|
||||
strokeWidth={2}
|
||||
/>
|
||||
)}
|
||||
<span
|
||||
className="text-xs font-medium"
|
||||
style={{ color: queue.pending_work_units > 0 ? "#f59e0b" : "#34d399" }}
|
||||
style={{ color: queue.pending_work_units > 0 ? COLOR.warning : COLOR.success }}
|
||||
>
|
||||
{queue.pending_work_units === 0
|
||||
? "Idle"
|
||||
@@ -301,13 +285,13 @@ export function WorkspaceDetail() {
|
||||
</td>
|
||||
<td
|
||||
className="py-1.5 px-3 text-right font-mono"
|
||||
style={{ color: "#34d399" }}
|
||||
style={{ color: COLOR.success }}
|
||||
>
|
||||
{s.completed_work_units}
|
||||
</td>
|
||||
<td
|
||||
className="py-1.5 px-3 text-right font-mono"
|
||||
style={{ color: "#f59e0b" }}
|
||||
style={{ color: COLOR.warning }}
|
||||
>
|
||||
{s.in_progress_work_units}
|
||||
</td>
|
||||
@@ -337,9 +321,7 @@ export function WorkspaceDetail() {
|
||||
transition={{ delay: 0.38 }}
|
||||
className="rounded-xl p-5 theme-card"
|
||||
>
|
||||
<h2 className="text-sm font-medium mb-3" style={{ color: "var(--text-1)" }}>
|
||||
Metadata
|
||||
</h2>
|
||||
<SectionHeading>Metadata</SectionHeading>
|
||||
<JsonViewer data={workspace.metadata} />
|
||||
</motion.div>
|
||||
</div>
|
||||
|
||||
@@ -7,6 +7,8 @@ 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 { PageTitle, Muted, MonoCaption } from "@/components/ui/typography";
|
||||
import { COLOR } from "@/lib/constants";
|
||||
import type { components } from "@/api/schema.d.ts";
|
||||
|
||||
type Workspace = components["schemas"]["Workspace"];
|
||||
@@ -40,28 +42,21 @@ export function WorkspaceList() {
|
||||
>
|
||||
<div className="flex items-center gap-2 mb-1">
|
||||
<Boxes className="w-5 h-5" style={{ color: "#6366f1" }} strokeWidth={1.5} />
|
||||
<h1
|
||||
className="text-xl font-semibold tracking-tight"
|
||||
style={{ color: "#e4e4f0" }}
|
||||
>
|
||||
Workspaces
|
||||
</h1>
|
||||
<PageTitle>Workspaces</PageTitle>
|
||||
{total > 0 && (
|
||||
<span
|
||||
className="ml-auto text-xs font-mono px-2 py-0.5 rounded-full"
|
||||
style={{
|
||||
background: "rgba(99,102,241,0.1)",
|
||||
color: "#818cf8",
|
||||
border: "1px solid rgba(99,102,241,0.2)",
|
||||
background: COLOR.accentSubtle,
|
||||
color: COLOR.accentText,
|
||||
border: `1px solid ${COLOR.accentBorder}`,
|
||||
}}
|
||||
>
|
||||
{total}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
<p className="text-sm" style={{ color: "rgba(148,163,184,0.6)" }}>
|
||||
All workspaces in your Honcho instance
|
||||
</p>
|
||||
<Muted>All workspaces in your Honcho instance</Muted>
|
||||
</motion.div>
|
||||
|
||||
<ErrorAlert error={error instanceof Error ? error : null} />
|
||||
@@ -124,9 +119,7 @@ export function WorkspaceList() {
|
||||
style={{ color: "rgba(148,163,184,0.35)" }}
|
||||
strokeWidth={1.5}
|
||||
/>
|
||||
<p className="text-xs font-mono" style={{ color: "rgba(148,163,184,0.35)" }}>
|
||||
{new Date(ws.created_at).toLocaleString()}
|
||||
</p>
|
||||
<MonoCaption>{new Date(ws.created_at).toLocaleString()}</MonoCaption>
|
||||
</div>
|
||||
)}
|
||||
</motion.button>
|
||||
|
||||
Reference in New Issue
Block a user