From 8392dbe724ff0061e952924df79377cea4a0c378 Mon Sep 17 00:00:00 2001
From: asepharyana
Date: Tue, 15 Sep 2026 17:40:39 +0700
Subject: [PATCH] =?UTF-8?q?feat:=20FE=20solid-ui=20(shadcn=20port)=20?=
=?UTF-8?q?=E2=80=94=20Tailwind=20+=2014=20components,=20all=20pages=20rew?=
=?UTF-8?q?ritten?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
.gitignore | 1 +
web/package.json | 13 +-
web/pnpm-lock.yaml | 806 ++++++++++++++++++++++++++-
web/postcss.config.js | 6 +
web/src/components/Cards.tsx | 70 ++-
web/src/components/Citations.tsx | 15 +-
web/src/components/WatchlistChat.tsx | 48 +-
web/src/components/auth.tsx | 26 +-
web/src/components/ui.tsx | 122 ++--
web/src/components/ui/avatar.tsx | 52 ++
web/src/components/ui/badge.tsx | 49 ++
web/src/components/ui/button.tsx | 54 ++
web/src/components/ui/card.tsx | 44 ++
web/src/components/ui/dialog.tsx | 142 +++++
web/src/components/ui/label.tsx | 20 +
web/src/components/ui/progress.tsx | 35 ++
web/src/components/ui/separator.tsx | 30 +
web/src/components/ui/skeleton.tsx | 25 +
web/src/components/ui/switch.tsx | 78 +++
web/src/components/ui/table.tsx | 71 +++
web/src/components/ui/tabs.tsx | 88 +++
web/src/components/ui/text-field.tsx | 153 +++++
web/src/components/ui/tooltip.tsx | 36 ++
web/src/index.tsx | 94 ++--
web/src/lib/utils.ts | 7 +
web/src/pages/Alerts.tsx | 156 +++---
web/src/pages/Dashboard.tsx | 74 +--
web/src/pages/Portfolio.tsx | 88 +--
web/src/pages/Report.tsx | 83 +--
web/src/pages/Routines.tsx | 96 ++--
web/src/pages/Screener.tsx | 105 ++--
web/src/styles/globals.css | 68 +++
web/src/styles/tokens.css | 135 -----
web/tailwind.config.ts | 42 ++
34 files changed, 2390 insertions(+), 542 deletions(-)
create mode 100644 web/postcss.config.js
create mode 100644 web/src/components/ui/avatar.tsx
create mode 100644 web/src/components/ui/badge.tsx
create mode 100644 web/src/components/ui/button.tsx
create mode 100644 web/src/components/ui/card.tsx
create mode 100644 web/src/components/ui/dialog.tsx
create mode 100644 web/src/components/ui/label.tsx
create mode 100644 web/src/components/ui/progress.tsx
create mode 100644 web/src/components/ui/separator.tsx
create mode 100644 web/src/components/ui/skeleton.tsx
create mode 100644 web/src/components/ui/switch.tsx
create mode 100644 web/src/components/ui/table.tsx
create mode 100644 web/src/components/ui/tabs.tsx
create mode 100644 web/src/components/ui/text-field.tsx
create mode 100644 web/src/components/ui/tooltip.tsx
create mode 100644 web/src/lib/utils.ts
create mode 100644 web/src/styles/globals.css
delete mode 100644 web/src/styles/tokens.css
create mode 100644 web/tailwind.config.ts
diff --git a/.gitignore b/.gitignore
index 392ed17..51b6eca 100644
--- a/.gitignore
+++ b/.gitignore
@@ -7,6 +7,7 @@
.claude-flow/logs/
.claude-flow/sessions/
.hermes/
+.planning
# Ruflo runtime artifacts (not source)
.swarm/
.claude/
diff --git a/web/package.json b/web/package.json
index 4b18d08..95b9bb2 100644
--- a/web/package.json
+++ b/web/package.json
@@ -9,12 +9,21 @@
"preview": "vite preview"
},
"dependencies": {
- "solid-js": "1.9.15",
+ "@kobalte/core": "^0.13.14",
"@solidjs/router": "0.15.3",
+ "autoprefixer": "^10.6.0",
"chart.js": "4.4.7",
- "solid-chartjs": "1.3.11"
+ "class-variance-authority": "^0.7.1",
+ "clsx": "^2.1.1",
+ "postcss": "^8.5.28",
+ "solid-chartjs": "1.3.11",
+ "solid-js": "1.9.15",
+ "tailwind-merge": "^3.7.0",
+ "tailwindcss": "^3.4.19",
+ "tailwindcss-animate": "^1.0.7"
},
"devDependencies": {
+ "@types/node": "^26.5.1",
"typescript": "5.6.3",
"vite": "6.4.3",
"vite-plugin-solid": "2.11.6"
diff --git a/web/pnpm-lock.yaml b/web/pnpm-lock.yaml
index 5f88e16..9cc1734 100644
--- a/web/pnpm-lock.yaml
+++ b/web/pnpm-lock.yaml
@@ -8,31 +8,62 @@ importers:
.:
dependencies:
+ '@kobalte/core':
+ specifier: ^0.13.14
+ version: 0.13.14(solid-js@1.9.15)
'@solidjs/router':
specifier: 0.15.3
version: 0.15.3(solid-js@1.9.15)
+ autoprefixer:
+ specifier: ^10.6.0
+ version: 10.6.0(postcss@8.5.28)
chart.js:
specifier: 4.4.7
version: 4.4.7
+ class-variance-authority:
+ specifier: ^0.7.1
+ version: 0.7.1
+ clsx:
+ specifier: ^2.1.1
+ version: 2.1.1
+ postcss:
+ specifier: ^8.5.28
+ version: 8.5.28
solid-chartjs:
specifier: 1.3.11
version: 1.3.11(chart.js@4.4.7)(solid-js@1.9.15)
solid-js:
specifier: 1.9.15
version: 1.9.15
+ tailwind-merge:
+ specifier: ^3.7.0
+ version: 3.7.0
+ tailwindcss:
+ specifier: ^3.4.19
+ version: 3.4.19
+ tailwindcss-animate:
+ specifier: ^1.0.7
+ version: 1.0.7(tailwindcss@3.4.19)
devDependencies:
+ '@types/node':
+ specifier: ^26.5.1
+ version: 26.5.1
typescript:
specifier: 5.6.3
version: 5.6.3
vite:
specifier: 6.4.3
- version: 6.4.3
+ version: 6.4.3(@types/node@26.5.1)(jiti@1.21.7)
vite-plugin-solid:
specifier: 2.11.6
- version: 2.11.6(solid-js@1.9.15)(vite@6.4.3)
+ version: 2.11.6(solid-js@1.9.15)(vite@6.4.3(@types/node@26.5.1)(jiti@1.21.7))
packages:
+ '@alloc/quick-lru@5.3.0':
+ resolution: {integrity: sha512-U4+70Pc5ZS9osnCBCE5Jha/ciHM+Yp+CNMNC/7HvYbNRk1Ldd+f7qO65W5qfhu/TCv+/ozljlXXe9Nj8419DMA==}
+ engines: {node: '>=10'}
+
'@babel/code-frame@7.29.7':
resolution: {integrity: sha512-Aup7aUOfpbAUg2ROOJN6Iw5f9DMBlzu0mIkm/malLQFN/YQgO48wCj0Kxa3sEHJvPVFg7siR+qRInwXd2qhQKw==}
engines: {node: '>=6.9.0'}
@@ -114,6 +145,11 @@ packages:
resolution: {integrity: sha512-Vj1jF3cPfxg7OAfoI7QnVKLoILlm2JF9pnVHrX8qx7AHMiYWT+NDAA7jChlNgRS4WTLc/fD1lXLmPixluj+3Gg==}
engines: {node: '>=6.9.0'}
+ '@corvu/utils@0.4.2':
+ resolution: {integrity: sha512-Ox2kYyxy7NoXdKWdHeDEjZxClwzO4SKM8plAaVwmAJPxHMqA0rLOoAsa+hBDwRLpctf+ZRnAd/ykguuJidnaTA==}
+ peerDependencies:
+ solid-js: ^1.8
+
'@esbuild/aix-ppc64@0.25.12':
resolution: {integrity: sha512-Hhmwd6CInZ3dwpuGTF8fJG6yoWmsToE+vYgD4nytZVxcu1ulHpUQRAB1UJ8+N1Am3Mz4+xOByoQoSZf4D+CpkA==}
engines: {node: '>=18'}
@@ -270,6 +306,18 @@ packages:
cpu: [x64]
os: [win32]
+ '@floating-ui/core@1.8.0':
+ resolution: {integrity: sha512-0CIZ5itps/8x7BG8dEIhs53BvCUH2PCoogtakwRTut+Arm58sJooJ0AuZhLw2HJYIR5cMLNPBSS728sPho2khQ==}
+
+ '@floating-ui/dom@1.8.0':
+ resolution: {integrity: sha512-yXSrzeHZBTZadLOlfyhCkJHNeLJnHRnRInwdZ40L7ZiaAtrBwoYlsDrX3v5zB1Utk7CLfzcOVnVVWoXEky7Ceg==}
+
+ '@floating-ui/utils@0.2.12':
+ resolution: {integrity: sha512-HpCo8tmWzLVad5s2d19EhAz5zqrrQ6s69qd6moPMQvkOuSwDT1YgRfWSVuc4ennqrgv3OHppiOGMQ7oC13yIww==}
+
+ '@internationalized/number@3.6.8':
+ resolution: {integrity: sha512-8UmMFia46DUt+k97zKd9fKWXcWHR+k8ae3eYzILETuT2KbIvLyOfac7zesw+sJdRAAZ7Q9pM1Mk22aXp2LD0Ig==}
+
'@jridgewell/gen-mapping@0.3.13':
resolution: {integrity: sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==}
@@ -286,6 +334,16 @@ packages:
'@jridgewell/trace-mapping@0.3.31':
resolution: {integrity: sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==}
+ '@kobalte/core@0.13.14':
+ resolution: {integrity: sha512-SBVB1lnvYbYt2x3t7fG0lo5fGLEbHQ6PII3NDPva8vD5e2v53On84ZVQiuIk0XI/55R4GQhden7Yem+R/XR8uQ==}
+ peerDependencies:
+ solid-js: ^1.9.8
+
+ '@kobalte/utils@0.9.2':
+ resolution: {integrity: sha512-jRVXr+zsVHxzDXRoh+CDeXzvCsFJ6uiHhqqNQ26Cw9ZsZ3D6nqPUBt1gGVtj2ZPmRL3a9Uk1v8D1aJ8/I12Dow==}
+ peerDependencies:
+ solid-js: ^1.8.8
+
'@kurkle/color@0.3.4':
resolution: {integrity: sha512-M5UknZPHRu3DEDWoipU6sE8PdkZ6Z/S+v4dD+Ke8IaNlpdSQah50lz1KtcFBa2vsdOnwbbnxJwVM4wty6udA5w==}
@@ -296,6 +354,18 @@ packages:
os: [linux]
libc: [glibc]
+ '@nodelib/fs.scandir@2.1.5':
+ resolution: {integrity: sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==}
+ engines: {node: '>= 8'}
+
+ '@nodelib/fs.stat@2.0.5':
+ resolution: {integrity: sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==}
+ engines: {node: '>= 8'}
+
+ '@nodelib/fs.walk@1.2.8':
+ resolution: {integrity: sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==}
+ engines: {node: '>= 8'}
+
'@rollup/rollup-android-arm-eabi@4.63.2':
resolution: {integrity: sha512-Xa6RDoWa+hNiX6PgsljlH6W75RaONx3y6PVlbLhkEWW+GaPQ3dP5gwbL/erAzQHWwkvW5UxdD5l87Qx2FAQ/4A==}
cpu: [arm]
@@ -434,11 +504,69 @@ packages:
cpu: [x64]
os: [win32]
+ '@solid-primitives/event-listener@2.4.6':
+ resolution: {integrity: sha512-5I0YJcTVYIWoMmgBSROBZGcz+ymhew/pGTg2dHW74BUjFKsV8Li4bOZYl0YAGP4mHw5o4UBd9/BEesqBci3wxw==}
+ peerDependencies:
+ solid-js: ^1.6.12
+
+ '@solid-primitives/keyed@1.5.3':
+ resolution: {integrity: sha512-zNadtyYBhJSOjXtogkGHmRxjGdz9KHc8sGGVAGlUABkE8BED2tbIZoxkwSqzOwde8OcUEH0bb5DLZUWIMvyBSA==}
+ peerDependencies:
+ solid-js: ^1.6.12
+
+ '@solid-primitives/map@0.4.13':
+ resolution: {integrity: sha512-B1zyFbsiTQvqPr+cuPCXO72sRuczG9Swncqk5P74NCGw1VE8qa/Ry9GlfI1e/VdeQYHjan+XkbE3rO2GW/qKew==}
+ peerDependencies:
+ solid-js: ^1.6.12
+
+ '@solid-primitives/media@2.3.6':
+ resolution: {integrity: sha512-pk49gPOq/UMRUJ+pTSrOfBiR8xJjRYHXIf1iR/jSnyQ/KroU+ZXhkZzavC7hvfp2vJeOTW5k2/HN0r3Q1VJ7Pw==}
+ peerDependencies:
+ solid-js: ^1.6.12
+
+ '@solid-primitives/props@3.2.4':
+ resolution: {integrity: sha512-MXXdvi2TSB6d+0N6ueA/HP1j/Kh9SEc4WdF1ZDMLwPagxW6pIHHSS9xbRO0RjqSVpNP4j0nxCWT1hx3CkJNhNA==}
+ peerDependencies:
+ solid-js: ^1.6.12
+
+ '@solid-primitives/refs@1.1.4':
+ resolution: {integrity: sha512-bLjwIs6ZPu8NQnuw04sU3Zc8qKSpbc0umUU/O4SHf6oWOdO4+dHY8vb1T7C4b/Tg103+9WGr6sEE0+NFlbaB/A==}
+ peerDependencies:
+ solid-js: ^1.6.12
+
+ '@solid-primitives/resize-observer@2.2.0':
+ resolution: {integrity: sha512-9Fuu/EWBeGj+atGHRJp70HKhdfalmpjwxY8a32NZixdLNmfCJ45AfhLQNr6uOzETbbiMx4iCKlTrJ8KZCHC2Ww==}
+ peerDependencies:
+ solid-js: ^1.6.12
+
+ '@solid-primitives/rootless@1.5.4':
+ resolution: {integrity: sha512-TOIZa1VUfVJ+9nkCcRajw3U4t9vBOP1HxX1WHNTbXq32mXwlqTvUnC4CRIilohcryBkT9u2ZkhUDSHRTaGp55g==}
+ peerDependencies:
+ solid-js: ^1.6.12
+
+ '@solid-primitives/static-store@0.1.4':
+ resolution: {integrity: sha512-LgtVaVBtB7EbmS4+M0b8xY5Iq6pUWXBsIC4VgtrFKDGDdyCaDt88sHk0fUlx1Enxm/XZnZyLXJABRoa39RjJqA==}
+ peerDependencies:
+ solid-js: ^1.6.12
+
+ '@solid-primitives/trigger@1.2.4':
+ resolution: {integrity: sha512-Ju0e+ZOD7hpOp7nptJimvDSZHWFvIvF9iBWMvuwt30smX7c5wmB8Kmc0AdDuv0wOTi06PQ1J5IrP1WJbH2yUBQ==}
+ peerDependencies:
+ solid-js: ^1.6.12
+
+ '@solid-primitives/utils@6.4.1':
+ resolution: {integrity: sha512-ISSB5QX1qP2ynrheIpYwc4oKR5Ny4siNuUyf1qZniy+Il+p/PtDB0QK1Dnle8noiHpwRD3gpPdubOC3qI/Zamg==}
+ peerDependencies:
+ solid-js: ^1.6.12
+
'@solidjs/router@0.15.3':
resolution: {integrity: sha512-iEbW8UKok2Oio7o6Y4VTzLj+KFCmQPGEpm1fS3xixwFBdclFVBvaQVeibl1jys4cujfAK5Kn6+uG2uBm3lxOMw==}
peerDependencies:
solid-js: ^1.8.6
+ '@swc/helpers@0.5.23':
+ resolution: {integrity: sha512-5lSsMOTXURePglDfvuAQUqkGek9Hg2kksOYay2m0+XR++b2NWYL/4sWyuvVBIs8oKnJaxkdi9whaL/sqN13afw==}
+
'@types/babel__core@7.20.5':
resolution: {integrity: sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==}
@@ -454,6 +582,26 @@ packages:
'@types/estree@1.0.9':
resolution: {integrity: sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg==}
+ '@types/node@26.5.1':
+ resolution: {integrity: sha512-CzNm2FezW4VR/LjG6yUdiEgLE/rAQ9Slj5gCu/C2VrdcW7I0ahNZ8DRbHT7zOZ6r3ONgd/bsQIeSaoDGrd1C6g==}
+
+ any-promise@1.3.0:
+ resolution: {integrity: sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A==}
+
+ anymatch@3.1.3:
+ resolution: {integrity: sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==}
+ engines: {node: '>= 8'}
+
+ arg@5.0.2:
+ resolution: {integrity: sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg==}
+
+ autoprefixer@10.6.0:
+ resolution: {integrity: sha512-A26d6qs9kqGgkmImIXMYvXTzqb4Qv7AVgpY1NXzr9Y659J8qHHnLOO/zE8ewIGFMprOolAoRAQYDgNryXIJKBw==}
+ engines: {node: ^10 || ^12 || >=14}
+ hasBin: true
+ peerDependencies:
+ postcss: ^8.1.0
+
babel-plugin-jsx-dom-expressions@0.40.10:
resolution: {integrity: sha512-lxve6Y02YiZTldB7efKpnbf1BH00XCFZNYYW235jSGsYaJNFtHrYlKV6/O+miHbjqpIr9FTe5+0no4hofAMbfA==}
peerDependencies:
@@ -473,11 +621,23 @@ packages:
engines: {node: '>=6.0.0'}
hasBin: true
+ binary-extensions@2.3.0:
+ resolution: {integrity: sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==}
+ engines: {node: '>=8'}
+
+ braces@3.0.3:
+ resolution: {integrity: sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==}
+ engines: {node: '>=8'}
+
browserslist@4.28.9:
resolution: {integrity: sha512-EWazOblFYUvlGZcfGhPUPmYh3nikUxBVb+y9MJun5f3hBi812X+8MSQTujLBtgK3cf51fJWbWfOjyeO954d+Eg==}
engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7}
hasBin: true
+ camelcase-css@2.0.1:
+ resolution: {integrity: sha512-QOSvevhslijgYwRx6Rv7zKdMF8lbRmx+uQGx2+vDc+KI/eBnsy9kit5aj23AgGu3pa4t9AgwbnXWqS+iOY+2aA==}
+ engines: {node: '>= 6'}
+
caniuse-lite@1.0.30001810:
resolution: {integrity: sha512-TITQPUkaz+aVk5GL6NhOdwk1aEaNTSDPsGFWrTuhKGtjTF70jL/Oht2W4c6rXUe5fu7Ie19VIahAXHIIiWWNeg==}
@@ -485,9 +645,29 @@ packages:
resolution: {integrity: sha512-pwkcKfdzTMAU/+jNosKhNL2bHtJc/sSmYgVbuGTEDhzkrhmyihmP7vUc/5ZK9WopidMDHNe3Wm7jOd/WhuHWuw==}
engines: {pnpm: '>=8'}
+ chokidar@3.6.0:
+ resolution: {integrity: sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==}
+ engines: {node: '>= 8.10.0'}
+
+ class-variance-authority@0.7.1:
+ resolution: {integrity: sha512-Ka+9Trutv7G8M6WT6SeiRWz792K5qEqIGEGzXKhAE6xOWAY6pPH8U+9IY3oCMv6kqTmLsv7Xh/2w2RigkePMsg==}
+
+ clsx@2.1.1:
+ resolution: {integrity: sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==}
+ engines: {node: '>=6'}
+
+ commander@4.1.1:
+ resolution: {integrity: sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==}
+ engines: {node: '>= 6'}
+
convert-source-map@2.0.0:
resolution: {integrity: sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==}
+ cssesc@3.0.0:
+ resolution: {integrity: sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==}
+ engines: {node: '>=4'}
+ hasBin: true
+
csstype@3.2.3:
resolution: {integrity: sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==}
@@ -500,6 +680,12 @@ packages:
supports-color:
optional: true
+ didyoumean@1.2.2:
+ resolution: {integrity: sha512-gxtyfqMg7GKyhQmb056K7M3xszy/myH8w+B4RT+QXBQsvAOdc3XymqDDPHx1BgPgsdAA5SIifona89YtRATDzw==}
+
+ dlv@1.1.3:
+ resolution: {integrity: sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA==}
+
electron-to-chromium@1.5.427:
resolution: {integrity: sha512-n14zb3FdsChZ2BNobqNHAJMcP3ifFv4paox2LvCrfVAQcqGiSURgbJl+PfMpHVCNFkStnNc+RRVtPBTVW5PDgw==}
@@ -507,6 +693,10 @@ packages:
resolution: {integrity: sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g==}
engines: {node: '>=0.12'}
+ es-errors@1.3.0:
+ resolution: {integrity: sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==}
+ engines: {node: '>= 0.4'}
+
esbuild@0.25.12:
resolution: {integrity: sha512-bbPBYYrtZbkt6Os6FiTLCTFxvq4tt3JKall1vRwshA3fdVztsLAatFaZobhkBC8/BrPetoa0oksYoKXoG4ryJg==}
engines: {node: '>=18'}
@@ -516,6 +706,13 @@ packages:
resolution: {integrity: sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==}
engines: {node: '>=6'}
+ fast-glob@3.3.3:
+ resolution: {integrity: sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==}
+ engines: {node: '>=8.6.0'}
+
+ fastq@1.20.3:
+ resolution: {integrity: sha512-XKv5nnLs6nLF71NgiKJLIZFLkPyIEuOselLG7ujZnGrRfQK8HpvY+WqKhAJUAdLomwVHErVS4LfxFlPq0/FTAw==}
+
fdir@6.5.0:
resolution: {integrity: sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==}
engines: {node: '>=12.0.0'}
@@ -525,22 +722,68 @@ packages:
picomatch:
optional: true
+ fill-range@7.1.1:
+ resolution: {integrity: sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==}
+ engines: {node: '>=8'}
+
+ fraction.js@5.3.4:
+ resolution: {integrity: sha512-1X1NTtiJphryn/uLQz3whtY6jK3fTqoE3ohKs0tT+Ujr1W59oopxmoEh7Lu5p6vBaPbgoM0bzveAW4Qi5RyWDQ==}
+
fsevents@2.3.3:
resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==}
engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0}
os: [darwin]
+ function-bind@1.1.2:
+ resolution: {integrity: sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==}
+
gensync@1.0.0-beta.2:
resolution: {integrity: sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==}
engines: {node: '>=6.9.0'}
+ glob-parent@5.1.2:
+ resolution: {integrity: sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==}
+ engines: {node: '>= 6'}
+
+ glob-parent@6.0.2:
+ resolution: {integrity: sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==}
+ engines: {node: '>=10.13.0'}
+
+ hasown@2.0.4:
+ resolution: {integrity: sha512-T2UbfbBEF32wiepXIsMlTW9+dDYC6wMh/t/vYA4tuOMKqWz/n3vr1NFSxQiyP+zk2mXsoMA/i/7qV6LKut1t1A==}
+ engines: {node: '>= 0.4'}
+
html-entities@2.3.3:
resolution: {integrity: sha512-DV5Ln36z34NNTDgnz0EWGBLZENelNAtkiFA4kyNOG2tDI6Mz1uSWiq1wAKdyjnJwyDiDO7Fa2SO1CTxPXL8VxA==}
+ is-binary-path@2.1.0:
+ resolution: {integrity: sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==}
+ engines: {node: '>=8'}
+
+ is-core-module@2.16.2:
+ resolution: {integrity: sha512-evOr8xfXKxE6qSR0hSXL2r3sd7ALj8+7jQEUvPYcm5sgZFdJ+AYzT6yNmJenvIYQBgIGwfwz08sL8zoL7yq2BA==}
+ engines: {node: '>= 0.4'}
+
+ is-extglob@2.1.1:
+ resolution: {integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==}
+ engines: {node: '>=0.10.0'}
+
+ is-glob@4.0.3:
+ resolution: {integrity: sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==}
+ engines: {node: '>=0.10.0'}
+
+ is-number@7.0.0:
+ resolution: {integrity: sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==}
+ engines: {node: '>=0.12.0'}
+
is-what@4.1.16:
resolution: {integrity: sha512-ZhMwEosbFJkA0YhFnNDgTM4ZxDRsS6HqTo7qsZM08fehyRYIYa0yHu5R6mgo1n/8MgaPBXiPimPD77baVFYg+A==}
engines: {node: '>=12.13'}
+ jiti@1.21.7:
+ resolution: {integrity: sha512-/imKNG4EbWNrVjoNC/1H5/9GFy+tqjGBHCaSsN+P2RnPqjsLmv6UD3Ej+Kj8nBWaRAwyk7kK5ZUc+OEatnTR3A==}
+ hasBin: true
+
js-tokens@4.0.0:
resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==}
@@ -554,6 +797,13 @@ packages:
engines: {node: '>=6'}
hasBin: true
+ lilconfig@3.1.3:
+ resolution: {integrity: sha512-/vlFKAoH5Cgt3Ie+JLhRbwOsCQePABiU3tJ1egGvyQ+33R/vcwM2Zl2QR/LzjsBeItPt3oSVXapn+m4nQDvpzw==}
+ engines: {node: '>=14'}
+
+ lines-and-columns@1.2.4:
+ resolution: {integrity: sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==}
+
lru-cache@5.1.1:
resolution: {integrity: sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==}
@@ -561,9 +811,20 @@ packages:
resolution: {integrity: sha512-eRtbOb1N5iyH0tkQDAoQ4Ipsp/5qSR79Dzrz8hEPxRX10RWWR/iQXdoKmBSRCThY1Fh5EhISDtpSc93fpxUniQ==}
engines: {node: '>=12.13'}
+ merge2@1.4.1:
+ resolution: {integrity: sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==}
+ engines: {node: '>= 8'}
+
+ micromatch@4.0.8:
+ resolution: {integrity: sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==}
+ engines: {node: '>=8.6'}
+
ms@2.1.3:
resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==}
+ mz@2.7.0:
+ resolution: {integrity: sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q==}
+
nanoid@3.3.19:
resolution: {integrity: sha512-Y2tUNy4ouw6tq5oDSKeQYGOyhkUBhNOcGV/02KC+6kd9eDGqdZd++mjMiIDilrBYvjEnCYvVtsuHCuP+okSfug==}
engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1}
@@ -573,25 +834,113 @@ packages:
resolution: {integrity: sha512-mIrE/Cw9y+9Au6dS5vDKDhQza9YvG6w+ZrS6X+ZzA7yFW/soAeaups4Qzn1bL6g5FVy8WtP79+0j82oPIbqRjQ==}
engines: {node: '>=18'}
+ normalize-path@3.0.0:
+ resolution: {integrity: sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==}
+ engines: {node: '>=0.10.0'}
+
+ object-assign@4.1.1:
+ resolution: {integrity: sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==}
+ engines: {node: '>=0.10.0'}
+
+ object-hash@3.0.0:
+ resolution: {integrity: sha512-RSn9F68PjH9HqtltsSnqYC1XXoWe9Bju5+213R98cNGttag9q9yAOTzdbsqvIa7aNm5WffBZFpWYr2aWrklWAw==}
+ engines: {node: '>= 6'}
+
parse5@7.3.0:
resolution: {integrity: sha512-IInvU7fabl34qmi9gY8XOVxhYyMyuH2xUNpb2q8/Y+7552KlejkRvqvD19nMoUW/uQGGbqNpA6Tufu5FL5BZgw==}
+ path-parse@1.0.7:
+ resolution: {integrity: sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==}
+
picocolors@1.1.1:
resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==}
+ picomatch@2.3.2:
+ resolution: {integrity: sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA==}
+ engines: {node: '>=8.6'}
+
picomatch@4.0.7:
resolution: {integrity: sha512-qcJu88Q2IWqJsDD529JKMdwGm/dvInW4HvQnRwiH9JtihJvzGOscDtHE3x1pBKeUOTysQ8kVmLnJ2kJu7yhcGA==}
engines: {node: '>=12'}
+ pirates@4.0.7:
+ resolution: {integrity: sha512-TfySrs/5nm8fQJDcBDuUng3VOUKsd7S+zqvbOTiGXHfxX4wK31ard+hoNuvkicM/2YFzlpDgABOevKSsB4G/FA==}
+ engines: {node: '>= 6'}
+
+ postcss-import@15.1.0:
+ resolution: {integrity: sha512-hpr+J05B2FVYUAXHeK1YyI267J/dDDhMU6B6civm8hSY1jYJnBXxzKDKDswzJmtLHryrjhnDjqqp/49t8FALew==}
+ engines: {node: '>=14.0.0'}
+ peerDependencies:
+ postcss: ^8.0.0
+
+ postcss-js@4.1.0:
+ resolution: {integrity: sha512-oIAOTqgIo7q2EOwbhb8UalYePMvYoIeRY2YKntdpFQXNosSu3vLrniGgmH9OKs/qAkfoj5oB3le/7mINW1LCfw==}
+ engines: {node: ^12 || ^14 || >= 16}
+ peerDependencies:
+ postcss: ^8.4.21
+
+ postcss-load-config@6.0.1:
+ resolution: {integrity: sha512-oPtTM4oerL+UXmx+93ytZVN82RrlY/wPUV8IeDxFrzIjXOLF1pN+EmKPLbubvKHT2HC20xXsCAH2Z+CKV6Oz/g==}
+ engines: {node: '>= 18'}
+ peerDependencies:
+ jiti: '>=1.21.0'
+ postcss: '>=8.0.9'
+ tsx: ^4.8.1
+ yaml: ^2.4.2
+ peerDependenciesMeta:
+ jiti:
+ optional: true
+ postcss:
+ optional: true
+ tsx:
+ optional: true
+ yaml:
+ optional: true
+
+ postcss-nested@6.2.0:
+ resolution: {integrity: sha512-HQbt28KulC5AJzG+cZtj9kvKB93CFCdLvog1WFLf1D+xmMvPGlBstkpTEZfK5+AN9hfJocyBFCNiqyS48bpgzQ==}
+ engines: {node: '>=12.0'}
+ peerDependencies:
+ postcss: ^8.2.14
+
+ postcss-selector-parser@6.1.4:
+ resolution: {integrity: sha512-bIoJLOmjCO1S9XdY/DcnR5hJxvrDir1PbGChrzXG3vw0/FOliy/fA3dmdhQ441kah4gKv+TwckGzex6wNS5cnQ==}
+ engines: {node: '>=4'}
+
+ postcss-value-parser@4.2.0:
+ resolution: {integrity: sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==}
+
postcss@8.5.28:
resolution: {integrity: sha512-RRuzqDtt5Y9h3quz5hWhK+TPnsmVs6WwSU6LkJMeY4HstUEDuYTG8UJSdawMRzmzAtV+KEoG8N3Qg2qLy5vM/A==}
engines: {node: ^10 || ^12 || >=14}
+ queue-microtask@1.2.3:
+ resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==}
+
+ read-cache@1.0.2:
+ resolution: {integrity: sha512-/peqiBB/n07gQGLsWaHho3WfvUyRscw0gYTsEFMhrIe/nWLkYaf5SbKYjGYqtRV3aPwykJgF2VEMo1ac4bnsGA==}
+
+ readdirp@3.6.0:
+ resolution: {integrity: sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==}
+ engines: {node: '>=8.10.0'}
+
+ resolve@1.22.12:
+ resolution: {integrity: sha512-TyeJ1zif53BPfHootBGwPRYT1RUt6oGWsaQr8UyZW/eAm9bKoijtvruSDEmZHm92CwS9nj7/fWttqPCgzep8CA==}
+ engines: {node: '>= 0.4'}
+ hasBin: true
+
+ reusify@1.1.0:
+ resolution: {integrity: sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==}
+ engines: {iojs: '>=1.0.0', node: '>=0.10.0'}
+
rollup@4.63.2:
resolution: {integrity: sha512-l5eyksV4tPBj6lJyEa37YzIOCSOV7lkZzEHUdpjWZbtD7wTcFYmEYXSgm5bT4vV+dZLb9rBG1W9GROOG4NS4Ew==}
engines: {node: '>=18.0.0', npm: '>=8.0.0'}
hasBin: true
+ run-parallel@1.2.0:
+ resolution: {integrity: sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==}
+
semver@6.3.1:
resolution: {integrity: sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==}
hasBin: true
@@ -615,6 +964,16 @@ packages:
solid-js@1.9.15:
resolution: {integrity: sha512-EeiY2xfpZJqPLjXspVEKjAII4yv8NyG//NxZ3IpOFHdUNnnTyL0uJOeS9LWGvA7cFCz5y94cjFwYlmw5Luncsg==}
+ solid-presence@0.2.0:
+ resolution: {integrity: sha512-YM92o+jvpzX3XGaD4rLYmq/Kc2ZVh47GSCLEufHBFQQIurvZTs8SoGJxO8BJGNDxBKdcS8F3dYhW1SDXp4BNjA==}
+ peerDependencies:
+ solid-js: ^1.8
+
+ solid-prevent-scroll@0.1.11:
+ resolution: {integrity: sha512-2PComVCDHaQN/5t7ogEqUYeHasritZKXZ8Sb/VLkl0Web8o9YhEwOo8LSujJEahvMlYCNyKt0zRJeHldOAeWZg==}
+ peerDependencies:
+ solid-js: ^1.8
+
solid-refresh@0.6.3:
resolution: {integrity: sha512-F3aPsX6hVw9ttm5LYlth8Q15x6MlI/J3Dn+o3EQyRTtTxidepSTwAYdozt01/YA+7ObcciagGEyXIopGZzQtbA==}
peerDependencies:
@@ -624,21 +983,66 @@ packages:
resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==}
engines: {node: '>=0.10.0'}
+ sucrase@3.35.1:
+ resolution: {integrity: sha512-DhuTmvZWux4H1UOnWMB3sk0sbaCVOoQZjv8u1rDoTV0HTdGem9hkAZtl4JZy8P2z4Bg0nT+YMeOFyVr4zcG5Tw==}
+ engines: {node: '>=16 || 14 >=14.17'}
+ hasBin: true
+
+ supports-preserve-symlinks-flag@1.0.0:
+ resolution: {integrity: sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==}
+ engines: {node: '>= 0.4'}
+
+ tailwind-merge@3.7.0:
+ resolution: {integrity: sha512-XPPUyAc+cvspz3lHTcR/QgPfW2A0lv/xQNIjX3HGhLR+Nq2lHaLq5MtTesHn8GUr3W3DguT2KT5x3NVgRtYwmA==}
+
+ tailwindcss-animate@1.0.7:
+ resolution: {integrity: sha512-bl6mpH3T7I3UFxuvDEXLxy/VuFxBk5bbzplh7tXI68mwMokNYd1t9qPBHlnyTwfa4JGC4zP516I1hYYtQ/vspA==}
+ peerDependencies:
+ tailwindcss: '>=3.0.0 || insiders'
+
+ tailwindcss@3.4.19:
+ resolution: {integrity: sha512-3ofp+LL8E+pK/JuPLPggVAIaEuhvIz4qNcf3nA1Xn2o/7fb7s/TYpHhwGDv1ZU3PkBluUVaF8PyCHcm48cKLWQ==}
+ engines: {node: '>=14.0.0'}
+ hasBin: true
+
+ thenify-all@1.6.0:
+ resolution: {integrity: sha512-RNxQH/qI8/t3thXJDwcstUO4zeqo64+Uy/+sNVRBx4Xn2OX+OZ9oP+iJnNFqplFra2ZUVeKCSa2oVWi3T4uVmA==}
+ engines: {node: '>=0.8'}
+
+ thenify@3.3.1:
+ resolution: {integrity: sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw==}
+
tinyglobby@0.2.17:
resolution: {integrity: sha512-wXR/dYpcqKmfWpEdZjiKJOwCNFndD0DMnrW/cYjVGttEkBfVgcLFHoNrlj47mjOVic9yyNu65alsgF4NQyTa2g==}
engines: {node: '>=12.0.0'}
+ to-regex-range@5.0.1:
+ resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==}
+ engines: {node: '>=8.0'}
+
+ ts-interface-checker@0.1.13:
+ resolution: {integrity: sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==}
+
+ tslib@2.8.1:
+ resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==}
+
typescript@5.6.3:
resolution: {integrity: sha512-hjcS1mhfuyi4WW8IWtjP7brDrG2cuDZukyrYrSauoXGNgx0S7zceP07adYkJycEr56BOUTNPzbInooiN3fn1qw==}
engines: {node: '>=14.17'}
hasBin: true
+ undici-types@8.9.0:
+ resolution: {integrity: sha512-KTDyRTYX8sWmKXAikPHHSyc63CRPETMctyjKFupcC6OBLXT3xsN0e9aF7m+mIXutFWpUXuedtowG7iLOzp0kQg==}
+
update-browserslist-db@1.3.3:
resolution: {integrity: sha512-pJ2sYawQS0R/WI928Gj5GlPhTGzbMelq0+4INtSYNDV9ErKJcX6xjGWkoG/VnB3dpUm00zALaqkrUD77pO5TDQ==}
hasBin: true
peerDependencies:
browserslist: '>= 4.21.0'
+ util-deprecate@1.0.2:
+ resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==}
+
vite-plugin-solid@2.11.6:
resolution: {integrity: sha512-Sl5CTqJTGyEeOsmdH6BOgalIZlwH3t4/y0RQuFLMGnvWMBvxb4+lq7x3BSiAw6etf0QexfNJW7HSOO/Qf7pigg==}
peerDependencies:
@@ -702,6 +1106,8 @@ packages:
snapshots:
+ '@alloc/quick-lru@5.3.0': {}
+
'@babel/code-frame@7.29.7':
dependencies:
'@babel/helper-validator-identifier': 7.29.7
@@ -813,6 +1219,11 @@ snapshots:
'@babel/helper-string-parser': 7.29.7
'@babel/helper-validator-identifier': 7.29.7
+ '@corvu/utils@0.4.2(solid-js@1.9.15)':
+ dependencies:
+ '@floating-ui/dom': 1.8.0
+ solid-js: 1.9.15
+
'@esbuild/aix-ppc64@0.25.12':
optional: true
@@ -891,6 +1302,21 @@ snapshots:
'@esbuild/win32-x64@0.25.12':
optional: true
+ '@floating-ui/core@1.8.0':
+ dependencies:
+ '@floating-ui/utils': 0.2.12
+
+ '@floating-ui/dom@1.8.0':
+ dependencies:
+ '@floating-ui/core': 1.8.0
+ '@floating-ui/utils': 0.2.12
+
+ '@floating-ui/utils@0.2.12': {}
+
+ '@internationalized/number@3.6.8':
+ dependencies:
+ '@swc/helpers': 0.5.23
+
'@jridgewell/gen-mapping@0.3.13':
dependencies:
'@jridgewell/sourcemap-codec': 1.6.0
@@ -910,11 +1336,45 @@ snapshots:
'@jridgewell/resolve-uri': 3.1.2
'@jridgewell/sourcemap-codec': 1.6.0
+ '@kobalte/core@0.13.14(solid-js@1.9.15)':
+ dependencies:
+ '@floating-ui/dom': 1.8.0
+ '@internationalized/number': 3.6.8
+ '@kobalte/utils': 0.9.2(solid-js@1.9.15)
+ '@solid-primitives/props': 3.2.4(solid-js@1.9.15)
+ '@solid-primitives/resize-observer': 2.2.0(solid-js@1.9.15)
+ solid-js: 1.9.15
+ solid-presence: 0.2.0(solid-js@1.9.15)
+ solid-prevent-scroll: 0.1.11(solid-js@1.9.15)
+
+ '@kobalte/utils@0.9.2(solid-js@1.9.15)':
+ dependencies:
+ '@solid-primitives/event-listener': 2.4.6(solid-js@1.9.15)
+ '@solid-primitives/keyed': 1.5.3(solid-js@1.9.15)
+ '@solid-primitives/map': 0.4.13(solid-js@1.9.15)
+ '@solid-primitives/media': 2.3.6(solid-js@1.9.15)
+ '@solid-primitives/props': 3.2.4(solid-js@1.9.15)
+ '@solid-primitives/refs': 1.1.4(solid-js@1.9.15)
+ '@solid-primitives/utils': 6.4.1(solid-js@1.9.15)
+ solid-js: 1.9.15
+
'@kurkle/color@0.3.4': {}
'@napi-rs/lzma-linux-x64-gnu@1.5.1':
optional: true
+ '@nodelib/fs.scandir@2.1.5':
+ dependencies:
+ '@nodelib/fs.stat': 2.0.5
+ run-parallel: 1.2.0
+
+ '@nodelib/fs.stat@2.0.5': {}
+
+ '@nodelib/fs.walk@1.2.8':
+ dependencies:
+ '@nodelib/fs.scandir': 2.1.5
+ fastq: 1.20.3
+
'@rollup/rollup-android-arm-eabi@4.63.2':
optional: true
@@ -990,10 +1450,73 @@ snapshots:
'@rollup/rollup-win32-x64-msvc@4.63.2':
optional: true
+ '@solid-primitives/event-listener@2.4.6(solid-js@1.9.15)':
+ dependencies:
+ '@solid-primitives/utils': 6.4.1(solid-js@1.9.15)
+ solid-js: 1.9.15
+
+ '@solid-primitives/keyed@1.5.3(solid-js@1.9.15)':
+ dependencies:
+ solid-js: 1.9.15
+
+ '@solid-primitives/map@0.4.13(solid-js@1.9.15)':
+ dependencies:
+ '@solid-primitives/trigger': 1.2.4(solid-js@1.9.15)
+ solid-js: 1.9.15
+
+ '@solid-primitives/media@2.3.6(solid-js@1.9.15)':
+ dependencies:
+ '@solid-primitives/event-listener': 2.4.6(solid-js@1.9.15)
+ '@solid-primitives/rootless': 1.5.4(solid-js@1.9.15)
+ '@solid-primitives/static-store': 0.1.4(solid-js@1.9.15)
+ '@solid-primitives/utils': 6.4.1(solid-js@1.9.15)
+ solid-js: 1.9.15
+
+ '@solid-primitives/props@3.2.4(solid-js@1.9.15)':
+ dependencies:
+ '@solid-primitives/utils': 6.4.1(solid-js@1.9.15)
+ solid-js: 1.9.15
+
+ '@solid-primitives/refs@1.1.4(solid-js@1.9.15)':
+ dependencies:
+ '@solid-primitives/utils': 6.4.1(solid-js@1.9.15)
+ solid-js: 1.9.15
+
+ '@solid-primitives/resize-observer@2.2.0(solid-js@1.9.15)':
+ dependencies:
+ '@solid-primitives/event-listener': 2.4.6(solid-js@1.9.15)
+ '@solid-primitives/rootless': 1.5.4(solid-js@1.9.15)
+ '@solid-primitives/static-store': 0.1.4(solid-js@1.9.15)
+ '@solid-primitives/utils': 6.4.1(solid-js@1.9.15)
+ solid-js: 1.9.15
+
+ '@solid-primitives/rootless@1.5.4(solid-js@1.9.15)':
+ dependencies:
+ '@solid-primitives/utils': 6.4.1(solid-js@1.9.15)
+ solid-js: 1.9.15
+
+ '@solid-primitives/static-store@0.1.4(solid-js@1.9.15)':
+ dependencies:
+ '@solid-primitives/utils': 6.4.1(solid-js@1.9.15)
+ solid-js: 1.9.15
+
+ '@solid-primitives/trigger@1.2.4(solid-js@1.9.15)':
+ dependencies:
+ '@solid-primitives/utils': 6.4.1(solid-js@1.9.15)
+ solid-js: 1.9.15
+
+ '@solid-primitives/utils@6.4.1(solid-js@1.9.15)':
+ dependencies:
+ solid-js: 1.9.15
+
'@solidjs/router@0.15.3(solid-js@1.9.15)':
dependencies:
solid-js: 1.9.15
+ '@swc/helpers@0.5.23':
+ dependencies:
+ tslib: 2.8.1
+
'@types/babel__core@7.20.5':
dependencies:
'@babel/parser': 7.29.8
@@ -1017,6 +1540,28 @@ snapshots:
'@types/estree@1.0.9': {}
+ '@types/node@26.5.1':
+ dependencies:
+ undici-types: 8.9.0
+
+ any-promise@1.3.0: {}
+
+ anymatch@3.1.3:
+ dependencies:
+ normalize-path: 3.0.0
+ picomatch: 2.3.2
+
+ arg@5.0.2: {}
+
+ autoprefixer@10.6.0(postcss@8.5.28):
+ dependencies:
+ browserslist: 4.28.9
+ caniuse-lite: 1.0.30001810
+ fraction.js: 5.3.4
+ picocolors: 1.1.1
+ postcss: 8.5.28
+ postcss-value-parser: 4.2.0
+
babel-plugin-jsx-dom-expressions@0.40.10(@babel/core@7.29.7):
dependencies:
'@babel/core': 7.29.7
@@ -1035,6 +1580,12 @@ snapshots:
baseline-browser-mapping@2.11.23: {}
+ binary-extensions@2.3.0: {}
+
+ braces@3.0.3:
+ dependencies:
+ fill-range: 7.1.1
+
browserslist@4.28.9:
dependencies:
baseline-browser-mapping: 2.11.23
@@ -1043,24 +1594,54 @@ snapshots:
node-releases: 2.0.55
update-browserslist-db: 1.3.3(browserslist@4.28.9)
+ camelcase-css@2.0.1: {}
+
caniuse-lite@1.0.30001810: {}
chart.js@4.4.7:
dependencies:
'@kurkle/color': 0.3.4
+ chokidar@3.6.0:
+ dependencies:
+ anymatch: 3.1.3
+ braces: 3.0.3
+ glob-parent: 5.1.2
+ is-binary-path: 2.1.0
+ is-glob: 4.0.3
+ normalize-path: 3.0.0
+ readdirp: 3.6.0
+ optionalDependencies:
+ fsevents: 2.3.3
+
+ class-variance-authority@0.7.1:
+ dependencies:
+ clsx: 2.1.1
+
+ clsx@2.1.1: {}
+
+ commander@4.1.1: {}
+
convert-source-map@2.0.0: {}
+ cssesc@3.0.0: {}
+
csstype@3.2.3: {}
debug@4.4.3:
dependencies:
ms: 2.1.3
+ didyoumean@1.2.2: {}
+
+ dlv@1.1.3: {}
+
electron-to-chromium@1.5.427: {}
entities@6.0.1: {}
+ es-errors@1.3.0: {}
+
esbuild@0.25.12:
optionalDependencies:
'@esbuild/aix-ppc64': 0.25.12
@@ -1092,25 +1673,79 @@ snapshots:
escalade@3.2.0: {}
+ fast-glob@3.3.3:
+ dependencies:
+ '@nodelib/fs.stat': 2.0.5
+ '@nodelib/fs.walk': 1.2.8
+ glob-parent: 5.1.2
+ merge2: 1.4.1
+ micromatch: 4.0.8
+
+ fastq@1.20.3:
+ dependencies:
+ reusify: 1.1.0
+
fdir@6.5.0(picomatch@4.0.7):
optionalDependencies:
picomatch: 4.0.7
+ fill-range@7.1.1:
+ dependencies:
+ to-regex-range: 5.0.1
+
+ fraction.js@5.3.4: {}
+
fsevents@2.3.3:
optional: true
+ function-bind@1.1.2: {}
+
gensync@1.0.0-beta.2: {}
+ glob-parent@5.1.2:
+ dependencies:
+ is-glob: 4.0.3
+
+ glob-parent@6.0.2:
+ dependencies:
+ is-glob: 4.0.3
+
+ hasown@2.0.4:
+ dependencies:
+ function-bind: 1.1.2
+
html-entities@2.3.3: {}
+ is-binary-path@2.1.0:
+ dependencies:
+ binary-extensions: 2.3.0
+
+ is-core-module@2.16.2:
+ dependencies:
+ hasown: 2.0.4
+
+ is-extglob@2.1.1: {}
+
+ is-glob@4.0.3:
+ dependencies:
+ is-extglob: 2.1.1
+
+ is-number@7.0.0: {}
+
is-what@4.1.16: {}
+ jiti@1.21.7: {}
+
js-tokens@4.0.0: {}
jsesc@3.1.0: {}
json5@2.2.3: {}
+ lilconfig@3.1.3: {}
+
+ lines-and-columns@1.2.4: {}
+
lru-cache@5.1.1:
dependencies:
yallist: 3.1.1
@@ -1119,26 +1754,99 @@ snapshots:
dependencies:
is-what: 4.1.16
+ merge2@1.4.1: {}
+
+ micromatch@4.0.8:
+ dependencies:
+ braces: 3.0.3
+ picomatch: 2.3.2
+
ms@2.1.3: {}
+ mz@2.7.0:
+ dependencies:
+ any-promise: 1.3.0
+ object-assign: 4.1.1
+ thenify-all: 1.6.0
+
nanoid@3.3.19: {}
node-releases@2.0.55: {}
+ normalize-path@3.0.0: {}
+
+ object-assign@4.1.1: {}
+
+ object-hash@3.0.0: {}
+
parse5@7.3.0:
dependencies:
entities: 6.0.1
+ path-parse@1.0.7: {}
+
picocolors@1.1.1: {}
+ picomatch@2.3.2: {}
+
picomatch@4.0.7: {}
+ pirates@4.0.7: {}
+
+ postcss-import@15.1.0(postcss@8.5.28):
+ dependencies:
+ postcss: 8.5.28
+ postcss-value-parser: 4.2.0
+ read-cache: 1.0.2
+ resolve: 1.22.12
+
+ postcss-js@4.1.0(postcss@8.5.28):
+ dependencies:
+ camelcase-css: 2.0.1
+ postcss: 8.5.28
+
+ postcss-load-config@6.0.1(jiti@1.21.7)(postcss@8.5.28):
+ dependencies:
+ lilconfig: 3.1.3
+ optionalDependencies:
+ jiti: 1.21.7
+ postcss: 8.5.28
+
+ postcss-nested@6.2.0(postcss@8.5.28):
+ dependencies:
+ postcss: 8.5.28
+ postcss-selector-parser: 6.1.4
+
+ postcss-selector-parser@6.1.4:
+ dependencies:
+ cssesc: 3.0.0
+ util-deprecate: 1.0.2
+
+ postcss-value-parser@4.2.0: {}
+
postcss@8.5.28:
dependencies:
nanoid: 3.3.19
picocolors: 1.1.1
source-map-js: 1.2.1
+ queue-microtask@1.2.3: {}
+
+ read-cache@1.0.2: {}
+
+ readdirp@3.6.0:
+ dependencies:
+ picomatch: 2.3.2
+
+ resolve@1.22.12:
+ dependencies:
+ es-errors: 1.3.0
+ is-core-module: 2.16.2
+ path-parse: 1.0.7
+ supports-preserve-symlinks-flag: 1.0.0
+
+ reusify@1.1.0: {}
+
rollup@4.63.2:
dependencies:
'@types/estree': 1.0.9
@@ -1171,6 +1879,10 @@ snapshots:
'@rollup/rollup-win32-x64-msvc': 4.63.2
fsevents: 2.3.3
+ run-parallel@1.2.0:
+ dependencies:
+ queue-microtask: 1.2.3
+
semver@6.3.1: {}
seroval-plugins@1.5.6(seroval@1.5.6):
@@ -1190,6 +1902,16 @@ snapshots:
seroval: 1.5.6
seroval-plugins: 1.5.6(seroval@1.5.6)
+ solid-presence@0.2.0(solid-js@1.9.15):
+ dependencies:
+ '@corvu/utils': 0.4.2(solid-js@1.9.15)
+ solid-js: 1.9.15
+
+ solid-prevent-scroll@0.1.11(solid-js@1.9.15):
+ dependencies:
+ '@corvu/utils': 0.4.2(solid-js@1.9.15)
+ solid-js: 1.9.15
+
solid-refresh@0.6.3(solid-js@1.9.15):
dependencies:
'@babel/generator': 7.29.8
@@ -1201,20 +1923,86 @@ snapshots:
source-map-js@1.2.1: {}
+ sucrase@3.35.1:
+ dependencies:
+ '@jridgewell/gen-mapping': 0.3.13
+ commander: 4.1.1
+ lines-and-columns: 1.2.4
+ mz: 2.7.0
+ pirates: 4.0.7
+ tinyglobby: 0.2.17
+ ts-interface-checker: 0.1.13
+
+ supports-preserve-symlinks-flag@1.0.0: {}
+
+ tailwind-merge@3.7.0: {}
+
+ tailwindcss-animate@1.0.7(tailwindcss@3.4.19):
+ dependencies:
+ tailwindcss: 3.4.19
+
+ tailwindcss@3.4.19:
+ dependencies:
+ '@alloc/quick-lru': 5.3.0
+ arg: 5.0.2
+ chokidar: 3.6.0
+ didyoumean: 1.2.2
+ dlv: 1.1.3
+ fast-glob: 3.3.3
+ glob-parent: 6.0.2
+ is-glob: 4.0.3
+ jiti: 1.21.7
+ lilconfig: 3.1.3
+ micromatch: 4.0.8
+ normalize-path: 3.0.0
+ object-hash: 3.0.0
+ picocolors: 1.1.1
+ postcss: 8.5.28
+ postcss-import: 15.1.0(postcss@8.5.28)
+ postcss-js: 4.1.0(postcss@8.5.28)
+ postcss-load-config: 6.0.1(jiti@1.21.7)(postcss@8.5.28)
+ postcss-nested: 6.2.0(postcss@8.5.28)
+ postcss-selector-parser: 6.1.4
+ resolve: 1.22.12
+ sucrase: 3.35.1
+ transitivePeerDependencies:
+ - tsx
+ - yaml
+
+ thenify-all@1.6.0:
+ dependencies:
+ thenify: 3.3.1
+
+ thenify@3.3.1:
+ dependencies:
+ any-promise: 1.3.0
+
tinyglobby@0.2.17:
dependencies:
fdir: 6.5.0(picomatch@4.0.7)
picomatch: 4.0.7
+ to-regex-range@5.0.1:
+ dependencies:
+ is-number: 7.0.0
+
+ ts-interface-checker@0.1.13: {}
+
+ tslib@2.8.1: {}
+
typescript@5.6.3: {}
+ undici-types@8.9.0: {}
+
update-browserslist-db@1.3.3(browserslist@4.28.9):
dependencies:
browserslist: 4.28.9
escalade: 3.2.0
picocolors: 1.1.1
- vite-plugin-solid@2.11.6(solid-js@1.9.15)(vite@6.4.3):
+ util-deprecate@1.0.2: {}
+
+ vite-plugin-solid@2.11.6(solid-js@1.9.15)(vite@6.4.3(@types/node@26.5.1)(jiti@1.21.7)):
dependencies:
'@babel/core': 7.29.7
'@types/babel__core': 7.20.5
@@ -1222,12 +2010,12 @@ snapshots:
merge-anything: 5.1.7
solid-js: 1.9.15
solid-refresh: 0.6.3(solid-js@1.9.15)
- vite: 6.4.3
- vitefu: 1.1.3(vite@6.4.3)
+ vite: 6.4.3(@types/node@26.5.1)(jiti@1.21.7)
+ vitefu: 1.1.3(vite@6.4.3(@types/node@26.5.1)(jiti@1.21.7))
transitivePeerDependencies:
- supports-color
- vite@6.4.3:
+ vite@6.4.3(@types/node@26.5.1)(jiti@1.21.7):
dependencies:
esbuild: 0.25.12
fdir: 6.5.0(picomatch@4.0.7)
@@ -1236,10 +2024,12 @@ snapshots:
rollup: 4.63.2
tinyglobby: 0.2.17
optionalDependencies:
+ '@types/node': 26.5.1
fsevents: 2.3.3
+ jiti: 1.21.7
- vitefu@1.1.3(vite@6.4.3):
+ vitefu@1.1.3(vite@6.4.3(@types/node@26.5.1)(jiti@1.21.7)):
optionalDependencies:
- vite: 6.4.3
+ vite: 6.4.3(@types/node@26.5.1)(jiti@1.21.7)
yallist@3.1.1: {}
diff --git a/web/postcss.config.js b/web/postcss.config.js
new file mode 100644
index 0000000..2aa7205
--- /dev/null
+++ b/web/postcss.config.js
@@ -0,0 +1,6 @@
+export default {
+ plugins: {
+ tailwindcss: {},
+ autoprefixer: {},
+ },
+};
diff --git a/web/src/components/Cards.tsx b/web/src/components/Cards.tsx
index 0554597..44fb6ba 100644
--- a/web/src/components/Cards.tsx
+++ b/web/src/components/Cards.tsx
@@ -1,45 +1,61 @@
import { For, Show } from "solid-js";
import { fmtIDR } from "../lib/api";
import { Citations } from "./Citations";
+import { Badge } from "./ui/badge";
+import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "./ui/card";
+import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "./ui/table";
export function AccumulationTable(props: { rows: { ticker: string; net_sum: number; brokers: number }[] }) {
return (
-
-
Top accumulation
-
- | Ticker | Net | Brokers |
-
-
- {(r) => | {r.ticker} | {fmtIDR(r.net_sum)} | {r.brokers} |
}
-
-
-
-
No accumulation snapshots yet.
-
+
+
+ Top accumulation
+ Net broker accumulation per ticker.
+
+
+
+ TickerNetBrokers
+
+
+ {(r) => {r.ticker}{fmtIDR(r.net_sum)}{r.brokers}}
+
+
+
+ No accumulation snapshots yet.
+
+
);
}
export function RotationMap(props: { signal?: string; citations?: Parameters[0]["items"] }) {
return (
-
-
Sector rotation
-
{props.signal || "No rotation flip detected."}
-
-
+
+
+ Sector rotation
+
+
+ {props.signal || "No rotation flip detected."}
+
+
+
);
}
export function ActivityFeed(props: { events: { id?: number; ticker?: string; message?: string; date?: string }[] }) {
return (
-
-
Activity
-
No events yet.}>
-
-
- {(e) => - {e.ticker} — {e.message} {e.date}
}
-
-
-
-
+
+
+ Activity
+
+
+ No events yet.
}>
+
+
+ {(e) => - {e.ticker} {e.message} {e.date}
}
+
+
+
+
+
);
}
diff --git a/web/src/components/Citations.tsx b/web/src/components/Citations.tsx
index 86de755..ad33358 100644
--- a/web/src/components/Citations.tsx
+++ b/web/src/components/Citations.tsx
@@ -1,5 +1,7 @@
import { For } from "solid-js";
import type { Citation } from "../lib/api";
+import { Badge } from "./ui/badge";
+import { Tooltip, TooltipContent, TooltipTrigger } from "./ui/tooltip";
export function Citations(props: { items?: Citation[] | string }) {
const list = (): Citation[] => {
@@ -10,12 +12,17 @@ export function Citations(props: { items?: Citation[] | string }) {
return props.items;
};
return (
-
+
{(c) => (
-
- {c.endpoint} {c.ticker || ""} @ {(c.snapshot_at || "").slice(0, 10)}{c.stale ? " · stale" : ""}
-
+
+
+
+ {c.endpoint} {c.ticker || ""} @ {(c.snapshot_at || "").slice(0, 10)}{c.stale ? " · stale" : ""}
+
+
+ {c.endpoint} @ {c.snapshot_at}{c.stale ? " (stale)" : ""}
+
)}
diff --git a/web/src/components/WatchlistChat.tsx b/web/src/components/WatchlistChat.tsx
index 8c07c4a..e4b7a25 100644
--- a/web/src/components/WatchlistChat.tsx
+++ b/web/src/components/WatchlistChat.tsx
@@ -1,7 +1,10 @@
import { createResource, createSignal, For, Show } from "solid-js";
import { api } from "../lib/api";
+import { Badge } from "./ui/badge";
+import { Button } from "./ui/button";
+import { Card, CardContent, CardHeader, CardTitle } from "./ui/card";
+import { TextField, TextFieldInput } from "./ui/text-field";
-// WatchlistDrawer: global watchlist add/remove backed by /api/watchlist.
export function WatchlistDrawer() {
const [wl, { refetch }] = createResource(() => api.watchlist());
const [ticker, setTicker] = createSignal("");
@@ -19,17 +22,22 @@ export function WatchlistDrawer() {
refetch();
}
return (
-
-
Watchlist
-
setTicker(e.currentTarget.value)} style={{ width: "90px" }} />
-
-
{err()}
-
-
+
+ Watchlist
+
+
+ setTicker(e.currentTarget.value)} />
+
+
+ {err()}
+
+ {(t) => - {t}
}
+
+
+
);
}
-// ChatSidebar: context-aware Q&A backed by POST /api/chat.
export function ChatSidebar() {
const [msg, setMsg] = createSignal("");
const [log, setLog] = createSignal<{ q: string; a: string }[]>([]);
@@ -45,13 +53,19 @@ export function ChatSidebar() {
}
}
return (
-
-
AI Chat
-
- {(m) => You: {m.q}
{m.a.slice(0, 500)}
}
-
-
setMsg(e.currentTarget.value)} style={{ width: "220px" }} />
-
-
+
+ AI Chat
+
+
+
+ {(m) => You: {m.q}
{m.a.slice(0, 500)}
}
+
+
+
+ setMsg(e.currentTarget.value)} onKeyDown={(e: KeyboardEvent) => { if (e.key === "Enter") send(); }} />
+
+
+
+
);
}
diff --git a/web/src/components/auth.tsx b/web/src/components/auth.tsx
index f0f12c6..cd4b3a5 100644
--- a/web/src/components/auth.tsx
+++ b/web/src/components/auth.tsx
@@ -1,14 +1,15 @@
import { createResource, createSignal, Show } from "solid-js";
import { api, type AuthUser } from "../lib/api";
+import { Button } from "./ui/button";
+import { Avatar, AvatarFallback, AvatarImage } from "./ui/avatar";
-// AuthState: loads /api/auth/me once; exposes login/logout + theme toggle.
+// Theme: .dark class on (shadcn convention). Default dark.
function theme(): string {
- return localStorage.getItem("fs-theme") ||
- (matchMedia("(prefers-color-scheme: light)").matches ? "light" : "dark");
+ return localStorage.getItem("fs-theme") || "dark";
}
export function applyTheme(t: string) {
- document.documentElement.dataset.theme = t;
+ document.documentElement.classList.toggle("dark", t === "dark");
localStorage.setItem("fs-theme", t);
}
applyTheme(theme());
@@ -16,12 +17,12 @@ applyTheme(theme());
export function ThemeToggle() {
const [t, setT] = createSignal(theme());
return (
-
);
}
@@ -35,13 +36,16 @@ export function useAuth() {
export function AuthButton(props: { me: AuthUser | null | undefined; onLogout: () => void }) {
return (
Sign in with Google
+ Sign in with Google
}>
{(u) => (
-
- {u().avatar_url ?
: null}
- {u().name || u().email}
- Logout
+
+
+ {u().avatar_url ? : null}
+ {(u().name || u().email || "?").slice(0, 1).toUpperCase()}
+
+ {u().name || u().email}
+ Logout
)}
diff --git a/web/src/components/ui.tsx b/web/src/components/ui.tsx
index 823f668..18281f4 100644
--- a/web/src/components/ui.tsx
+++ b/web/src/components/ui.tsx
@@ -1,66 +1,88 @@
-import { For, Show, type JSX } from "solid-js";
+import { For } from "solid-js";
+import type { Citation } from "../lib/api";
+import { Badge } from "./ui/badge";
+import { Tooltip, TooltipContent, TooltipTrigger } from "./ui/tooltip";
-// Shared building blocks: StatCard, EmptyState, Badge, SignalBar, Skeleton.
-
-export function StatCard(props: { title: string; value: string; sub?: string; children?: JSX.Element }) {
+export function Citations(props: { items?: Citation[] | string }) {
+ const list = (): Citation[] => {
+ if (!props.items) return [];
+ if (typeof props.items === "string") {
+ try { return JSON.parse(props.items); } catch { return []; }
+ }
+ return props.items;
+ };
return (
-
-
{props.title}
-
{props.value}
-
{props.sub}
- {props.children}
-
- );
-}
-
-export function EmptyState(props: { icon: string; title: string; hint?: string; action?: JSX.Element }) {
- return (
-
-
{props.icon}
-
{props.title}
-
{props.hint}
-
{props.action}
-
- );
-}
-
-export function Skeleton(props: { rows?: number }) {
- const n = props.rows ?? 3;
- return (
-
+
+
+ {(c) => (
+
+
+
+ {c.endpoint} {c.ticker || ""} @ {(c.snapshot_at || "").slice(0, 10)}{c.stale ? " · stale" : ""}
+
+
+ {c.endpoint} @ {c.snapshot_at}{c.stale ? " (stale)" : ""}
+
+ )}
+
+
);
}
export function RecBadge(props: { rec: string }) {
- const cls = props.rec === "BUY" ? "buy" : props.rec === "HOLD" ? "hold" : "avoid";
- return {props.rec};
+ const v = () =>
+ /buy/i.test(props.rec) ? { variant: "success" as const, icon: "▲" }
+ : /avoid|sell/i.test(props.rec) ? { variant: "error" as const, icon: "▼" }
+ : { variant: "warning" as const, icon: "●" };
+ return {v().icon} {props.rec};
}
-// SignalBar renders one -100..+100 signal as a labeled bar.
-export function SignalBar(props: { label: string; value: number }) {
- const v = Math.max(-100, Math.min(100, props.value));
- const left = v < 0 ? `${50 + v / 2}%` : "50%";
- const width = `${Math.abs(v) / 2}%`;
- const cls = v < 0 ? "neg" : v > 0 ? "pos" : "";
+export function BreakdownBars(props: { breakdown: Record }) {
+ const entries = () => Object.entries(props.breakdown)
+ .filter(([, v]) => typeof v === "number")
+ .sort((a, b) => Math.abs(b[1] as number) - Math.abs(a[1] as number))
+ .slice(0, 6);
return (
-
-
{props.label}
-
-
{v}
+
+
+ {([k, v]) => {
+ const n = v as number;
+ const pct = Math.min(100, Math.abs(n));
+ return (
+
+
{k}
+
+
= 0, "bg-error": n < 0 }}
+ style={{ width: `${pct}%`, "margin-left": n < 0 ? "auto" : undefined }}
+ />
+
+
{n.toFixed(1)}
+
+ );
+ }}
+
);
}
-// BreakdownBars renders a screen/report signal breakdown map as bars.
-// Unknown numeric fields are shown; booleans/strings are skipped except flags.
-export function BreakdownBars(props: { breakdown: Record }) {
- const entries = () =>
- Object.entries(props.breakdown || {}).filter(([, v]) => typeof v === "number") as [string, number][];
+export function PageHead(props: { title: string; sub?: string }) {
return (
- no signals}>
- {([k, v]) => }
-
+
+
{props.title}
+ {props.sub ?
{props.sub}
: null}
+
+ );
+}
+
+export function EmptyState(props: { icon: string; title: string; hint?: string; action?: import("solid-js").JSX.Element }) {
+ return (
+
+
{props.icon}
+
{props.title}
+ {props.hint ?
{props.hint}
: null}
+ {props.action}
+
);
}
diff --git a/web/src/components/ui/avatar.tsx b/web/src/components/ui/avatar.tsx
new file mode 100644
index 0000000..b84f7d9
--- /dev/null
+++ b/web/src/components/ui/avatar.tsx
@@ -0,0 +1,52 @@
+import type { ValidComponent } from "solid-js"
+import { splitProps } from "solid-js"
+
+import * as ImagePrimitive from "@kobalte/core/image"
+import type { PolymorphicProps } from "@kobalte/core/polymorphic"
+
+import { cn } from "../../lib/utils"
+
+type AvatarRootProps = ImagePrimitive.ImageRootProps & {
+ class?: string | undefined
+}
+
+const Avatar = (
+ props: PolymorphicProps>
+) => {
+ const [local, others] = splitProps(props as AvatarRootProps, ["class"])
+ return (
+
+ )
+}
+
+type AvatarImageProps = ImagePrimitive.ImageImgProps & {
+ class?: string | undefined
+}
+
+const AvatarImage = (
+ props: PolymorphicProps>
+) => {
+ const [local, others] = splitProps(props as AvatarImageProps, ["class"])
+ return
+}
+
+type AvatarFallbackProps =
+ ImagePrimitive.ImageFallbackProps & { class?: string | undefined }
+
+const AvatarFallback = (
+ props: PolymorphicProps>
+) => {
+ const [local, others] = splitProps(props as AvatarFallbackProps, ["class"])
+ return (
+
+ )
+}
+
+export { Avatar, AvatarImage, AvatarFallback }
+
diff --git a/web/src/components/ui/badge.tsx b/web/src/components/ui/badge.tsx
new file mode 100644
index 0000000..052bf40
--- /dev/null
+++ b/web/src/components/ui/badge.tsx
@@ -0,0 +1,49 @@
+import type { Component, ComponentProps } from "solid-js"
+import { splitProps } from "solid-js"
+
+import type { VariantProps } from "class-variance-authority"
+import { cva } from "class-variance-authority"
+
+import { cn } from "../../lib/utils"
+
+const badgeVariants = cva(
+ "inline-flex items-center rounded-md border px-2.5 py-0.5 text-xs font-semibold transition-colors focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2",
+ {
+ variants: {
+ variant: {
+ default: "border-transparent bg-primary text-primary-foreground",
+ secondary: "border-transparent bg-secondary text-secondary-foreground",
+ outline: "text-foreground",
+ success: "border-success-foreground bg-success text-success-foreground",
+ warning: "border-warning-foreground bg-warning text-warning-foreground",
+ error: "border-error-foreground bg-error text-error-foreground"
+ }
+ },
+ defaultVariants: {
+ variant: "default"
+ }
+ }
+)
+
+type BadgeProps = ComponentProps<"div"> &
+ VariantProps & {
+ round?: boolean
+ }
+
+const Badge: Component = (props) => {
+ const [local, others] = splitProps(props, ["class", "variant", "round"])
+ return (
+
+ )
+}
+
+export type { BadgeProps }
+export { Badge, badgeVariants }
+
diff --git a/web/src/components/ui/button.tsx b/web/src/components/ui/button.tsx
new file mode 100644
index 0000000..fc66652
--- /dev/null
+++ b/web/src/components/ui/button.tsx
@@ -0,0 +1,54 @@
+import type { JSX, ValidComponent } from "solid-js"
+import { splitProps } from "solid-js"
+
+import * as ButtonPrimitive from "@kobalte/core/button"
+import type { PolymorphicProps } from "@kobalte/core/polymorphic"
+import type { VariantProps } from "class-variance-authority"
+import { cva } from "class-variance-authority"
+
+import { cn } from "../../lib/utils"
+
+const buttonVariants = cva(
+ "inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0",
+ {
+ variants: {
+ variant: {
+ default: "bg-primary text-primary-foreground hover:bg-primary/90",
+ destructive: "bg-destructive text-destructive-foreground hover:bg-destructive/90",
+ outline: "border border-input hover:bg-accent hover:text-accent-foreground",
+ secondary: "bg-secondary text-secondary-foreground hover:bg-secondary/80",
+ ghost: "hover:bg-accent hover:text-accent-foreground",
+ link: "text-primary underline-offset-4 hover:underline"
+ },
+ size: {
+ default: "h-10 px-4 py-2",
+ sm: "h-9 px-3 text-xs",
+ lg: "h-11 px-8",
+ icon: "size-10"
+ }
+ },
+ defaultVariants: {
+ variant: "default",
+ size: "default"
+ }
+ }
+)
+
+type ButtonProps = ButtonPrimitive.ButtonRootProps &
+ VariantProps & { class?: string | undefined; children?: JSX.Element }
+
+const Button = (
+ props: PolymorphicProps>
+) => {
+ const [local, others] = splitProps(props as ButtonProps, ["variant", "size", "class"])
+ return (
+
+ )
+}
+
+export { Button, buttonVariants }
+export type { ButtonProps }
+
diff --git a/web/src/components/ui/card.tsx b/web/src/components/ui/card.tsx
new file mode 100644
index 0000000..dd05c58
--- /dev/null
+++ b/web/src/components/ui/card.tsx
@@ -0,0 +1,44 @@
+import type { Component, ComponentProps } from "solid-js"
+import { splitProps } from "solid-js"
+
+import { cn } from "../../lib/utils"
+
+const Card: Component> = (props) => {
+ const [local, others] = splitProps(props, ["class"])
+ return (
+
+ )
+}
+
+const CardHeader: Component> = (props) => {
+ const [local, others] = splitProps(props, ["class"])
+ return
+}
+
+const CardTitle: Component> = (props) => {
+ const [local, others] = splitProps(props, ["class"])
+ return (
+
+ )
+}
+
+const CardDescription: Component> = (props) => {
+ const [local, others] = splitProps(props, ["class"])
+ return
+}
+
+const CardContent: Component> = (props) => {
+ const [local, others] = splitProps(props, ["class"])
+ return
+}
+
+const CardFooter: Component> = (props) => {
+ const [local, others] = splitProps(props, ["class"])
+ return
+}
+
+export { Card, CardHeader, CardFooter, CardTitle, CardDescription, CardContent }
+
diff --git a/web/src/components/ui/dialog.tsx b/web/src/components/ui/dialog.tsx
new file mode 100644
index 0000000..f0e1bf8
--- /dev/null
+++ b/web/src/components/ui/dialog.tsx
@@ -0,0 +1,142 @@
+import type { Component, ComponentProps, JSX, ValidComponent } from "solid-js"
+import { splitProps } from "solid-js"
+
+import * as DialogPrimitive from "@kobalte/core/dialog"
+import type { PolymorphicProps } from "@kobalte/core/polymorphic"
+
+import { cn } from "../../lib/utils"
+
+const Dialog = DialogPrimitive.Root
+const DialogTrigger = DialogPrimitive.Trigger
+
+const DialogPortal: Component = (props) => {
+ const [, rest] = splitProps(props, ["children"])
+ return (
+
+
+ {props.children}
+
+
+ )
+}
+
+type DialogOverlayProps =
+ DialogPrimitive.DialogOverlayProps & { class?: string | undefined }
+
+const DialogOverlay = (
+ props: PolymorphicProps>
+) => {
+ const [, rest] = splitProps(props as DialogOverlayProps, ["class"])
+ return (
+
+ )
+}
+
+type DialogContentProps =
+ DialogPrimitive.DialogContentProps & {
+ class?: string | undefined
+ children?: JSX.Element
+ }
+
+const DialogContent = (
+ props: PolymorphicProps>
+) => {
+ const [, rest] = splitProps(props as DialogContentProps, ["class", "children"])
+ return (
+
+
+
+ {props.children}
+
+
+ Close
+
+
+
+ )
+}
+
+const DialogHeader: Component> = (props) => {
+ const [, rest] = splitProps(props, ["class"])
+ return (
+
+ )
+}
+
+const DialogFooter: Component> = (props) => {
+ const [, rest] = splitProps(props, ["class"])
+ return (
+
+ )
+}
+
+type DialogTitleProps = DialogPrimitive.DialogTitleProps & {
+ class?: string | undefined
+}
+
+const DialogTitle = (
+ props: PolymorphicProps>
+) => {
+ const [, rest] = splitProps(props as DialogTitleProps, ["class"])
+ return (
+
+ )
+}
+
+type DialogDescriptionProps =
+ DialogPrimitive.DialogDescriptionProps & {
+ class?: string | undefined
+ }
+
+const DialogDescription = (
+ props: PolymorphicProps>
+) => {
+ const [, rest] = splitProps(props as DialogDescriptionProps, ["class"])
+ return (
+
+ )
+}
+
+export {
+ Dialog,
+ DialogTrigger,
+ DialogContent,
+ DialogHeader,
+ DialogFooter,
+ DialogTitle,
+ DialogDescription
+}
+
diff --git a/web/src/components/ui/label.tsx b/web/src/components/ui/label.tsx
new file mode 100644
index 0000000..0a1a23b
--- /dev/null
+++ b/web/src/components/ui/label.tsx
@@ -0,0 +1,20 @@
+import type { Component, ComponentProps } from "solid-js"
+import { splitProps } from "solid-js"
+
+import { cn } from "../../lib/utils"
+
+const Label: Component> = (props) => {
+ const [local, others] = splitProps(props, ["class"])
+ return (
+
+ )
+}
+
+export { Label }
+
diff --git a/web/src/components/ui/progress.tsx b/web/src/components/ui/progress.tsx
new file mode 100644
index 0000000..cb32e83
--- /dev/null
+++ b/web/src/components/ui/progress.tsx
@@ -0,0 +1,35 @@
+import type { Component, JSX, ValidComponent } from "solid-js"
+import { splitProps } from "solid-js"
+
+import type { PolymorphicProps } from "@kobalte/core/polymorphic"
+import * as ProgressPrimitive from "@kobalte/core/progress"
+
+import { Label } from "./label"
+
+type ProgressRootProps =
+ ProgressPrimitive.ProgressRootProps & { children?: JSX.Element }
+
+const Progress = (
+ props: PolymorphicProps>
+) => {
+ const [local, others] = splitProps(props as ProgressRootProps, ["children"])
+ return (
+
+ {local.children}
+
+
+
+
+ )
+}
+
+const ProgressLabel: Component = (props) => {
+ return
+}
+
+const ProgressValueLabel: Component = (props) => {
+ return
+}
+
+export { Progress, ProgressLabel, ProgressValueLabel }
+
diff --git a/web/src/components/ui/separator.tsx b/web/src/components/ui/separator.tsx
new file mode 100644
index 0000000..ddc4ab8
--- /dev/null
+++ b/web/src/components/ui/separator.tsx
@@ -0,0 +1,30 @@
+import type { ValidComponent } from "solid-js"
+import { splitProps } from "solid-js"
+
+import type { PolymorphicProps } from "@kobalte/core/polymorphic"
+import * as SeparatorPrimitive from "@kobalte/core/separator"
+
+import { cn } from "../../lib/utils"
+
+type SeparatorRootProps =
+ SeparatorPrimitive.SeparatorRootProps & { class?: string | undefined }
+
+const Separator = (
+ props: PolymorphicProps>
+) => {
+ const [local, others] = splitProps(props as SeparatorRootProps, ["class", "orientation"])
+ return (
+
+ )
+}
+
+export { Separator }
+
diff --git a/web/src/components/ui/skeleton.tsx b/web/src/components/ui/skeleton.tsx
new file mode 100644
index 0000000..a2a581a
--- /dev/null
+++ b/web/src/components/ui/skeleton.tsx
@@ -0,0 +1,25 @@
+import type { ValidComponent } from "solid-js"
+import { splitProps } from "solid-js"
+
+import type { PolymorphicProps } from "@kobalte/core/polymorphic"
+import * as SkeletonPrimitive from "@kobalte/core/skeleton"
+
+import { cn } from "../../lib/utils"
+
+type SkeletonRootProps =
+ SkeletonPrimitive.SkeletonRootProps & { class?: string | undefined }
+
+const Skeleton = (
+ props: PolymorphicProps>
+) => {
+ const [local, others] = splitProps(props as SkeletonRootProps, ["class"])
+ return (
+
+ )
+}
+
+export { Skeleton }
+
diff --git a/web/src/components/ui/switch.tsx b/web/src/components/ui/switch.tsx
new file mode 100644
index 0000000..bcfc14e
--- /dev/null
+++ b/web/src/components/ui/switch.tsx
@@ -0,0 +1,78 @@
+import type { JSX, ValidComponent } from "solid-js"
+import { splitProps } from "solid-js"
+
+import type { PolymorphicProps } from "@kobalte/core"
+import * as SwitchPrimitive from "@kobalte/core/switch"
+
+import { cn } from "../../lib/utils"
+
+const Switch = SwitchPrimitive.Root
+const SwitchDescription = SwitchPrimitive.Description
+const SwitchErrorMessage = SwitchPrimitive.ErrorMessage
+
+type SwitchControlProps = SwitchPrimitive.SwitchControlProps & {
+ class?: string | undefined
+ children?: JSX.Element
+}
+
+const SwitchControl = (
+ props: PolymorphicProps
+) => {
+ const [local, others] = splitProps(props as SwitchControlProps, ["class", "children"])
+ return (
+ <>
+
+
+ {local.children}
+
+ >
+ )
+}
+
+type SwitchThumbProps = SwitchPrimitive.SwitchThumbProps & { class?: string | undefined }
+
+const SwitchThumb = (
+ props: PolymorphicProps
+) => {
+ const [local, others] = splitProps(props as SwitchThumbProps, ["class"])
+ return (
+
+ )
+}
+
+type SwitchLabelProps = SwitchPrimitive.SwitchLabelProps & { class?: string | undefined }
+
+const SwitchLabel = (
+ props: PolymorphicProps
+) => {
+ const [local, others] = splitProps(props as SwitchLabelProps, ["class"])
+ return (
+
+ )
+}
+
+export { Switch, SwitchControl, SwitchThumb, SwitchLabel, SwitchDescription, SwitchErrorMessage }
+
diff --git a/web/src/components/ui/table.tsx b/web/src/components/ui/table.tsx
new file mode 100644
index 0000000..3218dc6
--- /dev/null
+++ b/web/src/components/ui/table.tsx
@@ -0,0 +1,71 @@
+import type { Component, ComponentProps } from "solid-js"
+import { splitProps } from "solid-js"
+
+import { cn } from "../../lib/utils"
+
+const Table: Component> = (props) => {
+ const [local, others] = splitProps(props, ["class"])
+ return (
+
+ )
+}
+
+const TableHeader: Component> = (props) => {
+ const [local, others] = splitProps(props, ["class"])
+ return
+}
+
+const TableBody: Component> = (props) => {
+ const [local, others] = splitProps(props, ["class"])
+ return
+}
+
+const TableFooter: Component> = (props) => {
+ const [local, others] = splitProps(props, ["class"])
+ return (
+
+ )
+}
+
+const TableRow: Component> = (props) => {
+ const [local, others] = splitProps(props, ["class"])
+ return (
+
+ )
+}
+
+const TableHead: Component> = (props) => {
+ const [local, others] = splitProps(props, ["class"])
+ return (
+ |
+ )
+}
+
+const TableCell: Component> = (props) => {
+ const [local, others] = splitProps(props, ["class"])
+ return (
+ |
+ )
+}
+
+const TableCaption: Component> = (props) => {
+ const [local, others] = splitProps(props, ["class"])
+ return
+}
+
+export { Table, TableHeader, TableBody, TableFooter, TableHead, TableRow, TableCell, TableCaption }
+
diff --git a/web/src/components/ui/tabs.tsx b/web/src/components/ui/tabs.tsx
new file mode 100644
index 0000000..b1d1b89
--- /dev/null
+++ b/web/src/components/ui/tabs.tsx
@@ -0,0 +1,88 @@
+import type { ValidComponent } from "solid-js"
+import { splitProps } from "solid-js"
+
+import type { PolymorphicProps } from "@kobalte/core/polymorphic"
+import * as TabsPrimitive from "@kobalte/core/tabs"
+
+import { cn } from "../../lib/utils"
+
+const Tabs = TabsPrimitive.Root
+
+type TabsListProps = TabsPrimitive.TabsListProps & {
+ class?: string | undefined
+}
+
+const TabsList = (
+ props: PolymorphicProps>
+) => {
+ const [local, others] = splitProps(props as TabsListProps, ["class"])
+ return (
+
+ )
+}
+
+type TabsTriggerProps = TabsPrimitive.TabsTriggerProps & {
+ class?: string | undefined
+}
+
+const TabsTrigger = (
+ props: PolymorphicProps>
+) => {
+ const [local, others] = splitProps(props as TabsTriggerProps, ["class"])
+ return (
+
+ )
+}
+
+type TabsContentProps = TabsPrimitive.TabsContentProps & {
+ class?: string | undefined
+}
+
+const TabsContent = (
+ props: PolymorphicProps>
+) => {
+ const [local, others] = splitProps(props as TabsContentProps, ["class"])
+ return (
+
+ )
+}
+
+type TabsIndicatorProps = TabsPrimitive.TabsIndicatorProps & {
+ class?: string | undefined
+}
+
+const TabsIndicator = (
+ props: PolymorphicProps>
+) => {
+ const [local, others] = splitProps(props as TabsIndicatorProps, ["class"])
+ return (
+
+ )
+}
+
+export { Tabs, TabsList, TabsTrigger, TabsContent, TabsIndicator }
+
diff --git a/web/src/components/ui/text-field.tsx b/web/src/components/ui/text-field.tsx
new file mode 100644
index 0000000..6d37b94
--- /dev/null
+++ b/web/src/components/ui/text-field.tsx
@@ -0,0 +1,153 @@
+import type { ValidComponent } from "solid-js"
+import { mergeProps, splitProps } from "solid-js"
+
+import type { PolymorphicProps } from "@kobalte/core"
+import * as TextFieldPrimitive from "@kobalte/core/text-field"
+import { cva } from "class-variance-authority"
+
+import { cn } from "../../lib/utils"
+
+type TextFieldRootProps =
+ TextFieldPrimitive.TextFieldRootProps & {
+ class?: string | undefined
+ }
+
+const TextField = (
+ props: PolymorphicProps>
+) => {
+ const [local, others] = splitProps(props as TextFieldRootProps, ["class"])
+ return
+}
+
+type TextFieldInputProps =
+ TextFieldPrimitive.TextFieldInputProps & {
+ class?: string | undefined
+ type?:
+ | "button"
+ | "checkbox"
+ | "color"
+ | "date"
+ | "datetime-local"
+ | "email"
+ | "file"
+ | "hidden"
+ | "image"
+ | "month"
+ | "number"
+ | "password"
+ | "radio"
+ | "range"
+ | "reset"
+ | "search"
+ | "submit"
+ | "tel"
+ | "text"
+ | "time"
+ | "url"
+ | "week"
+ }
+
+const TextFieldInput = (
+ rawProps: PolymorphicProps>
+) => {
+ const props = mergeProps[]>({ type: "text" }, rawProps)
+ const [local, others] = splitProps(props as TextFieldInputProps, ["type", "class"])
+ return (
+
+ )
+}
+
+type TextFieldTextAreaProps =
+ TextFieldPrimitive.TextFieldTextAreaProps & { class?: string | undefined }
+
+const TextFieldTextArea = (
+ props: PolymorphicProps>
+) => {
+ const [local, others] = splitProps(props as TextFieldTextAreaProps, ["class"])
+ return (
+
+ )
+}
+
+const labelVariants = cva(
+ "text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70",
+ {
+ variants: {
+ variant: {
+ label: "data-[invalid]:text-destructive",
+ description: "font-normal text-muted-foreground",
+ error: "text-xs text-destructive"
+ }
+ },
+ defaultVariants: {
+ variant: "label"
+ }
+ }
+)
+
+type TextFieldLabelProps =
+ TextFieldPrimitive.TextFieldLabelProps & { class?: string | undefined }
+
+const TextFieldLabel = (
+ props: PolymorphicProps>
+) => {
+ const [local, others] = splitProps(props as TextFieldLabelProps, ["class"])
+ return
+}
+
+type TextFieldDescriptionProps =
+ TextFieldPrimitive.TextFieldDescriptionProps & {
+ class?: string | undefined
+ }
+
+const TextFieldDescription = (
+ props: PolymorphicProps>
+) => {
+ const [local, others] = splitProps(props as TextFieldDescriptionProps, ["class"])
+ return (
+
+ )
+}
+
+type TextFieldErrorMessageProps =
+ TextFieldPrimitive.TextFieldErrorMessageProps & {
+ class?: string | undefined
+ }
+
+const TextFieldErrorMessage = (
+ props: PolymorphicProps>
+) => {
+ const [local, others] = splitProps(props as TextFieldErrorMessageProps, ["class"])
+ return (
+
+ )
+}
+
+export {
+ TextField,
+ TextFieldInput,
+ TextFieldTextArea,
+ TextFieldLabel,
+ TextFieldDescription,
+ TextFieldErrorMessage
+}
+
diff --git a/web/src/components/ui/tooltip.tsx b/web/src/components/ui/tooltip.tsx
new file mode 100644
index 0000000..1f65e60
--- /dev/null
+++ b/web/src/components/ui/tooltip.tsx
@@ -0,0 +1,36 @@
+import type { ValidComponent } from "solid-js"
+import { splitProps, type Component } from "solid-js"
+
+import type { PolymorphicProps } from "@kobalte/core/polymorphic"
+import * as TooltipPrimitive from "@kobalte/core/tooltip"
+
+import { cn } from "../../lib/utils"
+
+const TooltipTrigger = TooltipPrimitive.Trigger
+
+const Tooltip: Component = (props) => {
+ return
+}
+
+type TooltipContentProps =
+ TooltipPrimitive.TooltipContentProps & { class?: string | undefined }
+
+const TooltipContent = (
+ props: PolymorphicProps>
+) => {
+ const [local, others] = splitProps(props as TooltipContentProps, ["class"])
+ return (
+
+
+
+ )
+}
+
+export { Tooltip, TooltipTrigger, TooltipContent }
+
diff --git a/web/src/index.tsx b/web/src/index.tsx
index d0ef895..6bc9cc9 100644
--- a/web/src/index.tsx
+++ b/web/src/index.tsx
@@ -3,6 +3,9 @@ import { Router, Route, useLocation, useNavigate, useParams, type RouteSectionPr
import { createSignal, Show } from "solid-js";
import { WatchlistDrawer, ChatSidebar } from "./components/WatchlistChat";
import { ThemeToggle, useAuth, AuthButton } from "./components/auth";
+import { Button } from "./components/ui/button";
+import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "./components/ui/card";
+import { TextField, TextFieldInput } from "./components/ui/text-field";
import { api } from "./lib/api";
import Dashboard from "./pages/Dashboard";
import Routines from "./pages/Routines";
@@ -10,24 +13,47 @@ import Screener from "./pages/Screener";
import Alerts from "./pages/Alerts";
import Report from "./pages/Report";
import Portfolio from "./pages/Portfolio";
-import "./styles/tokens.css";
+import "./styles/globals.css";
+
+const NAV = [
+ { href: "/", icon: "📊", label: "Dashboard" },
+ { href: "/routines", icon: "🗓️", label: "Routines" },
+ { href: "/screener", icon: "🔍", label: "Screener" },
+ { href: "/alerts", icon: "🔔", label: "Alerts" },
+ { href: "/portfolio", icon: "💼", label: "Portfolio" },
+];
function Nav() {
const loc = useLocation();
- const link = (href: string, icon: string, label: string) => (
- {icon} {label}
- );
return (
-