Skip to content

feat: improve workspace handling#1993

Merged
Davidknp merged 16 commits into
mainfrom
improve-workspaces
May 13, 2026
Merged

feat: improve workspace handling#1993
Davidknp merged 16 commits into
mainfrom
improve-workspaces

Conversation

@Davidknp
Copy link
Copy Markdown
Collaborator

@Davidknp Davidknp commented May 12, 2026

  • improve task workspace provision error handling (workspace not available)
  • improve workspace state management
  • show correct git status for workspace
  • show changed lines for all tasks
  • create file index and make files searchable in cmdk

@Davidknp Davidknp changed the title Improve workspaces feat: improve workspace handling May 13, 2026
@Davidknp Davidknp marked this pull request as ready for review May 13, 2026 15:07
@greptile-apps
Copy link
Copy Markdown
Contributor

greptile-apps Bot commented May 13, 2026

Greptile Summary

This PR overhauls workspace lifecycle management: workspaces get their own DB table (migration 0011), a new WorkspaceController drives the resolveBootstrap / adoptWorktree / createWorktree RPC flow, the renderer store hierarchy is split into stable registries plus a per-session WorkspaceViewModel, and a new FTS5-backed file-index service makes workspace files searchable in the command palette.

  • DB/backend: New workspaces table with partial unique index on key; workspace_file_index FTS5 virtual tables version-gated via kv; controller.ts handles legacy workspace-ID migration transparently during resolveBootstrap.
  • Renderer store refactor: TaskViewStore / task.ts replaced by WorkspaceViewModel / task-store.ts; stable sub-stores survive provision/unprovision cycles; old files kept as re-export shims.
  • New UX: WorkspaceResolutionView presents branch_elsewhere / path_missing states with adopt/create/cancel actions; file hits appear in command palette via the new index service.

Confidence Score: 3/5

Safe to merge with the provision-failure cleanup fix applied; without it a failed first-provision leaves the task in an unrecoverable state.

The workspace table creation and multi-step bootstrap flow are structurally sound, but in createTask.ts a provision failure deletes the workspace row while leaving tasks.workspaceId intact. Any subsequent re-provision attempt through resolveBootstrap finds the non-null UUID, skips the legacy migration branch, and throws Workspace not found — the task cannot be provisioned again without deletion.

Primary concern is src/main/core/tasks/operations/createTask.ts for the workspace cleanup on provision failure. Secondary attention to src/main/core/search/workspace-file-index-service.ts for the destroyed-workspace eviction and FTS5 operator handling.

Important Files Changed

Filename Overview
src/main/core/tasks/operations/createTask.ts Adds workspace row creation ahead of provision, but leaves tasks.workspaceId pointing to a deleted workspace on provision failure
src/main/core/workspaces/controller.ts New workspace bootstrap controller handling legacy ID migration and worktree path resolution
src/main/core/search/workspace-file-index-service.ts New FTS5-backed workspace file indexer; onWorkspaceDestroyed refreshes staleness timer instead of evicting, and FTS5 operators can leak through the term filter
src/main/core/tasks/provisionTask.ts Refactored to read WorkspaceRow before provisioning and write path/key/data back afterwards; logic looks correct
src/renderer/features/tasks/stores/task-manager.ts Provision flow split into resolveBootstrap + createWorktree + finishProvision; continueProvision adopt branch silently skips adoptWorktree when candidatePath is missing
src/renderer/features/tasks/stores/workspace-view-model.tsx New WorkspaceViewModel: stable view state across provision/unprovision cycles with clean initialize/suspend/dispose lifecycle
src/main/core/git/impl/git-service.ts Adds headKind/shortHash to FullGitStatus and fires status:updated hook for line-count caching
drizzle/0011_chunky_scarecrow.sql Adds workspaces table with partial unique index on key; schema matches Drizzle definition
src/renderer/features/tasks/workspace-resolution-view.tsx New UI for branch_elsewhere/path_missing resolution states; delegates correctly to continueProvision
src/main/db/initialize.ts Adds ensureFileIndex to create FTS5 virtual tables for workspace file search; version-gated via kv table
Prompt To Fix All With AI
Fix the following 4 code review issues. Work through them one at a time, proposing concise fixes.

