Commit graph

108 commits

Author SHA1 Message Date
Avi
41d4a60336 chore(deps): lockfile refresh from update_apply (yoke-derive 0.8.4, npm tree refresh) 2026-10-01 20:07:35 -05:00
Avi
d09c4ec1f0 feat(signer): interactive kind scope in the approval prompt (Step 4 finish)
'Always allow…' on a sign_event request now opens an inline kind editor
prefilled with the request's own kind, so the standing grant's scope is
chosen while the user sees the event. Approved with grant_kinds, the
backend records exactly the edited scope (normalised); without them the
fallback stays the request's own kind. Both approval UIs (Signer and
Signer Mode) share the parseKindsInput helper; the bunker status JSON now
carries pending 'details' so the legacy screen can prefill too.

Also fixes a latent bug: AppProvider.signerApprove dropped the 'always'
argument, so the legacy 'Always allow' button never actually recorded a
grant.
2026-10-01 13:31:36 -05:00
Avi
e8d2abbd1b feat(signer): inline kind-scope editing for always-allow grants
The 'Always-allow permissions' card on the Signer screen now edits an
existing grant's event-kind scope via the signer_grant_update RPC:
Edit toggles an input (comma-separated kinds, client-validated), Save
applies, Revert restores. Grants list is left alone by the 5s poll so
an open editor is never yanked out from under the user.
2026-10-01 10:36:30 -05:00
Avi
d7cf2a5fda feat(signer): grant kind-editing backend (vault + IPC + api plumbing)
Step 4 remainder, first half: Vault::update_signer_grant_kinds replaces a
standing grant's kind scope (sorted, deduped; empty = all kinds, a
deliberate broadening matching legacy semantics). New signer_grant_update
IPC + api/AppProvider/fakeBackend plumbing. The Signer-screen editor UI
is the next unit; no UI surface calls the RPC yet.
2026-09-30 15:03:05 -05:00
Avi
eea6f0e6b1 feat(assets): white monochrome launcher icon
The app icon is a dark glyph on transparency — invisible on dark launcher
backgrounds. icon-white.png keeps the exact silhouette/alpha with opaque
pixels recolored white; generated by make_icon_white.py. Used by the
desktop entry (launcher/taskbar); in-app artwork unchanged.
2026-09-30 12:18:07 -05:00
Avi
7891ccce1a docs: Step 7 rename/hygiene pass
- package.json homepage: github.com/avi/Keynctr -> git.atitlan.io/avi/Keynctr
- README: title 'Nostr Feed Manager' -> 'Keynctr', resolve all
  [YOUR_FORGEJO_INSTANCE_URL]/<OWNER>/<REPO> placeholders with the real
  Forgejo URL, fix clone dir and packaged-binary name (nost-feed-manager
  -> keynectr), data dir default ~/.local/share/keynectr, refresh test
  counts (cargo 225+6 e2e, npm 139/19 files) and project layout
  (audit/bunker/feed/updates modules, signer/ dir, Feed+SignerMode screens)
