megit-app 0.5.0 → 0.5.2

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.
@@ -0,0 +1 @@
1
+ @font-face{font-family:Ubuntu Mono;font-style:normal;font-weight:400;font-display:swap;src:url(/fonts/ubuntu-mono-400.woff2)format("woff2")}@font-face{font-family:Ubuntu Mono;font-style:normal;font-weight:700;font-display:swap;src:url(/fonts/ubuntu-mono-700.woff2)format("woff2")}@font-face{font-family:Ubuntu Mono;font-style:italic;font-weight:400;font-display:swap;src:url(/fonts/ubuntu-mono-400-italic.woff2)format("woff2")}:root{--bg:#1e2228;--bg-panel:#23272e;--bg-tabbar:#14171c;--bg-tab:#1a1e24;--bg-hover:#2c313a;--bg-selected:#354254;--bg-wip:#2a2e25;--border:#3a404b;--fg:#d4d8de;--fg-dim:#9aa4b2;--fg-faint:#6b7280;--head-bg:#2e4436;--head-bg-hover:#3d5a47;--head-fg:#a8d99a}:root[data-theme=light]{--bg:#f6f8fa;--bg-panel:#fff;--bg-tabbar:#e4e7eb;--bg-tab:#eef0f3;--bg-hover:#e8eaed;--bg-selected:#cfdff5;--bg-wip:#f3f5e8;--border:#d0d7de;--fg:#1f2328;--fg-dim:#57606a;--fg-faint:#8b949e;--head-bg:#d3f2d9;--head-bg-hover:#b4e7c0;--head-fg:#116329}*{box-sizing:border-box}html,body,#root{height:100%;margin:0}body{background:var(--bg);color:var(--fg);font:16px/1.4 Ubuntu Mono,ui-monospace,monospace}button{background:var(--bg-hover);color:inherit;border:1px solid var(--border);cursor:pointer;border-radius:4px}button:hover{background:var(--border)}.app{flex-direction:column;height:100%;display:flex}.empty{color:var(--fg-faint);flex:1;justify-content:center;align-items:center;display:flex}.error{color:#e06c75;white-space:pre-wrap;padding:8px}.toasts{z-index:20;flex-direction:column;gap:6px;max-width:380px;display:flex;position:fixed;bottom:12px;right:12px}.toast{background:var(--bg-panel);color:var(--fg);white-space:pre-wrap;cursor:pointer;border:1px solid #e06c75;border-left-width:3px;border-radius:6px;padding:8px 10px;animation:.15s ease-out appear;box-shadow:0 2px 8px #0000004d}.tabbar{background:var(--bg-tabbar);flex-shrink:0;gap:2px;padding:6px 6px 0;display:flex}.logo{pointer-events:none;align-self:center;width:18px;height:18px;margin:0 4px 6px 2px}.tab{background:var(--bg-tab);cursor:pointer;border-radius:6px 6px 0 0;align-items:center;gap:6px;padding:6px 10px;display:flex}.tab.active{background:var(--bg-panel)}.tab.dragging{opacity:.5}.tab-close{width:18px;height:18px;color:var(--fg-dim);background:0 0;border:none;border-radius:50%;flex-shrink:0;justify-content:center;align-items:center;margin:-2px -4px -2px 0;padding:0;display:flex}.tab-close:hover{background:var(--border);color:var(--fg)}.tab-add{background:0 0;border:none;padding:6px 12px;font-size:15px}.build-tag{color:var(--fg-faint);-webkit-user-select:none;user-select:none;align-self:center;margin:0 4px 6px auto;font-size:11px}.modal-backdrop{z-index:10;background:#00000080;justify-content:center;align-items:center;display:flex;position:fixed;inset:0}.modal{background:var(--bg-panel);border:1px solid var(--border);border-radius:8px;flex-direction:column;width:480px;max-height:70vh;display:flex}.modal-head{border-bottom:1px solid var(--border);align-items:center;gap:8px;padding:10px;display:flex}.modal-path{text-overflow:ellipsis;white-space:nowrap;color:var(--fg-dim);flex:1;overflow:hidden}.dirlist{margin:0;padding:4px;list-style:none;overflow-y:auto}.dirlist li{border-radius:4px;align-items:center;padding:4px 8px;display:flex}.dirlist li:hover{background:var(--bg-hover)}.dirname{cursor:pointer;text-overflow:ellipsis;white-space:nowrap;flex:1;overflow:hidden}.modal-label{text-transform:uppercase;letter-spacing:.5px;color:var(--fg-dim);padding:8px 12px 2px;font-size:11px}.modal-path b{color:var(--fg)}.picker{flex-direction:row;width:720px;height:440px;max-height:80vh;overflow:hidden}.picker-pane{flex-direction:column;flex:1;min-width:0;display:flex}.picker-recent{border-right:1px solid var(--border);background:var(--bg-tab);flex:none;width:240px}.picker-recent .dirlist li.sel{background:var(--bg-selected)}.recent-dot{background:#2ea043;border-radius:50%;flex-shrink:0;width:6px;height:6px}.dir-chevron{color:var(--fg-faint);cursor:pointer;padding-left:8px}.repoview{background:var(--bg-panel);flex-direction:column;flex:1;min-height:0;animation:.5s ease-out appear;display:flex}@keyframes appear{0%{opacity:0}}@media (prefers-reduced-motion:reduce){.repoview{animation:none}}.toolbar{border-bottom:1px solid var(--border);flex-shrink:0;grid-template-columns:1fr auto 1fr;align-items:center;gap:10px;margin-bottom:6px;padding:6px 10px;display:grid}.tb-left{align-items:center;gap:8px;min-width:0;display:flex}.tb-right{justify-content:flex-end;align-items:center;gap:6px;display:flex}.toolbar button{padding:3px 10px}.refresh-btn{align-items:center;padding:3px 5px;display:inline-flex}.refresh-btn .spin{animation:.5s cubic-bezier(.45,0,.55,1) infinite spin}@keyframes spin{to{transform:rotate(360deg)}}.tb-actions{align-items:center;gap:2px;display:flex}.toolbar .tb-btn{color:var(--fg-dim);background:0 0;border:none;align-items:center;gap:5px;padding:4px 8px;display:inline-flex}.tb-btn:hover:not(:disabled){background:var(--bg-hover);color:var(--fg)}.tb-btn:disabled{opacity:.4;cursor:default}.tb-badge{background:var(--bg-selected);min-width:15px;color:var(--fg);text-align:center;border-radius:7px;padding:0 4px;font-size:11px;line-height:15px}.repo-path{min-width:0;color:var(--fg-dim);text-overflow:ellipsis;white-space:nowrap;overflow:hidden}.github-link{color:var(--fg-dim);flex-shrink:0;align-items:center;display:inline-flex}.github-link:hover{color:var(--fg)}.term-btn{color:var(--fg-dim);align-items:center;padding:3px 5px;display:inline-flex}.term-btn:hover{color:var(--fg)}.term-btn.active{color:var(--fg);background:var(--bg-selected)}.term-panel{border-top:1px solid var(--border);background:var(--bg);flex-direction:column;flex-shrink:0;height:220px;display:flex;position:relative}.term-resizer{cursor:ns-resize;touch-action:none;z-index:3;height:6px;position:absolute;top:-3px;left:0;right:0}.term-resizer:hover,.term-resizer:active{background:#5a80b0}.term-head{flex-shrink:0;justify-content:space-between;align-items:center;padding:3px 6px 3px 8px;display:flex}.term-title{color:var(--fg-dim);align-items:center;gap:6px;font-size:12px;display:inline-flex}.term-head-actions{align-items:center;gap:2px;display:inline-flex}.term-close{color:var(--fg-dim);background:0 0;border:none;align-items:center;padding:0 4px;font-size:13px;display:inline-flex}.term-close:hover{color:var(--fg);background:0 0}.term-close:disabled{opacity:.35}.term-panes{flex:1;min-height:0;display:flex}.term-pane{flex:1;min-width:0;display:flex;position:relative}.term-pane+.term-pane{border-left:1px solid var(--border)}.term-pane-kill{z-index:2;color:#0000;background:0 0;border:none;padding:0 3px;font-size:12px;line-height:1.2;position:absolute;top:0;right:4px}.term-pane:hover .term-pane-kill{color:var(--fg-dim)}.term-pane-kill:hover,.term-pane-kill:focus-visible{color:var(--fg);background:0 0}.term-host{flex:1;min-width:0;min-height:0;padding:4px 0 0 8px}.term-host .xterm{height:100%}.theme-switch{background:var(--bg-hover);border:1px solid var(--border);width:40px;height:20px;color:var(--fg-dim);cursor:pointer;border-radius:10px;flex-shrink:0;justify-content:space-between;align-items:center;padding:0 4px;display:inline-flex;position:relative}.theme-switch input{opacity:0;cursor:pointer;margin:0;position:absolute;inset:0}.theme-switch svg{position:relative}.theme-switch .knob{background:var(--fg);border-radius:50%;width:14px;height:14px;transition:left .15s;position:absolute;top:2px;left:2px}.theme-switch input:checked~.knob{left:22px}.theme-switch:has(input:focus-visible){outline-offset:1px;outline:2px solid #5a80b0}.panes{flex:1;min-height:0;display:flex}.graph-pane{width:var(--graph-w,55%);flex-direction:column;flex-shrink:0;min-width:0;display:flex;position:relative}.graphview{flex:1;padding-bottom:8px;overflow:auto}.refs,.graph-col{transition:filter .15s}.graph-pane.busy .refs,.graph-pane.busy .graph-col{filter:blur(1px);pointer-events:none}.graph-pane.busy,.graph-pane.busy .row{cursor:progress}.graphview .row:not(.wip){content-visibility:auto;contain-intrinsic-size:auto 28px}.diff-overlay{z-index:2;background:var(--bg-panel);flex-direction:column;display:flex;position:absolute;inset:0}.diff-overlay-head{border-bottom:1px solid var(--border);flex-shrink:0;align-items:center;gap:8px;padding:6px 10px;display:flex}.diff-overlay-head .file-path{flex:1}.diff-close{background:0 0;border:none;padding:0 4px;font-size:14px}.searchbar{top:8px;left:calc(var(--refs-w) + var(--graph-col-w) + 24px);z-index:3;background:var(--bg-selected);border:1px solid var(--border);border-radius:8px;align-items:center;gap:6px;padding:5px 8px;animation:.12s ease-out appear;display:flex;position:absolute;box-shadow:0 3px 12px #00000059}.searchbar input{background:var(--bg-panel);width:220px;color:var(--fg);border:1px solid var(--border);font:inherit;border-radius:5px;padding:3px 6px}.searchbar .count{text-align:center;min-width:78px;color:var(--fg-dim);font-size:12px}.searchbar button{min-width:22px;color:var(--fg-dim);background:0 0;border:none;justify-content:center;align-items:center;padding:3px 5px;display:inline-flex}.searchbar button:hover,.searchbar button.deep.active{background:var(--bg-hover);color:var(--fg)}.splitter{cursor:col-resize;background:var(--border);touch-action:none;background-clip:padding-box;border:2px solid #0000;flex-shrink:0;width:5px}.splitter:hover,.splitter:active{background-color:#5a80b0}.row{cursor:pointer;white-space:nowrap;align-items:center;gap:8px;height:28px;padding:0 8px;scroll-margin-top:28px;display:flex;position:relative}.row.wip{background-color:var(--bg-panel);background-image:linear-gradient(to right, transparent calc(8px + var(--refs-w,120px)), var(--bg-wip) 0);z-index:1;font-style:italic;position:sticky;top:0}.row:hover:not(:has(.refs:hover)):not(.selected){background-image:linear-gradient(to right, transparent calc(8px + var(--refs-w,120px)), var(--bg-hover) 0)}.row.selected{background-image:linear-gradient(to right, transparent calc(8px + var(--refs-w,120px)), var(--bg-selected) 0)}.graph-cell{flex-shrink:0;overflow:visible}.refs{width:var(--refs-w,120px);cursor:default;flex-shrink:0;justify-content:flex-start;align-self:stretch;align-items:center;gap:4px;display:flex;overflow:hidden}.graph-col{width:var(--graph-col-w,90px);flex-shrink:0;justify-content:flex-start;align-self:stretch;align-items:center;padding:0 4px;display:flex;overflow:hidden}.col-splitter{cursor:col-resize;touch-action:none;z-index:1;width:5px;position:absolute;top:0;bottom:0}.col-splitter:hover,.col-splitter:active{background:#5a80b0}.ref-chips{gap:4px;min-width:0;display:flex}.ref-chip{color:var(--fg);background:var(--bg-panel);flex:1 1 calc(14px + var(--n-icons,0) * 15px);-webkit-user-select:none;user-select:none;cursor:pointer;border:1px solid;border-radius:8px;align-items:center;gap:3px;min-width:0;max-width:max-content;padding:1px 6px;font-size:14px;display:inline-flex}.ref-chip:hover{background:var(--bg-hover)}.ref-chip.head{background:var(--head-bg);color:var(--head-fg);font-weight:600}.ref-chip.head:hover{background:var(--head-bg-hover)}.ref-name{text-overflow:ellipsis;white-space:nowrap;min-width:0;overflow:hidden}.ref-line{opacity:.5;flex:1;align-self:center;min-width:6px;height:1px}.ref-bridge{opacity:.5;pointer-events:none;height:1px;position:absolute;top:calc(50% - .5px)}.ref-line.head,.ref-bridge.head{opacity:1;height:2px}.ref-bridge.head{top:calc(50% - 1px)}.ref-icon{opacity:.8;flex-shrink:0}.subject{text-overflow:ellipsis;flex:1;overflow:hidden}.date{color:var(--fg-dim);font-size:12px}.hash{color:var(--fg-faint);font-family:Ubuntu Mono,ui-monospace,monospace;font-size:12px}.load-more{margin:8px;padding:4px 14px}.ctx-menu{border:1px solid var(--border);background:var(--bg-panel);border-radius:6px;min-width:150px;margin:0;padding:4px;position:fixed;inset:auto;box-shadow:0 6px 20px #00000059}.ctx-item{width:100%;color:var(--fg);font:inherit;text-align:left;cursor:pointer;background:0 0;border:0;border-radius:4px;padding:5px 10px;font-size:14px;display:block}.ctx-item:hover{background:var(--bg-hover)}.ctx-item.sep{border-top:1px solid var(--border);border-radius:0 0 4px 4px;margin-top:5px;padding-top:9px}.ctx-item.danger{color:#e06c75}.ctx-item.danger:hover{color:#fff;background:#e06c75}.panel{flex-direction:column;flex:1;min-width:0;min-height:0;display:flex}.commit-msg{border-bottom:1px solid var(--border);flex-shrink:0;max-height:30%;padding:10px 12px;position:relative;overflow-y:auto}.commit-subject{font-weight:600}.commit-body{white-space:pre-wrap;color:var(--fg-dim);margin-top:8px;font-size:13px}.msg-edit{vertical-align:-4px;color:var(--fg-dim);opacity:0;margin-left:8px;padding:4px;line-height:0;transition:opacity .1s;display:inline-flex}.msg-edit:hover{color:var(--fg)}.commit-msg:hover .msg-edit,.msg-edit:focus-visible{opacity:1}.msg-box{border:1px solid var(--border);background:var(--bg);width:100%;min-height:84px;color:var(--fg);font:inherit;resize:vertical;border-radius:4px;padding:6px 8px;display:block}.msg-actions{align-items:center;gap:6px;margin-top:6px;display:flex}.msg-hint{color:var(--fg-faint);flex:1;font-size:12px}.msg-actions .primary{background:var(--head-bg);color:var(--head-fg);border-color:var(--head-fg)}.msg-actions .primary:disabled{opacity:.45}.head-actions,.row-action{gap:2px;display:flex}.head-actions button,.row-action{color:var(--fg-dim);background:0 0;border-color:#0000;padding:3px;line-height:0}.head-actions button:hover,.row-action:hover{border-color:var(--border);background:var(--bg-hover);color:var(--fg)}.head-actions .danger:hover,.row-action.danger:hover{color:#e06c75;border-color:#e06c75}.head-actions button:disabled,.row-action:disabled{opacity:.4}.section-head{z-index:1;background:var(--bg-panel);color:var(--fg);text-transform:uppercase;letter-spacing:.05em;cursor:pointer;-webkit-user-select:none;user-select:none;align-items:center;gap:5px;padding:8px 6px 5px;font-size:13px;font-weight:600;display:flex;position:sticky;top:0}.section-head:hover{background:var(--bg-hover)}.section-head .caret{color:var(--fg-dim);line-height:0;transition:transform .1s;display:flex}.section-head .caret.open{transform:rotate(90deg)}.section-head .count{background:var(--bg-selected);min-width:19px;height:19px;color:var(--fg);letter-spacing:0;border-radius:10px;justify-content:center;align-items:center;padding:0 5px;font-size:11px;display:inline-flex}.section-head .count.zero{background:var(--bg-hover);color:var(--fg-faint)}.section-head .head-actions{visibility:hidden;margin-left:auto}.section-head:hover .head-actions,.section-head .head-actions:focus-within{visibility:visible}.composer{border-bottom:1px solid var(--border);flex-shrink:0;padding:8px}.composer .msg-box{min-height:56px}.row-action{visibility:hidden}.row-action:first-of-type{margin-left:auto}.file-row:hover .row-action,.file-row.selected .row-action,.row-action:focus-visible{visibility:visible}.diff-side{border:1px solid var(--border);color:var(--fg-dim);border-radius:8px;padding:0 6px;font-size:12px}.commit-people{border-bottom:1px solid var(--border);flex-direction:column;flex-shrink:0;gap:6px;padding:10px 12px;display:flex;position:relative}.person{align-items:center;gap:8px;min-width:0;display:flex}.person-text{flex-direction:column;min-width:0;display:flex}.person-name{text-overflow:ellipsis;white-space:nowrap;overflow:hidden}.person-when{color:var(--fg-dim);font-size:12px}.face{object-fit:cover;border-radius:50%;flex-shrink:0;width:26px;height:26px}.face-fallback{background:var(--bg-hover);color:var(--fg-dim);justify-content:center;align-items:center;font-size:11px;display:inline-flex}.commit-hash{color:var(--fg-faint);font-family:Ubuntu Mono,ui-monospace,monospace;font-size:12px;position:absolute;top:10px;right:12px}.files-head{border-bottom:1px solid var(--border);flex-shrink:0;justify-content:space-between;align-items:center;gap:8px;padding:6px 12px;display:flex}.files-counts{color:var(--fg-dim);white-space:nowrap;gap:10px;font-size:12px;display:flex;overflow:hidden}.files-head .view-toggle button{padding:2px 10px;font-size:12px}.filelist{flex:1;padding:4px;overflow-y:auto}.file-dir{color:var(--fg-faint);text-overflow:ellipsis;white-space:nowrap;flex:0 auto;min-width:0;overflow:hidden}.dir-row{color:var(--fg-dim)}.dir-arrow{width:14px;color:var(--fg-faint);font-size:10px;line-height:inherit;flex-shrink:0}.file-row{cursor:pointer;border-radius:4px;gap:8px;padding:3px 8px;display:flex}.file-row:hover{background:var(--bg-hover)}.file-row.selected{background:var(--bg-selected)}.file-status{flex-shrink:0;width:14px;font-family:Ubuntu Mono,ui-monospace,monospace}.file-path{white-space:nowrap;min-width:0;display:flex;overflow:hidden}.file-name{flex:none}.diffview{flex-direction:column;flex:1;min-height:0;display:flex;overflow:auto}.diffview.empty{text-align:center;justify-content:center;align-items:center}.diff-state{text-align:center;flex:1;justify-content:center;align-items:center;display:flex}.diff-toolbar{border-bottom:1px solid var(--border);flex-shrink:0;justify-content:center;gap:8px;padding:6px 10px;display:flex}.diff-toolbar button{padding:3px 10px}.view-toggle{display:inline-flex}.view-toggle button{border-radius:0}.view-toggle button:first-child{border-radius:4px 0 0 4px}.view-toggle button:last-child{border-left:none;border-radius:0 4px 4px 0}.view-toggle button.active{background:var(--bg-selected)}.diff-html{flex:1;overflow:auto}.diff-html .d2h-file-wrapper{position:relative}.diff-html .d2h-file-diff{overflow-x:auto}.diff-html .d2h-diff-table{font-family:Ubuntu Mono,ui-monospace,monospace}.diff-html .d2h-code-linenumber,.diff-html .d2h-code-side-linenumber{cursor:default}.diff-html td.d2h-info .diff-expand-group{direction:ltr;gap:1px;padding-left:2px;display:flex}.diff-html .diff-expand{width:18px;height:18px;color:var(--fg-dim);cursor:pointer;background:0 0;border:0;border-radius:3px;justify-content:center;align-items:center;padding:0;display:flex}.diff-html .diff-expand:hover{background:var(--bg-hover);color:var(--fg)}.diff-plain{color:var(--fg-dim);padding:10px}.image-diff{flex:1;grid-template-columns:1fr 1fr;align-content:start;gap:12px;padding:12px;display:grid;overflow:auto}.image-pane{flex-direction:column;gap:6px;min-width:0;display:flex}.image-pane-label{color:var(--fg-dim);font-size:12px}.image-pane img{border:1px solid var(--border);background:repeating-conic-gradient(var(--bg-panel) 0% 25%, var(--bg) 0% 50%) 0 0 / 16px 16px;border-radius:4px;max-width:100%}.image-pane-none{text-align:center;color:var(--fg-dim);border:1px dashed var(--border);border-radius:4px;padding:20px}.d2h-wrapper{--d2h-bg-color:var(--bg-panel);--d2h-ins-bg-color:#e6ffec;--d2h-ins-highlight-bg-color:#abf2bc;--d2h-ins-border-color:#ccffd8;--d2h-ins-label-color:#1a7f37;--d2h-del-bg-color:#ffebe9;--d2h-del-highlight-bg-color:#ffcecb;--d2h-del-border-color:#ffd7d5;--d2h-del-label-color:#cf222e;--d2h-change-ins-color:#e6ffec;--d2h-change-del-color:#ffebe9;--d2h-info-bg-color:#ddf4ff;--d2h-info-border-color:#ddf4ff;--d2h-line-border-color:#d0d7de;--d2h-dark-bg-color:#23272e;--d2h-dark-border-color:#30363d;--d2h-dark-line-border-color:#30363d;--d2h-dark-ins-bg-color:#2ea04326;--d2h-dark-ins-highlight-bg-color:#2ea04366;--d2h-dark-ins-border-color:#3fb9504d;--d2h-dark-ins-label-color:#3fb950;--d2h-dark-del-bg-color:#f851491a;--d2h-dark-del-highlight-bg-color:#f8514966;--d2h-dark-del-border-color:#f851494d;--d2h-dark-del-label-color:#f85149;--d2h-dark-change-ins-color:#2ea04326;--d2h-dark-change-del-color:#f851491a;--d2h-dark-info-bg-color:#388bfd1a;--d2h-dark-info-border-color:#388bfd4d}.d2h-wrapper .hljs{background:0 0}.conflict-banner{background:var(--bg-wip);border-bottom:1px solid var(--border);flex-shrink:0;align-items:center;gap:10px;padding:6px 10px;font-size:12px;display:flex}.conflict-banner .cb-verb{font-weight:600}.conflict-banner .cb-label{color:var(--fg-dim);text-overflow:ellipsis;white-space:nowrap;max-width:40ch;overflow:hidden}.conflict-banner .cb-count{color:var(--fg-dim);margin-left:auto}.conflict-banner button{padding:3px 10px;font-size:12px}.conflict-banner button.danger{color:#e06c75;border-color:#e06c75}.conflict-banner button.primary:not(:disabled){background:var(--head-bg);color:var(--head-fg);border-color:var(--head-bg-hover)}.conflict-banner button:disabled{opacity:.5;cursor:default}.cf-view{flex:1;font-family:ui-monospace,SFMono-Regular,Menlo,monospace;font-size:12px;line-height:1.5;overflow:auto}.cf-progress{z-index:1;background:var(--bg-panel);border-bottom:1px solid var(--border);color:var(--fg-dim);padding:4px 10px;position:sticky;top:0}.cf-view pre{white-space:pre-wrap;word-break:break-word;margin:0;padding:0 10px}.cf-context{color:var(--fg-dim)}.cf-block{border:1px solid var(--border);border-radius:4px;margin:6px 0;overflow:hidden}.cf-block.picked{border-color:var(--head-bg-hover)}.cf-bar{background:var(--bg-hover);border-bottom:1px solid var(--border);align-items:center;gap:6px;padding:3px 8px;display:flex}.cf-side-name{color:var(--fg-dim);margin-right:auto}.cf-chosen{color:var(--head-fg);margin-right:auto}.cf-bar button{padding:2px 8px;font-size:11px}.cf-ours{background:#98c3791f}.cf-theirs{background:#61afef1f}.cf-mid{color:var(--fg-dim);border-top:1px dashed var(--border);border-bottom:1px dashed var(--border);padding:2px 8px}.cf-card{border:1px solid var(--border);text-align:center;border-radius:6px;max-width:420px;margin:20px auto;padding:16px;font-family:inherit}.cf-card-note{color:var(--fg-dim);margin-bottom:12px}.cf-card-actions{justify-content:center;gap:8px;display:flex}.cf-card-actions button.danger{color:#e06c75;border-color:#e06c75}.diff-side.conflict{color:#e06c75}
@@ -0,0 +1,100 @@
1
+ Ubuntu Mono, by Dalton Maag Ltd. Bundled with megit under the Ubuntu Font
2
+ Licence 1.0, reproduced in full below. Only the latin subset is shipped
3
+ (ubuntu-mono-400.woff2, -700, -400-italic), converted to woff2 by Google Fonts.
4
+
5
+ -------------------------------
6
+ UBUNTU FONT LICENCE Version 1.0
7
+ -------------------------------
8
+
9
+ PREAMBLE
10
+ This licence allows the licensed fonts to be used, studied, modified and
11
+ redistributed freely. The fonts, including any derivative works, can be
12
+ bundled, embedded, and redistributed provided the terms of this licence
13
+ are met. The fonts and derivatives, however, cannot be released under
14
+ any other licence. The requirement for fonts to remain under this
15
+ licence does not require any document created using the fonts or their
16
+ derivatives to be published under this licence, as long as the primary
17
+ purpose of the document is not to be a vehicle for the distribution of
18
+ the fonts.
19
+
20
+ DEFINITIONS
21
+ "Font Software" refers to the set of files released by the Copyright
22
+ Holder(s) under this licence and clearly marked as such. This may
23
+ include source files, build scripts and documentation.
24
+
25
+ "Original Version" refers to the collection of Font Software components
26
+ as received under this licence.
27
+
28
+ "Modified Version" refers to any derivative made by adding to, deleting,
29
+ or substituting -- in part or in whole -- any of the components of the
30
+ Original Version, by changing formats or by porting the Font Software to
31
+ a new environment.
32
+
33
+ "Copyright Holder(s)" refers to all individuals and companies who have a
34
+ copyright ownership of the Font Software.
35
+
36
+ "Substantially Changed" refers to Modified Versions which can be easily
37
+ identified as dissimilar to the Font Software by users of the Font
38
+ Software comparing the Original Version with the Modified Version.
39
+
40
+ To "Propagate" a work means to do anything with it that, without
41
+ permission, would make you directly or secondarily liable for
42
+ infringement under applicable copyright law, except executing it on a
43
+ computer or modifying a private copy. Propagation includes copying,
44
+ distribution (with or without modification and with or without charging
45
+ a redistribution fee), making available to the public, and in some
46
+ countries other activities as well.
47
+
48
+ PERMISSION & CONDITIONS
49
+ This licence does not grant any rights under trademark law and all such
50
+ rights are reserved.
51
+
52
+ Permission is hereby granted, free of charge, to any person obtaining a
53
+ copy of the Font Software, to propagate the Font Software, subject to
54
+ the below conditions:
55
+
56
+ 1) Each copy of the Font Software must contain the above copyright
57
+ notice and this licence. These can be included either as stand-alone
58
+ text files, human-readable headers or in the appropriate machine-
59
+ readable metadata fields within text or binary files as long as those
60
+ fields can be easily viewed by the user.
61
+
62
+ 2) The font name complies with the following:
63
+ (a) The Original Version must retain its name, unmodified.
64
+ (b) Modified Versions which are Substantially Changed must be renamed to
65
+ avoid use of the name of the Original Version or similar names entirely.
66
+ (c) Modified Versions which are not Substantially Changed must be
67
+ renamed to both (i) retain the name of the Original Version and (ii) add
68
+ additional naming elements to distinguish the Modified Version from the
69
+ Original Version. The name of such Modified Versions must be the name of
70
+ the Original Version, with "derivative X" where X represents the name of
71
+ the new work, appended to that name.
72
+
73
+ 3) The name(s) of the Copyright Holder(s) and any contributor to the
74
+ Font Software shall not be used to promote, endorse or advertise any
75
+ Modified Version, except (i) as required by this licence, (ii) to
76
+ acknowledge the contribution(s) of the Copyright Holder(s) or (iii) with
77
+ their explicit written permission.
78
+
79
+ 4) The Font Software, modified or unmodified, in part or in whole, must
80
+ be distributed entirely under this licence, and must not be distributed
81
+ under any other licence. The requirement for fonts to remain under this
82
+ licence does not affect any document created using the Font Software,
83
+ except any version of the Font Software extracted from a document
84
+ created using the Font Software may only be distributed under this
85
+ licence.
86
+
87
+ TERMINATION
88
+ This licence becomes null and void if any of the above conditions are
89
+ not met.
90
+
91
+ DISCLAIMER
92
+ THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
93
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
94
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT OF
95
+ COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE
96
+ COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
97
+ INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
98
+ DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
99
+ FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM OTHER
100
+ DEALINGS IN THE FONT SOFTWARE.
Binary file
Binary file
package/dist/index.html CHANGED
@@ -4,12 +4,9 @@
4
4
  <meta charset="UTF-8" />
