feat: add clide-web package replacing ttyd + zellij
Python web server that wraps Clide for browser access. Replaces the previous ttyd (C binary) + zellij (Rust binary) stack with a single FastAPI application using tmux for session persistence. Key features: - WebSocket ↔ PTY bridge via tmux attach - Project switching via /projects/<name> URL routing - Vendored xterm.js for offline LAN operation - Auto-respawn on Clide exit (tmux pane-died hook) - Setup wizard for first-run configuration - No scrollbar (TUI manages its own scrolling) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,388 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<title>Clide</title>
|
||||
<link rel="stylesheet" href="/static/vendor/xterm.min.css">
|
||||
<style>
|
||||
|
||||
* { margin: 0; padding: 0; box-sizing: border-box; }
|
||||
html, body {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
overflow: hidden;
|
||||
background: #21262f;
|
||||
font-family: 'JetBrains Mono', monospace;
|
||||
/* Kill all scrollbars */
|
||||
scrollbar-width: none; /* Firefox */
|
||||
-ms-overflow-style: none; /* IE/Edge */
|
||||
}
|
||||
html::-webkit-scrollbar,
|
||||
body::-webkit-scrollbar {
|
||||
display: none; /* Chrome/Safari */
|
||||
}
|
||||
/* Kill xterm.js internal scrollbar — TUI handles its own scrolling */
|
||||
.xterm-viewport {
|
||||
scrollbar-width: none !important;
|
||||
-ms-overflow-style: none !important;
|
||||
}
|
||||
.xterm-viewport::-webkit-scrollbar {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
/* Toolbar */
|
||||
#toolbar {
|
||||
height: 36px;
|
||||
background: #292e38;
|
||||
border-bottom: 1px solid #393e48;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 0 12px;
|
||||
gap: 12px;
|
||||
font-family: system-ui, -apple-system, sans-serif;
|
||||
color: #e2e8f5;
|
||||
font-size: 13px;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
#toolbar .logo {
|
||||
font-weight: 600;
|
||||
color: #00a3d2;
|
||||
font-family: 'JetBrains Mono', monospace;
|
||||
letter-spacing: 0.5px;
|
||||
}
|
||||
|
||||
#toolbar .separator {
|
||||
width: 1px;
|
||||
height: 20px;
|
||||
background: #393e48;
|
||||
}
|
||||
|
||||
#toolbar select {
|
||||
background: #393e48;
|
||||
color: #e2e8f5;
|
||||
border: 1px solid #525762;
|
||||
border-radius: 4px;
|
||||
padding: 4px 8px;
|
||||
font-size: 12px;
|
||||
font-family: system-ui, -apple-system, sans-serif;
|
||||
cursor: pointer;
|
||||
outline: none;
|
||||
}
|
||||
#toolbar select:hover {
|
||||
border-color: #00a3d2;
|
||||
}
|
||||
#toolbar select:focus {
|
||||
border-color: #00a3d2;
|
||||
box-shadow: 0 0 0 1px #00a3d2;
|
||||
}
|
||||
|
||||
#toolbar label {
|
||||
font-size: 11px;
|
||||
color: #898e9a;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.5px;
|
||||
}
|
||||
|
||||
.toolbar-group {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
}
|
||||
|
||||
.toolbar-spacer { flex: 1; }
|
||||
|
||||
#status {
|
||||
font-size: 12px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
}
|
||||
#status .dot {
|
||||
width: 8px;
|
||||
height: 8px;
|
||||
border-radius: 50%;
|
||||
background: #525762;
|
||||
transition: background 0.3s;
|
||||
}
|
||||
#status .dot.connected { background: #00ab9a; }
|
||||
#status .dot.reconnecting { background: #d08447; animation: pulse 1s infinite; }
|
||||
|
||||
@keyframes pulse {
|
||||
0%, 100% { opacity: 1; }
|
||||
50% { opacity: 0.4; }
|
||||
}
|
||||
|
||||
/* Terminal container */
|
||||
#terminal-container {
|
||||
height: calc(100% - 36px);
|
||||
width: 100%;
|
||||
position: absolute;
|
||||
top: 36px;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
}
|
||||
|
||||
/* Reconnect overlay */
|
||||
#overlay {
|
||||
display: none;
|
||||
position: fixed;
|
||||
top: 0; left: 0;
|
||||
width: 100%; height: 100%;
|
||||
background: rgba(33, 38, 47, 0.92);
|
||||
z-index: 100;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
flex-direction: column;
|
||||
gap: 12px;
|
||||
color: #e2e8f5;
|
||||
font-family: system-ui, -apple-system, sans-serif;
|
||||
}
|
||||
#overlay .message {
|
||||
font-size: 16px;
|
||||
font-weight: 500;
|
||||
}
|
||||
#overlay .sub {
|
||||
font-size: 13px;
|
||||
color: #898e9a;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div id="toolbar">
|
||||
<span class="logo">Clide</span>
|
||||
<div class="separator"></div>
|
||||
<div class="toolbar-group">
|
||||
<label>Project</label>
|
||||
<select id="project-select"><option value="">Loading...</option></select>
|
||||
</div>
|
||||
<div class="toolbar-spacer"></div>
|
||||
<div id="status">
|
||||
<div class="dot" id="status-dot"></div>
|
||||
<span id="status-text">Connecting...</span>
|
||||
</div>
|
||||
</div>
|
||||
<div id="terminal-container"></div>
|
||||
<div id="overlay">
|
||||
<div class="message">Reconnecting...</div>
|
||||
<div class="sub">Session is preserved</div>
|
||||
</div>
|
||||
|
||||
<script src="/static/vendor/xterm.min.js"></script>
|
||||
<script src="/static/vendor/addon-fit.min.js"></script>
|
||||
<script src="/static/vendor/addon-web-links.min.js"></script>
|
||||
<script>
|
||||
(function() {
|
||||
"use strict";
|
||||
|
||||
// --- State ---
|
||||
// Extract project from path: /projects/<name> or fallback to ?project=<name>
|
||||
function getProjectFromUrl() {
|
||||
var match = location.pathname.match(/^\/projects\/([^/]+)/);
|
||||
if (match) return decodeURIComponent(match[1]);
|
||||
return new URLSearchParams(location.search).get("project") || "";
|
||||
}
|
||||
let currentProject = getProjectFromUrl();
|
||||
let ws = null;
|
||||
let reconnectTimer = null;
|
||||
let reconnectDelay = 1000;
|
||||
|
||||
// --- DOM refs ---
|
||||
const statusDot = document.getElementById("status-dot");
|
||||
const statusText = document.getElementById("status-text");
|
||||
const overlay = document.getElementById("overlay");
|
||||
const projectSelect = document.getElementById("project-select");
|
||||
|
||||
// --- Terminal ---
|
||||
const term = new Terminal({
|
||||
fontFamily: "'JetBrains Mono', monospace",
|
||||
fontSize: 14,
|
||||
theme: {
|
||||
background: "#21262f",
|
||||
foreground: "#e2e8f5",
|
||||
cursor: "#00a3d2",
|
||||
cursorAccent: "#21262f",
|
||||
selectionBackground: "rgba(0, 163, 210, 0.3)",
|
||||
black: "#21262f",
|
||||
red: "#f06c6f",
|
||||
green: "#00ab9a",
|
||||
yellow: "#d08447",
|
||||
blue: "#00a3d2",
|
||||
magenta: "#fa5f8b",
|
||||
cyan: "#00a9b9",
|
||||
white: "#e2e8f5",
|
||||
brightBlack: "#393e48",
|
||||
brightRed: "#f06c6f",
|
||||
brightGreen: "#00ab9a",
|
||||
brightYellow: "#d08447",
|
||||
brightBlue: "#00a3d2",
|
||||
brightMagenta: "#fa5f8b",
|
||||
brightCyan: "#00a9b9",
|
||||
brightWhite: "#e2e8f5",
|
||||
},
|
||||
cursorBlink: true,
|
||||
allowProposedApi: true,
|
||||
scrollback: 0,
|
||||
});
|
||||
const fitAddon = new FitAddon.FitAddon();
|
||||
term.loadAddon(fitAddon);
|
||||
term.loadAddon(new WebLinksAddon.WebLinksAddon());
|
||||
term.open(document.getElementById("terminal-container"));
|
||||
fitAddon.fit();
|
||||
|
||||
// --- Status helpers ---
|
||||
function setStatus(state, text) {
|
||||
statusDot.className = "dot " + state;
|
||||
statusText.textContent = text;
|
||||
}
|
||||
|
||||
// --- WebSocket ---
|
||||
function connect() {
|
||||
if (ws) {
|
||||
ws.onclose = null;
|
||||
ws.close();
|
||||
}
|
||||
|
||||
const proto = location.protocol === "https:" ? "wss:" : "ws:";
|
||||
const url = proto + "//" + location.host + "/projects/" + encodeURIComponent(currentProject) + "/ws";
|
||||
ws = new WebSocket(url);
|
||||
ws.binaryType = "arraybuffer";
|
||||
|
||||
ws.onopen = function() {
|
||||
setStatus("connected", currentProject || "Connected");
|
||||
overlay.style.display = "none";
|
||||
reconnectDelay = 1000;
|
||||
|
||||
// Send initial terminal size (slight delay to ensure xterm is rendered)
|
||||
setTimeout(function() {
|
||||
fitAddon.fit();
|
||||
sendResize();
|
||||
}, 50);
|
||||
};
|
||||
|
||||
ws.onmessage = function(evt) {
|
||||
let prefix, payload;
|
||||
if (evt.data instanceof ArrayBuffer) {
|
||||
const bytes = new Uint8Array(evt.data);
|
||||
if (bytes.length < 1) return;
|
||||
prefix = String.fromCharCode(bytes[0]);
|
||||
payload = bytes.slice(1);
|
||||
if (prefix === "0") {
|
||||
term.write(payload);
|
||||
} else if (prefix === "1") {
|
||||
handleControl(new TextDecoder().decode(payload));
|
||||
}
|
||||
} else {
|
||||
// Text frame
|
||||
prefix = evt.data[0];
|
||||
payload = evt.data.slice(1);
|
||||
if (prefix === "0") {
|
||||
term.write(payload);
|
||||
} else if (prefix === "1") {
|
||||
handleControl(payload);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
ws.onclose = function() {
|
||||
setStatus("reconnecting", "Reconnecting...");
|
||||
overlay.style.display = "flex";
|
||||
reconnectTimer = setTimeout(function() {
|
||||
reconnectDelay = Math.min(reconnectDelay * 1.5, 10000);
|
||||
connect();
|
||||
}, reconnectDelay);
|
||||
};
|
||||
|
||||
ws.onerror = function() {
|
||||
// onclose will fire after this
|
||||
};
|
||||
}
|
||||
|
||||
// --- Terminal input → WebSocket ---
|
||||
term.onData(function(data) {
|
||||
if (ws && ws.readyState === WebSocket.OPEN) {
|
||||
ws.send("0" + data);
|
||||
}
|
||||
});
|
||||
|
||||
// --- Resize ---
|
||||
function sendResize() {
|
||||
if (ws && ws.readyState === WebSocket.OPEN) {
|
||||
const dims = fitAddon.proposeDimensions();
|
||||
if (dims) {
|
||||
ws.send("2" + dims.cols + "," + dims.rows);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const resizeObserver = new ResizeObserver(function() {
|
||||
fitAddon.fit();
|
||||
sendResize();
|
||||
});
|
||||
resizeObserver.observe(document.getElementById("terminal-container"));
|
||||
|
||||
// --- Control messages ---
|
||||
function handleControl(jsonStr) {
|
||||
let msg;
|
||||
try { msg = JSON.parse(jsonStr); } catch(e) { return; }
|
||||
|
||||
if (msg.type === "projects") {
|
||||
populateProjects(msg.projects);
|
||||
} else if (msg.type === "session_info") {
|
||||
setStatus("connected", msg.project);
|
||||
document.title = "Clide — " + msg.project;
|
||||
} else if (msg.type === "error") {
|
||||
term.write("\r\n\x1b[31mError: " + msg.message + "\x1b[0m\r\n");
|
||||
}
|
||||
}
|
||||
|
||||
// --- Project management ---
|
||||
function populateProjects(projects) {
|
||||
projectSelect.innerHTML = "";
|
||||
projects.forEach(function(p) {
|
||||
const opt = document.createElement("option");
|
||||
opt.value = p;
|
||||
opt.textContent = p;
|
||||
if (p === currentProject) opt.selected = true;
|
||||
projectSelect.appendChild(opt);
|
||||
});
|
||||
}
|
||||
|
||||
projectSelect.addEventListener("change", function() {
|
||||
currentProject = projectSelect.value;
|
||||
history.pushState(null, "", "/projects/" + encodeURIComponent(currentProject));
|
||||
document.title = "Clide — " + currentProject;
|
||||
if (reconnectTimer) clearTimeout(reconnectTimer);
|
||||
term.clear();
|
||||
term.reset();
|
||||
connect();
|
||||
});
|
||||
|
||||
// --- Load projects and connect ---
|
||||
fetch("/api/projects")
|
||||
.then(function(r) { return r.json(); })
|
||||
.then(function(data) {
|
||||
populateProjects(data.projects);
|
||||
// If no project selected, pick first available
|
||||
if (!currentProject && data.projects.length > 0) {
|
||||
currentProject = data.projects[0];
|
||||
projectSelect.value = currentProject;
|
||||
history.replaceState(null, "", "/projects/" + encodeURIComponent(currentProject));
|
||||
}
|
||||
if (currentProject) {
|
||||
connect();
|
||||
} else {
|
||||
setStatus("", "No projects found");
|
||||
term.write("\r\nNo projects found in configured projects directory.\r\n");
|
||||
}
|
||||
})
|
||||
.catch(function() {
|
||||
setStatus("reconnecting", "Server unreachable");
|
||||
});
|
||||
|
||||
})();
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,8 @@
|
||||
/**
|
||||
* Skipped minification because the original files appears to be already minified.
|
||||
* Original file: /npm/@xterm/addon-fit@0.11.0/lib/addon-fit.js
|
||||
*
|
||||
* Do NOT use SRI with dynamically generated files! More information: https://www.jsdelivr.com/using-sri-with-dynamic-files
|
||||
*/
|
||||
!function(e,t){"object"==typeof exports&&"object"==typeof module?module.exports=t():"function"==typeof define&&define.amd?define([],t):"object"==typeof exports?exports.FitAddon=t():e.FitAddon=t()}(globalThis,(()=>(()=>{"use strict";var e={};return(()=>{var t=e;Object.defineProperty(t,"__esModule",{value:!0}),t.FitAddon=void 0,t.FitAddon=class{activate(e){this._terminal=e}dispose(){}fit(){const e=this.proposeDimensions();if(!e||!this._terminal||isNaN(e.cols)||isNaN(e.rows))return;const t=this._terminal._core;this._terminal.rows===e.rows&&this._terminal.cols===e.cols||(t._renderService.clear(),this._terminal.resize(e.cols,e.rows))}proposeDimensions(){if(!this._terminal)return;if(!this._terminal.element||!this._terminal.element.parentElement)return;const e=this._terminal._core._renderService.dimensions;if(0===e.css.cell.width||0===e.css.cell.height)return;const t=0===this._terminal.options.scrollback?0:this._terminal.options.overviewRuler?.width||14,r=window.getComputedStyle(this._terminal.element.parentElement),i=parseInt(r.getPropertyValue("height")),o=Math.max(0,parseInt(r.getPropertyValue("width"))),s=window.getComputedStyle(this._terminal.element),n=i-(parseInt(s.getPropertyValue("padding-top"))+parseInt(s.getPropertyValue("padding-bottom"))),l=o-(parseInt(s.getPropertyValue("padding-right"))+parseInt(s.getPropertyValue("padding-left")))-t;return{cols:Math.max(2,Math.floor(l/e.css.cell.width)),rows:Math.max(1,Math.floor(n/e.css.cell.height))}}}})(),e})()));
|
||||
//# sourceMappingURL=addon-fit.js.map
|
||||
@@ -0,0 +1,8 @@
|
||||
/**
|
||||
* Skipped minification because the original files appears to be already minified.
|
||||
* Original file: /npm/@xterm/addon-web-links@0.12.0/lib/addon-web-links.js
|
||||
*
|
||||
* Do NOT use SRI with dynamically generated files! More information: https://www.jsdelivr.com/using-sri-with-dynamic-files
|
||||
*/
|
||||
!function(e,t){"object"==typeof exports&&"object"==typeof module?module.exports=t():"function"==typeof define&&define.amd?define([],t):"object"==typeof exports?exports.WebLinksAddon=t():e.WebLinksAddon=t()}(globalThis,(()=>(()=>{"use strict";var e={490:(e,t)=>{function n(e){try{const t=new URL(e),n=t.password&&t.username?`${t.protocol}//${t.username}:${t.password}@${t.host}`:t.username?`${t.protocol}//${t.username}@${t.host}`:`${t.protocol}//${t.host}`;return e.toLocaleLowerCase().startsWith(n.toLocaleLowerCase())}catch(e){return!1}}Object.defineProperty(t,"__esModule",{value:!0}),t.LinkComputer=t.WebLinkProvider=void 0,t.WebLinkProvider=class{constructor(e,t,n,o={}){this._terminal=e,this._regex=t,this._handler=n,this._options=o}provideLinks(e,t){const n=o.computeLink(e,this._regex,this._terminal,this._handler);t(this._addCallbacks(n))}_addCallbacks(e){return e.map((e=>(e.leave=this._options.leave,e.hover=(t,n)=>{if(this._options.hover){const{range:o}=e;this._options.hover(t,n,o)}},e)))}};class o{static computeLink(e,t,r,i){const s=new RegExp(t.source,(t.flags||"")+"g"),[a,c]=o._getWindowedLineStrings(e-1,r),l=a.join("");let d;const p=[];for(;d=s.exec(l);){const e=d[0];if(!n(e))continue;const[t,s]=o._mapStrIdx(r,c,0,d.index),[a,l]=o._mapStrIdx(r,t,s,e.length);if(-1===t||-1===s||-1===a||-1===l)continue;const h={start:{x:s+1,y:t+1},end:{x:l,y:a+1}};p.push({range:h,text:e,activate:i})}return p}static _getWindowedLineStrings(e,t){let n,o=e,r=e,i=0,s="";const a=[];if(n=t.buffer.active.getLine(e)){const e=n.translateToString(!0);if(n.isWrapped&&" "!==e[0]){for(i=0;(n=t.buffer.active.getLine(--o))&&i<2048&&(s=n.translateToString(!0),i+=s.length,a.push(s),n.isWrapped&&-1===s.indexOf(" ")););a.reverse()}for(a.push(e),i=0;(n=t.buffer.active.getLine(++r))&&n.isWrapped&&i<2048&&(s=n.translateToString(!0),i+=s.length,a.push(s),-1===s.indexOf(" ")););}return[a,o]}static _mapStrIdx(e,t,n,o){const r=e.buffer.active,i=r.getNullCell();let s=n;for(;o;){const e=r.getLine(t);if(!e)return[-1,-1];for(let n=s;n<e.length;++n){e.getCell(n,i);const s=i.getChars();if(i.getWidth()&&(o-=s.length||1,n===e.length-1&&""===s)){const e=r.getLine(t+1);e&&e.isWrapped&&(e.getCell(0,i),2===i.getWidth()&&(o+=1))}if(o<0)return[t,n]}t++,s=0}return[t,s]}}t.LinkComputer=o}},t={};function n(o){var r=t[o];if(void 0!==r)return r.exports;var i=t[o]={exports:{}};return e[o](i,i.exports,n),i.exports}var o={};return(()=>{var e=o;Object.defineProperty(e,"__esModule",{value:!0}),e.WebLinksAddon=void 0;const t=n(490),r=/(https?|HTTPS?):[/]{2}[^\s"'!*(){}|\\\^<>`]*[^\s"':,.!?{}|\\\^~\[\]`()<>]/;function i(e,t){const n=window.open();if(n){try{n.opener=null}catch{}n.location.href=t}else console.warn("Opening link blocked as opener could not be cleared")}e.WebLinksAddon=class{constructor(e=i,t={}){this._handler=e,this._options=t}activate(e){this._terminal=e;const n=this._options,o=n.urlRegex||r;this._linkProvider=this._terminal.registerLinkProvider(new t.WebLinkProvider(this._terminal,o,this._handler,n))}dispose(){this._linkProvider?.dispose()}}})(),o})()));
|
||||
//# sourceMappingURL=addon-web-links.js.map
|
||||
@@ -0,0 +1,8 @@
|
||||
/**
|
||||
* Minified by jsDelivr using clean-css v5.3.3.
|
||||
* Original file: /npm/@xterm/xterm@5.5.0/css/xterm.css
|
||||
*
|
||||
* Do NOT use SRI with dynamically generated files! More information: https://www.jsdelivr.com/using-sri-with-dynamic-files
|
||||
*/
|
||||
.xterm{cursor:text;position:relative;user-select:none;-ms-user-select:none;-webkit-user-select:none}.xterm.focus,.xterm:focus{outline:0}.xterm .xterm-helpers{position:absolute;top:0;z-index:5}.xterm .xterm-helper-textarea{padding:0;border:0;margin:0;position:absolute;opacity:0;left:-9999em;top:0;width:0;height:0;z-index:-5;white-space:nowrap;overflow:hidden;resize:none}.xterm .composition-view{background:#000;color:#fff;display:none;position:absolute;white-space:nowrap;z-index:1}.xterm .composition-view.active{display:block}.xterm .xterm-viewport{background-color:#000;overflow-y:scroll;cursor:default;position:absolute;right:0;left:0;top:0;bottom:0}.xterm .xterm-screen{position:relative}.xterm .xterm-screen canvas{position:absolute;left:0;top:0}.xterm .xterm-scroll-area{visibility:hidden}.xterm-char-measure-element{display:inline-block;visibility:hidden;position:absolute;top:0;left:-9999em;line-height:normal}.xterm.enable-mouse-events{cursor:default}.xterm .xterm-cursor-pointer,.xterm.xterm-cursor-pointer{cursor:pointer}.xterm.column-select.focus{cursor:crosshair}.xterm .xterm-accessibility:not(.debug),.xterm .xterm-message{position:absolute;left:0;top:0;bottom:0;right:0;z-index:10;color:transparent;pointer-events:none}.xterm .xterm-accessibility-tree:not(.debug) ::selection{color:transparent}.xterm .xterm-accessibility-tree{user-select:text;white-space:pre}.xterm .live-region{position:absolute;left:-9999px;width:1px;height:1px;overflow:hidden}.xterm-dim{opacity:1!important}.xterm-underline-1{text-decoration:underline}.xterm-underline-2{text-decoration:double underline}.xterm-underline-3{text-decoration:wavy underline}.xterm-underline-4{text-decoration:dotted underline}.xterm-underline-5{text-decoration:dashed underline}.xterm-overline{text-decoration:overline}.xterm-overline.xterm-underline-1{text-decoration:overline underline}.xterm-overline.xterm-underline-2{text-decoration:overline double underline}.xterm-overline.xterm-underline-3{text-decoration:overline wavy underline}.xterm-overline.xterm-underline-4{text-decoration:overline dotted underline}.xterm-overline.xterm-underline-5{text-decoration:overline dashed underline}.xterm-strikethrough{text-decoration:line-through}.xterm-screen .xterm-decoration-container .xterm-decoration{z-index:6;position:absolute}.xterm-screen .xterm-decoration-container .xterm-decoration.xterm-decoration-top-layer{z-index:7}.xterm-decoration-overview-ruler{z-index:8;position:absolute;top:0;right:0;pointer-events:none}.xterm-decoration-top{z-index:2;position:relative}
|
||||
/*# sourceMappingURL=/sm/97377c0c258e109358121823f5790146c714989366481f90e554c42277efb500.map */
|
||||
File diff suppressed because one or more lines are too long
Reference in New Issue
Block a user