codex-lens 0.1.25 → 0.1.26

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-modified{color:var(--accent-color);font-weight:700;margin-right:2px}.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}.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}@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-DJ9sK-1n.js"></script>
8
- <link rel="stylesheet" crossorigin href="./assets/main-CYNmzqDG.css">
7
+ <script type="module" crossorigin src="./assets/main-BO4694Xj.js"></script>
8
+ <link rel="stylesheet" crossorigin href="./assets/main-DNXrKVO-.css">
9
9
  </head>
10
10
  <body>
11
11
  <div id="root"></div>
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "codex-lens",
3
- "version": "0.1.25",
3
+ "version": "0.1.26",
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
@@ -121,7 +121,7 @@ class Aggregator {
121
121
  logger.info(`File saved: ${filePath}`);
122
122
  res.json({ success: true, path: filePath });
123
123
  } catch (error) {
124
- logger.error(`Failed to save file: ${filePath} - ${error.message}`);
124
+ logger.error(`Failed to save file: ${error.message}`);
125
125
  res.status(500).json({ error: error.message });
126
126
  }
127
127
  });
@@ -12,59 +12,23 @@ export function App() {
12
12
  const [latestVersion, setLatestVersion] = useState(null);
13
13
  const [hasUpdate, setHasUpdate] = useState(false);
14
14
  const [projectName, setProjectName] = useState('');
15
+ const [saving, setSaving] = useState(false);
15
16
  const wsRef = useRef(null);
16
- const activeTabIdRef = useRef(null);
17
- const tabsRef = useRef([]);
18
- const saveCurrentFileRef = useRef(null);
19
17
 
20
18
  useEffect(() => {
21
- activeTabIdRef.current = activeTabId;
22
- }, [activeTabId]);
23
-
24
- useEffect(() => {
25
- tabsRef.current = tabs;
26
- }, [tabs]);
27
-
28
- function saveCurrentFile() {
29
- const currentTabId = activeTabIdRef.current;
30
- const currentTabs = tabsRef.current;
31
- const activeTab = currentTabs.find(t => t.id === currentTabId);
32
-
33
- if (!activeTab || activeTab.isDiff) {
34
- console.log('No active tab or is diff, skip save');
35
- return;
36
- }
37
-
38
- const port = window.location.port === '5173' ? '5174' : window.location.port;
39
- const protocol = window.location.protocol === 'https:' ? 'https:' : 'http:';
40
-
41
- console.log('Saving file:', activeTab.path);
42
-
43
- fetch(`${protocol}//${window.location.hostname}:${port}/api/save-file`, {
44
- method: 'POST',
45
- headers: { 'Content-Type': 'application/json' },
46
- body: JSON.stringify({ path: activeTab.path, content: activeTab.content })
47
- })
48
- .then(response => {
49
- if (response.ok) {
50
- console.log('File saved successfully');
51
- setTabs(prevTabs => prevTabs.map(t =>
52
- t.id === currentTabId ? { ...t, isModified: false } : t
53
- ));
54
- } else {
55
- response.json().then(error => {
56
- console.error('Failed to save file:', error);
57
- });
58
- }
59
- })
60
- .catch(error => {
61
- console.error('Failed to save file:', error);
62
- });
63
- }
19
+ fetchStatus();
20
+ connectWebSocket();
21
+ document.addEventListener('click', handleDocumentClick);
22
+ document.addEventListener('keydown', handleKeyDown);
64
23
 
65
- useEffect(() => {
66
- saveCurrentFileRef.current = saveCurrentFile;
67
- });
24
+ return () => {
25
+ document.removeEventListener('click', handleDocumentClick);
26
+ document.removeEventListener('keydown', handleKeyDown);
27
+ if (wsRef.current) {
28
+ wsRef.current.close();
29
+ }
30
+ };
31
+ }, []);
68
32
 
