diff --git a/CHECKPOINT-encryption.md b/CHECKPOINT-encryption.md index 81923f2..643445a 100644 --- a/CHECKPOINT-encryption.md +++ b/CHECKPOINT-encryption.md @@ -6,13 +6,14 @@ verified green at the moment this file was written. ## Where things are - Project: `/home/avi/Projects/Nostr_Keynctr` -- Git repo: `master` @ `fa5ba08` ("Security: upgrade nostr stack 0.40->0.45 - clearing 11 RustSec advisories"). Before it: `bf10ae3` (Checkpoint: clarify - NIP-42 status), `e210f17` (Checkpoint: DRY modal save lifecycle / R3), - `7098e75` (refactor: share modal save lifecycle), `e6a1efc` (R4), - `6d5063d` (R2), `1c428a9` (R1). -- Working tree: only the long-standing user changes remain — `concept3.svg` - deleted, `KeynectrAppIconPossibility02.jpeg` untracked. +- Git repo: `master` @ `835b0a0` ("frontend: list all profiles on Home, pin + dev host, widen profiles card"). Before it: `71e580f` (Trim white margins + from sidebar logo artwork), `f29d4f9` (Checkpoint 0.45 migration), + `fa5ba08` (Security: upgrade nostr stack 0.40->0.45), `bf10ae3`, `e210f17`, + `7098e75`, `e6a1efc`, `6d5063d`, `1c428a9`. +- Working tree: still DIRTY with the long-standing user changes — `concept3.svg` + deleted, `KeynectrAppIconPossibility02.jpeg` untracked. These are NOT part + of the frontend commit above and remain uncommitted. ## What was completed @@ -41,6 +42,7 @@ verified green at the moment this file was written. ## Commits added most recently +- `835b0a0` frontend: list all profiles on Home, pin dev host, widen profiles card - `fa5ba08` Security: upgrade nostr stack 0.40->0.45 clearing 11 RustSec advisories ## Verification commands run (all green) @@ -1349,3 +1351,76 @@ $B rename # rename + publish the new name - Relay config at time of writing (all enabled): nos.lol, relay.primal.net, l484.com (user's own), damus.io, snort.social, soloco.nl; relay.nostr.band added but was unreachable (handshake timeout) — retry enabling later. + +## Checkpoint — frontend home screen improvements (2026-08-26) + +A stopping point you can return to if this session is closed. + +### Where things are + +- Project: `/home/avi/Projects/Nostr_Keynctr` +- Git repo: `master`. **Working tree is DIRTY** — the changes below are local + edits and have NOT been committed yet (see "outstanding items"). +- Dev-run fix also applied: `frontend/vite.config.ts` now binds the dev server + to `host: '127.0.0.1'` so Electron (`NOSTR_GUI_DEV_URL=http://127.0.0.1:5173`) + and Vite agree on address and the window is no longer a white box. + +### What was completed (user-facing) + +1. **Profile pictures now show on Home.** The Home screen's active-profile + `Avatar` was missing the `picture` prop, so the image only appeared on the + Profiles screen. `HomeScreen.tsx` now passes `picture={profile.picture}`. +2. **All profiles listed on Home.** Replaced the single "Active profile" card + with a "Your profiles" card listing every profile (avatar + picture, label, + shortened npub, Active badge). Each non-active row has a working **Select** + button (calls `selectProfile`); a **Manage** link goes to the Profiles + screen and an **Add profile** button is at the bottom. +3. **`Badge` now accepts `className`** so the Active badge can be inline next to + the name. +4. **Relays card removed from Home.** The "Relays" card (live connection status, + refresh, "enable a relay" link) is no longer shown on Home — relay status + lives on the Relays screen. Removed the now-unused relay-check logic + (`relayTest`/`runCheck`/`statuses`/`testing`/`enabled`/`Spinner`) and the + corresponding test. +5. **Profile card now full-width on Home.** `.home-grid` was a two-column grid + (profiles + relays); with Relays gone the profiles card only filled the left + half. Changed it to a single `1fr` column and dropped the now-redundant + `@media (max-width: 900px)` override, so "Your profiles" matches the width + of "Most recent publication" below it. No JSX change. + +### Files changed + +- `frontend/src/screens/HomeScreen.tsx` (profiles list + picture prop + select; relays removed) +- `frontend/src/components/Badge.tsx` (add `className` prop) +- `frontend/src/styles.css` (`.home-profile-list` / `.home-profile-row` / etc.) +- `frontend/src/test/HomeScreen.test.tsx` (scope copy-button test to Alice's row; drop relay-status test) +- `frontend/vite.config.ts` (dev server `host: '127.0.0.1'`) + +### Verification commands run (all green) + +Frontend (`frontend/`): `npm run typecheck` clean; `npm run lint` clean; +`npm test` 15 files / 98 tests pass; `npm run format:check` clean (after +`npm run format`). + +### How to use / reproduce + +Dev mode (two terminals, from `~/Projects/Nostr_Keynctr/frontend`): + +``` +# Terminal 1 +npm run dev # prints Local: http://127.0.0.1:5173/ + +# Terminal 2 +NOSTR_GUI_DEV_URL=http://127.0.0.1:5173 npm start +``` + +Home now shows every profile with its picture, and selecting one there updates +the active profile immediately. + +### Outstanding items + +- Committed as `835b0a0`. The working tree still has unrelated user changes + (`concept3.svg` deleted, `KeynectrAppIconPossibility02.jpeg` untracked) that + were intentionally left out of the commit. +- The dev-server `host: '127.0.0.1'` change is intentional; if you later need + LAN access to the dev server, set it back to `true` and use the matching URL. diff --git a/frontend/src/assets/logo.png b/frontend/src/assets/logo.png index d02f1d6..fcb48ba 100644 Binary files a/frontend/src/assets/logo.png and b/frontend/src/assets/logo.png differ diff --git a/frontend/src/components/Badge.tsx b/frontend/src/components/Badge.tsx index b0c3748..265b6a7 100644 --- a/frontend/src/components/Badge.tsx +++ b/frontend/src/components/Badge.tsx @@ -2,6 +2,16 @@ import type { ReactNode } from 'react'; export type BadgeTone = 'neutral' | 'success' | 'warning' | 'danger' | 'info'; -export function Badge({ tone = 'neutral', children }: { tone?: BadgeTone; children: ReactNode }) { - return {children}; +export function Badge({ + tone = 'neutral', + className, + children, +}: { + tone?: BadgeTone; + className?: string; + children: ReactNode; +}) { + return ( + {children} + ); } diff --git a/frontend/src/screens/HomeScreen.tsx b/frontend/src/screens/HomeScreen.tsx index 31c38a3..da4a7f3 100644 --- a/frontend/src/screens/HomeScreen.tsx +++ b/frontend/src/screens/HomeScreen.tsx @@ -1,4 +1,4 @@ -import { useCallback, useEffect, useMemo, useState } from 'react'; +import { useState } from 'react'; import { Alert } from '../components/Alert'; import { Avatar } from '../components/Avatar'; import { Badge } from '../components/Badge'; @@ -6,67 +6,27 @@ import { Button } from '../components/Button'; import { CopyButton } from '../components/CopyButton'; import { EmptyState } from '../components/EmptyState'; import { Icon } from '../components/Icon'; -import { Spinner } from '../components/Spinner'; import { shortenNpub } from '../lib/format'; import type { Screen } from '../lib/navigation'; import { useApp } from '../state/AppProvider'; -type RelayStatus = 'checking' | 'connected' | 'failed'; - interface HomeScreenProps { onNavigate: (screen: Screen) => void; onCreateProfile: () => void; } export function HomeScreen({ onNavigate, onCreateProfile }: HomeScreenProps) { - const { state, relayTest, lastPublish } = useApp(); - const [statuses, setStatuses] = useState>( - {}, - ); - const [testing, setTesting] = useState(false); + const { state, lastPublish, selectProfile } = useApp(); + const [selecting, setSelecting] = useState(null); - const enabled = useMemo( - () => state?.settings.relays.filter((r) => r.enabled) ?? [], - [state?.settings.relays], - ); - - const runCheck = useCallback(async () => { - setTesting(true); - const entries: Record = {}; - for (const relay of enabled) { - entries[relay.url] = { state: 'checking' }; + const onSelect = async (npub: string) => { + setSelecting(npub); + try { + await selectProfile(npub); + } finally { + setSelecting(null); } - setStatuses({ ...entries }); - await Promise.all( - enabled.map(async (relay) => { - try { - const result = await relayTest(relay.url); - entries[relay.url] = { - state: 'connected', - detail: result.latency_ms != null ? `${result.latency_ms} ms` : 'connected', - }; - } catch (error) { - entries[relay.url] = { - state: 'failed', - detail: error instanceof Error ? error.message : String(error), - }; - } - }), - ); - setStatuses({ ...entries }); - setTesting(false); - }, [enabled, relayTest]); - - useEffect(() => { - if (enabled.length > 0) { - void runCheck(); - } else { - setStatuses({}); - } - }, [enabled, runCheck]); - - const connectedCount = Object.values(statuses).filter((s) => s.state === 'connected').length; - const failedCount = Object.values(statuses).filter((s) => s.state === 'failed').length; + }; const active = state?.active_profile ?? null; const shorten = state?.settings.shorten_npub ?? true; @@ -119,94 +79,72 @@ export function HomeScreen({ onNavigate, onCreateProfile }: HomeScreenProps) {
-

