Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
37 commits
Select commit Hold shift + click to select a range
b80ebc5
feat(web): add theme selector with Catpuccin as optional theme
gaius-codius Feb 18, 2026
67339d4
feat(web): add Gaius theme with Roman-inspired palette
gaius-codius Feb 18, 2026
7087fbe
fix(web): improve clipboard reliability and terminal paste fallback
gaius-codius Feb 18, 2026
cb399fb
fix(web): skip paste fallback dialog when clipboard is empty
gaius-codius Feb 18, 2026
cf7a7c1
fix(web): move file content copy action into file viewer
gaius-codius Feb 18, 2026
50501c3
feat(web): group sessions by machine and directory in sidebar
gaius-codius Feb 18, 2026
65caf12
fix(web): simplify session context labels in list
gaius-codius Feb 18, 2026
f93a499
feat(web): improve session list badges and permission mode display
gaius-codius Feb 18, 2026
7ec4cd7
fix(web): harden theme platform detection and theme label copy
gaius-codius Feb 18, 2026
afa34b2
refactor(web): simplify session badge rendering logic
gaius-codius Feb 18, 2026
b3a31ad
test(web): cover theme preference resolution and persistence
gaius-codius Feb 18, 2026
55bcb88
test(web): add clipboard fallback and terminal paste coverage
gaius-codius Feb 18, 2026
6d22b95
test(web): verify machine grouping and permission-mode badges
gaius-codius Feb 18, 2026
8423d82
fix(web): keep theme reactivity active outside settings page
gaius-codius Feb 18, 2026
0602869
fix(web): add baseline badge and flavor color tokens for session UI
gaius-codius Feb 19, 2026
e671094
chore(web): keep session-ui color token baseline in session-ui PR
gaius-codius Feb 19, 2026
2d7b5f8
merge: bring in PR186 theme system
gaius-codius Feb 19, 2026
78cffb0
merge: bring in PR187 clipboard/terminal fixes
gaius-codius Feb 19, 2026
a3f2e27
merge: bring in PR188 session list UI
gaius-codius Feb 19, 2026
87522d2
docs: add local fork install/update workflow and helper script
gaius-codius Feb 19, 2026
d70f93c
web: improve voice mic error handling; checkpoint current repo changes
gaius-codius Feb 19, 2026
7e03f6e
fix: isolate runner integration test to prevent killing production ru…
gaius-codius Feb 21, 2026
e0cb86d
feat: persist session sort mode and manual ordering in hub
gaius-codius Feb 21, 2026
9504738
fix: address PR review findings for session sort feature
gaius-codius Feb 21, 2026
3f19811
Merge pull request #1 from gaius-codius/feat/session-sort-mode-backend
gaius-codius Feb 21, 2026
92fca82
test(web): add playwright e2e coverage for session sort backend
gaius-codius Feb 21, 2026
79efc13
Merge pull request #2 from gaius-codius/feat/session-sort-mode-backend
gaius-codius Feb 21, 2026
59dfe0f
Merge remote-tracking branch 'upstream/main'
gaius-codius Mar 3, 2026
df182ea
Merge branch 'main' of https://github.com/gaius-codius/hapi
gaius-codius Mar 3, 2026
465a1d3
merge: bring in upstream changes (v0.16.1, teams, appearance, spawn e…
gaius-codius Mar 9, 2026
59f3dbe
Merge remote-tracking branch 'origin/main' into sync/fork-main-merge
gaius-codius Apr 1, 2026
ca3b5a5
feat(web): polish session list, header, and new-session UX
gaius-codius Apr 7, 2026
4655347
fix: address PR review follow-ups
gaius-codius Apr 7, 2026
6524779
chore: remove out-of-scope theme definitions
gaius-codius Apr 14, 2026
5f22ef5
merge: sync branch with main before PR review
gaius-codius Apr 19, 2026
c9d5660
chore: sync branch with main and refresh lockfile
gaius-codius Apr 19, 2026
156c907
fix: address PR #4 review findings
gaius-codius Apr 19, 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
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ coverage/

# OS / IDE
**/.DS_Store
**/._*
.idea/
.vscode/
**/*.swp
Expand All @@ -37,6 +38,9 @@ coverage/
.claude/settings.local.json
localdocs/
execplan/
docs/plans/
docs/LOCAL_FORK_WORKFLOW.md
docs/GAIUS_THEME.md

# Generated npm bundle output (local)
cli/npm/main/
Expand Down
13 changes: 13 additions & 0 deletions bun.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

100 changes: 83 additions & 17 deletions cli/src/runner/runner.integration.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,17 +15,18 @@
* - CLI_API_TOKEN=... (must match the hub)
*/

import { describe, it, expect, beforeEach, afterEach } from 'vitest';
import { describe, it, expect, beforeAll, afterAll, beforeEach, afterEach } from 'vitest';
import { execSync, spawn } from 'child_process';
import { existsSync, unlinkSync, readFileSync, writeFileSync, readdirSync } from 'fs';
import { existsSync, unlinkSync, readFileSync, writeFileSync, readdirSync, mkdirSync, cpSync, rmSync } from 'fs';
import { tmpdir } from 'os';
import path, { join } from 'path';
import { configuration } from '@/configuration';
import {
listRunnerSessions,
stopRunnerSession,
spawnRunnerSession,
stopRunnerHttp,
notifyRunnerSessionStarted,
import {
listRunnerSessions,
stopRunnerSession,
spawnRunnerSession,
stopRunnerHttp,
notifyRunnerSessionStarted,
stopRunner
} from '@/runner/controlClient';
import { readRunnerState, clearRunnerState } from '@/persistence';
Expand All @@ -34,6 +35,55 @@ import { spawnHappyCLI } from '@/utils/spawnHappyCLI';
import { getLatestRunnerLog } from '@/ui/logger';
import { isProcessAlive, isWindows, killProcess, killProcessByChildProcess } from '@/utils/process';

// --- Test isolation: use a temp HAPI_HOME so tests never touch the real runner ---

const testHapiHome = join(tmpdir(), `hapi-runner-test-${process.pid}-${Date.now()}`);
const testEnv = { ...process.env, HAPI_HOME: testHapiHome };

// Override the singleton configuration paths before any test runs.
// The spawned child processes get isolation via the HAPI_HOME env var.
function installTestHapiHome(): { originals: Record<string, string> } {
mkdirSync(testHapiHome, { recursive: true });
mkdirSync(join(testHapiHome, 'logs'), { recursive: true });

// Copy settings.json so the runner can auth with the hub
const realSettings = join(configuration.happyHomeDir, 'settings.json');
if (existsSync(realSettings)) {
cpSync(realSettings, join(testHapiHome, 'settings.json'));
}

const props = [
'happyHomeDir',
'logsDir',
'settingsFile',
'privateKeyFile',
'runnerStateFile',
'runnerLockFile',
] as const;

const originals: Record<string, string> = {};
for (const prop of props) {
originals[prop] = (configuration as any)[prop];
}

// Redirect configuration paths to the temp dir
Object.defineProperty(configuration, 'happyHomeDir', { value: testHapiHome, configurable: true });
Object.defineProperty(configuration, 'logsDir', { value: join(testHapiHome, 'logs'), configurable: true });
Object.defineProperty(configuration, 'settingsFile', { value: join(testHapiHome, 'settings.json'), configurable: true });
Object.defineProperty(configuration, 'privateKeyFile', { value: join(testHapiHome, 'access.key'), configurable: true });
Object.defineProperty(configuration, 'runnerStateFile', { value: join(testHapiHome, 'runner.state.json'), configurable: true });
Object.defineProperty(configuration, 'runnerLockFile', { value: join(testHapiHome, 'runner.state.json.lock'), configurable: true });

return { originals };
}

function uninstallTestHapiHome(originals: Record<string, string>): void {
for (const [prop, value] of Object.entries(originals)) {
Object.defineProperty(configuration, prop, { value, configurable: true });
}
try { rmSync(testHapiHome, { recursive: true, force: true }); } catch { /* best effort */ }
}

// Utility to wait for condition
async function waitFor(
condition: () => Promise<boolean>,
Expand Down Expand Up @@ -80,23 +130,38 @@ async function isServerHealthy(): Promise<boolean> {

describe.skipIf(!await isServerHealthy())('Runner Integration Tests', { timeout: 20_000 }, () => {
let runnerPid: number;
let savedOriginals: Record<string, string>;

beforeAll(() => {
const { originals } = installTestHapiHome();
savedOriginals = originals;
console.log(`[TEST] Using isolated HAPI_HOME: ${testHapiHome}`);
});

afterAll(async () => {
try {
await stopRunner();
} finally {
uninstallTestHapiHome(savedOriginals);
}
});

beforeEach(async () => {
// First ensure no runner is running by checking PID in metadata file
// First ensure no test runner is running by checking PID in metadata file
await stopRunner()

// Start fresh runner for this test
// This will return and start a background process - we don't need to wait for it

// Start fresh runner for this test, using isolated HAPI_HOME
void spawnHappyCLI(['runner', 'start'], {
stdio: 'ignore'
stdio: 'ignore',
env: testEnv
});

// Wait for runner to write its state file (it needs to auth, setup, and start server)
await waitFor(async () => {
const state = await readRunnerState();
return state !== null;
}, 10_000, 250); // Wait up to 10 seconds, checking every 250ms

const runnerState = await readRunnerState();
if (!runnerState) {
throw new Error('Runner failed to start within timeout');
Expand Down Expand Up @@ -200,7 +265,8 @@ describe.skipIf(!await isServerHealthy())('Runner Integration Tests', { timeout:
], {
cwd: '/tmp',
detached: true,
stdio: 'ignore'
stdio: 'ignore',
env: testEnv
});
if (!terminalHappyProcess || !terminalHappyProcess.pid) {
throw new Error('Failed to spawn terminal hapi process');
Expand Down Expand Up @@ -259,7 +325,7 @@ describe.skipIf(!await isServerHealthy())('Runner Integration Tests', { timeout:
// Try to start another runner
const secondChild = spawn('bun', ['src/index.ts', 'runner', 'start-sync'], {
cwd: process.cwd(),
env: process.env,
env: testEnv,
stdio: ['ignore', 'pipe', 'pipe']
});

Expand Down
Loading