megit-app 0.10.0 → 0.11.0

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.
package/dist/index.html CHANGED
@@ -5,8 +5,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
7
  <title>megit</title>
8
- <script type="module" crossorigin src="/assets/index-l_8r494M.js"></script>
9
- <link rel="stylesheet" crossorigin href="/assets/index-C4TwOraO.css">
8
+ <script type="module" crossorigin src="/assets/index-JAmzg-th.js"></script>
9
+ <link rel="stylesheet" crossorigin href="/assets/index-BXzSxW-2.css">
10
10
  </head>
11
11
  <body>
12
12
  <div id="root"></div>
@@ -1,6 +1,6 @@
1
1
  import { createApp, serveStatic } from './http.js';
2
2
  import { execFile } from 'node:child_process';
3
- import { existsSync, readdirSync } from 'node:fs';
3
+ import { existsSync, readdirSync, readFileSync } from 'node:fs';
4
4
  import { readFile, realpath, writeFile } from 'node:fs/promises';
5
5
  import { homedir } from 'node:os';
6
6
  import { dirname, join, resolve, sep } from 'node:path';
@@ -941,6 +941,66 @@ app.get('/api/blob', repoGuard, async (req, res) => {
941
941
  res.status(404).end();
942
942
  }
943
943
  });
944
+ // The repo's own favicon, used as its icon in the picker. Checked against the
945
+ // framework conventions people actually ship; no hit is a 404 and the client
946
+ // falls back to a letter chip. Not repoGuard'd: a recent repo need not be open,
947
+ // but it must still be one the user has added at some point.
948
+ // Most projects don't ship a file literally called favicon.*: they declare one
949
+ // in their entry html (megit's own is /logo.svg). Reading that link first is
950
+ // what makes the icon right rather than merely present.
951
+ function declaredIcon(repo) {
952
+ for (const html of ['index.html', 'public/index.html', 'app/index.html', 'src/index.html']) {
953
+ const file = join(repo, html);
954
+ if (!existsSync(file))
955
+ continue;
956
+ const href = /<link[^>]+rel=["'][^"']*icon[^"']*["'][^>]*>/i.exec(readFileSync(file, 'utf8'))?.[0]
957
+ ?.match(/href=["']([^"']+)["']/i)?.[1];
958
+ // data: and http: hrefs have nothing on disk to serve
959
+ if (!href || /^[a-z]+:/i.test(href))
960
+ continue;
961
+ const rel = href.split(/[?#]/)[0].replace(/^\/+/, '');
962
+ return [rel, join('public', rel), join('static', rel), join(dirname(html), rel)];
963
+ }
964
+ return [];
965
+ }
966
+ const FAVICONS = [
967
+ 'public/favicon.svg', 'public/favicon.ico', 'public/favicon.png', 'public/favicon-32x32.png',
968
+ 'app/favicon.ico', 'src/favicon.svg', 'static/favicon.svg', 'static/favicon.ico', 'static/favicon.png',
969
+ 'assets/favicon.ico', 'favicon.svg', 'favicon.ico', 'favicon.png',
970
+ ];
971
+ app.get('/api/favicon', async (req, res) => {
972
+ const repo = String(req.query.repo ?? '');
973
+ const cfg = loadConfig();
974
+ if (!cfg.repos.includes(repo) && !cfg.recent.includes(repo)) {
975
+ res.status(400).json({ error: 'unknown repo' });
976
+ return;
977
+ }
978
+ const hit = [...declaredIcon(repo), ...FAVICONS].map(f => join(repo, f)).find(f => existsSync(f) && MIME[f.split('.').pop().toLowerCase()]);
979
+ if (!hit) {
980
+ res.status(404).end();
981
+ return;
982
+ }
983
+ try {
984
+ // same symlink escape as /api/blob: a clone can carry public/favicon.ico ->
985
+ // /etc/passwd, and serving it would leak the bytes under an image type
986
+ const abs = await realpath(hit);
987
+ const root = await realpath(repo);
988
+ if (!abs.startsWith(root + sep)) {
989
+ res.status(400).json({ error: 'path outside repo' });
990
+ return;
991
+ }
992
+ // repo content is untrusted (an SVG can carry script)
993
+ res.set('Content-Security-Policy', "sandbox; default-src 'none'").set('X-Content-Type-Options', 'nosniff');
994
+ // A project's icon changes about never, and every picker open would otherwise
995
+ // re-walk the filesystem for each recent repo. Short enough that swapping a
996
+ // favicon shows up without a restart.
997
+ res.set('Cache-Control', 'private, max-age=300');
998
+ res.type(MIME[hit.split('.').pop().toLowerCase()]).send(await readFile(abs));
999
+ }
1000
+ catch {
1001
+ res.status(404).end();
1002
+ }
1003
+ });
944
1004
  const dist = join(import.meta.dirname, '..', 'dist');
945
1005
  if (existsSync(dist))
946
1006
  app.fallback(serveStatic(dist));
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "megit-app",
3
- "version": "0.10.0",
3
+ "version": "0.11.0",
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",
@@ -48,7 +48,7 @@
48
48
  "devDependencies": {
49
49
  "@types/node": "^26.4.1",
50
50
  "@types/react": "^19.2.18",
51
- "@types/react-dom": "^19.2.5",
51
+ "@types/react-dom": "^19.2.7",
52
52
  "@types/ws": "^8.18.1",
53
53
  "@vitejs/plugin-react": "^6.1.1",
54
54
  "@xterm/addon-fit": "^0.11.0",
@@ -60,6 +60,6 @@
60
60
  "react-dom": "^19.2.8",
61
61
  "typescript": "^7.0.2",
62
62
  "vite": "^8.2.2",
63
- "vitest": "^4.1.11"
63
+ "vitest": "^5.0.0"
64
64
  }
65
65
  }
@@ -1 +0,0 @@
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{--font-stack:"Ubuntu Mono", ui-monospace, monospace;--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{font:16px/1.4 var(--font-stack);background:var(--bg);color:var(--fg)}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}.brand{align-self:center;align-items:flex-end;gap:5px;margin:0 6px 6px 2px;display:flex}.logo{pointer-events:none;width:18px;height:18px;display:block}.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;font-size:11px;line-height:1}.tab-cog{color:var(--fg-faint);background:0 0;border:none;align-self:center;align-items:center;margin:0 4px 6px auto;padding:3px;display:inline-flex}.tab-cog:hover{color:var(--fg);background:0 0}.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:70%;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}.modal.settings{--s1:4px;--s2:8px;--s3:12px;--s4:16px;--s5:24px;--t1:12px;--t2:14px;--t3:16px;--ctrl-h:26px;--ctrl-w:232px;flex-direction:column;width:620px;max-width:92%;display:flex}.modal.settings .modal-head{padding:var(--s3) var(--s5)}.modal.settings .modal-head b{font-size:var(--t3);color:var(--fg)}.settings-close{width:24px;height:24px;color:var(--fg-dim);background:0 0;border:none;border-radius:4px;justify-content:center;align-items:center;padding:0;display:inline-flex}.settings-close:hover{background:var(--bg-hover);color:var(--fg)}.settings-body{padding:var(--s4) var(--s5) var(--s5);flex:1;overflow-y:auto}.settings-group+.settings-group{margin-top:var(--s5)}.settings-h{margin-bottom:var(--s2);font-size:var(--t2);color:var(--fg);font-weight:600}.settings-row{grid-template-columns:1fr var(--ctrl-w);align-items:center;gap:var(--s3);min-height:30px;display:grid}.settings-row>label{font-size:var(--t2);color:var(--fg)}.modal.settings select,.modal.settings input:not([type=checkbox]){width:100%;height:var(--ctrl-h);padding:0 var(--s2);font:inherit;font-size:var(--t2);background:var(--bg-panel);color:var(--fg);border:1px solid var(--border);border-radius:5px}.modal.settings select{appearance:none;padding-right:var(--s5);background-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 12 12' fill='none' stroke='%239aa4b2' stroke-width='1.5'%3E%3Cpath d='M3 4.5 6 7.5 9 4.5'/%3E%3C/svg%3E");background-repeat:no-repeat;background-position:right var(--s2) center;background-size:12px}.modal.settings input[type=checkbox]{accent-color:#5a80b0;justify-self:end;width:15px;height:15px;margin:0}.modal.settings .theme-switch{justify-self:end}.zoom-presets{grid-template-columns:repeat(4,1fr);display:grid}.zoom-presets button{height:var(--ctrl-h);color:var(--fg-dim);border-radius:0;justify-content:center;align-items:center;padding:0;display:inline-flex}.zoom-presets button+button{border-left:none}.zoom-presets button:first-child{border-radius:5px 0 0 5px}.zoom-presets button:last-child{border-radius:0 5px 5px 0}.zoom-presets button.active{background:var(--bg-selected);color:var(--fg)}.zoom-presets [data-z=sm]{font-size:10px}.zoom-presets [data-z=md]{font-size:12px}.zoom-presets [data-z=lg]{font-size:14px}.zoom-presets [data-z=xl]{font-size:16px}.settings-warn{margin:var(--s1) 0 var(--s3);font-size:var(--t1);color:var(--fg-faint);line-height:1.45}.sc-group+.sc-group{margin-top:var(--s3)}.sc-group-name{margin-bottom:var(--s1);font-size:var(--t1);color:var(--fg-faint)}.sc-row{gap:var(--s3);grid-template-columns:108px 1fr;align-items:center;min-height:26px;display:grid}.sc-keys{justify-content:flex-end;align-items:center;gap:3px;display:flex}.sc-alt,.sc-alt>span{align-items:center;gap:3px;display:inline-flex}.sc-row kbd{border:1px solid var(--border);background:var(--bg-hover);min-width:22px;height:20px;font:inherit;font-size:var(--t1);color:var(--fg-dim);border-radius:4px;justify-content:center;align-items:center;padding:0 5px;line-height:1;display:inline-flex}.sc-plus{color:var(--fg-faint);flex:none}.sc-label{font-size:var(--t2);color:var(--fg-dim)}.settings-foot{margin-top:var(--s5);padding-top:var(--s3);border-top:1px solid var(--border);font-size:var(--t1);color:var(--fg-faint);text-align:center}.settings-foot a{color:var(--fg-dim)}.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:80%;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-filter{background:var(--bg-panel);color:var(--fg);border:1px solid var(--border);font:inherit;border-radius:5px;min-width:0;margin:2px 12px 4px;padding:3px 6px}.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:var(--font-stack);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}.amend-toggle{color:var(--fg-dim);cursor:pointer;flex-shrink:0;align-items:center;gap:5px;font-size:12px;display:flex}.amend-toggle input{cursor:pointer;margin:0}.amend-warn{color:#e5c07b;margin-top:6px;font-size:12px;line-height:1.4}.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:var(--font-stack);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{font-family:var(--font-stack);flex-shrink:0;width:14px}.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:var(--font-stack)}.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}