5
5
  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
6
6
  <link rel="icon" type="image/svg+xml" href="/logo.svg" />
7
- <link rel="preconnect" href="https://fonts.googleapis.com" />
8
- <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
9
- <link href="https://fonts.googleapis.com/css2?family=Ubuntu+Mono:ital,wght@0,400;0,700;1,400&display=swap" rel="stylesheet" />
10
7
  <title>megit</title>
11
- <script type="module" crossorigin src="/assets/index-Bfrt9LxG.js"></script>
12
- <link rel="stylesheet" crossorigin href="/assets/index-C845Od_q.css">
8
+ <script type="module" crossorigin src="/assets/index-BZBjYCd4.js"></script>
9
+ <link rel="stylesheet" crossorigin href="/assets/index-ByYQYfuO.css">
13
10
  </head>
14
11
  <body>
15
12
  <div id="root"></div>
@@ -0,0 +1,174 @@
1
+ // A ~150-line stand-in for the slice of express this server actually used:
2
+ // exact-path routing, JSON bodies, static files and an SPA fallback. express
3
+ // itself is fine, but it drags 67 transitive packages into the published
4
+ // install — most of them single-function modules untouched for five years —
5
+ // and every one of those is install-time attack surface for a tool people run
6
+ // against their own repos. Node's http module covers the rest.
7
+ import { createServer } from 'node:http';
8
+ import { createReadStream, statSync } from 'node:fs';
9
+ import { extname, join, sep } from 'node:path';
10
+ // 10mb, not express's 100kb default: a resolved conflicted file goes back as a
11
+ // JSON string, and a few thousand lines of source blows past the default.
12
+ const BODY_LIMIT = 10 * 1024 * 1024;
13
+ const TYPES = {
14
+ '.html': 'text/html; charset=utf-8',
15
+ '.js': 'text/javascript; charset=utf-8',
16
+ '.css': 'text/css; charset=utf-8',
17
+ '.svg': 'image/svg+xml',
18
+ '.json': 'application/json; charset=utf-8',
19
+ '.map': 'application/json; charset=utf-8',
20
+ '.ico': 'image/x-icon',
21
+ '.png': 'image/png',
22
+ '.webp': 'image/webp',
23
+ '.woff2': 'font/woff2',
24
+ '.wasm': 'application/wasm',
25
+ };
26
+ function decorate(res) {
27
+ const r = res;
28
+ r.status = code => { r.statusCode = code; return r; };
29
+ r.set = (name, value) => { r.setHeader(name, value); return r; };
30
+ r.type = mime => r.set('Content-Type', mime);
31
+ r.json = value => {
32
+ if (!r.hasHeader('Content-Type'))
33
+ r.setHeader('Content-Type', 'application/json; charset=utf-8');
34
+ r.end(JSON.stringify(value));
35
+ };
36
+ r.send = body => {
37
+ const buf = Buffer.isBuffer(body) ? body : Buffer.from(body);
38
+ r.setHeader('Content-Length', String(buf.length));
39
+ r.end(buf);
40
+ };
41
+ return r;
42
+ }
43
+ // Resolves to {} unless the request carries a JSON body — matching express.json,
44
+ // which leaves req.body empty for any other content type.
45
+ function readBody(req) {
46
+ if (req.method === 'GET' || req.method === 'HEAD')
47
+ return Promise.resolve({});
48
+ if (!/^application\/json\b/.test(req.headers['content-type'] ?? ''))
49
+ return Promise.resolve({});
50
+ return new Promise((ok, fail) => {
51
+ const chunks = [];
52
+ let size = 0;
53
+ let over = false;
54
+ req.on('data', (c) => {
55
+ size += c.length;
56
+ if (over)
57
+ return;
58
+ if (size > BODY_LIMIT) {
59
+ // Past the cap: drop what was buffered and swallow the rest. Answering
60
+ // now instead would mean closing the socket with an upload still in
61
+ // flight, and that close is a TCP reset — which is allowed to discard
62
+ // the 413 along with it, leaving the client an ECONNRESET (or an EPIPE
63
+ // on its next write) and no status at all. Reading to the end costs
64
+ // constant memory and buys a clean FIN with the response intact.
65
+ chunks.length = 0;
66
+ over = true;
67
+ return;
68
+ }
69
+ chunks.push(c);
70
+ });
71
+ req.on('error', fail);
72
+ req.on('end', () => {
73
+ if (over)
74
+ return fail(Object.assign(new Error('request entity too large'), { status: 413 }));
75
+ if (!chunks.length)
76
+ return ok({});
77
+ try {
78
+ ok(JSON.parse(Buffer.concat(chunks).toString('utf8')));
79
+ }
80
+ catch {
81
+ fail(Object.assign(new Error('invalid JSON body'), { status: 400 }));
82
+ }
83
+ });
84
+ });
85
+ }
86
+ function sendFile(file, req, res) {
87
+ const st = statSync(file, { throwIfNoEntry: false });
88
+ if (!st?.isFile())
89
+ return false;
90
+ // vite fingerprints asset filenames, so a weak validator off size+mtime is
91
+ // enough to keep reloads from re-sending the bundle
92
+ const etag = `W/"${st.size.toString(16)}-${st.mtimeMs.toString(16)}"`;
93
+ res.set('ETag', etag);
94
+ if (req.headers['if-none-match'] === etag) {
95
+ res.status(304).end();
96
+ return true;
97
+ }
98
+ res.set('Content-Type', TYPES[extname(file).toLowerCase()] ?? 'application/octet-stream');
99
+ res.set('Content-Length', String(st.size));
100
+ createReadStream(file).pipe(res);
101
+ return true;
102
+ }
103
+ // Serves `dir`, falling back to its index.html so client-side routes survive a
104
+ // reload. /api/* never falls through — an unknown API path is a 404, not a page.
105
+ export function serveStatic(dir) {
106
+ const index = join(dir, 'index.html');
107
+ return (req, res) => {
108
+ const path = decodeURIComponent(new URL(req.url ?? '/', 'http://localhost').pathname);
109
+ const file = join(dir, path);
110
+ // join() collapses `..`, but only after decoding — `%2e%2e%2f` arrives here
111
+ // as a real traversal and this is what stops it
112
+ if (file !== dir && !file.startsWith(dir + sep)) {
113
+ res.status(403).json({ error: 'forbidden' });
114
+ return;
115
+ }
116
+ if (sendFile(file, req, res))
117
+ return;
118
+ if (path.startsWith('/api')) {
119
+ res.status(404).json({ error: 'not found' });
120
+ return;
121
+ }
122
+ if (!sendFile(index, req, res))
123
+ res.status(404).json({ error: 'not found' });
124
+ };
125
+ }
126
+ export function createApp() {
127
+ const middleware = [];
128
+ const routes = new Map();
129
+ let fallback = (_req, res) => { res.status(404).json({ error: 'not found' }); };
130
+ const on = (method) => (path, ...handlers) => {
131
+ routes.set(`${method} ${path}`, handlers);
132
+ };
133
+ const listener = async (raw, rawRes) => {
134
+ const req = raw;
135
+ const res = decorate(rawRes);
136
+ try {
137
+ const url = new URL(req.url ?? '/', 'http://localhost');
138
+ req.query = Object.fromEntries(url.searchParams);
139
+ req.body = await readBody(req);
140
+ // HEAD reuses the GET handler; node drops the body for us
141
+ const verb = req.method === 'HEAD' ? 'GET' : req.method;
142
+ for (const handler of [...middleware, ...(routes.get(`${verb} ${url.pathname}`) ?? [fallback])]) {
143
+ let advance = false;
144
+ await handler(req, res, () => { advance = true; });
145
+ if (!advance)
146
+ return;
147
+ }
148
+ }
149
+ catch (e) {
150
+ // express 5 turns a rejected handler into a 500 rather than an unhandled
151
+ // rejection that takes the process down; so does this
152
+ const err = e;
153
+ if (res.headersSent) {
154
+ res.end();
155
+ return;
156
+ }
157
+ // a client that overshot the cap gets its connection retired rather than
158
+ // parked for reuse. Safe only because the body was drained to the end
159
+ // first — closing with an upload still in flight is a reset, not a FIN.
160
+ if (err.status === 413)
161
+ res.setHeader('Connection', 'close');
162
+ res.status(err.status ?? 500).json({ error: err.message });
163
+ }
164
+ };
165
+ return {
166
+ use: (handler) => { middleware.push(handler); },
167
+ get: on('GET'),
168
+ post: on('POST'),
169
+ put: on('PUT'),
170
+ delete: on('DELETE'),
171
+ fallback: (handler) => { fallback = handler; },
172
+ listen: (port, host) => createServer(listener).listen(port, host),
173
+ };
174
+ }
@@ -1,4 +1,4 @@
1
- import express from 'express';
1
+ import { createApp, serveStatic } from './http.js';
2
2
  import { execFile } from 'node:child_process';
