@hienlh/ppm 0.8.76 → 0.8.77

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 (33) hide show
  1. package/CHANGELOG.md +14 -0
  2. package/dist/web/assets/{browser-tab-D5GfU4Ja.js → browser-tab-DQJLMN11.js} +1 -1
  3. package/dist/web/assets/{chat-tab-BJeNwwUM.js → chat-tab-xp4ZqCGD.js} +4 -4
  4. package/dist/web/assets/{code-editor-CTjgdXh2.js → code-editor-De6Xs-Kq.js} +1 -1
  5. package/dist/web/assets/{database-viewer-QzEuetE6.js → database-viewer-CExMyEmq.js} +1 -1
  6. package/dist/web/assets/{diff-viewer-CvZ06EAH.js → diff-viewer-C7EECdwr.js} +1 -1
  7. package/dist/web/assets/git-graph-DqFYj10H.js +1 -0
  8. package/dist/web/assets/index-CiuAeXR3.js +37 -0
  9. package/dist/web/assets/keybindings-store-DYgvd7L0.js +1 -0
  10. package/dist/web/assets/{markdown-renderer-BVxlq4zO.js → markdown-renderer-CnBEa3kk.js} +1 -1
  11. package/dist/web/assets/{postgres-viewer-DP0FOQOa.js → postgres-viewer-DvMnxJ7g.js} +1 -1
  12. package/dist/web/assets/{settings-tab-CcmhnYpw.js → settings-tab-C0ltpIWq.js} +1 -1
  13. package/dist/web/assets/{sqlite-viewer-4a4hHLZk.js → sqlite-viewer-DR9KKOhW.js} +1 -1
  14. package/dist/web/assets/tab-store-BJw7OCmy.js +1 -0
  15. package/dist/web/assets/{terminal-tab-CKsBIgnq.js → terminal-tab-B__sTLzq.js} +1 -1
  16. package/dist/web/assets/{use-monaco-theme-BwIb9BHq.js → use-monaco-theme-BZiSwNRE.js} +1 -1
  17. package/dist/web/index.html +2 -2
  18. package/dist/web/sw.js +1 -1
  19. package/docs/code-standards.md +126 -0
  20. package/docs/project-changelog.md +36 -1
  21. package/docs/system-architecture.md +35 -3
  22. package/package.json +1 -1
  23. package/src/server/routes/project-scoped.ts +2 -0
  24. package/src/server/routes/workspace.ts +35 -0
  25. package/src/services/db.service.ts +37 -1
  26. package/src/web/app.tsx +37 -35
  27. package/src/web/hooks/use-url-sync.ts +173 -21
  28. package/src/web/stores/panel-store.ts +63 -9
  29. package/src/web/stores/panel-utils.ts +145 -3
  30. package/dist/web/assets/git-graph-BQqdvSjX.js +0 -1
  31. package/dist/web/assets/index-5a-tMkk5.js +0 -37
  32. package/dist/web/assets/keybindings-store-zY8zbJ2c.js +0 -1
  33. package/dist/web/assets/tab-store-BCfMgMKM.js +0 -1
@@ -1,5 +1,4 @@
1
1
  import { create } from "zustand";
2
- import { randomId } from "@/lib/utils";
3
2
  import type { Tab, TabType } from "./tab-store";
