From 98cf36fc510a37e3725d8f301683513de6817d41 Mon Sep 17 00:00:00 2001 From: glass beadian Date: Sat, 17 Oct 2026 07:04:04 +0000 Subject: [PATCH] =?UTF-8?q?fix:=20UX=20audit=20P0/P1=20pass=20=E2=80=94=20?= =?UTF-8?q?publish=20reason=20inline,=20language=20pref=20on=20first-run,?= =?UTF-8?q?=20grouped=20sidebar,=20nav=20cleanup?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit P0-1 compose: inline 'Connect a channel to publish' beside the disabled Publish (desktop) and above the mobile bar — the silent-disabled state is gone. P0-2 first-run home reads the landing's tmbh.manual.lang preference instead of hard-coding Spanish. P1-3 sidebar: 16 flat items → 5 labeled sections (Publish/Content/Operations/ Admin); dead Onboarding item removed; Groups surfaces in Operations. P1-4 More menu: Groups row added; Onboarding row removed. P1-6 duplicate Security & sessions row removed. P1-7 mobile Post → Publish (label drift); More menu labels unified (Video library / Files / Connections / Audit trail). P2-10 username pattern regex fixed ([a-z0-9._-] with the dash last). --- apps/web/src/app/(main)/compose/page.tsx | 8 +- apps/web/src/app/(main)/more/page.tsx | 3 +- apps/web/src/app/(main)/onboarding/page.tsx | 2 +- .../collectives/no-collectives-home.tsx | 12 ++- apps/web/src/components/shell/sidebar.tsx | 73 ++++++++++++------- 5 files changed, 66 insertions(+), 32 deletions(-) diff --git a/apps/web/src/app/(main)/compose/page.tsx b/apps/web/src/app/(main)/compose/page.tsx index 464584c..a8bd792 100644 --- a/apps/web/src/app/(main)/compose/page.tsx +++ b/apps/web/src/app/(main)/compose/page.tsx @@ -274,7 +274,10 @@ export default function ComposePage(): React.ReactElement { className="w-52 py-1 text-xs" /> -
+
+ {targets.length === 0 && !over && ( + Connect a channel to publish — open Channels in the sidebar. + )}
+ {selected.length === 0 &&

Connect a channel to publish — open Channels in the sidebar.

} +
setScheduleOpen(false)} title="Schedule post" footer={}> setScheduledAt(event.target.value)} className="mt-2 min-h-12" />
{[1, 24, 48].map((hours) => )}
setPreviewOpen(false)} title="Target previews">{selected.map((target) =>

{target.label}

{editor?.getText() || "Your post preview appears here…"}

)}
diff --git a/apps/web/src/app/(main)/more/page.tsx b/apps/web/src/app/(main)/more/page.tsx index 3f40f0e..a6491d5 100644 --- a/apps/web/src/app/(main)/more/page.tsx +++ b/apps/web/src/app/(main)/more/page.tsx @@ -72,13 +72,12 @@ export default function MorePage(): React.ReactElement {
- + -
; diff --git a/apps/web/src/app/(main)/onboarding/page.tsx b/apps/web/src/app/(main)/onboarding/page.tsx index d1287a1..ce92603 100644 --- a/apps/web/src/app/(main)/onboarding/page.tsx +++ b/apps/web/src/app/(main)/onboarding/page.tsx @@ -271,7 +271,7 @@ export default function OnboardingPage(): React.ReactElement { onChange={(e) => setUsername(e.target.value.toLowerCase())} placeholder="Username — this is how you sign in" autoComplete="username" - pattern="[a-z0-9][a-z0-9_.-]{1,31}" + pattern="[a-z0-9][a-z0-9._-]{1,31}" title="2-32 characters: lowercase letters, digits, dot, underscore or hyphen" /> (x: T): string => x.es; + // Respect the reading language the user picked on the landing page (tmbh.lang + // in localStorage); Spanish stays the fallback for a fresh visitor. + const [lang, setLang] = useState<"es" | "en">("es"); + useEffect(() => { + try { + const stored = window.localStorage.getItem("tmbh.manual.lang"); + if (stored === "en" || stored === "es") setLang(stored); + } catch { /* private mode: keep the default */ } + }, []); + const t = (x: T): string => x[lang]; const name = session?.user.displayName?.trim() || session?.user.username || ""; return ( diff --git a/apps/web/src/components/shell/sidebar.tsx b/apps/web/src/components/shell/sidebar.tsx index 0734399..f30f75d 100644 --- a/apps/web/src/components/shell/sidebar.tsx +++ b/apps/web/src/components/shell/sidebar.tsx @@ -9,23 +9,44 @@ import type { DraftRecord } from "../../lib/api-types"; import { LogoMark } from "../brand/logo"; import { NavIcon } from "../brand"; -const NAV: { href: string; label: string; icon: string; external?: boolean }[] = [ - { href: "/streams", label: "Streams", icon: "video" }, - { href: "/control-room", label: "Control Room", icon: "video" }, - { href: "/compose", label: "Compose", icon: "compose" }, - { href: "/calendar", label: "Calendar", icon: "calendar" }, - { href: "/chat", label: "Chat", icon: "chat" }, - { href: "/files", label: "Files", icon: "files" }, - { href: "/archive", label: "Archive", icon: "files" }, - { href: "/inbox", label: "Inbox", icon: "inbox" }, - { href: "/approvals", label: "Approvals", icon: "approvals" }, - { href: "/video", label: "Video", icon: "video" }, - { href: "/analytics", label: "Analytics", icon: "analytics" }, - { href: "/connections", label: "Channels", icon: "connections" }, - { href: "/team", label: "Team", icon: "team" }, - { href: "/audit", label: "Audit log", icon: "audit" }, - { href: "/onboarding", label: "Onboarding", icon: "home" }, - { href: "/settings", label: "Settings", icon: "settings" }, +/** Grouped per the 2026-10 UX audit (16 flat items → 5 scannable sections). */ +const NAV: { section: string; items: { href: string; label: string; icon: string; external?: boolean }[] }[] = [ + { + section: "Publish", + items: [ + { href: "/compose", label: "Compose", icon: "compose" }, + { href: "/calendar", label: "Calendar", icon: "calendar" }, + { href: "/streams", label: "Home", icon: "video" }, + ], + }, + { + section: "Content", + items: [ + { href: "/video", label: "Video library", icon: "video" }, + { href: "/files", label: "Files", icon: "files" }, + { href: "/archive", label: "Archive", icon: "archive" }, + { href: "/chat", label: "Chat", icon: "chat" }, + ], + }, + { + section: "Operations", + items: [ + { href: "/inbox", label: "Inbox", icon: "inbox" }, + { href: "/approvals", label: "Approvals", icon: "approvals" }, + { href: "/groups", label: "Groups", icon: "team" }, + { href: "/control-room", label: "Control Room", icon: "control" }, + { href: "/analytics", label: "Analytics", icon: "analytics" }, + { href: "/audit", label: "Audit trail", icon: "audit" }, + ], + }, + { + section: "Admin", + items: [ + { href: "/connections", label: "Connections", icon: "connections" }, + { href: "/team", label: "Team", icon: "team" }, + { href: "/settings", label: "Settings", icon: "settings" }, + ], + }, ]; const identity = (drafts: DraftRecord[]): DraftRecord[] => drafts; @@ -72,18 +93,16 @@ export function Sidebar(): React.ReactElement { );