Active profile

-
-
- {active ? ( -
- -
- {active.label} - - {shortenNpub(active.npub, shorten)} - -
- -
- ) : ( -

- No profile selected.{' '} - {' '} - to get started. -

- )} -
-
- -
-
-

Relays

+

Your profiles

- {enabled.length === 0 ? ( -

- No relays enabled.{' '} - {' '} - to publish notes. -

- ) : testing && Object.keys(statuses).length === 0 ? ( - - ) : ( -
    - {enabled.map((relay) => { - const status = statuses[relay.url]; - return ( -
  • -
  • - ); - })} -
- )} - {failedCount > 0 && - !testing && - enabled.length > 0 && - Object.keys(statuses).length === enabled.length && ( -

- {connectedCount} of {enabled.length} enabled relays reachable. -

- )} +
    + {state?.profiles.map((profile) => ( +
  • + +
    + + {profile.label} + {profile.is_active && ( + + Active + + )} + + + {shortenNpub(profile.npub, shorten)} + +
    +
    + + {profile.is_active ? ( + + ) : ( + + )} +
    +
  • + ))} +
+
diff --git a/frontend/src/styles.css b/frontend/src/styles.css index 947ee5e..1dd6c1d 100644 --- a/frontend/src/styles.css +++ b/frontend/src/styles.css @@ -1003,16 +1003,10 @@ select { .home-grid { display: grid; - grid-template-columns: 1fr 1fr; + grid-template-columns: 1fr; gap: 20px; } -@media (max-width: 900px) { - .home-grid { - grid-template-columns: 1fr; - } -} - .active-profile-row { display: flex; align-items: center; @@ -1067,6 +1061,50 @@ select { object-fit: cover; } +.home-profile-list { + list-style: none; + margin: 0; + padding: 0; + display: flex; + flex-direction: column; + gap: 10px; +} + +.home-profile-row { + display: flex; + align-items: center; + gap: 14px; + padding: 8px; + border-radius: 8px; + border: 1px solid transparent; +} + +.home-profile-row.is-active { + background: var(--token-item-bg, rgba(0, 0, 0, 0.04)); + border-color: var(--token-border, rgba(0, 0, 0, 0.12)); +} + +.home-profile-row .active-profile-meta { + flex: 1; + min-width: 0; +} + +.home-profile-actions { + display: flex; + align-items: center; + gap: 8px; + flex-shrink: 0; +} + +.home-active-badge { + margin-left: 8px; + vertical-align: middle; +} + +.home-add-profile { + margin-top: 14px; +} + .relay-status-list { list-style: none; margin: 0; diff --git a/frontend/src/test/HomeScreen.test.tsx b/frontend/src/test/HomeScreen.test.tsx index 8b8572d..516c4b1 100644 --- a/frontend/src/test/HomeScreen.test.tsx +++ b/frontend/src/test/HomeScreen.test.tsx @@ -1,4 +1,4 @@ -import { screen, waitFor } from '@testing-library/react'; +import { screen, waitFor, within } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; import { HomeScreen } from '../screens/HomeScreen'; import { renderWithApp } from './render'; @@ -36,8 +36,8 @@ describe('HomeScreen', () => { renderHome(backend); renderWithApp(); - await screen.findByText('Alice'); - await userEvent.setup().click(screen.getByRole('button', { name: 'Copy full npub' })); + const aliceRow = (await screen.findByText('Alice')).closest('.home-profile-row') as HTMLElement; + await userEvent.setup().click(within(aliceRow).getByRole('button', { name: 'Copy full npub' })); await waitFor(() => { expect(backend.copied).toContain(ALICE); }); @@ -54,13 +54,4 @@ describe('HomeScreen', () => { .click(screen.getByRole('button', { name: /Create your first profile/i })); expect(onCreateProfile).toHaveBeenCalled(); }); - - it('shows live relay connection status from the backend', async () => { - const backend = createFakeBackend(); - renderHome(backend); - renderWithApp(); - - expect(await screen.findAllByText(/Connected/)).not.toHaveLength(0); - expect(screen.getAllByText('wss://relay.damus.io')).not.toHaveLength(0); - }); }); diff --git a/frontend/vite.config.ts b/frontend/vite.config.ts index d87dd77..4983c5f 100644 --- a/frontend/vite.config.ts +++ b/frontend/vite.config.ts @@ -6,6 +6,7 @@ import type { UserConfig } from 'vite'; export default defineConfig({ plugins: [react()], server: { + host: '127.0.0.1', port: 5173, strictPort: true, },