- PRODUCT.md: data dir corrected with migration note
2026-09-30 11:57:49 -05:00
Avi
fac4ba3cde feat(desktop): launch from the applications list (folio-style)
- launch-keynctr.sh: builds renderer/electron main if missing, exports
  KEYNCTR_ENABLE_GPU=1 (software rendering dies 'GPU process isn't
  usable' on this Hyprland box), execs bundled electron with
  --class=keynectr for WM_CLASS grouping.
- keynctr.desktop installed at ~/.local/share/applications/ (validated,
  icon = public/icon.png, categories Utility, StartupWMClass keynectr).
- requestSingleInstanceLock: second launch focuses the existing window
  (app.exit(0) in the doomed instance) instead of a duplicate shell
  fighting the vault.
Verified via gtk-launch: 'keynectr | Keynctr' window maps; second
gtk-launch keeps exactly 1 window.
2026-09-30 09:35:25 -05:00
Avi
d792a72dc2 feat(theme): Archipelago uses the reference artwork as the canvas
First pass was abstract gradient washes; the reference mock is the
synthwave sun/tower illustration itself behind frosted-glass panels.
Now: real artwork (public/archipelago-bg.jpg from the user's reference)
fills .main cover/fixed under a dark scrim, cards + sidebar are
translucent blurred glass with light hairline borders, coral accent and
is-active nav pill kept. Verified computed background stack in browser;
139 frontend tests + build green.
2026-09-30 09:35:10 -05:00
Avi
4d0df31726 feat(theme): Archipelago — synthwave sunset sea from reference art
Teal-navy dusk, coral scanline sun (subtle repeating-linear horizon
stripes + low-center sun glow + pink cloud banks, fixed attachment),
glassmorphic cards/sidebar (blur, translucent surfaces), coral primary
accent, white logo filter on the dark canvas. Parsed as 'archipelago'
in settings; Settings -> Appearance option 'Archipelago - Sunset Sea'.
Verified tokens + all 6 rules compute in the built bundle; 221+6 Rust,
139 frontend tests, clippy 0, electron:build green.
2026-09-30 09:11:55 -05:00
Avi
a6182e3cb2 feat(theme): iOS blue accent for Workshop Dark + custom accent color setting
Workshop Dark accent is now #007AFF (hover #4da3ff, soft #0f2a44, white labels), including the ambient wash tint. New Settings -> Appearance -> Accent color: a color picker plus hex field that overrides primary/focus for ANY theme, with a 'Use theme default' reset. Persisted in settings.json (accent_color), validated server-side (#rrggbb, empty clears), painted over the theme via inline CSS custom properties with automatic hover/soft mixes and luminance-based label contrast. Verified live: .btn-primary renders rgb(0,122,255)/white under workshop-dark. 220 Rust unit + 6 e2e, 139 frontend tests (4 new), clippy 0, all gates green, release rebuilt.
2026-09-28 10:17:03 -05:00
Avi
dcc701f88b feat(updater): apply updates without restarting the app
New app:selfupdate IPC (main process): npm run build + cargo build --release with the augmented PATH, then kill the backend child, reset the spawn flag so the next request starts the NEW binary, and reloadIgnoringCache every window. The Electron shell keeps running — no manual restart. Settings install now triggers it automatically when restart_required. Honest limits: a change to the Electron main process itself still needs one manual relaunch, and packaged builds report that bundle replacement is the update path.
2026-09-28 09:26:48 -05:00
Avi
4cc04812fe feat(theme): white logo mark on workshop-dark
The logo PNG is dark ink art; invert+brighten it on the dark workshop material (same technique cosmic uses) so it reads as warm paper ink on #12110f. Nav icons are currentColor and already themed. Verified filter present in built bundle.
2026-09-28 09:17:30 -05:00
Avi
de804c8fc5 feat(theme): Workshop atmosphere — Moi's graph-paper grid + mint/clay washes
The Moi dark material is not just tokens: site.css paints a 24px hairline grid (rgba(235,230,220,0.035)) plus pine and clay radial washes over the paper. Applied the identical background stack to .main under both workshop themes (fixed attachment), with per-theme --wk-grid/--wk-wash-mint/--wk-wash-clay/--wk-copper tokens from Moi's light and dark blocks. Sidebar stays a clean opaque surface. Verified against the live Moi site in a browser: both render body #12110f with grid:true, wash:true, pine #7eb89a, copper #d4a574.
2026-09-28 09:10:00 -05:00
Avi
c18f59ad6c feat(theme): Workshop — Dark, the Moi dark material
Moi DESIGN.md dark column: paper #12110f, surface #1c1a17, stone #26221c, warm ink #ebe6dc, pale pine #7eb89a. Same serif type and radii as the light Workshop theme. Verified rendering live via computed styles (body rgb(18,17,15), cards rgb(28,26,23), pine #7eb89a).
2026-09-28 09:02:17 -05:00
Avi
add956abdd feat(theme): add Workshop theme — Cybernetic Workshop identity from Moi DESIGN.md
Warm paper (#f3efe6), near-black ink, hairline borders, one pine accent (#215c48), serif display headings, Moi radii (14/9). Tokens verified rendering live via computed styles on the built shell: body bg #f3efe6, card #faf7f0, border #d9d1c3, h2 Iowan/Palatino 500. Settings -> Appearance -> 'Workshop — Cybernetic'.
2026-09-28 08:54:13 -05:00
Avi
15fa331f46 chore(deps): apply dependency updates from the in-app updater run
Applying npm audit fix + npm update + cargo update via the (fixed) updater: clears the high-severity js-yaml advisory (maxTotalMergeKeys CPU use on empty merge sources). Full suites verified green after the bump: 219 Rust unit + 6 e2e, 135 frontend, vite build clean.
2026-09-28 08:30:48 -05:00
Avi
adbc7c2d75 feat(signer): permissions UI — declared grants surfaced, always-allow kind-scoped
Step 4 groundwork, the enforcement half that was invisible or too broad:

- Nip46Status now carries the connection's declared perms= grant list and
  its expiry; Signer Mode shows a Permissions panel on a live session
  (explicit grant rows, or a plain statement that the signer app approves
  each request when no list was declared).
- 'Always allow' grants are kind-scoped: a sign_event grant records the
  kind of the request the user actually approved and never covers other
  kinds. Legacy kind-less grants keep their all-kinds meaning so existing
  vaults keep working. Enforced in both bunker.rs and nip46_client.rs.
- Grants list on the Signer screen renders human labels with kind scope.

Tests: vault kind-scoping unit tests, frontend permission-label unit
tests + two SignerModeScreen tests (declared list, signer-side note).
2026-09-27 22:37:54 -05:00
Avi
c89b31aaf1 feat(nip46): pair a second signer account — park the live session, switch re-dials it
One live NIP-46 session, many saved ones (Option A):
- start_pairing/connect while a session is live PARKS it instead of
  refusing: row, pairing secret, and persisted client key stay intact,
  so the parked account is restorable with no fresh scan.
- SelectProfile follows the switch: target has a restorable connection ->
  park current + re-dial target's row (expected_identity guard applies);
  target is local-key or unpaired -> live session untouched.
- New nip46_cancel_pairing IPC: aborts ONLY an in-flight pairing and
  re-dials the parked session, so cancel-after-park is transparent.
  The QR cancel paths (Add-profile modal, Signer Mode screen) use it —
  plain disconnect would revoke the parked connection.
- e2e: two fake Ambers on one relay; A pairs, B's pairing parks A
  (revoked_at none, client key resolvable), switch back re-dials A and
  signs; no-op switch; local profile leaves session alone; B restorable.
2026-09-27 21:01:23 -05:00
Avi
fc2fe93161 feat(ui): drop the pairing label step — profile names come from the account
Sep 25 feedback: the prefilled 'Amber' name had to be cleared letter by
letter before the QR would appear (the input fight), and naming should
be automatic anyway. One click on 'Sign in with a signer app (Amber)'
now goes straight to the QR with the seed label 'Amber'; the existing
adopt_identity background enrichment fetches the account's kind-0 after
the handshake and upgrades the profile row to the account's REAL
display name whenever it still carries our seed (a manual rename in
Profiles always wins and is never overwritten).

frontend: 125 tests green (pairing test asserts the one-click path and
the 'Amber' seed label), typecheck/lint/format/electron:build/build
green. Rust untouched — the auto-naming enrichment already shipped at
a76d8df.
2026-09-25 16:30:18 -05:00
Avi
a809a67023 feat(ui): name the signer connection before the QR; prune stale vault rows
The pairing path minted its profile under the backend's 'Remote Signer'
default, so several test pairings left indistinguishable rows. The
'Sign in with a signer app (Amber)' button now shows a prefilled
('Amber') Connection name step first; the QR step starts only from
there and the typed name reaches nip46_pair_start as the profile label.
Enter submits directly (input focused, prefilled).

Also cleaned the live vault (not in git): dropped 2 stale 'Remote
Signer' connections and the secret-less 'Dev' remote stub tied to them,
plus 13 orphaned connection_secrets entries; kept the live Amber
pairing (npub1qn0w4a…, its connection + client key) and Testing123.

frontend: 125 tests passed (label step covered: prefill, typed label
reaches nip46_pair_start), typecheck/lint/format/electron:build/build
green. Rust untouched.
2026-09-25 16:01:57 -05:00
Avi
5b13162a36 feat(ui): Add profile now offers 'Sign in with a signer (Amber)' first
The Amber pairing QR existed but only behind the sidebar's Signer Mode,
so clicking 'Add profile' expectedly led to a local-key form and users
never found the signer flow. The modal now opens as a choice:

- 'Sign in with a signer app (Amber)' — mints the nostrconnect:// QR
  inline, polls signer status every 2s (same channel as Signer Mode),
  and flips to an 'Amber is now your signer!' confirmation once the
  handshake lands; cancelling mid-pairing aborts it cleanly.
- 'Create a new key on this computer' — the previous local-key form,
  unchanged, with a Back step.

frontend: 125 tests passed (CreateProfileModal suite rewritten to cover
choice, QR start, connected poll, cancel-abort; App.test updated for the
new dialog title), typecheck/lint/format:check/electron:build/build green.
2026-09-25 15:34:28 -05:00
Avi
a76d8dff4e feat(feed): resolve author names and pictures from kind-0 metadata 2026-09-23 16:06:19 -05:00
Avi
6f4dbb2ca1 feat(nip46): publish kind-0 metadata through the connected signer 2026-09-23 15:30:35 -05:00
Avi
3d1c30662a fix(ui): silent background state poll - no refetch churn when vault unchanged 2026-09-23 15:09:37 -05:00
Avi
6ebc364fcf fix(profiles): label-only rename for secretless remote-signer profiles 2026-09-23 14:44:18 -05:00
Avi
a8d112b368 fix(ui): poll vault state so background Amber pairing appears without reload 2026-09-23 14:30:07 -05:00
Avi
8f055f71bf fix(nip46): spec-correct connect params, full-handshake [NIP46] trace, visible handshake errors 2026-09-23 13:02:19 -05:00
Avi
13a66f28d8 feat(onboarding): first-run screen offers import-existing-account and external-signer paths
- Home first-run now has three entry points: create a new profile, I already
  have an account (opens ImportProfileModal), and Sign in with a signer
  (navigates to Signer Mode where Amber/NIP-46 pairing lives).
- Copy states the per-mode truth: local-vault keys vs remote signer where the
  private key never lives on this device.
- Tests updated to assert all three entry points; 116 frontend tests green.
2026-09-23 09:00:07 -05:00
Avi
22d7c01193 chore(hygiene): ignore editor artifacts; prettier SignerScreen
- .gitignore now covers .directory, .opencode/, .impeccable/.
- SignerScreen.tsx reformatted (format:check was failing since 81b082f).
- Dead untracked stub src/signer/nip46_external.rs deleted from disk
  (superseded by nip46_client.rs, never declared in signer/mod.rs).
2026-09-12 17:55:32 -05:00
Avi
81b082f238 feat(signer): always-allow grants for external signer requests
Apps asking Keynctr to sign (NIP-46) can now be granted standing
permission per (peer pubkey, method). Approvals gained an 'Always
allow' option; existing grants are listed with a Revoke button on the
Signer screen and persist in the encrypted vault.
2026-09-12 17:00:41 -05:00
Avi
c5004ebb26 fix(electron): allow nip46_pair_start through the renderer method allowlist
The new QR pairing IPC method was rejected by the main-process allowlist
before reaching the Rust backend ('That operation is not permitted').
2026-09-12 05:01:46 -05:00
Avi
38499d4506 feat(signer): QR pairing — client-initiated nostrconnect:// flow for Amber
Keynctr is the NIP-46 client; Amber is the scanner. Amber hands out no
link — it scans one — so the signer screen now mints a pairing token:

- start_pairing(): ephemeral key + secret, nostrconnect:// token via
  NostrConnectUri::client_with_secret, status().pairing_uri for the GUI
- run_pairing_task(): listens for the signer's connect request, echoes
  the secret (anti-spoofing), persists the connection row + secret,
  then adopts identity via get_public_key and hands to the demux loop
- pairing subscription is closed at handoff so the demux loop owns the
  conversation (relay could otherwise deliver signer replies under the
  stale pairing sub id where nobody routes them)
- IPC: nip46_pair_start; status carries pairing_uri
- SignerModeScreen: 'Show QR' button, QR render (qrcode) of the token,
  copy-link fallback, cancel; paste-link flow unchanged
- e2e: fake QR scanner consumes the real pairing token end-to-end
  (scan -> secret echo -> identity -> sign -> vault persistence)
2026-09-12 04:47:20 -05:00
Avi
85756df081 feat(signer): accept bunker:// URIs, async signer permissions, NIP-46 e2e test
- SignerManager/SignerModeScreen parse both nostrconnect:// and bunker://
  (Amber presents bunker://; signer pubkey extracted before '@')
- Signer permission surface made async (permissions, can_*, is_connection_valid)
- tests/nip46_e2e.rs: full client handshake against fake Amber over a local
  relay — NIP-44 round-trip, get_public_key identity, signed-event verification,
  vault persistence asserting no secret material for remote profiles
- prettier formatting of touched frontend files
2026-09-12 02:40:40 -05:00
Avi
d580139e5a fix(icons): true alpha channel, no white matte or white tile
- public/icon.png and src/assets/logo.png were grayscale (mode L): a flat
  white field, no alpha, which rendered as a white box/halo on every
  non-white surface (window/taskbar icon, sidebar, launchers)
- regenerate both as RGBA: alpha is the ink coverage, RGB forced to 0 so
  no white matte can leak through semi-transparent edge pixels
- styles.css: drop the white tile background/border-radius and cover-fit
  from .sidebar-logo; the artwork now composites directly (contain-fit)
- originals preserved under deferred/original-icons/
2026-09-09 19:58:56 -05:00
Avi
0814a53cb4 fix(linux): work on X11, Wayland, and Hyprland
- detect the session platform explicitly (Hyprland exports both DISPLAY
  and WAYLAND_DISPLAY) and set ozone-platform before Chromium init
- software rendering by default on Linux: the GPU process segfaults in
  eglCreateWindowSurface on some Mesa/Wayland setups (reproduced on
  Intel Iris Xe under Hyprland), so hardware GL is opt-in via
  KEYNCTR_ENABLE_GPU=1
- startup watchdog + bounded relaunch ladder (platform swap, then GPU
  opt-in) when a launch dies before its window paints; give-up dialog
  lists the escape hatches
- sandbox pre-flight: skip the SUID sandbox when user namespaces are
  restricted (Ubuntu 24.04 AppArmor) instead of failing silently
2026-09-09 19:58:38 -05:00
Avi
114b34319e fix(packaging): restore Linux app icon so dist builds ship an icon
The packaging break: frontend/build/icon.png was deleted from the working
tree, so electron-builder had no Linux icon and every AppImage/deb it
emitted carried the generic Electron placeholder instead of the Keynctr
mark.

Regenerate build/icon.png from KeynectrAppIconPossibility02.jpeg rather than
resizing the old file:
  - cut the white (254) JPEG background to transparent (alpha from luma),
  - flatten the art to a square canvas with symmetric padding,
  - keep the ink pure black (RGB 0,0,0), export 512x512 RGBA.

The 512x512 master satisfies both declared linux targets with the config
kept single (linux.icon: build/icon.png, no build/linux/ fan-out):
  - AppImage: electron-builder downscales to 256 internally (>=256 required).
  - deb: installs usr/share/icons/hicolor/512x512/apps/keynectr.png, matching
    the generated .desktop Icon=keynectr.

Verified end to end (npm run dist green): the embedded icon is byte-identical
(md5 b3e372f7) in the AppImage hicolor set, the AppImage .DirIcon, and the
deb hicolor set, all 512x512 RGBA with real transparency.

The source JPEG (KeynectrAppIconPossibility02.jpeg) stays untracked, as does
the pre-existing hygiene leftover set. No package.json change needed: the
single build/icon.png path is already correct.
2026-09-03 13:13:11 -05:00
Avi
6eff510609 feat: fail-closed ExportSecretKey with fresh auth and audit
Replace the plain reveal_secret_key flow with an explicit, audited key
export that is hard to misuse:

Backend (src/app.rs, src/ipc.rs):
- New App::export_secret_key(): always requires the vault passphrase
  (even when the vault is already unlocked), requires a non-blank reason,
  and resolves the profile server-side via profiles::find_stored_profile.
- Refuses export for Nip46Client (external) profiles — the secret key is
  not present locally — logging the denial.
- FAIL-CLOSED: the successful audit entry is written and flushed BEFORE the
  key is returned; if the audit write fails, the key is not returned
  (log.record(...)? instead of let _ =).
- Audit entries are written on every outcome: external-profile denial,
  wrong password, and the successful export.
- RevealSecretKey IPC is deprecated: it now errors when the vault is locked
  and, when unlocked, records a [deprecated direct call] audit entry.
  The method stays registered for the deprecation window.

Frontend:
- ExportSecretKeyModal requires password + reason every time; clears
  sensitive state on close.
- ProfilesScreen uses ExportSecretKeyModal; ShowSecretKeyModal and its test
  are removed. AppProvider exposes exportSecretKey (revealSecretKey gone);
  api.ts maps to export_secret_key.
- fakeBackend implements the full export contract (profile-not-found,
  external-signer refusal, password check, blank-reason rejection); apiMock
  exposes exportSecretKey. 10 tests cover the required scenarios.

No secret material is logged; the reason is logged by design. Verified:
cargo test --release 186 passed; frontend tsc clean, vitest 116 passed.
2026-09-03 09:50:32 -05:00
Avi
2c61830390 feat: add per-profile signer modes with persisted NIP-46 connections
Introduce three coexisting signing modes:
- Embedded (INTERNAL): vault-held nsec, decrypted in Rust, signs locally.
- Nip46Client (EXTERNAL): Keynctr is the NIP-46 CLIENT; the key never
  touches this machine.
- Nip46Bunker: legacy inverted mode (Keynctr as signer serving others).

Data model:
- StoredProfile gains signer_mode (serde-defaults to Embedded for legacy
  profiles); SignerMode moves from app.rs to vault.rs to break a circular
  dependency; app.rs re-exports it.
- Vault gains nip46_connections (profile-owned) and bumps VAULT_VERSION to
  3; migrate_vault_signer_modes() normalises on load (idempotent).
- Nip46Connection gains profile_npub ownership, parsed permissions,
  expires_at, and revoked_at.

Signer abstraction (src/signer):
- Signer trait gains pubkey_for() identity validation, a Signing enum
  (Local vs External) that re-verifies the returned event, and a permission
  surface (permissions/can_*/is_connection_valid) with safe defaults.
- permissions.rs: NIP-46 per-connection permission model (parse, validate,
  deny-by-default, no-broadening checks) with 52 unit tests.
- Nip46ClientSigner parses perms from nostrconnect:// URIs, enforces
  permissions on every gated request, persists/revokes connections in the
  vault, and audits permission denials via the app's audit log.
- App gains audit_log and a nip46_bunker_signer handle; default mode is
  Nip46Client (most secure).

Frontend: SignerModeScreen redesigned for the three modes with a
nostr-tools-based SignerManager client, new IPC allowlist entries, and
signer-mode styling.

Verified: cargo test --release 186 passed; clippy/fmt clean; frontend tsc
clean, vitest 110 passed.
2026-09-03 09:47:19 -05:00
Avi
b484bdeb08 feat: add embedded signer and NIP-46 client signer modes
- Add Signer trait with common interface for both signing modes
- Implement EmbeddedSigner: keys stored in encrypted vault (Argon2id + AES-256-GCM)
- Implement Nip46ClientSigner: connects to remote signer via nostrconnect:// URI
- Support both local and remote NIP-46 signers
- Add signer mode selection UI (SignerModeScreen)
- Add IPC endpoints for signer mode management, embedded signer, and NIP-46 client
- Update frontend types, API, and AppProvider
- All tests pass (119 Rust + 110 frontend)
2026-09-01 20:16:14 -05:00
Avi
ea56806c0c fix: show only fully published events in Most Recent Publication box
- Add PublicationStatus type and computePublicationStatus() helper
- Add useProfilePublications hook that queries relays via feedGet and
  determines per-event publication status by comparing served relays
  against all enabled relays
- Rewrite HomeScreen PublicationResult to show only fully published
  events in the main box; partial events appear only in the expandable
  Relay results section
- Show empty state when no fully published events exist
- Add tests: fully published shown, partial hidden, older full shown
  when newest is partial, all-partial shows empty, relay details
  expandable, no duplicates
- Fix publishFlow integration test to seed profileFeedItems
2026-09-01 14:07:36 -05:00
Avi
cd5d2d7fd1 Show published note content inline on Home screen 2026-09-01 13:06:33 -05:00
Avi
bab82f5148 Persist last publish report across restarts 2026-09-01 12:32:28 -05:00
Avi
b0d9143228 Add 'View in Feed' button after publishing a note 2026-09-01 12:13:48 -05:00
Avi
471acf8125 polish: align HomeScreen spacing to design scale, add profile row hover 2026-09-01 12:02:17 -05:00
Avi
269f0c0230 Remove identity card from HomeScreen — redundant with subtitle and profile list 2026-09-01 11:58:15 -05:00
Avi
566aa466b9 Remove redundant 'Active profile' heading from identity card 2026-09-01 11:48:43 -05:00
Avi
85ba0889eb fix(polish): clean up HomeScreen dead code and token drift
- Remove dead .active-profile-row CSS (unused class)
- Replace hardcoded rgba fallbacks in .home-profile-row.is-active with
  design tokens (var(--surface-2), var(--border))
- Remove duplicate edit icon from publication empty state (icon was
  shown above the button, redundant with the button's own icon)
2026-09-01 11:38:21 -05:00
Avi
e854c95d41 fix(polish): remove identity card kicker, flatten to success border
- Remove 'Active signing identity' kicker text
- Card uses flat surface background with var(--success) border
  (matches active profile card treatment)
- Remove dead .home-identity-kicker CSS
- Update test to find card by 'Active profile' heading
2026-09-01 11:32:34 -05:00
Avi
ee13d47aeb fix(polish): align Compose button label with sidebar nav
- HomeScreen header button: 'Compose note' → 'Compose' (matches sidebar)
- Updated tests to use exact name match and scoped queries to avoid
  matching the sidebar nav button
2026-09-01 11:27:23 -05:00
Avi
32fc764d54 fix(polish): restore success-soft background on identity card
Flat var(--success-soft) instead of the removed gradient — keeps the
green tint that signals active security state without breaking the
flat-by-default rule.
2026-09-01 11:18:56 -05:00