@hienlh/ppm 0.9.86 → 0.9.87

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (52) hide show
  1. package/260415-0932-git-graph-stash-rebase-conflicts/reports/code-reviewer-260415-1020-stash-rebase-conflicts.md +288 -0
  2. package/260415-0932-git-graph-stash-rebase-conflicts/reports/tester-260415-1020-build-check.md +117 -0
  3. package/260415-1150-ext-silent-failure-debugging/reports/code-reviewer-260415-1159-ext-error-reporting-review.md +205 -0
  4. package/260415-1150-ext-silent-failure-debugging/reports/docs-manager-260415-1206-ext-error-reporting.md +99 -0
  5. package/260415-1150-ext-silent-failure-debugging/reports/tester-260415-1159-extension-error-reporting.md +174 -0
  6. package/CHANGELOG.md +14 -0
  7. package/dist/web/assets/{chat-tab-BEEd-Km4.js → chat-tab-R4gKsnxD.js} +1 -1
  8. package/dist/web/assets/{code-editor-Ij4p30cr.js → code-editor-Br0vzTOy.js} +2 -2
  9. package/dist/web/assets/conflict-editor-BPgCjnNz.js +19 -0
  10. package/dist/web/assets/{csv-preview-CwQnOa3E.js → csv-preview-BZRICDP0.js} +1 -1
  11. package/dist/web/assets/{database-viewer-C1UHSgft.js → database-viewer-DaUoQ-oR.js} +1 -1
  12. package/dist/web/assets/{diff-viewer-CVx5naBA.js → diff-viewer-BzvK3gAE.js} +1 -1
  13. package/dist/web/assets/extension-webview-CGepEw-b.js +3 -0
  14. package/dist/web/assets/{index-OqgGFmh8.js → index-CKsEzQ4f.js} +4 -4
  15. package/dist/web/assets/index-Chf0otez.css +2 -0
  16. package/dist/web/assets/keybindings-store-D5zgHod8.js +1 -0
  17. package/dist/web/assets/{markdown-renderer-CRy8xw2B.js → markdown-renderer-DSYnGywb.js} +1 -1
  18. package/dist/web/assets/{port-forwarding-tab-Biua8ov5.js → port-forwarding-tab-vmqDKmk2.js} +1 -1
  19. package/dist/web/assets/{postgres-viewer-BcVjCAl4.js → postgres-viewer-0lIAosrr.js} +1 -1
  20. package/dist/web/assets/{settings-tab-C9X-N8hE.js → settings-tab-CMnv1fce.js} +1 -1
  21. package/dist/web/assets/{sql-query-editor-BFvRvJn0.js → sql-query-editor-Bc2hAwqT.js} +1 -1
  22. package/dist/web/assets/{sqlite-viewer-CPfvwFl4.js → sqlite-viewer-B60MS2Dy.js} +1 -1
  23. package/dist/web/assets/{terminal-tab-mWwk_weB.js → terminal-tab-CCJoLstH.js} +1 -1
  24. package/dist/web/assets/{use-monaco-theme-CPaeSMAA.js → use-monaco-theme-BJK48EmK.js} +1 -1
  25. package/dist/web/index.html +2 -2
  26. package/dist/web/sw.js +1 -1
  27. package/docs/codebase-summary.md +39 -6
  28. package/docs/project-changelog.md +86 -25
  29. package/docs/project-roadmap.md +3 -2
  30. package/docs/system-architecture.md +44 -1
  31. package/package.json +1 -1
  32. package/packages/ext-git-graph/src/extension.ts +126 -5
  33. package/packages/ext-git-graph/src/types.ts +13 -2
  34. package/packages/ext-git-graph/src/webview-html.ts +223 -5
  35. package/src/server/ws/extensions.ts +28 -2
  36. package/src/services/extension-host-worker.ts +6 -1
  37. package/src/services/extension.service.ts +17 -3
  38. package/src/types/extension-messages.ts +1 -1
  39. package/src/web/components/editor/conflict-editor.tsx +368 -0
  40. package/src/web/components/extensions/extension-webview.tsx +45 -3
  41. package/src/web/components/layout/editor-panel.tsx +1 -0
  42. package/src/web/components/layout/mobile-nav.tsx +1 -0
  43. package/src/web/components/layout/tab-bar.tsx +1 -0
  44. package/src/web/components/layout/tab-content.tsx +5 -0
  45. package/src/web/hooks/use-extension-ws.ts +8 -0
  46. package/src/web/stores/extension-store.ts +8 -0
  47. package/src/web/stores/panel-utils.ts +2 -0
  48. package/src/web/stores/tab-store.ts +2 -1
  49. package/dist/web/assets/extension-webview-CHVVpV34.js +0 -3
  50. package/dist/web/assets/index-vA7juDri.css +0 -2
  51. package/dist/web/assets/keybindings-store-BQxgPV5o.js +0 -1
  52. /package/dist/web/assets/{lib-CeBVkQ-7.js → lib-DSLzfeW0.js} +0 -0
@@ -2,13 +2,51 @@
2
2
 
3
3
  All notable changes to PPM are documented here. Format follows [Keep a Changelog](https://keepachangelog.com/).
4
4
 
5
- **Current Version:** v0.9.85
5
+ **Current Version:** v0.9.86
6
6
 
7
7
  ---
8
8
 
9
- ## [Unreleased] — Bundled Extensions + Git-Graph Refinement + Code Quality Improvements
9
+ ## [Unreleased] — Git-Graph Stash Management, Rebase, Conflict Resolution + Worktree CRUD
10
10
 
11
11
  ### Added
12
+ - **Git Stash Management** — Toolbar popover for interactive stash operations
13
+ - List all stashes with index, hash (abbreviated), and message
14
+ - Apply/Pop/Drop actions per stash via context menu or action buttons
15
+ - "Stash Changes" button in main toolbar to stash uncommitted work
16
+ - Stash list persisted in RepoInfo and refreshed on uncommitted status updates
17
+ - Keyboard shortcuts for quick access to stash operations
18
+
19
+ - **Interactive Rebase** — Branch context menu + commit history actions
20
+ - "Rebase current branch onto..." option in commit context menu
21
+ - Confirmation dialog shows branch selection and target commit
22
+ - Rebase state detection and progress tracking (e.g., "3/5" for interactive rebase)
23
+ - Continue/Skip/Abort controls in merge state banner during rebase
24
+
25
+ - **Merge/Rebase/Cherry-Pick Conflict Detection** — Visual conflict indicators
26
+ - Detects merge state from .git sentinel files (MERGE_HEAD, REBASE_MERGE, CHERRY_PICK_HEAD)
27
+ - Parses git status UU/AA/DD/AU/UA/DU/UD codes for unmerged entries
28
+ - Conflicted files displayed in "Conflicts" section in uncommitted status
29
+ - Conflict state banner shows merge state type + progress + action buttons (Continue/Skip/Abort)
30
+ - Auto-detects merge state only when conflicts exist (performance optimization)
31
+
32
+ - **Inline Conflict Resolution Editor** — New conflict-editor tab type with Monaco
33
+ - Dedicated `conflict-editor` tab type for visual conflict resolution
34
+ - Monaco-based editor with syntax highlighting per file type (JS/TS/Python/etc.)
35
+ - Conflict regions visually highlighted: green for current, blue for incoming, gray for markers
36
+ - Accept buttons: Accept Current / Accept Incoming / Accept Both (with proper merging)
37
+ - Conflict counter in header: "N conflicts remaining" → "All conflicts resolved" on completion
38
+ - Automatic file save after each resolution; conflict count updates in real-time
39
+ - Parses 3-way conflict markers (<<<<<<, =======, >>>>>>>) and extracts labels (HEAD/branch names)
40
+
41
+ - **Worktree Management** — Full CRUD with project integration
42
+ - Worktree toolbar popover listing all worktrees (with path, branch, HEAD status)
43
+ - Create worktree from commit context menu ("Create Worktree Here...")
44
+ - Remove, prune, and auto-lock operations per worktree
45
+ - Current worktree highlighted with badge and active background
46
+ - Auto-add unregistered worktrees as projects via confirmation dialog
47
+ - Branch-already-exists dialog offers force-replace option for branch conflicts
48
+ - Project switcher integration: switch to worktree branches directly
49
+
12
50
  - **Bundled Extensions Support** — Auto-discover extensions from packages/ext-* directories
13
51
  - PPM discovers bundled extensions (e.g., ext-git-graph) without manual installation
14
52
  - Bundled extensions available out-of-the-box with all PPM instances
@@ -29,12 +67,13 @@ All notable changes to PPM are documented here. Format follows [Keep a Changelog
29
67
  - Path traversal validation for security (assertSafePath in RPC handlers)
30
68
  - Fallback guards for all tab type handling (unknown tab types safely ignored)
31
69
 
32
- - **Extension Host Stability** — Worker debugging & error handling improvements
33
- - Enhanced error logging in extension host worker
34
- - Fixed localHandlers presence check before RPC invocation
35
- - Proper disposed flag tracking to prevent polling race conditions
36
- - HEAD ref type detection corrected for git operations
37
- - Removed unused variable warnings from build
70
+ - **Extension Error Reporting & Logging** — Silent failure debugging & user feedback
71
+ - Activation error tracking: Map stores `extId → error message` in ExtensionService
72
+ - Error toasts on command failure: "Extension command failed: {error}" displays in UI
73
+ - Timeout UX improved: Fallback UI shows activation error + "Retry" button for quick recovery
74
+ - Breadcrumb logging with tags for debugging: `[ExtService]`, `[ExtHost]`, `[ExtWS]`, `[ext-git-graph]`
75
+ - Console logs track: activation start/success, command routing, failures with context
76
+ - Activation errors included in `contributions:update` message sent to browser on WS connect
38
77
 
39
78
  - **Faithful SVG Graph Rendering** — Port of vscode-git-graph algorithm with deterministic layout
40
79
  - Single SVG model with continuous branch paths using Bézier curves
@@ -47,32 +86,54 @@ All notable changes to PPM are documented here. Format follows [Keep a Changelog
47
86
  - Removed dot alignment bug that forced rows to 29px
48
87
 
49
88
  ### Technical Details
89
+
90
+ **Stash/Rebase/Conflict/Worktree Implementation:**
91
+ - **Files Created:**
92
+ - `src/web/components/editor/conflict-editor.tsx` — Monaco-based conflict resolution editor with visual highlighting, accept buttons, real-time conflict counter, auto-save on resolution
93
+
50
94
  - **Files Modified:**
95
+ - `packages/ext-git-graph/src/extension.ts` — Added stash parsing, merge state detection (merge/rebase/cherry-pick), conflict file opening, worktree CRUD operations, branch context menu rebase action
96
+ - `packages/ext-git-graph/src/types.ts` — Added Stash interface, MergeState interface (type, progress, message), FileChange status "U" for unmerged, Worktree interface, updated UncommittedData with conflicted field
97
+ - `packages/ext-git-graph/src/webview-html.ts` — Added stash popover UI, rebase context menu item, conflict section in uncommitted panel, merge state banner with Continue/Skip/Abort, worktree popover with CRUD UI
98
+ - `src/web/stores/tab-store.ts` — Added "conflict-editor" as valid TabType
99
+ - `src/web/stores/panel-utils.ts` — Added conflict-editor case to deriveTabId() for metadata-driven tab ID generation
100
+ - `src/web/components/layout/editor-panel.tsx` — Lazy-imported ConflictEditor component with fallback
101
+ - `src/web/components/layout/tab-content.tsx` — Lazy-imported ConflictEditor component with fallback
102
+ - `src/web/components/layout/mobile-nav.tsx` — Added conflict-editor icon (conflict/warning icon)
103
+ - `src/web/components/layout/tab-bar.tsx` — Added conflict-editor icon (conflict/warning icon)
104
+
105
+ - **Services Modified:**
51
106
  - `src/services/extension-manifest.ts` — Added discoverBundledManifests() to scan packages/ext-* dirs
52
107
  - `src/services/extension.service.ts` — Added extensionPaths Map, bundledIds Set, isBundled() method; updated discover()/activate()/remove() to handle bundled extensions
53
108
  - `src/cli/commands/ext-cmd.ts` — Added "Source" column to `ppm ext list`, calls discover() to populate bundled info
54
- - `packages/ext-git-graph/src/webview-html.ts` — UX refinements: branch context menu, dblclick checkout, toast notifications, SVG Lucide icons, 10s auto-fetch option, improved UI responsiveness
55
- - `packages/ext-git-graph/src/extension.ts` — 5s uncommitted status polling, openFile/openSourceControl handlers, enhanced error handling
56
- - `packages/ext-git-graph/src/types.ts` — Added autoFetchInterval, new message types
57
109
  - `src/services/extension-rpc-handlers.ts` — Allow git operations in all registered project paths (not just CWD), improved path validation
58
110
  - `src/services/extension-host-worker.ts` — Enhanced error logging, localHandlers check, disposed flag fix for polling race conditions
59
- - `src/web/components/layout/tab-bar.tsx` — Fallback guard for unknown tab types
60
- - `src/web/components/layout/mobile-nav.tsx` — Fallback guard for unknown tab types
61
- - `src/web/components/layout/tab-content.tsx` — Fallback guard for unknown tab types
62
- - `src/web/components/layout/editor-panel.tsx` — Fallback guard for unknown tab types
63
- - `src/web/hooks/use-extension-ws.ts` — Enhanced RPC integration
64
- - `src/types/extension-messages.ts` — New message types for UX events
65
- - **New Tests:**
66
- - `tests/unit/services/extension-manifest.test.ts` — 9 tests for discoverBundledManifests() and manifest parsing
67
- - `tests/unit/services/extension-service-bundled.test.ts` — 9 tests for isBundled(), discover(), and removal protection
111
+
68
112
  - **Type Changes:**
69
- - Settings: Added `autoFetchInterval: number` option
70
- - Messages: New `openFile`, `openSourceControl`, toast notification message types
71
- - New type: `BundledManifest` (ExtensionManifest with _dir field)
72
- - HEAD ref type detection corrected
113
+ - New: `Stash` = { index, hash, message }
114
+ - New: `MergeState` = { type: "merge" | "rebase" | "cherry-pick", progress?, message? }
115
+ - New: `Worktree` = { path, branch, head, isMain, isDetached, locked, lockReason?, prunable }
116
+ - Updated: `FileChange.status` now includes "U" for unmerged/conflicted entries (was "A" | "M" | "D" | "R" | "C")
117
+ - Updated: `UncommittedData` now includes `conflicted: FileChange[]` and optional `mergeState: MergeState`
118
+ - Updated: `RepoInfo` now includes `stashes: Stash[]` and `currentBranch: string`
119
+ - Updated: `TabType` now includes "conflict-editor"
120
+
121
+ - **Git State Detection:**
122
+ - Merge state determined by .git sentinel files: MERGE_HEAD (merge), rebase-merge/ (interactive rebase), CHERRY_PICK_HEAD (cherry-pick)
123
+ - Progress tracked: "3/5" format from rebase-merge/msgnum and rebase-merge/end
124
+ - Conflict detection: UU/AA/DD/AU/UA/DU/UD git status codes parsed as unmerged entries
125
+ - Only detects merge state when conflicts exist (perf optimization)
126
+
127
+ - **Conflict Resolution Flow:**
128
+ - User clicks conflicted file → opens conflict-editor tab
129
+ - Component parses conflict markers (<<<<<<, =======, >>>>>>>) from file content
130
+ - Displays visual highlighting + Accept buttons above each conflict region
131
+ - Resolution applies edit via Monaco, saves file automatically, updates conflict counter
132
+ - Conflict count reflects real-time resolution progress
133
+
73
134
  - **Security:** Path validation ensures extensions can only operate on registered project paths
74
135
  - **Breaking Changes:** None (backward compatible)
75
- - **Test Coverage:** 129 tests passing (111 extension tests + 18 bundled tests)
136
+ - **Test Coverage:** All changes maintain test suite passing
76
137
 
77
138
  ---
78
139
 
@@ -71,6 +71,7 @@ PPM is the **lightest path from phone to code** — a self-hosted, BYOK, multi-d
71
71
  - File download feature (v0.9.2) — Single-file + folder-as-zip downloads with short-lived tokens, context menu + toolbar UI
72
72
  - Agent Team UI (v0.9.9) — Real-time team monitoring dashboard: REST API + fs.watch inbox events, team activity button with unread pulse, popover/drawer with members + messages, team management in Settings
73
73
  - Git-Graph UI Improvements (v0.9.85+) — ✅ Faithful SVG graph rendering (vscode-git-graph port), interactive git workflow (stage/unstage/commit/stash), branch filters, auto-fetch, mobile support, tab system safety guards, UX refinements (branch context menu, dblclick checkout, toast notifications, SVG icons)
74
+ - Git Workflow Enhancements (v0.9.86+) — ✅ Stash management (popover with Apply/Pop/Drop), rebase from context menu, conflict detection (merge/rebase/cherry-pick states), inline conflict resolution editor (Monaco with visual highlighting + Accept buttons), worktree full CRUD (create/remove/prune with project integration)
74
75
 
75
76
  **Multi-provider — v0.9 scope (reduced):**
76
77
  - Tier 1 (full agentic): Claude Agent SDK — file edit, terminal, git, full autonomy
@@ -100,12 +101,12 @@ PPM is the **lightest path from phone to code** — a self-hosted, BYOK, multi-d
100
101
 
101
102
  ### v0.10.0 — "Enhanced Workflow" (Q3 2026)
102
103
 
103
- **Theme:** Agent collaboration + git workflow. High-impact, independent features that ship fast.
104
+ **Theme:** Agent collaboration + advanced git workflow. High-impact, independent features that ship fast.
104
105
 
105
106
  | Feature | Priority | Description |
106
107
  |---------|----------|-------------|
107
108
  | **Agent Team** | High | Multi-agent collaboration within PPM. Spawn agent teams for parallel task execution — lead agent delegates to specialist agents (coder, tester, reviewer). Task coordination, file ownership, progress tracking. |
108
- | **Worktree management** | Medium | UI to create/switch/delete git worktrees. Use different providers on different branches. Integrated with project switcher. |
109
+ | **Advanced Git Operations** | Medium | Interactive rebase UI, cherry-pick workflow, merge strategy selection. (Worktree management completed in v0.9.86) |
109
110
 
110
111
  ---
111
112
 
@@ -151,13 +151,14 @@ WS /ws/project/:name/terminal/:id → Terminal I/O
151
151
  ```
152
152
  /project/{name} → Project root (project switcher)
153
153
  /project/{name}/editor/{filePath} → Open editor tab (e.g., src/index.ts)
154
+ /project/{name}/conflict-editor/{filePath} → Open conflict resolution editor (during merge/rebase)
154
155
  /project/{name}/chat/{provider}/{sessionId} → Open chat tab
155
156
  /project/{name}/terminal/{index} → Open terminal tab
156
157
  /project/{name}/database/{connId}/{table} → Open database browser
157
158
  /project/{name}/git-graph → Git history graph (singleton)
158
159
  /project/{name}/settings → Settings panel (singleton)
159
160
  ```
160
- Tab IDs are deterministic: `{type}:{identifier}` (e.g., `editor:src/index.ts`, `chat:claude/abc123`). Deep links auto-create missing tabs.
161
+ Tab IDs are deterministic: `{type}:{identifier}` (e.g., `editor:src/index.ts`, `conflict-editor:src/file.ts`, `chat:claude/abc123`). Deep links auto-create missing tabs.
161
162
 
162
163
  ---
163
164
 
@@ -1676,6 +1677,48 @@ Main Process Worker
1676
1677
 
1677
1678
  5. Extension auto-activates based on `activationEvents`, state persists
1678
1679
 
1680
+ ### Error Handling & Debugging
1681
+
1682
+ **Activation Error Tracking:**
1683
+ - `ExtensionService.activationErrors` Map tracks `extId → error message` for all failed activations
1684
+ - Errors set during `activate()` if worker response indicates failure (`!result.ok`)
1685
+ - Errors cleared on successful activation or worker termination
1686
+ - Errors included in `contributions:update` message sent via WS to browser on client connect
1687
+
1688
+ **User Feedback (UI):**
1689
+ - **Command Errors:** When extension command fails, toast shows "Extension command failed: {error}" with error details
1690
+ - **Timeout Handling:** If webview panel doesn't load within 10s, fallback UI displays activation error (if available) + "Retry" button
1691
+ - **Retry Button:** User can click to re-trigger the command without page reload (re-dispatches `ext:command:execute`)
1692
+
1693
+ **Breadcrumb Logging (Console):**
1694
+ - **`[ExtService]`** — Main process lifecycle: activation start/success, worker lifecycle, contributions broadcast
1695
+ - **`[ExtHost]`** — Worker-side execution: command routing, handler invocation, error context
1696
+ - **`[ExtWS]`** — WebSocket bridge: client connect, message handling, error responses
1697
+ - **Extension-specific tags** — e.g., `[ext-git-graph]` for extension-specific log context
1698
+
1699
+ **Example Log Flow (normal):**
1700
+ ```
1701
+ [ExtService] startup: activating ext-git-graph...
1702
+ [ExtWS] Client connected (1 total)
1703
+ [ExtHost] activating ext-git-graph from dist/extension.js
1704
+ [ExtHost] activated ext-git-graph (1 total)
1705
+ [ExtService] activated ext-git-graph successfully
1706
+ [ExtWS] command:execute "git-graph.view"
1707
+ [ExtHost] command:execute "git-graph.view" (1 extensions active)
1708
+ [ExtHost] routing "git-graph.view" → ext-git-graph
1709
+ ```
1710
+
1711
+ **Example Log Flow (error):**
1712
+ ```
1713
+ [ExtService] startup: activating ext-git-graph...
1714
+ [ExtHost] activating ext-git-graph from dist/extension.ts
1715
+ [ExtHost] ERROR: Cannot find module 'missing-dep'
1716
+ [ExtService] Failed to activate ext-git-graph on startup: Cannot find module 'missing-dep'
1717
+ → activationErrors["ext-git-graph"] = "Cannot find module 'missing-dep'"
1718
+ → browser receives { type: "contributions:update", activationErrors: {"ext-git-graph": "..."} }
1719
+ → user sees toast: "Extension "ext-git-graph" failed to activate: Cannot find module..."
1720
+ ```
1721
+
1679
1722
  ### Crash Safety
1680
1723
 
1681
1724
  **Worker Isolation:**
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hienlh/ppm",
3
- "version": "0.9.86",
3
+ "version": "0.9.87",
4
4
  "description": "Personal Project Manager — mobile-first web IDE with AI assistance",
5
5
  "author": "hienlh",
6
6
  "license": "MIT",
@@ -63,12 +63,20 @@ export function activate(context: ExtensionContext, vscode: VscodeApi): void {
63
63
  context.subscriptions.push(
64
64
  vscode.commands.registerCommand("git-graph.view", async (...args: unknown[]) => {
65
65
  const projectPath = args[0] as string | undefined;
66
+ console.log(`[ext-git-graph] git-graph.view command received, projectPath=${projectPath ?? "(none)"}`);
66
67
  const resolvedPath = projectPath || await resolveProjectPath();
67
68
  if (!resolvedPath) {
69
+ console.warn("[ext-git-graph] no project path resolved");
68
70
  await vscode.window.showErrorMessage("Git Graph: No project selected. Open a project first, then try again.");
69
71
  return;
70
72
  }
71
- await openGitGraph(vscode, context, resolvedPath);
73
+ try {
74
+ await openGitGraph(vscode, context, resolvedPath);
75
+ console.log("[ext-git-graph] webview panel created");
76
+ } catch (e) {
77
+ console.error("[ext-git-graph] openGitGraph failed:", e);
78
+ throw e;
79
+ }
72
80
  }),
73
81
  );
74
82
 
@@ -156,6 +164,7 @@ function openGitGraph(
156
164
  await handleRequestCommits(vscode, panel, pp, context);
157
165
  handleUncommittedStatus(vscode, panel, pp); // fire-and-forget
158
166
  handleWorktrees(vscode, panel, pp); // fire-and-forget
167
+ handleStashes(vscode, panel, pp); // fire-and-forget
159
168
  break;
160
169
  case "requestRepoInfo":
161
170
  await handleRepoInfo(vscode, panel, pp);
@@ -263,6 +272,9 @@ function openGitGraph(
263
272
  case "requestWorktrees":
264
273
  await handleWorktrees(vscode, panel, pp);
265
274
  break;
275
+ case "requestStashes":
276
+ await handleStashes(vscode, panel, pp);
277
+ break;
266
278
  case "addWorktree": {
267
279
  const addArgs = ["worktree", "add"];
268
280
  if (msg.newBranch) {
@@ -333,6 +345,16 @@ function openGitGraph(
333
345
  }
334
346
  break;
335
347
  }
348
+ case "openConflictFile": {
349
+ assertSafeFilePaths([msg.filePath], pp);
350
+ const projectName = await resolveProjectName(pp);
351
+ // Opens as conflict-editor tab (Phase 4 will wire this properly)
352
+ await vscode.window.openTab("conflict-editor", `Conflict: ${msg.filePath.split(/[\\/]/).pop()}`, projectName, {
353
+ projectName,
354
+ filePath: msg.filePath,
355
+ });
356
+ break;
357
+ }
336
358
  case "openSourceControl": {
337
359
  await vscode.window.showInformationMessage("Open the Source Control panel from the sidebar.");
338
360
  break;
@@ -372,6 +394,7 @@ async function reloadPanelData(
372
394
  await handleRequestCommits(vscode, panel, projectPath, context);
373
395
  handleUncommittedStatus(vscode, panel, projectPath);
374
396
  handleWorktrees(vscode, panel, projectPath);
397
+ handleStashes(vscode, panel, projectPath);
375
398
  }
376
399
 
377
400
  async function handleRepoInfo(
@@ -453,6 +476,8 @@ async function handleCommitDetails(
453
476
  await panel.webview.postMessage({ command: "commitDetails", data: detail });
454
477
  }
455
478
 
479
+ const UNMERGED_CODES = new Set(["DD", "AU", "UD", "UA", "DU", "AA", "UU"]);
480
+
456
481
  async function handleUncommittedStatus(
457
482
  vscode: VscodeApi,
458
483
  panel: ReturnType<VscodeApi["window"]["createWebviewPanel"]>,
@@ -466,11 +491,20 @@ async function handleUncommittedStatus(
466
491
  }
467
492
  const staged: import("./types.ts").FileChange[] = [];
468
493
  const unstaged: import("./types.ts").FileChange[] = [];
494
+ const conflicted: import("./types.ts").FileChange[] = [];
469
495
  for (const line of result.stdout.split("\n").filter(Boolean)) {
470
- if (staged.length + unstaged.length >= 500) break; // cap total
471
- const x = line[0]; // staged status
472
- const y = line[1]; // unstaged status
496
+ if (staged.length + unstaged.length + conflicted.length >= 500) break;
497
+ const xy = line.substring(0, 2);
473
498
  const filePath = line.substring(3);
499
+
500
+ // Check for unmerged/conflict entries first
501
+ if (UNMERGED_CODES.has(xy)) {
502
+ conflicted.push({ path: filePath, status: "U", additions: 0, deletions: 0 });
503
+ continue;
504
+ }
505
+
506
+ const x = xy[0]; // staged status
507
+ const y = xy[1]; // unstaged status
474
508
  if (x !== " " && x !== "?") {
475
509
  staged.push({ path: filePath, status: mapStatusCode(x), additions: 0, deletions: 0 });
476
510
  }
@@ -481,15 +515,72 @@ async function handleUncommittedStatus(
481
515
  unstaged.push({ path: filePath, status: "A", additions: 0, deletions: 0 });
482
516
  }
483
517
  }
518
+
519
+ // Only detect merge state when conflicts exist (perf optimization)
520
+ let mergeState: import("./types.ts").MergeState | undefined;
521
+ if (conflicted.length > 0) {
522
+ mergeState = await detectMergeState(vscode, projectPath);
523
+ }
524
+
484
525
  await panel.webview.postMessage({
485
526
  command: "loadUncommitted",
486
- data: { staged, unstaged },
527
+ data: { staged, unstaged, conflicted, mergeState },
487
528
  });
488
529
  } catch {
489
530
  await panel.webview.postMessage({ command: "loadUncommitted", data: null });
490
531
  }
491
532
  }
492
533
 
534
+ async function detectMergeState(
535
+ vscode: VscodeApi,
536
+ projectPath: string,
537
+ ): Promise<import("./types.ts").MergeState | undefined> {
538
+ // Resolve the actual GIT_DIR (handles worktrees where .git is a pointer file)
539
+ const gitDirResult = await spawnGit(vscode, ["rev-parse", "--git-dir"], projectPath, 2000);
540
+ if (gitDirResult.exitCode !== 0) return undefined;
541
+ let gitDir = gitDirResult.stdout.trim();
542
+ // Make absolute if relative
543
+ if (!gitDir.startsWith("/")) gitDir = `${projectPath}/${gitDir}`;
544
+
545
+ // Check rebase-merge (interactive rebase)
546
+ const rebaseMergeDir = `${gitDir}/rebase-merge`;
547
+ const checkRebase = await vscode.process.spawn("test", ["-d", rebaseMergeDir], projectPath, { timeout: 2000 });
548
+ if (checkRebase.exitCode === 0) {
549
+ const [numResult, endResult, msgResult] = await Promise.all([
550
+ vscode.process.spawn("cat", [`${rebaseMergeDir}/msgnum`], projectPath, { timeout: 2000 }),
551
+ vscode.process.spawn("cat", [`${rebaseMergeDir}/end`], projectPath, { timeout: 2000 }),
552
+ vscode.process.spawn("cat", [`${rebaseMergeDir}/message`], projectPath, { timeout: 2000 }),
553
+ ]);
554
+ const current = numResult.stdout.trim();
555
+ const total = endResult.stdout.trim();
556
+ return {
557
+ type: "rebase",
558
+ progress: current && total ? `${current}/${total}` : undefined,
559
+ message: msgResult.stdout.trim().split("\n")[0] || undefined,
560
+ };
561
+ }
562
+
563
+ // Check rebase-apply (non-interactive rebase / am)
564
+ const checkRebaseApply = await vscode.process.spawn("test", ["-d", `${gitDir}/rebase-apply`], projectPath, { timeout: 2000 });
565
+ if (checkRebaseApply.exitCode === 0) {
566
+ return { type: "rebase" };
567
+ }
568
+
569
+ // Check merge
570
+ const checkMerge = await vscode.process.spawn("test", ["-f", `${gitDir}/MERGE_HEAD`], projectPath, { timeout: 2000 });
571
+ if (checkMerge.exitCode === 0) {
572
+ return { type: "merge" };
573
+ }
574
+
575
+ // Check cherry-pick
576
+ const checkCherry = await vscode.process.spawn("test", ["-f", `${gitDir}/CHERRY_PICK_HEAD`], projectPath, { timeout: 2000 });
577
+ if (checkCherry.exitCode === 0) {
578
+ return { type: "cherry-pick" };
579
+ }
580
+
581
+ return undefined;
582
+ }
583
+
493
584
  async function handleWorktrees(
494
585
  vscode: VscodeApi,
495
586
  panel: ReturnType<VscodeApi["window"]["createWebviewPanel"]>,
@@ -527,6 +618,29 @@ async function handleWorktrees(
527
618
  await panel.webview.postMessage({ command: "loadWorktrees", data: worktrees });
528
619
  }
529
620
 
621
+ async function handleStashes(
622
+ vscode: VscodeApi,
623
+ panel: ReturnType<VscodeApi["window"]["createWebviewPanel"]>,
624
+ projectPath: string,
625
+ ): Promise<void> {
626
+ const result = await spawnGit(vscode, ["stash", "list", "--format=%gd|%H|%s"], projectPath, 10_000);
627
+ const stashes: import("./types.ts").Stash[] = [];
628
+ if (result.exitCode === 0 && result.stdout.trim()) {
629
+ for (const line of result.stdout.split("\n").filter(Boolean)) {
630
+ const parts = line.split("|");
631
+ if (parts.length >= 3) {
632
+ const refMatch = parts[0].match(/\{(\d+)\}/);
633
+ stashes.push({
634
+ index: refMatch ? parseInt(refMatch[1]) : stashes.length,
635
+ hash: parts[1],
636
+ message: parts.slice(2).join("|"),
637
+ });
638
+ }
639
+ }
640
+ }
641
+ await panel.webview.postMessage({ command: "loadStashes", data: stashes });
642
+ }
643
+
530
644
  function mapStatusCode(code: string): "A" | "M" | "D" | "R" {
531
645
  if (code === "A" || code === "?") return "A";
532
646
  if (code === "D") return "D";
@@ -754,6 +868,13 @@ function buildGitActionArgs(action: string, args: Record<string, unknown>): stri
754
868
  case "stashSave": return ["stash", "push", ...(args.message ? ["-m", String(args.message)] : [])];
755
869
  case "stashPop": return ["stash", "pop", ...(args.stashRef ? [assertValidRef(args.stashRef, "stashRef")] : [])];
756
870
  case "stashDrop": return ["stash", "drop", ...(args.stashRef ? [assertValidRef(args.stashRef, "stashRef")] : [])];
871
+ case "stashApply": return ["stash", "apply", ...(args.stashRef ? [assertValidRef(args.stashRef, "stashRef")] : [])];
872
+ case "rebaseContinue": return ["rebase", "--continue"];
873
+ case "rebaseAbort": return ["rebase", "--abort"];
874
+ case "rebaseSkip": return ["rebase", "--skip"];
875
+ case "mergeAbort": return ["merge", "--abort"];
876
+ case "cherryPickAbort": return ["cherry-pick", "--abort"];
877
+ case "cherryPickContinue": return ["cherry-pick", "--continue"];
757
878
  case "fetch": return ["fetch", ...(args.remote ? [assertValidRemote(args.remote)] : []), ...(args.prune ? ["--prune"] : [])];
758
879
  case "pull": return ["pull", ...(args.remote ? [assertValidRemote(args.remote)] : []), ...(args.branch ? [assertValidRef(args.branch, "branch")] : [])];
759
880
  case "renameBranch": {
@@ -71,11 +71,17 @@ export interface CommitDetail {
71
71
  export interface FileChange {
72
72
  path: string;
73
73
  oldPath?: string;
74
- status: "A" | "M" | "D" | "R" | "C";
74
+ status: "A" | "M" | "D" | "R" | "C" | "U";
75
75
  additions: number;
76
76
  deletions: number;
77
77
  }
78
78
 
79
+ export interface MergeState {
80
+ type: "merge" | "rebase" | "cherry-pick";
81
+ progress?: string; // e.g. "3/5" for rebase
82
+ message?: string; // current commit message being rebased
83
+ }
84
+
79
85
  export interface RepoInfo {
80
86
  path: string;
81
87
  branches: Branch[];
@@ -94,6 +100,8 @@ export interface ActionResult {
94
100
  export interface UncommittedData {
95
101
  staged: FileChange[];
96
102
  unstaged: FileChange[];
103
+ conflicted: FileChange[];
104
+ mergeState?: MergeState;
97
105
  }
98
106
 
99
107
  // --- Settings ---
@@ -152,6 +160,7 @@ export type ExtToWebview =
152
160
  | { command: "refresh"; data: GitVertex[]; repoInfo: RepoInfo }
153
161
  | { command: "actionResult"; action: string; args?: Record<string, unknown>; result: ActionResult }
154
162
  | { command: "loadWorktrees"; data: Worktree[] }
163
+ | { command: "loadStashes"; data: Stash[] }
155
164
  | { command: "error"; message: string };
156
165
 
157
166
  // --- Webview → Extension messages ---
@@ -178,4 +187,6 @@ export type WebviewToExt =
178
187
  | { command: "pruneWorktrees" }
179
188
  | { command: "openWorktree"; path: string }
180
189
  | { command: "openFile"; filePath: string }
181
- | { command: "openSourceControl" };
190
+ | { command: "openConflictFile"; filePath: string }
191
+ | { command: "openSourceControl" }
192
+ | { command: "requestStashes" };