fix(web): replace Zustand device store with local useDeviceId hook

Move device ID logic from @multica/store (Zustand persist) into a
simple useDeviceId hook in the web app. SSR returns empty string,
client reads/writes localStorage directly — no hydration mismatch,
no suppressHydrationWarning needed.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
Naiyuan Qing 2026-01-30 22:16:54 +08:00
parent 7d326695c1
commit 5f367fb6b7
7 changed files with 27 additions and 25 deletions

View file

@ -4,13 +4,13 @@ import { useRef } from "react";
import { SidebarTrigger } from "@multica/ui/components/ui/sidebar";
import { ChatInput } from "@multica/ui/components/chat-input";
import { MemoizedMarkdown } from "@multica/ui/components/markdown";
import { useDeviceStore } from "@multica/store";
import { useMessages } from "../hooks/use-messages";
import { useDeviceId } from "../hooks/use-device-id";
import { useScrollFade } from "../hooks/use-scroll-fade";
import { cn } from "@multica/ui/lib/utils";
export function Chat() {
const deviceId = useDeviceStore((s) => s.deviceId);
const deviceId = useDeviceId();
const messages = useMessages();
const mainRef = useRef<HTMLElement>(null);
const fadeStyle = useScrollFade(mainRef);
@ -19,8 +19,8 @@ export function Chat() {
<div className="h-dvh flex flex-col overflow-hidden w-full">
<header className="flex items-center gap-2 p-2">
<SidebarTrigger />
<span className="text-xs text-muted-foreground font-mono" suppressHydrationWarning>
{deviceId.slice(0, 8)}
<span className="text-xs text-muted-foreground font-mono">
{deviceId ? deviceId.slice(0, 8) : "\u00A0"}
</span>
</header>

View file

@ -0,0 +1,19 @@
import { useState, useEffect } from "react"
import { v7 as uuidv7 } from "uuid"
const STORAGE_KEY = "multica-device-id"
export function useDeviceId(): string {
const [deviceId, setDeviceId] = useState("")
useEffect(() => {
let id = localStorage.getItem(STORAGE_KEY)
if (!id) {
id = uuidv7()
localStorage.setItem(STORAGE_KEY, id)
}
setDeviceId(id)
}, [])
return deviceId
}

View file

@ -11,6 +11,7 @@
"dependencies": {
"@multica/store": "workspace:*",
"@multica/ui": "workspace:*",
"uuid": "^13.0.0",
"@hugeicons/core-free-icons": "^3.1.1",
"@hugeicons/react": "^1.1.4",
"next": "16.1.6",

View file

@ -8,7 +8,6 @@
"./*": "./src/*.ts"
},
"dependencies": {
"uuid": "^13.0.0",
"zustand": "catalog:"
},
"devDependencies": {

View file

@ -1,16 +0,0 @@
import { create } from 'zustand'
import { persist } from 'zustand/middleware'
import { v7 as uuidv7 } from 'uuid'
interface DeviceState {
deviceId: string
}
export const useDeviceStore = create<DeviceState>()(
persist(
() => ({
deviceId: uuidv7(),
}),
{ name: 'multica-device' }
)
)

View file

@ -1,2 +1 @@
export { useCounterStore } from './counter'
export { useDeviceStore } from './device'

6
pnpm-lock.yaml generated
View file

@ -223,6 +223,9 @@ importers:
react-dom:
specifier: 'catalog:'
version: 19.2.3(react@19.2.3)
uuid:
specifier: ^13.0.0
version: 13.0.0
devDependencies:
'@types/node':
specifier: 'catalog:'
@ -261,9 +264,6 @@ importers:
packages/store:
dependencies:
uuid:
specifier: ^13.0.0
version: 13.0.0
zustand:
specifier: 'catalog:'
version: 5.0.10(@types/react@19.2.10)(react@19.2.3)(use-sync-external-store@1.6.0(react@19.2.3))