diff --git a/apps/frontend/package.json b/apps/frontend/package.json
index b9446c7..eb048f5 100644
--- a/apps/frontend/package.json
+++ b/apps/frontend/package.json
@@ -22,6 +22,7 @@
"@eslint/compat": "^1.2.5",
"@eslint/js": "^9.18.0",
"@inlang/paraglide-js": "^2.2.0",
+ "@internationalized/date": "^3.10.0",
"@langchain/core": "^0.3.78",
"@langchain/langgraph-sdk": "^0.1.9",
"@lucide/svelte": "^0.554.0",
@@ -35,6 +36,7 @@
"@testing-library/jest-dom": "^6.6.3",
"@testing-library/svelte": "^5.2.4",
"@vitest/coverage-v8": "^3.2.4",
+ "bits-ui": "^2.14.4",
"clsx": "^2.1.1",
"eslint": "^9.18.0",
"eslint-config-prettier": "^10.0.1",
diff --git a/apps/frontend/src/lib/components/ui/avatar/avatar-fallback.svelte b/apps/frontend/src/lib/components/ui/avatar/avatar-fallback.svelte
new file mode 100644
index 0000000..b911baf
--- /dev/null
+++ b/apps/frontend/src/lib/components/ui/avatar/avatar-fallback.svelte
@@ -0,0 +1,17 @@
+
+
+
diff --git a/apps/frontend/src/lib/components/ui/avatar/avatar-image.svelte b/apps/frontend/src/lib/components/ui/avatar/avatar-image.svelte
new file mode 100644
index 0000000..7ccc3ce
--- /dev/null
+++ b/apps/frontend/src/lib/components/ui/avatar/avatar-image.svelte
@@ -0,0 +1,17 @@
+
+
+
diff --git a/apps/frontend/src/lib/components/ui/avatar/avatar.svelte b/apps/frontend/src/lib/components/ui/avatar/avatar.svelte
new file mode 100644
index 0000000..3fd4dc2
--- /dev/null
+++ b/apps/frontend/src/lib/components/ui/avatar/avatar.svelte
@@ -0,0 +1,19 @@
+
+
+
diff --git a/apps/frontend/src/lib/components/ui/avatar/index.ts b/apps/frontend/src/lib/components/ui/avatar/index.ts
new file mode 100644
index 0000000..9585f8a
--- /dev/null
+++ b/apps/frontend/src/lib/components/ui/avatar/index.ts
@@ -0,0 +1,13 @@
+import Root from './avatar.svelte';
+import Image from './avatar-image.svelte';
+import Fallback from './avatar-fallback.svelte';
+
+export {
+ Root,
+ Image,
+ Fallback,
+ //
+ Root as Avatar,
+ Image as AvatarImage,
+ Fallback as AvatarFallback
+};
diff --git a/apps/frontend/src/routes/+layout.svelte b/apps/frontend/src/routes/+layout.svelte
index 240b7f4..b1201ab 100644
--- a/apps/frontend/src/routes/+layout.svelte
+++ b/apps/frontend/src/routes/+layout.svelte
@@ -20,15 +20,38 @@
NavHamburger,
Dropdown,
DropdownHeader,
- DropdownDivider,
- Avatar
+ DropdownDivider
} from 'flowbite-svelte';
-
+ import { Avatar, AvatarImage, AvatarFallback } from '$lib/components/ui/avatar';
import { ModeWatcher, toggleMode, mode } from 'mode-watcher';
import LanguageSwitcher from '$lib/components/LanguageSwitcher.svelte';
import SignInButton from '$lib/auth/components/SignInButton.svelte';
+ type SessionUser = {
+ name?: string | null;
+ email?: string | null;
+ };
+
+ function getInitials(name?: string | null) {
+ if (!name) return 'U';
+ const parts = name.trim().split(/\s+/).filter(Boolean);
+ if (parts.length === 0) return 'U';
+ return parts
+ .slice(0, 2)
+ .map((p) => p[0]!.toUpperCase())
+ .join('');
+ }
+
+ function getDisplayName(user?: SessionUser | null) {
+ if (user?.name && user.name.trim().length > 0) return user.name;
+ if (user?.email) {
+ const localPart = user.email.split('@')[0] ?? user.email;
+ return localPart || user.email;
+ }
+ return m.user_fallback();
+ }
+
let { children } = $props();
@@ -43,20 +66,21 @@
{#if page.data.session}
-
-
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 565be5e..6ad1d7c 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -32,6 +32,9 @@ importers:
'@inlang/paraglide-js':
specifier: ^2.2.0
version: 2.2.0
+ '@internationalized/date':
+ specifier: ^3.10.0
+ version: 3.10.0
'@langchain/core':
specifier: ^0.3.78
version: 0.3.78(@opentelemetry/api@1.9.0)(@opentelemetry/sdk-trace-base@2.0.1(@opentelemetry/api@1.9.0))
@@ -71,6 +74,9 @@ importers:
'@vitest/coverage-v8':
specifier: ^3.2.4
version: 3.2.4(vitest@3.2.4(@types/debug@4.1.12)(@types/node@24.1.0)(jiti@2.5.0)(jsdom@26.1.0)(lightningcss@1.30.1))
+ bits-ui:
+ specifier: ^2.14.4
+ version: 2.14.4(@internationalized/date@3.10.0)(@sveltejs/kit@2.25.2(@sveltejs/vite-plugin-svelte@5.1.1(svelte@5.36.14)(vite@6.3.5(@types/node@24.1.0)(jiti@2.5.0)(lightningcss@1.30.1)))(svelte@5.36.14)(vite@6.3.5(@types/node@24.1.0)(jiti@2.5.0)(lightningcss@1.30.1)))(svelte@5.36.14)
clsx:
specifier: ^2.1.1
version: 2.1.1
@@ -554,6 +560,9 @@ packages:
resolution: {integrity: sha512-cvz/C1rF5WBxzHbEoiBoI6Sz6q6M+TdxfWkEGBYTD77opY8i8WN01prUWXEM87GPF4SZcyIySez9U0Ccm12oFQ==}
engines: {node: '>=18.0.0'}
+ '@internationalized/date@3.10.0':
+ resolution: {integrity: sha512-oxDR/NTEJ1k+UFVQElaNIk65E/Z83HK1z1WI3lQyhTtnNg4R5oVXaPzK3jcpKG8UHKDVuDQHzn+wsxSz8RP3aw==}
+
'@isaacs/cliui@8.0.2':
resolution: {integrity: sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==}
engines: {node: '>=12'}
@@ -1188,6 +1197,9 @@ packages:
peerDependencies:
'@svgdotjs/svg.js': ^3.2.4
+ '@swc/helpers@0.5.17':
+ resolution: {integrity: sha512-5IKx/Y13RsYd+sauPb2x+U/xZikHjolzfuDgTAl/Tdf3Q8rslRvC19NKDLgAJQ6wsqADk10ntlv08nPFw/gO/A==}
+
'@tailwindcss/forms@0.5.10':
resolution: {integrity: sha512-utI1ONF6uf/pPNO68kmN1b8rEwNXv3czukalo8VtJH8ksIkZXr3Q3VYudZLkCsDd4Wku120uF02hYK25XGPorw==}
peerDependencies:
@@ -1569,6 +1581,13 @@ packages:
resolution: {integrity: sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==}
engines: {node: '>=8'}
+ bits-ui@2.14.4:
+ resolution: {integrity: sha512-W6kenhnbd/YVvur+DKkaVJ6GldE53eLewur5AhUCqslYQ0vjZr8eWlOfwZnMiPB+PF5HMVqf61vXBvmyrAmPWg==}
+ engines: {node: '>=20'}
+ peerDependencies:
+ '@internationalized/date': ^3.8.1
+ svelte: ^5.33.0
+
brace-expansion@1.1.12:
resolution: {integrity: sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==}
@@ -2905,6 +2924,15 @@ packages:
peerDependencies:
svelte: ^5.7.0
+ runed@0.35.1:
+ resolution: {integrity: sha512-2F4Q/FZzbeJTFdIS/PuOoPRSm92sA2LhzTnv6FXhCoENb3huf5+fDuNOg1LNvGOouy3u/225qxmuJvcV3IZK5Q==}
+ peerDependencies:
+ '@sveltejs/kit': ^2.21.0
+ svelte: ^5.7.0
+ peerDependenciesMeta:
+ '@sveltejs/kit':
+ optional: true
+
sade@1.8.1:
resolution: {integrity: sha512-xal3CZX1Xlo/k4ApwCFrHVACi9fBqJ7V+mwhBsuf/1IOKbBy098Fex+Wa/5QMubw09pSZ/u8EY8PWgevJsXp1A==}
engines: {node: '>=6'}
@@ -3036,6 +3064,12 @@ packages:
peerDependencies:
svelte: ^5.1.3
+ svelte-toolbelt@0.10.6:
+ resolution: {integrity: sha512-YWuX+RE+CnWYx09yseAe4ZVMM7e7GRFZM6OYWpBKOb++s+SQ8RBIMMe+Bs/CznBMc0QPLjr+vDBxTAkozXsFXQ==}
+ engines: {node: '>=18', pnpm: '>=8.7.0'}
+ peerDependencies:
+ svelte: ^5.30.2
+
svelte-toolbelt@0.7.1:
resolution: {integrity: sha512-HcBOcR17Vx9bjaOceUvxkY3nGmbBmCBBbuWLLEWO6jtmWH8f/QoWmbyUfQZrpDINH39en1b8mptfPQT9VKQ1xQ==}
engines: {node: '>=18', pnpm: '>=8.7.0'}
@@ -3049,6 +3083,9 @@ packages:
symbol-tree@3.2.4:
resolution: {integrity: sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==}
+ tabbable@6.3.0:
+ resolution: {integrity: sha512-EIHvdY5bPLuWForiR/AN2Bxngzpuwn1is4asboytXtpTgsArc+WmSJKVLlhdh71u7jFcryDqB2A8lQvj78MkyQ==}
+
tailwind-merge@3.3.1:
resolution: {integrity: sha512-gBXpgUm/3rp1lMZZrM/w7D8GKqshif0zAymAhbCyIt8KMe+0v9DQ7cdYLR4FHH/cKpdTXb+A/tKKU3eolfsI+g==}
@@ -3759,6 +3796,10 @@ snapshots:
transitivePeerDependencies:
- babel-plugin-macros
+ '@internationalized/date@3.10.0':
+ dependencies:
+ '@swc/helpers': 0.5.17
+
'@isaacs/cliui@8.0.2':
dependencies:
string-width: 5.1.2
@@ -4490,6 +4531,10 @@ snapshots:
dependencies:
'@svgdotjs/svg.js': 3.2.5
+ '@swc/helpers@0.5.17':
+ dependencies:
+ tslib: 2.8.1
+
'@tailwindcss/forms@0.5.10(tailwindcss@4.1.11)':
dependencies:
mini-svg-data-uri: 1.4.4
@@ -4908,6 +4953,19 @@ snapshots:
binary-extensions@2.3.0: {}
+ bits-ui@2.14.4(@internationalized/date@3.10.0)(@sveltejs/kit@2.25.2(@sveltejs/vite-plugin-svelte@5.1.1(svelte@5.36.14)(vite@6.3.5(@types/node@24.1.0)(jiti@2.5.0)(lightningcss@1.30.1)))(svelte@5.36.14)(vite@6.3.5(@types/node@24.1.0)(jiti@2.5.0)(lightningcss@1.30.1)))(svelte@5.36.14):
+ dependencies:
+ '@floating-ui/core': 1.7.3
+ '@floating-ui/dom': 1.7.4
+ '@internationalized/date': 3.10.0
+ esm-env: 1.2.2
+ runed: 0.35.1(@sveltejs/kit@2.25.2(@sveltejs/vite-plugin-svelte@5.1.1(svelte@5.36.14)(vite@6.3.5(@types/node@24.1.0)(jiti@2.5.0)(lightningcss@1.30.1)))(svelte@5.36.14)(vite@6.3.5(@types/node@24.1.0)(jiti@2.5.0)(lightningcss@1.30.1)))(svelte@5.36.14)
+ svelte: 5.36.14
+ svelte-toolbelt: 0.10.6(@sveltejs/kit@2.25.2(@sveltejs/vite-plugin-svelte@5.1.1(svelte@5.36.14)(vite@6.3.5(@types/node@24.1.0)(jiti@2.5.0)(lightningcss@1.30.1)))(svelte@5.36.14)(vite@6.3.5(@types/node@24.1.0)(jiti@2.5.0)(lightningcss@1.30.1)))(svelte@5.36.14)
+ tabbable: 6.3.0
+ transitivePeerDependencies:
+ - '@sveltejs/kit'
+
brace-expansion@1.1.12:
dependencies:
balanced-match: 1.0.2
@@ -6343,6 +6401,15 @@ snapshots:
esm-env: 1.2.2
svelte: 5.36.14
+ runed@0.35.1(@sveltejs/kit@2.25.2(@sveltejs/vite-plugin-svelte@5.1.1(svelte@5.36.14)(vite@6.3.5(@types/node@24.1.0)(jiti@2.5.0)(lightningcss@1.30.1)))(svelte@5.36.14)(vite@6.3.5(@types/node@24.1.0)(jiti@2.5.0)(lightningcss@1.30.1)))(svelte@5.36.14):
+ dependencies:
+ dequal: 2.0.3
+ esm-env: 1.2.2
+ lz-string: 1.5.0
+ svelte: 5.36.14
+ optionalDependencies:
+ '@sveltejs/kit': 2.25.2(@sveltejs/vite-plugin-svelte@5.1.1(svelte@5.36.14)(vite@6.3.5(@types/node@24.1.0)(jiti@2.5.0)(lightningcss@1.30.1)))(svelte@5.36.14)(vite@6.3.5(@types/node@24.1.0)(jiti@2.5.0)(lightningcss@1.30.1))
+
sade@1.8.1:
dependencies:
mri: 1.2.0
@@ -6471,6 +6538,15 @@ snapshots:
transitivePeerDependencies:
- supports-color
+ svelte-toolbelt@0.10.6(@sveltejs/kit@2.25.2(@sveltejs/vite-plugin-svelte@5.1.1(svelte@5.36.14)(vite@6.3.5(@types/node@24.1.0)(jiti@2.5.0)(lightningcss@1.30.1)))(svelte@5.36.14)(vite@6.3.5(@types/node@24.1.0)(jiti@2.5.0)(lightningcss@1.30.1)))(svelte@5.36.14):
+ dependencies:
+ clsx: 2.1.1
+ runed: 0.35.1(@sveltejs/kit@2.25.2(@sveltejs/vite-plugin-svelte@5.1.1(svelte@5.36.14)(vite@6.3.5(@types/node@24.1.0)(jiti@2.5.0)(lightningcss@1.30.1)))(svelte@5.36.14)(vite@6.3.5(@types/node@24.1.0)(jiti@2.5.0)(lightningcss@1.30.1)))(svelte@5.36.14)
+ style-to-object: 1.0.12
+ svelte: 5.36.14
+ transitivePeerDependencies:
+ - '@sveltejs/kit'
+
svelte-toolbelt@0.7.1(svelte@5.36.14):
dependencies:
clsx: 2.1.1
@@ -6497,6 +6573,8 @@ snapshots:
symbol-tree@3.2.4: {}
+ tabbable@6.3.0: {}
+
tailwind-merge@3.3.1: {}
tailwind-variants@3.1.1(tailwind-merge@3.3.1)(tailwindcss@4.1.11):