3
3
  import { existsSync, readdirSync } from 'node:fs';
4
4
  import { readFile, realpath, writeFile } from 'node:fs/promises';
@@ -10,12 +10,11 @@ import { mergeMatches, parseBranchHeader, parseLog, parseMatches, parseMeta, par
10
10
  import { pickOperation, STATE_FILES } from './operation.js';
11
11
  import { subscribe } from './watch.js';
12
12
  import { wireTerminal, hasPty } from './term.js';
13
- const app = express();
14
- // 10mb, not the 100kb default: a resolved conflicted file goes back as a JSON
15
- // string, and a few thousand lines of source blows past the default. The GET
16
- // side refuses anything over DIFF_CAP, so the client can't assemble a body
17
- // larger than 1 MB.
18
- app.use(express.json({ limit: '10mb' }));
13
+ // JSON bodies are capped at 10mb by createApp — a resolved conflicted file goes
14
+ // back as a JSON string, and a few thousand lines of source blows past a smaller
15
+ // limit. The GET side refuses anything over DIFF_CAP, so the client can't
16
+ // assemble a body larger than 1 MB.
17
+ const app = createApp();
19
18
  // The server listens on loopback only, but that alone doesn't stop a page on
20
19
  // attacker.tld from rebinding its DNS to 127.0.0.1: the browser then treats this
21
20
  // API as same-origin and CORS never applies. Pinning Host closes that — a rebound
@@ -921,15 +920,12 @@ app.get('/api/blob', repoGuard, async (req, res) => {
921
920
  }
922
921
  });
