codex-lens 0.1.27 → 0.1.29

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.
@@ -29,4 +29,4 @@
29
29
  * The original design remains. The terminal itself
30
30
  * has been extended to include xterm CSI codes, among
31
31
  * other features.
32
- */.xterm{cursor:text;position:relative;user-select:none;-ms-user-select:none;-webkit-user-select:none}.xterm.focus,.xterm:focus{outline:none}.xterm .xterm-helpers{position:absolute;top:0;z-index:5}.xterm .xterm-helper-textarea{padding:0;border:0;margin:0;position:absolute;opacity:0;left:-9999em;top:0;width:0;height:0;z-index:-5;white-space:nowrap;overflow:hidden;resize:none}.xterm .composition-view{background:#000;color:#fff;display:none;position:absolute;white-space:nowrap;z-index:1}.xterm .composition-view.active{display:block}.xterm .xterm-viewport{background-color:#000;overflow-y:scroll;cursor:default;position:absolute;right:0;left:0;top:0;bottom:0}.xterm .xterm-screen{position:relative}.xterm .xterm-screen canvas{position:absolute;left:0;top:0}.xterm .xterm-scroll-area{visibility:hidden}.xterm-char-measure-element{display:inline-block;visibility:hidden;position:absolute;top:0;left:-9999em;line-height:normal}.xterm.enable-mouse-events{cursor:default}.xterm.xterm-cursor-pointer,.xterm .xterm-cursor-pointer{cursor:pointer}.xterm.column-select.focus{cursor:crosshair}.xterm .xterm-accessibility:not(.debug),.xterm .xterm-message{position:absolute;left:0;top:0;bottom:0;right:0;z-index:10;color:transparent;pointer-events:none}.xterm .xterm-accessibility-tree:not(.debug) *::selection{color:transparent}.xterm .xterm-accessibility-tree{-webkit-user-select:text;user-select:text;white-space:pre}.xterm .live-region{position:absolute;left:-9999px;width:1px;height:1px;overflow:hidden}.xterm-dim{opacity:1!important}.xterm-underline-1{text-decoration:underline}.xterm-underline-2{text-decoration:double underline}.xterm-underline-3{text-decoration:wavy underline}.xterm-underline-4{text-decoration:dotted underline}.xterm-underline-5{text-decoration:dashed underline}.xterm-overline{text-decoration:overline}.xterm-overline.xterm-underline-1{text-decoration:overline underline}.xterm-overline.xterm-underline-2{text-decoration:overline double underline}.xterm-overline.xterm-underline-3{text-decoration:overline wavy underline}.xterm-overline.xterm-underline-4{text-decoration:overline dotted underline}.xterm-overline.xterm-underline-5{text-decoration:overline dashed underline}.xterm-strikethrough{text-decoration:line-through}.xterm-screen .xterm-decoration-container .xterm-decoration{z-index:6;position:absolute}.xterm-screen .xterm-decoration-container .xterm-decoration.xterm-decoration-top-layer{z-index:7}.xterm-decoration-overview-ruler{z-index:8;position:absolute;top:0;right:0;pointer-events:none}.xterm-decoration-top{z-index:2;position:relative}:root{--bg-primary: #000000;--bg-secondary: #0A0A0A;--bg-tertiary: #141414;--bg-elevated: #1A1A1A;--bg-surface: #0F0F0F;--text-primary: #F8FAFC;--text-secondary: #94A3B8;--text-muted: #64748B;--border-color: rgba(148, 163, 184, .15);--border-glow: rgba(34, 197, 94, .3);--accent-color: #22C55E;--accent-hover: #16A34A;--accent-glow: rgba(34, 197, 94, .4);--accent-soft: rgba(34, 197, 94, .1);--diff-add-bg: rgba(34, 197, 94, .12);--diff-add-text: #4ADE80;--diff-add-border: #22C55E;--diff-remove-bg: rgba(239, 68, 68, .12);--diff-remove-text: #F87171;--diff-remove-border: #EF4444;--danger-color: #EF4444;--danger-hover: #DC2626;--success-color: #22C55E;--success-hover: #16A34A;--warning-color: #F59E0B;--info-color: #3B82F6;--font-family: "DM Sans", -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;--font-display: "Space Grotesk", -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;--font-mono: "JetBrains Mono", "Fira Code", "SF Mono", "Consolas", monospace;--shadow-xs: 0 1px 2px rgba(0, 0, 0, .2);--shadow-sm: 0 2px 4px rgba(0, 0, 0, .2);--shadow-md: 0 4px 12px rgba(0, 0, 0, .3);--shadow-lg: 0 8px 24px rgba(0, 0, 0, .4);--shadow-xl: 0 16px 48px rgba(0, 0, 0, .5);--shadow-glow: 0 0 24px var(--accent-glow);--shadow-glow-sm: 0 0 12px var(--accent-glow);--radius-xs: 4px;--radius-sm: 6px;--radius-md: 10px;--radius-lg: 14px;--radius-xl: 20px;--transition-fast: .12s ease;--transition-normal: .2s ease;--transition-slow: .3s ease;--blur-sm: 8px;--blur-md: 16px;--blur-lg: 24px}@media (prefers-reduced-motion: reduce){*,*:before,*:after{animation-duration:.01ms!important;animation-iteration-count:1!important;transition-duration:.01ms!important}}*{margin:0;padding:0;box-sizing:border-box}html,body,#root{height:100%;width:100%;overflow:hidden}body{font-family:var(--font-family);background:var(--bg-primary);color:var(--text-primary);font-size:14px;line-height:1.6;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}.app-container{display:flex;flex-direction:column;height:100vh;width:100vw;background:var(--bg-primary)}.top-bar{display:flex;align-items:center;justify-content:space-between;padding:12px 20px;background:linear-gradient(180deg,#1e293bf2,#1e293bd9);-webkit-backdrop-filter:blur(var(--blur-md));backdrop-filter:blur(var(--blur-md));border-bottom:1px solid var(--border-color);min-height:52px;position:relative;z-index:100}.top-bar:after{content:"";position:absolute;bottom:0;left:0;right:0;height:1px;background:linear-gradient(90deg,transparent,var(--accent-color),transparent);opacity:.3}.top-bar-left,.top-bar-center,.top-bar-right{display:flex;align-items:center;gap:16px}.top-bar-left{width:260px}.top-bar-center{flex:1;justify-content:center}.top-bar-right{width:40%;justify-content:flex-end}.top-bar-title{font-family:var(--font-display);font-weight:600;font-size:13px;text-transform:uppercase;letter-spacing:1px;color:var(--text-secondary);display:flex;align-items:center;gap:8px}.top-bar-title:before{content:"";display:inline-block;width:3px;height:14px;background:var(--accent-color);border-radius:2px}.main-content{display:flex;flex:1;overflow:hidden;gap:1px;background:var(--border-color)}.panel{display:flex;flex-direction:column;overflow:hidden;background:linear-gradient(180deg,var(--bg-secondary) 0%,rgba(30,41,59,.95) 100%)}.left-panel{width:260px;flex-shrink:0}.middle-panel{flex:1;min-width:300px}.right-panel{width:40%;min-width:300px;max-width:60%}.panel-content{flex:1;overflow:auto;padding:8px}.file-tree{padding:4px}.file-item{padding:8px 12px;cursor:pointer;border-radius:var(--radius-sm);display:flex;align-items:center;gap:10px;transition:all var(--transition-fast);position:relative;margin:2px 0}.file-item:before{content:"";position:absolute;left:0;top:50%;transform:translateY(-50%);width:3px;height:0;background:var(--accent-color);border-radius:0 2px 2px 0;transition:height var(--transition-fast)}.file-item:hover{background:#94a3b814}.file-item:hover:before{height:60%}.file-item:active{transform:scale(.98)}.file-item.active{background:var(--accent-soft);color:var(--text-primary)}.file-item.active:before{height:70%}.file-icon{width:18px;height:18px;display:flex;align-items:center;justify-content:center;font-size:14px;opacity:.9}.file-name{flex:1;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;font-size:13px}.diff-line{font-family:var(--font-mono);font-size:13px;padding:3px 16px 3px 20px;white-space:pre;border-left:3px solid transparent;transition:background var(--transition-fast);position:relative}.diff-line.added{background:var(--diff-add-bg);color:var(--diff-add-text);border-left-color:var(--diff-add-border)}.diff-line.removed{background:var(--diff-remove-bg);color:var(--diff-remove-text);border-left-color:var(--diff-remove-border)}.diff-line:before{display:inline-block;width:16px;margin-left:-16px;text-align:center;font-weight:600;font-size:12px}.diff-line.added:before{content:"+";color:var(--diff-add-text)}.diff-line.removed:before{content:"-";color:var(--diff-remove-text)}.tab-bar{display:flex;background:#0f172a99;border-bottom:1px solid var(--border-color);overflow-x:auto;min-height:40px;scrollbar-width:none}.tab-bar::-webkit-scrollbar{display:none}.tab{display:flex;align-items:center;gap:8px;padding:10px 16px;background:transparent;border-right:1px solid var(--border-color);cursor:pointer;min-width:100px;max-width:180px;font-size:13px;color:var(--text-muted);transition:all var(--transition-fast);position:relative}.tab:hover{background:#94a3b80d;color:var(--text-secondary)}.tab.active{background:var(--bg-secondary);color:var(--text-primary)}.tab.active:after{content:"";position:absolute;bottom:0;left:0;right:0;height:2px;background:var(--accent-color)}.tab-name{flex:1;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.tab-close{background:none;border:none;color:var(--text-muted);font-size:16px;cursor:pointer;padding:2px 6px;border-radius:var(--radius-xs);line-height:1;transition:all var(--transition-fast);opacity:.6}.tab-close:hover{background:#ef444433;color:var(--danger-color);opacity:1}.tab.modified .tab-name{color:var(--accent-color)}.tab-modified-mark{color:var(--accent-color);margin-right:4px;font-size:10px}.context-menu{position:fixed;background:linear-gradient(180deg,var(--bg-tertiary) 0%,var(--bg-secondary) 100%);border:1px solid var(--border-color);border-radius:var(--radius-md);padding:6px;min-width:160px;box-shadow:var(--shadow-lg);z-index:1000;animation:contextMenuIn .15s ease;-webkit-backdrop-filter:blur(var(--blur-sm));backdrop-filter:blur(var(--blur-sm))}@keyframes contextMenuIn{0%{opacity:0;transform:scale(.95) translateY(-4px)}to{opacity:1;transform:scale(1) translateY(0)}}.context-menu-item{padding:10px 14px;cursor:pointer;font-size:13px;color:var(--text-primary);border-radius:var(--radius-sm);transition:all var(--transition-fast)}.context-menu-item:hover{background:var(--accent-soft);color:var(--accent-color)}.file-context-menu{position:fixed;background:linear-gradient(180deg,var(--bg-tertiary) 0%,var(--bg-secondary) 100%);border:1px solid var(--border-color);border-radius:var(--radius-md);padding:6px;min-width:200px;box-shadow:var(--shadow-lg);z-index:1000;animation:contextMenuIn .15s ease;-webkit-backdrop-filter:blur(var(--blur-sm));backdrop-filter:blur(var(--blur-sm))}.ws-status{display:inline-block;width:8px;height:8px;border-radius:50%;margin-left:8px;transition:all var(--transition-normal)}.ws-status.connected{background:var(--success-color);box-shadow:0 0 8px var(--success-color);animation:pulse 2s infinite}.ws-status.disconnected{background:var(--danger-color);box-shadow:0 0 8px var(--danger-color)}@keyframes pulse{0%,to{opacity:1;box-shadow:0 0 8px var(--success-color)}50%{opacity:.7;box-shadow:0 0 4px var(--success-color)}}.version-info{display:flex;align-items:center;gap:10px}.version-number{font-size:11px;color:var(--text-muted);font-weight:500;font-family:var(--font-mono);padding:3px 8px;background:#94a3b81a;border-radius:var(--radius-xs)}.update-badge{font-size:10px;padding:4px 10px;background:linear-gradient(135deg,var(--warning-color) 0%,#FBBF24 100%);color:var(--bg-primary);border-radius:var(--radius-sm);cursor:pointer;font-weight:600;transition:all var(--transition-fast)}.update-badge:hover{transform:translateY(-1px);box-shadow:0 4px 12px #f59e0b4d}.terminal-wrapper{flex:1;overflow:hidden;min-height:0}.section{margin-bottom:8px}.section-title{padding:10px 12px 6px;font-size:10px;font-weight:600;color:var(--text-muted);text-transform:uppercase;letter-spacing:.8px}.empty-state{padding:40px 24px;text-align:center;color:var(--text-muted);font-size:13px;display:flex;flex-direction:column;align-items:center;gap:12px}.empty-state:before{content:"";width:48px;height:48px;background:linear-gradient(135deg,var(--bg-tertiary) 0%,var(--bg-elevated) 100%);border-radius:var(--radius-lg);display:flex;align-items:center;justify-content:center}.code-panel{font-family:var(--font-mono)}.code-viewer-codemirror{height:100%;width:100%;display:flex;min-height:0;min-width:0}.code-viewer-codemirror .cm-theme{flex:1;display:flex;min-height:0;min-width:0}.code-viewer-codemirror .cm-editor{flex:1;min-width:0;min-height:0}.code-viewer-codemirror .cm-scroller{flex:1;overflow:auto!important;min-height:0}.code-content{padding:16px;white-space:pre-wrap;word-break:break-all;line-height:1.7;font-size:13px}.diff-container{font-family:var(--font-mono);font-size:13px;padding:8px 0}.task-btn{padding:8px 18px;border:none;border-radius:var(--radius-sm);font-size:12px;font-weight:600;cursor:pointer;transition:all var(--transition-fast);font-family:var(--font-family);display:inline-flex;align-items:center;gap:6px}.task-btn:hover{transform:translateY(-1px)}.task-btn:active{transform:translateY(0)}.task-btn-clear{background:transparent;color:var(--text-secondary);border:1px solid var(--border-color)}.task-btn-clear:hover{background:var(--accent-soft);border-color:var(--accent-color);color:var(--accent-color);box-shadow:var(--shadow-glow-sm)}::-webkit-scrollbar{width:6px;height:6px}::-webkit-scrollbar-track{background:transparent}::-webkit-scrollbar-thumb{background:var(--bg-elevated);border-radius:3px}::-webkit-scrollbar-thumb:hover{background:var(--text-muted)}::-webkit-scrollbar-corner{background:transparent}@media (max-width: 1024px){.left-panel{width:220px}.right-panel{width:45%;min-width:250px}.top-bar-left{width:220px}}@media (max-width: 768px){.main-content{flex-direction:column}.left-panel,.right-panel{width:100%;max-width:none;min-width:auto}.middle-panel{min-height:300px}.top-bar{flex-wrap:wrap;padding:10px 16px}.top-bar-left,.top-bar-center,.top-bar-right{width:auto}.top-bar-center{order:3;width:100%;justify-content:flex-start;margin-top:8px;padding-top:8px;border-top:1px solid var(--border-color)}}
32
+ */.xterm{cursor:text;position:relative;user-select:none;-ms-user-select:none;-webkit-user-select:none}.xterm.focus,.xterm:focus{outline:none}.xterm .xterm-helpers{position:absolute;top:0;z-index:5}.xterm .xterm-helper-textarea{padding:0;border:0;margin:0;position:absolute;opacity:0;left:-9999em;top:0;width:0;height:0;z-index:-5;white-space:nowrap;overflow:hidden;resize:none}.xterm .composition-view{background:#000;color:#fff;display:none;position:absolute;white-space:nowrap;z-index:1}.xterm .composition-view.active{display:block}.xterm .xterm-viewport{background-color:#000;overflow-y:scroll;cursor:default;position:absolute;right:0;left:0;top:0;bottom:0}.xterm .xterm-screen{position:relative}.xterm .xterm-screen canvas{position:absolute;left:0;top:0}.xterm .xterm-scroll-area{visibility:hidden}.xterm-char-measure-element{display:inline-block;visibility:hidden;position:absolute;top:0;left:-9999em;line-height:normal}.xterm.enable-mouse-events{cursor:default}.xterm.xterm-cursor-pointer,.xterm .xterm-cursor-pointer{cursor:pointer}.xterm.column-select.focus{cursor:crosshair}.xterm .xterm-accessibility:not(.debug),.xterm .xterm-message{position:absolute;left:0;top:0;bottom:0;right:0;z-index:10;color:transparent;pointer-events:none}.xterm .xterm-accessibility-tree:not(.debug) *::selection{color:transparent}.xterm .xterm-accessibility-tree{-webkit-user-select:text;user-select:text;white-space:pre}.xterm .live-region{position:absolute;left:-9999px;width:1px;height:1px;overflow:hidden}.xterm-dim{opacity:1!important}.xterm-underline-1{text-decoration:underline}.xterm-underline-2{text-decoration:double underline}.xterm-underline-3{text-decoration:wavy underline}.xterm-underline-4{text-decoration:dotted underline}.xterm-underline-5{text-decoration:dashed underline}.xterm-overline{text-decoration:overline}.xterm-overline.xterm-underline-1{text-decoration:overline underline}.xterm-overline.xterm-underline-2{text-decoration:overline double underline}.xterm-overline.xterm-underline-3{text-decoration:overline wavy underline}.xterm-overline.xterm-underline-4{text-decoration:overline dotted underline}.xterm-overline.xterm-underline-5{text-decoration:overline dashed underline}.xterm-strikethrough{text-decoration:line-through}.xterm-screen .xterm-decoration-container .xterm-decoration{z-index:6;position:absolute}.xterm-screen .xterm-decoration-container .xterm-decoration.xterm-decoration-top-layer{z-index:7}.xterm-decoration-overview-ruler{z-index:8;position:absolute;top:0;right:0;pointer-events:none}.xterm-decoration-top{z-index:2;position:relative}:root{--bg-primary: #000000;--bg-secondary: #0A0A0A;--bg-tertiary: #141414;--bg-elevated: #1A1A1A;--bg-surface: #0F0F0F;--text-primary: #F8FAFC;--text-secondary: #94A3B8;--text-muted: #64748B;--border-color: rgba(148, 163, 184, .15);--border-glow: rgba(34, 197, 94, .3);--accent-color: #22C55E;--accent-hover: #16A34A;--accent-glow: rgba(34, 197, 94, .4);--accent-soft: rgba(34, 197, 94, .1);--diff-add-bg: rgba(34, 197, 94, .12);--diff-add-text: #4ADE80;--diff-add-border: #22C55E;--diff-remove-bg: rgba(239, 68, 68, .12);--diff-remove-text: #F87171;--diff-remove-border: #EF4444;--danger-color: #EF4444;--danger-hover: #DC2626;--success-color: #22C55E;--success-hover: #16A34A;--warning-color: #F59E0B;--info-color: #3B82F6;--font-family: "DM Sans", -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;--font-display: "Space Grotesk", -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;--font-mono: "JetBrains Mono", "Fira Code", "SF Mono", "Consolas", monospace;--shadow-xs: 0 1px 2px rgba(0, 0, 0, .2);--shadow-sm: 0 2px 4px rgba(0, 0, 0, .2);--shadow-md: 0 4px 12px rgba(0, 0, 0, .3);--shadow-lg: 0 8px 24px rgba(0, 0, 0, .4);--shadow-xl: 0 16px 48px rgba(0, 0, 0, .5);--shadow-glow: 0 0 24px var(--accent-glow);--shadow-glow-sm: 0 0 12px var(--accent-glow);--radius-xs: 4px;--radius-sm: 6px;--radius-md: 10px;--radius-lg: 14px;--radius-xl: 20px;--transition-fast: .12s ease;--transition-normal: .2s ease;--transition-slow: .3s ease;--blur-sm: 8px;--blur-md: 16px;--blur-lg: 24px}@media (prefers-reduced-motion: reduce){*,*:before,*:after{animation-duration:.01ms!important;animation-iteration-count:1!important;transition-duration:.01ms!important}}*{margin:0;padding:0;box-sizing:border-box}html,body,#root{height:100%;width:100%;overflow:hidden}body{font-family:var(--font-family);background:var(--bg-primary);color:var(--text-primary);font-size:14px;line-height:1.6;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}.app-container{display:flex;flex-direction:column;height:100vh;width:100vw;background:var(--bg-primary)}.top-bar{display:flex;align-items:center;justify-content:space-between;padding:12px 20px;background:linear-gradient(180deg,#1e293bf2,#1e293bd9);-webkit-backdrop-filter:blur(var(--blur-md));backdrop-filter:blur(var(--blur-md));border-bottom:1px solid var(--border-color);min-height:52px;position:relative;z-index:100}.top-bar:after{content:"";position:absolute;bottom:0;left:0;right:0;height:1px;background:linear-gradient(90deg,transparent,var(--accent-color),transparent);opacity:.3}.top-bar-left,.top-bar-center,.top-bar-right{display:flex;align-items:center;gap:16px}.top-bar-left{width:260px}.top-bar-center{flex:1;justify-content:center}.top-bar-right{width:40%;justify-content:flex-end}.top-bar-title{font-family:var(--font-display);font-weight:600;font-size:13px;text-transform:uppercase;letter-spacing:1px;color:var(--text-secondary);display:flex;align-items:center;gap:8px}.top-bar-title:before{content:"";display:inline-block;width:3px;height:14px;background:var(--accent-color);border-radius:2px}.main-content{display:flex;flex:1;overflow:hidden;gap:1px;background:var(--border-color)}.panel{display:flex;flex-direction:column;overflow:hidden;background:linear-gradient(180deg,var(--bg-secondary) 0%,rgba(30,41,59,.95) 100%)}.left-panel{width:260px;flex-shrink:0}.middle-panel{flex:1;min-width:300px}.right-panel{width:40%;min-width:300px;max-width:60%}.panel-content{flex:1;overflow:auto;padding:8px}.file-tree{padding:4px}.file-item{padding:8px 12px;cursor:pointer;border-radius:var(--radius-sm);display:flex;align-items:center;gap:10px;transition:all var(--transition-fast);position:relative;margin:2px 0}.file-item:before{content:"";position:absolute;left:0;top:50%;transform:translateY(-50%);width:3px;height:0;background:var(--accent-color);border-radius:0 2px 2px 0;transition:height var(--transition-fast)}.file-item:hover{background:#94a3b814}.file-item:hover:before{height:60%}.file-item:active{transform:scale(.98)}.file-item.active{background:var(--accent-soft);color:var(--text-primary)}.file-item.active:before{height:70%}.file-icon{width:18px;height:18px;display:flex;align-items:center;justify-content:center;font-size:14px;opacity:.9}.file-name{flex:1;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;font-size:13px}.diff-line{font-family:var(--font-mono);font-size:13px;padding:3px 16px 3px 20px;white-space:pre;border-left:3px solid transparent;transition:background var(--transition-fast);position:relative}.diff-line.added{background:var(--diff-add-bg);color:var(--diff-add-text);border-left-color:var(--diff-add-border)}.diff-line.removed{background:var(--diff-remove-bg);color:var(--diff-remove-text);border-left-color:var(--diff-remove-border)}.diff-line:before{display:inline-block;width:16px;margin-left:-16px;text-align:center;font-weight:600;font-size:12px}.diff-line.added:before{content:"+";color:var(--diff-add-text)}.diff-line.removed:before{content:"-";color:var(--diff-remove-text)}.tab-bar{display:flex;background:#0f172a99;border-bottom:1px solid var(--border-color);overflow-x:auto;min-height:40px;scrollbar-width:none}.tab-bar::-webkit-scrollbar{display:none}.tab{display:flex;align-items:center;gap:8px;padding:10px 16px;background:transparent;border-right:1px solid var(--border-color);cursor:pointer;min-width:100px;max-width:180px;font-size:13px;color:var(--text-muted);transition:all var(--transition-fast);position:relative}.tab:hover{background:#94a3b80d;color:var(--text-secondary)}.tab.active{background:var(--bg-secondary);color:var(--text-primary)}.tab.active:after{content:"";position:absolute;bottom:0;left:0;right:0;height:2px;background:var(--accent-color)}.tab-name{flex:1;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.tab-close{background:none;border:none;color:var(--text-muted);font-size:16px;cursor:pointer;padding:2px 6px;border-radius:var(--radius-xs);line-height:1;transition:all var(--transition-fast);opacity:.6}.tab-close:hover{background:#ef444433;color:var(--danger-color);opacity:1}.tab.modified .tab-name{color:var(--accent-color)}.tab-modified-mark{color:var(--accent-color);margin-right:4px;font-size:10px}.context-menu{position:fixed;background:linear-gradient(180deg,var(--bg-tertiary) 0%,var(--bg-secondary) 100%);border:1px solid var(--border-color);border-radius:var(--radius-md);padding:6px;min-width:160px;box-shadow:var(--shadow-lg);z-index:1000;animation:contextMenuIn .15s ease;-webkit-backdrop-filter:blur(var(--blur-sm));backdrop-filter:blur(var(--blur-sm))}@keyframes contextMenuIn{0%{opacity:0;transform:scale(.95) translateY(-4px)}to{opacity:1;transform:scale(1) translateY(0)}}.context-menu-item{padding:10px 14px;cursor:pointer;font-size:13px;color:var(--text-primary);border-radius:var(--radius-sm);transition:all var(--transition-fast)}.context-menu-item:hover{background:var(--accent-soft);color:var(--accent-color)}.file-context-menu{position:fixed;background:linear-gradient(180deg,var(--bg-tertiary) 0%,var(--bg-secondary) 100%);border:1px solid var(--border-color);border-radius:var(--radius-md);padding:6px;min-width:200px;box-shadow:var(--shadow-lg);z-index:1000;animation:contextMenuIn .15s ease;-webkit-backdrop-filter:blur(var(--blur-sm));backdrop-filter:blur(var(--blur-sm))}.ws-status{display:inline-block;width:8px;height:8px;border-radius:50%;margin-left:8px;transition:all var(--transition-normal)}.ws-status.connected{background:var(--success-color);box-shadow:0 0 8px var(--success-color);animation:pulse 2s infinite}.ws-status.disconnected{background:var(--danger-color);box-shadow:0 0 8px var(--danger-color)}@keyframes pulse{0%,to{opacity:1;box-shadow:0 0 8px var(--success-color)}50%{opacity:.7;box-shadow:0 0 4px var(--success-color)}}.version-info{display:flex;align-items:center;gap:10px}.version-number{font-size:11px;color:var(--text-muted);font-weight:500;font-family:var(--font-mono);padding:3px 8px;background:#94a3b81a;border-radius:var(--radius-xs)}.update-badge{font-size:10px;padding:4px 10px;background:linear-gradient(135deg,var(--warning-color) 0%,#FBBF24 100%);color:var(--bg-primary);border-radius:var(--radius-sm);cursor:pointer;font-weight:600;transition:all var(--transition-fast)}.update-badge:hover{transform:translateY(-1px);box-shadow:0 4px 12px #f59e0b4d}.terminal-wrapper{flex:1;overflow:hidden;min-height:0}.section{margin-bottom:8px}.section-title{padding:10px 12px 6px;font-size:10px;font-weight:600;color:var(--text-muted);text-transform:uppercase;letter-spacing:.8px}.empty-state{padding:40px 24px;text-align:center;color:var(--text-muted);font-size:13px;display:flex;flex-direction:column;align-items:center;gap:12px}.empty-state:before{content:"";width:48px;height:48px;background:linear-gradient(135deg,var(--bg-tertiary) 0%,var(--bg-elevated) 100%);border-radius:var(--radius-lg);display:flex;align-items:center;justify-content:center}.code-panel{font-family:var(--font-mono)}.code-viewer-codemirror{height:100%;width:100%;display:flex;min-height:0;min-width:0}.code-viewer-codemirror .cm-theme{flex:1;display:flex;min-height:0;min-width:0}.code-viewer-codemirror .cm-editor{flex:1;min-width:0;min-height:0}.code-viewer-codemirror .cm-scroller{flex:1;overflow:auto!important;min-height:0}.code-content{padding:16px;white-space:pre-wrap;word-break:break-all;line-height:1.7;font-size:13px}.diff-container{font-family:var(--font-mono);font-size:13px;padding:8px 0}.task-btn{padding:8px 18px;border:none;border-radius:var(--radius-sm);font-size:12px;font-weight:600;cursor:pointer;transition:all var(--transition-fast);font-family:var(--font-family);display:inline-flex;align-items:center;gap:6px}.task-btn:hover{transform:translateY(-1px)}.task-btn:active{transform:translateY(0)}.task-btn-clear{background:transparent;color:var(--text-secondary);border:1px solid var(--border-color)}.task-btn-clear:hover{background:var(--accent-soft);border-color:var(--accent-color);color:var(--accent-color);box-shadow:var(--shadow-glow-sm)}::-webkit-scrollbar{width:6px;height:6px}::-webkit-scrollbar-track{background:transparent}::-webkit-scrollbar-thumb{background:var(--bg-elevated);border-radius:3px}::-webkit-scrollbar-thumb:hover{background:var(--text-muted)}::-webkit-scrollbar-corner{background:transparent}.git-status-bar{display:flex;align-items:center;gap:16px;padding:6px 14px;background:#94a3b814;border-radius:var(--radius-sm);border:1px solid var(--border-color)}.git-branch{display:flex;align-items:center;gap:6px;font-family:var(--font-mono);font-size:12px;font-weight:600;color:var(--accent-color)}.git-branch-icon{font-size:14px;opacity:.8}.git-stats{display:flex;align-items:center;gap:12px}.git-staged,.git-unstaged{display:flex;align-items:center;gap:4px;font-size:11px;color:var(--text-secondary)}.git-staged-count{font-weight:600;color:var(--success-color)}.git-unstaged-count{font-weight:600;color:var(--warning-color)}.git-changes-btn{padding:4px 10px;font-size:11px;background:transparent;border:1px solid var(--border-color);border-radius:var(--radius-xs);color:var(--text-secondary);cursor:pointer;transition:all var(--transition-fast)}.git-changes-btn:hover{background:var(--accent-soft);border-color:var(--accent-color);color:var(--accent-color)}.staged-changes-panel{position:absolute;top:52px;left:0;right:0;max-height:400px;background:linear-gradient(180deg,var(--bg-tertiary) 0%,var(--bg-secondary) 100%);border-bottom:1px solid var(--border-color);z-index:99;overflow-y:auto;animation:slideDown .2s ease}@keyframes slideDown{0%{opacity:0;transform:translateY(-8px)}to{opacity:1;transform:translateY(0)}}.staged-panel-header{display:flex;justify-content:space-between;align-items:center;padding:10px 16px;background:#0f172a99;border-bottom:1px solid var(--border-color);font-weight:600;font-size:12px;color:var(--text-secondary)}.staged-panel-actions{display:flex;gap:8px;align-items:center}.stage-btn,.unstage-btn{padding:4px 10px;font-size:11px;background:var(--bg-elevated);border:1px solid var(--border-color);border-radius:var(--radius-xs);color:var(--text-secondary);cursor:pointer;transition:all var(--transition-fast)}.stage-btn:hover,.unstage-btn:hover{background:var(--accent-soft);border-color:var(--accent-color);color:var(--accent-color)}.close-btn{width:24px;height:24px;display:flex;align-items:center;justify-content:center;background:transparent;border:none;color:var(--text-muted);cursor:pointer;font-size:16px;border-radius:var(--radius-xs);transition:all var(--transition-fast)}.close-btn:hover{background:#ef444433;color:var(--danger-color)}.changes-section{padding:8px 0;border-bottom:1px solid var(--border-color)}.changes-section:last-child{border-bottom:none}.changes-section-header{padding:4px 16px}.changes-section-title{font-size:10px;font-weight:600;color:var(--text-muted);text-transform:uppercase;letter-spacing:.5px}.changes-list{display:flex;flex-direction:column}.change-item{display:flex;align-items:center;gap:8px;padding:6px 16px;cursor:pointer;transition:background var(--transition-fast)}.change-item:hover{background:#94a3b814}.change-item.staged{background:var(--accent-soft)}.change-icon{width:16px;text-align:center;font-size:12px;color:var(--text-muted)}.change-item.staged .change-icon{color:var(--success-color)}.change-status{width:14px;height:14px;display:flex;align-items:center;justify-content:center;font-size:10px;font-weight:700;font-family:var(--font-mono);border-radius:2px}.change-status.M{background:var(--warning-color);color:var(--bg-primary)}.change-status.A{background:var(--success-color);color:var(--bg-primary)}.change-status.D{background:var(--danger-color);color:var(--text-primary)}.change-status.R{background:var(--info-color);color:var(--text-primary)}.change-status.\?{background:var(--text-muted);color:var(--bg-primary)}.change-path{flex:1;font-size:12px;font-family:var(--font-mono);color:var(--text-primary);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.no-changes{padding:24px;text-align:center;color:var(--text-muted);font-size:13px}.commit-section{display:flex;gap:8px;padding:12px 16px;background:#0f172a99;border-top:1px solid var(--border-color)}.commit-input{flex:1;padding:8px 12px;background:var(--bg-primary);border:1px solid var(--border-color);border-radius:var(--radius-sm);color:var(--text-primary);font-size:12px;font-family:var(--font-family)}.commit-input:focus{outline:none;border-color:var(--accent-color)}.commit-btn{padding:8px 16px;background:var(--accent-color);border:none;border-radius:var(--radius-sm);color:var(--bg-primary);font-size:12px;font-weight:600;cursor:pointer;transition:all var(--transition-fast)}.commit-btn:hover{background:var(--accent-hover);transform:translateY(-1px)}.file-git-badge{width:14px;height:14px;display:flex;align-items:center;justify-content:center;font-size:9px;font-weight:700;font-family:var(--font-mono);border-radius:2px;margin-right:2px}.file-git-badge.staged{background:var(--success-color);color:var(--bg-primary)}.file-git-badge.wt{background:var(--warning-color);color:var(--bg-primary)}@media (max-width: 1024px){.left-panel{width:220px}.right-panel{width:45%;min-width:250px}.top-bar-left{width:220px}}@media (max-width: 768px){.main-content{flex-direction:column}.left-panel,.right-panel{width:100%;max-width:none;min-width:auto}.middle-panel{min-height:300px}.top-bar{flex-wrap:wrap;padding:10px 16px}.top-bar-left,.top-bar-center,.top-bar-right{width:auto}.top-bar-center{order:3;width:100%;justify-content:flex-start;margin-top:8px;padding-top:8px;border-top:1px solid var(--border-color)}}
@@ -4,8 +4,8 @@
4
4
  <meta charset="UTF-8" />
5
5
  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
6
6
  <title>Codex Lens</title>
7
- <script type="module" crossorigin src="./assets/main-d-2BqwRB.js"></script>
8
- <link rel="stylesheet" crossorigin href="./assets/main-DNXrKVO-.css">
7
+ <script type="module" crossorigin src="./assets/main-8FK9vFAz.js"></script>
8
+ <link rel="stylesheet" crossorigin href="./assets/main-CnRUPtz5.css">
9
9
  </head>
10
10
  <body>
11
11
  <div id="root"></div>
package/dist/watcher.js CHANGED
@@ -109,6 +109,21 @@ class FileWatcher {
109
109
  this.wsEmitter = wsEmitter;
110
110
  this.watcher = null;
111
111
  this.fileContents = /* @__PURE__ */ new Map();
112
+ this.gitStatusCallback = null;
113
+ this._gitStatusTimeout = null;
114
+ }
115
+ setGitStatusCallback(callback) {
116
+ this.gitStatusCallback = callback;
117
+ }
118
+ _scheduleGitStatusUpdate(filePath) {
119
+ if (this.gitStatusCallback) {
120
+ if (this._gitStatusTimeout) {
121
+ clearTimeout(this._gitStatusTimeout);
122
+ }
123
+ this._gitStatusTimeout = setTimeout(() => {
124
+ this.gitStatusCallback(filePath);
125
+ }, 500);
126
+ }
112
127
  }
113
128
  async start() {
114
129
  const ignored = [
@@ -174,6 +189,7 @@ class FileWatcher {
174
189
  }
175
190
  this.emitFileChange(filePath, newContent, diff);
176
191
  }
192
+ this._scheduleGitStatusUpdate(filePath);
177
193
  } catch (error) {
178
194
  logger.errorWithStack(`Error handling file change: ${filePath}`, error);
179
195
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "codex-lens",
3
- "version": "0.1.27",
3
+ "version": "0.1.29",
4
4
  "description": "A visualization tool for Codex that monitors API requests and file system changes",
5
5
  "license": "MIT",
6
6
  "type": "module",
package/src/aggregator.js CHANGED
@@ -1,5 +1,4 @@
1
1
  import { createServer } from 'http';
2
- import { createHash, createHash as cryptoCreateHash } from 'crypto';
3
2
  import { WebSocketServer } from 'ws';
4
3
  import express from 'express';
5
4
  import { spawn } from 'child_process';
@@ -7,6 +6,7 @@ import { createProxyServer } from './proxy.js';
7
6
  import { FileWatcher, scanDirectory } from './watcher.js';
8
7
  import { createLogger } from './lib/logger.js';
9
8
  import { spawnCodex, writeToPty, resizePty, killPty, onPtyData, onPtyExit, getPtyState, getOutputBuffer } from './pty-manager.js';
9
+ import { createGitManager } from './git-manager.js';
10
10
  import path from 'path';
11
11
  import { fileURLToPath } from 'url';
12
12
  import { readFileSync, existsSync, writeFileSync } from 'fs';
@@ -56,6 +56,7 @@ class Aggregator {
56
56
  this.proxyServer = null;
57
57
  this.fileWatcher = null;
58
58
  this.ptyProcess = null;
59
+ this.gitManager = null;
59
60
  }
60
61
 
61
62
  async start(proxyPort) {
@@ -158,6 +159,19 @@ class Aggregator {
158
159
  this.fileWatcher = new FileWatcher(this.projectRoot, (event) => this.broadcast(event));
159
160
  await this.fileWatcher.start();
160
161
 
162
+ // Initialize Git Manager
163
+ this.gitManager = createGitManager(this.projectRoot, (event) => this.broadcast(event));
164
+ if (this.gitManager.isGitRepo()) {
165
+ await this.gitManager.broadcastUpdate();
166
+ logger.info('Git repository detected, git status broadcasting enabled');
167
+ // Set up git status update trigger on file changes
168
+ this.fileWatcher.setGitStatusCallback((filePath) => {
169
+ if (this.gitManager?.isGitRepo()) {
170
+ this.gitManager.scheduleStatusUpdate();
171
+ }
172
+ });
173
+ }
174
+
161
175
  await this.startCodex(proxyPort);
162
176
 
163
177
  return { httpPort: HTTP_PORT, proxyPort };
@@ -249,7 +263,7 @@ class Aggregator {
249
263
  }
250
264
  }
251
265
 
252
- handleClientMessage(data, ws) {
266
+ async handleClientMessage(data, ws) {
253
267
  if (data.type === 'user_message') {
254
268
  writeToPty(data.data + '\r');
255
269
  } else if (data.type === 'open_file') {
@@ -267,6 +281,33 @@ class Aggregator {
267
281
  }
268
282
  }));
269
283
  }
284
+ } else if (data.type === 'git_status_request') {
285
+ if (this.gitManager?.isGitRepo()) {
286
+ this.gitManager.broadcastUpdate();
287
+ }
288
+ } else if (data.type === 'git_stage') {
289
+ if (this.gitManager?.isGitRepo()) {
290
+ if (data.filePath) {
291
+ await this.gitManager.stageFile(data.filePath);
292
+ } else {
293
+ await this.gitManager.stageAll();
294
+ }
295
+ this.gitManager.broadcastUpdate();
296
+ }
297
+ } else if (data.type === 'git_unstage') {
298
+ if (this.gitManager?.isGitRepo()) {
299
+ if (data.filePath) {
300
+ await this.gitManager.unstageFile(data.filePath);
301
+ } else {
302
+ await this.gitManager.unstageAll();
303
+ }
304
+ this.gitManager.broadcastUpdate();
305
+ }
306
+ } else if (data.type === 'git_commit') {
307
+ if (this.gitManager?.isGitRepo()) {
308
+ await this.gitManager.commit(data.message);
309
+ this.gitManager.broadcastUpdate();
310
+ }
270
311
  }
271
312
  }
272
313
 
@@ -13,6 +13,14 @@ export function App() {
13
13
  const [hasUpdate, setHasUpdate] = useState(false);
14
14
  const [projectName, setProjectName] = useState('');
15
15
  const [saving, setSaving] = useState(false);
16
+ const [gitInfo, setGitInfo] = useState({
17
+ isRepo: false,
18
+ branch: null,
19
+ status: null,
20
+ stagedCount: 0,
21
+ unstagedCount: 0
22
+ });
23
+ const [showStagedPanel, setShowStagedPanel] = useState(false);
16
24
  const wsRef = useRef(null);
17
25
 
18
26
  useEffect(() => {
@@ -122,6 +130,15 @@ export function App() {
122
130
  case 'file_content':
123
131
  openFileInTab(msg.data);
124
132
  break;
133
+ case 'git_status':
134
+ setGitInfo({
135
+ isRepo: msg.data.isRepo,
136
+ branch: msg.data.branch,
137
+ status: msg.data.status,
138
+ stagedCount: msg.data.stagedCount,
139
+ unstagedCount: msg.data.unstagedCount
140
+ });
141
+ break;
125
142
  case 'connected':
126
143
  console.log('Server confirmed connection');
127
144
  break;
@@ -130,6 +147,24 @@ export function App() {
130
147
  }
131
148
  }
132
149
 
150
+ function sendGitCommand(type, payload) {
151
+ if (wsRef.current?.readyState === WebSocket.OPEN) {
152
+ wsRef.current.send(JSON.stringify({ type, ...payload }));
153
+ }
154
+ }
155
+
156
+ function handleStage(path) {
157
+ sendGitCommand('git_stage', { filePath: path });
158
+ }
159
+
160
+ function handleUnstage(path) {
161
+ sendGitCommand('git_unstage', { filePath: path });
162
+ }
163
+
164
+ function handleCommit(message) {
165
+ sendGitCommand('git_commit', { message });
166
+ }
167
+
133
168
  function openFileInTab(data) {
134
169
  const fileName = data.path.split(/[/\\]/).pop();
135
170
 
@@ -380,6 +415,30 @@ export function App() {
380
415
  <span className="top-bar-title">当前工作区: {projectName}</span>
381
416
  </div>
382
417
  <div className="top-bar-center">
418
+ {gitInfo.isRepo && (
419
+ <div className="git-status-bar">
420
+ <span className="git-branch">
421
+ <span className="git-branch-icon">⎇</span>
422
+ {gitInfo.branch || 'main'}
423
+ </span>
424
+ <div className="git-stats">
425
+ <span className="git-staged" title="已暂存">
426
+ <span className="git-staged-count">{gitInfo.stagedCount}</span>
427
+ <span className="git-staged-label">已暂存</span>
428
+ </span>
429
+ <span className="git-unstaged" title="未暂存">
430
+ <span className="git-unstaged-count">{gitInfo.unstagedCount}</span>
431
+ <span className="git-unstaged-label">未暂存</span>
432
+ </span>
433
+ </div>
434
+ <button
435
+ className="git-changes-btn"
436
+ onClick={() => setShowStagedPanel(!showStagedPanel)}
437
+ >
438
+ {showStagedPanel ? '隐藏变更' : '查看变更'}
439
+ </button>
440
+ </div>
441
+ )}
383
442
  <button className="task-btn task-btn-clear" onClick={clearAllDiff} title="清空所有 diff 显示">
384
443
  清空 diff
385
444
  </button>
@@ -402,6 +461,7 @@ export function App() {
402
461
  files={files}
403
462
  activeFile={activeTab?.path || null}
404
463
  onFileClick={handleFileClick}
464
+ gitInfo={gitInfo}
405
465
  />
406
466
  <div className="panel middle-panel">
407
467
  <TabBar
@@ -455,6 +515,15 @@ export function App() {
455
515
  }}
456
516
  />
457
517
  )}
518
+ {showStagedPanel && gitInfo.isRepo && (
519
+ <StagedChangesPanel
520
+ gitInfo={gitInfo}
521
+ onStage={handleStage}
522
+ onUnstage={handleUnstage}
523
+ onCommit={handleCommit}
524
+ onClose={() => setShowStagedPanel(false)}
525
+ />
526
+ )}
458
527
  <div className="panel right-panel">
459
528
  <div className="terminal-wrapper">
460
529
  <TerminalPanel />
@@ -516,7 +585,93 @@ function ContextMenu({ x, y, tab, tabs, saving, onClose, onCloseTab, onCloseOthe
516
585
  );
517
586
  }
518
587
 
519
- function LeftPanel({ files, activeFile, onFileClick }) {
588
+ function StagedChangesPanel({ gitInfo, onStage, onUnstage, onCommit, onClose }) {
589
+ if (!gitInfo.isRepo || !gitInfo.status) return null;
590
+
591
+ const { staged, unstaged, untracked } = gitInfo.status;
592
+
593
+ return (
594
+ <div className="staged-changes-panel">
595
+ <div className="staged-panel-header">
596
+ <span>Git 变更</span>
597
+ <div className="staged-panel-actions">
598
+ <button onClick={() => onStage(null)} className="stage-btn">Stage All</button>
599
+ <button onClick={() => onUnstage(null)} className="unstage-btn">Unstage All</button>
600
+ <button onClick={onClose} className="close-btn">×</button>
601
+ </div>
602
+ </div>
603
+
604
+ {staged?.length > 0 && (
605
+ <div className="changes-section">
606
+ <div className="changes-section-header">
607
+ <span className="changes-section-title">已暂存 ({staged.length})</span>
608
+ </div>
609
+ <div className="changes-list">
610
+ {staged.map((file, i) => (
611
+ <div key={i} className="change-item staged" onClick={() => onUnstage(file.path)}>
612
+ <span className="change-icon">✓</span>
613
+ <span className={`change-status ${file.indexStatus}`}>{file.indexStatus}</span>
614
+ <span className="change-path">{file.path}</span>
615
+ </div>
616
+ ))}
617
+ </div>
618
+ </div>
619
+ )}
620
+
621
+ {unstaged?.length > 0 && (
622
+ <div className="changes-section">
623
+ <div className="changes-section-header">
624
+ <span className="changes-section-title">未暂存修改 ({unstaged.length})</span>
625
+ </div>
626
+ <div className="changes-list">
627
+ {unstaged.map((file, i) => (
628
+ <div key={i} className="change-item" onClick={() => onStage(file.path)}>
629
+ <span className="change-icon">○</span>
630
+ <span className={`change-status ${file.workTreeStatus}`}>{file.workTreeStatus}</span>
631
+ <span className="change-path">{file.path}</span>
632
+ </div>
633
+ ))}
634
+ </div>
635
+ </div>
636
+ )}
637
+
638
+ {untracked?.length > 0 && (
639
+ <div className="changes-section">
640
+ <div className="changes-section-header">
641
+ <span className="changes-section-title">未跟踪 ({untracked.length})</span>
642
+ </div>
643
+ <div className="changes-list">
644
+ {untracked.map((file, i) => (
645
+ <div key={i} className="change-item untracked" onClick={() => onStage(file.path)}>
646
+ <span className="change-icon">?</span>
647
+ <span className="change-status">?</span>
648
+ <span className="change-path">{file.path}</span>
649
+ </div>
650
+ ))}
651
+ </div>
652
+ </div>
653
+ )}
654
+
655
+ {((!staged?.length) && (!unstaged?.length) && (!untracked?.length)) && (
656
+ <div className="no-changes">无变更</div>
657
+ )}
658
+
659
+ {staged?.length > 0 && (
660
+ <div className="commit-section">
661
+ <input type="text" className="commit-input" placeholder="提交信息..." id="commit-message" />
662
+ <button className="commit-btn" onClick={() => {
663
+ const msg = document.getElementById('commit-message')?.value;
664
+ if (msg) onCommit(msg);
665
+ }}>
666
+ Commit
667
+ </button>
668
+ </div>
669
+ )}
670
+ </div>
671
+ );
672
+ }
673
+
674
+ function LeftPanel({ files, activeFile, onFileClick, gitInfo }) {
520
675
  const [expandedDirs, setExpandedDirs] = useState({});
521
676
  const [contextMenu, setContextMenu] = useState(null);
522
677
 
@@ -567,6 +722,14 @@ function LeftPanel({ files, activeFile, onFileClick }) {
567
722
  const isExpanded = expandedDirs[item.path];
568
723
  const indent = 8 + depth * 16;
569
724
 
725
+ // Get file git status
726
+ let fileGitStatus = null;
727
+ if (!isDir && gitInfo.status) {
728
+ fileGitStatus = gitInfo.status.staged?.find(f => f.path === item.path) ||
729
+ gitInfo.status.unstaged?.find(f => f.path === item.path) ||
730
+ gitInfo.status.untracked?.find(f => f.path === item.path);
731
+ }
732
+
570
733
  return (
571
734
  <React.Fragment key={item.path}>
572
735
  <div
@@ -576,6 +739,11 @@ function LeftPanel({ files, activeFile, onFileClick }) {
576
739
  onContextMenu={(e) => handleContextMenu(e, item)}
577
740
  style={{ paddingLeft: `${indent}px` }}
578
741
  >
742
+ {fileGitStatus && (
743
+ <span className={`file-git-badge ${fileGitStatus.indexStatus !== '?' ? 'staged' : 'wt'}`}>
744
+ {fileGitStatus.indexStatus !== '?' ? fileGitStatus.indexStatus : fileGitStatus.workTreeStatus}
745
+ </span>
746
+ )}
579
747
  <span className="file-icon">
580
748
  {isDir ? (isExpanded ? '📂' : '📁') : getFileIcon(item.type)}
581
749
  </span>
@@ -1,6 +1,6 @@
1
- import React, { useMemo, useRef } from 'react';
1
+ import { useMemo, useRef } from 'react';
2
2
  import CodeMirror from '@uiw/react-codemirror';
3
- import { EditorView, Decoration, ViewPlugin, ViewUpdate } from '@codemirror/view';
3
+ import { EditorView, Decoration, ViewPlugin } from '@codemirror/view';
4
4
  import { HighlightStyle, syntaxHighlighting } from '@codemirror/language';
5
5
  import { tags as t } from '@lezer/highlight';
6
6
  import { RangeSetBuilder } from '@codemirror/state';
@@ -0,0 +1,172 @@
1
+ import { Component } from 'react';
2
+
3
+ class DefaultErrorFallback extends Component {
4
+ render() {
5
+ const { error, onReload, onReset } = this.props;
6
+
7
+ return (
8
+ <div style={{
9
+ height: '100vh',
10
+ display: 'flex',
11
+ flexDirection: 'column',
12
+ alignItems: 'center',
13
+ justifyContent: 'center',
14
+ background: '#1e1e1e',
15
+ color: '#e0e0e0',
16
+ fontFamily: '-apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif',
17
+ padding: '20px',
18
+ }}>
19
+ <div style={{
20
+ maxWidth: '600px',
21
+ padding: '32px',
22
+ background: '#252526',
23
+ borderRadius: '8px',
24
+ border: '1px solid #3c3c3c',
25
+ boxShadow: '0 4px 20px rgba(0, 0, 0, 0.4)',
26
+ textAlign: 'center',
27
+ }}>
28
+ <div style={{
29
+ fontSize: '48px',
30
+ marginBottom: '16px',
31
+ }}>
32
+ <span style={{ color: '#f85149' }}>!</span>
33
+ </div>
34
+
35
+ <h1 style={{
36
+ fontSize: '24px',
37
+ fontWeight: 600,
38
+ marginBottom: '8px',
39
+ color: '#ffffff',
40
+ }}>
41
+ 应用程序出错
42
+ </h1>
43
+
44
+ <p style={{
45
+ fontSize: '14px',
46
+ color: '#858585',
47
+ marginBottom: '24px',
48
+ }}>
49
+ 抱歉,应用程序遇到了一个错误。请尝试刷新页面或重置应用状态。
50
+ </p>
51
+
52
+ {error && (
53
+ <details style={{
54
+ marginBottom: '24px',
55
+ padding: '12px',
56
+ background: '#1e1e1e',
57
+ borderRadius: '4px',
58
+ textAlign: 'left',
59
+ }}>
60
+ <summary style={{
61
+ cursor: 'pointer',
62
+ fontSize: '13px',
63
+ color: '#e0e0e0',
64
+ fontWeight: 500,
65
+ }}>
66
+ 错误详情
67
+ </summary>
68
+ <pre style={{
69
+ marginTop: '12px',
70
+ padding: '12px',
71
+ background: '#0a0a0a',
72
+ borderRadius: '4px',
73
+ fontSize: '12px',
74
+ color: '#f48771',
75
+ overflow: 'auto',
76
+ maxHeight: '200px',
77
+ fontFamily: '"SF Mono", Consolas, monospace',
78
+ }}>
79
+ {error.toString()}
80
+ {error.stack && `\n\n${error.stack}`}
81
+ </pre>
82
+ </details>
83
+ )}
84
+
85
+ <div style={{
86
+ display: 'flex',
87
+ gap: '12px',
88
+ justifyContent: 'center',
89
+ }}>
90
+ <button
91
+ onClick={onReset}
92
+ style={{
93
+ padding: '10px 20px',
94
+ background: '#3c3c3c',
95
+ border: '1px solid #4c4c4c',
96
+ borderRadius: '4px',
97
+ color: '#cccccc',
98
+ fontSize: '14px',
99
+ cursor: 'pointer',
100
+ }}
101
+ >
102
+ 重置应用
103
+ </button>
104
+ <button
105
+ onClick={onReload}
106
+ style={{
107
+ padding: '10px 20px',
108
+ background: '#0078d4',
109
+ border: 'none',
110
+ borderRadius: '4px',
111
+ color: '#ffffff',
112
+ fontSize: '14px',
113
+ cursor: 'pointer',
114
+ fontWeight: 500,
115
+ }}
116
+ >
117
+ 刷新页面
118
+ </button>
119
+ </div>
120
+ </div>
121
+ </div>
122
+ );
123
+ }
124
+ }
125
+
126
+ export class ErrorBoundary extends Component {
127
+ constructor(props) {
128
+ super(props);
129
+ this.state = {
130
+ hasError: false,
131
+ error: null,
132
+ errorInfo: null,
133
+ };
134
+ }
135
+
136
+ static getDerivedStateFromError(error) {
137
+ return { hasError: true, error };
138
+ }
139
+
140
+ componentDidCatch(error, errorInfo) {
141
+ this.setState({ errorInfo });
142
+ this.props.onError?.(error, errorInfo);
143
+ console.error('[ErrorBoundary]', error, errorInfo);
144
+ }
145
+
146
+ handleReload = () => {
147
+ this.setState({ hasError: false, error: null, errorInfo: null });
148
+ window.location.reload();
149
+ };
150
+
151
+ handleReset = () => {
152
+ this.setState({ hasError: false, error: null, errorInfo: null });
153
+ };
154
+
155
+ render() {
156
+ if (this.state.hasError) {
157
+ if (this.props.fallback) {
158
+ return this.props.fallback;
159
+ }
160
+ return (
161
+ <DefaultErrorFallback
162
+ error={this.state.error}
163
+ errorInfo={this.state.errorInfo}
164
+ onReload={this.handleReload}
165
+ onReset={this.handleReset}
166
+ />
167
+ );
168
+ }
169
+
170
+ return this.props.children;
171
+ }
172
+ }