diff --git a/README.md b/README.md index e902dab..8551080 100644 --- a/README.md +++ b/README.md @@ -35,10 +35,10 @@ heatmap**, a gym SBD panel and links to friends' sites. hidden `ssh` easter egg. - **Live service status** — each self-hosted service is health-checked through the site's own API; Minecraft is checked through mcstatus.io. -- **Embedded live services** — opening a self-hosted service (Llama.cpp/Open WebUI, - Jellyfin, Navidrome, GitDXD) from the desktop shows the real site inside the window (an - iframe). The service must allow framing; SMP (Discord) keeps a link card because Discord - sends `X-Frame-Options: DENY`. +- **Embedded live services** — opening a frameable self-hosted service (Llama.cpp/Open WebUI, + Jellyfin) from the desktop shows the real site inside the window (an iframe). Services that + send `X-Frame-Options` / `frame-ancestors` can't be framed, so they keep a link card or their + own app card instead (Navidrome sends `DENY`, Forgejo/GitDXD `SAMEORIGIN`, Discord `DENY`). - **Minecraft-style SMP window** — the SMP shortcut opens a join menu modelled on Minecraft's multiplayer screen, with live status, player count, MOTD and version pulled from mcstatus.io through the site's own ping API. diff --git a/opencode.md b/opencode.md index 2d335fb..29e4b62 100644 --- a/opencode.md +++ b/opencode.md @@ -267,14 +267,16 @@ list reordering, not free placement. Don't confuse the two. redirects. **Adding a service or friend host means adding it to `ALLOWED_HOSTS` in `api/ping/route.ts`**, otherwise its status dot reads "Dead". Do not turn this route back into an open fetch proxy. -- Service shortcuts show the real site in an iframe via - `createWebsiteApp({ embed: true })` (the shortcut's `embed` flag). It only - works for sites that don't send `X-Frame-Options` / a `frame-ancestors` CSP. - `embed` is set on Llama.cpp (Open WebUI), Jellyfin, Navidrome and GitDXD; SMP - (Discord) is excluded because Discord sends `DENY`. Navidrome (`DENY`) and - GitDXD (`SAMEORIGIN`) still need those headers stripped at the edge (a - Cloudflare "Modify Response Header" rule or nginx) before their frames render. - Always verify headers before enabling `embed` on a new service. +- Service shortcuts can show the real site in an iframe via + `createWebsiteApp({ embed: true })` (the shortcut's `embed` flag), but the + browser refuses to frame a site that sends `X-Frame-Options` / + `frame-ancestors`, leaving a blank window. `embed` is only set on Llama.cpp + (Open WebUI) and Jellyfin, which allow framing. Navidrome (`DENY`) and + Forgejo/GitDXD (`SAMEORIGIN`) are deliberately **not** embedded: Navidrome + falls back to the link card and GitDXD opens its dedicated app card. To embed + them live you must strip those headers at the edge (Cloudflare "Modify + Response Header" or nginx) and accept the clickjacking risk. Always verify + headers before enabling `embed` on a new service. - Reduced motion is handled with a near-zero `animation-duration` (not `animation: none`) so `animationend` still fires and windows unmount. diff --git a/src/app/components/WindowsDesktop.tsx b/src/app/components/WindowsDesktop.tsx index db82b06..586eb1b 100644 --- a/src/app/components/WindowsDesktop.tsx +++ b/src/app/components/WindowsDesktop.tsx @@ -39,13 +39,18 @@ interface IconEntry { onOpen: () => void; } +// `embed: true` renders the real site in an iframe, which the browser refuses +// (blank window) when the server sends X-Frame-Options / frame-ancestors. +// Llama.cpp and Jellyfin allow framing; Navidrome sends `X-Frame-Options: DENY` +// and Forgejo (GitDXD) sends `SAMEORIGIN`, so those two use their link card or +// the dedicated GitDXD app instead. const SERVICE_SHORTCUTS: Shortcut[] = [ { id: "svc-proxmox", icon: "🖥️", label: "Proxmox", appId: "alacritty", color: "#1257a0", desc: "Server Management" }, { id: "svc-ai", icon: "🦾", label: "Llama.cpp", url: "https://ai.dvorakmortonxd.dev/", color: "#157347", desc: "Local AI Server", embed: true }, { id: "svc-jellyfin", icon: "🎬", label: "Jellyfin", url: "https://jelly.dvorakmortonxd.dev/", color: "#6a4fb3", desc: "Local Media Server", embed: true }, { id: "svc-smp", icon: "🎮", label: "SMP", appId: "smp", color: "#c0392b", desc: "My Survival Minecraft Server" }, - { id: "svc-navidrome", icon: "🎸", label: "Navidrome", url: "https://navidrome.dvorakmortonxd.dev/", color: "#b3005f", desc: "Music Streaming", embed: true }, - { id: "svc-gitdxd", icon: "🐙", label: "GitDXD", url: "https://git.dvorakmortonxd.dev", color: "#6a4fb3", desc: "Best Git Hosting OAT", embed: true }, + { id: "svc-navidrome", icon: "🎸", label: "Navidrome", url: "https://navidrome.dvorakmortonxd.dev/", color: "#b3005f", desc: "Music Streaming" }, + { id: "svc-gitdxd", icon: "🐙", label: "GitDXD", appId: "gitdxd", color: "#6a4fb3", desc: "Best Git Hosting OAT" }, ]; const FRIEND_SHORTCUTS: Shortcut[] = friends.map((f, i) => ({ diff --git a/src/app/components/apps.tsx b/src/app/components/apps.tsx index 5d2b3c4..7bfb964 100644 --- a/src/app/components/apps.tsx +++ b/src/app/components/apps.tsx @@ -875,7 +875,7 @@ export const APPS: AppDef[] = [ { id: "projects", title: "Projects", icon: "🎮", color: "#b3005f", width: 860, height: 560, content: }, { id: "hardware", title: "Hardware", icon: "💻", color: "#1257a0", width: 860, height: 560, content: }, { id: "friends", title: "Friends", icon: "🫂", color: "#c0392b", width: 780, height: 560, content: }, - { id: "gitdxd", title: "GitDXD", icon: "🐙", color: "#333333", width: 780, height: 560, content: }, + { id: "gitdxd", title: "GitDXD", icon: "🐙", color: "#333333", width: 780, height: 560, url: "https://git.dvorakmortonxd.dev", content: }, { id: "contributions", title: "Contributions", icon: "📊", color: "#157347", width: 900, height: 470, content: }, { id: "gym", title: "Gym SBD", icon: "🏋️", color: "#c0392b", width: 620, height: 380, content: }, { id: "revival", title: "Revival Inc.", icon: "🧟", color: "#6a4fb3", width: 740, height: 560, content: },