- CRITICAL: draft text and voice-recording state were never isolated per conversation. ConversationView is one long-lived instance reused across every topic (`:topicId` route param change doesn't remount it), so a single `draft` string leaked one conversation's unsent text into whatever conversation was opened next -- and, far more seriously, starting a voice recording in conversation A and then switching to conversation B before hitting send would publish that recording into B: VoiceRecorder keeps its own MediaRecorder running across the switch, but the onSend callback passed to it is a fresh closure over whatever topic is CURRENT when it finally fires. Fixed by keying drafts per topicId and force- resetting `recording` (unmounting VoiceRecorder, which stops its stream) whenever topicId changes. - CRITICAL: the detail/settings panel was completely unreachable below 960px -- it only ever rendered inside `lg:block` (>=1280px, docked) or `md:block lg:hidden` (960-1279px, drawer) wrappers, so tapping the header "i" button below 960px flipped internal state and fired its data queries with zero visible effect. Added the README §1.4-mandated <960px full-page overlay tier, and a small new useMediaQuery hook so the header's "i" button highlight reflects whether the panel is ACTUALLY visible right now instead of being hardcoded to a breakpoint (it was previously always blue at 960-1279px regardless of the drawer's real open state, and always grey at >=1280px despite the panel being permanently docked open there). - CRITICAL (visually confirmed against 1a-desktop-chat.png): messages you send never showed an avatar at all (just an empty spacer) -- every reference screenshot shows your own "我" avatar beside your bubbles the same as the other party's. MessageRow now takes myUserId and renders it. While in there, also moved the failed-send error icon beside the bubble (README: "气泡左侧") instead of below it with the retry text. - CRITICAL: ChatListColumn's search box had no value/onChange at all -- typing into it did nothing. Now filters by contact name; added the search_off/"未找到相关会话" and forum/"暂无会话" empty states README §2.1 calls for, neither of which existed before. - MEDIUM (real Tailwind cascade bug): the selected chat-list row's highlight was being overridden by the unconditional `hover:bg-[...]` class on the same element -- Tailwind emits hover-variant rules after base rules regardless of source order, so hovering an already-selected row visually un-selected it until the mouse moved away. Scoped the hover class to the non-selected branch only. - NavRail: inactive icons used the wrong color token (icon-default #555c65 instead of the spec's #656b74, this app's text-secondary); the unread badge prop was never actually supplied by AppShell, so the badge's otherwise-pixel-correct rendering never appeared in the running app. Fixed both, and gave BottomTabBar's 消息 tab the same real badge (computed once in AppShell from useChatList(), passed to both). Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
React + TypeScript + Vite
This template provides a minimal setup to get React working in Vite with HMR and some Oxlint rules.
Currently, two official plugins are available:
- @vitejs/plugin-react uses Oxc
- @vitejs/plugin-react-swc uses SWC
React Compiler
The React Compiler is not enabled on this template because of its impact on dev & build performances. To add it, see this documentation.
Expanding the Oxlint configuration
If you are developing a production application, we recommend enabling type-aware lint rules by installing oxlint-tsgolint and editing .oxlintrc.json:
{
"$schema": "./node_modules/oxlint/configuration_schema.json",
"plugins": ["react", "typescript", "oxc"],
"options": {
"typeAware": true
},
"rules": {
"react/rules-of-hooks": "error",
"react/only-export-components": ["warn", { "allowConstantExport": true }]
}
}
See the Oxlint rules documentation for the full list of rules and categories.