Files
grimoire/src/root.tsx
Alejandro 59fdfc5611 Add route for command results in popup window (#93)
* feat: Add pop-out command route for standalone window rendering

Add a new /run route that allows windows to be opened in separate
browser windows/tabs without affecting the main workspace layout.

Changes:
- Add RunCommandPage component for /run?cmd=<command> route
- Add Pop Out button to WindowToolbar (ExternalLink icon)
- Parse command from URL query parameter and render result
- Construct minimal WindowInstance for rendering
- Display command string in header with clean minimal UI

This enables users to pop out any window into a separate browser
context while maintaining the main workspace layout, useful for
multi-monitor setups or keeping reference windows visible.

* refactor: Remove header from pop-out command page

Simplify RunCommandPage to only show the window renderer without
any additional UI chrome. This provides a cleaner, more focused
experience for popped-out windows.

* refactor: Move pop-out action to window menu dropdown

Move the pop-out button from a standalone icon to the three-dot
menu dropdown to reduce toolbar clutter. The menu now always
appears since pop-out is always available.

* feat: Add AppShell header to pop-out command page

Wrap RunCommandPage with AppShell (hideBottomBar) to show the header
with user menu and command launcher, matching the behavior of NIP-19
preview pages.

When a command is launched from the /run page, it navigates to the
main dashboard (/) where the window system exists.

---------

Co-authored-by: Claude <noreply@anthropic.com>
2026-01-14 17:30:56 +01:00

72 lines
1.7 KiB
TypeScript

import { createBrowserRouter, RouterProvider } from "react-router";
import { AppShell } from "./components/layouts/AppShell";
import DashboardPage from "./components/pages/DashboardPage";
import SpellbookPage from "./components/pages/SpellbookPage";
import Nip19PreviewRouter from "./components/pages/Nip19PreviewRouter";
import RunCommandPage from "./components/pages/RunCommandPage";
const router = createBrowserRouter([
{
path: "/",
element: (
<AppShell>
<DashboardPage />
</AppShell>
),
},
{
path: "/run",
element: (
<AppShell hideBottomBar>
<RunCommandPage />
</AppShell>
),
},
{
path: "/preview/:actor/:identifier",
element: (
<AppShell>
<SpellbookPage />
</AppShell>
),
},
// NIP-19 identifier preview route - must come before /:actor/:identifier catch-all
{
path: "/:identifier",
element: (
<AppShell hideBottomBar>
<Nip19PreviewRouter />
</AppShell>
),
// Only match single-segment paths that look like NIP-19 identifiers
loader: ({ params }) => {
const id = params.identifier;
if (
!id ||
!(
id.startsWith("npub1") ||
id.startsWith("note1") ||
id.startsWith("nevent1") ||
id.startsWith("naddr1")
)
) {
throw new Response("Not Found", { status: 404 });
}
return null;
},
},
// Catch-all for two-segment paths (spellbooks, etc.)
{
path: "/:actor/:identifier",
element: (
<AppShell>
<SpellbookPage />
</AppShell>
),
},
]);
export default function Root() {
return <RouterProvider router={router} />;
}