diff --git a/.impeccable/config.json b/.impeccable/config.json deleted file mode 100644 index 8894c7d..0000000 --- a/.impeccable/config.json +++ /dev/null @@ -1 +0,0 @@ -{"buildPath":"code"} diff --git a/.impeccable/design.json b/.impeccable/design.json deleted file mode 100644 index a5d23e2..0000000 --- a/.impeccable/design.json +++ /dev/null @@ -1,380 +0,0 @@ -{ - "schemaVersion": 2, - "generatedAt": "2026-08-27T18:00:17.873Z", - "title": "Design System: Nostr Keynctr", - "extensions": { - "colorMeta": { - "warm-ivory": { - "role": "neutral", - "displayName": "Warm Ivory", - "canonical": "#f6f4f0", - "tonalRamp": [ - "#1e1b15", - "#3d3830", - "#5c564e", - "#7c756d", - "#9c958d", - "#bdb9b3", - "#deddda", - "#f6f4f0" - ] - }, - "paper": { - "role": "neutral", - "displayName": "Paper", - "canonical": "#ffffff", - "tonalRamp": [ - "#1f1b15", - "#3e3a34", - "#5d5953", - "#7d7872", - "#9c9791", - "#bcb6b0", - "#deddda", - "#ffffff" - ] - }, - "soft-stone": { - "role": "neutral", - "displayName": "Soft Stone", - "canonical": "#f1ede7", - "tonalRamp": [ - "#1d1b18", - "#3c3936", - "#5b5854", - "#7a7772", - "#999690", - "#b8b5ae", - "#d7d4cc", - "#f1ede7" - ] - }, - "ink": { - "role": "neutral", - "displayName": "Graphite Ink", - "canonical": "#201b15", - "tonalRamp": [ - "#201b15", - "#4d4741", - "#7a756d", - "#a7a39e", - "#d4d1ce", - "#eceae8", - "#f5f4f3", - "#faf9f8" - ] - }, - "ink-muted": { - "role": "neutral", - "displayName": "Ink Muted", - "canonical": "#6d655a", - "tonalRamp": [ - "#2a2722", - "#403d38", - "#56544f", - "#6d655a", - "#8a857e", - "#a7a5a1", - "#c4c2be", - "#e1dfdc" - ] - }, - "line": { - "role": "neutral", - "displayName": "Line", - "canonical": "#e6e0d6", - "tonalRamp": [ - "#2c2a27", - "#4a4845", - "#686663", - "#868481", - "#a4a29f", - "#c2c0bd", - "#e0dedb", - "#e6e0d6" - ] - }, - "antique-brass": { - "role": "primary", - "displayName": "Antique Brass", - "canonical": "#6d5ae6", - "tonalRamp": [ - "#221b4a", - "#3a2d7a", - "#523faa", - "#6d5ae6", - "#8b7cff", - "#a99dff", - "#c7bdff", - "#efecfd" - ] - }, - "antique-brass-soft": { - "role": "primary", - "displayName": "Brass Wash", - "canonical": "#efecfd", - "tonalRamp": [ - "#221b4a", - "#3a2d7a", - "#523faa", - "#6d5ae6", - "#8b7cff", - "#c7bdff", - "#efecfd", - "#f8f7ff" - ] - }, - "signal-coral": { - "role": "neutral", - "displayName": "Signal Coral", - "canonical": "#c0392b", - "tonalRamp": [ - "#3a1512", - "#5a1f1b", - "#7a2a24", - "#9a352d", - "#c0392b", - "#d66a60", - "#e99a96", - "#fdecec" - ] - }, - "muted-sage": { - "role": "neutral", - "displayName": "Muted Sage", - "canonical": "#22713c", - "tonalRamp": [ - "#0f2a18", - "#143d22", - "#1a502c", - "#22713c", - "#4a8a5f", - "#73a382", - "#9cbca5", - "#e7f5ec" - ] - }, - "antique-gold": { - "role": "neutral", - "displayName": "Antique Gold", - "canonical": "#9a6b13", - "tonalRamp": [ - "#2e2110", - "#4a3418", - "#664720", - "#825a28", - "#9a6b13", - "#b88a3a", - "#d6aa62", - "#fcf3e0" - ] - }, - "clear-ink": { - "role": "neutral", - "displayName": "Clear Ink", - "canonical": "#2868a4", - "tonalRamp": [ - "#0f2538", - "#163a58", - "#1d4f78", - "#2868a4", - "#4d82b4", - "#739dc4", - "#99b8d4", - "#e9f2fb" - ] - } - }, - "typographyMeta": { - "display": { - "displayName": "Display", - "purpose": "Page titles (h1) only — 24px, weight 650." - }, - "headline": { - "displayName": "Headline", - "purpose": "Card and modal heads (h2) — 16px, weight 650." - }, - "body": { - "displayName": "Body", - "purpose": "Reading text — 15px / 1.5, max 65–75ch." - }, - "label": { - "displayName": "Label", - "purpose": "Field labels and hints — 13px / 600." - }, - "mono": { - "displayName": "Mono", - "purpose": "Code and npub — SFMono 13.8px." - } - }, - "shadows": [ - { - "name": "paper-lift", - "value": "0 1px 2px rgba(30,25,18,0.05), 0 8px 24px rgba(30,25,18,0.06)", - "purpose": "Default card lift on Warm Ivory." - }, - { - "name": "modal-lift", - "value": "0 12px 40px rgba(20,15,10,0.22)", - "purpose": "Modal over app." - }, - { - "name": "neon-glow", - "value": "0 0 32px rgba(255,20,147,0.12)", - "purpose": "Gated neon modal glow only on dark." - } - ], - "motion": [ - { - "name": "ease-standard", - "value": "0.12s ease", - "purpose": "Button, nav, border-color transitions." - }, - { - "name": "ease-glass", - "value": "blur 18px saturate(160%)", - "purpose": "Aurora glass panels." - } - ], - "breakpoints": [ - { - "name": "sidebar", - "value": "920px" - }, - { - "name": "card-grid", - "value": "280px" - } - ] - }, - "components": [ - { - "name": "Primary Button", - "kind": "button", - "refersTo": "button-primary", - "description": "Main action — Antique Brass on Paper.", - "html": "", - "css": ".ds-btn-primary{background:var(--primary);color:var(--on-primary);border:1px solid transparent;border-radius:9px;padding:10px 18px;font-weight:600;transition:background 0.12s ease} .ds-btn-primary:hover{background:var(--primary-hover)} .ds-btn-primary:focus-visible{outline:2px solid var(--focus);outline-offset:2px}" - }, - { - "name": "Secondary Button", - "kind": "button", - "refersTo": "button-secondary", - "description": "Paper button with strong line border.", - "html": "", - "css": ".ds-btn-secondary{background:var(--surface);color:var(--text);border:1px solid var(--border-strong);border-radius:9px;padding:10px 18px;font-weight:600} .ds-btn-secondary:hover{background:var(--surface-hover)}" - }, - { - "name": "Ghost Button", - "kind": "button", - "refersTo": "button-ghost", - "description": "Transparent for tertiary actions.", - "html": "", - "css": ".ds-btn-ghost{background:transparent;color:var(--text-muted);border:1px solid transparent;border-radius:9px;padding:7px 12px;font-weight:600} .ds-btn-ghost:hover{background:var(--surface-2);color:var(--text)}" - }, - { - "name": "Danger Button", - "kind": "button", - "refersTo": "button-danger", - "description": "Signal Coral wash for destructive actions.", - "html": "", - "css": ".ds-btn-danger{background:var(--danger-soft);color:var(--danger);border:1px solid transparent;border-radius:9px;padding:7px 12px;font-weight:600} .ds-btn-danger:hover{background:var(--danger);color:#fff}" - }, - { - "name": "Card", - "kind": "card", - "refersTo": "card", - "description": "Paper surface with soft lift.", - "html": "
Active profile
Profile content
", - "css": ".ds-card{background:var(--surface);border:1px solid var(--border);border-radius:14px;box-shadow:0 1px 2px rgba(30,25,18,0.05),0 8px 24px rgba(30,25,18,0.06);overflow:hidden} .ds-card-head{padding:16px 20px;border-bottom:1px solid var(--border);font-weight:650} .ds-card-body{padding:20px}" - }, - { - "name": "Input", - "kind": "input", - "refersTo": "input", - "description": "Paper field with strong line border.", - "html": "", - "css": ".ds-input{font:inherit;color:var(--text);background:var(--surface);border:1px solid var(--border-strong);border-radius:9px;padding:10px 12px;width:100%} .ds-input:focus{outline:none;border-color:var(--focus);box-shadow:0 0 0 3px color-mix(in srgb,var(--focus) 22%, transparent)}" - }, - { - "name": "Navigation Item", - "kind": "nav", - "refersTo": "nav-item", - "description": "Sidebar nav with muted → brass active.", - "html": "", - "css": ".ds-nav{display:flex;gap:12px;padding:11px 12px;border-radius:9px;border:none;background:transparent;color:var(--text-muted);font-weight:550} .ds-nav:hover{background:var(--surface-2);color:var(--text)} .ds-nav.is-active{background:var(--primary-soft);color:var(--primary)}" - }, - { - "name": "Undo Bar", - "kind": "custom", - "refersTo": "undo-bar", - "description": "Themed dismissal bar after delete — 5s, primary-soft.", - "html": "
Profile “Alice” deleted.—
", - "css": ".ds-undo{display:flex;gap:8px;align-items:center;flex-wrap:wrap;margin:12px 0;padding:10px 14px;background:var(--primary-soft);border:1px solid var(--border);border-radius:8px} .ds-undo-link{background:none;border:none;padding:0;color:var(--primary);font-weight:600;cursor:pointer;text-decoration:underline;text-underline-offset:2px}" - }, - { - "name": "Badge", - "kind": "chip", - "refersTo": "badge", - "description": "Pill tag for state.", - "html": "Active", - "css": ".ds-badge{display:inline-flex;gap:5px;padding:3px 10px;border-radius:999px;font-size:12px;font-weight:650;white-space:nowrap} .ds-badge.success{background:var(--success-soft);color:var(--success)}" - }, - { - "name": "Profile Edit Modal", - "kind": "custom", - "refersTo": "modal-profile-edit", - "description": "Paper modal for Rename / Picture / NIP-05 — Paper Lift, vault separation.", - "html": "

Edit profile — Alice

A

Public npub is visible to relays; private key never leaves Rust.

", - "css": ".ds-backdrop{position:fixed;inset:0;background:rgba(20,16,12,0.45);display:grid;place-items:center;padding:24px} .ds-modal{background:var(--surface);border:1px solid var(--border);border-radius:16px;box-shadow:0 12px 40px rgba(20,15,10,0.22);width:100%;max-width:520px;max-height:88vh;overflow:auto} .ds-modal-head{display:flex;align-items:center;justify-content:space-between;gap:12px;padding:18px 22px;border-bottom:1px solid var(--border);font-weight:650} .ds-modal-body{padding:20px 22px;display:flex;flex-direction:column;gap:16px} .ds-avatar-lg{width:52px;height:52px;border-radius:50%;display:grid;place-items:center;background:var(--primary-soft);color:var(--primary);font-weight:700;font-size:21px} .ds-muted{color:var(--text-muted);font-size:13px;margin:0} .ds-field{display:flex;flex-direction:column;gap:6px;font-size:13px;font-weight:600;color:var(--text)} .ds-modal-actions{display:flex;justify-content:flex-end;gap:10px;align-items:center;margin-top:4px} .ds-icon{width:34px;height:34px;border-radius:999px;border:1px solid var(--border);background:var(--surface)} [data-theme=\"glass\"] .ds-modal{backdrop-filter:blur(18px) saturate(160%);-webkit-backdrop-filter:blur(18px) saturate(160%)} [data-theme=\"neon\"] .ds-modal{box-shadow:0 12px 40px rgba(0,0,0,0.85),0 0 32px rgba(255,20,147,0.12)}" - } - ], - "narrative": { - "northStar": "Vault & Atelier", - "overview": "Nostr Keynctr is an atelier, not a dashboard — a warm, quiet workshop where identity work is done with care. The space feels like heavy paper and soft stone, with ink that is near-black, not pure black. Instruments are laid out plainly; nothing shouts for attention. Trust is built through precision: consistent edges, settled type, and state that is always legible.", - "keyCharacteristics": [ - "Warm and human, precise and calm", - "Paper and stone material language — tonal layering over heavy shadows", - "Semantic tokens only — every color has a job", - "Operate density — tasks complete faster because hierarchy is consistent", - "Vault logic made visible — locked/muted states are distinct at a glance" - ], - "rules": [ - { - "name": "The One Accent Rule", - "body": "Antique Brass is the only saturated accent in the restrained palette. Its rarity is the point — use it for primary actions and active state only.", - "section": "colors" - }, - { - "name": "The Vault Separation Rule", - "body": "Secret-dependent UI never shares color with public data: private operations are flagged via muted-sage/signal-coral and explicit Approve/Reject.", - "section": "colors" - }, - { - "name": "The Weight Restraint Rule", - "body": "Only headings are 650. Do not add semibold to body or emphasize muted text with weight.", - "section": "typography" - }, - { - "name": "The Flat-By-Default Rule", - "body": "Surfaces are flat at rest. Shadows appear only as a response to layer (card on canvas, modal over app).", - "section": "elevation" - } - ], - "dos": [ - "Do keep Warm Ivory as the only page canvas on light", - "Do use Muted Sage wash only for successful security confirmation", - "Do keep one primary per screen — Antique Brass is rare by design", - "Do use var(--focus) for every focus ring; keep the 2px + 2px offset invariant", - "Do preserve the side-by-side rate: cards on Paper with 1px Line borders before adding any shadow" - ], - "donts": [ - "Don't use Signal Coral outside error/destructive states", - "Don't introduce a new accent hue outside the four semantic washes", - "Don't bump heading weight beyond 650 or add bold to muted text", - "Don't apply neon glow outside data-theme='neon' modals", - "Don't invent a new radius — step on 9px/14px/16px/999px only", - "Don't replace the 5s auto-dismiss undo bar with a permanent modal" - ] - } -} \ No newline at end of file diff --git a/CHECKPOINT-encryption.md b/CHECKPOINT-encryption.md index c73e18e..da13251 100644 --- a/CHECKPOINT-encryption.md +++ b/CHECKPOINT-encryption.md @@ -1,29 +1,27 @@ -# Checkpoint — Impeccable themes + ProfileEditModal (2026-08-27) +# Checkpoint — fix profile delete/undo + themed auto-dismiss bar (2026-08-27) ## Where things are - Project: `/home/avi/Projects/Nostr_Keynctr` -- Git repo: `master` @ `8366af7` ("feat: add Impeccable themes (light + dark) + unified ProfileEditModal"). Before it: `832e114` (checkpoint: fix delete/undo), `9e635e7` (fix delete/undo bar), `56c2a6d` (click profile row), `8130c3e` etc. -- Working tree: clean except `.opencode/` (impeccable skill install, not tracked) and `KeynectrAppIconPossibility02.jpeg` untracked (long-standing). +- Git repo: `master` @ `9e635e7` ("fix: restore profile delete/undo and themed auto-dismiss undo bar"). Before it: `56c2a6d` (checkpoint: click profile row), `8130c3e` (click profile row on Home), `a5838ea`, `98f4520` etc. +- Working tree: clean except `KeynectrAppIconPossibility02.jpeg` untracked (long-standing, not part of commits). ## What was completed -1. **Fixed broken profile delete/undo** (previous). `src/ipc.rs` missing `DeleteProfile`/`UndoDelete`; added handlers returning `state_view`; exposed `profiles::delete_profile` outside tests; `api.ts`/`AppProvider` now `call` via `applyState`; `fakeBackend` delete/undo. -2. **Themed auto-dismiss undo bar.** Replaced permanent white bar with `var(--primary-soft)` + `var(--primary)` link `Undo and restore profile`, 5s `useEffect` watching `lastDeleted`, shown in both empty/populated states. -3. **Impeccable themes (light + dark).** `src/settings.rs`: `Theme::Impeccable` + `ImpeccableDark` (serde `impeccable-dark`); `types.ts` union extended; `styles.css` added `:root[data-theme='impeccable']` (oklch 97% lacquer light, kinpaku gold `oklch(77% .13 82)`, Alumni Sans 300) and `impeccable-dark` (oklch 15% lacquer-deep, champagne text), editorial refinements (uppercase labels, 8/3px radii, nav left-border active, card offset bar); `SettingsScreen.tsx` adds both options with live `var(--*)` swatches. -4. **Unified ProfileEditModal.** `frontend/src/components/ProfileEditModal.tsx` — Paper Lift modal (`var(--surface)`/`var(--border)`/`16px`/`0 12px 40px`), 3 tabs (Name/Picture/NIP-05) sharing `renameProfile`/`setProfilePicture`/`setNip05`; `ProfilesScreen.tsx` wires `Edit profile` (secondary) alongside legacy ghosts; `DESIGN.md` + `PRODUCT.md` + `.impeccable/design.json` from `impeccable document` (Vault & Atelier, warm ivory/charcoal/coral, 9 primitives). +1. **Fixed broken profile delete/undo.** `src/ipc.rs` was missing `DeleteProfile`/`UndoDelete` request variants and handlers, so `delete_profile` from renderer returned `Unexpected signer request`. Added `DeleteProfile {npub}`/`UndoDelete` to `Request`, handlers that call `profiles::delete_profile`/`app.undo_delete()`, save vault, push/pop `undo_history`, return `state_view()`. Exposed `profiles::delete_profile` outside `#[cfg(test)]` (was only inside tests). Updated `src/profiles.rs` accordingly. CLI `delete_profile_direct` kept for `main.rs`. +2. **Wired frontend to backend state.** `frontend/src/lib/api.ts`: `deleteProfile`/`undoDelete` now `call` (was `ProfileSummary`). `frontend/src/state/AppProvider.tsx`: both now use `applyState(...)` and type `Promise`; added `clearLastDeleted` that locally slices `undo_history` for UI dismissal. +3. **Themed auto-dismiss undo bar.** `frontend/src/screens/ProfilesScreen.tsx`: replaced permanent white bar (`var(--token-item-bg, #f0f0f0)` + "Profile deleted. You can restore it until you delete another or quit.") with themed bar (`var(--primary-soft)` bg, `var(--border)` border) showing `Profile "X" deleted. — Undo and restore profile` where the link uses `var(--primary)` (adapts to light/dark/neon/glass). Bar appears in both empty and populated states, auto-dismisses after 5s via `useEffect` watching `lastDeleted` (previous version fired unconditionally on mount). Fake backend updated to simulate delete/undo for tests. ## Commits added in this session (newest first) -- `8366af7` feat: add Impeccable themes (light + dark) + unified ProfileEditModal -- `832e114` checkpoint: fix delete/undo + themed auto-dismiss bar - `9e635e7` fix: restore profile delete/undo and themed auto-dismiss undo bar ## Verification commands run (all green) -- Rust (repo root): `cargo fmt --check` clean; `cargo clippy --all-targets` 2 pre-existing warnings; `cargo test` 115 passed; `cargo build --release` success -- Frontend (`frontend/`): `npm test` 15 files / 99 tests passed; `npm run typecheck` clean; `npm run lint` clean; `npm run format:check` clean (after `npm run format`); `npm run build` success; `npm run electron:build` success -- Wayland `--ozone-platform` / `has no handler` messages on `electron:build` are harmless. +- Rust: `cargo fmt --check` clean; `cargo clippy --all-targets` 2 pre-existing warnings; `cargo test` 115 passed; `cargo build --release` success +- Frontend (`frontend/`): `npm run typecheck` clean; `npm run lint` clean; `npm run format:check` clean (after `npm run format`); `npm test` 15 files / 99 tests passed; `npm run build` success; `npm run electron:build` success +- The `--ozone-platform=wayland` Vulkan and `has no handler with id` messages seen on `electron:build` are harmless Electron/Wayland warnings, not profile errors. ## How to resume / reproduce -GUI (Impeccable): `cargo build --release && cd frontend && npm run build && npm run electron:build && npm start` (or dev: `npm run dev` in one terminal + `NOSTR_GUI_DEV_URL=http://localhost:5173 npm start` in second). Settings → Appearance → `Impeccable — Light` / `Impeccable — Dark` (gold on ivory lacquer vs lacquer-deep). Profiles → Delete → see themed undo bar 5s; Edit profile → tabbed modal. CLI: `cargo run -- settings set theme impeccable` / `impeccable-dark`. +GUI: `cargo build --release && cd frontend && npm run build && npm run electron:build && npm start` (or `npm run dev`). Create 2 profiles, delete one via Profiles → Delete → confirm, see themed undo bar for 5s with "Undo and restore profile" in primary color, click it to restore, or wait for auto-dismiss. +CLI: `cargo run -- delete-profile ` then `cargo run -- undo-delete` (in-memory undo only within same `serve` session; CLI undo across processes is not persisted). ## Outstanding / next-step items -- Undo restores with empty `secret_key` (stores `ProfileSummary`); needs `StoredProfile` in `undo_history` for full secret recovery. -- `.opencode/` skill files and `KeynectrAppIconPossibility02.jpeg` remain untracked; not part of theme commits. +- Undo restores profile with empty `secret_key` (`StoredProfile { secret_key: "" }`) — existing design stores `ProfileSummary` in `undo_history` so secret cannot be recovered. Needs full `StoredProfile` in undo stack if secret preservation is required. +- `KeynectrAppIconPossibility02.jpeg` remains untracked; `concept3.svg` deletion already committed earlier. diff --git a/DESIGN.md b/DESIGN.md deleted file mode 100644 index b496f5c..0000000 --- a/DESIGN.md +++ /dev/null @@ -1,263 +0,0 @@ ---- -name: Nostr Keynctr -description: Local-first Nostr identity atelier — keys stay in Rust, the UI stays calm and warm. -colors: - warm-ivory: "#f6f4f0" - soft-stone: "#f1ede7" - stone-hover: "#faf8f5" - paper: "#ffffff" - ink: "#201b15" - ink-muted: "#6d655a" - line: "#e6e0d6" - line-strong: "#d8d0c3" - antique-brass: "#6d5ae6" - antique-brass-hover: "#5c49d4" - antique-brass-soft: "#efecfd" - on-brass: "#ffffff" - signal-coral: "#c0392b" - signal-coral-soft: "#fdecec" - antique-gold: "#9a6b13" - antique-gold-soft: "#fcf3e0" - muted-sage: "#22713c" - muted-sage-soft: "#e7f5ec" - clear-ink: "#2868a4" - clear-ink-soft: "#e9f2fb" - focus: "#6d5ae6" -typography: - display: - fontFamily: "system-ui, -apple-system, 'Segoe UI', Roboto, Ubuntu, Cantarell, 'Noto Sans', sans-serif" - fontSize: "24px" - fontWeight: 650 - lineHeight: 1.25 - letterSpacing: "normal" - headline: - fontFamily: "system-ui, -apple-system, 'Segoe UI', Roboto, Ubuntu, Cantarell, 'Noto Sans', sans-serif" - fontSize: "16px" - fontWeight: 650 - lineHeight: 1.25 - letterSpacing: "normal" - body: - fontFamily: "system-ui, -apple-system, 'Segoe UI', Roboto, Ubuntu, Cantarell, 'Noto Sans', sans-serif" - fontSize: "15px" - fontWeight: 400 - lineHeight: 1.5 - letterSpacing: "normal" - label: - fontFamily: "system-ui, -apple-system, 'Segoe UI', Roboto, Ubuntu, Cantarell, 'Noto Sans', sans-serif" - fontSize: "13px" - fontWeight: 600 - lineHeight: 1.5 - letterSpacing: "normal" - mono: - fontFamily: "'SFMono-Regular', Consolas, 'Liberation Mono', Menlo, monospace" - fontSize: "13.8px" - fontWeight: 400 - lineHeight: 1.5 - letterSpacing: "normal" -rounded: - sm: "9px" - md: "14px" - lg: "16px" - pill: "999px" -spacing: - xs: "8px" - sm: "12px" - md: "16px" - lg: "20px" - xl: "32px" -components: - button-primary: - backgroundColor: "{colors.antique-brass}" - textColor: "{colors.on-brass}" - rounded: "{rounded.sm}" - padding: "10px 18px" - button-primary-hover: - backgroundColor: "{colors.antique-brass-hover}" - textColor: "{colors.on-brass}" - rounded: "{rounded.sm}" - padding: "10px 18px" - button-secondary: - backgroundColor: "{colors.paper}" - textColor: "{colors.ink}" - rounded: "{rounded.sm}" - padding: "10px 18px" - button-ghost: - backgroundColor: "transparent" - textColor: "{colors.ink-muted}" - rounded: "{rounded.sm}" - padding: "7px 12px" - button-danger: - backgroundColor: "{colors.signal-coral-soft}" - textColor: "{colors.signal-coral}" - rounded: "{rounded.sm}" - padding: "7px 12px" - card: - backgroundColor: "{colors.paper}" - textColor: "{colors.ink}" - rounded: "{rounded.md}" - padding: "18px" - input: - backgroundColor: "{colors.paper}" - textColor: "{colors.ink}" - rounded: "{rounded.sm}" - padding: "10px 12px" ---- - -# Design System: Nostr Keynctr - -## Overview - -**Creative North Star: "Vault & Atelier"** - -Nostr Keynctr is an atelier, not a dashboard — a warm, quiet workshop where identity work is done with care. The space feels like heavy paper and soft stone, with ink that is near-black, not pure black. Instruments are laid out plainly; nothing shouts for attention. Trust is built through precision: consistent edges, settled type, and state that is always legible. The product truth — keys never leave Rust — is mirrored visually: the UI is restrained, the material is honest, and every destructive or security-relevant moment is given deliberate weight. - -The aesthetic is *warm and human*, not technical or bold. Density is Operate: scannable lists, clear hierarchies, and generous but not loose spacing (8/12/16/20/32). The four themes (light, dark, glass/Aurora, neon) share the same semantic roles; only the material values shift. Neon and glass are gated expressions, never the default. - -**Key Characteristics:** -- Warm and human, precise and calm — humanity first, then exactness. -- Paper and stone material language — tonal layering over heavy shadows. -- Semantic tokens only — every color has a job (public data vs. private operation vs. relay health vs. security). -- Operate density — tasks complete faster because hierarchy is consistent. -- Vault logic made visible — locked/muted states are distinct at a glance. - -## Colors - -Four themes share the same semantic roles; values below are the light (paper) baseline extracted from `frontend/src/styles.css` (`:root` / `:root[data-theme='light']`). Dark, neon, and glass override the same tokens (see Elevation notes for glass treatment). - -### Primary -- **Antique Brass — restrained identity accent** (`{colors.antique-brass}` #6d5ae6): Used for primary actions, active nav (`is-active`), focus rings, and the themed undo bar link ("Undo and restore profile"). Restrained — ≤10% of any screen. Hover is `{colors.antique-brass-hover}` #5c49d4; wash is `{colors.antique-brass-soft}` #efecfd. -- **Focus Violet** (`{colors.focus}` #6d5ae6): Same value as primary; outlines via `outline: 2px solid var(--focus)`. - -### Neutral -- **Warm Ivory — canvas** (`{colors.warm-ivory}` #f6f4f0): `var(--bg)` — page background. -- **Paper — surface** (`{colors.paper}` #ffffff): `var(--surface)` — cards, sidebar, modals. -- **Soft Stone — raised/tinted surface** (`{colors.soft-stone}` #f1ede7): `var(--surface-2)` — inline code, secondary surfaces, segmented control track, `create-explainer`. -- **Stone Hover — hover wash** (`{colors.stone-hover}` #faf8f5): `var(--surface-hover)`. -- **Ink — primary text** (`{colors.ink}` #201b15): `var(--text)` — near-black, never pure black. -- **Ink Muted — secondary text** (`{colors.ink-muted}` #6d655a): `var(--text-muted)` — subtitles, hints, `.muted`, dates. -- **Line — hairline border** (`{colors.line}` #e6e0d6): `var(--border)`. -- **Line Strong — emphasized border** (`{colors.line-strong}` #d8d0c3): `var(--border-strong)` — input strokes, secondary buttons. - -### Semantic Feedback -- **Muted Sage — secure/success** (`{colors.muted-sage}` #22713c / wash #e7f5ec): active profile border, success badges/alerts. Not decorative — only for successful security-state confirmation. -- **Signal Coral — destructive/warning/error** (`{colors.signal-coral}` #c0392b / wash #fdecec): danger buttons, error alerts, relay bad dots. Destructive actions live here alone. -- **Antique Gold — caution** (`{colors.antique-gold}` #9a6b13 / wash #fcf3e0): warning badges/alerts. -- **Clear Ink — information/relay** (`{colors.clear-ink}` #2868a4 / wash #e9f2fb): info badges/alerts, relay neutral states. -- **On Brass — text on accent** (`{colors.on-brass}` #ffffff): label inside primary buttons. - -**Theme overrides (same roles, different material):** Dark inverts to ink surfaces (`#211d29`, `#2a2534`) with primary `#8b7cff`; Neon uses pure black panels (`#000000`) and Cyber Pink primary `#ff1493` + Aurora Purple/Yellow highlights; Glass (Aurora) uses translucent `rgba(23,29,41,0.58)` surfaces with `backdrop-filter: blur(18px) saturate(160%)` and cyan primary `#7fdbff`. Tokens stay semantically identical, so components do not branch per theme. - -### Named Rules -**The One Accent Rule.** Antique Brass is the only saturated accent in the restrained palette. Its rarity is the point — use it for primary actions and active state only. - -**The Vault Separation Rule.** Secret-dependent UI never shares color with public data: private operations are flagged via `muted-sage`/`signal-coral` and explicit Approve/Reject, never via primary alone. - -## Typography - -**Display Font:** System UI stack (`system-ui, -apple-system, 'Segoe UI', Roboto, Ubuntu, Cantarell, 'Noto Sans', sans-serif`) — no custom display face; trust is in weight and rhythm, not ornament. -**Body Font:** Same System UI stack — continuity over contrast. -**Label/Mono Font:** `SFMono-Regular, Consolas, Liberation Mono, Menlo, monospace` — code and `npub` only. - -**Character:** Humanist, quiet, and precise. Weight 650 for headings is the only emphasis; body is regular 15/1.5 for long-form readability without dense compaction. - -### Hierarchy -- **Display** (650, 24px, 1.25): `h1` page titles only — one per screen. -- **Headline** (650, 16px, 1.25): `h2` card heads, modal titles. -- **Title** (650, 16px, 1.25): Profile/cluster names (`.profile-name`, `.profile-card-meta h3`). -- **Body** (400, 15px, 1.5): All reading text, `p`, `li`, `.page-subtitle` (14px muted variant). Max width ~65–75ch in cards. -- **Label** (600, 13px): Field labels, `.hint`, `.char-count`, `.field > label`. -- **Mono** (400, 13.8px / 0.92em): `code`, `.mono`, npub displays — always on `Soft Stone` pill. - -### Named Rules -**The Weight Restraint Rule.** Only headings are 650. Do not add semibold to body or emphasize muted text with weight — color shift (`ink` → `ink-muted`) is the emphasis. - -## Layout - -**Canvas:** Centered column (`max-width: 920px`, `margin: 0 auto`) with screen padding `32px 40px 56px` (collapses proportionally on narrow viewports). Inner stack via `.screen-inner` (`gap: 20px`, `flex-direction: column`). - -**Shell:** Fixed sidebar `248px` + flexible `.main` (`overflow-y: auto`). Sidebar is paper on light, translucent blurred pane on glass. App is `height: 100vh` locked; only main scrolls. - -**Grids:** Home is single-column (`gap: 20px`). Profiles use responsive `repeat(auto-fill, minmax(280px, 1fr))` (`gap: 16px`). Relays are vertical list (`gap: 12px`). Compose card keeps tabs above editor; preview stacks below. - -**Spacing rhythm:** 8/12/16/20/32. Section gaps 20px; card internal padding 18–20px; button padding `10px 18px` (md) / `7px 12px` (sm); input padding `10px 12px`. No arbitrary values — step on the scale. - -**Responsive:** Flex-wrap at page heads and action rows; segmented controls wrap; profile grid collapses to single column; modal max-width `520px`, max-height `88vh`. - -## Elevation & Depth - -System is **flat-by-default, tonal before lifted.** Depth is conveyed first by material contrast (Warm Ivory → Paper → Soft Stone) and 1px `Line` borders. Shadows are whisper-low and never the primary separation. - -### Shadow Vocabulary -- **Paper Lift** (`box-shadow: 0 1px 2px rgba(30,25,18,0.05), 0 8px 24px rgba(30,25,18,0.06)`): Cards, profile cards — the default lift for paper surfaces on ivory. -- **Modal Lift** (`box-shadow: 0 12px 40px rgba(20,15,10,0.22)`): Modals — only overlay that earns a large, soft shadow. On dark: `0 12px 40px rgba(0,0,0,0.6)`. -- **Neon Glow (gated)** (`0 0 32px rgba(255,20,147,0.12)` paired with dark shadows): Only in `data-theme='neon'` modals — never on light/paper. - -**Glass/Aurora exception:** Panels become translucent (`rgba(23,29,41,0.58)`, `rgba(255,255,255,0.05)`) with `backdrop-filter: blur(18px) saturate(160%)`; body carries three radial gradients (cyan/purple/aqua washes) to create depth through blur, not shadow. - -### Named Rules -**The Flat-By-Default Rule.** Surfaces are flat at rest. Shadows appear only as a response to layer (card on canvas, modal over app). Never add shadow to inline controls or navigation. - -## Shapes - -Form language is *softly squared with a pill exception.* Corners are gentle, never sharp, never fully rounded except for tags and avatars. - -- **Soft square** (`border-radius: var(--radius)` 14px): Cards, modals (16px), empty states — the workhorse. -- **Compact square** (`border-radius: var(--radius-sm)` 9px): Buttons, inputs, alerts, segmented control, nav items. -- **Pill** (`border-radius: 999px`): Badges, `npub-chip`, toggles, compose tabs, segmented buttons. -- **Circle** (`50%`): Avatars (42px / 52px lg), toggles thumb. -- **Empty-state dash** (`border: 1.5px dashed var(--border-strong)`): Deliberately unfinished — signals "bring your own." -- **Glass blur** is shape-adjacent: panels keep the same radii but gain `backdrop-filter` to soften edges without changing geometry. - -## Components - -### Buttons -- **Shape:** Compact square (9px), `font-weight: 600`, `gap: 8px`. -- **Primary (Antique Brass on Paper):** `background: var(--primary)` / `color: var(--on-primary)` / `padding: 10px 18px` (md) or `7px 12px` (sm). Hover → `var(--primary-hover)`. Loading shows spinner in `rgba(255,255,255,0.4)` track. -- **Secondary:** `background: var(--surface)` / `border: 1px solid var(--border-strong)` / `color: var(--text)` / Hover → `var(--surface-hover)`. -- **Danger:** `background: var(--danger-soft)` / `color: var(--danger)` / Hover → `var(--danger)` on white. -- **Ghost:** `background: transparent` / `color: var(--text-muted)` / Hover → `var(--surface-2)` + `var(--text)`. -- Focus everywhere: `outline: 2px solid var(--focus)` `offset: 2px`. - -### Chips / Badges -- **Style:** Pill (999px), `padding: 3px 10px`, `font-size: 12px` / `weight: 650`, neutral uses `var(--surface-2)` + `var(--border)`, semantic variants use `-soft` washes. - -### Cards / Containers -- **Corner Style:** Soft square (14px). -- **Background:** `var(--surface)` (or translucent on glass). -- **Shadow Strategy:** Paper Lift by default; see Elevation. -- **Border:** `1px solid var(--border)` (active profile card upgrades to `var(--success)`). -- **Internal Padding:** `18px` (profile card) / `20px` (card-body). - -### Inputs / Fields -- **Style:** `background: var(--surface)` / `border: 1px solid var(--border-strong)` / `radius: 9px` / `padding: 10px 12px` / `font: inherit`. -- **Focus:** `border-color: var(--focus)` + `box-shadow: 0 0 0 3px color-mix(in srgb, var(--focus) 22%, transparent)`. -- **Error:** `.field-error` in `var(--danger)` 13px / 500. -- **Hint:** `.hint` in `var(--text-muted)` 13px. - -### Navigation (Sidebar) -- **Style:** Sidebar `248px`, `background: var(--surface)`, `border-right: 1px solid var(--border)`. Brand block with `38px` white tile (inverted `filter: invert(1)` on dark/neon/glass). -- **Nav item:** `padding: 11px 12px` / `radius: 9px` / `font-weight: 550` / `color: var(--text-muted)` → hover `var(--surface-2)` + `var(--text)` → active `var(--primary-soft)` + `var(--primary)`. -- **Footer:** Profile row + top border `1px solid var(--border)`. - -### Undo Bar (Signature Component) -- **Shape:** Compact square (`8px`), `padding: 10px 14px`, `border: 1px solid var(--border)`, `background: var(--primary-soft)`. -- **Character:** Warm, not alarming — confirms deletion without shouting. Label in `var(--text)` 500, dash in `var(--text-muted)`, action as themed link. -- **Action:** Inline ` - ))} - - - {tab === 'name' && ( -
- - setLabel(e.target.value)} - placeholder="My Profile" - autoFocus - /> -
- - -
-
- )} - - {tab === 'picture' && ( -
- - setPictureUrl(e.target.value)} - placeholder="https://…/avatar.png" - /> - - {nipProblem && {nipProblem}} -
- {target.picture && ( - - )} - - -
-
- )} - - {tab === 'nip05' && ( -
- - setNip05Value(e.target.value)} - placeholder="name@domain.com" - /> - {nipProblem && nip05.trim() && {nipProblem}} -

- Also serve /.well-known/nostr.json on your domain — see{' '} - docs/NIP-05.md. -

-
- {target.nip05 && ( - - )} - - -
-
- )} - - - ); -} diff --git a/frontend/src/lib/types.ts b/frontend/src/lib/types.ts index 0d44331..08455e1 100644 --- a/frontend/src/lib/types.ts +++ b/frontend/src/lib/types.ts @@ -1,4 +1,4 @@ -export type Theme = 'light' | 'dark' | 'glass' | 'neon' | 'impeccable' | 'impeccable-dark'; +export type Theme = 'light' | 'dark' | 'glass' | 'neon'; /** Lifecycle of the NIP-46 remote signer. */ export type SignerPhase = 'stopped' | 'connecting' | 'connected'; diff --git a/frontend/src/screens/ProfilesScreen.tsx b/frontend/src/screens/ProfilesScreen.tsx index fdd796b..f74c483 100644 --- a/frontend/src/screens/ProfilesScreen.tsx +++ b/frontend/src/screens/ProfilesScreen.tsx @@ -7,7 +7,6 @@ import { EmptyState } from '../components/EmptyState'; import { ErrorText } from '../components/ErrorText'; import { Icon } from '../components/Icon'; import { Modal } from '../components/Modal'; -import { ProfileEditModal } from '../components/ProfileEditModal'; import { ShowSecretKeyModal } from '../components/ShowSecretKeyModal'; import { formatDate, shortenNpub } from '../lib/format'; import type { MetadataPublishReport } from '../lib/types'; @@ -35,12 +34,6 @@ export function ProfilesScreen({ onCreateProfile }: ProfilesScreenProps) { const [pictureTarget, setPictureTarget] = useState(null); const [renameTarget, setRenameTarget] = useState<{ npub: string; label: string } | null>(null); const [nip05Target, setNip05Target] = useState(null); - const [editTarget, setEditTarget] = useState<{ - npub: string; - label: string; - picture?: string | null; - nip05?: string | null; - } | null>(null); const profiles = state?.profiles ?? []; const shorten = state?.settings.shorten_npub ?? true; @@ -251,20 +244,6 @@ export function ProfilesScreen({ onCreateProfile }: ProfilesScreenProps) { > Publish name -