4
3
  import {
5
4
  type Panel,
@@ -13,18 +12,15 @@ import {
13
12
  MAX_ROWS,
14
13
  savePanelLayout,
15
14
  loadPanelLayout,
15
+ deriveTabId,
16
16
  } from "./panel-utils";
17
17
 
18
18
  /** Tab types that can only have 1 instance per project */
19
- const SINGLETON_TYPES = new Set<TabType>(["git-graph"]);
19
+ const SINGLETON_TYPES = new Set<TabType>(["git-graph", "settings"]);
20
20
 
21
21
  /** Tab types removed in a prior version — filter them out when loading persisted state */
22
22
  const OBSOLETE_TAB_TYPES = new Set(["projects", "git-status"]);
23
23
 
24
- function generateTabId(): string {
25
- return `tab-${randomId()}`;
26
- }
27
-
28
24
  function pushHistory(history: string[], id: string): string[] {
29
25
  const filtered = history.filter((h) => h !== id);
30
26
  filtered.push(id);
@@ -47,6 +43,7 @@ export interface PanelStore {
47
43
 
48
44
  // Project lifecycle
49
45
  switchProject: (projectName: string) => void;
46
+ reloadProject: (projectName: string) => void;
50
47
 
51
48
  // Panel focus
52
49
  setFocusedPanel: (panelId: string) => void;
@@ -188,6 +185,25 @@ export const usePanelStore = create<PanelStore>()((set, get) => {
188
185
  }
189
186
  },
190
187
 
188
+ reloadProject: (projectName) => {
189
+ const { projectGrids, projectFocused, panels } = get();
190
+ // Clear in-memory cache so switchProject re-reads from localStorage
191
+ const newGrids = { ...projectGrids };
192
+ const newFocused = { ...projectFocused };
193
+ delete newGrids[projectName];
194
+ delete newFocused[projectName];
195
+
196
+ // Remove old panels belonging to this project from flat map
197
+ const oldGrid = projectGrids[projectName];
198
+ const oldPanelIds = oldGrid ? new Set(oldGrid.flat()) : new Set<string>();
199
+ const cleanedPanels = { ...panels };
200
+ for (const id of oldPanelIds) delete cleanedPanels[id];
201
+
202
+ set({ projectGrids: newGrids, projectFocused: newFocused, panels: cleanedPanels, currentProject: null });
203
+ // Re-trigger full load from localStorage
204
+ get().switchProject(projectName);
205
+ },
206
+
191
207
  setFocusedPanel: (panelId) => {
192
208
  if (get().panels[panelId]) set({ focusedPanelId: panelId });
193
209
  },
@@ -197,10 +213,25 @@ export const usePanelStore = create<PanelStore>()((set, get) => {
197
213
  const panel = get().panels[pid];
198
214
  if (!panel) return "";
199
215
 
200
- // Singleton check across ALL panels
216
+ // Terminal: compute next available index if not provided
217
+ if (tabDef.type === "terminal" && !tabDef.metadata?.terminalIndex) {
218
+ const allTabs = Object.values(get().panels).flatMap((p) => p.tabs);
219
+ const terminalNums = allTabs
220
+ .filter((t) => t.type === "terminal")
221
+ .map((t) => {
222
+ const match = t.id.match(/^terminal:(\d+)/);
223
+ return match ? parseInt(match[1]!, 10) : 0;
224
+ });
225
+ const nextIndex = terminalNums.length > 0 ? Math.max(...terminalNums) + 1 : 1;
226
+ tabDef = { ...tabDef, metadata: { ...tabDef.metadata, terminalIndex: nextIndex } };
227
+ }
228
+
229
+ const baseId = deriveTabId(tabDef.type, tabDef.metadata);
230
+
231
+ // Singleton check — focus existing across ALL panels
201
232
  if (SINGLETON_TYPES.has(tabDef.type)) {
202
233
  for (const p of Object.values(get().panels)) {
203
- const existing = p.tabs.find((t) => t.type === tabDef.type && t.projectId === tabDef.projectId);
234
+ const existing = p.tabs.find((t) => t.id === baseId);
204
235
  if (existing) {
205
236
  set((s) => ({
206
237
  focusedPanelId: p.id,
@@ -219,7 +250,30 @@ export const usePanelStore = create<PanelStore>()((set, get) => {
219
250
  }
220
251
  }
221
252
 
222
- const id = generateTabId();
253
+ // Non-singleton: dedup within SAME panel only
254
+ const currentPanel = get().panels[pid]!;
255
+ const existingInPanel = currentPanel.tabs.find((t) => t.id === baseId);
256
+ if (existingInPanel) {
257
+ set((s) => ({
258
+ panels: {
259
+ ...s.panels,
260
+ [pid]: {
261
+ ...currentPanel,
262
+ activeTabId: existingInPanel.id,
263
+ tabHistory: pushHistory(currentPanel.tabHistory, existingInPanel.id),
264
+ },
265
+ },
266
+ }));
267
+ persist();
268
+ return existingInPanel.id;
269
+ }
270
+
271
+ // Check if same base ID exists in OTHER panels (split case)
272
+ const existsElsewhere = Object.values(get().panels).some(
273
+ (p) => p.id !== pid && p.tabs.some((t) => t.id === baseId),
274
+ );
275
+ const id = existsElsewhere ? `${baseId}@${pid}` : baseId;
276
+
223
277
  const tab: Tab = { ...tabDef, id };
224
278
  set((s) => {
225
279
  const p = s.panels[pid]!;
@@ -1,5 +1,5 @@
1
1
  import { randomId } from "@/lib/utils";
2
- import type { Tab } from "./tab-store";
2
+ import type { Tab, TabType } from "./tab-store";
3
3
 
4
4
  // ---------------------------------------------------------------------------
5
5
  // Panel types
@@ -69,6 +69,62 @@ export function findPanelPosition(grid: string[][], panelId: string): { row: num
69
69
  return null;
70
70
  }
71
71
 
72
+ // ---------------------------------------------------------------------------
73
+ // Deterministic tab IDs
74
+ // ---------------------------------------------------------------------------
75
+
76
+ /** Derive deterministic tab ID from type + metadata */
77
+ export function deriveTabId(type: TabType, metadata?: Record<string, unknown>): string {
78
+ switch (type) {
79
+ case "editor":
80
+ return `editor:${metadata?.filePath ?? "untitled"}`;
81
+ case "chat": {
82
+ const provider = metadata?.providerId ?? "default";
83
+ return `chat:${provider}/${metadata?.sessionId ?? randomId()}`;
84
+ }
85
+ case "terminal":
86
+ return `terminal:${metadata?.terminalIndex ?? 1}`;
87
+ case "database":
88
+ return `database:${metadata?.connectionId ?? "default"}:${metadata?.tableName ?? ""}`;
89
+ case "sqlite":
90
+ return `sqlite:${metadata?.filePath ?? "default"}`;
91
+ case "postgres":
92
+ return `postgres:${metadata?.connectionId ?? "default"}:${metadata?.tableName ?? ""}`;
93
+ case "git-graph":
94
+ return "git-graph";
95
+ case "git-diff":
96
+ return `git-diff:${metadata?.filePath ?? "unknown"}`;
97
+ case "settings":
98
+ return "settings";
99
+ case "browser":
100
+ return `browser:${metadata?.url ?? "blank"}`;
101
+ default:
102
+ return `${type}:${randomId()}`;
103
+ }
104
+ }
105
+
106
+ /** Migrate old random tab IDs to deterministic IDs */
107
+ export function migrateTabIds(layout: PanelLayout): PanelLayout {
108
+ const migrated = { ...layout, panels: { ...layout.panels } };
109
+ for (const [panelId, panel] of Object.entries(migrated.panels)) {
110
+ const newTabs = panel.tabs.map((tab) => {
111
+ if (tab.id.startsWith("tab-")) {
112
+ const newId = deriveTabId(tab.type, tab.metadata);
113
+ return { ...tab, id: newId };
114
+ }
115
+ return tab;
116
+ });
117
+ const idMap = new Map<string, string>();
118
+ panel.tabs.forEach((old, i) => {
119
+ if (old.id !== newTabs[i]!.id) idMap.set(old.id, newTabs[i]!.id);
120
+ });
121
+ const newActive = idMap.get(panel.activeTabId ?? "") ?? panel.activeTabId;
122
+ const newHistory = panel.tabHistory.map((h) => idMap.get(h) ?? h);
123
+ migrated.panels[panelId] = { ...panel, tabs: newTabs, activeTabId: newActive, tabHistory: newHistory };
124
+ }
125
+ return migrated;
126
+ }
127
+
72
128
  // ---------------------------------------------------------------------------
73
129
  // Persistence
74
130
  // ---------------------------------------------------------------------------
@@ -81,20 +137,106 @@ function storageKey(projectName: string): string {
81
137
 
82
138
  export function savePanelLayout(projectName: string, layout: PanelLayout): void {
83
139
  try {
84
- localStorage.setItem(storageKey(projectName), JSON.stringify(layout));
140
+ const withTimestamp = { ...layout, updatedAt: new Date().toISOString() };
141
+ localStorage.setItem(storageKey(projectName), JSON.stringify(withTimestamp));
142
+ // Debounced server sync
143
+ syncWorkspaceToServer(projectName, layout);
85
144
  } catch { /* ignore */ }
86
145
  }
87
146
 
88
147
  export function loadPanelLayout(projectName: string): PanelLayout | null {
89
148
  try {
90
149
  const raw = localStorage.getItem(storageKey(projectName));
91
- if (raw) return JSON.parse(raw) as PanelLayout;
150
+ if (raw) {
151
+ const layout = JSON.parse(raw) as PanelLayout;
152
+ return migrateTabIds(layout);
153
+ }
92
154
  } catch { /* ignore */ }
93
155
 
94
156
  // Migrate from old tab-store format
95
157
  return migrateOldTabStore(projectName);
96
158
  }
97
159
 
160
+ // ---------------------------------------------------------------------------
161
+ // Server sync
162
+ // ---------------------------------------------------------------------------
163
+
164
+ export interface PanelLayoutWithTimestamp extends PanelLayout {
165
+ updatedAt: string;
166
+ }
167
+
168
+ /** Fetch workspace from server */
169
+ export async function fetchWorkspaceFromServer(
170
+ projectName: string,
171
+ ): Promise<PanelLayoutWithTimestamp | null> {
172
+ try {
173
+ const headers: Record<string, string> = {};
174
+ const token = localStorage.getItem("ppm-auth-token");
175
+ if (token) headers["Authorization"] = `Bearer ${token}`;
176
+
177
+ const res = await fetch(`/api/project/${encodeURIComponent(projectName)}/workspace`, { headers });
178
+ if (!res.ok) return null;
179
+ const json = await res.json();
180
+ if (!json.ok || !json.data) return null;
181
+ return { ...json.data.layout, updatedAt: json.data.updatedAt };
182
+ } catch {
183
+ return null;
184
+ }
185
+ }
186
+
187
+ /** Save workspace to server (debounced per project) */
188
+ const syncTimers = new Map<string, ReturnType<typeof setTimeout>>();
189
+
190
+ function syncWorkspaceToServer(projectName: string, layout: PanelLayout): void {
191
+ const existing = syncTimers.get(projectName);
192
+ if (existing) clearTimeout(existing);
193
+
194
+ syncTimers.set(projectName, setTimeout(async () => {
195
+ syncTimers.delete(projectName);
196
+ try {
197
+ const headers: Record<string, string> = { "Content-Type": "application/json" };
198
+ const token = localStorage.getItem("ppm-auth-token");
199
+ if (token) headers["Authorization"] = `Bearer ${token}`;
200
+
201
+ const res = await fetch(`/api/project/${encodeURIComponent(projectName)}/workspace`, {
202
+ method: "PUT",
203
+ headers,
204
+ body: JSON.stringify({ layout }),
205
+ });
206
+ if (res.ok) {
207
+ const json = await res.json();
208
+ if (json.data?.updatedAt) {
209
+ const key = `${STORAGE_PREFIX}${projectName}`;
210
+ const raw = localStorage.getItem(key);
211
+ if (raw) {
212
+ const local = JSON.parse(raw);
213
+ local.updatedAt = json.data.updatedAt;
214
+ localStorage.setItem(key, JSON.stringify(local));
215
+ }
216
+ }
217
+ }
218
+ } catch { /* silent fail — localStorage is still the cache */ }
219
+ }, 1500));
220
+ }
221
+
222
+ /** Compare local vs server timestamps, return newer layout */
223
+ export function resolveWorkspaceConflict(
224
+ local: PanelLayoutWithTimestamp | null,
225
+ server: PanelLayoutWithTimestamp | null,
226
+ ): PanelLayoutWithTimestamp | null {
227
+ if (!local && !server) return null;
228
+ if (!local) return server!;
229
+ if (!server) return local;
230
+
231
+ const localTime = new Date(local.updatedAt).getTime();
232
+ const serverTime = new Date(server.updatedAt).getTime();
233
+ return serverTime >= localTime ? server : local;
234
+ }
235
+
236
+ // ---------------------------------------------------------------------------
237
+ // Old format migration
238
+ // ---------------------------------------------------------------------------
239
+
98
240
  function migrateOldTabStore(projectName: string): PanelLayout | null {
99
241
  try {
100
242
  const raw = localStorage.getItem(`${OLD_STORAGE_PREFIX}${projectName}`);
@@ -1 +0,0 @@
1
- import{o as e}from"./chunk-CFjPhJqf.js";import{t}from"./react-nm2Ru1Pt.js";import"./react-dom-Bpkvzu3U.js";import{n,t as r}from"./jsx-runtime-BRW_vwa9.js";import{a as i,i as a,n as o,r as s,t as c}from"./input-BglMT33g.js";import{o as l,t as u}from"./tab-store-BCfMgMKM.js";import{t as d}from"./tag-CCtdV063.js";import"./dist-lF8CoYII.js";import{t as f}from"./utils-BNytJOb1.js";import{i as p,t as m}from"./api-client-BfBM3I7n.js";import{B as h,C as g,F as _,L as v,Q as y,S as b,T as x,V as S,X as C,_t as w,b as T,ct as E,g as D,h as O,ot as k,v as A,w as j,x as M,y as N,z as P}from"./index-5a-tMkk5.js";var F=n(`cherry`,[[`path`,{d:`M2 17a5 5 0 0 0 10 0c0-2.76-2.5-5-5-3-2.5-2-5 .24-5 3Z`,key:`cvxqlc`}],[`path`,{d:`M12 17a5 5 0 0 0 10 0c0-2.76-2.5-5-5-3-2.5-2-5 .24-5 3Z`,key:`1ostrc`}],[`path`,{d:`M7 14c3.22-2.91 4.29-8.75 5-12 1.66 2.38 4.94 9 5 12`,key:`hqx58h`}],[`path`,{d:`M22 9c-4.29 0-7.14-2.33-10-7 5.71 0 10 4.67 10 7Z`,key:`eykp1o`}]]),ee=n(`git-merge`,[[`circle`,{cx:`18`,cy:`18`,r:`3`,key:`1xkwt0`}],[`circle`,{cx:`6`,cy:`6`,r:`3`,key:`1lh9wr`}],[`path`,{d:`M6 21V9a9 9 0 0 0 9 9`,key:`7kw0sc`}]]),I=e(t(),1),L=[`#0085d9`,`#d73a49`,`#6f42c1`,`#2cbe4e`,`#e36209`,`#005cc5`,`#b31d28`,`#5a32a3`,`#22863a`,`#cb2431`];function te(e){let t=new Map;if(!e)return t;let n=new Set;for(let t of e.branches)if(!t.remote)for(let e of t.remotes)n.add(`remotes/${e}/${t.name}`);for(let r of e.branches)if(r.remote){if(n.has(r.name))continue;let e=t.get(r.commitHash)??[],i=r.name.replace(/^remotes\//,``);e.push({name:i,type:`branch`,remotes:[],current:!1}),t.set(r.commitHash,e)}else{let e=t.get(r.commitHash)??[];e.push({name:r.name,type:`branch`,remotes:r.remotes,current:r.current}),t.set(r.commitHash,e)}for(let n of e.commits)for(let e of n.refs)if(e.startsWith(`tag: `)){let r=e.replace(`tag: `,``),i=t.get(n.hash)??[];i.push({name:r,type:`tag`,remotes:[],current:!1}),t.set(n.hash,i)}return t}function ne(e){let t=new Map;if(!e)return{laneMap:t,maxLane:0,unloadedParentLanes:new Map};let n=0,r=0,i=new Map,a=new Set(e.commits.map(e=>e.hash)),o=[],s=()=>o.length>0?(o.sort((e,t)=>e-t),o.shift()):n++;for(let n of e.commits){let e=i.get(n.hash);e===void 0&&(e=s()),t.set(n.hash,e),e>r&&(r=e),i.delete(n.hash);let a=!1;for(let t=0;t<n.parents.length;t++){let o=n.parents[t];if(!i.has(o))if(t===0)i.set(o,e),a=!0;else{let e=s();i.set(o,e),e>r&&(r=e)}}a||o.push(e)}let c=new Map;for(let[e,t]of i)a.has(e)||c.set(e,t);return{laneMap:t,maxLane:r,unloadedParentLanes:c}}function re(e,t,n,r){if(!e)return[];let i=[],a=new Set(e.commits.map(e=>e.hash));for(let o=0;o<e.commits.length;o++){let s=e.commits[o],c=t.get(s.hash)??0,l=L[c%L.length];for(let u of s.parents){let d=e.commits.findIndex(e=>e.hash===u);if(d>=0){let e=t.get(u)??0,n=L[e%L.length],r=c*16+16/2,a=o*24+24/2,f=e*16+16/2,p=d*24+24/2,m,h=s.parents.indexOf(u)>0;if(r===f)m=`M ${r} ${a} L ${f} ${p}`;else if(h){let e=a+24;m=`M ${r} ${a} C ${r} ${e} ${f} ${a} ${f} ${e} L ${f} ${p}`}else{let e=p-24;m=`M ${r} ${a} L ${r} ${e} C ${r} ${p} ${f} ${e} ${f} ${p}`}let g=s.parents.indexOf(u)===0?l:n;i.push({d:m,color:g})}else if(!a.has(u)){let e=n.get(u)??c,t=L[e%L.length],a=c*16+16/2,s=o*24+24/2,d=e*16+16/2;if(a===d)i.push({d:`M ${a} ${s} L ${a} ${r}`,color:l});else{let e=s+24,n=`M ${a} ${s} C ${a} ${e} ${d} ${s} ${d} ${e} L ${d} ${r}`;i.push({d:n,color:t})}}}}return i}function R(e){let t=new Date(e),n=new Date().getTime()-t.getTime(),r=Math.floor(n/6e4);if(r<1)return`just now`;if(r<60)return`${r}m ago`;let i=Math.floor(r/60);if(i<24)return`${i}h ago`;let a=Math.floor(i/24);if(a<30)return`${a}d ago`;let o=Math.floor(a/30);return o<12?`${o}mo ago`:`${Math.floor(o/12)}y ago`}var z=200;function B(e){let[t,n]=(0,I.useState)(null),[r,i]=(0,I.useState)(!0),[a,o]=(0,I.useState)(!1),[s,c]=(0,I.useState)(!0),[l,d]=(0,I.useState)(null),[f,h]=(0,I.useState)(!1),[g,_]=(0,I.useState)(null),[v,y]=(0,I.useState)([]),[b,x]=(0,I.useState)(!1),[S,C]=(0,I.useState)(`__all__`),[w,T]=(0,I.useState)(``),[E,D]=(0,I.useState)(!1),{openTab:O}=u(),k=(0,I.useRef)(0),A=(0,I.useCallback)(async()=>{if(e)try{i(!0);let t=Math.max(z,k.current),r=await m.get(`${p(e)}/git/graph?max=${t}`);n(r),k.current=r.commits.length,c(r.commits.length>=t),d(null)}catch(e){d(e instanceof Error?e.message:`Failed to fetch graph`)}finally{i(!1)}},[e]),j=(0,I.useCallback)(async()=>{if(!(!e||a||!s))try{o(!0);let t=k.current,r=await m.get(`${p(e)}/git/graph?max=${z}&skip=${t}`);if(r.commits.length===0){c(!1);return}n(e=>{if(!e)return r;let t=new Set(e.commits.map(e=>e.hash)),n=r.commits.filter(e=>!t.has(e.hash)),i=new Set(e.branches.map(e=>e.name)),a=r.branches.filter(e=>!i.has(e.name));return{commits:[...e.commits,...n],branches:[...e.branches,...a],head:e.head}}),k.current=t+r.commits.length,c(r.commits.length>=z)}catch(e){d(e instanceof Error?e.message:`Failed to load more`)}finally{o(!1)}},[e,a,s]);(0,I.useEffect)(()=>{A();let e=setInterval(A,1e4);return()=>clearInterval(e)},[A]);let M=async(t,n)=>{if(e){h(!0);try{await m.post(`${p(e)}${t}`,n),await A()}catch(e){d(e instanceof Error?e.message:`Action failed`)}finally{h(!1)}}},N=()=>M(`/git/fetch`,{}),P=e=>M(`/git/checkout`,{ref:e}),F=e=>M(`/git/cherry-pick`,{hash:e}),ee=e=>M(`/git/revert`,{hash:e}),L=e=>M(`/git/merge`,{source:e}),R=e=>M(`/git/branch/delete`,{name:e}),B=e=>M(`/git/push`,{branch:e}),V=(e,t)=>M(`/git/tag`,{name:e,hash:t}),H=e=>navigator.clipboard.writeText(e),U=async(e,n)=>{if(t?.branches.some(t=>t.name===e||t.name.endsWith(`/${e}`))){if(!window.confirm(`Branch "${e}" already exists.\nDelete and recreate from this commit?`))return;await M(`/git/branch/delete`,{name:e})}await M(`/git/branch/create`,{name:e,from:n})},W=async t=>{if(e)try{let n=await m.get(`${p(e)}/git/pr-url?branch=${encodeURIComponent(t)}`);n.url&&window.open(n.url,`_blank`)}catch{}},G=async t=>{if(g?.hash===t.hash){_(null);return}_(t),x(!0);try{let n=t.parents[0]??``,r=n?`ref1=${encodeURIComponent(n)}&`:``,i=await m.get(`${p(e)}/git/diff-stat?${r}ref2=${encodeURIComponent(t.hash)}`);y(Array.isArray(i)?i:[])}catch{y([])}finally{x(!1)}},K=t=>O({type:`git-diff`,title:`Diff ${t.abbreviatedHash}`,closable:!0,metadata:{projectName:e,ref1:t.parents[0]??void 0,ref2:t.hash},projectId:e??null}),q=(0,I.useMemo)(()=>te(t),[t]),J=t?.branches.find(e=>e.current),Y=t?.head??``,X=(0,I.useMemo)(()=>{if(!t)return[];let e=t.commits;if(S!==`__all__`){let n=t.branches.find(e=>e.name===S);if(n){let r=new Set,i=[n.commitHash];for(;i.length>0;){let e=i.pop();if(r.has(e))continue;r.add(e);let n=t.commits.find(t=>t.hash===e);n&&i.push(...n.parents)}e=e.filter(e=>r.has(e.hash))}}if(w.trim()){let t=w.toLowerCase();e=e.filter(e=>e.subject.toLowerCase().includes(t)||e.authorName.toLowerCase().includes(t)||e.abbreviatedHash.includes(t)||e.hash.includes(t))}return e},[t,S,w]),Z=(0,I.useMemo)(()=>t?{...t,commits:X}:null,[t,X]),Q=(0,I.useMemo)(()=>ne(Z),[Z]),$=X.length*24+48;return{data:t,loading:r,loadingMore:a,hasMore:s,error:l,acting:f,selectedCommit:g,setSelectedCommit:_,commitFiles:v,loadingDetail:b,branchFilter:S,setBranchFilter:C,searchQuery:w,setSearchQuery:T,showSearch:E,setShowSearch:D,fetchGraph:A,fetchFromRemotes:N,loadMore:j,handleCheckout:P,handleCherryPick:F,handleRevert:ee,handleMerge:L,handleDeleteBranch:R,handlePushBranch:B,handleCreateBranch:U,handleCreateTag:V,handleCreatePr:W,copyHash:H,selectCommit:G,openDiffForCommit:K,commitLabels:q,currentBranch:J,headHash:Y,filteredCommits:X,filteredLanes:Q,svgHeight:$,svgPaths:(0,I.useMemo)(()=>re(Z,Q.laneMap,Q.unloadedParentLanes,$),[Z,Q.laneMap,Q.unloadedParentLanes,$])}}function V(e){let[t,n]=(0,I.useState)(e),r=(0,I.useRef)(e);r.current=t;let i=(0,I.useRef)(!1);return{widths:t,startResize:(e,t)=>{i.current=!0;let a=r.current[e]??80,o=r=>{if(!i.current)return;let o=`touches`in r?r.touches[0].clientX:r.clientX,s=Math.max(40,a+o-t);n(t=>({...t,[e]:s}))},s=()=>{i.current=!1,window.removeEventListener(`mousemove`,o),window.removeEventListener(`mouseup`,s),window.removeEventListener(`touchmove`,o),window.removeEventListener(`touchend`,s)};window.addEventListener(`mousemove`,o),window.addEventListener(`mouseup`,s),window.addEventListener(`touchmove`,o,{passive:!1}),window.addEventListener(`touchend`,s)}}}var H=r();function U({branches:e,branchFilter:t,onBranchFilterChange:n,searchQuery:r,onSearchQueryChange:i,showSearch:s,onToggleSearch:l,onFetch:u,onRefresh:d,onOpenSettings:f,loading:p,acting:m,projectName:g}){let v=e.filter(e=>!e.remote),[y,b]=(0,I.useState)(!1),[x,C]=(0,I.useState)(``),w=(0,I.useRef)(null);(0,I.useEffect)(()=>{if(!y)return;let e=e=>{w.current&&!w.current.contains(e.target)&&(b(!1),C(``))};return document.addEventListener(`mousedown`,e),()=>document.removeEventListener(`mousedown`,e)},[y]);let T=x?v.filter(e=>e.name.toLowerCase().includes(x.toLowerCase())):v,E=t===`__all__`?`Show All`:v.find(e=>e.name===t)?.name??`Show All`;return(0,H.jsx)(`div`,{className:`border-b bg-background`,children:(0,H.jsxs)(`div`,{className:`flex items-center gap-1.5 px-2 py-1.5`,children:[(0,H.jsxs)(`div`,{className:`flex items-center gap-1 text-xs text-muted-foreground shrink-0`,children:[(0,H.jsx)(`span`,{className:`font-semibold`,children:`Repo:`}),(0,H.jsx)(`span`,{className:`font-medium text-foreground truncate max-w-[120px]`,children:g??`—`})]}),(0,H.jsx)(`div`,{className:`w-px h-4 bg-border mx-1`}),(0,H.jsxs)(`div`,{className:`relative shrink-0`,ref:w,children:[(0,H.jsxs)(`button`,{type:`button`,className:`flex items-center gap-1 h-6 px-2 text-xs border rounded-md bg-transparent hover:bg-muted/50`,onClick:()=>{b(e=>!e),C(``)},children:[(0,H.jsx)(`span`,{className:`font-semibold text-muted-foreground`,children:`Branches:`}),(0,H.jsx)(`span`,{className:`max-w-[100px] truncate`,children:E}),(0,H.jsx)(a,{className:`size-3 opacity-50`})]}),y&&(0,H.jsxs)(`div`,{className:`absolute top-full left-0 mt-1 z-50 w-[220px] rounded-md border bg-popover shadow-md`,children:[(0,H.jsx)(`div`,{className:`p-1.5`,children:(0,H.jsx)(c,{className:`h-6 text-xs px-2`,placeholder:`Filter branches...`,value:x,onChange:e=>C(e.target.value),autoFocus:!0})}),(0,H.jsxs)(`div`,{className:`max-h-[200px] overflow-y-auto p-1`,children:[(0,H.jsx)(W,{label:`Show All`,selected:t===`__all__`,onClick:()=>{n(`__all__`),b(!1)}}),T.map(e=>(0,H.jsx)(W,{label:e.name,current:e.current,selected:t===e.name,onClick:()=>{n(e.name),b(!1)}},e.name)),T.length===0&&x&&(0,H.jsx)(`div`,{className:`px-2 py-1.5 text-xs text-muted-foreground`,children:`No branches found`})]})]})]}),(0,H.jsx)(`div`,{className:`flex-1`}),s&&(0,H.jsxs)(`div`,{className:`flex items-center gap-1`,children:[(0,H.jsx)(c,{className:`h-6 text-xs w-[160px] px-2`,placeholder:`Search commits...`,value:r,onChange:e=>i(e.target.value),autoFocus:!0}),(0,H.jsx)(o,{variant:`ghost`,size:`icon-xs`,onClick:l,children:(0,H.jsx)(_,{className:`size-3`})})]}),!s&&(0,H.jsx)(o,{variant:`ghost`,size:`icon-xs`,onClick:l,title:`Find`,children:(0,H.jsx)(h,{className:`size-3.5`})}),(0,H.jsx)(o,{variant:`ghost`,size:`icon-xs`,onClick:f,title:`Settings`,children:(0,H.jsx)(P,{className:`size-3.5`})}),(0,H.jsx)(o,{variant:`ghost`,size:`icon-xs`,onClick:u,disabled:m,title:`Fetch`,children:(0,H.jsx)(k,{className:`size-3.5`})}),(0,H.jsx)(o,{variant:`ghost`,size:`icon-xs`,onClick:d,disabled:m,title:`Refresh`,children:(0,H.jsx)(S,{className:`size-3.5 ${p?`animate-spin`:``}`})})]})})}function W({label:e,selected:t,current:n,onClick:r}){return(0,H.jsxs)(`button`,{type:`button`,className:`flex items-center gap-2 w-full px-2 py-1 text-xs rounded-sm hover:bg-accent hover:text-accent-foreground text-left`,onClick:r,children:[(0,H.jsx)(i,{className:`size-3 shrink-0 ${t?`opacity-100`:`opacity-0`}`}),(0,H.jsx)(`span`,{className:`truncate flex-1`,children:e}),n&&(0,H.jsx)(`span`,{className:`text-[10px] text-muted-foreground italic`,children:`current`})]})}function G({commits:e,laneMap:t,svgPaths:n,width:r,height:i,headHash:a}){return(0,H.jsxs)(`svg`,{width:r,height:i,children:[n.map((e,t)=>(0,H.jsx)(`path`,{d:e.d,stroke:e.color,strokeWidth:2,fill:`none`},t)),e.map((e,n)=>{let r=t.get(e.hash)??0,i=r*16+16/2,o=n*24+24/2,s=L[r%L.length],c=e.hash===a;return(0,H.jsx)(`circle`,{cx:i,cy:o,r:c?5:4,fill:s,stroke:c?`#000`:`none`,strokeWidth:c?2:0},e.hash)})]})}function K({name:e,type:t,remotes:n,isCurrent:r,color:i,currentBranch:a,onCheckout:o,onMerge:s,onPush:c,onCreatePr:u,onDelete:f}){return t===`tag`?(0,H.jsxs)(`span`,{className:`inline-flex items-center gap-0.5 px-1.5 py-0.5 rounded text-[10px] font-medium shrink-0 bg-amber-500/20 text-amber-500 border border-amber-500/30`,children:[(0,H.jsx)(d,{className:`size-2.5`}),e]}):(0,H.jsxs)(M,{children:[(0,H.jsx)(x,{asChild:!0,children:(0,H.jsxs)(`span`,{className:`inline-flex items-center rounded text-[10px] font-medium shrink-0 cursor-context-menu overflow-hidden`,style:{border:r?`1.5px solid ${i}`:`1px solid ${i}50`},children:[(0,H.jsxs)(`span`,{className:`inline-flex items-center gap-0.5 px-1.5 py-0.5`,style:{backgroundColor:r?i:`${i}30`,color:r?`#fff`:i},children:[(0,H.jsx)(y,{className:`size-2.5`}),e]}),n.map(e=>(0,H.jsx)(`span`,{className:`px-1.5 py-0.5 italic opacity-70`,style:{borderLeft:`1px solid ${i}40`,color:i,backgroundColor:`${i}15`},children:e},e))]})}),(0,H.jsxs)(b,{children:[(0,H.jsx)(g,{onClick:()=>o(e),children:`Checkout`}),(0,H.jsxs)(g,{onClick:()=>s(e),disabled:e===a?.name,children:[(0,H.jsx)(ee,{className:`size-3`}),`Merge into current`]}),(0,H.jsx)(j,{}),(0,H.jsxs)(g,{onClick:()=>c(e),children:[(0,H.jsx)(w,{className:`size-3`}),`Push`]}),(0,H.jsxs)(g,{onClick:()=>u(e),children:[(0,H.jsx)(l,{className:`size-3`}),`Create PR`]}),(0,H.jsx)(j,{}),(0,H.jsxs)(g,{variant:`destructive`,onClick:()=>f(e),disabled:e===a?.name,children:[(0,H.jsx)(v,{className:`size-3`}),`Delete`]})]})]})}function q({commit:e,lane:t,isSelected:n,isHead:r,labels:i,currentBranch:a,onSelect:o,onCheckout:c,onCherryPick:l,onRevert:u,onMerge:f,onDeleteBranch:p,onPushBranch:m,onCreatePr:h,onOpenCreateBranch:_,onOpenCreateTag:v,onOpenDiff:S,onCopyHash:C}){let w=L[t%L.length],T=i.filter(e=>e.type===`branch`),D=i.filter(e=>e.type===`tag`);return(0,H.jsxs)(M,{children:[(0,H.jsx)(x,{asChild:!0,children:(0,H.jsxs)(`tr`,{className:`hover:bg-muted/50 cursor-pointer border-b border-border/20 ${n?`bg-primary/10`:``} ${r?`font-medium`:``}`,style:{height:`24px`},onClick:o,children:[(0,H.jsx)(`td`,{className:`px-2 truncate max-w-0`,children:(0,H.jsxs)(`div`,{className:`flex items-center gap-1.5 min-w-0`,children:[T.map(e=>(0,H.jsx)(K,{name:e.name,type:`branch`,remotes:e.remotes,isCurrent:e.current,color:w,currentBranch:a,onCheckout:c,onMerge:f,onPush:m,onCreatePr:h,onDelete:p},`branch-${e.name}`)),D.map(e=>(0,H.jsx)(K,{name:e.name,type:`tag`,remotes:[],isCurrent:!1,color:w,currentBranch:a,onCheckout:c,onMerge:f,onPush:m,onCreatePr:h,onDelete:p},`tag-${e.name}`)),(0,H.jsx)(`span`,{className:`truncate text-xs`,children:e.subject})]})}),(0,H.jsx)(`td`,{className:`px-2 text-xs text-muted-foreground whitespace-nowrap shrink-0`,children:R(e.authorDate)}),(0,H.jsx)(`td`,{className:`px-2 text-xs text-muted-foreground truncate max-w-[120px]`,children:e.authorName}),(0,H.jsx)(`td`,{className:`px-2 text-xs text-muted-foreground font-mono whitespace-nowrap`,children:e.abbreviatedHash})]})}),(0,H.jsxs)(b,{children:[(0,H.jsx)(g,{onClick:()=>c(e.hash),children:`Checkout`}),(0,H.jsxs)(g,{onClick:()=>_(e.hash),children:[(0,H.jsx)(y,{className:`size-3`}),`Create Branch...`]}),(0,H.jsx)(j,{}),(0,H.jsxs)(g,{onClick:()=>l(e.hash),children:[(0,H.jsx)(F,{className:`size-3`}),`Cherry Pick`]}),(0,H.jsxs)(g,{onClick:()=>u(e.hash),children:[(0,H.jsx)(s,{className:`size-3`}),`Revert`]}),(0,H.jsxs)(g,{onClick:()=>v(e.hash),children:[(0,H.jsx)(d,{className:`size-3`}),`Create Tag...`]}),(0,H.jsx)(j,{}),(0,H.jsx)(g,{onClick:S,children:`View Diff`}),(0,H.jsxs)(g,{onClick:C,children:[(0,H.jsx)(E,{className:`size-3`}),`Copy Hash`]})]})]})}function J({commit:e,files:t,loadingDetail:n,projectName:r,onClose:i,copyHash:a}){let{openTab:s}=u();return(0,H.jsxs)(`div`,{className:`border-t bg-muted/30 max-h-[40%] overflow-auto`,children:[(0,H.jsxs)(`div`,{className:`px-3 py-2 border-b flex items-center justify-between`,children:[(0,H.jsxs)(`span`,{className:`text-sm font-medium truncate`,children:[e.abbreviatedHash,` — `,e.subject]}),(0,H.jsx)(o,{variant:`ghost`,size:`icon-xs`,onClick:i,children:`✕`})]}),(0,H.jsxs)(`div`,{className:`px-3 py-2 text-xs space-y-1`,children:[(0,H.jsxs)(`div`,{className:`flex gap-4`,children:[(0,H.jsx)(`span`,{className:`text-muted-foreground w-12 shrink-0`,children:`Author`}),(0,H.jsxs)(`span`,{children:[e.authorName,` <`,e.authorEmail,`>`]})]}),(0,H.jsxs)(`div`,{className:`flex gap-4`,children:[(0,H.jsx)(`span`,{className:`text-muted-foreground w-12 shrink-0`,children:`Date`}),(0,H.jsx)(`span`,{children:new Date(e.authorDate).toLocaleString()})]}),(0,H.jsxs)(`div`,{className:`flex gap-4`,children:[(0,H.jsx)(`span`,{className:`text-muted-foreground w-12 shrink-0`,children:`Hash`}),(0,H.jsx)(`span`,{className:`font-mono cursor-pointer hover:text-primary`,onClick:()=>a(e.hash),children:e.hash})]}),e.parents.length>0&&(0,H.jsxs)(`div`,{className:`flex gap-4`,children:[(0,H.jsx)(`span`,{className:`text-muted-foreground w-12 shrink-0`,children:`Parents`}),(0,H.jsx)(`span`,{className:`font-mono`,children:e.parents.map(e=>e.slice(0,7)).join(`, `)})]}),e.body&&(0,H.jsx)(`div`,{className:`mt-2 p-2 bg-background rounded text-xs whitespace-pre-wrap`,children:e.body})]}),(0,H.jsxs)(`div`,{className:`px-3 py-1 border-t`,children:[(0,H.jsx)(`div`,{className:`text-xs text-muted-foreground py-1`,children:n?`Loading files...`:`${t.length} file${t.length===1?``:`s`} changed`}),t.map(t=>(0,H.jsxs)(`div`,{className:`flex items-center gap-2 py-0.5 text-xs hover:bg-muted/50 rounded px-1 cursor-pointer`,onClick:()=>s({type:`git-diff`,title:`Diff ${f(t.path)}`,closable:!0,metadata:{projectName:r,ref1:e.parents[0]??void 0,ref2:e.hash,filePath:t.path},projectId:r}),children:[(0,H.jsx)(`span`,{className:`flex-1 truncate font-mono`,children:t.path}),t.additions>0&&(0,H.jsxs)(`span`,{className:`text-green-500`,children:[`+`,t.additions]}),t.deletions>0&&(0,H.jsxs)(`span`,{className:`text-red-500`,children:[`-`,t.deletions]})]},t.path))]})]})}function Y({type:e,hash:t,onClose:n,onCreateBranch:r,onCreateTag:i}){let[a,s]=(0,I.useState)(``),l=()=>{a.trim()&&(e===`branch`?r(a.trim(),t):i(a.trim(),t),n())};return(0,H.jsx)(O,{open:e!==null,onOpenChange:e=>{e||n()},children:(0,H.jsxs)(D,{children:[(0,H.jsx)(N,{children:(0,H.jsx)(T,{children:e===`branch`?`Create Branch`:`Create Tag`})}),(0,H.jsx)(c,{placeholder:e===`branch`?`Branch name`:`Tag name`,value:a,onChange:e=>s(e.target.value),onKeyDown:e=>{e.key===`Enter`&&l()},autoFocus:!0}),(0,H.jsxs)(A,{children:[(0,H.jsx)(o,{variant:`outline`,onClick:n,children:`Cancel`}),(0,H.jsx)(o,{disabled:!a.trim(),onClick:l,children:`Create`})]})]})})}function X({open:e,onClose:t,projectName:n,branches:r}){let i=r.filter(e=>!e.remote),a=r.filter(e=>e.remote),o=new Map;for(let e of a){let t=e.name.replace(/^remotes\//,``),n=t.indexOf(`/`);if(n<0)continue;let r=t.slice(0,n),i=t.slice(n+1),a=o.get(r)??[];a.push(i),o.set(r,a)}return(0,H.jsx)(O,{open:e,onOpenChange:e=>{e||t()},children:(0,H.jsxs)(D,{className:`max-w-md`,children:[(0,H.jsx)(N,{children:(0,H.jsx)(T,{children:`Repository Settings`})}),(0,H.jsxs)(`div`,{className:`space-y-4 text-sm`,children:[(0,H.jsxs)(Z,{title:`General`,children:[(0,H.jsx)(Q,{label:`Name`,value:n}),(0,H.jsx)(Q,{label:`Branches`,value:`${i.length} local, ${a.length} remote`})]}),(0,H.jsx)(Z,{title:`Local Branches`,children:i.map(e=>(0,H.jsxs)(`div`,{className:`flex items-center gap-2 py-0.5`,children:[(0,H.jsx)(`span`,{className:`text-xs ${e.current?`font-semibold text-primary`:`text-foreground`}`,children:e.name}),e.current&&(0,H.jsx)(`span`,{className:`text-[10px] text-muted-foreground italic`,children:`HEAD`}),e.remotes.length>0&&(0,H.jsxs)(`span`,{className:`text-[10px] text-muted-foreground`,children:[`(`,e.remotes.join(`, `),`)`]})]},e.name))}),(0,H.jsxs)(Z,{title:`Remotes`,children:[[...o.entries()].map(([e,t])=>(0,H.jsxs)(`div`,{className:`py-0.5`,children:[(0,H.jsx)(`span`,{className:`text-xs font-medium`,children:e}),(0,H.jsxs)(`span`,{className:`text-[10px] text-muted-foreground ml-2`,children:[t.length,` branch`,t.length===1?``:`es`]})]},e)),o.size===0&&(0,H.jsx)(`span`,{className:`text-xs text-muted-foreground`,children:`No remotes configured`})]})]})]})})}function Z({title:e,children:t}){return(0,H.jsxs)(`div`,{children:[(0,H.jsx)(`h4`,{className:`text-xs font-semibold text-muted-foreground uppercase tracking-wider mb-1.5`,children:e}),(0,H.jsx)(`div`,{className:`pl-1`,children:t})]})}function Q({label:e,value:t}){return(0,H.jsxs)(`div`,{className:`flex items-center gap-3 py-0.5`,children:[(0,H.jsx)(`span`,{className:`text-xs text-muted-foreground w-16 shrink-0`,children:e}),(0,H.jsx)(`span`,{className:`text-xs`,children:t})]})}function $({metadata:e}){let t=e?.projectName,n=B(t),[r,i]=(0,I.useState)({type:null}),[a,o]=(0,I.useState)(!1),s=(typeof window<`u`&&window.innerWidth<768?6:10)*16+16,[c,l]=(0,I.useState)(s),u=(0,I.useRef)(s);u.current=c;let d=(0,I.useRef)(!1),f=e=>{d.current=!0;let t=u.current,n=n=>{if(!d.current)return;let r=`touches`in n?n.touches[0].clientX:n.clientX;l(Math.max(40,t+r-e))},r=()=>{d.current=!1,window.removeEventListener(`mousemove`,n),window.removeEventListener(`mouseup`,r),window.removeEventListener(`touchmove`,n),window.removeEventListener(`touchend`,r)};window.addEventListener(`mousemove`,n),window.addEventListener(`mouseup`,r),window.addEventListener(`touchmove`,n,{passive:!1}),window.addEventListener(`touchend`,r)},{widths:p,startResize:m}=V({date:80,author:120,commit:70}),h=(0,I.useRef)(n.loadMore);h.current=n.loadMore;let g=(0,I.useCallback)(e=>{let t=e.currentTarget;t.scrollHeight-t.scrollTop-t.clientHeight<200&&h.current()},[]);return t?n.loading&&!n.data?(0,H.jsx)(oe,{}):n.error&&!n.data?(0,H.jsx)(se,{error:n.error,onRetry:n.fetchGraph}):(0,H.jsxs)(`div`,{className:`flex flex-col h-full`,children:[(0,H.jsx)(U,{branches:n.data?.branches??[],branchFilter:n.branchFilter,onBranchFilterChange:n.setBranchFilter,searchQuery:n.searchQuery,onSearchQueryChange:n.setSearchQuery,showSearch:n.showSearch,onToggleSearch:()=>n.setShowSearch(!n.showSearch),onFetch:n.fetchFromRemotes,onRefresh:n.fetchGraph,onOpenSettings:()=>o(!0),loading:n.loading,acting:n.acting,projectName:t}),n.error&&(0,H.jsx)(`div`,{className:`px-3 py-1.5 text-xs text-destructive bg-destructive/10`,children:n.error}),(0,H.jsx)(`div`,{className:`flex-1 overflow-auto`,onScroll:g,children:(0,H.jsxs)(`div`,{className:`flex min-w-max md:min-w-0`,children:[(0,H.jsxs)(`div`,{className:`sticky left-0 z-10 shrink-0 bg-background relative overflow-hidden`,style:{width:`${c}px`},children:[(0,H.jsx)(`div`,{className:`text-[11px] font-semibold text-muted-foreground px-2 border-b bg-background sticky top-0 z-20`,style:{height:`24px`,lineHeight:`24px`},children:`Graph`}),(0,H.jsx)(G,{commits:n.filteredCommits,laneMap:n.filteredLanes.laneMap,svgPaths:n.svgPaths,width:(n.filteredLanes.maxLane+2)*16,height:n.svgHeight,headHash:n.headHash}),(0,H.jsx)(`div`,{className:`absolute top-0 right-0 w-1.5 h-full cursor-col-resize hover:bg-primary/30`,onMouseDown:e=>{e.preventDefault(),f(e.clientX)},onTouchStart:e=>f(e.touches[0].clientX)})]}),(0,H.jsxs)(`div`,{className:`flex-1 min-w-[400px]`,children:[(0,H.jsxs)(`table`,{className:`w-full border-collapse text-xs`,style:{tableLayout:`fixed`},children:[(0,H.jsxs)(`colgroup`,{children:[(0,H.jsx)(`col`,{}),(0,H.jsx)(`col`,{style:{width:`${p.date}px`}}),(0,H.jsx)(`col`,{style:{width:`${p.author}px`}}),(0,H.jsx)(`col`,{style:{width:`${p.commit}px`}})]}),(0,H.jsx)(`thead`,{className:`sticky top-0 z-10 bg-background`,children:(0,H.jsxs)(`tr`,{className:`border-b text-[11px] font-semibold text-muted-foreground`,style:{height:`24px`},children:[(0,H.jsx)(`th`,{className:`text-left px-2 font-semibold`,children:`Description`}),(0,H.jsx)(ie,{label:`Date`,colKey:`date`,onStartResize:m}),(0,H.jsx)(ie,{label:`Author`,colKey:`author`,onStartResize:m}),(0,H.jsx)(`th`,{className:`text-left px-2 font-semibold`,children:`Commit`})]})}),(0,H.jsx)(`tbody`,{children:n.filteredCommits.map(e=>(0,H.jsx)(q,{commit:e,lane:n.filteredLanes.laneMap.get(e.hash)??0,isSelected:n.selectedCommit?.hash===e.hash,isHead:e.hash===n.headHash,labels:n.commitLabels.get(e.hash)??[],currentBranch:n.currentBranch,onSelect:()=>n.selectCommit(e),onCheckout:n.handleCheckout,onCherryPick:n.handleCherryPick,onRevert:n.handleRevert,onMerge:n.handleMerge,onDeleteBranch:n.handleDeleteBranch,onPushBranch:n.handlePushBranch,onCreatePr:n.handleCreatePr,onOpenCreateBranch:e=>i({type:`branch`,hash:e}),onOpenCreateTag:e=>i({type:`tag`,hash:e}),onOpenDiff:()=>n.openDiffForCommit(e),onCopyHash:()=>n.copyHash(e.hash)},e.hash))})]}),n.loadingMore&&(0,H.jsxs)(`div`,{className:`flex items-center justify-center gap-2 py-3 text-xs text-muted-foreground`,children:[(0,H.jsx)(C,{className:`size-3.5 animate-spin`}),` Loading more commits...`]}),!n.hasMore&&n.data&&n.data.commits.length>0&&(0,H.jsxs)(`div`,{className:`text-center py-2 text-xs text-muted-foreground`,children:[n.data.commits.length,` commits loaded`]})]})]})}),n.selectedCommit&&t&&(0,H.jsx)(J,{commit:n.selectedCommit,files:n.commitFiles,loadingDetail:n.loadingDetail,projectName:t,onClose:()=>n.setSelectedCommit(null),copyHash:n.copyHash}),(0,H.jsx)(Y,{type:r.type,hash:r.hash,onClose:()=>i({type:null}),onCreateBranch:n.handleCreateBranch,onCreateTag:n.handleCreateTag}),(0,H.jsx)(X,{open:a,onClose:()=>o(!1),projectName:t,branches:n.data?.branches??[]})]}):(0,H.jsx)(ae,{msg:`No project selected.`})}function ie({label:e,colKey:t,onStartResize:n}){return(0,H.jsxs)(`th`,{className:`text-left px-2 font-semibold relative`,children:[e,(0,H.jsx)(`div`,{className:`absolute top-0 right-0 w-1.5 h-full cursor-col-resize hover:bg-primary/30`,onMouseDown:e=>{e.preventDefault(),n(t,e.clientX)},onTouchStart:e=>n(t,e.touches[0].clientX)})]})}function ae({msg:e}){return(0,H.jsx)(`div`,{className:`flex items-center justify-center h-full text-muted-foreground text-sm`,children:e})}function oe(){return(0,H.jsxs)(`div`,{className:`flex items-center justify-center h-full gap-2 text-muted-foreground`,children:[(0,H.jsx)(C,{className:`size-5 animate-spin`}),(0,H.jsx)(`span`,{className:`text-sm`,children:`Loading git graph...`})]})}function se({error:e,onRetry:t}){return(0,H.jsxs)(`div`,{className:`flex flex-col items-center justify-center h-full gap-2 text-destructive text-sm`,children:[(0,H.jsx)(`p`,{children:e}),(0,H.jsx)(o,{variant:`outline`,size:`sm`,onClick:t,children:`Retry`})]})}export{$ as GitGraph};