Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
45 commits
Select commit Hold shift + click to select a range
798ce0f
Enhance Makefile with improved portability and dependency handling
jcrussell Dec 31, 2025
aa7c7d4
Fix Quick Win bugs: window classification and GNOME integration
jcrussell Jan 2, 2026
965f3d7
Fix focus & navigation: workspace change border and window close focus
jcrussell Jan 2, 2026
c148cd6
Fix Google Chrome/Chromium always-on-top issue
jcrussell Jan 2, 2026
2d7db18
Fix GNOME auto-maximize conflict with tiling
jcrussell Jan 2, 2026
5f858d4
Fix critical bugs: CSS errors, JSON parsing, and password dialog focus
jcrussell Jan 3, 2026
f35b546
Fix Brave browser tiling
jcrussell Jan 3, 2026
67b8098
Fix theme backup path bug - undefined.bak
jcrussell Jan 3, 2026
93456a1
Update roadmap: mark 14 completed issues
jcrussell Jan 3, 2026
bb743e1
Add comprehensive testing infrastructure with Vitest
jcrussell Jan 3, 2026
5cbab47
Add comprehensive Node class tests
jcrussell Jan 3, 2026
d8ea2d9
Fix 9 critical Phase 1 bugs with minimal defensive changes
jcrussell Jan 3, 2026
9797b39
Add Tree class basic operations tests
jcrussell Jan 3, 2026
5fde81b
Add comprehensive Tree layout algorithm tests
jcrussell Jan 3, 2026
1da5d63
Fix 4 Phase 2 major bugs with minimal defensive changes
jcrussell Jan 3, 2026
e84ab0f
Add comprehensive Tree manipulation operations tests
jcrussell Jan 3, 2026
5e5e28d
Fix Phase 2 major bugs: PIP, Brave popups, always-on-top
jcrussell Jan 3, 2026
629c4c4
Add comprehensive WindowManager floating mode tests
jcrussell Jan 3, 2026
0171bd2
Add comprehensive WindowManager command system tests
jcrussell Jan 3, 2026
5582414
Fix Bug #322: ddterm blinking when Forge enabled
jcrussell Jan 4, 2026
9c480c9
Fix Bugs #260 and #271: Blender and Steam app-specific issues
jcrussell Jan 4, 2026
51125e9
Fix unit testing infrastructure and resolve circular dependencies
jcrussell Jan 4, 2026
57780df
Add comprehensive WindowManager gap and movement tests
jcrussell Jan 5, 2026
14f69fe
Add comprehensive WindowManager lifecycle tests
jcrussell Jan 5, 2026
ba8c4c4
Add comprehensive WindowManager workspace management tests
jcrussell Jan 5, 2026
3e8ade6
Add WindowManager pointer & focus management tests (partial)
jcrussell Jan 9, 2026
cca4770
Add comprehensive WindowManager batch float operations tests
jcrussell Jan 9, 2026
385e715
Add CLAUDE.md with development guidance for Claude Code
jcrussell Jan 9, 2026
e42c730
Add WindowManager override management and resize operation tests
jcrussell Jan 10, 2026
9d361fd
Fix 6 bugs with minimal code changes
jcrussell Jan 10, 2026
1cfc0d1
Fix Bugs #433 and #387: Preview hint ghost box and St.Icon disposed
jcrussell Jan 10, 2026
1e8bb15
Fix WindowManager focus tests - 36/37 now passing (97%)
jcrussell Jan 10, 2026
db2f449
Implement 3 features: #262, #382, #286
jcrussell Jan 10, 2026
322b472
Implement 2 features: #308 config reload, #361 border radius
jcrussell Jan 11, 2026
629a163
Implement 4 features: #435, #414, #287, #398
jcrussell Jan 11, 2026
23e0a56
Fix all 64 failing tests - test suite now 100% passing
jcrussell Jan 11, 2026
760def0
Add comprehensive tests for theme.js (56 tests)
jcrussell Jan 11, 2026
c3c8b9e
Add comprehensive tests for settings.js (31 tests)
jcrussell Jan 11, 2026
373baf6
Update COVERAGE-GAPS.md with current test status
jcrussell Jan 11, 2026
2a41cf2
Add CLAUDE.md with project guidance for Claude Code
jcrussell Jan 11, 2026
fb84f7d
Implement 4 features: #297, #315, #348, #158/#365
jcrussell Jan 11, 2026
f3a4578
Implement 3 features: #396, #462, #458
jcrussell Jan 11, 2026
7bedfda
Implement 2 features: #227, #295
jcrussell Jan 11, 2026
10a944a
Merge testing infrastructure from claude-tests branch
jcrussell Jan 11, 2026
f91b2d9
Update tests to match claude-fixes behavior
jcrussell Jan 11, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
43 changes: 43 additions & 0 deletions .dockerignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
# Dependencies
node_modules/
npm-debug.log*
yarn-debug.log*
yarn-error.log*

