("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: {},
+ },
};