Version: 0.1.0 | Platform: GNOME Shell 45–49 | Date: March 2026
- What is DevWatch?
- The Core Idea
- Installation
- Local Testing
- Feature Walkthrough
- Preferences
- Understanding the Status Dot
- Data Storage
- Adding a Translation
- Development Workflow
- Troubleshooting
- Architecture Overview
DevWatch is a GNOME Shell panel extension that adds a developer-aware intelligence layer to your desktop.
It lives as a small ● DevWatch button in your top panel. Click it to open a dropdown that
shows — in one place — everything happening in your development environment:
- Which projects are running and how much CPU/RAM they use
- Which ports are bound and which processes own them
- Zombie and orphaned dev processes wasting memory
- A one-click way to save and restore your entire dev session
- A history of your recent builds with timing and resource peaks
It replaces four common terminal round-trips:
| Without DevWatch | With DevWatch |
|---|---|
lsof -i :3000 |
See "Port 3000 → backend-api (node)" in the panel |
ps aux | grep node |
Project cluster with all child processes listed |
kill $(lsof -t -i:5173) |
One-click Kill button on the port row |
| Reopen 5 terminals after a reboot | Restore a saved session snapshot |
Every other tool (htop, lsof, GNOME System Monitor) shows you a flat list of processes.
DevWatch works the other way: it starts from your projects and shows you the processes
that belong to each one.
- Window focus — when you focus a terminal or editor window, DevWatch reads its CWD via
/proc/<pid>/cwdand walks up the directory tree looking for a.gitfolder. - Process scanner — every 10 seconds DevWatch reads
/procto find all running processes, maps each one to a project root via/proc/<pid>/cwd → git root, and groups them. - Port mapper — runs
ss -Htulnpto list listening sockets, then matches each PID back to its project using the process map built in step 2.
Everything runs locally, offline, with no elevated privileges. No cloud, no telemetry.
| Tool | Purpose | Install |
|---|---|---|
| GNOME Shell 45+ | Runtime | Already on Ubuntu 22.04+ |
ss |
Port scanning | sudo apt install iproute2 |
git |
Project root detection | sudo apt install git |
glib-compile-schemas |
Build step | sudo apt install libglib2.0-dev-bin |
# 1. Clone
git clone https://github.com/Adithya-Balan/DevWatch.git
cd DevWatch
# 2. Compile schema and symlink files into GNOME's extension directory
make link
# 3. Enable the extension
gnome-extensions enable devwatch@github.io
# 4. Reload GNOME Shell (Wayland — must log out and back in)
gnome-session-quit --logout
# OR on X11 only:
# killall -SIGUSR1 gnome-shellAfter logging back in, the ● DevWatch button appears in the top-right corner of your panel.
gnome-extensions info devwatch@github.io
# Should show: State: ENABLEDmake link
gnome-extensions enable devwatch@github.io
# Log out and back inThe extension is now active. Open the panel dropdown to see it.
A nested GNOME Shell runs as a window inside your current session — if you break something it won't crash your desktop.
# From a terminal INSIDE your graphical session (not SSH):
make nestedThis opens a new window with a complete GNOME desktop inside it. Inside the nested window, open a terminal and run:
gnome-extensions enable devwatch@github.ioNote:
make nestedmust be run from a terminal that has$WAYLAND_DISPLAYset. If you're using the VS Code integrated terminal, open a regular GNOME Terminal instead,cdto the project, and runmake nestedfrom there.
# Disable, wait 1 s, re-enable — GNOME hot-reloads the extension
gnome-extensions disable devwatch@github.io && sleep 1 && gnome-extensions enable devwatch@github.ioThis works for most JS changes. If you edit metadata.json or schemas/, a full log-out/in is required.
make log
# Equivalent: journalctl -f -o cat /usr/bin/gnome-shellEvery console.log('[DevWatch] ...') call appears here immediately.
Errors appear as [DevWatch] <message>.
The ● DevWatch button sits in the top-right area of your panel.
The coloured dot to the left of "DevWatch" is a health indicator:
| Colour | Meaning |
|---|---|
| 🟢 Green | Everything looks healthy |
| 🟡 Yellow | High CPU (>80%), idle dev tools, or hot build (CPU >90%) |
| 🔴 Red | Zombie process, orphan process, or newly occupied dev port detected |
Click the button to open the dropdown. The dropdown auto-refreshes whenever you open it and also polls every 10 seconds in the background (configurable in Preferences).
Section header: ACTIVE PROJECTS
One expandable row per detected project, sorted highest CPU first:
▸ backend-api [3 proc · CPU 4.2% · RAM 312 MB]
Expand a project row to see every process inside it:
node server.js (4821) S CPU 1.2% RAM 128 MB [⧉ Copy PID]
postgres (4900) S CPU 0.8% RAM 96 MB [⧉ Copy PID]
redis-server (4981) S CPU 0.1% RAM 12 MB [⧉ Copy PID]
⎋ Open terminal here
- ⧉ Copy PID — copies the process ID to your clipboard (useful for
strace,gdb, etc.) - ⎋ Open terminal here — opens
gnome-terminalwith CWD set to the project's git root
Open a terminal, navigate to any git repository, and run a process there:
cd ~/Projects/my-app
node server.js &Within 10 seconds (or immediately when you open the dropdown) the project appears.
If no projects are detected: "No dev projects detected — Focus a terminal or editor window to detect a project"
This means: click on a terminal window that is cd'd inside a git repo — DevWatch will pick it up immediately via the focus-change signal.
Section header: ACTIVE PORTS
All listening sockets from ss -Htulnp, grouped and color-coded:
● 3000 TCP node (4821) backend-api 2h 14m [⧉ Copy PID] [Kill]
● 5173 TCP vite (9002) frontend 45m [⧉ Copy PID] [Kill]
8080 TCP python3 (4200) — 12m [⧉ Copy PID] [Kill]
- Blue dot (●) = recognised dev port (3000, 4200, 5000, 5173, 8080, 8888, …)
- No dot / dimmed = system port (shown only if "Show system ports" is enabled in Preferences)
- Runtime = how long this port has been occupied since DevWatch first saw it
- Project = the git root the owning process belongs to;
—if unknown
- ⧉ Copy PID — copies the PID of the process owning this port
- Kill — sends SIGTERM to the process; the row disappears after ~1.5 s
When a dev port (e.g. 3000) is newly occupied by a process, DevWatch fires a GNOME desktop notification:
DevWatch: Port 3000 occupied node (PID 4821) · backend-api
This fires once per (protocol, port, PID) triple — it won't spam you during polling. Disable in Preferences → Ports → "Port conflict notifications".
3000, 3001, 3002, 3003, 4000, 4200, 5000, 5001, 5173, 6006, 8000, 8080, 8081, 8888, 9000, 9090, 4173, 5432, 3306, 6379, 27017, 9200, 9300, 6443, 8001, 2375, 2376
Section header: CLEANUP CANDIDATES [Clean All (N)]
Processes that are wasting resources and are safe to terminate:
☠ node (4821) ZOMBIE 128 MB backend-api awaiting reap
⚱ nodemon (5100) ORPHAN 28 MB — [Kill]
⏸ vite (6200) IDLE 8 MB frontend [Kill]
| Icon | Type | Meaning | Can Kill? |
|---|---|---|---|
| ☠ | Zombie | Process exited but not reaped by its parent. Already dead — only the PID table entry remains. | No — killing is meaningless; parent must wait() |
| ⚱ | Orphan | A known dev tool (node, cargo, go, …) whose parent is dead/PID 1 AND has no detectable project root | Yes |
| ⏸ | Idle dev | A known dev tool at <0.5% CPU for >10 min (configurable) with no open port | Yes |
- Kill (per row) — SIGTERM the individual process
- Clean All (N) — SIGTERM all killable candidates (zombies excluded) at once
- After closing a project: dev watchers (vite, nodemon, pytest-watch) that were left running show up here
- After a crash: orphaned server processes that lost their parent shell
- End of the day: idle background services burning RAM
Section header: SESSION SNAPSHOTS [Save Now]
Your saved session snapshots, newest first:
📷 before-refactor 03 Mar 14:30 3 proj [Restore] [✕]
📷 auto 03 Mar 09:15 2 proj [Restore] [✕]
Each snapshot records:
- All active project roots + their git branch names at the time of saving
- All currently bound dev ports + which process/project owns them
- Process names in each project cluster
- Timestamp + optional label
- Save Now — captures the current state and saves to
~/.local/share/devwatch/snapshots/. Up to 20 snapshots are kept; oldest are auto-pruned. - Restore — opens a new
gnome-terminalwindow for each saved project root, pre-titled with the project name and branch. Does not restart processes — it just gets your terminals in the right places. - ✕ Delete — permanently removes the snapshot file
YYYY-MM-DD_HH-MM-SS_<label>.json
Example: 2026-03-03_14-30-00_before-refactor.json
Morning: Restore last snapshot → all terminals open in the right projects
Evening: Save Now → close laptop
Section header: BUILD PERFORMANCE
Active builds (processes currently running a build tool with >1% CPU):
⚙ cargo backend-api 1m 12s CPU 82%
⚙ npm frontend 8s CPU 45%
Recent builds (completed runs, newest first):
RECENT BUILDS
✓ cargo backend-api 1m 42s CPU 78% RAM 312 MB
✓ npm frontend 48s CPU 45% RAM 180 MB
✗ tsc frontend 4s CPU 20% RAM 64 MB
- ⚙ Active (amber) — build currently running; shows live elapsed time and current CPU%
- ✓ Completed (green) — build finished; shows total duration, peak CPU%, peak RAM
- ✗ Short (grey) — build finished in <5 s (considered incomplete or failed)
npm, yarn, pnpm, bun, webpack, vite, esbuild, swc, tsc, cargo, rustc,
go, make, cmake, ninja, gcc, g++, clang, mvn, gradle, javac, pip,
pytest, docker build, podman build, and more (35+ tools total).
Build history is saved to ~/.local/share/devwatch/build_history.json and restored every time
the extension is reloaded — so your build history survives lock screen, log-out, and reboots.
Up to 5 runs per project are kept in history. The number shown in the panel is configurable in Preferences.
Open with:
gnome-extensions prefs devwatch@github.io
# or: Settings → Extensions → DevWatch → ⚙| Setting | Default | Description |
|---|---|---|
| Poll interval | 10 s | How often DevWatch rescans processes and ports. Lower = fresher data, slightly more CPU. Range: 5–60 s. Changes take effect live without reloading the extension. |
| Setting | Default | Description |
|---|---|---|
| Show system ports | Off | When on, all listening ports appear — not just the built-in dev-port set. |
| Port conflict notifications | On | GNOME notifications when a dev port is newly occupied. Turn off if noisy. |
| Setting | Default | Description |
|---|---|---|
| Idle threshold | 10 min | Minutes of <0.5% CPU (with no open port) before a dev tool is flagged as idle. Range: 1–60 min. |
| Setting | Default | Description |
|---|---|---|
| Max history rows | 8 | Number of completed build runs shown in the BUILD PERFORMANCE section. Range: 1–20. |
The dot changes colour based on the most severe condition detected:
Normal day: ● (green) — everything running, nothing unusual
Running webpack: ● (yellow) — build pushing CPU >90%
Left vite running: ● (yellow) — idle dev tool detected after threshold
Port 3000 grabbed: ● (red) — new process on a dev port (notification also fires)
Zombie detected: ● (red) — un-reaped zombie process in process table
The dot returns to green as soon as the condition is resolved — e.g. after you kill the idle process or the zombie is reaped.
All data is stored locally in ~/.local/share/devwatch/:
~/.local/share/devwatch/
├── snapshots/
│ ├── 2026-03-03_14-30-00_before-refactor.json
│ └── 2026-03-03_09-15-00_auto.json
└── build_history.json
Nothing is ever sent off your machine.
To wipe all DevWatch data:
rm -rf ~/.local/share/devwatch/DevWatch uses standard GNU gettext. Adding a translation for a new language:
# 1. Add your locale code to po/LINGUAS
echo "de" >> po/LINGUAS # German example
# 2. Extract strings (creates/updates the .pot template)
make pot
# 3. Create the .po translation file
make update-po # creates po/de.po
# 4. Edit po/de.po in Poedit, GNOME Translation Editor, or any text editor
# Translate each msgstr "" string.
# 5. Compile to binary and link
make compile-mo
make linkThe extension will use your translation automatically when GNOME is set to that language.
git clone https://github.com/Adithya-Balan/DevWatch.git
cd DevWatch
make link
gnome-extensions enable devwatch@github.io
# Log out and back in- Edit any
.jsor.cssfile in the project - Run:
On Wayland this requires a log-out/in — use the nested session instead.
gnome-extensions disable devwatch@github.io gnome-extensions enable devwatch@github.io - Watch
make logfor errors
After creating any new .js or .css file, re-run:
make linkThis symlinks the new file into the GNOME extension directory.
After editing schemas/org.gnome.shell.extensions.devwatch.gschema.xml:
make link # auto-runs compile-schemas
# Then reload the extension (log out/in on Wayland)make pack
# Produces: devwatch@github.io.shell-extension.zip| Target | Description |
|---|---|
make link |
Compile schema + symlink all files into GNOME extension dir |
make compile-schemas |
Compile GSettings schema only |
make enable |
gnome-extensions enable |
make disable |
gnome-extensions disable |
make pack |
Build distributable .zip (compiles schema + .mo files first) |
make pot |
Extract translatable strings → po/devwatch@github.io.pot |
make update-po |
Merge .pot changes into existing .po translation files |
make compile-mo |
Compile .po files → binary .mo files in locale/ |
make log |
Tail GNOME Shell journal (your console.log appears here) |
make nested |
Launch a nested Wayland GNOME Shell for safe testing |
make status |
Show gnome-extensions info |
- On Wayland you must log out and back in after the first
gnome-extensions enable - Check
make status— if State isERROR, the extension crashed on load - Check
make logfor the specific JavaScript error
make nested must be run from a terminal inside your graphical GNOME session with
$WAYLAND_DISPLAY set. The VS Code integrated terminal may not have this variable.
Fix: open a regular GNOME Terminal (Ctrl+Alt+T), cd to the project, and run make nested from there.
- The focused window must be a terminal or editor whose process has a CWD inside a git repo
- Verify:
cd ~/Projects/my-repo && git rev-parse --show-toplevel— must return a path - Click on the terminal window to focus it — DevWatch re-detects on window focus
- Confirm the process is actually listening:
ss -tulnp | grep 3000 - If it's not in the built-in dev port set and "Show system ports" is off, it won't appear
- Enable Show system ports in Preferences → Ports
make log
# Look for lines starting with JS ERROR or [DevWatch]Disable and re-enable to recover:
gnome-extensions disable devwatch@github.io
gnome-extensions enable devwatch@github.iognome-extensions prefs devwatch@github.io 2>&1Most common cause: GSettings schema not compiled. Fix:
make link # re-compiles the schemaextension.js (entry point)
│
├── core/ ← Data layer — no UI, no GLib.idle, pure logic
│ ├── projectDetector.js Window focus → git root (async, event-driven)
│ ├── processTracker.js /proc scan every 10s → ProjectData map
│ ├── portMonitor.js ss -tulnp → PortRecord list
│ ├── conflictNotifier.js Deduplicated GNOME notifications for new ports
│ ├── cleanupEngine.js Zombie/orphan/idle-dev detection (sync, per-scan)
│ ├── snapshotManager.js JSON save/list/load/restore to ~/.local/share/devwatch/
│ └── buildDetector.js Build tool detection, peakCPU/RAM tracking, history
│
├── ui/ ← Stateless renderers — caller passes data, they build rows
│ ├── projectSection.js PopupSubMenuMenuItem per project
│ ├── portSection.js One row per port, Kill + Copy PID buttons
│ ├── cleanupSection.js Clean All button + per-candidate Kill
│ ├── snapshotSection.js Save Now, Restore, Delete rows
│ └── perfSection.js Active build rows + history rows
│
├── utils/
│ ├── subprocess.js Promisified Gio.Subprocess wrapper
│ ├── procReader.js Sync /proc/<pid>/{stat,statm,cmdline,cwd} reader
│ └── i18n.js Re-exports gettext/ngettext for UI modules
│
├── schemas/ GSettings schema definition
├── po/ Translation source files
├── prefs.js GTK4/Adw preferences window
└── stylesheet.css St widget CSS (panel dot, section titles, buttons)
processTracker.scan() — reads /proc, builds ProjectData map
│
▼
portMonitor.scan() — runs ss, maps port→PID→project
│
▼
cleanupEngine.analyse() — zombie/orphan/idle detection (in-memory, no I/O)
buildDetector.analyse() — build tool CPU tracking (in-memory, async persist)
snapshotManager.list() — async dir scan of ~/.local/share/devwatch/snapshots/
│
▼
buildProjectSection() — clears + rebuilds ACTIVE PROJECTS rows
buildPortSection() — clears + rebuilds ACTIVE PORTS rows
buildCleanupSection() — clears + rebuilds CLEANUP CANDIDATES rows
buildSnapshotSection() — clears + rebuilds SESSION SNAPSHOTS rows
buildPerfSection() — clears + rebuilds BUILD PERFORMANCE rows
│
▼
_updateStatusDot() — green / yellow / red based on combined results
All async I/O uses Gio.Subprocess with _promisify — the GNOME main loop is never blocked.
All operations are guarded by a shared Gio.Cancellable that is cancelled inside disable()
to ensure no callbacks fire after the extension is unloaded.