923
922
  const dist = join(import.meta.dirname, '..', 'dist');
924
- if (existsSync(dist)) {
925
- app.use(express.static(dist));
926
- app.get(/^(?!\/api).*/, (_req, res) => res.sendFile(join(dist, 'index.html')));
927
- }
923
+ if (existsSync(dist))
924
+ app.fallback(serveStatic(dist));
928
925
  const port = Number(process.env.PORT) || 3411;
929
926
  // exported so bin/megit.js can wait for 'listening' before opening the browser.
930
- // The banner hangs off the 'listening' event rather than app.listen's callback:
931
- // express 5 runs that callback even when the bind failed, which would announce a
932
- // URL that never came up.
927
+ // The banner hangs off the 'listening' event rather than a listen callback, which
928
+ // can fire even when the bind failed and would announce a URL that never came up.
933
929
  export const server = app.listen(port, '127.0.0.1');
934
930
  server.on('listening', () => console.log(`megit API on http://127.0.0.1:${port}`));
935
931
  server.on('error', (e) => {
@@ -1,5 +1,6 @@
1
- import { existsSync } from 'node:fs';
1
+ import { chmodSync, existsSync, readdirSync } from 'node:fs';
2
2
  import { createRequire } from 'node:module';
3
+ import { dirname, join } from 'node:path';
3
4
  import { WebSocketServer, WebSocket } from 'ws';
4
5
  import { loadConfig } from './config.js';
5
6
  // node-pty is an optionalDependency: it ships prebuilds for darwin and win32 only,
@@ -35,10 +36,37 @@ export function termKey(repo, pane) {
35
36
  return null;
36
37
  return `${repo}\0${p}`;
37
38
  }
39
+ // node-pty ships spawn-helper as a prebuilt binary, and some package managers drop
40
+ // the executable bit when extracting the tarball — the terminal then fails to spawn.
41
+ // This ran as a postinstall script until an install script proved to be the kind of
42
+ // supply-chain surface scanners flag on sight; it costs nothing to do it here, once,
43
+ // on the first shell. Resolve node-pty rather than guessing ./node_modules: under
44
+ // `npx` it is hoisted to the installing project's node_modules, not ours.
45
+ let helperFixed = false;
46
+ function fixSpawnHelper() {
47
+ if (helperFixed)
48
+ return;
49
+ helperFixed = true;
50
+ try {
51
+ const prebuilds = join(dirname(createRequire(import.meta.url).resolve('node-pty/package.json')), 'prebuilds');
52
+ for (const platform of readdirSync(prebuilds)) {
53
+ try {
54
+ chmodSync(join(prebuilds, platform, 'spawn-helper'), 0o755);
55
+ }
56
+ catch {
57
+ // win32 prebuilds have no spawn-helper; nothing to do
58
+ }
59
+ }
60
+ }
61
+ catch {
62
+ // node-pty is optional — absent on Linux installs without build tools
63
+ }
64
+ }
38
65
  async function getSession(key, repo) {
39
66
  const existing = sessions.get(key);
40
67
  if (existing)
41
68
  return existing;
69
+ fixSpawnHelper();
42
70
  // dynamic import: the native module never loads until a terminal is actually opened
43
71
  const { spawn } = await import('node-pty');
44
72
  const shell = process.env.SHELL || '/bin/sh';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "megit-app",
3
- "version": "0.5.0",
3
+ "version": "0.5.2",
4
4
  "description": "Git repository viewer in the browser: commit graph with branch lanes, diffs, stashes and WIP",
5
5
  "license": "MIT",
6
6
  "author": "Hoang Vuong Vu",
@@ -25,7 +25,6 @@
25
25
  "bin",
26
26
  "dist",
27
27
  "dist-server",
28
- "scripts/fix-pty-perms.mjs",
29
28
  "CHANGELOG.md"
30
29
  ],
31
30
  "engines": {
@@ -37,20 +36,17 @@
37
36
  "build:server": "node -e \"fs.rmSync('dist-server',{recursive:true,force:true})\" && tsc -p tsconfig.server.json",
38
37
  "start": "PORT=4500 node server/index.ts",
39
38
  "test": "vitest run",
40
- "prepublishOnly": "pnpm test && pnpm build && pnpm build:server",
41
- "postinstall": "node scripts/fix-pty-perms.mjs"
39
+ "prepublishOnly": "pnpm test && pnpm build && pnpm build:server"
42
40
  },
43
41
  "packageManager": "pnpm@11.18.0+sha512.33d83c77da82f49fba836925c6f1b841181ec3132b670639bd012f7075f5c7cf634c5f870147c19aae7478fac01df09d8892e880454896edd23ee9b33757563c",
44
42
  "dependencies": {
45
- "express": "^5.2.1",
46
- "ws": "^8.21.2"
43
+ "ws": "^8.21.3"
47
44
  },
48
45
  "optionalDependencies": {
49
46
  "node-pty": "^1.1.0"
50
47
  },
51
48
  "devDependencies": {
52
- "@types/express": "^5.0.6",
53
- "@types/node": "^26.1.2",
49
+ "@types/node": "^26.2.0",
54
50
  "@types/react": "^19.2.18",
55
51
  "@types/react-dom": "^19.2.4",
56
52
  "@types/ws": "^8.18.1",
@@ -63,7 +59,7 @@
63
59
  "react": "^19.2.8",
64
60
  "react-dom": "^19.2.8",
65
61
  "typescript": "^7.0.2",
66
- "vite": "^8.2.0",
62
+ "vite": "^8.2.1",
67
63
  "vitest": "^4.1.10"
68
64
  }
69
65
  }
@@ -1 +0,0 @@
1
- :root{--bg:#1e2228;--bg-panel:#23272e;--bg-tabbar:#14171c;--bg-tab:#1a1e24;--bg-hover:#2c313a;--bg-selected:#354254;--bg-wip:#2a2e25;--border:#3a404b;--fg:#d4d8de;--fg-dim:#9aa4b2;--fg-faint:#6b7280;--head-bg:#2e4436;--head-bg-hover:#3d5a47;--head-fg:#a8d99a}:root[data-theme=light]{--bg:#f6f8fa;--bg-panel:#fff;--bg-tabbar:#e4e7eb;--bg-tab:#eef0f3;--bg-hover:#e8eaed;--bg-selected:#cfdff5;--bg-wip:#f3f5e8;--border:#d0d7de;--fg:#1f2328;--fg-dim:#57606a;--fg-faint:#8b949e;--head-bg:#d3f2d9;--head-bg-hover:#b4e7c0;--head-fg:#116329}*{box-sizing:border-box}html,body,#root{height:100%;margin:0}body{background:var(--bg);color:var(--fg);font:16px/1.4 Ubuntu Mono,ui-monospace,monospace}button{background:var(--bg-hover);color:inherit;border:1px solid var(--border);cursor:pointer;border-radius:4px}button:hover{background:var(--border)}.app{flex-direction:column;height:100%;display:flex}.empty{color:var(--fg-faint);flex:1;justify-content:center;align-items:center;display:flex}.error{color:#e06c75;white-space:pre-wrap;padding:8px}.toasts{z-index:20;flex-direction:column;gap:6px;max-width:380px;display:flex;position:fixed;bottom:12px;right:12px}.toast{background:var(--bg-panel);color:var(--fg);white-space:pre-wrap;cursor:pointer;border:1px solid #e06c75;border-left-width:3px;border-radius:6px;padding:8px 10px;animation:.15s ease-out appear;box-shadow:0 2px 8px #0000004d}.tabbar{background:var(--bg-tabbar);flex-shrink:0;gap:2px;padding:6px 6px 0;display:flex}.logo{pointer-events:none;align-self:center;width:18px;height:18px;margin:0 4px 6px 2px}.tab{background:var(--bg-tab);cursor:pointer;border-radius:6px 6px 0 0;align-items:center;gap:6px;padding:6px 10px;display:flex}.tab.active{background:var(--bg-panel)}.tab.dragging{opacity:.5}.tab-close{width:18px;height:18px;color:var(--fg-dim);background:0 0;border:none;border-radius:50%;flex-shrink:0;justify-content:center;align-items:center;margin:-2px -4px -2px 0;padding:0;display:flex}.tab-close:hover{background:var(--border);color:var(--fg)}.tab-add{background:0 0;border:none;padding:6px 12px;font-size:15px}.build-tag{color:var(--fg-faint);-webkit-user-select:none;user-select:none;align-self:center;margin:0 4px 6px auto;font-size:11px}.modal-backdrop{z-index:10;background:#00000080;justify-content:center;align-items:center;display:flex;position:fixed;inset:0}.modal{background:var(--bg-panel);border:1px solid var(--border);border-radius:8px;flex-direction:column;width:480px;max-height:70vh;display:flex}.modal-head{border-bottom:1px solid var(--border);align-items:center;gap:8px;padding:10px;display:flex}.modal-path{text-overflow:ellipsis;white-space:nowrap;color:var(--fg-dim);flex:1;overflow:hidden}.dirlist{margin:0;padding:4px;list-style:none;overflow-y:auto}.dirlist li{border-radius:4px;align-items:center;padding:4px 8px;display:flex}.dirlist li:hover{background:var(--bg-hover)}.dirname{cursor:pointer;text-overflow:ellipsis;white-space:nowrap;flex:1;overflow:hidden}.modal-label{text-transform:uppercase;letter-spacing:.5px;color:var(--fg-dim);padding:8px 12px 2px;font-size:11px}.modal-path b{color:var(--fg)}.picker{flex-direction:row;width:720px;height:440px;max-height:80vh;overflow:hidden}.picker-pane{flex-direction:column;flex:1;min-width:0;display:flex}.picker-recent{border-right:1px solid var(--border);background:var(--bg-tab);flex:none;width:240px}.picker-recent .dirlist li.sel{background:var(--bg-selected)}.recent-dot{background:#2ea043;border-radius:50%;flex-shrink:0;width:6px;height:6px}.dir-chevron{color:var(--fg-faint);cursor:pointer;padding-left:8px}.repoview{background:var(--bg-panel);flex-direction:column;flex:1;min-height:0;animation:.5s ease-out appear;display:flex}@keyframes appear{0%{opacity:0}}@media (prefers-reduced-motion:reduce){.repoview{animation:none}}.toolbar{border-bottom:1px solid var(--border);flex-shrink:0;grid-template-columns:1fr auto 1fr;align-items:center;gap:10px;margin-bottom:6px;padding:6px 10px;display:grid}.tb-left{align-items:center;gap:8px;min-width:0;display:flex}.tb-right{justify-content:flex-end;align-items:center;gap:6px;display:flex}.toolbar button{padding:3px 10px}.refresh-btn{align-items:center;padding:3px 5px;display:inline-flex}.refresh-btn .spin{animation:.5s cubic-bezier(.45,0,.55,1) infinite spin}@keyframes spin{to{transform:rotate(360deg)}}.tb-actions{align-items:center;gap:2px;display:flex}.toolbar .tb-btn{color:var(--fg-dim);background:0 0;border:none;align-items:center;gap:5px;padding:4px 8px;display:inline-flex}.tb-btn:hover:not(:disabled){background:var(--bg-hover);color:var(--fg)}.tb-btn:disabled{opacity:.4;cursor:default}.tb-badge{background:var(--bg-selected);min-width:15px;color:var(--fg);text-align:center;border-radius:7px;padding:0 4px;font-size:11px;line-height:15px}.repo-path{min-width:0;color:var(--fg-dim);text-overflow:ellipsis;white-space:nowrap;overflow:hidden}.github-link{color:var(--fg-dim);flex-shrink:0;align-items:center;display:inline-flex}.github-link:hover{color:var(--fg)}.term-btn{color:var(--fg-dim);align-items:center;padding:3px 5px;display:inline-flex}.term-btn:hover{color:var(--fg)}.term-btn.active{color:var(--fg);background:var(--bg-selected)}.term-panel{border-top:1px solid var(--border);background:var(--bg);flex-direction:column;flex-shrink:0;height:220px;display:flex;position:relative}.term-resizer{cursor:ns-resize;touch-action:none;z-index:3;height:6px;position:absolute;top:-3px;left:0;right:0}.term-resizer:hover,.term-resizer:active{background:#5a80b0}.term-head{flex-shrink:0;justify-content:space-between;align-items:center;padding:3px 6px 3px 8px;display:flex}.term-title{color:var(--fg-dim);align-items:center;gap:6px;font-size:12px;display:inline-flex}.term-head-actions{align-items:center;gap:2px;display:inline-flex}.term-close{color:var(--fg-dim);background:0 0;border:none;align-items:center;padding:0 4px;font-size:13px;display:inline-flex}.term-close:hover{color:var(--fg);background:0 0}.term-close:disabled{opacity:.35}.term-panes{flex:1;min-height:0;display:flex}.term-pane{flex:1;min-width:0;display:flex;position:relative}.term-pane+.term-pane{border-left:1px solid var(--border)}.term-pane-kill{z-index:2;color:#0000;background:0 0;border:none;padding:0 3px;font-size:12px;line-height:1.2;position:absolute;top:0;right:4px}.term-pane:hover .term-pane-kill{color:var(--fg-dim)}.term-pane-kill:hover,.term-pane-kill:focus-visible{color:var(--fg);background:0 0}.term-host{flex:1;min-width:0;min-height:0;padding:4px 0 0 8px}.term-host .xterm{height:100%}.theme-switch{background:var(--bg-hover);border:1px solid var(--border);width:40px;height:20px;color:var(--fg-dim);cursor:pointer;border-radius:10px;flex-shrink:0;justify-content:space-between;align-items:center;padding:0 4px;display:inline-flex;position:relative}.theme-switch input{opacity:0;cursor:pointer;margin:0;position:absolute;inset:0}.theme-switch svg{position:relative}.theme-switch .knob{background:var(--fg);border-radius:50%;width:14px;height:14px;transition:left .15s;position:absolute;top:2px;left:2px}.theme-switch input:checked~.knob{left:22px}.theme-switch:has(input:focus-visible){outline-offset:1px;outline:2px solid #5a80b0}.panes{flex:1;min-height:0;display:flex}.graph-pane{width:var(--graph-w,55%);flex-direction:column;flex-shrink:0;min-width:0;display:flex;position:relative}.graphview{flex:1;padding-bottom:8px;overflow:auto}.refs,.graph-col{transition:filter .15s}.graph-pane.busy .refs,.graph-pane.busy .graph-col{filter:blur(1px);pointer-events:none}.graph-pane.busy,.graph-pane.busy .row{cursor:progress}.graphview .row:not(.wip){content-visibility:auto;contain-intrinsic-size:auto 28px}.diff-overlay{z-index:2;background:var(--bg-panel);flex-direction:column;display:flex;position:absolute;inset:0}.diff-overlay-head{border-bottom:1px solid var(--border);flex-shrink:0;align-items:center;gap:8px;padding:6px 10px;display:flex}.diff-overlay-head .file-path{flex:1}.diff-close{background:0 0;border:none;padding:0 4px;font-size:14px}.searchbar{top:8px;left:calc(var(--refs-w) + var(--graph-col-w) + 24px);z-index:3;background:var(--bg-selected);border:1px solid var(--border);border-radius:8px;align-items:center;gap:6px;padding:5px 8px;animation:.12s ease-out appear;display:flex;position:absolute;box-shadow:0 3px 12px #00000059}.searchbar input{background:var(--bg-panel);width:220px;color:var(--fg);border:1px solid var(--border);font:inherit;border-radius:5px;padding:3px 6px}.searchbar .count{text-align:center;min-width:78px;color:var(--fg-dim);font-size:12px}.searchbar button{min-width:22px;color:var(--fg-dim);background:0 0;border:none;justify-content:center;align-items:center;padding:3px 5px;display:inline-flex}.searchbar button:hover,.searchbar button.deep.active{background:var(--bg-hover);color:var(--fg)}.splitter{cursor:col-resize;background:var(--border);touch-action:none;background-clip:padding-box;border:2px solid #0000;flex-shrink:0;width:5px}.splitter:hover,.splitter:active{background-color:#5a80b0}.row{cursor:pointer;white-space:nowrap;align-items:center;gap:8px;height:28px;padding:0 8px;scroll-margin-top:28px;display:flex;position:relative}.row.wip{background-color:var(--bg-panel);background-image:linear-gradient(to right, transparent calc(8px + var(--refs-w,120px)), var(--bg-wip) 0);z-index:1;font-style:italic;position:sticky;top:0}.row:hover:not(:has(.refs:hover)):not(.selected){background-image:linear-gradient(to right, transparent calc(8px + var(--refs-w,120px)), var(--bg-hover) 0)}.row.selected{background-image:linear-gradient(to right, transparent calc(8px + var(--refs-w,120px)), var(--bg-selected) 0)}.graph-cell{flex-shrink:0;overflow:visible}.refs{width:var(--refs-w,120px);cursor:default;flex-shrink:0;justify-content:flex-start;align-self:stretch;align-items:center;gap:4px;display:flex;overflow:hidden}.graph-col{width:var(--graph-col-w,90px);flex-shrink:0;justify-content:flex-start;align-self:stretch;align-items:center;padding:0 4px;display:flex;overflow:hidden}.col-splitter{cursor:col-resize;touch-action:none;z-index:1;width:5px;position:absolute;top:0;bottom:0}.col-splitter:hover,.col-splitter:active{background:#5a80b0}.ref-chips{gap:4px;min-width:0;display:flex}.ref-chip{color:var(--fg);background:var(--bg-panel);flex:1 1 calc(14px + var(--n-icons,0) * 15px);-webkit-user-select:none;user-select:none;cursor:pointer;border:1px solid;border-radius:8px;align-items:center;gap:3px;min-width:0;max-width:max-content;padding:1px 6px;font-size:14px;display:inline-flex}.ref-chip:hover{background:var(--bg-hover)}.ref-chip.head{background:var(--head-bg);color:var(--head-fg);font-weight:600}.ref-chip.head:hover{background:var(--head-bg-hover)}.ref-name{text-overflow:ellipsis;white-space:nowrap;min-width:0;overflow:hidden}.ref-line{opacity:.5;flex:1;align-self:center;min-width:6px;height:1px}.ref-bridge{opacity:.5;pointer-events:none;height:1px;position:absolute;top:calc(50% - .5px)}.ref-line.head,.ref-bridge.head{opacity:1;height:2px}.ref-bridge.head{top:calc(50% - 1px)}.ref-icon{opacity:.8;flex-shrink:0}.subject{text-overflow:ellipsis;flex:1;overflow:hidden}.date{color:var(--fg-dim);font-size:12px}.hash{color:var(--fg-faint);font-family:Ubuntu Mono,ui-monospace,monospace;font-size:12px}.load-more{margin:8px;padding:4px 14px}.ctx-menu{border:1px solid var(--border);background:var(--bg-panel);border-radius:6px;min-width:150px;margin:0;padding:4px;position:fixed;inset:auto;box-shadow:0 6px 20px #00000059}.ctx-item{width:100%;color:var(--fg);font:inherit;text-align:left;cursor:pointer;background:0 0;border:0;border-radius:4px;padding:5px 10px;font-size:14px;display:block}.ctx-item:hover{background:var(--bg-hover)}.ctx-item.sep{border-top:1px solid var(--border);border-radius:0 0 4px 4px;margin-top:5px;padding-top:9px}.ctx-item.danger{color:#e06c75}.ctx-item.danger:hover{color:#fff;background:#e06c75}.panel{flex-direction:column;flex:1;min-width:0;min-height:0;display:flex}.commit-msg{border-bottom:1px solid var(--border);flex-shrink:0;max-height:30%;padding:10px 12px;position:relative;overflow-y:auto}.commit-subject{font-weight:600}.commit-body{white-space:pre-wrap;color:var(--fg-dim);margin-top:8px;font-size:13px}.msg-edit{vertical-align:-4px;color:var(--fg-dim);opacity:0;margin-left:8px;padding:4px;line-height:0;transition:opacity .1s;display:inline-flex}.msg-edit:hover{color:var(--fg)}.commit-msg:hover .msg-edit,.msg-edit:focus-visible{opacity:1}.msg-box{border:1px solid var(--border);background:var(--bg);width:100%;min-height:84px;color:var(--fg);font:inherit;resize:vertical;border-radius:4px;padding:6px 8px;display:block}.msg-actions{align-items:center;gap:6px;margin-top:6px;display:flex}.msg-hint{color:var(--fg-faint);flex:1;font-size:12px}.msg-actions .primary{background:var(--head-bg);color:var(--head-fg);border-color:var(--head-fg)}.msg-actions .primary:disabled{opacity:.45}.head-actions,.row-action{gap:2px;display:flex}.head-actions button,.row-action{color:var(--fg-dim);background:0 0;border-color:#0000;padding:3px;line-height:0}.head-actions button:hover,.row-action:hover{border-color:var(--border);background:var(--bg-hover);color:var(--fg)}.head-actions .danger:hover,.row-action.danger:hover{color:#e06c75;border-color:#e06c75}.head-actions button:disabled,.row-action:disabled{opacity:.4}.section-head{z-index:1;background:var(--bg-panel);color:var(--fg);text-transform:uppercase;letter-spacing:.05em;cursor:pointer;-webkit-user-select:none;user-select:none;align-items:center;gap:5px;padding:8px 6px 5px;font-size:13px;font-weight:600;display:flex;position:sticky;top:0}.section-head:hover{background:var(--bg-hover)}.section-head .caret{color:var(--fg-dim);line-height:0;transition:transform .1s;display:flex}.section-head .caret.open{transform:rotate(90deg)}.section-head .count{background:var(--bg-selected);min-width:19px;height:19px;color:var(--fg);letter-spacing:0;border-radius:10px;justify-content:center;align-items:center;padding:0 5px;font-size:11px;display:inline-flex}.section-head .count.zero{background:var(--bg-hover);color:var(--fg-faint)}.section-head .head-actions{visibility:hidden;margin-left:auto}.section-head:hover .head-actions,.section-head .head-actions:focus-within{visibility:visible}.composer{border-bottom:1px solid var(--border);flex-shrink:0;padding:8px}.composer .msg-box{min-height:56px}.row-action{visibility:hidden}.row-action:first-of-type{margin-left:auto}.file-row:hover .row-action,.file-row.selected .row-action,.row-action:focus-visible{visibility:visible}.diff-side{border:1px solid var(--border);color:var(--fg-dim);border-radius:8px;padding:0 6px;font-size:12px}.commit-people{border-bottom:1px solid var(--border);flex-direction:column;flex-shrink:0;gap:6px;padding:10px 12px;display:flex;position:relative}.person{align-items:center;gap:8px;min-width:0;display:flex}.person-text{flex-direction:column;min-width:0;display:flex}.person-name{text-overflow:ellipsis;white-space:nowrap;overflow:hidden}.person-when{color:var(--fg-dim);font-size:12px}.face{object-fit:cover;border-radius:50%;flex-shrink:0;width:26px;height:26px}.face-fallback{background:var(--bg-hover);color:var(--fg-dim);justify-content:center;align-items:center;font-size:11px;display:inline-flex}.commit-hash{color:var(--fg-faint);font-family:Ubuntu Mono,ui-monospace,monospace;font-size:12px;position:absolute;top:10px;right:12px}.files-head{border-bottom:1px solid var(--border);flex-shrink:0;justify-content:space-between;align-items:center;gap:8px;padding:6px 12px;display:flex}.files-counts{color:var(--fg-dim);white-space:nowrap;gap:10px;font-size:12px;display:flex;overflow:hidden}.files-head .view-toggle button{padding:2px 10px;font-size:12px}.filelist{flex:1;padding:4px;overflow-y:auto}.file-dir{color:var(--fg-faint);text-overflow:ellipsis;white-space:nowrap;flex:0 auto;min-width:0;overflow:hidden}.dir-row{color:var(--fg-dim)}.dir-arrow{width:14px;color:var(--fg-faint);font-size:10px;line-height:inherit;flex-shrink:0}.file-row{cursor:pointer;border-radius:4px;gap:8px;padding:3px 8px;display:flex}.file-row:hover{background:var(--bg-hover)}.file-row.selected{background:var(--bg-selected)}.file-status{flex-shrink:0;width:14px;font-family:Ubuntu Mono,ui-monospace,monospace}.file-path{white-space:nowrap;min-width:0;display:flex;overflow:hidden}.file-name{flex:none}.diffview{flex-direction:column;flex:1;min-height:0;display:flex;overflow:auto}.diffview.empty{text-align:center;justify-content:center;align-items:center}.diff-state{text-align:center;flex:1;justify-content:center;align-items:center;display:flex}.diff-toolbar{border-bottom:1px solid var(--border);flex-shrink:0;justify-content:center;gap:8px;padding:6px 10px;display:flex}.diff-toolbar button{padding:3px 10px}.view-toggle{display:inline-flex}.view-toggle button{border-radius:0}.view-toggle button:first-child{border-radius:4px 0 0 4px}.view-toggle button:last-child{border-left:none;border-radius:0 4px 4px 0}.view-toggle button.active{background:var(--bg-selected)}.diff-html{flex:1;overflow:auto}.diff-html .d2h-file-wrapper{position:relative}.diff-html .d2h-file-diff{overflow-x:auto}.diff-html .d2h-diff-table{font-family:Ubuntu Mono,ui-monospace,monospace}.diff-html .d2h-code-linenumber,.diff-html .d2h-code-side-linenumber{cursor:default}.diff-html td.d2h-info .diff-expand-group{direction:ltr;gap:1px;padding-left:2px;display:flex}.diff-html .diff-expand{width:18px;height:18px;color:var(--fg-dim);cursor:pointer;background:0 0;border:0;border-radius:3px;justify-content:center;align-items:center;padding:0;display:flex}.diff-html .diff-expand:hover{background:var(--bg-hover);color:var(--fg)}.diff-plain{color:var(--fg-dim);padding:10px}.image-diff{flex:1;grid-template-columns:1fr 1fr;align-content:start;gap:12px;padding:12px;display:grid;overflow:auto}.image-pane{flex-direction:column;gap:6px;min-width:0;display:flex}.image-pane-label{color:var(--fg-dim);font-size:12px}.image-pane img{border:1px solid var(--border);background:repeating-conic-gradient(var(--bg-panel) 0% 25%, var(--bg) 0% 50%) 0 0 / 16px 16px;border-radius:4px;max-width:100%}.image-pane-none{text-align:center;color:var(--fg-dim);border:1px dashed var(--border);border-radius:4px;padding:20px}.d2h-wrapper{--d2h-bg-color:var(--bg-panel);--d2h-ins-bg-color:#e6ffec;--d2h-ins-highlight-bg-color:#abf2bc;--d2h-ins-border-color:#ccffd8;--d2h-ins-label-color:#1a7f37;--d2h-del-bg-color:#ffebe9;--d2h-del-highlight-bg-color:#ffcecb;--d2h-del-border-color:#ffd7d5;--d2h-del-label-color:#cf222e;--d2h-change-ins-color:#e6ffec;--d2h-change-del-color:#ffebe9;--d2h-info-bg-color:#ddf4ff;--d2h-info-border-color:#ddf4ff;--d2h-line-border-color:#d0d7de;--d2h-dark-bg-color:#23272e;--d2h-dark-border-color:#30363d;--d2h-dark-line-border-color:#30363d;--d2h-dark-ins-bg-color:#2ea04326;--d2h-dark-ins-highlight-bg-color:#2ea04366;--d2h-dark-ins-border-color:#3fb9504d;--d2h-dark-ins-label-color:#3fb950;--d2h-dark-del-bg-color:#f851491a;--d2h-dark-del-highlight-bg-color:#f8514966;--d2h-dark-del-border-color:#f851494d;--d2h-dark-del-label-color:#f85149;--d2h-dark-change-ins-color:#2ea04326;--d2h-dark-change-del-color:#f851491a;--d2h-dark-info-bg-color:#388bfd1a;--d2h-dark-info-border-color:#388bfd4d}.d2h-wrapper .hljs{background:0 0}.conflict-banner{background:var(--bg-wip);border-bottom:1px solid var(--border);flex-shrink:0;align-items:center;gap:10px;padding:6px 10px;font-size:12px;display:flex}.conflict-banner .cb-verb{font-weight:600}.conflict-banner .cb-label{color:var(--fg-dim);text-overflow:ellipsis;white-space:nowrap;max-width:40ch;overflow:hidden}.conflict-banner .cb-count{color:var(--fg-dim);margin-left:auto}.conflict-banner button{padding:3px 10px;font-size:12px}.conflict-banner button.danger{color:#e06c75;border-color:#e06c75}.conflict-banner button.primary:not(:disabled){background:var(--head-bg);color:var(--head-fg);border-color:var(--head-bg-hover)}.conflict-banner button:disabled{opacity:.5;cursor:default}.cf-view{flex:1;font-family:ui-monospace,SFMono-Regular,Menlo,monospace;font-size:12px;line-height:1.5;overflow:auto}.cf-progress{z-index:1;background:var(--bg-panel);border-bottom:1px solid var(--border);color:var(--fg-dim);padding:4px 10px;position:sticky;top:0}.cf-view pre{white-space:pre-wrap;word-break:break-word;margin:0;padding:0 10px}.cf-context{color:var(--fg-dim)}.cf-block{border:1px solid var(--border);border-radius:4px;margin:6px 0;overflow:hidden}.cf-block.picked{border-color:var(--head-bg-hover)}.cf-bar{background:var(--bg-hover);border-bottom:1px solid var(--border);align-items:center;gap:6px;padding:3px 8px;display:flex}.cf-side-name{color:var(--fg-dim);margin-right:auto}.cf-chosen{color:var(--head-fg);margin-right:auto}.cf-bar button{padding:2px 8px;font-size:11px}.cf-ours{background:#98c3791f}.cf-theirs{background:#61afef1f}.cf-mid{color:var(--fg-dim);border-top:1px dashed var(--border);border-bottom:1px dashed var(--border);padding:2px 8px}.cf-card{border:1px solid var(--border);text-align:center;border-radius:6px;max-width:420px;margin:20px auto;padding:16px;font-family:inherit}.cf-card-note{color:var(--fg-dim);margin-bottom:12px}.cf-card-actions{justify-content:center;gap:8px;display:flex}.cf-card-actions button.danger{color:#e06c75;border-color:#e06c75}.diff-side.conflict{color:#e06c75}
@@ -1,21 +0,0 @@
1
- // node-pty ships spawn-helper as a prebuilt binary, and some package managers drop
2
- // the executable bit when extracting the tarball — the terminal then fails to spawn.
3
- // Resolve node-pty rather than guessing ./node_modules: under `npx` it is hoisted to
4
- // the installing project's node_modules, not ours.
5
- import { chmodSync, readdirSync } from 'node:fs'
6
- import { createRequire } from 'node:module'
7
- import { dirname, join } from 'node:path'
8
-
9
- try {
10
- const require = createRequire(import.meta.url)
11
- const prebuilds = join(dirname(require.resolve('node-pty/package.json')), 'prebuilds')
12
- for (const platform of readdirSync(prebuilds)) {
13
- try {
14
- chmodSync(join(prebuilds, platform, 'spawn-helper'), 0o755)
15
- } catch {
16
- // win32 prebuilds have no spawn-helper; nothing to do
17
- }
18
- }
19
- } catch {
20
- // node-pty is optional — absent on Linux installs without build tools
21
- }