- GNOME Shell 49 development environment (Wayland session)
- TypeScript compiler (
tsc) installed system-wide (v5.0+) glib-compile-schemas(part oflibglib2.0-dev/glib2-devel)mutter-devkitfor nested testing sessions (mutter-dev-binon Ubuntu/Debian)- Git (for submodule checkout)
git clone --recurse-submodules https://github.com/kirushik/kodecanter.git
cd kodecanter
make check # verify types resolve
make build # compile to dist/src/ TypeScript source (compiles to dist/)
extension.ts Extension lifecycle (enable/disable)
prefs.ts Preferences UI (libadwaita)
windowTracker.ts Window creation/title/destruction signals
decorationManager.ts Orchestrates borders, badges, overlays per window
borderWidget.ts CSS border frame (St.Bin child of WindowActor)
overlayWidget.ts CSS color overlay (St.Bin child of WindowActor)
scaleAwareFrameEffect.ts Clutter.Effect that draws compensated border at small paint scales
colorResolver.ts Hash-based color assignment (pure, no gi:// imports)
constants.ts Shared constants (pure, no gi:// imports)
schemas/ GSettings schema (compiled at build time)
types/
girs/ git submodule: @girs type definitions (gjsify/types)
gnome-shell/ git submodule: GNOME Shell types (gjsify/gnome-shell)
local/ hand-maintained .d.ts shims for Extension/ExtensionPreferences
docs/
past/ superseded design documents (historical reference)
future/ feature ideas and improvement plans
sessions/ engineering session logs
dist/ build output (gitignored)
| Command | What it does |
|---|---|
make check |
Typecheck only — fast, run after every change |
make build |
tsc + compile GSettings schema + copy assets to dist/ |
make install |
Build + copy dist/ to ~/.local/share/gnome-shell/extensions/ |
make dev |
Build + install + launch a nested GNOME Shell session via --devkit |
make pack |
Build + zip for distribution |
make clean |
Remove dist/, zip, compiled schemas |
Use make dev to test extension loading, preferences UI, and catch JS errors
without disrupting your session:
- Edit source in
src/ make check— catch type errors earlymake dev— launches a nested GNOME Shell via--devkit- Inside the nested session, enable the extension:
gnome-extensions enable kodecanter@kirushik.github.io - Verify the extension loads without errors (check the nested shell's log output)
- Test the preferences UI:
gnome-extensions prefs kodecanter@kirushik.github.io
Note: Zed (and most GUI apps) cannot easily run inside the nested session — they connect to the parent compositor's Wayland display instead. This is a known limitation of nested Wayland compositors.
make devis for testing shell-side behavior only.
To test actual Zed window decorations:
make check— catch type errorsmake install— build and copy to~/.local/share/gnome-shell/extensions/- Log out and log back in (required on Wayland for GNOME Shell to reload extensions)
- Enable the extension if not already enabled:
gnome-extensions enable kodecanter@kirushik.github.io - Open Zed with a project, verify decorations appear
- Watch logs:
journalctl -f -o cat /usr/bin/gnome-shell | grep Kodecanter
Types come from git submodules, not npm. There is no node_modules/ directory.
types/girs/maps to@girs/*via a tsconfigpathswildcardtypes/local/extension.d.tsandtypes/local/prefs.d.tsare hand-maintained shims forresource:///extension APIs (needed because the gjsify/gnome-shell.tssource files have resolution issues with tsc 5.0 bundler mode)ambient.d.tsimports allgi://ambient declarationstypes/gnome-shell/.../global.d.tsprovides theglobalobject type (included via tsconfig)
See docs/sessions/session-2025-02-14-initial-setup.md for the full story on why this setup exists and what problems it solves.
global.display 'window-created'
→ WindowTracker checks WM_CLASS (dev.zed.Zed)
→ if title available: parse project name → callback
→ if title null: defer via notify::title
→ on title change: parse → onWindowTracked / onWindowUpdated
Meta.Window 'unmanaging'
→ onWindowLost → DecorationManager.removeDecorations()
Meta.Window 'notify::fullscreen'
→ onWindowFullscreen → hide/show decorations
Meta.Window 'size-changed'
→ onWindowSizeChanged → reposition badge
| Type | Implementation | Attached to |
|---|---|---|
| Border | St.Bin with CSS border (BorderWidget) |
Meta.WindowActor child |
| Badge | St.Label |
Meta.WindowActor child |
| Overlay | St.Bin with CSS background (OverlayWidget) |
Meta.WindowActor child |
| Thumbnail frame | Clutter.Effect with ColorNode (ScaleAwareFrameEffect) |
Meta.WindowActor effect |
The first three propagate through Clutter.Clone (Overview, Alt-Tab, dock previews) automatically but become invisible at small scales. The thumbnail frame effect uses Clutter.Effect with is_in_clone_paint() to detect clone context and draws a scale-compensated border at small scales (workspace thumbnails, dock previews).
project name → DJB2 hash → golden-angle hue distribution → HSL(hue, 75%, 50%) → hex/RGBA
Override map (from GSettings color-overrides) is checked first; hash is the fallback.
Unit tests for pure modules (colorResolver.ts, parseZedTitle from windowTracker.ts) are planned but not yet implemented. These modules have no gi:// imports and can run under Node.js.
Manual testing uses two modes: make dev for quick shell-side checks (extension loading, prefs UI) and make install + logout/login for full integration testing with Zed. See Development workflow above.
Mtk.Rectangle, notMeta.Rectangle(removed in GNOME 47)global.compositor.get_window_actors(), notglobal.get_window_actors()(moved in GNOME 48)Cogl.ShaderType.FRAGMENT, notClutter.ShaderType.FRAGMENT_SHADER(enum moved from Clutter to Cogl)gnome-shell --devkit, notgnome-shell --nested(removed in GNOME 49; requiresmutter-devkit)- Wayland-only — no X11 APIs