Three new wires from page to shell and back, proving the bridge end to
end. The dashboard placeholder now follows the system color-scheme via
the full chain: page-side dashShell.call('getTheme') → WebKit script
message handler → DBus method on the shell-owned service → gsettings
read → return value back up. ThemeChanged proves the reverse direction:
the shell watches color-scheme and emits a DBus signal that the
container forwards to the page as a CustomEvent.
extension/src/dbus-service.ts
Owns top.hangmanlab.Dashward.Shell on the session bus; exports
GetTheme (reads org.gnome.desktop.interface::color-scheme, maps
prefer-dark→dark / else→light); emits ThemeChanged on the gsettings
changed signal. Bus acquisition is async, so the constructor takes an
onReady callback that fires from the bus_acquired handler.
extension/src/extension.ts
Sequencing: warden → guard → dbus, and only after dbus_acquired
callback fires do we spawn ContainerSupervisor. Disable order is
reversed (container first so its DBus proxy stops calling the service
before we unown the bus name).
container/src/dbus-client.ts
Thin GJS proxy wrapper around the Shell interface; exposes a typed
getTheme() / onThemeChange(cb) API.
container/src/bridge.ts
Registers a `shellCall` UCM script-message handler; parses
{id, method, args} JSON from the page, dispatches to invoke(), and
feeds the result back via evaluate_javascript. Shell signals are
forwarded to the page via window.__dashShell__._onSignal.
container/runtime/runtime.ts
Installs window.__dashShell__ at script start, calls getTheme() on
load and applies the result to <html data-theme>, listens for the
ThemeChanged CustomEvent.
container/runtime/dashboard.html + esbuild.config.js
Switched the runtime bundle from `format: 'esm'` to IIFE so it loads
as a classic <script> -- WebKitGTK silently refuses `<script
type="module">` over file:// for ES module resolution. Added an
inline error catcher that surfaces JS errors in the visible boot
diagnostic div instead of failing silently.
container/src/main.ts
Construct ShellClient + Bridge before load_uri so the very first
page-side dashShell.call() has a handler waiting. Added load-changed
/ load-failed signal logging for future diagnosis.
Verified on ubuntu2504-test VM: enable produces clean log chain through
"container window arrived", page renders with "P4 — shell bridge
online" placeholder and the correct system theme on first paint, manual
`gsettings set ... color-scheme prefer-dark` flips the placeholder
background live.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
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>
Replace the workspace-warden.ts stub with a real implementation matching
design §5:
- On enable: snapshot org.gnome.mutter::dynamic-workspaces and
org.gnome.desktop.wm.preferences::num-workspaces, persist to
~/.local/state/dashward/workspace-warden.json, then disable dynamic
mode and append a new workspace as the dashboard slot. num-workspaces
is updated to match the new count so external observers stay in sync.
- Position invariant: connect to workspace-manager's workspaces-reordered,
workspace-added, and workspace-removed signals; whenever the dashboard
is no longer last, reorder it back. If something external removes the
dashboard, append a replacement.
- Defensive guards: external changes to num-workspaces or
dynamic-workspaces are clamped back; a `suppressGuard` flag avoids
feedback loops between our own writes and our own signal handlers.
- On disable: remove the dashboard workspace, restore both gsettings,
delete the state file.
Supporting infrastructure:
- util/logger.ts: console.log/warn/error wrappers with [Dashward] prefix.
- util/state-store.ts: load/save/clear JSON state under XDG_STATE_HOME.
- types/globals.d.ts: minimal Shell.Global declaration covering
workspace_manager / display / get_current_time().
Build chain fixes uncovered while wiring P1:
- Replace placeholder @girs/* versions in extension/ and container/
package.json with real published versions from npm (gnome-shell@50.0.0,
meta-16@16.0.0-4.0.0, etc.). Add the required @girs/gio/glib/gobject
packages so resolution actually succeeds.
- Set tsconfig `types` arrays to include each girs `/ambient` entry so
the `gi://*` and `resource:///*` module specifiers resolve.
- Add `override` modifiers on Extension.enable/disable (required under
the base tsconfig's noImplicitOverride).
- Fix workspace iteration to use get_workspace_by_index in a loop
instead of the non-existent get_workspaces() — Meta 16 doesn't expose
the bulk getter.
Verified: `pnpm -r build` and `pnpm -r exec tsc --noEmit` are both clean.
Functional verification against a real GNOME session is pending P2 — the
extension cannot be loaded yet because we haven't packaged it for the
test VM.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Bootstrap the Dashward repo per arch/UBUNTU-DASHBOARD-SPACE.md:
- pnpm-workspaces monorepo (sdk, extension, container, widgets-builtin/*)
- GNOME extension stub (metadata.json, src/*.ts placeholders for warden,
guard, supervisor, entry UX, DBus service)
- WebKit container stub (GJS main + page-side runtime + dashboard.html)
- TypeScript widget SDK (defineWidget + types)
- Builtin clock widget as the first SDK consumer example
- DBus interface XML (proto/shell.iface.xml) and shared types
- esbuild configs for extension and container; tsc for SDK
- Design doc copied in at repo root for discoverability
No functional logic yet -- all components are placeholders that compose
in extension.ts so the build chain can be exercised. P1 (workspace
warden) starts next.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>