Privacy Policy & ToS
diff --git a/apps/web/src/features/settings/components.tsx b/apps/web/src/features/settings/components.tsx
new file mode 100644
index 0000000..351bae6
--- /dev/null
+++ b/apps/web/src/features/settings/components.tsx
@@ -0,0 +1,35 @@
+import { Label } from "@tensamin/ui/cmp/label";
+import { Switch as UISwitch } from "@tensamin/ui/cmp/switch";
+import { useEffect, useState } from "react";
+
+import { settingsStorageDefaults } from "@tensamin/shared/settings";
+import { useStorage } from "@tensamin/storage/context";
+
+export function Switch({
+ label,
+ id,
+}: {
+ label: string;
+ id: keyof typeof settingsStorageDefaults;
+}) {
+ const { save, load } = useStorage();
+ const [value, setValue] = useState(settingsStorageDefaults[id]);
+
+ useEffect(() => {
+ load(id).then((value) => setValue(value));
+ }, [id, load]);
+
+ return (
+
+ {
+ setValue(value);
+ save(id, value);
+ }}
+ />
+ {label}
+
+ );
+}
diff --git a/apps/web/src/features/settings/layout.tsx b/apps/web/src/features/settings/layout.tsx
new file mode 100644
index 0000000..fb84f4d
--- /dev/null
+++ b/apps/web/src/features/settings/layout.tsx
@@ -0,0 +1,64 @@
+import { Button } from "@tensamin/ui/cmp/button";
+
+import options from "@tensamin/shared/settings";
+import { cn, useIsMobile } from "@tensamin/ui/utils";
+import { Outlet, useNavigate } from "@tanstack/react-router";
+
+export default function Screen() {
+ const isMobile = useIsMobile();
+
+ return (
+
+ {!isMobile &&
}
+
+ {/* Page */}
+
+
+
+
+ );
+}
+
+export function SettingsSidebar() {
+ const settingsOptions = options as Record<
+ string,
+ Record>
+ >;
+
+ const isMobile = useIsMobile();
+ const navigate = useNavigate();
+
+ return (
+
+ {/* Settings */}
+ {Object.keys(settingsOptions).map((category) => (
+ // Category
+
+
{category}
+ {Object.keys(settingsOptions[category]).map((page) => (
+ // Page
+
+
+ navigate({
+ to: "/settings/" + page.toLowerCase(),
+ })
+ }
+ >
+ {(page as string).charAt(0).toUpperCase() +
+ (page as string).slice(1)}
+
+
+ ))}
+
+ ))}
+
+ );
+}
diff --git a/apps/web/src/index.tsx b/apps/web/src/index.tsx
index be49329..939c676 100644
--- a/apps/web/src/index.tsx
+++ b/apps/web/src/index.tsx
@@ -10,8 +10,9 @@ import "./index.css";
import NotFound from "@/routes/404";
-import RootLayout from "./routes/layout";
-import AppLayout from "./routes/app/layout";
+import RootLayout from "@/routes/layout";
+import AppLayout from "@/routes/app/layout";
+import SettingsLayout from "@/features/settings/layout";
import Home from "@/routes/app/home";
import ChatScreen from "@tensamin/chat/screen";
@@ -22,6 +23,7 @@ import ChatContext from "@tensamin/chat/context";
import CallContext from "@tensamin/call/context";
import SocketContext from "@tensamin/ttp/context";
import UserContext from "@tensamin/user/context";
+import DeeplinkContext from "@tensamin/tauri/deeplinkHandler";
import { ThemeProvider } from "@tensamin/ui/theme";
import z from "zod";
@@ -47,13 +49,15 @@ window.setLogLevelToMax = () => {
function RootShell() {
return (
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
);
}
@@ -89,10 +93,55 @@ const appRoute = createRoute({
component: AppShell,
});
+const settingsRoute = createRoute({
+ getParentRoute: () => appRoute,
+ path: "settings",
+ component: SettingsLayout,
+ staticData: {
+ showMobileNavbar: true,
+ },
+});
+
+type SettingsRouteModule = {
+ default?: () => React.JSX.Element;
+ component?: () => React.JSX.Element;
+};
+
+const settingsRouteModules = import.meta.glob(
+ "./routes/settings/*.tsx",
+ { eager: true },
+);
+
+const settingsChildren = Object.entries(settingsRouteModules).map(
+ ([filePath, module]) => {
+ const fileName = filePath.split("/").pop()?.replace(".tsx", "") ?? "";
+ const path = fileName === "index" ? "/" : fileName;
+ const component = module.component ?? module.default;
+
+ if (!component) {
+ throw new Error(
+ `Settings route module "${filePath}" must export a default component`,
+ );
+ }
+
+ return createRoute({
+ getParentRoute: () => settingsRoute,
+ path,
+ component,
+ staticData: {
+ showMobileNavbar: true,
+ },
+ });
+ },
+);
+
const homeRoute = createRoute({
getParentRoute: () => appRoute,
path: "/",
component: Home,
+ staticData: {
+ showMobileNavbar: true,
+ },
});
const chatRoute = createRoute({
@@ -102,22 +151,36 @@ const chatRoute = createRoute({
validateSearch: z.object({
id: z.number().optional(),
}),
+ staticData: {
+ showMobileNavbar: false,
+ },
});
const callRoute = createRoute({
getParentRoute: () => appRoute,
path: "call",
component: CallScreen,
+ staticData: {
+ showMobileNavbar: false,
+ },
});
const loginRoute = createRoute({
getParentRoute: () => rootRoute,
path: "login",
component: Login,
+ staticData: {
+ showMobileNavbar: false,
+ },
});
const routeTree = rootRoute.addChildren([
- appRoute.addChildren([homeRoute, chatRoute, callRoute]),
+ appRoute.addChildren([
+ homeRoute,
+ chatRoute,
+ callRoute,
+ settingsRoute.addChildren(settingsChildren),
+ ]),
loginRoute,
]);
diff --git a/apps/web/src/routes/app/layout.tsx b/apps/web/src/routes/app/layout.tsx
index fad88d8..0f6a143 100644
--- a/apps/web/src/routes/app/layout.tsx
+++ b/apps/web/src/routes/app/layout.tsx
@@ -7,31 +7,31 @@ import { SidebarProvider } from "@tensamin/ui/cmp/sidebar";
import { useIsMobile, cn } from "@tensamin/ui/utils";
import { isTauri } from "@tauri-apps/api/core";
-import { useLocation } from "@tanstack/react-router";
+import { useLocation, useMatches } from "@tanstack/react-router";
/**
* Executes Layout.
* @param props Parameter props.
* @returns unknown.
*/
-export default function Layout(props: { children: ReactNode }) {
+export default function Layout({ children }: { children: ReactNode }) {
const isMobile = useIsMobile();
- const location = useLocation();
+ const showMobileNavbar = useShowMobileNavbar();
return (
{!isMobile &&
}
- {isMobile && location.pathname === "/chat" && (
-
- )}
+ {isMobile && !showMobileNavbar &&
}
- {props.children}
+ {children}
- {isMobile && location.pathname !== "/chat" &&
}
+
+ {isMobile && showMobileNavbar &&
}
);
}
+
+export function useShowMobileNavbar(): boolean {
+ const matches = useMatches();
+ const location = useLocation();
+
+ const value = matches.some(
+ (match) =>
+ match.pathname === location.pathname &&
+ // @ts-expect-error Stuff
+ match.staticData?.showMobileNavbar === true,
+ );
+
+ return value;
+}
diff --git a/apps/web/src/routes/settings/chat.tsx b/apps/web/src/routes/settings/chat.tsx
new file mode 100644
index 0000000..3dafa45
--- /dev/null
+++ b/apps/web/src/routes/settings/chat.tsx
@@ -0,0 +1,12 @@
+import { Switch } from "@/features/settings/components";
+
+export default function Page() {
+ return (
+
+
+
+ );
+}
diff --git a/apps/web/src/routes/settings/index.tsx b/apps/web/src/routes/settings/index.tsx
new file mode 100644
index 0000000..3ea9a55
--- /dev/null
+++ b/apps/web/src/routes/settings/index.tsx
@@ -0,0 +1,7 @@
+import { SettingsSidebar } from "@/features/settings/layout";
+import { useIsMobile } from "@tensamin/ui/utils";
+
+export default function Page() {
+ const isMobile = useIsMobile();
+ return isMobile && ;
+}
diff --git a/apps/web/src/routes/settings/security.tsx b/apps/web/src/routes/settings/security.tsx
new file mode 100644
index 0000000..f52636f
--- /dev/null
+++ b/apps/web/src/routes/settings/security.tsx
@@ -0,0 +1,111 @@
+import { useStorage } from "@tensamin/storage/context";
+import { useEffect, useState } from "react";
+import QRCode from "qrcode";
+import {
+ AlertDialog,
+ AlertDialogAction,
+ AlertDialogCancel,
+ AlertDialogContent,
+ AlertDialogDescription,
+ AlertDialogFooter,
+ AlertDialogHeader,
+ AlertDialogTitle,
+ AlertDialogTrigger,
+} from "@tensamin/ui/cmp/alert-dialog";
+import { Button } from "@tensamin/ui/cmp/button";
+
+export default function Page() {
+ return (
+
+
+
+ );
+}
+
+// QR Code Login
+const generateQR = async (text: string): Promise => {
+ try {
+ const url = await QRCode.toDataURL(text, {
+ errorCorrectionLevel: "H",
+ margin: 1,
+ color: {
+ dark: "#000000FF",
+ light: "#FFFFFFFF",
+ },
+ });
+ return url;
+ } catch (err) {
+ console.error(err);
+ throw err;
+ }
+};
+function QrCodeLogin() {
+ const { load } = useStorage();
+ const [userId, setUserId] = useState(0);
+ const [privateKey, setPrivateKey] = useState("");
+ const [qrCodeBase64, setQrCodeBase64] = useState(
+ undefined,
+ );
+
+ useEffect(() => {
+ load("private_key").then((value) => {
+ if (value) {
+ setPrivateKey(value);
+ }
+ });
+ load("user_id").then((value) => {
+ if (value) {
+ setUserId(value);
+ }
+ });
+ }, [load]);
+
+ useEffect(() => {
+ if (userId && privateKey) {
+ generateQR(`tensamin://tu::${userId}::${privateKey}`).then(
+ setQrCodeBase64,
+ );
+ }
+ }, [userId, privateKey]);
+
+ const [qrCodeVisible, setQrCodeVisible] = useState(false);
+
+ return (
+
+ {qrCodeBase64 && (
+
+ )}
+ {!qrCodeVisible && (
+ <>
+
+
+
+ Show QR Code} />
+
+
+ Are you sure?
+
+ This will expose your private key! This is the same as
+ sharing the .tu file! It's very tedious to change the
+ private key, so only do this if you are sure no one can
+ steal it!
+
+
+
+ Cancel
+ setQrCodeVisible(true)}>
+ Show QR Code
+
+ }
+ />
+
+
+
+
+ >
+ )}
+
+ );
+}
diff --git a/bun.lock b/bun.lock
index 1372191..a8e64c4 100644
--- a/bun.lock
+++ b/bun.lock
@@ -23,7 +23,10 @@
"version": "0.0.0",
"dependencies": {
"@tauri-apps/api": "^2",
+ "@tauri-apps/plugin-barcode-scanner": "~2",
+ "@tauri-apps/plugin-deep-link": "~2",
"@tauri-apps/plugin-opener": "^2",
+ "@tensamin/shared": "workspace:*",
"@tensamin/ui": "workspace:*",
"lucide-react": "^1.7.0",
"react": "^19.2.0",
@@ -57,6 +60,7 @@
"comlink": "^4.4.2",
"framer-motion": "^12.38.0",
"lucide-react": "^0.564.0",
+ "qrcode": "^1.5.4",
"react": "^19.2.0",
"react-dom": "^19.2.0",
"shadcn": "^4.0.7",
@@ -70,6 +74,7 @@
},
"devDependencies": {
"@eslint/js": "^10.0.1",
+ "@types/qrcode": "^1.5.6",
"@types/react": "^19.2.2",
"@types/react-dom": "^19.2.2",
"@vitejs/plugin-react": "^5.0.4",
@@ -198,6 +203,7 @@
"version": "0.0.0",
"dependencies": {
"@base-ui/react": "^1.3.0",
+ "@tauri-apps/api": "^2",
"@tensamin/tauri": "workspace:*",
"class-variance-authority": "^0.7.1",
"clsx": "^2.1.1",
@@ -601,6 +607,10 @@
"@tauri-apps/cli-win32-x64-msvc": ["@tauri-apps/cli-win32-x64-msvc@2.10.1", "", { "os": "win32", "cpu": "x64" }, "sha512-6Cn7YpPFwzChy0ERz6djKEmUehWrYlM+xTaNzGPgZocw3BD7OfwfWHKVWxXzdjEW2KfKkHddfdxK1XXTYqBRLg=="],
+ "@tauri-apps/plugin-barcode-scanner": ["@tauri-apps/plugin-barcode-scanner@2.4.4", "", { "dependencies": { "@tauri-apps/api": "^2.10.1" } }, "sha512-uXvyMI8UgQjSrGxzTU5isNoQarMGRxFmTmb4TsgiWZHf/g7LsIyAQCwoFShjax0fXCK5mdVKDOvlkfOr21fo6g=="],
+
+ "@tauri-apps/plugin-deep-link": ["@tauri-apps/plugin-deep-link@2.4.8", "", { "dependencies": { "@tauri-apps/api": "^2.10.1" } }, "sha512-Cd2Cs960MGuGONeIwxOPx9wqwedetAHOGlwK5boJ/SMTfAtAyfErpfVPEn+EJzgXsJun8EKzsEumHjr+64V4fw=="],
+
"@tauri-apps/plugin-opener": ["@tauri-apps/plugin-opener@2.5.3", "", { "dependencies": { "@tauri-apps/api": "^2.8.0" } }, "sha512-CCcUltXMOfUEArbf3db3kCE7Ggy1ExBEBl51Ko2ODJ6GDYHRp1nSNlQm5uNCFY5k7/ufaK5Ib3Du/Zir19IYQQ=="],
"@tensamin/call": ["@tensamin/call@workspace:packages/call"],
@@ -647,6 +657,8 @@
"@types/node": ["@types/node@25.4.0", "", { "dependencies": { "undici-types": "~7.18.0" } }, "sha512-9wLpoeWuBlcbBpOY3XmzSTG3oscB6xjBEEtn+pYXTfhyXhIxC5FsBer2KTopBlvKEiW9l13po9fq+SJY/5lkhw=="],
+ "@types/qrcode": ["@types/qrcode@1.5.6", "", { "dependencies": { "@types/node": "*" } }, "sha512-te7NQcV2BOvdj2b1hCAHzAoMNuj65kNBMz0KBaxM6c3VGBOhU0dURQKOtH8CFNI/dsKkwlv32p26qYQTWoB5bw=="],
+
"@types/react": ["@types/react@19.2.14", "", { "dependencies": { "csstype": "^3.2.2" } }, "sha512-ilcTH/UniCkMdtexkoCN0bI7pMcJDvmQFPvuPvmEaYA/NSfFTAgdUSLAoVjaRJm7+6PvcM+q1zYOwS4wTYMF9w=="],
"@types/react-dom": ["@types/react-dom@19.2.3", "", { "peerDependencies": { "@types/react": "^19.2.0" } }, "sha512-jp2L/eY6fn+KgVVQAOqYItbF0VY/YApe5Mz2F0aykSO8gx31bYCZyvSeYxCHKvzHG5eZjc+zyaS5BrBWya2+kQ=="],
@@ -721,6 +733,8 @@
"callsites": ["callsites@3.1.0", "", {}, "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ=="],
+ "camelcase": ["camelcase@5.3.1", "", {}, "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg=="],
+
"caniuse-lite": ["caniuse-lite@1.0.30001770", "", {}, "sha512-x/2CLQ1jHENRbHg5PSId2sXq1CIO1CISvwWAj027ltMVG2UNgW+w9oH2+HzgEIRFembL8bUlXtfbBHR1fCg2xw=="],
"chalk": ["chalk@5.6.2", "", {}, "sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA=="],
@@ -733,7 +747,7 @@
"cli-width": ["cli-width@4.1.0", "", {}, "sha512-ouuZd4/dm2Sw5Gmqy6bGyNNNe1qt9RpmxveLSO7KcgsTnU7RXfsw+/bukWGo1abgBiMAic068rclZsO4IWmmxQ=="],
- "cliui": ["cliui@8.0.1", "", { "dependencies": { "string-width": "^4.2.0", "strip-ansi": "^6.0.1", "wrap-ansi": "^7.0.0" } }, "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ=="],
+ "cliui": ["cliui@6.0.0", "", { "dependencies": { "string-width": "^4.2.0", "strip-ansi": "^6.0.0", "wrap-ansi": "^6.2.0" } }, "sha512-t6wbgtoCXvAzst7QgXxJYqPt0usEfbgQdftEPbLL/cvv6HPE5VgvqCuAIDR0NgU52ds6rFwqrgakNLrHEjCbrQ=="],
"clsx": ["clsx@2.1.1", "", {}, "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA=="],
@@ -775,6 +789,8 @@
"debug": ["debug@4.4.3", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA=="],
+ "decamelize": ["decamelize@1.2.0", "", {}, "sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA=="],
+
"dedent": ["dedent@1.7.2", "", { "peerDependencies": { "babel-plugin-macros": "^3.1.0" }, "optionalPeers": ["babel-plugin-macros"] }, "sha512-WzMx3mW98SN+zn3hgemf4OzdmyNhhhKz5Ay0pUfQiMQ3e1g+xmTJWp/pKdwKVXhdSkAEGIIzqeuWrL3mV/AXbA=="],
"deep-is": ["deep-is@0.1.4", "", {}, "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ=="],
@@ -793,6 +809,8 @@
"diff": ["diff@8.0.3", "", {}, "sha512-qejHi7bcSD4hQAZE0tNAawRK1ZtafHDmMTMkrrIGgSLl7hTnQHmKCeB45xAcbfTqK2zowkM3j3bHt/4b/ARbYQ=="],
+ "dijkstrajs": ["dijkstrajs@1.0.3", "", {}, "sha512-qiSlmBq9+BCdCA/L46dw8Uy93mloxsPSbwnm5yrKn2vMPiy8KyAskTF6zuV/j5BMsmOGZDPs7KjU+mjb670kfA=="],
+
"dotenv": ["dotenv@17.3.1", "", {}, "sha512-IO8C/dzEb6O3F9/twg6ZLXz164a2fhTnEWb95H23Dm4OuN+92NmEAlTrupP9VW6Jm3sO26tQlqyvyi4CsnY9GA=="],
"dunder-proto": ["dunder-proto@1.0.1", "", { "dependencies": { "call-bind-apply-helpers": "^1.0.1", "es-errors": "^1.3.0", "gopd": "^1.2.0" } }, "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A=="],
@@ -803,7 +821,7 @@
"electron-to-chromium": ["electron-to-chromium@1.5.286", "", {}, "sha512-9tfDXhJ4RKFNerfjdCcZfufu49vg620741MNs26a9+bhLThdB+plgMeou98CAaHu/WATj2iHOOHTp1hWtABj2A=="],
- "emoji-regex": ["emoji-regex@10.6.0", "", {}, "sha512-toUI84YS5YmxW219erniWD0CIVOo46xGKColeNQRgOzDorgBi1v4D71/OFzgD9GO2UGKIv1C3Sp8DAn0+j5w7A=="],
+ "emoji-regex": ["emoji-regex@8.0.0", "", {}, "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="],
"encodeurl": ["encodeurl@2.0.0", "", {}, "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg=="],
@@ -1147,6 +1165,8 @@
"p-locate": ["p-locate@5.0.0", "", { "dependencies": { "p-limit": "^3.0.2" } }, "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw=="],
+ "p-try": ["p-try@2.2.0", "", {}, "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ=="],
+
"package-manager-detector": ["package-manager-detector@1.6.0", "", {}, "sha512-61A5ThoTiDG/C8s8UMZwSorAGwMJ0ERVGj2OjoW5pAalsNOg15+iQiPzrLJ4jhZ1HJzmC2PIHT2oEiH3R5fzNA=="],
"parent-module": ["parent-module@1.0.1", "", { "dependencies": { "callsites": "^3.0.0" } }, "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g=="],
@@ -1171,6 +1191,8 @@
"pkce-challenge": ["pkce-challenge@5.0.1", "", {}, "sha512-wQ0b/W4Fr01qtpHlqSqspcj3EhBvimsdh0KlHhH8HRZnMsEa0ea2fTULOXOS9ccQr3om+GcGRk4e+isrZWV8qQ=="],
+ "pngjs": ["pngjs@5.0.0", "", {}, "sha512-40QW5YalBNfQo5yRYmiw7Yz6TKKVr3h6970B2YE+3fQpsWcrbj1PzJgxeJ19DRQjhMbKPIuMY8rFaXc8moolVw=="],
+
"postcss": ["postcss@8.5.6", "", { "dependencies": { "nanoid": "^3.3.11", "picocolors": "^1.1.1", "source-map-js": "^1.2.1" } }, "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg=="],
"postcss-selector-parser": ["postcss-selector-parser@7.1.1", "", { "dependencies": { "cssesc": "^3.0.0", "util-deprecate": "^1.0.2" } }, "sha512-orRsuYpJVw8LdAwqqLykBj9ecS5/cRHlI5+nvTo8LcCKmzDmqVORXtOIYEEQuL9D4BxtA1lm5isAqzQZCoQ6Eg=="],
@@ -1189,6 +1211,8 @@
"punycode": ["punycode@2.3.1", "", {}, "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg=="],
+ "qrcode": ["qrcode@1.5.4", "", { "dependencies": { "dijkstrajs": "^1.0.1", "pngjs": "^5.0.0", "yargs": "^15.3.1" }, "bin": { "qrcode": "bin/qrcode" } }, "sha512-1ca71Zgiu6ORjHqFBDpnSMTR2ReToX4l1Au1VFLyVeBTFavzQnv5JxMFr3ukHVKpSrSA2MCk0lNJSykjUfz7Zg=="],
+
"qs": ["qs@6.15.0", "", { "dependencies": { "side-channel": "^1.1.0" } }, "sha512-mAZTtNCeetKMH+pSjrb76NAM8V9a05I9aBZOHztWy/UqcJdQYNsf59vrRKWnojAT9Y+GbIvoTBC++CPHqpDBhQ=="],
"queue-microtask": ["queue-microtask@1.2.3", "", {}, "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A=="],
@@ -1209,6 +1233,8 @@
"require-from-string": ["require-from-string@2.0.2", "", {}, "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw=="],
+ "require-main-filename": ["require-main-filename@2.0.0", "", {}, "sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg=="],
+
"reselect": ["reselect@5.1.1", "", {}, "sha512-K/BG6eIky/SBpzfHZv/dd+9JBFiS4SWV7FIujVyJRux6e45+73RaUHXLmIR1f7WOMaQ0U1km6qwklRQxpJJY0w=="],
"resolve-from": ["resolve-from@4.0.0", "", {}, "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g=="],
@@ -1247,6 +1273,8 @@
"serve-static": ["serve-static@2.2.1", "", { "dependencies": { "encodeurl": "^2.0.0", "escape-html": "^1.0.3", "parseurl": "^1.3.3", "send": "^1.2.0" } }, "sha512-xRXBn0pPqQTVQiC8wyQrKs2MOlX24zQ0POGaj0kultvoOCstBQM5yvOhAVSUwOMjQtTvsPWoNCHfPGwaaQJhTw=="],
+ "set-blocking": ["set-blocking@2.0.0", "", {}, "sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw=="],
+
"setprototypeof": ["setprototypeof@1.2.0", "", {}, "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw=="],
"shadcn": ["shadcn@4.0.7", "", { "dependencies": { "@antfu/ni": "^25.0.0", "@babel/core": "^7.28.0", "@babel/parser": "^7.28.0", "@babel/plugin-transform-typescript": "^7.28.0", "@babel/preset-typescript": "^7.27.1", "@dotenvx/dotenvx": "^1.48.4", "@modelcontextprotocol/sdk": "^1.26.0", "@types/validate-npm-package-name": "^4.0.2", "browserslist": "^4.26.2", "commander": "^14.0.0", "cosmiconfig": "^9.0.0", "dedent": "^1.6.0", "deepmerge": "^4.3.1", "diff": "^8.0.2", "execa": "^9.6.0", "fast-glob": "^3.3.3", "fs-extra": "^11.3.1", "fuzzysort": "^3.1.0", "https-proxy-agent": "^7.0.6", "kleur": "^4.1.5", "msw": "^2.10.4", "node-fetch": "^3.3.2", "open": "^11.0.0", "ora": "^8.2.0", "postcss": "^8.5.6", "postcss-selector-parser": "^7.1.0", "prompts": "^2.4.2", "recast": "^0.23.11", "stringify-object": "^5.0.0", "tailwind-merge": "^3.0.1", "ts-morph": "^26.0.0", "tsconfig-paths": "^4.2.0", "validate-npm-package-name": "^7.0.1", "zod": "^3.24.1", "zod-to-json-schema": "^3.24.6" }, "bin": { "shadcn": "dist/index.js" } }, "sha512-QwnD52EtnZo575yG/mk4fp6n0Qu8HZTVj10dsbg1O8DBLZPZG1V7AGOgTQSTjdsl8Jtye8GJfoKFz5npBkfExg=="],
@@ -1279,7 +1307,7 @@
"strict-event-emitter": ["strict-event-emitter@0.5.1", "", {}, "sha512-vMgjE/GGEPEFnhFub6pa4FmJBRBVOLpIII2hvCZ8Kzb7K0hlHo7mQv6xYrBvCL2LtAIBwFUK8wvuJgTVSQ5MFQ=="],
- "string-width": ["string-width@7.2.0", "", { "dependencies": { "emoji-regex": "^10.3.0", "get-east-asian-width": "^1.0.0", "strip-ansi": "^7.1.0" } }, "sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ=="],
+ "string-width": ["string-width@4.2.3", "", { "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", "strip-ansi": "^6.0.1" } }, "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g=="],
"stringify-object": ["stringify-object@5.0.0", "", { "dependencies": { "get-own-enumerable-keys": "^1.0.0", "is-obj": "^3.0.0", "is-regexp": "^3.1.0" } }, "sha512-zaJYxz2FtcMb4f+g60KsRNFOpVMUyuJgA51Zi5Z1DOTC3S59+OQiVOzE9GZt0x72uBGWKsQIuBKeF9iusmKFsg=="],
@@ -1381,6 +1409,8 @@
"which": ["which@2.0.2", "", { "dependencies": { "isexe": "^2.0.0" }, "bin": { "node-which": "./bin/node-which" } }, "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA=="],
+ "which-module": ["which-module@2.0.1", "", {}, "sha512-iBdZ57RDvnOR9AGBhML2vFZf7h8vmBjhoaZqODJBFWHVtKkDmKuHai3cx5PgVMrX5YDNp27AofYbAwctSS+vhQ=="],
+
"word-wrap": ["word-wrap@1.2.5", "", {}, "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA=="],
"wrap-ansi": ["wrap-ansi@6.2.0", "", { "dependencies": { "ansi-styles": "^4.0.0", "string-width": "^4.1.0", "strip-ansi": "^6.0.0" } }, "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA=="],
@@ -1389,13 +1419,13 @@
"wsl-utils": ["wsl-utils@0.3.1", "", { "dependencies": { "is-wsl": "^3.1.0", "powershell-utils": "^0.1.0" } }, "sha512-g/eziiSUNBSsdDJtCLB8bdYEUMj4jR7AGeUo96p/3dTafgjHhpF4RiCFPiRILwjQoDXx5MqkBr4fwWtR3Ky4Wg=="],
- "y18n": ["y18n@5.0.8", "", {}, "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA=="],
+ "y18n": ["y18n@4.0.3", "", {}, "sha512-JKhqTOwSrqNA1NY5lSztJ1GrBiUodLMmIZuLiDaMRJ+itFd+ABVE8XBjOvIWL+rSqNDC74LCSFmlb/U4UZ4hJQ=="],
"yallist": ["yallist@3.1.1", "", {}, "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g=="],
- "yargs": ["yargs@17.7.2", "", { "dependencies": { "cliui": "^8.0.1", "escalade": "^3.1.1", "get-caller-file": "^2.0.5", "require-directory": "^2.1.1", "string-width": "^4.2.3", "y18n": "^5.0.5", "yargs-parser": "^21.1.1" } }, "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w=="],
+ "yargs": ["yargs@15.4.1", "", { "dependencies": { "cliui": "^6.0.0", "decamelize": "^1.2.0", "find-up": "^4.1.0", "get-caller-file": "^2.0.1", "require-directory": "^2.1.1", "require-main-filename": "^2.0.0", "set-blocking": "^2.0.0", "string-width": "^4.2.0", "which-module": "^2.0.0", "y18n": "^4.0.0", "yargs-parser": "^18.1.2" } }, "sha512-aePbxDmcYW++PaqBsJ+HYUFwCdv4LVvdnhBy78E57PIor8/OVvhMrADFFEDh8DHDFRv/O9i3lPhsENjO7QX0+A=="],
- "yargs-parser": ["yargs-parser@21.1.1", "", {}, "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw=="],
+ "yargs-parser": ["yargs-parser@18.1.3", "", { "dependencies": { "camelcase": "^5.0.0", "decamelize": "^1.2.0" } }, "sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ=="],
"yocto-queue": ["yocto-queue@0.1.0", "", {}, "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q=="],
@@ -1451,12 +1481,8 @@
"ajv-formats/ajv": ["ajv@8.18.0", "", { "dependencies": { "fast-deep-equal": "^3.1.3", "fast-uri": "^3.0.1", "json-schema-traverse": "^1.0.0", "require-from-string": "^2.0.2" } }, "sha512-PlXPeEWMXMZ7sPYOHqmDyCJzcfNrUr3fGNKtezX14ykXOEIvyK81d+qydx89KY5O71FKMPaQ2vBfBFI5NHR63A=="],
- "cliui/string-width": ["string-width@4.2.3", "", { "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", "strip-ansi": "^6.0.1" } }, "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g=="],
-
"cliui/strip-ansi": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="],
- "cliui/wrap-ansi": ["wrap-ansi@7.0.0", "", { "dependencies": { "ansi-styles": "^4.0.0", "string-width": "^4.1.0", "strip-ansi": "^6.0.0" } }, "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q=="],
-
"eciesjs/@noble/curves": ["@noble/curves@1.9.7", "", { "dependencies": { "@noble/hashes": "1.8.0" } }, "sha512-gbKGcRUYIjA3/zCCNaWDciTMFI0dCkvou3TL8Zmy5Nc7sJ47a0jtOeZoTaMxkuqRo9cRhjOdZJXegxYE5FN/xw=="],
"eciesjs/@noble/hashes": ["@noble/hashes@1.8.0", "", {}, "sha512-jCs9ldd7NwzpgXDIf6P3+NrHh9/sD6CQdxHyjQI+h/6rDNo88ypBxxz45UDuZHz9r3tNz7N/VInSVoVdtXEI4A=="],
@@ -1469,8 +1495,12 @@
"micromatch/picomatch": ["picomatch@2.3.1", "", {}, "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA=="],
+ "msw/yargs": ["yargs@17.7.2", "", { "dependencies": { "cliui": "^8.0.1", "escalade": "^3.1.1", "get-caller-file": "^2.0.5", "require-directory": "^2.1.1", "string-width": "^4.2.3", "y18n": "^5.0.5", "yargs-parser": "^21.1.1" } }, "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w=="],
+
"npm-run-path/path-key": ["path-key@4.0.0", "", {}, "sha512-haREypq7xkM7ErfgIyA0z+Bj4AGKlMSdlQE2jvJo6huWD1EdkKYV+G/T4nq0YEF2vgTT8kqMFKo1uHn950r4SQ=="],
+ "ora/string-width": ["string-width@7.2.0", "", { "dependencies": { "emoji-regex": "^10.3.0", "get-east-asian-width": "^1.0.0", "strip-ansi": "^7.1.0" } }, "sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ=="],
+
"prompts/kleur": ["kleur@3.0.3", "", {}, "sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w=="],
"restore-cursor/onetime": ["onetime@7.0.0", "", { "dependencies": { "mimic-function": "^5.0.0" } }, "sha512-VXJjc87FScF88uafS3JllDgvAm+c/Slfz06lorj2uAY34rlUu0Nt+v8wreiImcrgAjjIHp1rXpTDlLOGw29WwQ=="],
@@ -1479,11 +1509,11 @@
"shadcn/zod": ["zod@3.25.76", "", {}, "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ=="],
- "wrap-ansi/string-width": ["string-width@4.2.3", "", { "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", "strip-ansi": "^6.0.1" } }, "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g=="],
+ "string-width/strip-ansi": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="],
"wrap-ansi/strip-ansi": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="],
- "yargs/string-width": ["string-width@4.2.3", "", { "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", "strip-ansi": "^6.0.1" } }, "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g=="],
+ "yargs/find-up": ["find-up@4.1.0", "", { "dependencies": { "locate-path": "^5.0.0", "path-exists": "^4.0.0" } }, "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw=="],
"@dotenvx/dotenvx/execa/get-stream": ["get-stream@6.0.1", "", {}, "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg=="],
@@ -1503,18 +1533,30 @@
"ajv-formats/ajv/json-schema-traverse": ["json-schema-traverse@1.0.0", "", {}, "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug=="],
- "cliui/string-width/emoji-regex": ["emoji-regex@8.0.0", "", {}, "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="],
-
"cliui/strip-ansi/ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="],
- "wrap-ansi/string-width/emoji-regex": ["emoji-regex@8.0.0", "", {}, "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="],
+ "msw/yargs/cliui": ["cliui@8.0.1", "", { "dependencies": { "string-width": "^4.2.0", "strip-ansi": "^6.0.1", "wrap-ansi": "^7.0.0" } }, "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ=="],
+
+ "msw/yargs/y18n": ["y18n@5.0.8", "", {}, "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA=="],
+
+ "msw/yargs/yargs-parser": ["yargs-parser@21.1.1", "", {}, "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw=="],
+
+ "ora/string-width/emoji-regex": ["emoji-regex@10.6.0", "", {}, "sha512-toUI84YS5YmxW219erniWD0CIVOo46xGKColeNQRgOzDorgBi1v4D71/OFzgD9GO2UGKIv1C3Sp8DAn0+j5w7A=="],
+
+ "string-width/strip-ansi/ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="],
"wrap-ansi/strip-ansi/ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="],
- "yargs/string-width/emoji-regex": ["emoji-regex@8.0.0", "", {}, "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="],
+ "yargs/find-up/locate-path": ["locate-path@5.0.0", "", { "dependencies": { "p-locate": "^4.1.0" } }, "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g=="],
- "yargs/string-width/strip-ansi": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="],
+ "msw/yargs/cliui/strip-ansi": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="],
- "yargs/string-width/strip-ansi/ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="],
+ "msw/yargs/cliui/wrap-ansi": ["wrap-ansi@7.0.0", "", { "dependencies": { "ansi-styles": "^4.0.0", "string-width": "^4.1.0", "strip-ansi": "^6.0.0" } }, "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q=="],
+
+ "yargs/find-up/locate-path/p-locate": ["p-locate@4.1.0", "", { "dependencies": { "p-limit": "^2.2.0" } }, "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A=="],
+
+ "msw/yargs/cliui/strip-ansi/ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="],
+
+ "yargs/find-up/locate-path/p-locate/p-limit": ["p-limit@2.3.0", "", { "dependencies": { "p-try": "^2.0.0" } }, "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w=="],
}
}
diff --git a/packages/chat/src/components/input.tsx b/packages/chat/src/components/input.tsx
index e770122..cb5888a 100644
--- a/packages/chat/src/components/input.tsx
+++ b/packages/chat/src/components/input.tsx
@@ -10,6 +10,7 @@ import { useTTP } from "@tensamin/ttp/context";
import { useCrypto } from "@tensamin/crypto/context";
import { log, toast } from "@tensamin/shared/log";
import Message from "./message";
+import { cn, useIsMobile } from "@tensamin/ui/utils";
export default function InputComponent({
value,
@@ -27,7 +28,7 @@ export default function InputComponent({
const { load } = useStorage();
React.useEffect(() => {
- void load("chat_invert_enter_behaviour").then((shouldInvert) => {
+ void load("settings.reverse_enter_behavior").then((shouldInvert) => {
setInvertEnterBehavior(shouldInvert);
});
}, [load]);
@@ -100,8 +101,17 @@ export default function InputComponent({
setValue("");
}
+ const isMobile = useIsMobile();
+
return (
-
+
-
diff --git a/packages/shared/package.json b/packages/shared/package.json
index db6b93d..baeaa0d 100644
--- a/packages/shared/package.json
+++ b/packages/shared/package.json
@@ -6,6 +6,7 @@
"exports": {
"./data": "./src/data.ts",
"./log": "./src/log.tsx",
+ "./settings": "./src/settings.ts",
"./features/legal/schema": "./src/features/legal/schema.ts",
"./features/conversation/schema": "./src/features/conversation/schema.ts"
},
diff --git a/packages/shared/src/data.ts b/packages/shared/src/data.ts
index 4a14dbb..7de68ce 100644
--- a/packages/shared/src/data.ts
+++ b/packages/shared/src/data.ts
@@ -1,6 +1,7 @@
import { z } from "zod";
import { legalDocsSchema } from "./features/legal/schema";
+import { settingsStorageDefaults, type SettingsStorageDefaults } from "./settings";
const fileFromMessage = z.object({
name: z.string(),
@@ -168,7 +169,7 @@ export const ttp = {
export type TTP = typeof ttp;
// Storage
-export interface Storage {
+export interface Storage extends SettingsStorageDefaults {
session_id: number;
user_id: number;
private_key: string;
@@ -179,7 +180,6 @@ export interface Storage {
analytics_usage_data: boolean;
analytics_done: boolean;
legal_docs: z.infer;
- chat_invert_enter_behaviour: boolean;
}
export const storageDefaults: Storage = {
@@ -192,6 +192,7 @@ export const storageDefaults: Storage = {
analytics_crash_reports: true,
analytics_usage_data: true,
analytics_done: false,
+ ...settingsStorageDefaults,
legal_docs: {
eula: {
version: "0.0",
@@ -209,5 +210,4 @@ export const storageDefaults: Storage = {
unix: 0,
},
},
- chat_invert_enter_behaviour: false,
};
diff --git a/packages/shared/src/settings.ts b/packages/shared/src/settings.ts
new file mode 100644
index 0000000..70212d2
--- /dev/null
+++ b/packages/shared/src/settings.ts
@@ -0,0 +1,76 @@
+type StringKeyOf = Extract;
+
+type SettingDefinition = {
+ display: string;
+ type: string;
+ default?: unknown;
+};
+
+export type SettingsSchema = Record<
+ string,
+ Record>
+>;
+
+const settings = {
+ account: {
+ security: {
+ login_qr_code: {
+ display: "Login QR Code",
+ type: "custom",
+ },
+ },
+ },
+ accessability: {
+ chat: {
+ reverse_enter_behavior: {
+ display: "Reverse Enter Behavior",
+ type: "boolean",
+ default: false,
+ },
+ },
+ },
+} as const satisfies SettingsSchema;
+
+export default settings;
+
+type BooleanSettingNames = {
+ [C in StringKeyOf]: {
+ [P in StringKeyOf]: {
+ [S in StringKeyOf]: T[C][P][S] extends {
+ type: "boolean";
+ default: boolean;
+ }
+ ? S
+ : never;
+ }[StringKeyOf];
+ }[StringKeyOf];
+}[StringKeyOf];
+
+export type SettingsStorageKey =
+ `settings.${BooleanSettingNames}`;
+
+export type SettingsStorageDefaults =
+ {
+ [K in SettingsStorageKey]: boolean;
+ };
+
+function buildSettingsStorageDefaults(
+ schema: T,
+): SettingsStorageDefaults {
+ const defaults: Partial> = {};
+
+ for (const category of Object.values(schema)) {
+ for (const page of Object.values(category)) {
+ for (const [settingName, setting] of Object.entries(page)) {
+ if (setting.type === "boolean" && typeof setting.default === "boolean") {
+ const key = `settings.${settingName}` as SettingsStorageKey;
+ defaults[key] = setting.default;
+ }
+ }
+ }
+ }
+
+ return defaults as SettingsStorageDefaults;
+}
+
+export const settingsStorageDefaults = buildSettingsStorageDefaults(settings);
\ No newline at end of file
diff --git a/packages/ui/package.json b/packages/ui/package.json
index d523bc0..c80665b 100644
--- a/packages/ui/package.json
+++ b/packages/ui/package.json
@@ -11,12 +11,12 @@
"./utils": "./src/lib/utils.ts"
},
"scripts": {
- "patch": "find src -type f \\( -name \"*.ts\" -o -name \"*.tsx\" \\) -print0 | xargs -0 sed -i 's|@/lib/utils|../lib/utils|g; s|@/cmp/|./|g' && bunx prettier --write .",
"format": "bunx prettier --write .",
"lint": "eslint src",
"build": "tsc -p tsconfig.json --noEmit"
},
"dependencies": {
+ "@tauri-apps/api": "^2",
"@tensamin/tauri": "workspace:*",
"@base-ui/react": "^1.3.0",
"class-variance-authority": "^0.7.1",
diff --git a/packages/ui/src/cmp/switch.tsx b/packages/ui/src/cmp/switch.tsx
new file mode 100644
index 0000000..88ac5e9
--- /dev/null
+++ b/packages/ui/src/cmp/switch.tsx
@@ -0,0 +1,30 @@
+import { Switch as SwitchPrimitive } from "@base-ui/react/switch"
+
+import { cn } from "../lib/utils"
+
+function Switch({
+ className,
+ size = "default",
+ ...props
+}: SwitchPrimitive.Root.Props & {
+ size?: "sm" | "default"
+}) {
+ return (
+
+
+
+ )
+}
+
+export { Switch }
diff --git a/packages/ui/src/screens/util.tsx b/packages/ui/src/screens/util.tsx
index 584296c..1029d57 100644
--- a/packages/ui/src/screens/util.tsx
+++ b/packages/ui/src/screens/util.tsx
@@ -1,5 +1,6 @@
import { cn } from "../lib/utils";
import Controls from "@tensamin/tauri/controls";
+import { isTauri } from "@tauri-apps/api/core";
export default function CreateScreen({
children,
@@ -41,7 +42,13 @@ import { useIsMobile } from "../lib/utils";
export function StorageDestoryer() {
const isMobile = useIsMobile();
return (
-