codex-lens 0.1.24 → 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-fEtdvo0X.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.24",
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
  });
@@ -1,4 +1,4 @@
1
- import React, { useState, useEffect, useRef, useCallback } from 'react';
1
+ import React, { useState, useEffect, useRef } from 'react';
2
2
  import { TerminalPanel } from './TerminalPanel';
3
3
  import { CodeViewer } from './CodeViewer';
4
4
 
@@ -12,17 +12,8 @@ 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
-
19
- useEffect(() => {
20
- activeTabIdRef.current = activeTabId;
21
- }, [activeTabId]);
22
-
23
- useEffect(() => {
24
- tabsRef.current = tabs;
25
- }, [tabs]);
26
17
 
27
18
  useEffect(() => {
28
19
  fetchStatus();
@@ -30,23 +21,14 @@ export function App() {
30
21
  document.addEventListener('click', handleDocumentClick);
31
22
  document.addEventListener('keydown', handleKeyDown);
32
23
 
33
- const preventBrowserSave = (e) => {
34
- if (e.ctrlKey && e.key === 's') {
35
- e.preventDefault();
36
- e.stopPropagation();
37
- }
38
- };
39
- window.addEventListener('keydown', preventBrowserSave, true);
40
-
41
24
  return () => {
42
25
  document.removeEventListener('click', handleDocumentClick);
43
26
  document.removeEventListener('keydown', handleKeyDown);
44
- window.removeEventListener('keydown', preventBrowserSave, true);
45
27
  if (wsRef.current) {
46
28
  wsRef.current.close();
47
29
  }
48
30
  };
49
- }, [handleKeyDown]);
31
+ }, []);
50
32
 
