Skip to content

Commit f2923a7

Browse files
authored
Address 4 PR review comments + add learnings to copilot instructions
Agent-Logs-Url: https://github.com/InterestingSoftware/SproutGit/sessions/741dc53d-08c0-4423-961d-568eca87c217
1 parent 832671f commit f2923a7

5 files changed

Lines changed: 20 additions & 32 deletions

File tree

.github/copilot-instructions.md

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -301,6 +301,7 @@ static WORKSPACE_MIGRATIONS: LazyLock<Migrations<'static>> = LazyLock::new(|| {
301301
- Append the new `M::up(include_str!("../migrations/<db>/<NNN>_….sql"))` entry to the correct `LazyLock` vec.
302302
- Indexes that reference a column added in the same migration must appear after that `ALTER TABLE` in the same file, or in a later migration file.
303303
- Do not create bespoke `PRAGMA table_info` checks — the framework handles idempotency.
304+
- **Never add `ALTER TABLE … ADD COLUMN` statements outside migration files**, even as "compatibility shims" that try to swallow "duplicate column name" errors. SQLite error strings can vary across versions, making string-matching fragile. If a column is needed, add a numbered migration file and register it in the `LazyLock` vec.
304305

305306
### `db.rs` public API
306307

@@ -436,7 +437,7 @@ When adding or changing **any** git or system interaction, follow all rules belo
436437
- **Path handling**: Use `Path`/`PathBuf` and platform-aware path/env separators. Do not hardcode `:` as PATH separator.
437438
- **Path canonicalization on Windows**: `std::fs::canonicalize()` returns `\\?\`-prefixed extended-length paths on Windows (e.g. `\\?\D:\...`). Always chain `strip_win_prefix()` from `crate::git::helpers` immediately after any `canonicalize()` call. This applies to paths stored in SQLite, set as subprocess env vars, or passed to shell scripts — not just paths serialised to the frontend. Git for Windows tolerates extended-length paths silently, but PowerShell cmdlets (e.g. `Join-Path`) reject them with cryptic errors.
438439
- **Single-source normalization rule**: Path normalization logic must live in shared helpers, not duplicated at call sites. When introducing a new canonicalization/normalization path, reuse existing helper functions or add one shared helper first.
439-
- **Path serialization — frontend-bound values**: Any `Path`/`PathBuf` value serialised in a Tauri command response (struct fields, `Ok(...)` return values) **must** be converted with `path_to_frontend()` from `crate::git::helpers`, not with `to_string_lossy()`. Git always outputs forward slashes on every OS; using the same convention prevents frontend path-comparison mismatches on Windows. Paths passed as arguments to git/system commands should stay as native OS paths via `to_string_lossy()` directly.
440+
- **Path serialization — frontend-bound values**: Any `Path`/`PathBuf` value serialised in a Tauri command response (struct fields, `Ok(...)` return values) **must** be converted with `path_to_frontend()` from `crate::git::helpers`, not with `to_string_lossy()`. Git always outputs forward slashes on every OS; using the same convention prevents frontend path-comparison mismatches on Windows. Paths passed as arguments to git/system commands should stay as native OS paths via `to_string_lossy()` directly. **This also applies to path strings parsed from git command output** (e.g. the `worktree <path>` lines from `git worktree list --porcelain`) — call `path_to_frontend(Path::new(parsed_str))` rather than inlining `str.replace('\\', "/")` at the call site.
440441
- **Least privilege and clear errors**: Fail closed on invalid input and return clear, user-safe error messages.
441442

442443
## Security Testing Requirements
@@ -492,6 +493,7 @@ When editing or relying on documentation in this repository:
492493
- If a command, script, route, or API wrapper changes in code, update the corresponding docs in the same change.
493494
- Prefer representative command/API summaries plus explicit source-of-truth links over exhaustive static lists that quickly drift.
494495
- Remove placeholders and stale claims (for example, outdated URLs or deleted files) immediately when discovered.
496+
- **When editing component markup or styles, check nearby code comments for implementation-detail references** (e.g. hardcoded class names, colour literals) that may no longer be accurate. Stale comments about specifics like "hardcoded `bg-[#1e1e2e]`" that have since been replaced with CSS-variable equivalents cause confusion and must be updated or removed in the same change.
495497

496498
Quick verification command before commit:
497499

@@ -780,3 +782,4 @@ The `Spinner.svelte` component supports:
780782
- Svelte 5 `class:` directive with Tailwind arbitrary values (e.g., `class:bg-[var(--x)]/10={cond}`) works but looks odd
781783
- Window overflow: parent containers must be `flex flex-col overflow-hidden` for child `flex-1 overflow-auto` to scroll properly
782784
- **Windows `\\?\` paths in PowerShell**: If a path produced by `canonicalize()` is set as a subprocess env var and consumed by PowerShell, `Join-Path` will fail with "Cannot process argument because the value of argument 'drive' is null". The env var is non-null so a `if (-not $var)` guard won't catch it — the crash happens on the next line that uses the path. Always apply `strip_win_prefix` before passing any path into a hook or child process environment.
785+
- **`TerminalContainer.svelte` auto-spawn**: The component spawns an initial terminal session on mount via a `$effect` guarded by a plain (non-reactive) `_autoSpawned` flag. Do not remove this — E2E flows assert that a `[data-pty-id]` element exists immediately after the Terminal tab is revealed. If the auto-spawn is absent, the terminal tab is stuck on "No terminal sessions" until the user manually clicks `+`. The flag guard also prevents a reactive loop that would occur if `sessions.length` were read directly inside the effect (which Svelte would track as a dependency and re-fire on every session change).

e2e/helpers/screenshots.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -116,8 +116,8 @@ const DARK_XTERM_THEME = {
116116
async function forceTheme(tauriPage: TauriPage | BrowserPageAdapter, theme: 'light' | 'dark') {
117117
const termBg = theme === 'dark' ? DARK_XTERM_THEME.background : LIGHT_XTERM_THEME.background;
118118
const cssVars = theme === 'dark' ? DARK_CSS_VARS : LIGHT_CSS_VARS;
119-
// Include a rule that overrides the terminal wrapper background so the dark
120-
// hardcoded bg-[#1e1e2e] class is replaced for light-mode screenshots.
119+
// Include a rule that forces the terminal wrapper background to match the
120+
// selected screenshot theme so the wrapper stays in sync with the xterm canvas.
121121
const css = JSON.stringify(
122122
`:root{${cssVars}} [data-sg-terminal]{background-color:${termBg}!important}`
123123
);

src-tauri/src/db.rs

Lines changed: 1 addition & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -48,30 +48,6 @@ static CONFIG_MIGRATIONS: LazyLock<Migrations<'static>> = LazyLock::new(|| {
4848
))])
4949
});
5050

51-
fn ensure_workspace_schema_compatibility(conn: &rusqlite::Connection) -> Result<(), String> {
52-
let compatibility_statements = [
53-
"ALTER TABLE hook_definitions ADD COLUMN keep_open_on_completion INTEGER NOT NULL DEFAULT 0",
54-
"ALTER TABLE hook_definitions ADD COLUMN execution_target TEXT NOT NULL DEFAULT 'trigger_worktree'",
55-
"ALTER TABLE hook_definitions ADD COLUMN execution_mode TEXT NOT NULL DEFAULT 'headless'",
56-
];
57-
58-
for statement in compatibility_statements {
59-
match conn.execute(statement, []) {
60-
Ok(_) => {},
61-
Err(err) => {
62-
let message = err.to_string();
63-
if !message.contains("duplicate column name") {
64-
return Err(format!(
65-
"Failed to ensure workspace schema compatibility: {message}"
66-
));
67-
}
68-
},
69-
}
70-
}
71-
72-
Ok(())
73-
}
74-
7551
fn run_workspace_migrations(db_path: &Path) -> Result<(), String> {
7652
if let Some(parent) = db_path.parent() {
7753
ensure_directory(parent)?;
@@ -81,8 +57,7 @@ fn run_workspace_migrations(db_path: &Path) -> Result<(), String> {
8157
apply_connection_pragmas(&conn)?;
8258
WORKSPACE_MIGRATIONS
8359
.to_latest(&mut conn)
84-
.map_err(|e| format!("Workspace database migration failed: {e}"))?;
85-
ensure_workspace_schema_compatibility(&conn)
60+
.map_err(|e| format!("Workspace database migration failed: {e}"))
8661
}
8762

8863
fn run_config_migrations(db_path: &Path) -> Result<(), String> {

src-tauri/src/git/operations.rs

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -202,10 +202,8 @@ pub async fn list_worktrees(repo_path: String) -> Result<WorktreeListResult, Str
202202
if let Some(item) = current.take() {
203203
worktrees.push(item);
204204
}
205-
// Normalize path to forward slashes for consistent frontend comparison
206-
let normalized_path = rest.replace('\\', "/");
207205
current = Some(WorktreeInfo {
208-
path: normalized_path,
206+
path: path_to_frontend(Path::new(rest)),
209207
head: None,
210208
branch: None,
211209
detached: false,

src/lib/components/TerminalContainer.svelte

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,18 @@
3737
let counter = 0;
3838
let lastLaunchRequestId = $state<string | null>(null);
3939
40+
// Auto-spawn the first session when the component mounts and a default shell is available.
41+
// The plain (non-reactive) `_autoSpawned` flag ensures this runs only once even if
42+
// `defaultShell` later changes, and prevents a reactive loop since `sessions` is never
43+
// read inside this effect.
44+
let _autoSpawned = false;
45+
$effect(() => {
46+
if (!_autoSpawned && defaultShell) {
47+
_autoSpawned = true;
48+
addSession(defaultShell);
49+
}
50+
});
51+
4052
// Panel component refs — used to forward focus() and refit().
4153
// Plain object (not $state) because we don't need reactivity on the refs themselves.
4254
const panelInstances: Partial<Record<string, { focus: () => void; refit: () => void } | null>> =

0 commit comments

Comments
 (0)