fix(web): code review round 3 -- UI bugs and remaining design fidelity

- FileAttachment never surfaced a failed authenticated download -- the
  card looked identically clickable whether it worked or not, and
  clicking while broken silently no-op'd. Added an error state/retry
  (useAuthedBlobUrl gained a retry() escape hatch for this).
  ImageAttachment/AudioAttachment already handled this; only the file
  card didn't.
- --shadow-btn was one token serving two different specs: round 2's fix
  (0 6px 16px, from the 50px auth CTAs) actually regressed the 36px
  messaging send button (ConversationView/VoiceRecorder), which needs
  0 4px 12px per 02-messages.dc.html. Split into --shadow-btn /
  --shadow-btn-sm.
- loadOlder's scroll-restore guard compared topicId by value, which
  false-positives on an A->B->A round trip within one request's
  lifetime (same string, but the DOM was torn down and rebuilt by the
  intervening open/close). Switched to a generation counter, same
  pattern as topics.ts's leave-race fix.
- errorToast wasn't included in ConversationView/DetailColumn's
  topicId-reset effects -- a failed action's toast could keep showing
  over a conversation/contact switched to afterward. Also switched
  those effects (and MessagesLayout's) from useEffect to
  useLayoutEffect -- since none of these components remount on
  topicId change, a post-paint effect could let one frame render with
  the previous topicId's stale UI still showing.
- VoiceRecorder never listened for MediaRecorder's own onerror -- a
  mid-recording failure left the "录音中" timer ticking forever with no
  indication the recording had already died.
- VerificationCodeButton's phone-change countdown reset (round 2) only
  cleared the button's own state; RegisterPage/ResetPasswordPage's
  challengeId was still tied to whatever phone the code was actually
  issued for, with nothing cross-checking it against a since-edited
  phone number.
- Agreement's checkbox used bare accent-color on a native input, which
  only tints the checked fill and leaves size/radius/border to the
  browser/OS; replaced with the spec's exact 16x16 border-radius:4px
  box + check glyph.
- Added a catch-all route (App.tsx), TenantFeedbackPage image-picker
  validation feedback + corrected remove-button style, and a handful of
  smaller token/spacing corrections against screens/*.dc.html (NavRail
  font-weight, LoginPage/TenantEntryPage logo size+shadow+color,
  ChatListColumn row padding/icon size, --shadow-modal alpha).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
Your Name
2026-09-08 05:27:41 +08:00
co-authored by Claude Sonnet 5
parent 8ec74dffcc
commit 72b7b79caf
16 changed files with 130 additions and 53 deletions
+7 -2
View File
@@ -11,6 +11,7 @@ import { parseWebsocketEndpoint, useSessionStore } from '@/stores/session';
export function TenantEntryPage() {
const navigate = useNavigate();
const setTenant = useSessionStore((s) => s.setTenant);
const myUserId = useSessionStore((s) => s.myUserId);
const [code, setCode] = useState('');
const [error, setError] = useState('');
const [submitting, setSubmitting] = useState(false);
@@ -18,6 +19,10 @@ export function TenantEntryPage() {
const submit = async (e: React.FormEvent) => {
e.preventDefault();
if (!code.trim() || submitting) return;
// Reachable by navigating back here while already logged in (no route guard keeps an
// authenticated user out of /tenant) -- resetTinode() below would otherwise silently tear down
// that live session with no warning at all.
if (myUserId && !window.confirm('切换企业将退出当前登录,确定继续吗?')) return;
setSubmitting(true);
setError('');
try {
@@ -43,10 +48,10 @@ export function TenantEntryPage() {
</Link>
<form onSubmit={submit} className="flex w-full max-w-[380px] flex-col gap-5">
<div className="mb-2 flex flex-col items-center gap-3">
<div className="grid h-[72px] w-[72px] place-items-center rounded-2xl bg-accent text-3xl font-bold text-white">
<div className="grid h-[72px] w-[72px] place-items-center rounded-2xl bg-accent text-[36px] font-bold text-white shadow-[0_10px_26px_rgba(35,74,125,0.18)]">
M
</div>
<strong className="text-2xl font-bold text-text-heading">MeetingIM</strong>
<strong className="text-[26px] font-bold text-text-title">MeetingIM</strong>
</div>
{error ? (
<div className="flex items-center gap-1.5 rounded-lg bg-error-strip-bg px-3 py-2.5 text-sm text-error-strip-text">