# Testing
coverage/

# Build outputs
temp/
dist/
*.zip

# Git
.git/
.gitignore

# IDE
.vscode/
.idea/
*.swp
*.swo
*~

# OS
.DS_Store
Thumbs.db

# GNOME extension install path
.local/

# Config
.config/

# Compiled schemas
schemas/gschemas.compiled

# Locale/translation build artifacts
po/*.mo

# Generated metadata
lib/prefs/metadata.js
16 changes: 15 additions & 1 deletion .github/workflows/testing.yml
Original file line number Diff line number Diff line change
Expand Up @@ -60,8 +60,22 @@ jobs:
- name: Build Node.js project
run: npm run build --if-present --verbose

- name: Test Node.js project
- name: Run linter (prettier)
run: npm run lint --verbose

- name: Run unit tests
run: npm test --verbose

- name: Generate coverage report
run: npm run test:coverage --verbose

- name: Upload coverage report
uses: actions/upload-artifact@v4
if: always()
with:
name: coverage-report-node-${{ matrix.node_version }}
path: coverage/
retention-days: 30

- name: Test Packaging
run: make dist
139 changes: 139 additions & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,139 @@
# CLAUDE.md

This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.

## Project Overview

Forge is a GNOME Shell extension providing i3/sway-style tiling window management. It supports GNOME 40+ on both X11 and Wayland, featuring tree-based tiling with horizontal/vertical split containers, stacked/tabbed layouts, vim-like keybindings, drag-and-drop tiling, and multi-monitor support.

## Build & Development Commands

```bash
# Install dependencies (Node.js 16+ and gettext required)
npm install

# Development build: compile, set debug mode, install to ~/.local/share/gnome-shell/extensions/
make dev

# Production build: compile, install, enable extension, restart shell
make prod

# Testing in nested Wayland session (no shell restart needed)
make test

# Testing on X11 (restarts gnome-shell)
make test-x

# Unit tests (mocked GNOME APIs via Vitest)
npm test # Run all tests
npm run test:watch # Watch mode
npm run test:coverage # With coverage report

# Code formatting
npm run format # Format code with Prettier
npm run lint # Check formatting

# View extension logs
make log
```

For Wayland nested testing, use `make test-open` to launch apps in the nested session.

## Architecture

### Entry Points

- `extension.js` - Main extension entry point, creates ForgeExtension class that manages lifecycle
- `prefs.js` - Preferences window entry point (GTK4/Adwaita)

### Core Components (lib/extension/)

- **tree.js** - Tree data structure for window layout (central to tiling logic)
- `Node` class: Represents monitors, workspaces, containers, and windows in a tree hierarchy
- `Tree` class: Manages the entire tree structure, handles layout calculations
- `Queue` class: Event queue for window operations
- Node types: ROOT, MONITOR, WORKSPACE, CON (container), WINDOW
- Layout types: HSPLIT, VSPLIT, STACKED, TABBED, PRESET

- **window.js** - WindowManager class, handles window signals, grab operations, tiling logic, and focus management (~3000 lines)

- **keybindings.js** - Keyboard shortcut management (vim-like hjkl navigation)

- **utils.js** - Utility functions for geometry calculations, window operations

- **enum.js** - `createEnum()` helper for creating frozen enum objects

- **indicator.js** - Quick settings panel integration

### Shared Modules (lib/shared/)

- **settings.js** - ConfigManager for loading window overrides from `~/.config/forge/config/windows.json`
- **logger.js** - Debug logging (controlled by settings)
- **theme.js** - ThemeManagerBase for CSS parsing and stylesheet management

### Preferences UI (lib/prefs/)

GTK4/Adwaita preference pages - not covered by unit tests.

### GSettings Schemas

Located in `schemas/org.gnome.shell.extensions.forge.gschema.xml`. Compiled during build.

## Testing Infrastructure

Tests use Vitest with mocked GNOME APIs (tests/mocks/gnome/). The mocks simulate Meta, Gio, GLib, Shell, St, Clutter, and GObject APIs so tests can run in Node.js without GNOME Shell.

**Always run tests in Docker** to ensure consistent environment:

```bash
# Run all tests in Docker (preferred)
make unit-test-docker

# Run with coverage report
make unit-test-docker-coverage

# Watch mode for development
make unit-test-docker-watch

# Run locally (if Node.js environment matches)
npm test
npm run test:coverage
```

**Coverage**: 60.5% overall, 728 tests. See `tests/COVERAGE-GAPS.md` for detailed breakdown.

Test structure:
- `tests/setup.js` - Global test setup, loads mocks
- `tests/mocks/gnome/` - GNOME API mocks (Meta.js, GLib.js, etc.)
- `tests/mocks/helpers/` - Test helpers like `createMockWindow()`
- `tests/unit/` - Unit tests organized by module
- `tests/COVERAGE-GAPS.md` - Coverage analysis and gaps documentation

## Key Concepts

- **Tiling tree**: Windows are organized in a tree structure similar to i3/sway. Containers can split horizontally or vertically, or display children in stacked/tabbed mode.

- **Window modes**: TILE (managed by tree), FLOAT (unmanaged), GRAB_TILE (being dragged), DEFAULT

- **Session modes**: Extension disables keybindings on lock screen but keeps tree in memory to preserve layout

- **GObject Classes**: All core classes extend GObject with `static { GObject.registerClass(this); }` pattern.

- **Signal Connections**: Track signal IDs for proper cleanup in disable().

## Configuration Files

- GSettings schema: `org.gnome.shell.extensions.forge`
- Window overrides: `~/.config/forge/config/windows.json`
- Stylesheet overrides: `~/.config/forge/stylesheet/forge/stylesheet.css`

## Code Style

- Prettier with 2-space indentation, 100-char line width
- Husky pre-commit hooks enforce formatting
- Use `npm run format` before committing

## Branches

- `main` - GNOME 40+ (current development)
- `legacy`/`gnome-3-36` - GNOME 3.36 support (feature-frozen)
15 changes: 15 additions & 0 deletions Dockerfile.test
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
FROM node:20-alpine

WORKDIR /app

# Copy package files
COPY package*.json ./

# Install dependencies (use install to update lock file with new vitest deps)
RUN npm install

# Copy source code
COPY . .

# Default command runs tests
CMD ["npm", "test"]
107 changes: 91 additions & 16 deletions Makefile
Original file line number Diff line number Diff line change
@@ -1,8 +1,16 @@
UUID = "forge@jmmaranan.com"
UUID = forge@jmmaranan.com
INSTALL_PATH = $(HOME)/.local/share/gnome-shell/extensions/$(UUID)
MSGSRC = $(wildcard po/*.po)

.PHONY: all clean install schemas uninstall enable disable log debug patchcss
# Shell configuration - explicitly use bash for portability across distros
SHELL := /bin/bash
.SHELLFLAGS := -eo pipefail -c

# Tool detection (using bash-specific &> redirect)
HAS_XGETTEXT := $(shell command -v xgettext &>/dev/null && echo yes || echo no)
HAS_MSGFMT := $(shell command -v msgfmt &>/dev/null && echo yes || echo no)

.PHONY: all clean install schemas uninstall enable disable log debug patchcss check-deps

all: build install enable restart

Expand All @@ -19,12 +27,24 @@ schemas/gschemas.compiled: schemas/*.gschema.xml
patchcss:
# TODO: add the script to update css tag when delivering theme.js


metadata:
echo "export const developers = Object.entries([" > lib/prefs/metadata.js
git shortlog -sne || echo "" >> lib/prefs/metadata.js
awk -i inplace '!/dependabot|noreply/' lib/prefs/metadata.js
sed -i 's/^[[:space:]]*[0-9]*[[:space:]]*\(.*\) <\(.*\)>/ {name:"\1", email:"\2"},/g' lib/prefs/metadata.js
echo "].reduce((acc, x) => ({ ...acc, [x.email]: acc[x.email] ?? x.name }), {})).map(([email, name]) => name + ' <' + email + '>')" >> lib/prefs/metadata.js
@echo "Generating developer metadata..."
@echo "export const developers = [" > lib/prefs/metadata.js
@git shortlog -sne --all \
| (grep -vE 'dependabot|noreply' || true) \
| awk '{ \
email = $$NF; \
if (email in seen) next; \
seen[email] = 1; \
name = ""; \
for (i = 2; i < NF; i++) { \
name = name (i == 2 ? "" : " ") $$i; \
} \
gsub(/"/, "\\\"", name); \
printf " \"%s %s\",\n", name, email; \
}' >> lib/prefs/metadata.js
@echo "];" >> lib/prefs/metadata.js

build: clean metadata.json schemas compilemsgs metadata
rm -rf temp
Expand All @@ -39,10 +59,12 @@ build: clean metadata.json schemas compilemsgs metadata
cp LICENSE temp
mkdir -p temp/locale
for msg in $(MSGSRC:.po=.mo); do \
msgf=temp/locale/`basename $$msg .mo`; \
mkdir -p $$msgf; \
mkdir -p $$msgf/LC_MESSAGES; \
cp $$msg $$msgf/LC_MESSAGES/forge.mo; \
if [ -f $$msg ]; then \
msgf=temp/locale/`basename $$msg .mo`; \
mkdir -p $$msgf; \
mkdir -p $$msgf/LC_MESSAGES; \
cp $$msg $$msgf/LC_MESSAGES/forge.mo; \
fi; \
done;

./po/%.mo: ./po/%.po
Expand All @@ -54,22 +76,52 @@ debug:

potfile: ./po/forge.pot

# Conditional potfile generation based on xgettext availability
ifeq ($(HAS_XGETTEXT),yes)
./po/forge.pot: metadata ./prefs.js ./extension.js ./lib/**/*.js
mkdir -p po
xgettext --from-code=UTF-8 --output=po/forge.pot --package-name "Forge" ./prefs.js ./extension.js ./lib/**/*.js

