feat: scope + un-bundle the plugin; @unom/ui SPA; custom-emulator editor
Reliability + correctness pass on the packaging and UI. Packaging — drop the self-contained bundle: - Rename to `@punktfunk/plugin-rom-manager` (scoped). An unscoped package can't be split across registries, which is the only reason bundling effect + the SDK into every plugin seemed necessary. Scoped names resolve from one scope-map, so the plugin depends on `@punktfunk/host` + `effect` as SHARED (hoisted) deps — no duplication, no ~1 MB effect copy per plugin. Build is back to plain `tsc`. (Runner-side discovery of `@punktfunk/plugin-*` shipped in @punktfunk/host 0.1.1.) UI — use the console's design system so it reads as family: - SPA rebuilt on @unom/style tokens + Tailwind v4 + the console's exact theme (violet chrome, Geist, card/button vocabulary). Build-time only → static assets in dist/ui, no @unom runtime dep. Skips @unom/ui's AnimatedButton/Card, which statically import ~7 MB of UI sound assets — same look, 416 KB dist/ui. - Add a custom-emulator editor (Emulators page) writing config.emulators — you can now add a launcher Punktfunk doesn't ship a definition for. - Fix brand capitalization to "Punktfunk" in all UI copy + docs. Install docs + CI updated for the scoped `bun add @punktfunk/plugin-rom-manager` flow. 48 engine tests green; backend + UI typecheck + biome clean. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
+35
-23
@@ -1,5 +1,6 @@
|
||||
import { Gamepad2 } from "lucide-react";
|
||||
import { useEffect, useState } from "react";
|
||||
import { ToastHost } from "./components.js";
|
||||
import { cn } from "./lib/utils.js";
|
||||
import { Emulators } from "./pages/Emulators.js";
|
||||
import { Games } from "./pages/Games.js";
|
||||
import { Setup } from "./pages/Setup.js";
|
||||
@@ -42,29 +43,40 @@ export const App = () => {
|
||||
const Page = TABS.find((t) => t.id === tab)?.Page ?? Setup;
|
||||
|
||||
return (
|
||||
<ToastHost>
|
||||
<div className="app">
|
||||
<div className="topbar">
|
||||
<span className="logo">🎮</span>
|
||||
<h1>ROM Manager</h1>
|
||||
<span className="subtle">
|
||||
— scan ROMs into your punktfunk library
|
||||
</span>
|
||||
</div>
|
||||
<div className="tabs">
|
||||
{TABS.map((t) => (
|
||||
<button
|
||||
type="button"
|
||||
key={t.id}
|
||||
className={`tab${t.id === tab ? " active" : ""}`}
|
||||
onClick={() => go(t.id)}
|
||||
>
|
||||
{t.label}
|
||||
</button>
|
||||
))}
|
||||
<div className="mx-auto max-w-5xl px-6 pb-16 pt-6">
|
||||
<header className="flex items-center gap-3">
|
||||
<span className="grid size-9 place-items-center rounded-card bg-primary/15 text-primary">
|
||||
<Gamepad2 className="size-5" />
|
||||
</span>
|
||||
<div>
|
||||
<h1 className="text-lg font-semibold leading-tight">ROM Manager</h1>
|
||||
<p className="text-sm text-muted-foreground">
|
||||
Scan ROMs into your Punktfunk library
|
||||
</p>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<nav className="mt-6 flex flex-wrap gap-1 border-b border-border">
|
||||
{TABS.map((t) => (
|
||||
<button
|
||||
type="button"
|
||||
key={t.id}
|
||||
onClick={() => go(t.id)}
|
||||
className={cn(
|
||||
"-mb-px border-b-2 px-4 py-2 text-sm font-medium transition-colors",
|
||||
t.id === tab
|
||||
? "border-primary text-foreground"
|
||||
: "border-transparent text-muted-foreground hover:text-foreground",
|
||||
)}
|
||||
>
|
||||
{t.label}
|
||||
</button>
|
||||
))}
|
||||
</nav>
|
||||
|
||||
<main className="mt-6 flex flex-col gap-6">
|
||||
<Page />
|
||||
</div>
|
||||
</ToastHost>
|
||||
</main>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user