From 91018fec93716266e2d676f1578a28dc4fdda87d Mon Sep 17 00:00:00 2001 From: Claude Date: Wed, 21 Jan 2026 21:20:10 +0000 Subject: [PATCH] feat: add comprehensive settings UI with Post and Appearance sections Created a minimal MVP settings system accessible via command palette and user menu. Settings are organized in a clean tabbed interface with two initial sections. UI Features: - SettingsViewer component with sidebar navigation - Post section: Toggle to include Grimoire client tag in published events - Appearance section: - Theme selector (light/dark/system) - Toggle to show/hide client tags in event UI ("via Grimoire" etc) Integration: - Added "settings" command to command palette - Added "Settings" option to user menu (before Support Grimoire) - Registered "settings" as new AppId in window system Display Logic: - BaseEventRenderer now honors settings.appearance.showClientTags - When disabled, "via Grimoire" and other client tags are hidden from events - Setting applies instantly across all event renderers Technical Details: - SettingsViewer uses existing UI components (Checkbox, Button, Label) - Leverages useSettings hook for reactive updates - Settings persist to localStorage via settingsManager - Simple button group for theme selection instead of dropdown - Clean two-column layout with icons for each section This provides a solid foundation for adding more settings sections later (relay config, privacy, database, notifications, developer options). --- src/components/SettingsViewer.tsx | 153 ++++++++++++++++++ src/components/WindowRenderer.tsx | 6 + .../nostr/kinds/BaseEventRenderer.tsx | 4 +- src/components/nostr/user-menu.tsx | 10 ++ src/services/settings.ts | 3 + src/types/app.ts | 1 + src/types/man.ts | 12 ++ 7 files changed, 188 insertions(+), 1 deletion(-) create mode 100644 src/components/SettingsViewer.tsx diff --git a/src/components/SettingsViewer.tsx b/src/components/SettingsViewer.tsx new file mode 100644 index 0000000..0dadeb3 --- /dev/null +++ b/src/components/SettingsViewer.tsx @@ -0,0 +1,153 @@ +import { useState } from "react"; +import { Settings, Palette } from "lucide-react"; +import { Label } from "./ui/label"; +import { Checkbox } from "./ui/checkbox"; +import { Button } from "./ui/button"; +import { useSettings } from "@/hooks/useSettings"; +import { cn } from "@/lib/utils"; + +type SettingsTab = "post" | "appearance"; + +interface TabConfig { + id: SettingsTab; + label: string; + icon: React.ReactNode; +} + +const TABS: TabConfig[] = [ + { + id: "post", + label: "Post", + icon: , + }, + { + id: "appearance", + label: "Appearance", + icon: , + }, +]; + +export function SettingsViewer() { + const { settings, updateSetting } = useSettings(); + const [activeTab, setActiveTab] = useState("post"); + + return ( +
+ {/* Sidebar */} +
+
+

Settings

+
+ {TABS.map((tab) => ( + + ))} +
+
+
+ + {/* Content */} +
+
+ {activeTab === "post" && ( +
+
+

Post Settings

+

+ Configure how your posts are published +

+
+ +
+
+ + updateSetting("post", "includeClientTag", checked) + } + /> +
+ +

+ Add Grimoire client tag to your published events (kind 1 + posts, spells, deletions) +

+
+
+
+
+ )} + + {activeTab === "appearance" && ( +
+
+

+ Appearance Settings +

+

+ Customize how Grimoire looks +

+
+ +
+
+ +
+ {(["light", "dark", "system"] as const).map((theme) => ( + + ))} +
+

+ Choose your preferred color scheme +

+
+ +
+ + updateSetting("appearance", "showClientTags", checked) + } + /> +
+ +

+ Display "via Grimoire" and other client tags in event UI +

+
+
+
+
+ )} +
+
+
+ ); +} diff --git a/src/components/WindowRenderer.tsx b/src/components/WindowRenderer.tsx index fa366d9..ad307ef 100644 --- a/src/components/WindowRenderer.tsx +++ b/src/components/WindowRenderer.tsx @@ -50,6 +50,9 @@ const CountViewer = lazy(() => import("./CountViewer")); const PostViewer = lazy(() => import("./PostViewer").then((m) => ({ default: m.PostViewer })), ); +const SettingsViewer = lazy(() => + import("./SettingsViewer").then((m) => ({ default: m.SettingsViewer })), +); // Loading fallback component function ViewerLoading() { @@ -247,6 +250,9 @@ export function WindowRenderer({ window, onClose }: WindowRendererProps) { case "post": content = ; break; + case "settings": + content = ; + break; default: content = (
diff --git a/src/components/nostr/kinds/BaseEventRenderer.tsx b/src/components/nostr/kinds/BaseEventRenderer.tsx index 6b36d11..5c3bade 100644 --- a/src/components/nostr/kinds/BaseEventRenderer.tsx +++ b/src/components/nostr/kinds/BaseEventRenderer.tsx @@ -28,6 +28,7 @@ import { import { useGrimoire } from "@/core/state"; import { useCopy } from "@/hooks/useCopy"; import { useAccount } from "@/hooks/useAccount"; +import { useSettings } from "@/hooks/useSettings"; import { JsonViewer } from "@/components/JsonViewer"; import { EmojiPickerDialog } from "@/components/chat/EmojiPickerDialog"; import { formatTimestamp } from "@/hooks/useLocale"; @@ -531,6 +532,7 @@ export function BaseEventContainer({ }) { const { locale, addWindow } = useGrimoire(); const { canSign, signer, pubkey } = useAccount(); + const { settings } = useSettings(); const [emojiPickerOpen, setEmojiPickerOpen] = useState(false); const handleReactClick = () => { @@ -612,7 +614,7 @@ export function BaseEventContainer({ > {relativeTime} - {clientName && ( + {settings?.appearance?.showClientTags && clientName && ( via{" "} {clientAppPointer ? ( diff --git a/src/components/nostr/user-menu.tsx b/src/components/nostr/user-menu.tsx index 5a7667c..c9bb4fa 100644 --- a/src/components/nostr/user-menu.tsx +++ b/src/components/nostr/user-menu.tsx @@ -9,6 +9,7 @@ import { Zap, LogIn, LogOut, + Settings, } from "lucide-react"; import accounts from "@/services/accounts"; import { useProfile } from "@/hooks/useProfile"; @@ -515,6 +516,15 @@ export default function UserMenu() { + {/* Settings */} + addWindow("settings", {}, "Settings")} + > + + Settings + + {/* Support Grimoire */} = { category: "Nostr", defaultProps: {}, }, + settings: { + name: "settings", + section: "1", + synopsis: "settings", + description: + "Configure Grimoire application settings. Includes post composition settings (client tag), appearance settings (theme, show client tags), and more. Settings are persisted to localStorage and synchronized across all windows.", + examples: ["settings Open settings panel"], + seeAlso: ["post", "help"], + appId: "settings", + category: "System", + defaultProps: {}, + }, };