feat: P3 — WebKit container process pinned to dashboard

Dashward now actually renders content on the dashboard workspace. The
container is a separate gjs subprocess (so a WebKit crash can't take
down gnome-shell), and the extension pins its window to the dashboard
slot via the WindowGuard whitelist.

container/src/main.ts: tiny GJS bootstrap that creates a borderless
fullscreen Gtk.Window with a WebKit2 WebView and loads dashboard.html
from a runtime directory passed in via argv. `GLib.set_prgname` happens
before any GTK init so Wayland's xdg-shell app_id matches
`top.hangmanlab.dashward.container` -- that's the wm_class fingerprint
the extension matches against.

extension/src/container-supervisor.ts: spawn the container via
Gio.Subprocess; pump its stdout/stderr into journal under `[container
stdout|stderr]` tags so we can diagnose WebKit crashes without
attaching; watch display::window-created for the app_id match; on
arrival, whitelist with WindowGuard before moving to dashboard (so the
move's window-added doesn't bounce); make_fullscreen; clear the cached
ref on the window's `unmanaged` signal. Dispose SIGTERMs the
subprocess. P3 explicitly skips auto-restart / exponential backoff and
DBus signaling -- those land in P4.

container/runtime/runtime.ts + styles.css: a "Dashward" placeholder
card on the 12-column grid so the dashboard workspace is visually
distinct from a regular workspace; widget mounting / edit mode is P5+.

Verified on the ubuntu2504-test VM: extension enables cleanly, dashboard
shows the placeholder, switching to/from dashboard works, ding's window
is still ignored. MESA/EGL stderr lines are VM-only software-rendering
fallback noise (no virgl).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
h z
2026-05-23 00:22:51 +01:00
parent 5b871b8ecb
commit e1ae88948e
5 changed files with 255 additions and 18 deletions

View File

@@ -1,6 +1,7 @@
// Stub: page-side runtime (design §11).
// Loads layout.json, instantiates widgets in iframes on the 12-column grid,
// handles edit mode, persists changes via the shell bridge.
// Page-side runtime (design §11). For P3 the grid is just decoration —
// it renders a "no widgets yet" placeholder so the dashboard workspace
// has visible content. Real widget loading / edit mode / layout
// persistence come in P5+.
declare global {
interface Window {
@@ -10,6 +11,18 @@ declare global {
}
}
console.info('[dashward-runtime] P0 stub loaded');
const grid = document.getElementById('grid');
if (grid) {
const placeholder = document.createElement('section');
placeholder.className = 'placeholder';
placeholder.innerHTML = `
<h1>Dashward</h1>
<p class="hint">P3 — empty dashboard.</p>
<p class="meta">No widgets installed yet. Widget SDK + edit mode arrive in P5.</p>
`;
grid.appendChild(placeholder);
}
console.info('[dashward-runtime] P3 placeholder mounted');
export {};

View File

@@ -1,16 +1,28 @@
:root {
--bg: #f5f5f7;
--fg: #1c1c1e;
--fg-muted: rgba(28, 28, 30, 0.55);
--grid-gap: 16px;
}
[data-theme="dark"] {
--bg: #0f0f12;
--fg: #f0f0f5;
--fg-muted: rgba(240, 240, 245, 0.55);
}
html, body { margin: 0; padding: 0; height: 100%; background: var(--bg); color: var(--fg); }
body { font-family: ui-sans-serif, system-ui, -apple-system, sans-serif; }
html, body {
margin: 0;
padding: 0;
height: 100%;
background: var(--bg);
color: var(--fg);
}
body {
font-family: ui-sans-serif, system-ui, -apple-system, 'Inter', sans-serif;
-webkit-font-smoothing: antialiased;
}
#grid {
display: grid;
@@ -20,3 +32,32 @@ body { font-family: ui-sans-serif, system-ui, -apple-system, sans-serif; }
min-height: 100%;
box-sizing: border-box;
}
.placeholder {
grid-column: 1 / -1;
display: grid;
place-items: center;
align-content: center;
gap: 12px;
min-height: 80vh;
text-align: center;
}
.placeholder h1 {
font-size: clamp(3rem, 8vw, 6rem);
font-weight: 200;
letter-spacing: -0.02em;
margin: 0;
}
.placeholder .hint {
font-size: 1.25rem;
margin: 0;
opacity: 0.7;
}
.placeholder .meta {
font-size: 0.9rem;
margin: 0;
color: var(--fg-muted);
}

View File

@@ -1,10 +1,51 @@
// Stub: see design §7 — WebKit kiosk bootstrap.
// Creates a GtkWindow, attaches a WebKitWebView, loads dashboard.html,
// connects to the Shell DBus service.
// Dashward web container — GJS bootstrap.
// Spawned by the extension's ContainerSupervisor as a separate process so
// a crash here cannot bring down gnome-shell.
//
// argv[0] (after the script): the runtime directory containing
// dashboard.html, runtime.js, styles.css. The directory is passed by the
// extension so we don't have to guess our install path.
import GLib from 'gi://GLib';
import Gtk from 'gi://Gtk?version=3.0';
import WebKit2 from 'gi://WebKit2?version=4.1';
import System from 'system';
GLib.set_prgname('dashward-container');
log('[dashward-container] P0 stub started');
// Wayland's xdg-shell app_id is derived from prgname for plain GTK
// windows (i.e. not a GApplication). Set this BEFORE any GTK init so
// the compositor labels our top-level correctly -- the extension uses
// this app_id to identify and pin our window to the dashboard workspace.
const APP_ID = 'top.hangmanlab.dashward.container';
GLib.set_prgname(APP_ID);
GLib.set_application_name('Dashward');
declare function log(msg: string): void;
const args = System.programArgs;
const runtimeDir = args[0];
if (!runtimeDir) {
printerr('dashward-container: missing runtime dir argument');
System.exit(2);
}
Gtk.init(null);
const window = new Gtk.Window({
type: Gtk.WindowType.TOPLEVEL,
title: 'Dashward',
decorated: false,
});
// X11 fallback for app_id; harmless on Wayland.
window.set_wmclass(APP_ID, APP_ID);
window.set_default_size(800, 600);
const webview = new WebKit2.WebView();
window.add(webview);
const indexUri = `file://${runtimeDir}/dashboard.html`;
webview.load_uri(indexUri);
print(`dashward-container: loading ${indexUri}`);
window.connect('destroy', () => Gtk.main_quit());
window.show_all();
window.fullscreen();
Gtk.main();