Skip to content

Commit 945dd22

Browse files
committed
feat: remove headless execution mode — all hooks run in a terminal tab
- Remove headless subprocess execution path from hooks.rs - Remove ALLOWED_EXECUTION_MODES, validate_execution_mode, trigger_supports_terminal_tab helpers - Remove write_hook_script_file, read_limited_output, truncate_utf8, hook_script_extension, resolve_script_execution - Remove SystemAction::HookExecute variant from git/helpers.rs - Remove execution_mode from HookUpsertInput; hardcode terminal_tab in create/update handlers - Lift before_* trigger restriction since headless is no longer an alternative - Add DB migration 005 to update any existing headless rows to terminal_tab - Narrow HookExecutionMode to 'terminal_tab' only in sproutgit.ts - Remove execution mode UI from WorkspaceHooksModal.svelte - Update E2E seeds (including raw SQL inserts) to terminal_tab - Update website Features.astro hooks description
1 parent 2479953 commit 945dd22

32 files changed

Lines changed: 122 additions & 455 deletions

e2e/specs/daily-workflow.spec.ts

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -97,7 +97,7 @@ type HookSeedInput = {
9797
script: string;
9898
scope?: 'worktree' | 'workspace';
9999
executionTarget?: 'workspace' | 'trigger_worktree' | 'initiating_worktree';
100-
executionMode?: 'headless' | 'terminal_tab';
100+
executionMode?: 'terminal_tab';
101101
enabled?: 0 | 1;
102102
critical?: 0 | 1;
103103
keepOpenOnCompletion?: 0 | 1;
@@ -119,7 +119,7 @@ function insertHookDefinition(dbPath: string, input: HookSeedInput) {
119119
` ${sqlString(input.scope ?? 'workspace')},`,
120120
` ${sqlString(input.trigger)},`,
121121
` ${sqlString(input.executionTarget ?? 'trigger_worktree')},`,
122-
` ${sqlString(input.executionMode ?? 'headless')},`,
122+
` ${sqlString(input.executionMode ?? 'terminal_tab')},`,
123123
` ${sqlString(input.shell)},`,
124124
` ${sqlString(input.script)},`,
125125
` ${input.enabled ?? 1},`,
@@ -558,14 +558,15 @@ test.describe('Daily developer workflow', () => {
558558
stateDbPath,
559559
[
560560
'INSERT INTO hook_definitions (',
561-
' id, name, scope, trigger, shell, script, enabled, critical, timeout_seconds, created_at, updated_at',
561+
' id, name, scope, trigger, shell, script, execution_mode, enabled, critical, timeout_seconds, created_at, updated_at',
562562
') VALUES (',
563563
` ${sqlString(hookId)},`,
564564
` ${sqlString('Daily after-create hook')},`,
565565
` ${sqlString('workspace')},`,
566566
` ${sqlString('after_worktree_create')},`,
567567
` ${sqlString(shell)},`,
568568
` ${sqlString(script)},`,
569+
` ${sqlString('terminal_tab')},`,
569570
' 1,',
570571
' 0,',
571572
' 60,',

e2e/specs/hero-screenshots.spec.ts

Lines changed: 76 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,14 @@
1+
import { dirname, join } from 'node:path';
2+
13
import { test } from '../fixtures';
24
import { createHeroMediaRepo } from '../helpers/benchmark-repos';
3-
import { appendRepoFile, resetConfigDb, resetTestDirs, writeRepoFile } from '../helpers/fixtures';
5+
import {
6+
appendRepoFile,
7+
executeSqlite,
8+
resetConfigDb,
9+
resetTestDirs,
10+
writeRepoFile,
11+
} from '../helpers/fixtures';
412
import { captureScreenshotVariants, resizeWindowForScreenshot } from '../helpers/screenshots';
513
import {
614
createWorktreeViaUi,
@@ -11,6 +19,42 @@ import {
1119
reloadToHome,
1220
} from '../helpers/ui';
1321

22+
function sqlStr(value: string) {
23+
return `'${value.replaceAll("'", "''")}'`;
24+
}
25+
26+
function seedHook(
27+
dbPath: string,
28+
id: string,
29+
name: string,
30+
trigger: string,
31+
executionMode: 'terminal_tab',
32+
critical: 0 | 1 = 0
33+
) {
34+
const now = Math.floor(Date.now() / 1000);
35+
executeSqlite(
36+
dbPath,
37+
[
38+
'INSERT INTO hook_definitions (',
39+
' id, name, scope, trigger, execution_target, execution_mode, shell, script,',
40+
' enabled, critical, keep_open_on_completion, timeout_seconds, created_at, updated_at',
41+
') VALUES (',
42+
` ${sqlStr(id)}, ${sqlStr(name)}, 'workspace', ${sqlStr(trigger)},`,
43+
` 'trigger_worktree', ${sqlStr(executionMode)}, 'bash',`,
44+
` 'echo "Running ${name}"',`,
45+
` 1, ${critical}, 0, 60, ${now}, ${now}`,
46+
');',
47+
].join('\n')
48+
);
49+
}
50+
51+
function seedHookDependency(dbPath: string, hookId: string, dependsOnId: string) {
52+
executeSqlite(
53+
dbPath,
54+
`INSERT INTO hook_dependencies (hook_id, depends_on_hook_id) VALUES (${sqlStr(hookId)}, ${sqlStr(dependsOnId)});`
55+
);
56+
}
57+
1458
test.describe('Hero screenshots @screenshots', () => {
1559
test.skip(
1660
!process.env.CAPTURE_SCREENSHOTS,
@@ -208,5 +252,36 @@ test.describe('Hero screenshots @screenshots', () => {
208252
// Give terminals time to render their output.
209253
await new Promise(r => setTimeout(r, 2000));
210254
await captureScreenshotVariants(tauriPage, testInfo, 'terminal/grid-view');
255+
256+
// ── Shot 6: hooks/management — workspace hooks modal with seeded hooks ────
257+
// Derive stateDbPath from settingsPath: worktree → worktrees → workspace → .sproutgit/state.db
258+
const stateDbPath = join(dirname(dirname(settingsPath)), '.sproutgit', 'state.db');
259+
260+
// Seed three representative hooks so the modal looks rich.
261+
const hookSetupId = 'hero-hook-setup';
262+
const hookLintId = 'hero-hook-lint';
263+
const hookCleanId = 'hero-hook-clean';
264+
seedHook(stateDbPath, hookSetupId, 'Set up dev environment', 'after_worktree_create', 'terminal_tab', 1);
265+
seedHook(stateDbPath, hookLintId, 'Run linter & type check', 'after_worktree_create', 'terminal_tab');
266+
seedHook(stateDbPath, hookCleanId, 'Clean build cache', 'before_worktree_remove', 'terminal_tab');
267+
// "Run linter" depends on "Set up dev environment" so it shows indentation.
268+
seedHookDependency(stateDbPath, hookLintId, hookSetupId);
269+
270+
// Open the hooks modal via the workspace settings button.
271+
await tauriPage.getByTestId('btn-workspace-settings').click();
272+
await tauriPage.getByTestId('hooks-modal').waitFor(DEFAULT_UI_TIMEOUT);
273+
274+
// Wait until hook rows are rendered (hooks loaded from DB).
275+
await tauriPage.waitForFunction(
276+
`(() => document.querySelectorAll('[data-testid="hook-list-row"]').length >= 3)()`,
277+
DEFAULT_UI_TIMEOUT
278+
);
279+
// Let entrance animations finish.
280+
await new Promise(r => setTimeout(r, 400));
281+
282+
await captureScreenshotVariants(tauriPage, testInfo, 'hooks/management');
283+
284+
// Close the modal before the test ends.
285+
await tauriPage.evaluate(`document.body.click()`);
211286
});
212287
});
-2.59 KB
Loading
-2.45 KB
Loading
1.17 KB
Loading
-1.65 KB
Loading
219 KB
Loading
234 KB
Loading
10 Bytes
Loading
65.5 KB
Loading

0 commit comments

Comments
 (0)