From 60ff82b4a221e0c75377e5a95b0f5c7e161762ff Mon Sep 17 00:00:00 2001 From: Jason Tudisco Date: Wed, 27 May 2026 21:45:21 -0600 Subject: [PATCH 01/13] =?UTF-8?q?design(kez-chat/web):=20redesign=20founda?= =?UTF-8?q?tion=20=E2=80=94=20tactical-terminal=20theme=20+=20tokens?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Phase 0 of the redesign (see DESIGN.md). Establishes the visual foundation; route restyling + IA reorg follow in subsequent commits. Design direction (decided with a 3-agent design-team debate): • Audience: hackers, privacy absolutists, anti-surveillance, Meshtastic / off-grid, journalists in hostile environments. • Aesthetic: "muted tactical terminal" — Mullvad-calm restraint, not neon cyberpunk cosplay. Monospace as identity. Hard-ish edges. • Signature color: electric cyan #28C8E8 on neutral near-black #0B0C0E (chosen over signal-amber and phosphor-green — ages better, reads "serious infrastructure" without shouting). Verified-green reserved for proofs only. Changes: • app.css: full Tailwind v4 @theme token set — elevation ramp, text tiers, accent + dim + contrast, semantic colors, Inter + JetBrains Mono via Google Fonts, tactical radius scale, accent glow, dark color-scheme, cyan text-selection, thin dark scrollbars, and the kez-cursor blink keyframe (respects prefers-reduced-motion). • Wordmark.svelte: `kez▌` mono wordmark with blinking cyan block cursor — the cursor is the brand mark. • Avatar.svelte: deterministic 5×5 symmetric identicon from the ed25519 key, cyan-arc hue. Every KEZ gets a stable face. • kez-icon.svg: amber key → cyan key-meets-cursor glyph; regenerated the full PWA icon set + apple-touch-icon from it. • manifest + index.html theme/background color → #0B0C0E. • DESIGN.md: the full system + IA plan as source of truth. Note: existing route components still use light-theme utility classes and will look inconsistent until restyled in the next phases — that work lands next (shell/nav → Chats → Identity → Settings → Contacts). Co-Authored-By: Claude Opus 4.7 --- kez-chat/web/DESIGN.md | 167 ++++++++++++++++++ kez-chat/web/index.html | 2 +- .../web/public/apple-touch-icon-180x180.png | Bin 749 -> 718 bytes kez-chat/web/public/kez-icon.svg | 23 ++- kez-chat/web/public/maskable-icon-512x512.png | Bin 2497 -> 2452 bytes kez-chat/web/public/pwa-192x192.png | Bin 917 -> 853 bytes kez-chat/web/public/pwa-512x512.png | Bin 2636 -> 2454 bytes kez-chat/web/public/pwa-64x64.png | Bin 429 -> 408 bytes kez-chat/web/src/app.css | 122 ++++++++++++- kez-chat/web/src/lib/Avatar.svelte | 73 ++++++++ kez-chat/web/src/lib/Wordmark.svelte | 18 ++ kez-chat/web/vite.config.ts | 4 +- 12 files changed, 386 insertions(+), 23 deletions(-) create mode 100644 kez-chat/web/DESIGN.md create mode 100644 kez-chat/web/src/lib/Avatar.svelte create mode 100644 kez-chat/web/src/lib/Wordmark.svelte diff --git a/kez-chat/web/DESIGN.md b/kez-chat/web/DESIGN.md new file mode 100644 index 0000000..e605e63 --- /dev/null +++ b/kez-chat/web/DESIGN.md @@ -0,0 +1,167 @@ +# KEZ Design System + +> Source of truth for the kez-chat redesign (`redesign-kez-theme` branch). +> Goal: take the chat app out of the prototype phase into a real, +> WhatsApp/Discord-caliber messenger with an iconic KEZ identity. + +## Who we're designing for + +Hackers, infosec people, privacy absolutists, anti-surveillance / sovereignty +folks, Meshtastic & off-grid comms operators, journalists/activists in hostile +environments — the Signal / Briar / Tails / Mullvad / Monero crowd. They trust +**verifiability over promises**, have a finely-tuned bullshit detector, and +bounce instantly from anything that smells like VC surveillance-ware. + +**Positioning:** _KEZ is the sovereign identity layer + encrypted comms for +people who assume the network is hostile._ + +## Aesthetic direction + +**Muted tactical terminal — restraint, not neon cosplay.** Mullvad's calm +authority. Monospace as identity. The first 3 seconds should feel like opening +an operational tool, not a brochure. Hard-ish edges, visible structure, a single +cold accent. No gradients-blobs, no mascots, no "delightful." + +### Hard DO-NOTs +- No rounded-blob/gradient SaaS look, no mascots, no illustrations of laughing people. +- No "military-grade / bank-level" marketing adjectives. Show, don't boast. +- No surveillance tells: no third-party analytics, no social login, no email-required signup. +- No stock photography. + +## Color palette (dark-first; light theme = v2, out of scope) + +Tailwind v4 `@theme` tokens, in `src/app.css`. + +| Token | Hex | Use | +|---|---|---| +| `--color-bg` | `#0B0C0E` | app background (neutral near-black) | +| `--color-surface` | `#16181C` | cards, conversation list, sidebars | +| `--color-elevated` | `#1E2127` | modals, menus, input wells | +| `--color-border` | `#2A2E35` | hairlines, dividers | +| `--color-text` | `#E8EAED` | primary text (neutral off-white) | +| `--color-text-secondary` | `#9BA3AD` | secondary | +| `--color-text-muted` | `#5C636D` | timestamps, meta | +| `--color-text-disabled` | `#3A4049` | disabled | +| `--color-accent` | `#28C8E8` | **the KEZ color** — electric cyan | +| `--color-accent-dim` | `#1B9DBC` | hover/pressed, accent borders | +| `--color-accent-contrast` | `#04131A` | text on accent fills | +| `--color-verified` | `#4ADE80` | proof verified (distinct from accent) | +| `--color-danger` | `#FF5C6C` | destructive, failed | +| `--color-warning` | `#FFB13D` | needs-attention | +| `--color-bubble-recv` | `#1B1F25` | received message bubble fill | + +Accent is used surgically: send bubbles, focus rings, active nav, the wordmark +cursor, links, live/streaming indicators. Greys carry the weight. **Verified +green is for proofs only** — never as a general accent, so a verification badge +never camouflages into accent UI. + +## Typography + +- **UI/body:** `Inter` — `--font-sans: "Inter", system-ui, -apple-system, "Segoe UI", Roboto, sans-serif` +- **Monospace:** `JetBrains Mono` — `--font-mono: "JetBrains Mono", ui-monospace, "SF Mono", Menlo, monospace`. Used for keys, hashes, handles, the wordmark. + +Loaded via Google Fonts. + +| Role | Size / weight / line-height | Notes | +|---|---|---| +| Wordmark | 22px / 700 / 1.1 mono | `-0.02em`, + cyan block cursor | +| Section header | 12px / 600 / 1.3 sans | uppercase, `+0.08em`, secondary color | +| Body / message | 15px / 450 / 1.45 sans | | +| Conversation name | 15px / 600 / 1.3 sans | | +| Timestamp/meta | 12px / 500 / 1.2 | muted | +| Mono key display | 13px / 500 / 1.5 mono | `+0.01em` | + +## Spacing / radius / shadow + +- **Spacing** (4px base): 4, 8, 12, 16, 20, 24, 32, 48. +- **Radius — tactical, mid-soft:** `--radius-sm: 4px` (chips/badges), `--radius-md: 8px` (inputs/buttons), `--radius-lg: 12px` (bubbles/cards), `--radius-xl: 16px` (modals). Fully-round reads consumer-soft; sharp reads unfinished; 8–12 says "engineered." +- **Shadows = terminal glow, not ambient drop shadows.** Surfaces use `1px solid --color-border` hairlines. Modals: `0 8px 24px -8px rgba(0,0,0,0.6)`. Accent focus/glow: `0 0 0 1px #28C8E833, 0 0 16px -2px #28C8E866`. + +## Signature components + +- **Message bubbles** — radius `lg`, padding `8px 12px`, max-width ~78%. + - Sent: `--color-accent` fill, `--color-accent-contrast` text, `border-bottom-right-radius: 4px` tail. + - Received: `--color-bubble-recv` fill, `--color-text`, `1px solid --color-border`, `border-bottom-left-radius: 4px`. +- **Conversation row** — 64px tall, 40px avatar (`radius-md`), name 600, preview secondary, time muted. Active = left 2px accent bar + elevated bg. Unread = accent dot + name 700. +- **Buttons** — height 40px, `radius-md`, 600. Primary: accent fill / contrast text / dim hover / 0.98 active / glow focus. Secondary: transparent, `1px solid border`, hover elevated bg + accent-dim border. +- **Inputs** — elevated bg, `1px solid border`, `radius-md`, focus → accent border + `0 0 0 3px #28C8E822` ring. Key inputs use mono. +- **Handle/identity chip** — inline mono, `radius-sm`, `2px 8px`, bg `#28C8E814`, `1px solid #28C8E833`, accent text; leading `@`/`0x` muted. +- **Verified proof badge** — `radius-sm`, bg `#4ADE8014`, border `#4ADE8040`, `--color-verified` text, mono 11/600, leading ✓. +- **Avatars** — deterministic identicon generated from the ed25519 key, so every KEZ has a stable face. Eliminates the biggest "prototype" tell. + +## Motion + +Fast (120–200ms), `cubic-bezier(0.2,0.8,0.2,1)`, on state change only, respect +`prefers-reduced-motion`. +- Received message: slide-up 6px + fade. +- Sent message: spring `scale .96→1` + brief accent-glow pulse decaying ~600ms. +- Thread push/back slide on mobile. +- Tasteful terminal flourish: a single cyan block-cursor blink on the empty + compose field + after the wordmark. No content scanlines. + +## Wordmark / icon + +- **Wordmark:** `kez` lowercase, JetBrains Mono 700, `-0.02em`, primary text, + followed by a blinking cyan block cursor `▌`. The cursor is the brand mark. +- **Icon:** evolve the amber key → cyan. Recolor `public/kez-icon.svg` stroke + `#fbbf24` → `#28C8E8` on `#0B0C0E`; reshape so the key reads as a + key-meets-cursor glyph. Drop the literal 🔑 emoji everywhere. Regenerate PWA + icon set. Manifest `theme_color` + `background_color` → `#0B0C0E`. + +## Information architecture (the big structural change) + +**Land logged-in users on Chats, not a Dashboard.** The "dashboard" as a +destination is killed; its contents redistribute. + +### Navigation — 4 destinations +| Destination | Purpose | +|---|---| +| **Chats** | conversation list + threads (the home) | +| **Contacts** | known KEZs + verification status + start-new-chat | +| **Identity** | your KEZ + claims/proofs (the superpower surface) | +| **Settings** | security, backup, notifications, account, about | + +- **Mobile (PWA):** fixed bottom tab bar, 4 tabs, unread badge on Chats. + Thread view pushes full-screen with a back chevron. +- **Desktop:** slim left icon rail (4 destinations) + secondary list column + + main content pane. Replaces the current top nav bar entirely. + +### Feature → new home +| Existing | New home | +|---|---| +| Landing/Create/Restore/Unlock | unauthenticated flow (pre-nav), restyled | +| Messages (list+thread+compose, SSE, emoji, unread, notifications) | **Chats** (default surface) | +| Start chat by handle | **Contacts → New chat** (preview card before opening) | +| Claims list | **Identity → My proofs** (grouped verified/failed/pending) | +| AddClaim | **Identity → Add proof** | +| Identity display (handle@server, ed25519 key, registry) | **Identity** header card (avatar, copyable KEZ, fingerprint, QR) | +| Seed/key backup | **Settings → Security → Recovery phrase** (re-auth gated) | +| Biometric/passkey | **Settings → Security → App lock** | +| Notifications perm + test | **Settings → Notifications** | +| Build SHA / source link | **Settings → About** | + +### New-conversation flow (the KEZ moment) ++ FAB on Chats (mobile) / "New chat" in Contacts column (desktop) → +"Enter a KEZ" (`handle@server`, paste/QR) → `lookup` → **preview card**: +avatar, handle, key fingerprint, and **inline verified proofs** (✓ github:you, +✓ dns:yourdomain) → "Message". Verification is always one tap from a thread via +the contact-detail header. You see who someone is before you trust them. + +### Polish signals to ship +1. Identicon avatars everywhere (from ed25519 key). +2. Message status ticks (sent / SSE-delivered) + day separators. +3. Real empty + skeleton-loading states. +4. Verification shield badge system (green verified / neutral none / amber failed), consistent across Chats, Contacts, Identity. +5. Native push/back + send transitions; smooth auto-scroll (already shipped). + +## Implementation phases + +0. **Foundation** — `app.css` Tailwind v4 `@theme` tokens, Google Fonts, recolor icon + regenerate PWA assets, manifest colors. +1. **Shell + nav** — bottom tab bar / left rail, router lands on `/chats`, wordmark component. +2. **Chats** — restyle list + thread + bubbles, identicon avatars, empty/skeleton states. Keep SSE/emoji/notifications/auto-scroll. +3. **Identity** — identity card + proofs (migrate Claims/AddClaim). +4. **Settings** — Account / Security / Notifications / About (migrate Dashboard remainder). +5. **Contacts** — list + new-chat preview card with proofs. +6. **Auth flow restyle** — Landing/Create/Restore/Unlock to the new theme. + +All existing functionality is preserved; only its placement and presentation change. diff --git a/kez-chat/web/index.html b/kez-chat/web/index.html index e6be98d..87d855b 100644 --- a/kez-chat/web/index.html +++ b/kez-chat/web/index.html @@ -22,7 +22,7 @@ - +
diff --git a/kez-chat/web/public/apple-touch-icon-180x180.png b/kez-chat/web/public/apple-touch-icon-180x180.png index 02d42b2d96d1e6ea485bd70f7ab277d5254d39bf..4230fc221fa8a42cc626a9ffe40746c482f1aa06 100644 GIT binary patch delta 668 zcmaFMdX9C1rUo|;-~a#rHBP*cYo9BwsG6IbFXQB`ID3m^W|hjO^Ve_OSg~TIC~tS> z#6XGq08bakkcwMx@7$f#tiZ$iAjHX`=KuG*b6=i+(H9!NxZu|0(_f!Sn>9T6a)Nh5 zOU9y3wOJU%dgtkivsPJ5Uv+fhoMyd?bFCL1f7GWJ=(qmto8A}PC;lhoJMn9YPvwhy zp&=gj#W6nX-jZq8&O~3iG^et@xr*1;qgASJVfQtMY|ECuprwxlOD1}S@-0Z#ExFUb z&g_D=v#u}Sms0`yi?+%|eP5(qcD$(m#7pV!rPVX zdhwTU#>!4HutOb6>J}IaRpkr%np|`d1`+4yIp{9`w>{_tThq2Hy{pdG-?F>)>s>x; zuJqcKvsd3Ss!E$7pDvOsmV5X1tF3`wnr^z+GDWHX6(~BFct>}o>y~eeQ{R0({=icD z@5RJ8^CBa26tmqf1Vc#Mh z=3G4gjw65GdYg64+orDk_U6@wm@>`!UqAoHSMLj3$h7PJ*C->s-2$><*S4H};qZlh z8~YQNmY7cp=He%EWj?oZe!431NuZ(fPLs_Izx4tlPq-3yg*L}rONi@EjB`ke`Y@}!WfJWFdAzy z8%<#``p0UV!ESVk!`Oz)Xc@P$0IzWspYaoZV?ROT9YV%RA|^ee#^1zD5`G-}lZiDk c*t7m+{<=QctbR)2B?chyboFyt=akR{0Ql2D;s5{u delta 700 zcmX@d`j&NqriP$|`v3p`fA3eRO;y#?*AMeGtFCXqxKL$vo9emqmtJjFdHv=s>zVio z69Xmc^F3W0Ln>~)y>qju*?@;NK-4?##Q*>K%VjrhJ=l<}qgniQ%FD}!qQ`C=UaH^j z>cGrvash*|<%>-cNqZG@Mkv{mJvOQQo$%j=YW-atbK`<`Ez+OZ7P9Ayv-mQ{qf5oD zE*Dw<%4AI)xthXfODesnyg-d1E zmMt{bGnIVv;_SM0iz4~YuFW|5ZpC-@n{WE6>T6`8OfFvPNb@x@U&Q%hF)NzOwuGA$ zzyJKa_@c}wo4;YF9x>}?R;{tT;a)uNUzq;4%ohfGR~?yFf3dPEcJmF_<(XTXLccsN zofmx0KUrY*iw^~_b$xZd7oN!x)YVV#{+%;PXVK+TA%az=Cpa@A=iYN#?p@1dwRU^n z3*+W5?%r$JgQk1mm7klv?sdlv39HcjZC4iC?^;~>^2INad58X=`uTJE2ZJw{7Dmx12n#N4j#~-4uB^vyx@i z!r9x7MwG4H;gjcZ>f61JgKO{Y+*Y=6*UEpF?|wZv(`MfdtB6C67t>`O-R`M^gWEpI;z5eWIJ);l(k>N`CUV ze3rC*Zv5nY`kY1QA9&3dTxfSVV(zhfR=g7boc^Ij2b4R%=^tBrT`)sAEAt`-1_n+B MPgg&ebxsLQ04Bm%t^fc4 diff --git a/kez-chat/web/public/kez-icon.svg b/kez-chat/web/public/kez-icon.svg index ab40845..a840b06 100644 --- a/kez-chat/web/public/kez-icon.svg +++ b/kez-chat/web/public/kez-icon.svg @@ -1,17 +1,14 @@ - - - - + + + + - - - - - - + + + + + diff --git a/kez-chat/web/public/maskable-icon-512x512.png b/kez-chat/web/public/maskable-icon-512x512.png index eedd1dd2a6dbb017768a5db91e36feb8ab1d2e36..fd5e99702c6f8f35b3730978a12078da89899cfd 100644 GIT binary patch literal 2452 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 delta 886 zcmcc0HkEyXPQ9F>nt(V62}-C6N~r(duM*^@c6hopSsQ%Xdo?4aDb!^-B34hls znl9PDqsyyl`}W!0@DAtKr0dmkBJ) z3ht{jV;q#6_!k^@oFcZs;{@}I1Odm=1Do|aA{sdEG1l)^Si~r|+u;HO({6T~``k)$ z3Q7G7+6||O)gSP<5u9Mp`G8SSP+;Tp4|QU~2GScG*ce#^92l??|MeZN=;S9Z>tesK z=bh4Q2F4@T)F+CvC{)^Z1h!-|MEfgsmp457YIDW>z_sa#^B7rw<{v(xzd++ZV^f$z z;=lLeCQM)a=iRUVAU2Ktd~?0of#iR?*M7gafRFL#o?iKD;f3rQrvIMGJDW|HK~v(9 zRhGb3hBYZ&uNLTKFnHV)TAaLV0b@d|Z?-d#4%oBri6D2NtwZ;B4cU#euQI=LbNg8~ zQI@f&a&y+sQiTO+xsn_BLYY@ZnXV64x?Rn<^RaXJ@68pa2eQJ%_HtFDd_s9HwWB4~Q&t}2r$6qQOp39p&W}I6e=I}CqsX3Wmex|Tt-TKJ?e)bjt| zIx~iS4NNth+<%mmKBUGx;C}gFx>1w98f*MH?)?j-YVtj;XRMmi?#lY`wACZ=Ye)2R zx#DBQ_V3ZJYfSwkbp40xv5%+s(>X=t!0a&%WR0JbL#1X;)j&;_*c#`^nS%$&W)z4*}Q$iB}+f9WC diff --git a/kez-chat/web/public/pwa-512x512.png b/kez-chat/web/public/pwa-512x512.png index 8da9eec03e38499791ce33e639e871db939c4dc2..76f63f005f26cdd70539421779d9f388f3c83b33 100644 GIT binary patch literal 2454 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 d`i0rKSyy0>WrV+DD3|~M002ovPDHLkV1iH + // Deterministic identicon avatar derived from a KEZ identity (the + // ed25519 primary, or any stable string). Same identity → same glyph, + // forever, on every device. A 5×5 vertically-symmetric grid (GitHub + // style) rendered in a cyan-family hue picked from the hash, on a dark + // tile. Gives every KEZ a stable "face" in lists/headers/previews — + // the biggest single fix for the prototype look. + + interface Props { + /** Stable seed — usually the ed25519 primary "ed25519:". */ + seed: string; + /** Rendered size in px. */ + size?: number; + /** Optional ring (e.g. for the active/own avatar). */ + ring?: boolean; + } + let { seed, size = 40, ring = false }: Props = $props(); + + // Cheap, stable 32-bit FNV-1a hash — no crypto needed, just spreading. + function hash(str: string): number { + let h = 0x811c9dc5; + for (let i = 0; i < str.length; i++) { + h ^= str.charCodeAt(i); + h = Math.imul(h, 0x01000193); + } + return h >>> 0; + } + + const h = $derived(hash(seed || "kez")); + + // Hue restricted to the cool cyan→teal→blue arc so avatars stay on-brand + // (160–210°), with controlled saturation/lightness for legibility on dark. + const hue = $derived(160 + (h % 50)); + const fg = $derived(`hsl(${hue} 70% 62%)`); + const tile = $derived(`hsl(${hue} 28% 14%)`); + + // Build a 5×5 grid; mirror columns 0↔4, 1↔3 for symmetry. Cell on/off + // from successive bits of the hash (re-mixed per cell so it's not too + // sparse/dense). + const cells = $derived.by(() => { + const out: boolean[] = []; + for (let i = 0; i < 15; i++) { + // 15 unique cells (3 columns × 5 rows), mirrored to 25. + const bit = (Math.imul(h ^ (i * 0x9e3779b1), 0x85ebca6b) >>> 28) & 1; + out.push(bit === 1); + } + return out; + }); + + function isOn(col: number, row: number): boolean { + const c = col < 3 ? col : 4 - col; // mirror + return cells[c * 5 + row]; + } + + + + + {#each [0, 1, 2, 3, 4] as col} + {#each [0, 1, 2, 3, 4] as row} + {#if isOn(col, row)} + + {/if} + {/each} + {/each} + diff --git a/kez-chat/web/src/lib/Wordmark.svelte b/kez-chat/web/src/lib/Wordmark.svelte new file mode 100644 index 0000000..1ace0e8 --- /dev/null +++ b/kez-chat/web/src/lib/Wordmark.svelte @@ -0,0 +1,18 @@ + + + + kez{#if cursor}{/if} + diff --git a/kez-chat/web/vite.config.ts b/kez-chat/web/vite.config.ts index bea44c2..5d1f83f 100644 --- a/kez-chat/web/vite.config.ts +++ b/kez-chat/web/vite.config.ts @@ -50,8 +50,8 @@ export default defineConfig({ start_url: "/", scope: "/", display: "standalone", - background_color: "#111827", - theme_color: "#111827", + background_color: "#0b0c0e", + theme_color: "#0b0c0e", categories: ["social", "communication"], icons: [ { src: "pwa-64x64.png", sizes: "64x64", type: "image/png" }, From 40ebd63ed7883a800c2997544b61fe0d6c753231 Mon Sep 17 00:00:00 2001 From: Jason Tudisco Date: Wed, 27 May 2026 21:50:10 -0600 Subject: [PATCH 02/13] 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 @@ } -
- -