diff --git a/src/components/SettingsDialog.tsx b/src/components/SettingsDialog.tsx deleted file mode 100644 index 7224aa7..0000000 --- a/src/components/SettingsDialog.tsx +++ /dev/null @@ -1,113 +0,0 @@ -import { useGrimoire } from "@/core/state"; -import { Button } from "@/components/ui/button"; -import { Badge } from "@/components/ui/badge"; -import { - Dialog, - DialogContent, - DialogDescription, - DialogHeader, - DialogTitle, -} from "@/components/ui/dialog"; -import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"; -import { X } from "lucide-react"; -import { KindSelector } from "./KindSelector"; -import { getKindName } from "@/constants/kinds"; - -interface SettingsDialogProps { - open: boolean; - onOpenChange: (open: boolean) => void; -} - -export default function SettingsDialog({ - open, - onOpenChange, -}: SettingsDialogProps) { - const { state, setCompactModeKinds } = useGrimoire(); - const compactKinds = state.compactModeKinds || []; - - const removeKind = (kindToRemove: number) => { - setCompactModeKinds(compactKinds.filter((k) => k !== kindToRemove)); - }; - - const addKind = (kind: number) => { - if (!compactKinds.includes(kind)) { - setCompactModeKinds([...compactKinds, kind].sort((a, b) => a - b)); - } - }; - - return ( - - - - Settings - - Manage your workspace preferences. - - - - - - - - - Appearance - - {/* Future tabs can be added here */} - - - - - - {/* Section: Compact Events */} - - - Compact Events - - Select event kinds to display in a compact format within - timelines and feeds. - - - - - - - - - - {compactKinds.length === 0 && ( - - No compact kinds configured. - - )} - {compactKinds.map((kind) => ( - - - {kind} - - {getKindName(kind)} - removeKind(kind)} - > - - Remove Kind {kind} - - - ))} - - - - - - - - - - ); -} diff --git a/src/components/nostr/user-menu.tsx b/src/components/nostr/user-menu.tsx index 0df2ba6..16221b0 100644 --- a/src/components/nostr/user-menu.tsx +++ b/src/components/nostr/user-menu.tsx @@ -38,7 +38,6 @@ import { import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar"; import Nip05 from "./nip05"; import { RelayLink } from "./RelayLink"; -import SettingsDialog from "@/components/SettingsDialog"; import LoginDialog from "./LoginDialog"; import ConnectWalletDialog from "@/components/ConnectWalletDialog"; import { useState } from "react"; @@ -87,7 +86,6 @@ export default function UserMenu() { const relays = state.activeAccount?.relays; const blossomServers = state.activeAccount?.blossomServers; const nwcConnection = state.nwcConnection; - const [showSettings, setShowSettings] = useState(false); const [showLogin, setShowLogin] = useState(false); const [showConnectWallet, setShowConnectWallet] = useState(false); const [showWalletInfo, setShowWalletInfo] = useState(false); @@ -182,7 +180,6 @@ export default function UserMenu() { return ( <> - { - return { - ...state, - compactModeKinds: kinds, - }; -}; - /** * Clears the currently active spellbook tracking. */ diff --git a/src/core/state.ts b/src/core/state.ts index 6775b1f..9feea0e 100644 --- a/src/core/state.ts +++ b/src/core/state.ts @@ -26,7 +26,6 @@ const initialState: GrimoireState = { insertionPosition: "second", autoPreset: undefined, }, - compactModeKinds: [6, 7, 16, 9735], workspaces: { default: { id: "default", @@ -326,13 +325,6 @@ export const useGrimoire = () => { [setState], ); - const setCompactModeKinds = useCallback( - (kinds: number[]) => { - setState((prev) => Logic.setCompactModeKinds(prev, kinds)); - }, - [setState], - ); - const loadSpellbook = useCallback( (spellbook: ParsedSpellbook) => { setState((prev) => SpellbookManager.loadSpellbook(prev, spellbook)); @@ -409,7 +401,6 @@ export const useGrimoire = () => { applyPresetLayout, updateWorkspaceLabel, reorderWorkspaces, - setCompactModeKinds, loadSpellbook, clearActiveSpellbook, switchToTemporary, diff --git a/src/hooks/useSettings.ts b/src/hooks/useSettings.ts index 5ead8ee..45ef8f3 100644 --- a/src/hooks/useSettings.ts +++ b/src/hooks/useSettings.ts @@ -9,16 +9,6 @@ import { settingsManager, type AppSettings } from "@/services/settings"; export function useSettings() { const settings = use$(settingsManager.stream$); - const updateSection = useCallback( - >( - section: K, - updates: Partial, - ) => { - settingsManager.updateSection(section, updates); - }, - [], - ); - const updateSetting = useCallback( < S extends keyof Omit, @@ -37,18 +27,9 @@ export function useSettings() { settingsManager.reset(); }, []); - const resetSection = useCallback( - >(section: K) => { - settingsManager.resetSection(section); - }, - [], - ); - return { settings, - updateSection, updateSetting, reset, - resetSection, }; } diff --git a/src/lib/migrations.ts b/src/lib/migrations.ts index a8bdcee..283f917 100644 --- a/src/lib/migrations.ts +++ b/src/lib/migrations.ts @@ -105,12 +105,13 @@ const migrations: Record = { __version: 9, }; }, - // Migration from v9 to v10 - adds compactModeKinds + // Migration from v9 to v10 - version bump (compactModeKinds removed) 9: (state: any) => { + // Remove compactModeKinds if it exists (no longer used) + const { compactModeKinds: _, ...rest } = state; return { - ...state, + ...rest, __version: 10, - compactModeKinds: [6, 7, 16, 9735], }; }, }; @@ -142,11 +143,6 @@ export function validateState(state: any): state is GrimoireState { return false; } - // compactModeKinds must be an array if present - if (state.compactModeKinds && !Array.isArray(state.compactModeKinds)) { - return false; - } - // Windows must be an object if (typeof state.windows !== "object") { return false; diff --git a/src/services/settings.ts b/src/services/settings.ts index b0f45fa..4effa8f 100644 --- a/src/services/settings.ts +++ b/src/services/settings.ts @@ -1,6 +1,5 @@ /** - * Global application settings with namespaced structure - * Manages user preferences with localStorage persistence, validation, and migrations + * Global application settings with localStorage persistence */ import { BehaviorSubject } from "rxjs"; @@ -15,125 +14,23 @@ import { BehaviorSubject } from "rxjs"; export interface PostSettings { /** Include Grimoire client tag in published events */ includeClientTag: boolean; - /** Default relay selection preference (user-relays, aggregators, custom) */ - defaultRelayMode: "user-relays" | "aggregators" | "custom"; - /** Custom relay list for posting (when defaultRelayMode is "custom") */ - customPostRelays: string[]; } /** - * Appearance and theme settings + * Appearance settings */ export interface AppearanceSettings { - /** Theme mode (light, dark, or system) */ - theme: "light" | "dark" | "system"; /** Show client tags in event UI */ showClientTags: boolean; - /** Event kinds to render in compact mode */ - compactModeKinds: number[]; - /** Font size multiplier (0.8 = 80%, 1.0 = 100%, 1.2 = 120%) */ - fontSizeMultiplier: number; - /** Enable UI animations */ - animationsEnabled: boolean; - /** Accent color (hue value 0-360) */ - accentHue: number; -} - -/** - * Relay configuration settings - */ -export interface RelaySettings { - /** Fallback aggregator relays when user has no relay list */ - fallbackRelays: string[]; - /** Discovery relays for bootstrapping (NIP-05, relay lists, etc.) */ - discoveryRelays: string[]; - /** Enable NIP-65 outbox model for finding events */ - outboxEnabled: boolean; - /** Fallback to aggregators if outbox fails */ - outboxFallbackEnabled: boolean; - /** Relay connection timeout in milliseconds */ - relayTimeout: number; - /** Maximum concurrent relay connections per query */ - maxRelaysPerQuery: number; - /** Automatically connect to inbox relays when viewing DMs */ - autoConnectInbox: boolean; -} - -/** - * Privacy and security settings - */ -export interface PrivacySettings { - /** Share read receipts (NIP-15) */ - shareReadReceipts: boolean; - /** Blur wallet balances in UI */ - blurWalletBalances: boolean; - /** Blur sensitive content (marked with content-warning tag) */ - blurSensitiveContent: boolean; - /** Warn before opening external links */ - warnExternalLinks: boolean; -} - -/** - * Local database and caching settings - */ -export interface DatabaseSettings { - /** Maximum events to cache in IndexedDB (0 = unlimited) */ - maxEventsCached: number; - /** Auto-cleanup old events after N days (0 = never) */ - autoCleanupDays: number; - /** Enable IndexedDB caching */ - cacheEnabled: boolean; - /** Cache profile metadata */ - cacheProfiles: boolean; - /** Cache relay lists */ - cacheRelayLists: boolean; -} - -/** - * Notification preferences - */ -export interface NotificationSettings { - /** Enable browser notifications */ - enabled: boolean; - /** Notify on mentions */ - notifyOnMention: boolean; - /** Notify on zaps received */ - notifyOnZap: boolean; - /** Notify on replies */ - notifyOnReply: boolean; - /** Play sound on notification */ - soundEnabled: boolean; -} - -/** - * Developer and debug settings - */ -export interface DeveloperSettings { - /** Enable debug mode */ - debugMode: boolean; - /** Show event IDs in UI */ - showEventIds: boolean; - /** Console log level */ - logLevel: "none" | "error" | "warn" | "info" | "debug"; - /** Enable experimental features */ - experimentalFeatures: boolean; - /** Show performance metrics */ - showPerformanceMetrics: boolean; } /** * Complete application settings structure - * Version 1: Initial namespaced structure */ export interface AppSettings { __version: 1; post: PostSettings; appearance: AppearanceSettings; - relay: RelaySettings; - privacy: PrivacySettings; - database: DatabaseSettings; - notifications: NotificationSettings; - developer: DeveloperSettings; } // ============================================================================ @@ -142,78 +39,16 @@ export interface AppSettings { const DEFAULT_POST_SETTINGS: PostSettings = { includeClientTag: true, - defaultRelayMode: "user-relays", - customPostRelays: [], }; const DEFAULT_APPEARANCE_SETTINGS: AppearanceSettings = { - theme: "dark", showClientTags: true, - compactModeKinds: [6, 7, 16, 9735], // reactions, reposts, zaps - fontSizeMultiplier: 1.0, - animationsEnabled: true, - accentHue: 280, // Purple -}; - -const DEFAULT_RELAY_SETTINGS: RelaySettings = { - fallbackRelays: [ - "wss://relay.damus.io", - "wss://relay.nostr.band", - "wss://nos.lol", - "wss://relay.primal.net", - ], - discoveryRelays: [ - "wss://relay.damus.io", - "wss://relay.nostr.band", - "wss://purplepag.es", - ], - outboxEnabled: true, - outboxFallbackEnabled: true, - relayTimeout: 5000, - maxRelaysPerQuery: 10, - autoConnectInbox: true, -}; - -const DEFAULT_PRIVACY_SETTINGS: PrivacySettings = { - shareReadReceipts: false, - blurWalletBalances: false, - blurSensitiveContent: true, - warnExternalLinks: false, -}; - -const DEFAULT_DATABASE_SETTINGS: DatabaseSettings = { - maxEventsCached: 50000, - autoCleanupDays: 30, - cacheEnabled: true, - cacheProfiles: true, - cacheRelayLists: true, -}; - -const DEFAULT_NOTIFICATION_SETTINGS: NotificationSettings = { - enabled: false, - notifyOnMention: true, - notifyOnZap: true, - notifyOnReply: true, - soundEnabled: false, -}; - -const DEFAULT_DEVELOPER_SETTINGS: DeveloperSettings = { - debugMode: false, - showEventIds: false, - logLevel: "warn", - experimentalFeatures: false, - showPerformanceMetrics: false, }; export const DEFAULT_SETTINGS: AppSettings = { __version: 1, post: DEFAULT_POST_SETTINGS, appearance: DEFAULT_APPEARANCE_SETTINGS, - relay: DEFAULT_RELAY_SETTINGS, - privacy: DEFAULT_PRIVACY_SETTINGS, - database: DEFAULT_DATABASE_SETTINGS, - notifications: DEFAULT_NOTIFICATION_SETTINGS, - developer: DEFAULT_DEVELOPER_SETTINGS, }; // ============================================================================ @@ -226,49 +61,49 @@ const SETTINGS_STORAGE_KEY = "grimoire-settings-v2"; * Validate settings structure and return valid settings * Falls back to defaults for invalid sections */ -function validateSettings(settings: any): AppSettings { +function validateSettings(settings: unknown): AppSettings { if (!settings || typeof settings !== "object") { return DEFAULT_SETTINGS; } - // Ensure all namespaces exist + const s = settings as Record; + return { __version: 1, - post: { ...DEFAULT_POST_SETTINGS, ...(settings.post || {}) }, + post: { + ...DEFAULT_POST_SETTINGS, + ...((s.post as object) || {}), + }, appearance: { ...DEFAULT_APPEARANCE_SETTINGS, - ...(settings.appearance || {}), + ...((s.appearance as object) || {}), }, - relay: { ...DEFAULT_RELAY_SETTINGS, ...(settings.relay || {}) }, - privacy: { ...DEFAULT_PRIVACY_SETTINGS, ...(settings.privacy || {}) }, - database: { ...DEFAULT_DATABASE_SETTINGS, ...(settings.database || {}) }, - notifications: { - ...DEFAULT_NOTIFICATION_SETTINGS, - ...(settings.notifications || {}), - }, - developer: { ...DEFAULT_DEVELOPER_SETTINGS, ...(settings.developer || {}) }, }; } /** * Migrate settings from old format to current version */ -function migrateSettings(stored: any): AppSettings { - // If it's already v2 format, validate and return - if (stored && stored.__version === 1) { +function migrateSettings(stored: unknown): AppSettings { + if (!stored || typeof stored !== "object") { + return DEFAULT_SETTINGS; + } + + const s = stored as Record; + + // If it's already current format, validate and return + if (s.__version === 1) { return validateSettings(stored); } - // Migrate from v1 (flat structure with only includeClientTag) + // Migrate from old flat structure const migrated: AppSettings = { ...DEFAULT_SETTINGS, }; - if (stored && typeof stored === "object") { - // Migrate old includeClientTag setting - if ("includeClientTag" in stored) { - migrated.post.includeClientTag = stored.includeClientTag; - } + // Migrate old includeClientTag setting + if ("includeClientTag" in s && typeof s.includeClientTag === "boolean") { + migrated.post.includeClientTag = s.includeClientTag; } return migrated; @@ -319,17 +154,12 @@ function saveSettings(settings: AppSettings): void { /** * Global settings manager with reactive updates - * Use settings$ to reactively observe settings changes - * Use getSection() for non-reactive access to a settings section - * Use updateSection() to update an entire section - * Use updateSetting() to update a specific setting within a section */ class SettingsManager { private settings$ = new BehaviorSubject(loadSettings()); /** * Observable stream of settings - * Subscribe to get notified of changes */ get stream$() { return this.settings$.asObservable(); @@ -342,18 +172,8 @@ class SettingsManager { return this.settings$.value; } - /** - * Get a specific settings section - */ - getSection>( - section: K, - ): AppSettings[K] { - return this.settings$.value[section]; - } - /** * Get a specific setting within a section - * @example getSetting("post", "includeClientTag") */ getSetting< S extends keyof Omit, @@ -362,29 +182,8 @@ class SettingsManager { return this.settings$.value[section][key]; } - /** - * Update an entire settings section - * Automatically persists to localStorage - */ - updateSection>( - section: K, - updates: Partial, - ): void { - const newSettings = { - ...this.settings$.value, - [section]: { - ...this.settings$.value[section], - ...updates, - }, - }; - this.settings$.next(newSettings); - saveSettings(newSettings); - } - /** * Update a specific setting within a section - * Automatically persists to localStorage - * @example updateSetting("post", "includeClientTag", true) */ updateSetting< S extends keyof Omit, @@ -408,48 +207,9 @@ class SettingsManager { this.settings$.next(DEFAULT_SETTINGS); saveSettings(DEFAULT_SETTINGS); } - - /** - * Reset a specific section to defaults - */ - resetSection>( - section: K, - ): void { - const newSettings = { - ...this.settings$.value, - [section]: DEFAULT_SETTINGS[section], - }; - this.settings$.next(newSettings); - saveSettings(newSettings); - } - - /** - * Export settings as JSON string - */ - export(): string { - return JSON.stringify(this.settings$.value, null, 2); - } - - /** - * Import settings from JSON string - * Validates and migrates imported settings - */ - import(json: string): boolean { - try { - const parsed = JSON.parse(json); - const validated = validateSettings(parsed); - this.settings$.next(validated); - saveSettings(validated); - return true; - } catch (err) { - console.error("Failed to import settings:", err); - return false; - } - } } /** * Global settings manager instance - * Import this to access settings throughout the app */ export const settingsManager = new SettingsManager(); diff --git a/src/types/app.ts b/src/types/app.ts index ff449c8..97ee235 100644 --- a/src/types/app.ts +++ b/src/types/app.ts @@ -119,7 +119,6 @@ export interface GrimoireState { relays?: RelayInfo[]; blossomServers?: string[]; }; - compactModeKinds?: number[]; locale?: { locale: string; language: string;
- Select event kinds to display in a compact format within - timelines and feeds. -