diff --git a/TODO.md b/TODO.md
index d7a8624..c7aaca0 100644
--- a/TODO.md
+++ b/TODO.md
@@ -3,7 +3,7 @@
## Known Issues
### RTL Support in Rich Text
-**Priority**: Medium
+**Priority**: Medium
**File**: `src/components/nostr/RichText/Text.tsx`
Current RTL implementation is partial and has limitations:
@@ -11,7 +11,7 @@ Current RTL implementation is partial and has limitations:
- RTL text alignment (right-align) doesn't work properly with inline elements
- Mixed LTR/RTL content with inline elements (hashtags, mentions) creates layout conflicts
-**The core problem**:
+**The core problem**:
- Inline elements (hashtags, mentions) need inline flow to stay on same line
- RTL alignment requires block-level containers
- These two requirements conflict
diff --git a/src/components/Home.tsx b/src/components/Home.tsx
index 8e342b8..05743ad 100644
--- a/src/components/Home.tsx
+++ b/src/components/Home.tsx
@@ -1,21 +1,11 @@
import { useState, useEffect } from "react";
import { useGrimoire } from "@/core/state";
import { useAccountSync } from "@/hooks/useAccountSync";
-import Feed from "./nostr/Feed";
-import { WinViewer } from "./WinViewer";
-import { WindowToolbar } from "./WindowToolbar";
import { TabBar } from "./TabBar";
import { Mosaic, MosaicWindow, MosaicBranch } from "react-mosaic-component";
-import { NipRenderer } from "./NipRenderer";
-import ManPage from "./ManPage";
import CommandLauncher from "./CommandLauncher";
-import ReqViewer from "./ReqViewer";
-import { EventDetailViewer } from "./EventDetailViewer";
-import { ProfileViewer } from "./ProfileViewer";
-import EncodeViewer from "./EncodeViewer";
-import DecodeViewer from "./DecodeViewer";
-import { RelayViewer } from "./RelayViewer";
-import KindRenderer from "./KindRenderer";
+import { WindowToolbar } from "./WindowToolbar";
+import { WindowTile } from "./WindowTitle";
import { Terminal } from "lucide-react";
import UserMenu from "./nostr/user-menu";
import { GrimoireWelcome } from "./GrimoireWelcome";
@@ -62,68 +52,13 @@ export default function Home() {
);
}
- // Render based on appId
- let content;
- switch (window.appId) {
- case "nip":
- content = ;
- break;
- case "feed":
- content = ;
- break;
- case "win":
- content = ;
- break;
- case "kind":
- content = ;
- break;
- case "man":
- content = ;
- break;
- case "req":
- content = (
-
- );
- break;
- case "open":
- content = ;
- break;
- case "profile":
- content = ;
- break;
- case "encode":
- content = ;
- break;
- case "decode":
- content = ;
- break;
- case "relay":
- content = ;
- break;
- default:
- content = (
-
- Unknown app: {window.appId}
-
- );
- }
-
return (
- handleRemoveWindow(id)} />
- }
- >
- {content}
-
+ onClose={handleRemoveWindow}
+ />
);
};
diff --git a/src/components/WindowRenderer.tsx b/src/components/WindowRenderer.tsx
new file mode 100644
index 0000000..e2b9278
--- /dev/null
+++ b/src/components/WindowRenderer.tsx
@@ -0,0 +1,166 @@
+import { Component, ReactNode } from "react";
+import { AlertCircle } from "lucide-react";
+import { Button } from "@/components/ui/button";
+import { WindowInstance } from "@/types/app";
+import { NipRenderer } from "./NipRenderer";
+import ManPage from "./ManPage";
+import ReqViewer from "./ReqViewer";
+import { EventDetailViewer } from "./EventDetailViewer";
+import { ProfileViewer } from "./ProfileViewer";
+import EncodeViewer from "./EncodeViewer";
+import DecodeViewer from "./DecodeViewer";
+import { RelayViewer } from "./RelayViewer";
+import KindRenderer from "./KindRenderer";
+import Feed from "./nostr/Feed";
+import { WinViewer } from "./WinViewer";
+
+interface WindowRendererProps {
+ window: WindowInstance;
+ onClose: () => void;
+}
+
+interface WindowErrorBoundaryState {
+ hasError: boolean;
+ error?: Error;
+}
+
+class WindowErrorBoundary extends Component<
+ { children: ReactNode; windowTitle: string; onClose: () => void },
+ WindowErrorBoundaryState
+> {
+ constructor(props: {
+ children: ReactNode;
+ windowTitle: string;
+ onClose: () => void;
+ }) {
+ super(props);
+ this.state = { hasError: false };
+ }
+
+ static getDerivedStateFromError(error: Error): WindowErrorBoundaryState {
+ return { hasError: true, error };
+ }
+
+ componentDidCatch(error: Error, errorInfo: React.ErrorInfo) {
+ console.error(
+ `Window "${this.props.windowTitle}" crashed:`,
+ error,
+ errorInfo,
+ );
+ }
+
+ render() {
+ if (this.state.hasError) {
+ return (
+
+
+
+
+
+
+ Window Crashed
+
+
+ {this.state.error?.message ||
+ "An unexpected error occurred in this window."}
+
+
+
+
+
+
+ );
+ }
+
+ return this.props.children;
+ }
+}
+
+export function WindowRenderer({ window, onClose }: WindowRendererProps) {
+ let content: ReactNode;
+
+ try {
+ switch (window.appId) {
+ case "nip":
+ content = ;
+ break;
+ case "feed":
+ content = ;
+ break;
+ case "win":
+ content = ;
+ break;
+ case "kind":
+ content = ;
+ break;
+ case "man":
+ content = ;
+ break;
+ case "req":
+ content = (
+
+ );
+ break;
+ case "open":
+ content = ;
+ break;
+ case "profile":
+ content = ;
+ break;
+ case "encode":
+ content = ;
+ break;
+ case "decode":
+ content = ;
+ break;
+ case "relay":
+ content = ;
+ break;
+ default:
+ content = (
+
+ Unknown app: {window.appId}
+
+ );
+ }
+ } catch (error) {
+ content = (
+
+
+
+
+
+
+ Failed to render window
+
+
+ {error instanceof Error
+ ? error.message
+ : "An unexpected error occurred"}
+
+
+
+
+
+ );
+ }
+
+ return (
+
+ {content}
+
+ );
+}
diff --git a/src/components/WindowTitle.tsx b/src/components/WindowTitle.tsx
new file mode 100644
index 0000000..64a1342
--- /dev/null
+++ b/src/components/WindowTitle.tsx
@@ -0,0 +1,23 @@
+import { MosaicWindow, MosaicBranch } from "react-mosaic-component";
+import { WindowInstance } from "@/types/app";
+import { WindowToolbar } from "./WindowToolbar";
+import { WindowRenderer } from "./WindowRenderer";
+
+interface WindowTileProps {
+ id: string;
+ window: WindowInstance;
+ path: MosaicBranch[];
+ onClose: (id: string) => void;
+}
+
+export function WindowTile({ id, window, path, onClose }: WindowTileProps) {
+ return (
+ onClose(id)} />}
+ >
+ onClose(id)} />
+
+ );
+}