diff --git a/apps/web/package.json b/apps/web/package.json
index af7c2a8..0a64450 100644
--- a/apps/web/package.json
+++ b/apps/web/package.json
@@ -12,33 +12,34 @@
"preview": "cd dist && nix-shell -p python3 --run 'python3 -m http.server 3000' && cd .."
},
"dependencies": {
- "@tensamin/call": "workspace:*",
- "@tensamin/chat": "workspace:*",
- "@tensamin/crypto": "workspace:*",
- "@tensamin/ttp": "workspace:*",
- "@tensamin/storage": "workspace:*",
- "@tensamin/user": "workspace:*",
- "@tensamin/shared": "workspace:*",
- "@tensamin/ui": "workspace:*",
+ "@fontsource-variable/inter": "^5.2.8",
"@noble/curves": "^2.0.1",
"@tailwindcss/vite": "^4.2.1",
"@tanstack/react-router": "^1.0.0",
"@tanstack/react-virtual": "^3.0.0",
+ "@tensamin/call": "workspace:*",
+ "@tensamin/chat": "workspace:*",
+ "@tensamin/crypto": "workspace:*",
+ "@tensamin/shared": "workspace:*",
+ "@tensamin/storage": "workspace:*",
+ "@tensamin/ttp": "workspace:*",
+ "@tensamin/ui": "workspace:*",
+ "@tensamin/user": "workspace:*",
"class-variance-authority": "^0.7.1",
"clsx": "^2.1.1",
"comlink": "^4.4.2",
+ "framer-motion": "^12.38.0",
"lucide-react": "^0.564.0",
"react": "^19.2.0",
"react-dom": "^19.2.0",
+ "shadcn": "^4.0.7",
"sonner": "^1.0.0",
"tailwind-merge": "^3.5.0",
"tailwind-scrollbar-hide": "^4.0.0",
"tailwindcss": "^4.2.1",
"tw-animate-css": "^1.4.0",
"vite-tsconfig-paths": "^6.1.1",
- "zod": "^4.3.6",
- "@fontsource-variable/inter": "^5.2.8",
- "shadcn": "^4.0.7"
+ "zod": "^4.3.6"
},
"devDependencies": {
"@types/react": "^19.2.2",
diff --git a/apps/web/src/components/navbar.tsx b/apps/web/src/components/navbar.tsx
index 5b7b23c..8b646e8 100644
--- a/apps/web/src/components/navbar.tsx
+++ b/apps/web/src/components/navbar.tsx
@@ -21,7 +21,7 @@ export default function Navbar() {
const { pathname } = useLocation();
const { id } = useSearch({ strict: false });
- const currentCalls = id ? conversations[id]?.calls || [] : [];
+ const currentCalls = id ? conversations?.[id]?.calls || [] : [];
return (
diff --git a/apps/web/src/features/conversation/context.tsx b/apps/web/src/features/conversation/context.tsx
index 66b75ee..3968a67 100644
--- a/apps/web/src/features/conversation/context.tsx
+++ b/apps/web/src/features/conversation/context.tsx
@@ -8,8 +8,8 @@ import type {
} from "@tensamin/shared/features/conversation/schema";
interface contextValue {
- conversations: Conversation[];
- communities: Community[];
+ conversations: Conversation[] | null;
+ communities: Community[] | null;
}
const ConversationContext = React.createContext
(
@@ -24,8 +24,12 @@ const ConversationContext = React.createContext(
export default function ConversationProvider(props: {
children: React.ReactNode;
}) {
- const [conversations, setConversations] = React.useState([]);
- const [communities, setCommunities] = React.useState([]);
+ const [conversations, setConversations] = React.useState<
+ Conversation[] | null
+ >(null);
+ const [communities, setCommunities] = React.useState(
+ null,
+ );
const { send } = useSocket();
@@ -39,6 +43,12 @@ export default function ConversationProvider(props: {
}
})
.catch(() => {
+ setConversations([
+ {
+ last_message_at: 0,
+ user_id: 0,
+ },
+ ]);
toast("error", "Failed to load conversations");
});
@@ -49,6 +59,13 @@ export default function ConversationProvider(props: {
}
})
.catch(() => {
+ setCommunities([
+ {
+ community_address: "none",
+ community_title: "Failed",
+ position: "",
+ },
+ ]);
toast("error", "Failed to load communities");
});
@@ -57,13 +74,13 @@ export default function ConversationProvider(props: {
};
}, [send]);
- const value = React.useMemo(
- () => ({ conversations, communities }),
- [communities, conversations],
- );
-
return (
-
+
{props.children}
);
diff --git a/apps/web/src/features/conversation/list/body.tsx b/apps/web/src/features/conversation/list/body.tsx
index 53ce98a..1084c52 100644
--- a/apps/web/src/features/conversation/list/body.tsx
+++ b/apps/web/src/features/conversation/list/body.tsx
@@ -5,6 +5,7 @@ import { useConversation } from "../context";
import Switch from "./switch";
import ConversationModal from "../modal/conversation";
import CommunityModal from "../modal/community";
+import { Loader2 } from "lucide-react";
/**
* Executes List.
@@ -22,7 +23,9 @@ export default function List() {
const virtualizer = useVirtualizer({
count:
- category === "conversations" ? conversations.length : communities.length,
+ category === "conversations"
+ ? conversations?.length || 0
+ : communities?.length || 0,
estimateSize: () => 60,
getScrollElement: () => scrollRef.current,
});
@@ -41,32 +44,45 @@ export default function List() {
height: `${virtualizer.getTotalSize()}px`,
}}
>
- {virtualizer.getVirtualItems().map((virtualItem) => {
- const itemKey = `${category}-${virtualItem.index}`;
+ {communities === null || conversations === null ? (
+
+ ) : null}
- return (
-
- {category === "conversations" ? (
- conversations[virtualItem.index] ? (
-
{
+ const itemKey = `${category}-${virtualItem.index}`;
+
+ return (
+
+ {category === "conversations" ? (
+ conversations[virtualItem.index] ? (
+
+ ) : null
+ ) : communities[virtualItem.index] ? (
+
- ) : null
- ) : communities[virtualItem.index] ? (
-
- ) : null}
-
- );
- })}
+ ) : null}
+
+ );
+ })}
diff --git a/apps/web/src/index.css b/apps/web/src/index.css
index 30f037c..406e9e1 100644
--- a/apps/web/src/index.css
+++ b/apps/web/src/index.css
@@ -79,7 +79,7 @@
@apply border-border outline-ring/50;
}
body {
- @apply bg-background text-foreground;
+ @apply bg-black text-foreground;
}
html {
@apply font-sans;
diff --git a/apps/web/src/routes/app/layout.tsx b/apps/web/src/routes/app/layout.tsx
index 470568c..4251d75 100644
--- a/apps/web/src/routes/app/layout.tsx
+++ b/apps/web/src/routes/app/layout.tsx
@@ -5,6 +5,8 @@ import Sidebar from "@/components/sidebar";
import Navbar from "@/components/navbar";
import { useStorage } from "@tensamin/storage/context";
+import { motion } from "framer-motion";
+
/**
* Executes Layout.
* @param props Parameter props.
@@ -46,7 +48,20 @@ export default function Layout(props: { children: ReactNode }) {
}
return (
-
+
@@ -54,6 +69,11 @@ export default function Layout(props: { children: ReactNode }) {
{props.children}
-
+
);
}
+
+const animationTiming = {
+ first: 0.18,
+ second: 0.1,
+};
diff --git a/bun.lock b/bun.lock
index 91dea86..3abbc6e 100644
--- a/bun.lock
+++ b/bun.lock
@@ -49,6 +49,7 @@
"class-variance-authority": "^0.7.1",
"clsx": "^2.1.1",
"comlink": "^4.4.2",
+ "framer-motion": "^12.38.0",
"lucide-react": "^0.564.0",
"react": "^19.2.0",
"react-dom": "^19.2.0",
@@ -911,6 +912,8 @@
"forwarded": ["forwarded@0.2.0", "", {}, "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow=="],
+ "framer-motion": ["framer-motion@12.38.0", "", { "dependencies": { "motion-dom": "^12.38.0", "motion-utils": "^12.36.0", "tslib": "^2.4.0" }, "peerDependencies": { "@emotion/is-prop-valid": "*", "react": "^18.0.0 || ^19.0.0", "react-dom": "^18.0.0 || ^19.0.0" }, "optionalPeers": ["@emotion/is-prop-valid", "react", "react-dom"] }, "sha512-rFYkY/pigbcswl1XQSb7q424kSTQ8q6eAC+YUsSKooHQYuLdzdHjrt6uxUC+PRAO++q5IS7+TamgIw1AphxR+g=="],
+
"fresh": ["fresh@2.0.0", "", {}, "sha512-Rx/WycZ60HOaqLKAi6cHRKKI7zxWbJ31MhntmtwMoaTeF7XFH9hhBp8vITaMidfljRQ6eYWCKkaTK+ykVJHP2A=="],
"fs-extra": ["fs-extra@11.3.4", "", { "dependencies": { "graceful-fs": "^4.2.0", "jsonfile": "^6.0.1", "universalify": "^2.0.0" } }, "sha512-CTXd6rk/M3/ULNQj8FBqBWHYBVYybQ3VPBw0xGKFe3tuH7ytT6ACnvzpIQ3UZtB8yvUKC2cXn1a+x+5EVQLovA=="],
@@ -1107,6 +1110,10 @@
"minimist": ["minimist@1.2.8", "", {}, "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA=="],
+ "motion-dom": ["motion-dom@12.38.0", "", { "dependencies": { "motion-utils": "^12.36.0" } }, "sha512-pdkHLD8QYRp8VfiNLb8xIBJis1byQ9gPT3Jnh2jqfFtAsWUA3dEepDlsWe/xMpO8McV+VdpKVcp+E+TGJEtOoA=="],
+
+ "motion-utils": ["motion-utils@12.36.0", "", {}, "sha512-eHWisygbiwVvf6PZ1vhaHCLamvkSbPIeAYxWUuL3a2PD/TROgE7FvfHWTIH4vMl798QLfMw15nRqIaRDXTlYRg=="],
+
"ms": ["ms@2.1.3", "", {}, "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="],
"msw": ["msw@2.12.10", "", { "dependencies": { "@inquirer/confirm": "^5.0.0", "@mswjs/interceptors": "^0.41.2", "@open-draft/deferred-promise": "^2.2.0", "@types/statuses": "^2.0.6", "cookie": "^1.0.2", "graphql": "^16.12.0", "headers-polyfill": "^4.0.2", "is-node-process": "^1.2.0", "outvariant": "^1.4.3", "path-to-regexp": "^6.3.0", "picocolors": "^1.1.1", "rettime": "^0.10.1", "statuses": "^2.0.2", "strict-event-emitter": "^0.5.1", "tough-cookie": "^6.0.0", "type-fest": "^5.2.0", "until-async": "^3.0.2", "yargs": "^17.7.2" }, "peerDependencies": { "typescript": ">= 4.8.x" }, "optionalPeers": ["typescript"], "bin": { "msw": "cli/index.js" } }, "sha512-G3VUymSE0/iegFnuipujpwyTM2GuZAKXNeerUSrG2+Eg391wW63xFs5ixWsK9MWzr1AGoSkYGmyAzNgbR3+urw=="],
diff --git a/packages/chat/src/screen.tsx b/packages/chat/src/screen.tsx
index 76fd5d7..6e9a18c 100644
--- a/packages/chat/src/screen.tsx
+++ b/packages/chat/src/screen.tsx
@@ -6,7 +6,7 @@ import { useChat } from "./context";
import InputComponent from "./components/input";
import Message from "./components/message";
-const PAGE_SIZE = 50;
+import { PAGE_SIZE } from "./values";
/**
* Renders the chat screen with virtualized history and live message updates.
@@ -179,8 +179,8 @@ export default function Screen() {
if (!hasValidChatUser) {
return (
-
- Select a conversation to start chatting.
+
+ Invalid user
);
}
diff --git a/packages/chat/src/values.ts b/packages/chat/src/values.ts
index 692737b..9bfc328 100644
--- a/packages/chat/src/values.ts
+++ b/packages/chat/src/values.ts
@@ -11,3 +11,5 @@ export type LiveMessage = RawMessage & {
failed?: boolean;
localId: string;
};
+
+export const PAGE_SIZE = 25;
diff --git a/packages/shared/src/data.ts b/packages/shared/src/data.ts
index 9634269..e3b4d49 100644
--- a/packages/shared/src/data.ts
+++ b/packages/shared/src/data.ts
@@ -22,7 +22,7 @@ const message = z.object({
});
export const failedUser = {
- display: "Failed to load user",
+ display: "Failed",
iota_id: 0,
omikron_connections: [],
online_status: "user_borked",