Files
grimoire/src/types/app.ts
Alejandro 9f6e524ea9 feat: add tap-to-blur privacy feature for wallet balances (#143)
* feat: add tap-to-blur privacy feature for wallet balances

Implement privacy toggle for wallet balances and transaction amounts.
Tapping any balance display toggles a global blur effect across all
wallet UIs. Persisted to localStorage for consistent privacy.

- Add walletBalancesBlurred state to GrimoireState
- Add toggleWalletBalancesBlur pure function in core/logic
- Make big balance in WalletViewer clickable with eye icon indicator
- Apply blur to all transaction amounts in list and detail views
- Add blur to send/receive dialog amounts
- Make balance in user menu wallet info clickable with eye icon
- Apply blur to balance in dropdown menu item

UX matches common financial app pattern: tap balance → blur on/off

* refactor: replace blur with fixed-width placeholders for privacy

Prevent balance size information leakage by using fixed-width
placeholder characters instead of blur effect. A blurred "1000000"
would still reveal it's a large balance vs "100" even when blurred.

Changes:
- Replace blur-sm class with conditional placeholder text
- Use "••••••" for main balances
- Use "••••" for transaction amounts in lists
- Use "•••••• sats" for detailed amounts with unit
- Use "•••• sats" for smaller amounts like fees

Security improvement: No information about balance size is leaked
when privacy mode is enabled. All hidden amounts appear identical.

* refactor: improve privacy UX with stars and clearer send flow

Three UX improvements to the wallet privacy feature:

1. Don't hide amounts in send confirmation dialog
   - Users need to verify invoice amounts before sending
   - Privacy mode now only affects viewing, not sending

2. Replace bullet placeholders (••••) with stars (✦✦✦✦)
   - More visually distinct and recognizable as privacy indicator
   - Unicode BLACK FOUR POINTED STAR (U+2726)
   - Better matches common "redacted" aesthetic

3. Reduce eye icon sizes for subtler presence
   - Main balance: size-6 → size-5
   - Wallet info dialog: size-3.5 → size-3
   - Smaller icons feel less intrusive

Result: Clearer privacy state, safer payment flow, better aesthetics.

---------

Co-authored-by: Claude <noreply@anthropic.com>
2026-01-18 23:22:00 +01:00

142 lines
3.7 KiB
TypeScript

import type { MosaicNode } from "react-mosaic-component";
import type { GlobalRelayState } from "./relay-state";
export type AppId =
| "nip"
| "nips"
| "kind"
| "kinds"
| "man"
| "req"
| "count"
//| "event"
| "open"
| "profile"
| "encode"
| "decode"
| "relay"
| "debug"
| "conn"
| "chat"
| "spells"
| "spellbooks"
| "blossom"
| "wallet"
| "zap"
| "win";
export interface WindowInstance {
id: string;
appId: AppId;
title?: string; // Legacy field - rarely used now that DynamicWindowTitle handles all titles
customTitle?: string; // User-provided custom title via --title flag (overrides dynamic title)
props: any;
commandString?: string; // Original command that created this window (e.g., "profile alice@domain.com")
spellId?: string; // ID of the spell that created this window (if any)
}
/**
* Configuration for how new windows are inserted into the workspace layout tree
*/
export interface LayoutConfig {
/**
* How to determine split direction for new windows
* - 'smart': Auto-balance horizontal/vertical splits (recommended)
* - 'row': Always horizontal splits (side-by-side)
* - 'column': Always vertical splits (stacked)
*/
insertionMode: "smart" | "row" | "column";
/**
* Split percentage for new windows (10-90)
* Example: 70 means existing content gets 70%, new window gets 30%
*/
splitPercentage: number;
/**
* Where to place the new window
* - 'first': Left (for row) or Top (for column)
* - 'second': Right (for row) or Bottom (for column)
*/
insertionPosition: "first" | "second";
/**
* Optional: Auto-maintain a preset layout structure
* When set, system tries to preserve this preset when adding windows
*/
autoPreset?: string;
}
export interface Workspace {
id: string;
number: number; // Numeric identifier for shortcuts (e.g., Cmd+1, Cmd+2)
label?: string; // Optional user-editable label
layout: MosaicNode<string> | null;
windowIds: string[];
}
export interface RelayInfo {
url: string;
read: boolean;
write: boolean;
}
/**
* Nostr Wallet Connect (NIP-47) wallet connection
*/
export interface NWCConnection {
/** The wallet service's public key */
service: string;
/** Relay URL(s) for communication */
relays: string[];
/** Shared secret for encryption */
secret: string;
/** Optional lightning address (lud16) */
lud16?: string;
/** Optional cached balance in millisats */
balance?: number;
/** Optional wallet info */
info?: {
alias?: string;
methods?: string[];
notifications?: string[];
};
/** Last connection time */
lastConnected?: number;
}
export interface GrimoireState {
__version: number; // Schema version for migrations
windows: Record<string, WindowInstance>;
workspaces: Record<string, Workspace>;
activeWorkspaceId: string;
layoutConfig: LayoutConfig; // Global configuration for window insertion behavior
activeAccount?: {
pubkey: string;
relays?: RelayInfo[];
blossomServers?: string[];
};
compactModeKinds?: number[];
locale?: {
locale: string;
language: string;
region?: string;
timezone: string;
timeFormat: "12h" | "24h";
};
relayState?: GlobalRelayState;
activeSpellbook?: {
id: string; // event id or local uuid
slug: string; // d-tag
title: string;
description?: string;
pubkey?: string; // owner's pubkey (undefined = local-only, never published)
// Enhanced fields for better UX:
source: "local" | "network"; // Where the spellbook was loaded from
localId?: string; // Local DB ID if saved to library
isPublished?: boolean; // Whether it has been published to Nostr
};
nwcConnection?: NWCConnection;
walletBalancesBlurred?: boolean; // Privacy: blur balances and transaction amounts
}