diff --git a/CHANGELOG.md b/CHANGELOG.md index 1317d7e..3b2390d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,6 +14,17 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +## [0.2.11] - 2026-09-05 + +### Fixed + +- Gave the auth tab switcher a visible keyboard focus indicator (the selected tab previously showed no focus change at all) and raised the `--text-soft` token to an AA-compliant contrast in both themes (light 2.78→4.85:1, dark 4.1→5.76:1). +- Registration now marks the invite code as required and validates it inline, adds `autocomplete` to all auth inputs, announces server auth errors with `role="alert"`, and resolves OAuth links against the API origin so they work in split web/worker deployments. +- Workspace routes now set a per-page document title (e.g. "邮件列表 · WeMail") and the dashboard and inbox pages render a page-level heading, following the existing per-page sr-only h1 convention. +- Link-type extraction chips now name the link by type (登录链接 / 服务链接 / 订阅链接 / 有用链接) instead of a fixed English "LOGIN LINK" label. +- The create-mailbox dialog keeps its primary action enabled and validates on submit with field-level messages, instead of disabling the button until the form is valid. +- The mail list empty state now points users without any mailbox to creating one, and the detail metadata row no longer overflows its panel by a few pixels at 320px width. + ## [0.2.10] - 2026-09-05 ### Fixed diff --git a/apps/docs/package.json b/apps/docs/package.json index 9d2ffcc..426c4f5 100644 --- a/apps/docs/package.json +++ b/apps/docs/package.json @@ -1,6 +1,6 @@ { "name": "@wemail/docs", - "version": "0.2.10", + "version": "0.2.11", "private": true, "type": "module", "scripts": { diff --git a/apps/web/package.json b/apps/web/package.json index fc778b0..b569c42 100644 --- a/apps/web/package.json +++ b/apps/web/package.json @@ -1,6 +1,6 @@ { "name": "@wemail/web", - "version": "0.2.10", + "version": "0.2.11", "private": true, "type": "module", "scripts": { diff --git a/apps/web/src/app/AppLayout.tsx b/apps/web/src/app/AppLayout.tsx index a820ff0..3820f11 100644 --- a/apps/web/src/app/AppLayout.tsx +++ b/apps/web/src/app/AppLayout.tsx @@ -143,6 +143,14 @@ export function AppLayout({ const activeSecondaryRoute = shell.secondaryNav.find((item) => item.to === location.pathname)?.to ?? shell.secondaryNav[0]?.to ?? ""; const hasAnnouncementBadge = announcementCount > 0; + const activePageLabel = + shell.secondaryNav.find((item) => item.to === activeSecondaryRoute)?.label ?? shell.activePrimaryLabel; + + // Screen-reader and browser-history users identify the page by its title; + // without this every route shares the static "WeMail" document title. + useEffect(() => { + document.title = `${activePageLabel} · WeMail`; + }, [activePageLabel]); useEffect(() => { const areas = [railScrollRef.current, mainScrollRef.current].filter(Boolean) as Array; diff --git a/apps/web/src/features/auth/AuthForms.tsx b/apps/web/src/features/auth/AuthForms.tsx index d7cc291..22320a6 100644 --- a/apps/web/src/features/auth/AuthForms.tsx +++ b/apps/web/src/features/auth/AuthForms.tsx @@ -2,6 +2,7 @@ import type { FormEvent, ReactNode } from "react"; import { CircleAlert, Eye, EyeOff, KeyRound, Mail, Ticket, UserRound } from "lucide-react"; import { useRef, useState } from "react"; +import { resolveApiUrl } from "../../shared/api/client"; import { Button, ButtonAnchor } from "../../shared/button"; import { FormField, TextInput } from "../../shared/form"; @@ -51,6 +52,13 @@ function validatePassword(value: string) { return null; } +function validateInviteCode(value: string) { + // Registration requires a valid invite server-side; surface that at the + // field instead of letting an empty submit come back as a banner error. + if (!value.trim()) return getRequiredMessage("邀请码"); + return null; +} + function compactFieldErrors(errors: AuthFieldErrors) { const visibleErrors: AuthFieldErrors = {}; @@ -152,7 +160,8 @@ export function AuthForms({ authError, onRegister, onLogin, mode, oauthNext = "/ const nextErrors: AuthFieldErrors = { registerName: validateName(registerName) ?? undefined, registerEmail: validateEmail(registerEmail) ?? undefined, - registerPassword: validatePassword(registerPassword) ?? undefined + registerPassword: validatePassword(registerPassword) ?? undefined, + registerInviteCode: validateInviteCode(registerInviteCode) ?? undefined }; const visibleErrors = compactFieldErrors(nextErrors); @@ -168,7 +177,11 @@ export function AuthForms({ authError, onRegister, onLogin, mode, oauthNext = "/ return (
- {authError ?

{authError}

: null} + {authError ? ( +

+ {authError} +

+ ) : null} {mode === "login" ? (
@@ -181,6 +194,7 @@ export function AuthForms({ authError, onRegister, onLogin, mode, oauthNext = "/ { @@ -205,6 +219,7 @@ export function AuthForms({ authError, onRegister, onLogin, mode, oauthNext = "/