else
./po/forge.pot:
@echo "WARNING: xgettext not found, skipping pot file generation"
@echo "Install gettext package for translation support"
@mkdir -p po
@touch ./po/forge.pot
endif

# Conditional compilation of messages based on msgfmt availability
ifeq ($(HAS_MSGFMT),yes)
compilemsgs: potfile $(MSGSRC:.po=.mo)
for msg in $(MSGSRC); do \
msgmerge -U $$msg ./po/forge.pot; \
done;
else
compilemsgs:
@echo "WARNING: msgfmt not found, skipping translation compilation"
@echo "Install gettext package for translation support"
endif

clean:
rm -f lib/prefs/metadata.js
rm "$(UUID).zip" || echo "Nothing to delete"
rm -f lib/prefs/metadata.js "$(UUID).zip"
rm -rf temp schemas/gschemas.compiled

check-deps:
@echo "Checking build dependencies..."
@command -v glib-compile-schemas &>/dev/null || (echo "ERROR: glib-compile-schemas not found. Install glib2-devel or libglib2.0-dev" && exit 1)
@command -v git &>/dev/null || (echo "ERROR: git not found" && exit 1)
@command -v zip &>/dev/null || echo "WARNING: zip not found, 'make dist' will fail"
@command -v xgettext &>/dev/null || echo "WARNING: xgettext not found, translations will be skipped"
@command -v msgfmt &>/dev/null || echo "WARNING: msgfmt not found, translations will be skipped"
@echo "All required dependencies found!"