51
33
  async function fetchStatus() {
52
34
  try {
@@ -72,56 +54,12 @@ export function App() {
72
54
  setContextMenu(null);
73
55
  }
74
56
 
75
- const saveCurrentFile = useCallback(() => {
76
- const currentTabId = activeTabIdRef.current;
77
- const currentTabs = tabsRef.current;
78
- const activeTab = currentTabs.find(t => t.id === currentTabId);
79
-
80
- if (!activeTab || activeTab.isDiff) {
81
- console.log('No active tab or is diff, skip save');
82
- return;
83
- }
84
-
85
- const port = window.location.port === '5173' ? '5174' : window.location.port;
86
- const protocol = window.location.protocol === 'https:' ? 'https:' : 'http:';
87
-
88
- console.log('Saving file:', activeTab.path);
89
-
90
- fetch(`${protocol}//${window.location.hostname}:${port}/api/save-file`, {
91
- method: 'POST',
92
- headers: { 'Content-Type': 'application/json' },
93
- body: JSON.stringify({ path: activeTab.path, content: activeTab.content })
94
- })
95
- .then(response => {
96
- if (response.ok) {
97
- console.log('File saved successfully');
98
- setTabs(prevTabs => prevTabs.map(t =>
99
- t.id === currentTabId ? { ...t, isModified: false } : t
100
- ));
101
- } else {
102
- response.json().then(error => {
103
- console.error('Failed to save file:', error);
104
- });
105
- }
106
- })
107
- .catch(error => {
108
- console.error('Failed to save file:', error);
109
- });
110
- }, []);
111
-
112
- const handleKeyDown = useCallback((e) => {
113
- if (e.ctrlKey && e.key === 's') {
114
- e.preventDefault();
115
- console.log('Ctrl+S pressed, activeTabId:', activeTabIdRef.current);
116
- saveCurrentFile();
117
- return;
118
- }
119
-
120
- if (!activeTabIdRef.current) return;
57
+ function handleKeyDown(e) {
58
+ if (!activeTabId) return;
121
59
 
122
60
  if (e.ctrlKey && e.key === 'w') {
123
61
  e.preventDefault();
124
- closeTab(activeTabIdRef.current);
62
+ closeTab(activeTabId);
125
63
  } else if (e.ctrlKey && e.key === 'Tab') {
126
64
  e.preventDefault();
127
65
  if (e.shiftKey) {
@@ -129,16 +67,13 @@ export function App() {
129
67
  } else {
130
68
  switchToNextTab();
131
69
  }
132
- }
133
- }, [saveCurrentFile]);
134
-
135
- function handleContentChange(newContent) {
136
- setTabs(prevTabs => prevTabs.map(t => {
137
- if (t.id === activeTabId) {
138
- return { ...t, content: newContent, isModified: true };
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);
139
75
  }
140
- return t;
141
- }));
76
+ }
142
77
  }
143
78
 
144
79
  function connectWebSocket() {
@@ -210,8 +145,10 @@ export function App() {
210
145
  path: data.path,
211
146
  name: fileName,
212
147
  content: data.content,
148
+ originalContent: data.content,
213
149
  diff: data.diff || null,
214
- isDiff: !!data.diff
150
+ isDiff: !!data.diff,
151
+ modified: false
215
152
  };
216
153
  setActiveTabId(newTab.id);
217
154
  return [...prevTabs, newTab];
@@ -226,7 +163,14 @@ export function App() {
226
163
  const existingTab = prevTabs.find(t => t.path === data.path);
227
164
 
228
165
  if (existingTab) {
229
- 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
+ };
230
174
  setActiveTabId(existingTab.id);
231
175
  return prevTabs.map(t =>
232
176
  t.path === data.path ? updatedTab : t
@@ -237,8 +181,10 @@ export function App() {
237
181
  path: data.path,
238
182
  name: fileName,
239
183
  content: data.newContent,
184
+ originalContent: data.newContent,
240
185
  diff: data.diff,
241
- isDiff: true
186
+ isDiff: true,
187
+ modified: false
242
188
  };
243
189
  setActiveTabId(newTab.id);
244
190
  return [...prevTabs, newTab];
@@ -246,6 +192,51 @@ export function App() {
246
192
  });
247
193
  }
248
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
+
249
240
  function handleFileClick(path) {
250
241
  const existingTab = tabs.find(t => t.path === path);
251
242
  if (existingTab) {
@@ -258,6 +249,14 @@ export function App() {
258
249
  }
259
250
 
260
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
+
261
260
  setTabs(prev => {
262
261
  const newTabs = prev.filter(t => t.id !== tabId);
263
262
  if (activeTabId === tabId) {
@@ -269,11 +268,30 @@ export function App() {
269
268
  }
270
269
 
271
270
  function closeOtherTabs(tabId) {
272
- 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
+ });
273
283
  setActiveTabId(tabId);
274
284
  }
275
285
 
276
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
+ });
277
295
  setTabs([]);
278
296
  setActiveTabId(null);
279
297
  }
@@ -354,8 +372,7 @@ export function App() {
354
372
  diff={activeTab.diff}
355
373
  isDiff={activeTab.isDiff}
356
374
  filePath={activeTab.path}
357
- onChange={handleContentChange}
358
- isModified={activeTab.isModified}
375
+ onChange={(value) => handleContentChange(activeTabId, value)}
359
376
  />
360
377
  )}
361
378
  </div>
@@ -364,12 +381,9 @@ export function App() {
364
381
  <ContextMenu
365
382
  x={contextMenu.x}
366
383
  y={contextMenu.y}
367
- tabId={contextMenu.tabId}
384
+ tab={tabs.find(t => t.id === contextMenu.tabId)}
385
+ saving={saving}
368
386
  onClose={() => setContextMenu(null)}
369
- onSave={() => {
370
- saveCurrentFile();
371
- setContextMenu(null);
372
- }}
373
387
  onCloseTab={() => {
374
388
  closeTab(contextMenu.tabId);
375
389
  setContextMenu(null);
@@ -382,6 +396,10 @@ export function App() {
382
396
  closeAllTabs();
383
397
  setContextMenu(null);
384
398
  }}
399
+ onSave={() => {
400
+ saveFile(contextMenu.tabId);
401
+ setContextMenu(null);
402
+ }}
385
403
  />
386
404
  )}
387
405
  <div className="panel right-panel">
@@ -400,12 +418,12 @@ function TabBar({ tabs, activeTabId, onTabClick, onTabClose, onContextMenu }) {
400
418
  {tabs.map(tab => (
401
419
  <div
402
420
  key={tab.id}
403
- className={`tab ${activeTabId === tab.id ? 'active' : ''}`}
421
+ className={`tab ${activeTabId === tab.id ? 'active' : ''} ${tab.modified ? 'modified' : ''}`}
404
422
  onClick={() => onTabClick(tab.id)}
405
423
  onContextMenu={(e) => onContextMenu(e, tab.id)}
406
424
  >
407
425
  <span className="tab-name">
408
- {tab.isModified && <span className="tab-modified">*</span>}
426
+ {tab.modified && <span className="tab-modified-mark">●</span>}
409
427
  {tab.name}
410
428
  </span>
411
429
  <button
@@ -423,14 +441,14 @@ function TabBar({ tabs, activeTabId, onTabClick, onTabClose, onContextMenu }) {
423
441
  );
424
442
  }
425
443
 
426
- function ContextMenu({ x, y, tabId, onClose, onSave, onCloseTab, onCloseOtherTabs, onCloseAllTabs }) {
427
- const tabs = tabsRef.current;
428
- const tab = tabs.find(t => t.id === tabId);
429
- const canSave = tab && !tab.isDiff;
430
-
444
+ function ContextMenu({ x, y, tab, saving, onClose, onCloseTab, onCloseOtherTabs, onCloseAllTabs, onSave }) {
431
445
  return (
432
446
  <div className="context-menu" style={{ left: x, top: y }} onClick={(e) => e.stopPropagation()}>
433
- {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
+ )}
434
452
  <div className="context-menu-item" onClick={onCloseTab}>关闭</div>
435
453
  <div className="context-menu-item" onClick={onCloseOtherTabs}>关闭其他</div>
436
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%);