Files
punktfunk/web/src/stories/AppShell.stories.tsx
T
enricobuehler ae51276a03 feat(web): consolidate paired devices, self-contained sections, docs + lint
Web console
- Pairing/Library/Stats refactored into self-contained subsections that each own
  their own queries + mutations; a shared slot-based layout (view.tsx) is filled by
  the live page (containers) and Storybook (pure cards + fixtures) so the layout can't
  drift.
- All paired devices in one list on Pairing with a protocol column (punktfunk/1 +
  Moonlight), routing each unpair to the right endpoint; the redundant Clients page is
  removed.
- Library: overview grid split from the add/edit form into separate files.
- Login screen links out to the docs.

Docs
- "Console login password" section on every host page (apt/RPM/Bazzite/SteamOS/Windows)
  plus a new "Forgot your Password?" troubleshooting page, linked from the login screen.
- Console served as HTTP/1.1 over TLS (drop the unusable HTTP/3 advertising) across the
  Bun entry, launchers, systemd units, and packaging.

Tooling
- Biome now respects .gitignore (stops linting generated code), config migrated to
  2.5.1; all lint issues fixed cleanly.

Also includes this branch's in-progress host, Apple client, packaging, and CI changes.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-30 19:05:22 +02:00

72 lines
2.1 KiB
TypeScript

import type { Meta, StoryObj } from "@storybook/react-vite";
import {
createMemoryHistory,
createRootRoute,
createRoute,
createRouter,
RouterProvider,
} from "@tanstack/react-router";
import { AppShell } from "@/components/app-shell";
// AppShell is built from TanStack Router <Link>s, so it needs a router context.
// We stand up a throwaway in-memory router whose routes mirror the nav targets
// (so links resolve + the active highlight works) and render the shell from the
// root route. No loaders/data — purely for designing the chrome offline.
function ShellHarness({ initialPath }: { initialPath: string }) {
const rootRoute = createRootRoute({
component: () => (
<AppShell>
<div className="space-y-3">
<h1 className="text-2xl font-semibold tracking-tight">Dashboard</h1>
<p className="text-muted-foreground">
Placeholder content swap routes from the sidebar to preview the
active state.
</p>
</div>
</AppShell>
),
});
const navPaths = ["/", "/host", "/library", "/pairing", "/settings"];
const navRoutes = navPaths.map((path) =>
createRoute({
getParentRoute: () => rootRoute,
path,
component: () => null,
}),
);
// Splat so any other <Link> target still resolves without throwing.
const splat = createRoute({
getParentRoute: () => rootRoute,
path: "$",
component: () => null,
});
const router = createRouter({
routeTree: rootRoute.addChildren([...navRoutes, splat]),
history: createMemoryHistory({ initialEntries: [initialPath] }),
});
return <RouterProvider router={router} />;
}
const meta = {
title: "Shell/AppShell",
component: AppShell,
parameters: { layout: "fullscreen" },
// AppShell requires `children`; the harness supplies the real content, so this
// placeholder just satisfies the arg type.
args: { children: null },
} satisfies Meta<typeof AppShell>;
export default meta;
type Story = StoryObj<typeof meta>;
export const Dashboard: Story = {
render: () => <ShellHarness initialPath="/" />,
};
export const HostActive: Story = {
render: () => <ShellHarness initialPath="/host" />,
};