From 40ebd63ed7883a800c2997544b61fe0d6c753231 Mon Sep 17 00:00:00 2001 From: Jason Tudisco Date: Wed, 27 May 2026 21:50:10 -0600 Subject: [PATCH] design(kez-chat/web): new IA + nav shell, Chats/Identity/Settings (phase 1-2) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Kills the dashboard-as-home. Logged-in users now land on Chats like a real messenger. Implements the design-team's IA recommendation. Navigation: • Desktop: slim left icon rail (Chats / Identity / Settings) with the cyan key-cursor logo, active-state accent bar, unread badge. • Mobile: fixed bottom tab bar, same 3 destinations, safe-area inset. • Unauthenticated flow renders full-bleed with a wordmark header. • Legacy /dashboard + /messages redirect to /identity + /chats. Chats (restyled Messages): • Two-pane on desktop; on mobile the list is full-screen and the thread pushes over it with a back chevron. • Conversation rows get identicon avatars, active accent bar, truncated previews. Thread header shows avatar + handle + key. • Bubbles: sent = cyan accent fill / near-black text / tail; received = dark bubble-recv + hairline border. Emoji-only boost retained. • Compose + "start a chat" inputs use the dark token styling; live/ reconnecting status moved into the list header. • All functionality preserved: SSE, emoji picker, auto-scroll, notifications, unread badge. Identity (new — was Dashboard's identity + claims): • Identity card: identicon avatar (ring), copyable handle@server chip, key fingerprint, registration date. • Proofs grouped verified / failed / pending with verified-green badges; Add proof + Manage links. Settings (new — was Dashboard's remainder): • Security (app lock / biometric, reveal seed), Notifications (perm + test), Account (lock + build sha + source). Dashboard.svelte is now unused (left in tree, removed from routes; cleanup later). Claims/AddClaim + auth pages (Landing/Create/Restore/ Unlock) still use the old light classes — restyle is the next phase. Co-Authored-By: Claude Opus 4.7 --- kez-chat/web/src/App.svelte | 156 ++++++++++------- kez-chat/web/src/routes/Identity.svelte | 186 ++++++++++++++++++++ kez-chat/web/src/routes/Messages.svelte | 216 ++++++++++-------------- kez-chat/web/src/routes/Settings.svelte | 185 ++++++++++++++++++++ 4 files changed, 552 insertions(+), 191 deletions(-) create mode 100644 kez-chat/web/src/routes/Identity.svelte create mode 100644 kez-chat/web/src/routes/Settings.svelte diff --git a/kez-chat/web/src/App.svelte b/kez-chat/web/src/App.svelte index 0445c4c..736b89a 100644 --- a/kez-chat/web/src/App.svelte +++ b/kez-chat/web/src/App.svelte @@ -4,14 +4,16 @@ import { hasStoredIdentity } from "./lib/identity-store.js"; import { session } from "./lib/store.svelte.js"; import { inboxService } from "./lib/inbox-service.svelte.js"; + import Wordmark from "./lib/Wordmark.svelte"; import Landing from "./routes/Landing.svelte"; import CreateAccount from "./routes/CreateAccount.svelte"; import Restore from "./routes/Restore.svelte"; import Unlock from "./routes/Unlock.svelte"; - import Dashboard from "./routes/Dashboard.svelte"; + import Identity from "./routes/Identity.svelte"; import Claims from "./routes/Claims.svelte"; import AddClaim from "./routes/AddClaim.svelte"; + import Settings from "./routes/Settings.svelte"; import Messages from "./routes/Messages.svelte"; const routes = { @@ -19,83 +21,109 @@ "/create": CreateAccount, "/restore": Restore, "/unlock": Unlock, - "/dashboard": Dashboard, + "/chats": Messages, + "/identity": Identity, "/claims": Claims, "/claims/add": AddClaim, - "/messages": Messages, + "/settings": Settings, }; - // First-load: if there's a stored identity but session is locked, - // bounce to /unlock. If no stored identity and on a protected page, - // bounce to /. + // App routes show the nav chrome; everything else (auth flow) is full-bleed. + const APP_ROUTES = ["/chats", "/identity", "/claims", "/claims/add", "/settings"]; + const showNav = $derived(!!session.unlocked && APP_ROUTES.includes($location)); + onMount(async () => { const stored = await hasStoredIdentity(); - const protectedRoutes = ["/dashboard", "/claims", "/claims/add", "/messages"]; - if (!stored && protectedRoutes.includes($location)) { + // Redirect legacy paths. + if ($location === "/dashboard") return push(session.unlocked ? "/identity" : "/unlock"); + if ($location === "/messages") return push(session.unlocked ? "/chats" : "/unlock"); + if (!stored && APP_ROUTES.includes($location)) { push("/"); - } else if (stored && !session.unlocked && protectedRoutes.includes($location)) { + } else if (stored && !session.unlocked && APP_ROUTES.includes($location)) { push("/unlock"); } }); + + // Nav destinations — Chats / Identity / Settings. + const nav = [ + { path: "/chats", label: "Chats", badge: true }, + { path: "/identity", label: "Identity", badge: false }, + { path: "/settings", label: "Settings", badge: false }, + ]; + + function isActive(path: string): boolean { + if (path === "/identity") return $location === "/identity" || $location.startsWith("/claims"); + return $location === path; + } -
-
+ {/each} + -
- -
+ +
+
+ +
+
- +{:else} + +
+
+
+ +
+
+
+ +
+
+{/if} diff --git a/kez-chat/web/src/routes/Identity.svelte b/kez-chat/web/src/routes/Identity.svelte new file mode 100644 index 0000000..8e17437 --- /dev/null +++ b/kez-chat/web/src/routes/Identity.svelte @@ -0,0 +1,186 @@ + + +{#if session.unlocked} +
+ +
+
+ +
+
+ + {session.unlocked.handle}@{session.unlocked.server} + + +
+

Key fingerprint

+

+ {fingerprint(session.unlocked.primary)} +

+ {#if registryRecord} +

+ Registered {new Date(registryRecord.registered_at).toLocaleDateString()} +

+ {/if} +
+
+
+ + +
+
+
+

Proofs

+

+ Other accounts cryptographically linked to your KEZ. Anyone can + verify these without trusting the server. +

+
+
+ {#if claims.length > 0} + + {/if} + + + Add proof + +
+
+ + {#if claims.length === 0} +
+

No proofs yet.

+

+ Link GitHub, your domain, nostr, Bluesky — prove the accounts you control. +

+
+ {:else} +
    + {#each verified as c (c.id)} +
  • +
    +
    + + {channelLabel(c.channel)} + + {c.envelope.payload.subject} + ✓ verified +
    +
    + {#if c.last_verify?.evidence_url} + proof ↗ + {/if} +
  • + {/each} + {#each failed as c (c.id)} +
  • + {channelLabel(c.channel)} + {c.envelope.payload.subject} + ✗ failed +
  • + {/each} + {#each pending as c (c.id)} +
  • + {channelLabel(c.channel)} + {c.envelope.payload.subject} + pending +
  • + {/each} +
+ Manage proofs → + {/if} +
+
+{/if} diff --git a/kez-chat/web/src/routes/Messages.svelte b/kez-chat/web/src/routes/Messages.svelte index ace91e6..0236e49 100644 --- a/kez-chat/web/src/routes/Messages.svelte +++ b/kez-chat/web/src/routes/Messages.svelte @@ -6,6 +6,7 @@ import { lookup, ApiError } from "../lib/api.js"; import { inboxService } from "../lib/inbox-service.svelte.js"; import EmojiButton from "../lib/EmojiButton.svelte"; + import Avatar from "../lib/Avatar.svelte"; import { appendOutbound, ensureConversation, @@ -245,210 +246,171 @@ } -
- -