69
33
  async function fetchStatus() {
70
34
  try {
@@ -90,63 +54,26 @@ export function App() {
90
54
  setContextMenu(null);
91
55
  }
92
56
 
93
- useEffect(() => {
94
- fetchStatus();
95
- connectWebSocket();
57
+ function handleKeyDown(e) {
58
+ if (!activeTabId) return;
96
59
 
97
- function handleKeyDown(e) {
98
- if (e.ctrlKey && e.key === 's') {
99
- e.preventDefault();
100
- console.log('Ctrl+S pressed, activeTabId:', activeTabIdRef.current);
101
- if (saveCurrentFileRef.current) {
102
- saveCurrentFileRef.current();
103
- }
104
- return;
60
+ if (e.ctrlKey && e.key === 'w') {
61
+ e.preventDefault();
62
+ closeTab(activeTabId);
63
+ } else if (e.ctrlKey && e.key === 'Tab') {
64
+ e.preventDefault();
65
+ if (e.shiftKey) {
66
+ switchToPrevTab();
67
+ } else {
68
+ switchToNextTab();
105
69
  }
106
-
107
- if (!activeTabIdRef.current) return;
108
-
109
- if (e.ctrlKey && e.key === 'w') {
110
- e.preventDefault();
111
- closeTab(activeTabIdRef.current);
112
- } else if (e.ctrlKey && e.key === 'Tab') {
113
- e.preventDefault();
114
- if (e.shiftKey) {
115
- switchToPrevTab();
116
- } else {
117
- switchToNextTab();
118
- }
70
+ } else if (e.ctrlKey && e.key === 's') {
71
+ e.preventDefault();
72
+ const activeTab = tabs.find(t => t.id === activeTabId);
73
+ if (activeTab && activeTab.modified) {
74
+ saveFile(activeTabId);
119
75
  }
120
76
  }
121
-
122
- document.addEventListener('click', handleDocumentClick);
123
- document.addEventListener('keydown', handleKeyDown);
124
-
125
- const preventBrowserSave = (e) => {
126
- if (e.ctrlKey && e.key === 's') {
127
- e.preventDefault();
128
- e.stopPropagation();
129
- }
130
- };
131
- window.addEventListener('keydown', preventBrowserSave, true);
132
-
133
- return () => {
134
- document.removeEventListener('click', handleDocumentClick);
135
- document.removeEventListener('keydown', handleKeyDown);
136
- window.removeEventListener('keydown', preventBrowserSave, true);
137
- if (wsRef.current) {
138
- wsRef.current.close();
139
- }
140
- };
141
- }, []);
142
-
143
- function handleContentChange(newContent) {
144
- setTabs(prevTabs => prevTabs.map(t => {
145
- if (t.id === activeTabId) {
146
- return { ...t, content: newContent, isModified: true };
147
- }
148
- return t;
149
- }));
150
77
  }
151
78
 
152
79
  function connectWebSocket() {
@@ -218,8 +145,10 @@ export function App() {
218
145
  path: data.path,
219
146
  name: fileName,
220
147
  content: data.content,
148
+ originalContent: data.content,
221
149
  diff: data.diff || null,
222
- isDiff: !!data.diff
150
+ isDiff: !!data.diff,
151
+ modified: false
223
152
  };
224
153
  setActiveTabId(newTab.id);
225
154
  return [...prevTabs, newTab];
@@ -234,7 +163,14 @@ export function App() {
234
163
  const existingTab = prevTabs.find(t => t.path === data.path);
235
164
 
236
165
  if (existingTab) {
237
- const updatedTab = { ...existingTab, content: data.newContent, diff: data.diff, isDiff: true };
166
+ const updatedTab = {
167
+ ...existingTab,
168
+ content: data.newContent,
169
+ originalContent: data.newContent,
170
+ diff: data.diff,
171
+ isDiff: true,
172
+ modified: false
173
+ };
238
174
  setActiveTabId(existingTab.id);
239
175
  return prevTabs.map(t =>
240
176
  t.path === data.path ? updatedTab : t
@@ -245,8 +181,10 @@ export function App() {
245
181
  path: data.path,
246
182
  name: fileName,
247
183
  content: data.newContent,
184
+ originalContent: data.newContent,
248
185
  diff: data.diff,
249
- isDiff: true
186
+ isDiff: true,
187
+ modified: false
250
188
  };
251
189
  setActiveTabId(newTab.id);
252
190
  return [...prevTabs, newTab];
@@ -254,6 +192,51 @@ export function App() {
254
192
  });
255
193
  }
256
194
 
195
+ function handleContentChange(tabId, newContent) {
196
+ setTabs(prevTabs => prevTabs.map(tab => {
197
+ if (tab.id === tabId) {
198
+ const modified = newContent !== tab.originalContent;
199
+ return { ...tab, content: newContent, modified };
200
+ }
201
+ return tab;
202
+ }));
203
+ }
204
+
205
+ async function saveFile(tabId) {
206
+ const tab = tabs.find(t => t.id === tabId);
207
+ if (!tab || !tab.modified) return;
208
+
209
+ setSaving(true);
210
+ try {
211
+ const port = window.location.port === '5173' ? '5174' : window.location.port;
212
+ const protocol = window.location.protocol === 'https:' ? 'https:' : 'http:';
213
+ const response = await fetch(`${protocol}//${window.location.hostname}:${port}/api/save-file`, {
214
+ method: 'POST',
215
+ headers: { 'Content-Type': 'application/json' },
216
+ body: JSON.stringify({ path: tab.path, content: tab.content })
217
+ });
218
+
219
+ if (response.ok) {
220
+ setTabs(prevTabs => prevTabs.map(t => {
221
+ if (t.id === tabId) {
222
+ return { ...t, originalContent: t.content, modified: false };
223
+ }
224
+ return t;
225
+ }));
226
+ console.log('File saved:', tab.path);
227
+ } else {
228
+ const error = await response.json();
229
+ console.error('Failed to save file:', error.message);
230
+ alert('保存失败: ' + error.message);
231
+ }
232
+ } catch (error) {
233
+ console.error('Failed to save file:', error);
234
+ alert('保存失败: ' + error.message);
235
+ } finally {
236
+ setSaving(false);
237
+ }
238
+ }
239
+
257
240
  function handleFileClick(path) {
258
241
  const existingTab = tabs.find(t => t.path === path);
259
242
  if (existingTab) {
@@ -266,10 +249,17 @@ export function App() {
266
249
  }
267
250
 
268
251
  function closeTab(tabId) {
252
+ const tab = tabs.find(t => t.id === tabId);
253
+ if (tab?.modified) {
254
+ const confirmed = window.confirm(`文件 "${tab.name}" 已修改,是否保存?`);
255
+ if (confirmed) {
256
+ saveFile(tabId);
257
+ }
258
+ }
259
+
269
260
  setTabs(prev => {
270
261
  const newTabs = prev.filter(t => t.id !== tabId);
271
- const currentActiveId = activeTabIdRef.current;
272
- if (currentActiveId === tabId) {
262
+ if (activeTabId === tabId) {
273
263
  const newActiveId = newTabs.length > 0 ? newTabs[newTabs.length - 1].id : null;
274
264
  setActiveTabId(newActiveId);
275
265
  }
@@ -278,11 +268,30 @@ export function App() {
278
268
  }
279
269
 
280
270
  function closeOtherTabs(tabId) {
281
- setTabs(prev => prev.filter(t => t.id === tabId));
271
+ setTabs(prev => {
272
+ const tabsToClose = prev.filter(t => t.id !== tabId);
273
+ tabsToClose.forEach(t => {
274
+ if (t.modified) {
275
+ const confirmed = window.confirm(`文件 "${t.name}" 已修改,是否保存?`);
276
+ if (confirmed) {
277
+ saveFile(t.id);
278
+ }
279
+ }
280
+ });
281
+ return prev.filter(t => t.id === tabId);
282
+ });
282
283
  setActiveTabId(tabId);
283
284
  }
284
285
 
285
286
  function closeAllTabs() {
287
+ tabs.forEach(t => {
288
+ if (t.modified) {
289
+ const confirmed = window.confirm(`文件 "${t.name}" 已修改,是否保存?`);
290
+ if (confirmed) {
291
+ saveFile(t.id);
292
+ }
293
+ }
294
+ });
286
295
  setTabs([]);
287
296
  setActiveTabId(null);
288
297
  }
@@ -292,24 +301,20 @@ export function App() {
292
301
  }
293
302
 
294
303
  function switchToNextTab() {
295
- const currentTabs = tabsRef.current;
296
- const currentActiveId = activeTabIdRef.current;
297
- const currentIndex = currentTabs.findIndex(t => t.id === currentActiveId);
298
- if (currentIndex < currentTabs.length - 1) {
299
- setActiveTabId(currentTabs[currentIndex + 1].id);
300
- } else if (currentTabs.length > 0) {
301
- setActiveTabId(currentTabs[0].id);
304
+ const currentIndex = tabs.findIndex(t => t.id === activeTabId);
305
+ if (currentIndex < tabs.length - 1) {
306
+ setActiveTabId(tabs[currentIndex + 1].id);
307
+ } else if (tabs.length > 0) {
308
+ setActiveTabId(tabs[0].id);
302
309
  }
303
310
  }
304
311
 
305
312
  function switchToPrevTab() {
306
- const currentTabs = tabsRef.current;
307
- const currentActiveId = activeTabIdRef.current;
308
- const currentIndex = currentTabs.findIndex(t => t.id === currentActiveId);
313
+ const currentIndex = tabs.findIndex(t => t.id === activeTabId);
309
314
  if (currentIndex > 0) {
310
- setActiveTabId(currentTabs[currentIndex - 1].id);
311
- } else if (currentTabs.length > 0) {
312
- setActiveTabId(currentTabs[currentTabs.length - 1].id);
315
+ setActiveTabId(tabs[currentIndex - 1].id);
316
+ } else if (tabs.length > 0) {
317
+ setActiveTabId(tabs[tabs.length - 1].id);
313
318
  }
314
319
  }
315
320
 
@@ -367,8 +372,7 @@ export function App() {
367
372
  diff={activeTab.diff}
368
373
  isDiff={activeTab.isDiff}
369
374
  filePath={activeTab.path}
370
- onChange={handleContentChange}
371
- isModified={activeTab.isModified}
375
+ onChange={(value) => handleContentChange(activeTabId, value)}
372
376
  />
373
377
  )}
374
378
  </div>
@@ -377,12 +381,9 @@ export function App() {
377
381
  <ContextMenu
378
382
  x={contextMenu.x}
379
383
  y={contextMenu.y}
380
- tabId={contextMenu.tabId}
384
+ tab={tabs.find(t => t.id === contextMenu.tabId)}
385
+ saving={saving}
381
386
  onClose={() => setContextMenu(null)}
382
- onSave={() => {
383
- saveCurrentFile();
384
- setContextMenu(null);
385
- }}
386
387
  onCloseTab={() => {
387
388
  closeTab(contextMenu.tabId);
388
389
  setContextMenu(null);
@@ -395,6 +396,10 @@ export function App() {
395
396
  closeAllTabs();
396
397
  setContextMenu(null);
397
398
  }}
399
+ onSave={() => {
400
+ saveFile(contextMenu.tabId);
401
+ setContextMenu(null);
402
+ }}
398
403
  />
399
404
  )}
400
405
  <div className="panel right-panel">
@@ -413,12 +418,12 @@ function TabBar({ tabs, activeTabId, onTabClick, onTabClose, onContextMenu }) {
413
418
  {tabs.map(tab => (
414
419
  <div
415
420
  key={tab.id}
416
- className={`tab ${activeTabId === tab.id ? 'active' : ''}`}
421
+ className={`tab ${activeTabId === tab.id ? 'active' : ''} ${tab.modified ? 'modified' : ''}`}
417
422
  onClick={() => onTabClick(tab.id)}
418
423
  onContextMenu={(e) => onContextMenu(e, tab.id)}
419
424
  >
420
425
  <span className="tab-name">
421
- {tab.isModified && <span className="tab-modified">*</span>}
426
+ {tab.modified && <span className="tab-modified-mark">●</span>}
422
427
  {tab.name}
423
428
  </span>
424
429
  <button
@@ -436,14 +441,14 @@ function TabBar({ tabs, activeTabId, onTabClick, onTabClose, onContextMenu }) {
436
441
  );
437
442
  }
438
443
 
439
- function ContextMenu({ x, y, tabId, onClose, onSave, onCloseTab, onCloseOtherTabs, onCloseAllTabs }) {
440
- const tabs = tabsRef.current;
441
- const tab = tabs.find(t => t.id === tabId);
442
- const canSave = tab && !tab.isDiff;
443
-
444
+ function ContextMenu({ x, y, tab, saving, onClose, onCloseTab, onCloseOtherTabs, onCloseAllTabs, onSave }) {
444
445
  return (
445
446
  <div className="context-menu" style={{ left: x, top: y }} onClick={(e) => e.stopPropagation()}>
446
- {canSave && <div className="context-menu-item" onClick={onSave}>保存</div>}
447
+ {tab?.modified && (
448
+ <div className="context-menu-item" onClick={onSave} style={{ color: '#4ade80' }}>
449
+ {saving ? '保存中...' : '保存'}
450
+ </div>
451
+ )}
447
452
  <div className="context-menu-item" onClick={onCloseTab}>关闭</div>
448
453
  <div className="context-menu-item" onClick={onCloseOtherTabs}>关闭其他</div>
449
454
  <div className="context-menu-item" onClick={onCloseAllTabs}>关闭所有</div>
@@ -1,6 +1,6 @@
1
- import React, { useMemo, useRef, useState, useCallback } from 'react';
1
+ import React, { useMemo, useRef } from 'react';
2
2
  import CodeMirror from '@uiw/react-codemirror';
3
- import { EditorView, Decoration, ViewPlugin, keymap } from '@codemirror/view';
3
+ import { EditorView, Decoration, ViewPlugin, ViewUpdate } 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';
@@ -215,11 +215,8 @@ const darkHighlightStyle = HighlightStyle.define([
215
215
 
216
216
  const syntaxTheme = syntaxHighlighting(darkHighlightStyle);
217
217
 
218
- export function CodeViewer({ content, diff, isDiff, filePath, onChange, isModified }) {
218
+ export function CodeViewer({ content, diff, isDiff, filePath, onChange }) {
219
219
  const editorRef = useRef(null);
220
- const [localContent, setLocalContent] = useState(content || '');
221
-
222
- const isEditable = !isDiff;
223
220
 
224
221
  const extensions = useMemo(() => {
225
222
  const exts = [
@@ -257,27 +254,23 @@ export function CodeViewer({ content, diff, isDiff, filePath, onChange, isModifi
257
254
  return content || '';
258
255
  }, [content, diff, isDiff]);
259
256
 
260
- const handleChange = useCallback((value) => {
261
- setLocalContent(value);
262
- if (onChange) {
263
- onChange(value);
264
- }
265
- }, [onChange]);
257
+ const editable = !isDiff;
266
258
 
267
- React.useEffect(() => {
268
- if (!isDiff) {
269
- setLocalContent(content || '');
259
+ function handleChange(value) {
260
+ if (onChange && editable) {
261
+ onChange(value);
270
262
  }
271
- }, [content, isDiff]);
263
+ }
272
264
 
273
265
  return (
274
266
  <div className="code-viewer-codemirror">
275
267
  <CodeMirror
276
- value={isDiff ? code : localContent}
268
+ value={code}
277
269
  height="100%"
278
270
  theme={darkTheme}
279
271
  extensions={extensions}
280
- editable={isEditable}
272
+ editable={editable}
273
+ onChange={handleChange}
281
274
  basicSetup={{
282
275
  lineNumbers: true,
283
276
  foldGutter: false,
@@ -285,7 +278,6 @@ export function CodeViewer({ content, diff, isDiff, filePath, onChange, isModifi
285
278
  highlightSelectionMatches: false,
286
279
  bracketMatching: true,
287
280
  }}
288
- onChange={isEditable ? handleChange : undefined}
289
281
  onCreateEditor={(view) => {
290
282
  editorRef.current = view;
291
283
  }}
package/src/global.css CHANGED
@@ -357,12 +357,6 @@ body {
357
357
  white-space: nowrap;
358
358
  }
359
359
 
360
- .tab-modified {
361
- color: var(--accent-color);
362
- font-weight: bold;
363
- margin-right: 2px;
364
- }
365
-
366
360
  .tab-close {
367
361
  background: none;
368
362
  border: none;
@@ -382,6 +376,16 @@ body {
382
376
  opacity: 1;
383
377
  }
384
378
 
379
+ .tab.modified .tab-name {
380
+ color: var(--accent-color);
381
+ }
382
+
383
+ .tab-modified-mark {
384
+ color: var(--accent-color);
385
+ margin-right: 4px;
386
+ font-size: 10px;
387
+ }
388
+
385
389
  .context-menu {
386
390
  position: fixed;
387
391
  background: linear-gradient(180deg, var(--bg-tertiary) 0%, var(--bg-secondary) 100%);