feat(store): console plugin store, index repo, and the fixes on-glass found
apple / swift (push) Successful in 1m22s
release / apple (push) Successful in 9m59s
windows-host / package (push) Successful in 9m52s
windows-msix / package (arm64, C:\Users\Public\ffmpeg-arm64, --no-default-features, aarch64-pc-windows-msvc, C:\t-a64) (push) Successful in 3m20s
apple / screenshots (push) Successful in 6m28s
windows-msix / package (x64, C:\Users\Public\ffmpeg, , x86_64-pc-windows-msvc, C:\t) (push) Successful in 3m21s
windows / build (aarch64-pc-windows-msvc) (push) Successful in 4m39s
windows / build (x86_64-pc-windows-msvc) (push) Successful in 5m45s
audit / cargo-audit (push) Successful in 2m58s
audit / bun-audit (push) Successful in 13s
ci / web (push) Successful in 52s
ci / docs-site (push) Successful in 59s
android / android (push) Has been cancelled
arch / build-publish (push) Has been cancelled
ci / bench (push) Has been cancelled
ci / rust (push) Has been cancelled
docker / build-push (--build-arg FEDORA_VERSION=44, ci, ci/fedora-rpm.Dockerfile, punktfunk-fedora44-rpm) (push) Successful in 11s
docker / build-push (ci, ci/fedora-rpm.Dockerfile, punktfunk-fedora-rpm) (push) Successful in 10s
decky / build-publish (push) Successful in 23s
docker / build-push (ci, ci/rust-ci.Dockerfile, punktfunk-rust-ci) (push) Successful in 10s
docker / build-push (ci, ci/rust-ci-noble.Dockerfile, punktfunk-rust-ci-noble) (push) Successful in 10s
docker / build-push (., web/Dockerfile, punktfunk-web) (push) Successful in 35s
docker / build-push (docs-site, docs-site/Dockerfile, punktfunk-docs) (push) Successful in 49s
flatpak / build-publish (push) Successful in 7m53s
docker / deploy-docs (push) Has been cancelled
rpm / build-publish (43, bazzite, punktfunk-fedora-rpm) (push) Has been cancelled
rpm / build-publish (44, fedora-44, punktfunk-fedora44-rpm) (push) Has been cancelled
deb / build-publish (push) Successful in 11m41s
deb / build-publish-host (push) Successful in 13m32s
apple / swift (push) Successful in 1m22s
release / apple (push) Successful in 9m59s
windows-host / package (push) Successful in 9m52s
windows-msix / package (arm64, C:\Users\Public\ffmpeg-arm64, --no-default-features, aarch64-pc-windows-msvc, C:\t-a64) (push) Successful in 3m20s
apple / screenshots (push) Successful in 6m28s
windows-msix / package (x64, C:\Users\Public\ffmpeg, , x86_64-pc-windows-msvc, C:\t) (push) Successful in 3m21s
windows / build (aarch64-pc-windows-msvc) (push) Successful in 4m39s
windows / build (x86_64-pc-windows-msvc) (push) Successful in 5m45s
audit / cargo-audit (push) Successful in 2m58s
audit / bun-audit (push) Successful in 13s
ci / web (push) Successful in 52s
ci / docs-site (push) Successful in 59s
android / android (push) Has been cancelled
arch / build-publish (push) Has been cancelled
ci / bench (push) Has been cancelled
ci / rust (push) Has been cancelled
docker / build-push (--build-arg FEDORA_VERSION=44, ci, ci/fedora-rpm.Dockerfile, punktfunk-fedora44-rpm) (push) Successful in 11s
docker / build-push (ci, ci/fedora-rpm.Dockerfile, punktfunk-fedora-rpm) (push) Successful in 10s
decky / build-publish (push) Successful in 23s
docker / build-push (ci, ci/rust-ci.Dockerfile, punktfunk-rust-ci) (push) Successful in 10s
docker / build-push (ci, ci/rust-ci-noble.Dockerfile, punktfunk-rust-ci-noble) (push) Successful in 10s
docker / build-push (., web/Dockerfile, punktfunk-web) (push) Successful in 35s
docker / build-push (docs-site, docs-site/Dockerfile, punktfunk-docs) (push) Successful in 49s
flatpak / build-publish (push) Successful in 7m53s
docker / deploy-docs (push) Has been cancelled
rpm / build-publish (43, bazzite, punktfunk-fedora-rpm) (push) Has been cancelled
rpm / build-publish (44, fedora-44, punktfunk-fedora44-rpm) (push) Has been cancelled
deb / build-publish (push) Successful in 11m41s
deb / build-publish-host (push) Successful in 13m32s
Console: a Plugins section (Browse / Installed / Sources) on a static nav entry, with install friction proportional to trust — a plain confirm for a verified entry, a warning naming the curator for an external one, and a danger dialog that makes you retype the spec for a raw package. Tier badges are permanent and follow the plugin onto its own UI page. Index: unom/punktfunk-plugin-index published and served from Gitea's raw endpoint (real HTTPS, byte-exact, no vhost to stand up) — merge to main is publish, which resolves the design's open hosting question. Four things only running it could find: - runner discovery matched @punktfunk/plugin-* only, so a third-party scoped plugin (which D8 requires) would install and never run - ...and that convention also matches @punktfunk/plugin-kit, a plugin's own framework: it listed as installed and would have been imported as a unit. Both now key off the plugins dir's top-level dependencies, with an emptied dependency list meaning 'nothing installed' rather than falling back to the naming convention - the store must not pass new flags to the runner: the scripting package ships separately and an older one reads an unknown flag's value as a package name. The host writes the bunfig scope mapping itself - ureq reports only >= 400 as Err, so a conditional request's 304 arrives as Ok with an empty body — handled as an error it made every refresh after the first verify a signature over zero bytes and sit stale Also: the console's first Tabs use exposed an @unom/ui theme gap that rendered inactive tabs invisible (caught in a browser pass, not by types). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -6,6 +6,7 @@ import {
|
||||
LibraryBig,
|
||||
MonitorPlay,
|
||||
MoreHorizontal,
|
||||
Puzzle,
|
||||
ScrollText,
|
||||
Server,
|
||||
Settings,
|
||||
@@ -29,9 +30,15 @@ const NAV = [
|
||||
{ to: "/stats", icon: GaugeCircle, label: () => m.nav_stats() },
|
||||
{ to: "/logs", icon: ScrollText, label: () => m.nav_logs() },
|
||||
{ to: "/pairing", icon: KeyRound, label: () => m.nav_pairing() },
|
||||
{ to: "/plugins", icon: Puzzle, label: () => m.nav_plugins() },
|
||||
{ to: "/settings", icon: Settings, label: () => m.nav_settings() },
|
||||
] as const;
|
||||
|
||||
// "/plugins" is the store's index route and "/plugins/<id>" a plugin's own UI, so the store entry
|
||||
// only counts as active on an exact match — otherwise it would light up alongside the plugin's own
|
||||
// nav entry below. Same reason "/" is exact.
|
||||
const EXACT: readonly string[] = ["/", "/plugins"];
|
||||
|
||||
// On phones a flat 8-tab bar is too cramped, so the first four are pinned and the rest live behind a
|
||||
// "More" tab that opens a sheet above the bar. Keep it ≤ 5 slots including "More".
|
||||
const MOBILE_PRIMARY = NAV.slice(0, 4);
|
||||
@@ -74,7 +81,7 @@ export function AppShell({ children }: { children: ReactNode }) {
|
||||
whileHover={{ scale: 1.02 }}
|
||||
whileTap={{ scale: 0.98 }}
|
||||
to={to}
|
||||
activeOptions={{ exact: to === "/" }}
|
||||
activeOptions={{ exact: EXACT.includes(to) }}
|
||||
className="group relative flex items-center gap-3 rounded-md px-3 py-2 text-sm text-muted-foreground transition-colors hover:text-foreground"
|
||||
activeProps={{
|
||||
className: "bg-primary/15 text-foreground font-medium",
|
||||
@@ -200,6 +207,7 @@ function MobileNav() {
|
||||
key={to}
|
||||
to={to}
|
||||
onClick={() => setMoreOpen(false)}
|
||||
activeOptions={{ exact: EXACT.includes(to) }}
|
||||
className={cn(tab, "rounded-md")}
|
||||
activeProps={{ className: "text-[var(--brand-light)]" }}
|
||||
>
|
||||
@@ -232,7 +240,7 @@ function MobileNav() {
|
||||
key={to}
|
||||
to={to}
|
||||
onClick={() => setMoreOpen(false)}
|
||||
activeOptions={{ exact: to === "/" }}
|
||||
activeOptions={{ exact: EXACT.includes(to) }}
|
||||
className={tab}
|
||||
activeProps={{ className: "text-[var(--brand-light)]" }}
|
||||
>
|
||||
|
||||
@@ -0,0 +1,49 @@
|
||||
// The console's Dialog IS @unom/ui's radix dialog — brand surface (material gloss + card radius)
|
||||
// with the shared close/overlay behaviour. @unom/ui ships the SURFACE only and leaves placement to
|
||||
// the app, so `DialogContent` here is the surface already wrapped in its portal + overlay and
|
||||
// centred in the viewport; everything else is re-exported unchanged.
|
||||
import {
|
||||
Dialog,
|
||||
DialogClose,
|
||||
DialogDescription,
|
||||
DialogFooter,
|
||||
DialogHeader,
|
||||
DialogOverlay,
|
||||
DialogPortal,
|
||||
DialogContent as DialogSurface,
|
||||
DialogTitle,
|
||||
DialogTrigger,
|
||||
} from "@unom/ui/dialog";
|
||||
import type { ComponentProps } from "react";
|
||||
import { cn } from "@/lib/utils";
|
||||
|
||||
const DialogContent = ({
|
||||
className,
|
||||
...props
|
||||
}: ComponentProps<typeof DialogSurface>) => (
|
||||
<DialogPortal>
|
||||
<DialogOverlay />
|
||||
<DialogSurface
|
||||
className={cn(
|
||||
"fixed left-1/2 top-1/2 z-100 flex max-h-[calc(100dvh-2rem)] w-[calc(100vw-2rem)] max-w-lg -translate-x-1/2 -translate-y-1/2 flex-col gap-4 overflow-y-auto p-6",
|
||||
"data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95",
|
||||
className,
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
</DialogPortal>
|
||||
);
|
||||
DialogContent.displayName = "DialogContent";
|
||||
|
||||
export {
|
||||
Dialog,
|
||||
DialogClose,
|
||||
DialogContent,
|
||||
DialogDescription,
|
||||
DialogFooter,
|
||||
DialogHeader,
|
||||
DialogOverlay,
|
||||
DialogPortal,
|
||||
DialogTitle,
|
||||
DialogTrigger,
|
||||
};
|
||||
@@ -0,0 +1,34 @@
|
||||
// The console's Tabs ARE @unom/ui's radix tabs, with one correction: the inactive trigger colour.
|
||||
//
|
||||
// @unom/ui styles inactive triggers `text-secondary` and the active one `data-[state=active]:
|
||||
// text-main`. Those tokens come from @unom/ui's own palette; in the console's theme `text-secondary`
|
||||
// lands on (near) the tab strip's own background, so every inactive tab renders as an invisible
|
||||
// gap — the tab bar looks like a single lonely label with dead space beside it. Caught in a browser
|
||||
// pass, not by types: the markup and the a11y roles are entirely correct.
|
||||
//
|
||||
// Same shape as the other `components/ui/*` wrappers: adapt the shared primitive to this app's
|
||||
// tokens rather than restyling it at every call site.
|
||||
import {
|
||||
Tabs,
|
||||
TabsContent,
|
||||
TabsList,
|
||||
TabsTrigger as TabsTriggerBase,
|
||||
} from "@unom/ui/tabs";
|
||||
import type { ComponentProps } from "react";
|
||||
import { cn } from "@/lib/utils";
|
||||
|
||||
const TabsTrigger = ({
|
||||
className,
|
||||
...props
|
||||
}: ComponentProps<typeof TabsTriggerBase>) => (
|
||||
<TabsTriggerBase
|
||||
className={cn(
|
||||
"text-muted-foreground hover:text-foreground data-[state=active]:text-foreground",
|
||||
className,
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
TabsTrigger.displayName = "TabsTrigger";
|
||||
|
||||
export { Tabs, TabsContent, TabsList, TabsTrigger };
|
||||
Reference in New Issue
Block a user