feat(plugins): full-screen (immersive) plugin screens via manifest opt-in (#590)

DAW-style plugin UIs (e.g. a practice studio) need the whole viewport, not a
scrolling content page below the v3 topbar — embedded in the shell they get
cut off at the bottom with excess padding up top.

Add an opt-in top-level `"fullscreen": true` plugin.json field, surfaced as the
`fullscreen` boolean on /api/plugins (mirrors the settings_category plumbing in
plugins/__init__.py). When a fullscreen plugin's screen is active, static/v3/
shell.js toggles `html.fb-immersive` from syncActive() so it tracks every
navigation incl. deep-link; static/v3/v3.css then hides the topbar, collapses
the sidebar to a functional icon rail (kept reachable — Escape is bound only on
player/settings scopes, so a fully hidden sidebar would trap the user), and
lets the active plugin screen fill #v3-main. Mirrors the existing
ss-follower-pre chrome-hide pattern. Additive + opt-in: plugins without the
flag are unaffected.

Test: tests/test_plugins.py::test_fullscreen_flag_parsed_from_manifest


Claude-Session: https://claude.ai/code/session_01BmWopMsRjdZyD6RwmZAQBv

Signed-off-by: ChrisBeWithYou <christian.a.cowan@gmail.com>
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
Co-authored-by: byrongamatos <xasiklas@gmail.com>
This commit is contained in:
ChrisBeWithYou
2026-06-25 00:02:16 +02:00
committed by GitHub
co-authored by Claude Opus 4.8 byrongamatos
parent 8b4c9b0050
commit 4c3ec2ff66
5 changed files with 106 additions and 2 deletions
+13
View File
@@ -1350,6 +1350,13 @@ def load_plugins(app: FastAPI, context: dict, progress_cb=None, route_setup_fn=N
_icon = "assets/thumb.png"
except OSError:
_icon = None
# Immersive (full-screen) screen opt-in. A plugin that declares a
# top-level `"fullscreen": true` gets the whole content area when its
# screen is active: the v3 shell hides the topbar and collapses the
# sidebar to an icon rail (see static/v3/shell.js + v3.css). For
# DAW-style plugin UIs that need the viewport, not a scrolling content
# page. Strict `is True` so a stray truthy value can't silently opt in.
_fullscreen = manifest.get("fullscreen") is True
return {
"id": plugin_id,
"name": manifest.get("name", plugin_id),
@@ -1368,6 +1375,9 @@ def load_plugins(app: FastAPI, context: dict, progress_cb=None, route_setup_fn=N
"has_script": bool(manifest.get("script")),
"has_settings": bool(manifest.get("settings")),
"settings_category": _settings_category,
# Drives the v3 shell's immersive (full-screen) mode for this
# plugin's screen. False unless the manifest declares it explicitly.
"fullscreen": _fullscreen,
"has_tour": _is_valid_tour_manifest(manifest.get("tour")),
# `styles` is an optional relpath (under the plugin's assets/) to a
# compiled, preflight-off stylesheet the frontend injects as a
@@ -2080,6 +2090,8 @@ def register_plugin_api(app: FastAPI):
"has_screen": p["has_screen"],
"has_script": p["has_script"],
"has_settings": p["has_settings"],
# v3 immersive screen opt-in (full-screen plugin UI).
"fullscreen": p.get("fullscreen", False),
# Settings-tab placement; None when the manifest's `settings`
# is absent, a bare string, or omits `category`.
"settings_category": p.get("settings_category"),
@@ -2132,6 +2144,7 @@ def register_plugin_api(app: FastAPI):
"has_script": e.get("has_script", False),
"has_settings": e.get("has_settings", False),
"settings_category": e.get("settings_category"),
"fullscreen": e.get("fullscreen", False),
"has_tour": e.get("has_tour", False),
"has_styles": e.get("has_styles", False),
"styles": e.get("styles"),