enable:
gnome-extensions enable "$(UUID)"
@if gnome-extensions list | grep -q "^$(UUID)$$"; then \
gnome-extensions enable "$(UUID)" && echo "Extension enabled successfully"; \
else \
echo "WARNING: Extension not detected by GNOME Shell yet"; \
echo "On Wayland: Log out and log back in, then run 'make enable'"; \
echo "On X11: Press Alt+F2, type 'r', press Enter, then run 'make enable'"; \
fi

disable:
gnome-extensions disable "$(UUID)" || echo "Nothing to disable"
Expand Down Expand Up @@ -115,7 +167,7 @@ test-nested: horizontal-line
WAYLAND_DISPLAY=wayland-forge \
dbus-run-session -- gnome-shell --nested --wayland --wayland-display=wayland-forge

# Usage:
# Usage:
# make test-open &
# make test-open CMD=gnome-text-editor
# make test-open CMD=gnome-terminal ARGS='--app-id app.x'
Expand All @@ -140,3 +192,26 @@ lint:

check:
npx prettier --check "./**/*.{js,jsx,ts,tsx,json}"

# Unit tests (local with mocked GNOME APIs)
unit-test:
npm test

unit-test-watch:
npm run test:watch

unit-test-coverage:
npm run test:coverage

# Docker-based testing (for CI or consistent environments)
docker-test-build:
docker build -f Dockerfile.test -t forge-test .

unit-test-docker: docker-test-build
docker run --rm forge-test npm test

unit-test-docker-watch: docker-test-build
docker run --rm -it -v $(PWD):/app forge-test npm run test:watch

unit-test-docker-coverage: docker-test-build
docker run --rm -v $(PWD)/coverage:/app/coverage forge-test npm run test:coverage
Loading