---

### Issue 1 of 4
src/main/core/tasks/operations/createTask.ts:234-240
When `provisionTask` fails, the workspace row is deleted but `tasks.workspaceId` is not cleared. The task row now holds a UUID pointing to a non-existent workspace. If the user triggers re-provisioning, `resolveBootstrap` will find the non-null `workspaceId`, skip the legacy migration branch, and throw `"Workspace not found: {id}"` — making the task permanently unrecoverable without deletion.

```suggestion
  if (!provisionResult.success) {
    await Promise.all([
      db.delete(workspaces).where(eq(workspaces.id, workspaceId)).catch(() => {}),
      db.update(tasks).set({ workspaceId: null }).where(eq(tasks.id, params.id)).catch(() => {}),
    ]);
    return err(mapProvisionError(provisionResult.error));
  }
```

### Issue 2 of 4
src/main/core/search/workspace-file-index-service.ts:73-75
`onWorkspaceDestroyed` calls `touchMeta`, which sets `indexed_at = unixepoch()` — the current time. This resets the 14-day staleness clock rather than marking the workspace for cleanup. Every workspace destroy event extends the life of its file-index entries by another 14 days, so the FTS5 table grows unboundedly for frequently-cycled workspaces.

```suggestion
  onWorkspaceDestroyed(workspaceId: string): void {
    try {
      sqlite
        .prepare(
          `INSERT OR REPLACE INTO workspace_file_index_meta (workspace_id, indexed_at)
           VALUES (?, 0)`
        )
        .run(workspaceId);
    } catch (e) {
      log.warn('WorkspaceFileIndexService: onWorkspaceDestroyed failed', {
        workspaceId,
        error: String(e),
      });
    }
  }
```

### Issue 3 of 4
src/main/core/search/workspace-file-index-service.ts:85
User-supplied terms that are 3+ characters and happen to be uppercase FTS5 boolean operators (`AND`, `NOT`) pass the length filter and land verbatim in the `MATCH` expression. A search for `foo NOT bar` produces `ftsQuery = 'foo AND NOT AND bar'` — invalid FTS5 syntax that silently returns `[]`. Wrapping each term in double quotes forces FTS5 to treat them as phrase literals.

```suggestion
    const ftsQuery = terms.map((t) => `"${t.replace(/"/g, '')}"`).join(' AND ');
```

### Issue 4 of 4
src/renderer/features/tasks/stores/task-manager.ts:443-449
When `action === 'adopt'` but `candidatePath` is `undefined`, neither branch executes — `adoptWorktree` is silently skipped — and `_finishProvision` is called with the workspace path still unset. Raising a clear error makes the failure visible rather than causing a confusing downstream provision with a missing `workDir`.

```suggestion
    try {
      if (action === 'adopt') {
        if (!candidatePath) throw new Error('adopt action requires a candidatePath');
        await rpc.workspaces.adoptWorktree({ projectId: this.projectId, taskId, candidatePath });
      } else if (action === 'create') {
        await rpc.workspaces.createWorktree({ projectId: this.projectId, taskId });
      }
    } catch (err) {
```

Reviews (1): Last reviewed commit: "fix: format" | Re-trigger Greptile

Comment thread src/main/core/tasks/operations/createTask.ts
Comment thread src/main/core/search/workspace-file-index-service.ts Outdated
Comment thread src/main/core/search/workspace-file-index-service.ts Outdated
Comment thread src/renderer/features/tasks/stores/task-manager.ts
@Davidknp Davidknp merged commit ac161a0 into main May 13, 2026
1 check passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant