@hienlh/ppm 0.1.0

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 (159) hide show
  1. package/.claude/agent-memory/tester/MEMORY.md +3 -0
  2. package/.claude/agent-memory/tester/project-ppm-test-conventions.md +32 -0
  3. package/.env.example +1 -0
  4. package/.github/workflows/release.yml +46 -0
  5. package/README.md +349 -0
  6. package/bun.lock +1217 -0
  7. package/components.json +21 -0
  8. package/docs/code-standards.md +574 -0
  9. package/docs/codebase-summary.md +294 -0
  10. package/docs/deployment-guide.md +631 -0
  11. package/docs/design-guidelines.md +661 -0
  12. package/docs/project-overview-pdr.md +142 -0
  13. package/docs/project-roadmap.md +400 -0
  14. package/docs/system-architecture.md +459 -0
  15. package/package.json +68 -0
  16. package/plans/260314-2009-ppm-implementation/phase-01-project-skeleton.md +81 -0
  17. package/plans/260314-2009-ppm-implementation/phase-02-backend-core.md +148 -0
  18. package/plans/260314-2009-ppm-implementation/phase-03-frontend-shell.md +256 -0
  19. package/plans/260314-2009-ppm-implementation/phase-04-file-explorer-editor.md +120 -0
  20. package/plans/260314-2009-ppm-implementation/phase-05-web-terminal.md +174 -0
  21. package/plans/260314-2009-ppm-implementation/phase-06-git-integration.md +244 -0
  22. package/plans/260314-2009-ppm-implementation/phase-07-ai-chat.md +242 -0
  23. package/plans/260314-2009-ppm-implementation/phase-08-cli-commands.md +143 -0
  24. package/plans/260314-2009-ppm-implementation/phase-09-pwa-build-deploy.md +209 -0
  25. package/plans/260314-2009-ppm-implementation/phase-10-testing.md +311 -0
  26. package/plans/260314-2009-ppm-implementation/plan.md +202 -0
  27. package/plans/260315-0356-project-scoped-api-refactor/phase-01-backend-project-router.md +145 -0
  28. package/plans/260315-0356-project-scoped-api-refactor/phase-02-frontend-api-migration.md +107 -0
  29. package/plans/260315-0356-project-scoped-api-refactor/phase-03-per-project-tabs.md +100 -0
  30. package/plans/260315-0356-project-scoped-api-refactor/phase-04-websocket-migration.md +66 -0
  31. package/plans/260315-0356-project-scoped-api-refactor/plan.md +87 -0
  32. package/plans/reports/brainstorm-260314-1938-final-techstack.md +342 -0
  33. package/plans/reports/docs-manager-260315-1314-documentation-creation.md +386 -0
  34. package/plans/reports/fullstack-developer-260314-2252-phase-02-backend-core.md +57 -0
  35. package/plans/reports/fullstack-developer-260314-2253-phase-03-frontend-shell.md +70 -0
  36. package/plans/reports/fullstack-developer-260314-2300-phase-04-05-file-api-terminal-ws.md +49 -0
  37. package/plans/reports/fullstack-developer-260314-2300-phase-04-05-file-explorer-editor-terminal.md +52 -0
  38. package/plans/reports/fullstack-developer-260314-2307-ai-chat-phase7.md +58 -0
  39. package/plans/reports/fullstack-developer-260314-2307-phase-06-git-integration.md +33 -0
  40. package/plans/reports/research-260314-1911-ppm-tech-stack.md +318 -0
  41. package/plans/reports/research-260314-1930-claude-code-integration.md +293 -0
  42. package/plans/reports/researcher-260314-2232-node-pty-bun-crash-analysis.md +305 -0
  43. package/plans/reports/researcher-260314-2232-ui-style.md +942 -0
  44. package/plans/reports/researcher-260315-0300-opcode-claude-interaction.md +745 -0
  45. package/plans/reports/researcher-260315-0303-opcode-deep-analysis.md +742 -0
  46. package/plans/reports/researcher-260315-0305-claude-agent-sdk-github-research.md +423 -0
  47. package/plans/reports/tester-260314-2053-initial-test-suite.md +81 -0
  48. package/ppm.example.yaml +14 -0
  49. package/repomix-output.xml +23745 -0
  50. package/scripts/build.ts +13 -0
  51. package/src/cli/commands/chat-cmd.ts +259 -0
  52. package/src/cli/commands/config-cmd.ts +121 -0
  53. package/src/cli/commands/git-cmd.ts +315 -0
  54. package/src/cli/commands/init.ts +57 -0
  55. package/src/cli/commands/open.ts +19 -0
  56. package/src/cli/commands/projects.ts +100 -0
  57. package/src/cli/commands/start.ts +3 -0
  58. package/src/cli/commands/stop.ts +33 -0
  59. package/src/cli/utils/project-resolver.ts +27 -0
  60. package/src/index.ts +59 -0
  61. package/src/providers/claude-agent-sdk.ts +499 -0
  62. package/src/providers/claude-binary-finder.ts +256 -0
  63. package/src/providers/claude-code-cli.ts +413 -0
  64. package/src/providers/claude-process-registry.ts +106 -0
  65. package/src/providers/mock-provider.ts +171 -0
  66. package/src/providers/provider.interface.ts +10 -0
  67. package/src/providers/registry.ts +45 -0
  68. package/src/server/helpers/resolve-project.ts +22 -0
  69. package/src/server/index.ts +181 -0
  70. package/src/server/middleware/auth.ts +30 -0
  71. package/src/server/routes/chat.ts +153 -0
  72. package/src/server/routes/files.ts +168 -0
  73. package/src/server/routes/git.ts +261 -0
  74. package/src/server/routes/project-scoped.ts +27 -0
  75. package/src/server/routes/projects.ts +57 -0
  76. package/src/server/routes/static.ts +26 -0
  77. package/src/server/ws/chat.ts +130 -0
  78. package/src/server/ws/terminal.ts +89 -0
  79. package/src/services/chat.service.ts +110 -0
  80. package/src/services/claude-usage.service.ts +113 -0
  81. package/src/services/config.service.ts +90 -0
  82. package/src/services/file.service.ts +261 -0
  83. package/src/services/git-dirs.service.ts +112 -0
  84. package/src/services/git.service.ts +372 -0
  85. package/src/services/project.service.ts +107 -0
  86. package/src/services/slash-items.service.ts +184 -0
  87. package/src/services/terminal.service.ts +212 -0
  88. package/src/types/api.ts +37 -0
  89. package/src/types/chat.ts +92 -0
  90. package/src/types/config.ts +41 -0
  91. package/src/types/git.ts +50 -0
  92. package/src/types/project.ts +18 -0
  93. package/src/types/terminal.ts +20 -0
  94. package/src/web/app.tsx +168 -0
  95. package/src/web/components/auth/login-screen.tsx +88 -0
  96. package/src/web/components/chat/attachment-chips.tsx +55 -0
  97. package/src/web/components/chat/chat-placeholder.tsx +10 -0
  98. package/src/web/components/chat/chat-tab.tsx +301 -0
  99. package/src/web/components/chat/file-picker.tsx +126 -0
  100. package/src/web/components/chat/message-input.tsx +420 -0
  101. package/src/web/components/chat/message-list.tsx +838 -0
  102. package/src/web/components/chat/session-picker.tsx +139 -0
  103. package/src/web/components/chat/slash-command-picker.tsx +135 -0
  104. package/src/web/components/chat/usage-badge.tsx +186 -0
  105. package/src/web/components/editor/code-editor.tsx +329 -0
  106. package/src/web/components/editor/diff-viewer.tsx +276 -0
  107. package/src/web/components/editor/editor-placeholder.tsx +10 -0
  108. package/src/web/components/explorer/file-actions.tsx +191 -0
  109. package/src/web/components/explorer/file-tree.tsx +298 -0
  110. package/src/web/components/git/git-graph.tsx +727 -0
  111. package/src/web/components/git/git-placeholder.tsx +55 -0
  112. package/src/web/components/git/git-status-panel.tsx +850 -0
  113. package/src/web/components/layout/mobile-drawer.tsx +137 -0
  114. package/src/web/components/layout/mobile-nav.tsx +103 -0
  115. package/src/web/components/layout/sidebar.tsx +90 -0
  116. package/src/web/components/layout/tab-bar.tsx +152 -0
  117. package/src/web/components/layout/tab-content.tsx +85 -0
  118. package/src/web/components/projects/dir-suggest.tsx +152 -0
  119. package/src/web/components/projects/project-list.tsx +187 -0
  120. package/src/web/components/settings/settings-tab.tsx +57 -0
  121. package/src/web/components/terminal/terminal-placeholder.tsx +10 -0
  122. package/src/web/components/terminal/terminal-tab.tsx +133 -0
  123. package/src/web/components/ui/button.tsx +64 -0
  124. package/src/web/components/ui/context-menu.tsx +250 -0
  125. package/src/web/components/ui/dialog.tsx +156 -0
  126. package/src/web/components/ui/dropdown-menu.tsx +257 -0
  127. package/src/web/components/ui/input.tsx +21 -0
  128. package/src/web/components/ui/scroll-area.tsx +56 -0
  129. package/src/web/components/ui/separator.tsx +26 -0
  130. package/src/web/components/ui/sonner.tsx +40 -0
  131. package/src/web/components/ui/tabs.tsx +91 -0
  132. package/src/web/components/ui/tooltip.tsx +57 -0
  133. package/src/web/hooks/use-chat.ts +420 -0
  134. package/src/web/hooks/use-terminal.ts +182 -0
  135. package/src/web/hooks/use-url-sync.ts +66 -0
  136. package/src/web/hooks/use-websocket.ts +48 -0
  137. package/src/web/index.html +16 -0
  138. package/src/web/lib/api-client.ts +90 -0
  139. package/src/web/lib/file-support.ts +68 -0
  140. package/src/web/lib/utils.ts +6 -0
  141. package/src/web/lib/ws-client.ts +100 -0
  142. package/src/web/main.tsx +10 -0
  143. package/src/web/public/icon-192.svg +5 -0
  144. package/src/web/public/icon-512.svg +5 -0
  145. package/src/web/stores/file-store.ts +81 -0
  146. package/src/web/stores/project-store.ts +50 -0
  147. package/src/web/stores/settings-store.ts +65 -0
  148. package/src/web/stores/tab-store.ts +187 -0
  149. package/src/web/styles/globals.css +227 -0
  150. package/src/web/vite-env.d.ts +1 -0
  151. package/tests/integration/api/chat-routes.test.ts +95 -0
  152. package/tests/integration/claude-agent-sdk-integration.test.ts +228 -0
  153. package/tests/integration/ws/chat-websocket.test.ts +312 -0
  154. package/tests/test-setup.ts +5 -0
  155. package/tests/unit/providers/claude-agent-sdk.test.ts +339 -0
  156. package/tests/unit/providers/mock-provider.test.ts +143 -0
  157. package/tests/unit/services/chat-service.test.ts +100 -0
  158. package/tsconfig.json +32 -0
  159. package/vite.config.ts +62 -0
@@ -0,0 +1,202 @@
1
+ ---
2
+ status: pending
3
+ created: 2026-03-14
4
+ updated: 2026-03-14
5
+ slug: ppm-implementation
6
+ version: 3
7
+ ---
8
+
9
+ # PPM Implementation Plan (v2)
10
+
11
+ ## Overview
12
+
13
+ Build PPM (Personal Project Manager) — mobile-first web IDE running as CLI daemon.
14
+ Tech: Bun + Hono (backend), React 19 + Vite + Tailwind + shadcn/ui (frontend).
15
+
16
+ ## Context
17
+
18
+ - [Final Tech Stack](../reports/brainstorm-260314-1938-final-techstack.md)
19
+ - [Tech Stack Research](../reports/research-260314-1911-ppm-tech-stack.md)
20
+ - [Claude Code Integration Research](../reports/research-260314-1930-claude-code-integration.md)
21
+ - [UI Style Guide](../reports/researcher-260314-2232-ui-style.md)
22
+ - [node-pty Research](../reports/researcher-260314-2232-node-pty-bun-crash-analysis.md)
23
+
24
+ ## V1 Lessons (MUST follow)
25
+
26
+ These were bugs/issues found during v1 browser E2E testing. V2 must build them in from the start:
27
+
28
+ 1. **API envelope auto-unwrap:** `api-client.get<T>()` returns `T` directly, not `{ok, data: T}`. Backend wraps in `{ok, data}`, client unwraps.
29
+ 2. **Project resolution by NAME:** All API routes accept project name (e.g. "ppm"), not filesystem path. Use `resolveProjectPath(name)` helper in every route.
30
+ 3. **Terminal: Bun native Terminal API:** Use `Bun.spawn()` with `terminal` option (full PTY). node-pty uses NAN bindings incompatible with Bun — hard crash, no fix possible. See [research](../reports/researcher-260314-2232-node-pty-bun-crash-analysis.md).
31
+ 4. **Git log: use simple-git built-in:** Don't parse `git log --format` manually. Use `simple-git`'s `.log()` method which handles parsing correctly.
32
+ 5. **Metadata on all tab openers:** Both tab-bar AND mobile-nav must pass `{ projectName }` metadata when opening git/file tabs.
33
+ 6. **Mobile sidebar = overlay drawer:** Don't use `hidden md:flex`. Use absolute positioned overlay with backdrop that slides in on hamburger click.
34
+
35
+ ## Team
36
+
37
+ | Agent | Role | File Ownership |
38
+ |---|---|---|
39
+ | **Lead** (main) | Coordinator, shared types, config files | `src/types/**`, `package.json`, configs, `CLAUDE.md` |
40
+ | **backend-dev** | Server, CLI, services | `src/cli/**`, `src/server/**`, `src/services/**`, `src/providers/**` |
41
+ | **frontend-dev** | React UI, all components | `src/web/**`, `public/**`, `index.html` |
42
+ | **tester** | Unit + integration tests | `tests/**`, `*.test.ts` (read-only on src) |
43
+
44
+ ## Phases
45
+
46
+ | # | Phase | Owner | Depends | Status |
47
+ |---|---|---|---|---|
48
+ | 1 | [Project Skeleton + Shared Types](phase-01-project-skeleton.md) | Lead | — | pending |
49
+ | 2 | [Backend Core (Server + CLI + Config)](phase-02-backend-core.md) | backend-dev | 1 | pending |
50
+ | 3 | [Frontend Shell (Tab System + Layout)](phase-03-frontend-shell.md) | frontend-dev | 1 | pending |
51
+ | 4 | [File Explorer + Editor](phase-04-file-explorer-editor.md) | backend-dev + frontend-dev | 2, 3 | pending |
52
+ | 5 | [Web Terminal](phase-05-web-terminal.md) | backend-dev + frontend-dev | 2, 3 | pending |
53
+ | 6 | [Git Integration](phase-06-git-integration.md) | backend-dev + frontend-dev | 4 | pending |
54
+ | 7 | [AI Chat](phase-07-ai-chat.md) | backend-dev + frontend-dev | 2, 3 | pending |
55
+ | 8 | [CLI Commands](phase-08-cli-commands.md) | backend-dev | 2, 6, 7 | pending |
56
+ | 9 | [PWA + Build + Deploy](phase-09-pwa-build-deploy.md) | Lead | all | pending |
57
+ | 10 | [Testing](phase-10-testing.md) | tester | per phase | pending |
58
+
59
+ ## Execution Order
60
+
61
+ Backend phases run **sequentially** (single agent, 200K context limit). Frontend can parallel where independent.
62
+
63
+ ```
64
+ Phase 1 (Lead)
65
+ ├── Phase 2 (backend-dev) ──→ Phase 4-BE ──→ Phase 5-BE ──→ Phase 6-BE ──→ Phase 7-BE ──→ Phase 8
66
+ ├── Phase 3 (frontend-dev) ──→ Phase 4-FE ──→ Phase 5-FE ──→ Phase 6-FE ──→ Phase 7-FE
67
+
68
+ Phase 9 (Lead, after all)
69
+ Phase 10 (tester, continuous after each phase)
70
+ ```
71
+
72
+ Note: Each phase's backend + frontend can run in parallel (separate agents), but backend phases are sequential among themselves.
73
+
74
+ ## Project Structure
75
+
76
+ ```
77
+ ppm/
78
+ ├── package.json
79
+ ├── tsconfig.json
80
+ ├── bunfig.toml
81
+ ├── vite.config.ts
82
+ ├── ppm.example.yaml
83
+ ├── .env.example
84
+ ├── LICENSE (MIT)
85
+ ├── README.md
86
+ ├── CLAUDE.md
87
+ ├── src/
88
+ │ ├── index.ts # Entry point (CLI)
89
+ │ ├── types/
90
+ │ │ ├── config.ts # Config types
91
+ │ │ ├── project.ts # Project types
92
+ │ │ ├── git.ts # Git types (commit, branch, graph)
93
+ │ │ ├── chat.ts # Chat/AI types (ChatEvent, AIProvider)
94
+ │ │ ├── terminal.ts # Terminal session types
95
+ │ │ └── api.ts # API request/response types
96
+ │ ├── cli/
97
+ │ │ ├── index.ts # Commander.js setup
98
+ │ │ ├── commands/
99
+ │ │ │ ├── init.ts
100
+ │ │ │ ├── start.ts
101
+ │ │ │ ├── stop.ts
102
+ │ │ │ ├── open.ts
103
+ │ │ │ ├── projects.ts
104
+ │ │ │ ├── config.ts
105
+ │ │ │ ├── git.ts
106
+ │ │ │ └── chat.ts
107
+ │ │ └── utils/
108
+ │ │ └── project-resolver.ts # CWD auto-detect + -p flag
109
+ │ ├── server/
110
+ │ │ ├── index.ts # Hono app + Bun.serve + WS upgrade
111
+ │ │ ├── middleware/
112
+ │ │ │ └── auth.ts # Token auth middleware
113
+ │ │ ├── routes/
114
+ │ │ │ ├── projects.ts
115
+ │ │ │ ├── files.ts
116
+ │ │ │ ├── git.ts
117
+ │ │ │ └── static.ts # Serve embedded SPA
118
+ │ │ ├── ws/
119
+ │ │ │ ├── terminal.ts # WS /ws/terminal/:id
120
+ │ │ │ └── chat.ts # WS /ws/chat/:id
121
+ │ │ └── helpers/
122
+ │ │ └── resolve-project.ts # Shared: name → path resolver
123
+ │ ├── services/
124
+ │ │ ├── config.service.ts
125
+ │ │ ├── project.service.ts
126
+ │ │ ├── file.service.ts
127
+ │ │ ├── git.service.ts
128
+ │ │ ├── terminal.service.ts # Uses Bun.spawn (NOT node-pty)
129
+ │ │ └── chat.service.ts
130
+ │ ├── providers/
131
+ │ │ ├── provider.interface.ts
132
+ │ │ ├── claude-agent-sdk.ts
133
+ │ │ ├── cli-subprocess.ts # Future generic CLI provider
134
+ │ │ └── registry.ts
135
+ │ └── web/ # React SPA (Vite)
136
+ │ ├── index.html
137
+ │ ├── main.tsx
138
+ │ ├── app.tsx
139
+ │ ├── vite-env.d.ts
140
+ │ ├── components/
141
+ │ │ ├── auth/
142
+ │ │ │ └── login-screen.tsx # Token input screen
143
+ │ │ ├── layout/
144
+ │ │ │ ├── tab-bar.tsx
145
+ │ │ │ ├── tab-content.tsx
146
+ │ │ │ ├── sidebar.tsx # Desktop sidebar
147
+ │ │ │ ├── mobile-drawer.tsx # Mobile overlay sidebar
148
+ │ │ │ └── mobile-nav.tsx
149
+ │ │ ├── projects/
150
+ │ │ │ ├── project-list.tsx
151
+ │ │ │ └── project-card.tsx
152
+ │ │ ├── explorer/
153
+ │ │ │ ├── file-tree.tsx
154
+ │ │ │ └── file-actions.tsx
155
+ │ │ ├── editor/
156
+ │ │ │ ├── code-editor.tsx
157
+ │ │ │ └── diff-viewer.tsx
158
+ │ │ ├── terminal/
159
+ │ │ │ └── terminal-tab.tsx
160
+ │ │ ├── chat/
161
+ │ │ │ ├── chat-tab.tsx
162
+ │ │ │ ├── message-list.tsx
163
+ │ │ │ ├── message-input.tsx
164
+ │ │ │ ├── tool-approval.tsx
165
+ │ │ │ └── session-picker.tsx
166
+ │ │ ├── git/
167
+ │ │ │ ├── git-graph.tsx
168
+ │ │ │ ├── git-graph-renderer.tsx
169
+ │ │ │ ├── git-status-panel.tsx
170
+ │ │ │ ├── git-diff-tab.tsx
171
+ │ │ │ └── commit-context-menu.tsx
172
+ │ │ └── ui/ # shadcn/ui components
173
+ │ │ └── ...
174
+ │ ├── stores/
175
+ │ │ ├── tab.store.ts
176
+ │ │ ├── project.store.ts
177
+ │ │ └── settings.store.ts
178
+ │ ├── hooks/
179
+ │ │ ├── use-websocket.ts
180
+ │ │ ├── use-terminal.ts
181
+ │ │ └── use-chat.ts
182
+ │ ├── lib/
183
+ │ │ ├── api-client.ts # Auto-unwraps {ok, data} envelope
184
+ │ │ ├── ws-client.ts # WebSocket client with reconnect
185
+ │ │ └── git-graph-layout.ts
186
+ │ └── styles/
187
+ │ └── globals.css
188
+ ├── tests/
189
+ │ ├── unit/
190
+ │ │ ├── services/
191
+ │ │ └── providers/
192
+ │ ├── integration/
193
+ │ │ ├── api/
194
+ │ │ └── ws/
195
+ │ └── setup.ts
196
+ ├── scripts/
197
+ │ └── build.ts
198
+ └── docs/
199
+ ├── project-overview-pdr.md
200
+ ├── code-standards.md
201
+ └── system-architecture.md
202
+ ```
@@ -0,0 +1,145 @@
1
+ ---
2
+ phase: 1
3
+ title: "Backend project-scoped router"
4
+ status: completed
5
+ effort: 2h
6
+ ---
7
+
8
+ # Phase 1: Backend Project-Scoped Router
9
+
10
+ ## Context
11
+ - [plan.md](./plan.md)
12
+ - `src/server/index.ts` -- main app, route mounting, WS upgrade
13
+ - `src/server/routes/chat.ts`, `git.ts`, `files.ts`
14
+ - `src/server/helpers/resolve-project.ts`
15
+ - `src/server/ws/terminal.ts`, `chat.ts`
16
+
17
+ ## Overview
18
+ Create a Hono sub-router mounted at `/api/project/:projectName` with middleware that resolves the project path. Remount chat, git, files routes under it. Remove per-route `resolveProjectPath` calls.
19
+
20
+ ## Architecture
21
+
22
+ ```
23
+ app.route("/api/project/:projectName", projectScopedRouter)
24
+ -> middleware: resolve projectName, set c.set("projectPath", path)
25
+ -> projectScopedRouter.route("/chat", chatRoutes)
26
+ -> projectScopedRouter.route("/git", gitRoutes)
27
+ -> projectScopedRouter.route("/files", fileRoutes)
28
+ ```
29
+
30
+ ## Related Code Files
31
+
32
+ ### Files to modify
33
+ - `src/server/index.ts` -- new mount point, update WS upgrade paths
34
+ - `src/server/routes/chat.ts` -- read projectPath from context, remove dir/projectName from query/body
35
+ - `src/server/routes/git.ts` -- read projectPath from context, remove `:project` param from each route, remove `project` from POST bodies
36
+ - `src/server/routes/files.ts` -- read projectPath from context, remove `:project` param from each route
37
+
38
+ ### Files to create
39
+ - `src/server/routes/project-scoped.ts` -- the sub-router with middleware
40
+
41
+ ### Files unchanged
42
+ - `src/server/routes/projects.ts` -- stays at `/api/projects` (global)
43
+ - `src/server/routes/static.ts` -- unchanged
44
+ - `src/server/middleware/auth.ts` -- unchanged
45
+
46
+ ## Implementation Steps
47
+
48
+ ### 1. Create project-scoped router (`src/server/routes/project-scoped.ts`)
49
+ ```ts
50
+ import { Hono } from "hono";
51
+ import { resolveProjectPath } from "../helpers/resolve-project.ts";
52
+ import { chatRoutes } from "./chat.ts";
53
+ import { gitRoutes } from "./git.ts";
54
+ import { fileRoutes } from "./files.ts";
55
+
56
+ type Env = { Variables: { projectPath: string; projectName: string } };
57
+
58
+ export const projectScopedRouter = new Hono<Env>();
59
+
60
+ // Middleware: resolve project name to path
61
+ projectScopedRouter.use("*", async (c, next) => {
62
+ const name = c.req.param("projectName");
63
+ if (!name) return c.json({ ok: false, error: "Missing project name" }, 400);
64
+ const projectPath = resolveProjectPath(name);
65
+ c.set("projectPath", projectPath);
66
+ c.set("projectName", name);
67
+ await next();
68
+ });
69
+
70
+ projectScopedRouter.route("/chat", chatRoutes);
71
+ projectScopedRouter.route("/git", gitRoutes);
72
+ projectScopedRouter.route("/files", fileRoutes);
73
+ ```
74
+
75
+ ### 2. Update `src/server/index.ts`
76
+ - Replace separate route mounts:
77
+ ```diff
78
+ - app.route("/api/files", fileRoutes);
79
+ - app.route("/api/chat", chatRoutes);
80
+ - app.route("/api/git", gitRoutes);
81
+ + app.route("/api/project/:projectName", projectScopedRouter);
82
+ ```
83
+ - Keep: `app.route("/api/projects", projectRoutes)`
84
+ - Update WS upgrade paths (see Phase 4, but do now for consistency):
85
+ ```diff
86
+ - if (url.pathname.startsWith("/ws/terminal/"))
87
+ + if (url.pathname.startsWith("/ws/project/"))
88
+ ```
89
+ Parse: `/ws/project/:projectName/terminal/:id` and `/ws/project/:projectName/chat/:sessionId`
90
+
91
+ ### 3. Refactor `src/server/routes/git.ts`
92
+ - Remove `resolveProjectPath` import
93
+ - GET routes: remove `/:project` param segment; read `c.get("projectPath")` from context
94
+ - `/status/:project` -> `/status`
95
+ - `/diff/:project` -> `/diff`
96
+ - `/diff-stat/:project` -> `/diff-stat`
97
+ - `/file-diff/:project` -> `/file-diff`
98
+ - `/graph/:project` -> `/graph`
99
+ - `/branches/:project` -> `/branches`
100
+ - `/pr-url/:project` -> `/pr-url`
101
+ - POST routes: remove `project` from `c.req.json()` destructuring; use `c.get("projectPath")`
102
+ - `/stage`, `/unstage`, `/commit`, `/push`, `/pull`, `/branch/create`, `/checkout`, `/branch/delete`, `/merge`, `/cherry-pick`, `/revert`, `/tag`
103
+
104
+ ### 4. Refactor `src/server/routes/files.ts`
105
+ - Remove `resolveProjectPath` import
106
+ - All routes: remove `/:project` param; use `c.get("projectPath")`
107
+ - `/tree/:project` -> `/tree`
108
+ - `/read/:project` -> `/read`
109
+ - `/write/:project` -> `/write`
110
+ - `/create/:project` -> `/create`
111
+ - `/delete/:project` -> `/delete`
112
+ - `/compare/:project` -> `/compare`
113
+ - `/rename/:project` -> `/rename`
114
+ - `/move/:project` -> `/move`
115
+
116
+ ### 5. Refactor `src/server/routes/chat.ts`
117
+ - Remove `dir` query param from sessions list; project comes from URL context
118
+ - Remove `projectName` from POST body on session creation; use `c.get("projectName")`
119
+ - Keep `/providers` endpoint -- move to project-scoped or keep global. Decision: keep project-scoped since providers may vary per project in future.
120
+ - Sessions list: filter by `c.get("projectName")` instead of `dir` query
121
+
122
+ ### 6. Update WS handlers in `src/server/index.ts`
123
+ - Terminal WS: `/ws/project/:projectName/terminal/:id`
124
+ - Parse projectName from URL, pass to ws.data
125
+ - Remove `?project=` query param
126
+ - Chat WS: `/ws/project/:projectName/chat/:sessionId`
127
+ - Parse projectName from URL, pass to ws.data
128
+
129
+ ### 7. Update daemon `Bun.serve()` block (bottom of index.ts)
130
+ - Same WS path changes as foreground server
131
+
132
+ ## Todo List
133
+ - [x] Create `src/server/routes/project-scoped.ts`
134
+ - [x] Update `src/server/index.ts` mounting
135
+ - [x] Refactor `git.ts` routes (17 endpoints)
136
+ - [x] Refactor `files.ts` routes (8 endpoints)
137
+ - [x] Refactor `chat.ts` routes (4 endpoints)
138
+ - [x] Update WS upgrade paths (foreground + daemon)
139
+ - [x] Verify compile with `bun build`
140
+
141
+ ## Success Criteria
142
+ - All routes respond at new paths
143
+ - No `resolveProjectPath` calls in individual route files (only in middleware)
144
+ - Git POST routes no longer require `project` in body
145
+ - WS connections work at new paths
@@ -0,0 +1,107 @@
1
+ ---
2
+ phase: 2
3
+ title: "Frontend API client + calls migration"
4
+ status: pending
5
+ effort: 2h
6
+ depends_on: [1]
7
+ ---
8
+
9
+ # Phase 2: Frontend API Client + Calls Migration
10
+
11
+ ## Context
12
+ - [plan.md](./plan.md)
13
+ - [phase-01](./phase-01-backend-project-router.md)
14
+ - `src/web/lib/api-client.ts`
15
+ - All frontend components calling `/api/...`
16
+
17
+ ## Overview
18
+ Add project-scoped URL helper to ApiClient. Update all frontend API calls to use `/api/project/:projectName/...` pattern. Remove `project` from POST bodies for git operations.
19
+
20
+ ## Key Insight
21
+ Most components already have access to `projectName` via `useProjectStore` or props. The migration is mechanical: prefix every API path with `/api/project/${projectName}`.
22
+
23
+ ## Related Code Files
24
+
25
+ ### Files to modify
26
+ - `src/web/lib/api-client.ts` -- add `projectUrl()` helper
27
+ - `src/web/components/chat/chat-tab.tsx` -- session creation URL
28
+ - `src/web/components/chat/session-picker.tsx` -- session list/delete URLs
29
+ - `src/web/hooks/use-chat.ts` -- message history URL
30
+ - `src/web/components/git/git-graph.tsx` -- all git API calls (~12 calls)
31
+ - `src/web/components/git/git-status-panel.tsx` -- status, stage, unstage, commit, push, pull
32
+ - `src/web/stores/file-store.ts` -- file tree URL
33
+ - `src/web/components/editor/code-editor.tsx` -- read/write URLs
34
+ - `src/web/components/explorer/file-actions.tsx` -- create/rename/delete URLs
35
+ - `src/web/components/editor/diff-viewer.tsx` -- compare/diff URLs
36
+
37
+ ## Implementation Steps
38
+
39
+ ### 1. Add helper to `api-client.ts`
40
+ ```ts
41
+ /** Build project-scoped API path prefix */
42
+ export function projectUrl(projectName: string): string {
43
+ return `/api/project/${encodeURIComponent(projectName)}`;
44
+ }
45
+ ```
46
+
47
+ ### 2. Update git-graph.tsx
48
+ Before:
49
+ ```ts
50
+ `/api/git/graph/${encodeURIComponent(projectName)}?max=200`
51
+ gitAction("/api/git/checkout", { project: projectName, ref });
52
+ ```
53
+ After:
54
+ ```ts
55
+ `${projectUrl(projectName)}/git/graph?max=200`
56
+ gitAction(`${projectUrl(projectName)}/git/checkout`, { ref });
57
+ ```
58
+ - Remove `project` field from all `gitAction` call bodies
59
+ - Update all GET URLs: `/api/git/status/:project` -> `${projectUrl(name)}/git/status`
60
+
61
+ ### 3. Update git-status-panel.tsx
62
+ Same pattern:
63
+ - GET `/api/git/status/${name}` -> `${projectUrl(name)}/git/status`
64
+ - POST bodies: remove `project: projectName` field from stage/unstage/commit/push/pull
65
+
66
+ ### 4. Update file-related components
67
+ - `file-store.ts`: `/api/files/tree/${name}` -> `${projectUrl(name)}/files/tree`
68
+ - `code-editor.tsx`: `/api/files/read/${name}?path=` -> `${projectUrl(name)}/files/read?path=`
69
+ - `file-actions.tsx`: same pattern for create/rename/delete
70
+ - `diff-viewer.tsx`: same pattern for compare/file-diff/diff
71
+
72
+ ### 5. Update chat components
73
+ - `chat-tab.tsx`: `/api/chat/sessions` -> `${projectUrl(name)}/chat/sessions`
74
+ - Remove `projectName` from POST body
75
+ - `session-picker.tsx`: same prefix, remove `?dir=` query
76
+ - `use-chat.ts`: `/api/chat/sessions/:id/messages` -> `${projectUrl(name)}/chat/sessions/:id/messages`
77
+ - Need to pass projectName into `useChat` hook (add param)
78
+
79
+ ### 6. Update use-chat.ts signature
80
+ ```ts
81
+ export function useChat(
82
+ sessionId: string | null,
83
+ providerId: string,
84
+ projectName: string, // NEW
85
+ ): UseChatReturn
86
+ ```
87
+ - History fetch: `${projectUrl(projectName)}/chat/sessions/${sessionId}/messages?providerId=${providerId}`
88
+ - WS URL change handled in Phase 4
89
+
90
+ ## Todo List
91
+ - [ ] Add `projectUrl()` to api-client.ts
92
+ - [ ] Update git-graph.tsx (~15 call sites)
93
+ - [ ] Update git-status-panel.tsx (~6 call sites)
94
+ - [ ] Update file-store.ts (1 call)
95
+ - [ ] Update code-editor.tsx (2 calls)
96
+ - [ ] Update file-actions.tsx (3 calls)
97
+ - [ ] Update diff-viewer.tsx (4 calls)
98
+ - [ ] Update chat-tab.tsx (1 call)
99
+ - [ ] Update session-picker.tsx (2 calls)
100
+ - [ ] Update use-chat.ts (1 call + signature)
101
+ - [ ] Verify compile
102
+
103
+ ## Success Criteria
104
+ - All API calls use `/api/project/:projectName/...` prefix
105
+ - No `project` field in git POST bodies
106
+ - No `dir` query param in chat session list
107
+ - TypeScript compiles clean
@@ -0,0 +1,100 @@
1
+ ---
2
+ phase: 3
3
+ title: "Per-project tab storage"
4
+ status: pending
5
+ effort: 1.5h
6
+ depends_on: []
7
+ ---
8
+
9
+ # Phase 3: Per-Project Tab Storage
10
+
11
+ ## Context
12
+ - [plan.md](./plan.md)
13
+ - `src/web/stores/tab-store.ts` -- current global tab store
14
+ - `src/web/stores/project-store.ts` -- project selection
15
+ - `src/web/app.tsx` -- default tab opening logic
16
+
17
+ ## Overview
18
+ Make tabs project-scoped: each project gets its own tab set persisted under `ppm-tabs-{projectName}`. When user switches project, the tab bar swaps to that project's tabs.
19
+
20
+ ## Key Design Decision
21
+ **Approach: Dynamic storage key** -- Zustand `persist` middleware does not natively support dynamic keys. Two options:
22
+
23
+ **Option A (chosen): Manual localStorage swap.** On project change, serialize current tabs to `ppm-tabs-{oldProject}`, load from `ppm-tabs-{newProject}`, hydrate store. Simple, no lib changes.
24
+
25
+ **Option B (rejected): Multiple store instances.** One store per project. Over-engineered for this use case.
26
+
27
+ ## Related Code Files
28
+
29
+ ### Files to modify
30
+ - `src/web/stores/tab-store.ts` -- add project-aware persist, swap logic
31
+ - `src/web/app.tsx` -- pass project context on default tab, react to project switch
32
+
33
+ ## Implementation Steps
34
+
35
+ ### 1. Refactor tab-store.ts
36
+
37
+ Remove Zustand `persist` middleware. Replace with manual localStorage read/write:
38
+
39
+ ```ts
40
+ const STORAGE_PREFIX = "ppm-tabs-";
41
+
42
+ function storageKey(projectName: string): string {
43
+ return `${STORAGE_PREFIX}${projectName}`;
44
+ }
45
+
46
+ function loadTabs(projectName: string): { tabs: Tab[]; activeTabId: string | null } {
47
+ try {
48
+ const raw = localStorage.getItem(storageKey(projectName));
49
+ if (raw) return JSON.parse(raw);
50
+ } catch { /* ignore */ }
51
+ return { tabs: [], activeTabId: null };
52
+ }
53
+
54
+ function saveTabs(projectName: string, state: { tabs: Tab[]; activeTabId: string | null }) {
55
+ localStorage.setItem(storageKey(projectName), JSON.stringify(state));
56
+ }
57
+ ```
58
+
59
+ Add to store:
60
+ ```ts
61
+ interface TabStore {
62
+ // existing...
63
+ currentProject: string | null;
64
+ switchProject: (projectName: string) => void;
65
+ }
66
+ ```
67
+
68
+ `switchProject(newProject)`:
69
+ 1. If `currentProject` exists, save current tabs to localStorage under old key
70
+ 2. Load tabs from new key
71
+ 3. Set `currentProject = newProject`, hydrate tabs
72
+ 4. If no tabs loaded, open default "Projects" tab
73
+
74
+ ### 2. Auto-save on tab mutations
75
+ After every `openTab`, `closeTab`, `setActiveTab`, `updateTab` -- call `saveTabs(currentProject, { tabs, activeTabId })`. Use Zustand `subscribe` to batch this.
76
+
77
+ ### 3. Update app.tsx
78
+ - On project change (when `activeProject` changes), call `useTabStore.getState().switchProject(activeProject.name)`
79
+ - Remove the "open default tab if none" effect -- let `switchProject` handle defaults
80
+
81
+ ### 4. Clean up old storage
82
+ - Remove old `ppm-tabs` key migration: not needed on v2-fresh-start branch
83
+
84
+ ### 5. Fix nextId collision
85
+ Current `nextId` is a module-level counter. On project switch, tabs from different projects may have overlapping IDs. Fix: derive nextId from loaded tabs on every switch.
86
+
87
+ ## Todo List
88
+ - [ ] Remove `persist` middleware from tab-store
89
+ - [ ] Add manual localStorage read/write functions
90
+ - [ ] Add `currentProject` and `switchProject` to store
91
+ - [ ] Add `subscribe` auto-save
92
+ - [ ] Update `app.tsx` to call `switchProject` on project change
93
+ - [ ] Fix nextId derivation on hydration
94
+ - [ ] Test: switch project -> tabs change, switch back -> original tabs restored
95
+
96
+ ## Success Criteria
97
+ - Each project has independent tab state in localStorage
98
+ - Switching project instantly swaps visible tabs
99
+ - Closing all tabs in project A does not affect project B
100
+ - Default "Projects" tab opens if project has no saved tabs
@@ -0,0 +1,66 @@
1
+ ---
2
+ phase: 4
3
+ title: "WebSocket URL migration"
4
+ status: pending
5
+ effort: 0.5h
6
+ depends_on: [1]
7
+ ---
8
+
9
+ # Phase 4: WebSocket URL Migration
10
+
11
+ ## Context
12
+ - [plan.md](./plan.md)
13
+ - [phase-01](./phase-01-backend-project-router.md) -- backend WS path changes
14
+ - `src/web/hooks/use-terminal.ts` -- terminal WS connection
15
+ - `src/web/hooks/use-chat.ts` -- chat WS connection (via use-websocket)
16
+
17
+ ## Overview
18
+ Update frontend WS URLs to match new `/ws/project/:projectName/...` paths.
19
+
20
+ ## Related Code Files
21
+
22
+ ### Files to modify
23
+ - `src/web/hooks/use-terminal.ts` -- WS URL construction
24
+ - `src/web/hooks/use-chat.ts` -- WS URL passed to `useWebSocket`
25
+
26
+ ## Implementation Steps
27
+
28
+ ### 1. Update use-terminal.ts
29
+ Before:
30
+ ```ts
31
+ const url = `${protocol}//${host}/ws/terminal/${sid}${projectParam ? `?project=${encodeURIComponent(projectParam)}` : ""}`;
32
+ ```
33
+ After:
34
+ ```ts
35
+ const url = `${protocol}//${host}/ws/project/${encodeURIComponent(options.projectName!)}/terminal/${sid}`;
36
+ ```
37
+ - Remove `?project=` query param -- project is in path
38
+ - `projectName` is now required (not optional) in `UseTerminalOptions`
39
+
40
+ ### 2. Update use-chat.ts
41
+ Before:
42
+ ```ts
43
+ url: sessionId ? `/ws/chat/${sessionId}` : "",
44
+ ```
45
+ After:
46
+ ```ts
47
+ url: sessionId && projectName ? `/ws/project/${encodeURIComponent(projectName)}/chat/${sessionId}` : "",
48
+ ```
49
+ - `projectName` param was added in Phase 2
50
+
51
+ ### 3. Ensure backend parses new WS paths
52
+ Already handled in Phase 1, step 6. Verify:
53
+ - `/ws/project/:projectName/terminal/:id` extracts both projectName and id
54
+ - `/ws/project/:projectName/chat/:sessionId` extracts both
55
+
56
+ ## Todo List
57
+ - [ ] Update `use-terminal.ts` WS URL
58
+ - [ ] Make `projectName` required in `UseTerminalOptions`
59
+ - [ ] Update `use-chat.ts` WS URL
60
+ - [ ] Verify both WS connections work end-to-end
61
+
62
+ ## Success Criteria
63
+ - Terminal WS connects at `/ws/project/:projectName/terminal/:id`
64
+ - Chat WS connects at `/ws/project/:projectName/chat/:sessionId`
65
+ - No query params for project identification
66
+ - Both foreground and daemon server blocks handle new paths