@phenx-inc/ctlsurf 0.12.1 → 0.13.4
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/LICENSE +22 -0
- package/out/headless/index.mjs +33 -36
- package/out/headless/index.mjs.map +3 -3
- package/out/main/index.js +47 -40
- package/out/preload/index.js +1 -0
- package/out/renderer/assets/{cssMode-D7r66wvO.js → cssMode-CAEObGNt.js} +3 -3
- package/out/renderer/assets/{freemarker2-jSWJIjPG.js → freemarker2-D63hnnDq.js} +1 -1
- package/out/renderer/assets/{handlebars-BnFyz5RH.js → handlebars-UYo_IYpi.js} +1 -1
- package/out/renderer/assets/{html-Bl5UFavp.js → html-2dvYDi3g.js} +1 -1
- package/out/renderer/assets/{htmlMode-D4VByBT6.js → htmlMode-B6fxDDPF.js} +3 -3
- package/out/renderer/assets/{index-Cc_TqOCL.css → index-BEekQe3-.css} +232 -8
- package/out/renderer/assets/{index-DxhW1emk.js → index-CMb4AqkZ.js} +786 -473
- package/out/renderer/assets/{javascript-CMN1OmwG.js → javascript-8RyFDDdk.js} +2 -2
- package/out/renderer/assets/{jsonMode-CJ5AYSPV.js → jsonMode-CqspkWCb.js} +3 -3
- package/out/renderer/assets/{liquid-BGMIDTEm.js → liquid-DaEnAsYY.js} +1 -1
- package/out/renderer/assets/{lspLanguageFeatures-DGifVYB5.js → lspLanguageFeatures-Czkp5sZb.js} +1 -1
- package/out/renderer/assets/{mdx-B2Z7R10Q.js → mdx-Q-qSZsFg.js} +1 -1
- package/out/renderer/assets/{python-LMDMSSdn.js → python-Sw0S0BJV.js} +1 -1
- package/out/renderer/assets/{razor-BZSrziPu.js → razor-C7zQmabz.js} +1 -1
- package/out/renderer/assets/{tsMode-DF9m6_3n.js → tsMode-BWdOxk6v.js} +1 -1
- package/out/renderer/assets/{typescript-rZeCMf0a.js → typescript-BrdIiKZf.js} +1 -1
- package/out/renderer/assets/{xml-fNSZ-7so.js → xml-DbKLNPOq.js} +1 -1
- package/out/renderer/assets/{yaml-1wxIuvzh.js → yaml-ehupJBsZ.js} +1 -1
- package/out/renderer/index.html +2 -2
- package/package.json +2 -2
- package/src/main/bridge.ts +3 -2
- package/src/main/index.ts +15 -0
- package/src/main/orchestrator.ts +1 -1
- package/src/main/pty.ts +2 -5
- package/src/main/ticketStore.ts +28 -21
- package/src/main/timeTracker.ts +4 -1
- package/src/main/transcripts.ts +3 -2
- package/src/preload/index.ts +2 -0
- package/src/renderer/App.tsx +15 -2
- package/src/renderer/components/CtlsurfPanel.tsx +169 -35
- package/src/renderer/components/SettingsDialog.tsx +13 -0
- package/src/renderer/components/SpeakControls.tsx +5 -0
- package/src/renderer/components/TerminalPanel.tsx +155 -48
- package/src/renderer/components/TicketPanel.tsx +80 -24
- package/src/renderer/lib/speech.ts +2 -1
- package/src/renderer/styles.css +232 -8
package/out/main/index.js
CHANGED
|
@@ -122,6 +122,12 @@ function getSettingsDir(useElectron) {
|
|
|
122
122
|
return app.getPath("userData");
|
|
123
123
|
}
|
|
124
124
|
}
|
|
125
|
+
function log$3(...args) {
|
|
126
|
+
try {
|
|
127
|
+
console.log(...args);
|
|
128
|
+
} catch {
|
|
129
|
+
}
|
|
130
|
+
}
|
|
125
131
|
const require$1 = module$1.createRequire(require("url").pathToFileURL(__filename).href);
|
|
126
132
|
const pty = require$1("node-pty");
|
|
127
133
|
class PtyManager {
|
|
@@ -131,10 +137,7 @@ class PtyManager {
|
|
|
131
137
|
constructor(agent, cwd) {
|
|
132
138
|
const shell = agent.command;
|
|
133
139
|
const args = agent.args || [];
|
|
134
|
-
|
|
135
|
-
console.log(`[pty] Spawning: ${shell} ${args.join(" ")} in ${cwd}`);
|
|
136
|
-
} catch {
|
|
137
|
-
}
|
|
140
|
+
log$3(`[pty] Spawning: ${shell} ${args.join(" ")} in ${cwd}`);
|
|
138
141
|
this.process = pty.spawn(shell, args, {
|
|
139
142
|
name: "xterm-256color",
|
|
140
143
|
cwd,
|
|
@@ -5963,7 +5966,7 @@ class ConversationBridge {
|
|
|
5963
5966
|
this.inputBuffer = "";
|
|
5964
5967
|
this.terminalCapture.reset();
|
|
5965
5968
|
this.sessionActive = true;
|
|
5966
|
-
|
|
5969
|
+
log$3("[bridge] Session started");
|
|
5967
5970
|
}
|
|
5968
5971
|
feedOutput(data) {
|
|
5969
5972
|
if (!this.sessionActive || !this.loggingEnabled) return;
|
|
@@ -6043,7 +6046,7 @@ class ConversationBridge {
|
|
|
6043
6046
|
this.sessionActive = false;
|
|
6044
6047
|
this.inputBuffer = "";
|
|
6045
6048
|
this.outputBuffer = "";
|
|
6046
|
-
|
|
6049
|
+
log$3("[bridge] Session ended");
|
|
6047
6050
|
}
|
|
6048
6051
|
}
|
|
6049
6052
|
const DEFAULT_COLS = 80;
|
|
@@ -6252,7 +6255,7 @@ class TranscriptTailer {
|
|
|
6252
6255
|
this.sinceMs = Date.now();
|
|
6253
6256
|
this.files.clear();
|
|
6254
6257
|
this.pollTimer = setInterval(() => this.poll(), POLL_INTERVAL_MS);
|
|
6255
|
-
|
|
6258
|
+
log$3(`[transcripts] Tailing ${this.agentId} transcripts for ${this.cwd}`);
|
|
6256
6259
|
}
|
|
6257
6260
|
stop() {
|
|
6258
6261
|
if (!this.pollTimer) return;
|
|
@@ -6260,7 +6263,7 @@ class TranscriptTailer {
|
|
|
6260
6263
|
this.pollTimer = null;
|
|
6261
6264
|
this.poll();
|
|
6262
6265
|
this.files.clear();
|
|
6263
|
-
|
|
6266
|
+
log$3("[transcripts] Stopped");
|
|
6264
6267
|
}
|
|
6265
6268
|
poll() {
|
|
6266
6269
|
try {
|
|
@@ -10689,12 +10692,6 @@ function requireWebsocketServer() {
|
|
|
10689
10692
|
return websocketServer;
|
|
10690
10693
|
}
|
|
10691
10694
|
requireWebsocketServer();
|
|
10692
|
-
function log$3(...args) {
|
|
10693
|
-
try {
|
|
10694
|
-
console.log(...args);
|
|
10695
|
-
} catch {
|
|
10696
|
-
}
|
|
10697
|
-
}
|
|
10698
10695
|
const WS = typeof WebSocket !== "undefined" ? WebSocket : WebSocket$1;
|
|
10699
10696
|
const HEARTBEAT_INTERVAL_MS = 3e4;
|
|
10700
10697
|
const RECONNECT_DELAY_MS = 5e3;
|
|
@@ -10969,10 +10966,7 @@ function isSameLocalDay(a, b2) {
|
|
|
10969
10966
|
return da.getFullYear() === db.getFullYear() && da.getMonth() === db.getMonth() && da.getDate() === db.getDate();
|
|
10970
10967
|
}
|
|
10971
10968
|
function log$2(...args) {
|
|
10972
|
-
|
|
10973
|
-
console.log("[time-tracker]", ...args);
|
|
10974
|
-
} catch {
|
|
10975
|
-
}
|
|
10969
|
+
log$3("[time-tracker]", ...args);
|
|
10976
10970
|
}
|
|
10977
10971
|
function findPageByTitle$1(pages, title) {
|
|
10978
10972
|
for (const p2 of pages) {
|
|
@@ -11259,10 +11253,7 @@ const COLUMNS = [
|
|
|
11259
11253
|
{ name: "Created", type: "date" }
|
|
11260
11254
|
];
|
|
11261
11255
|
function log$1(...args) {
|
|
11262
|
-
|
|
11263
|
-
console.log("[ticket-store]", ...args);
|
|
11264
|
-
} catch {
|
|
11265
|
-
}
|
|
11256
|
+
log$3("[ticket-store]", ...args);
|
|
11266
11257
|
}
|
|
11267
11258
|
function findPageByTitle(pages, title) {
|
|
11268
11259
|
for (const p2 of pages) {
|
|
@@ -11276,7 +11267,10 @@ function findPageByTitle(pages, title) {
|
|
|
11276
11267
|
}
|
|
11277
11268
|
class TicketStore {
|
|
11278
11269
|
api;
|
|
11279
|
-
|
|
11270
|
+
// Per-cwd cache of the resolved Tickets datastore: the block (the datastore
|
|
11271
|
+
// element) and the Agent Datastore page it lives on. Both ids are handed to
|
|
11272
|
+
// the coding agent so it can read/update tickets via the ctlsurf MCP tools.
|
|
11273
|
+
dsCache = /* @__PURE__ */ new Map();
|
|
11280
11274
|
constructor(api) {
|
|
11281
11275
|
this.api = api;
|
|
11282
11276
|
}
|
|
@@ -11285,11 +11279,11 @@ class TicketStore {
|
|
|
11285
11279
|
const title = input.title?.trim();
|
|
11286
11280
|
if (!title) return { ok: false, error: "Title is required" };
|
|
11287
11281
|
try {
|
|
11288
|
-
const
|
|
11289
|
-
if (!
|
|
11282
|
+
const ds = await this.ensureDatastore(cwd, true);
|
|
11283
|
+
if (!ds) {
|
|
11290
11284
|
return { ok: false, error: "No ctlsurf project found for this folder" };
|
|
11291
11285
|
}
|
|
11292
|
-
await this.api.addRow(blockId, {
|
|
11286
|
+
await this.api.addRow(ds.blockId, {
|
|
11293
11287
|
Title: title,
|
|
11294
11288
|
Description: input.description?.trim() || "",
|
|
11295
11289
|
Status: input.status || "Open",
|
|
@@ -11308,9 +11302,9 @@ class TicketStore {
|
|
|
11308
11302
|
const title = input.title?.trim();
|
|
11309
11303
|
if (!title) return { ok: false, error: "Title is required" };
|
|
11310
11304
|
try {
|
|
11311
|
-
const
|
|
11312
|
-
if (!
|
|
11313
|
-
await this.api.updateRow(blockId, rowId, {
|
|
11305
|
+
const ds = await this.ensureDatastore(cwd, false);
|
|
11306
|
+
if (!ds) return { ok: false, error: "No Tickets datastore for this project" };
|
|
11307
|
+
await this.api.updateRow(ds.blockId, rowId, {
|
|
11314
11308
|
Title: title,
|
|
11315
11309
|
Description: input.description?.trim() || "",
|
|
11316
11310
|
Status: input.status || "Open",
|
|
@@ -11327,9 +11321,9 @@ class TicketStore {
|
|
|
11327
11321
|
* datastore — an unconfigured project simply has no tickets yet. */
|
|
11328
11322
|
async listTickets(cwd) {
|
|
11329
11323
|
try {
|
|
11330
|
-
const
|
|
11331
|
-
if (!
|
|
11332
|
-
const res = await this.api.queryRows(blockId, { orderBy: "Created", order: "desc", limit: 200 });
|
|
11324
|
+
const ds = await this.ensureDatastore(cwd, false);
|
|
11325
|
+
if (!ds) return { ok: true, tickets: [] };
|
|
11326
|
+
const res = await this.api.queryRows(ds.blockId, { orderBy: "Created", order: "desc", limit: 200 });
|
|
11333
11327
|
const tickets = (res?.rows || []).map((r) => ({
|
|
11334
11328
|
id: r.id,
|
|
11335
11329
|
title: String(r.data?.Title ?? ""),
|
|
@@ -11338,14 +11332,14 @@ class TicketStore {
|
|
|
11338
11332
|
priority: String(r.data?.Priority ?? "Med"),
|
|
11339
11333
|
created: r.data?.Created ?? r.created_at ?? null
|
|
11340
11334
|
}));
|
|
11341
|
-
return { ok: true, tickets };
|
|
11335
|
+
return { ok: true, tickets, blockId: ds.blockId, pageId: ds.pageId };
|
|
11342
11336
|
} catch (err) {
|
|
11343
11337
|
log$1(`listTickets failed for ${cwd}: ${err?.message || err}`);
|
|
11344
11338
|
return { ok: false, tickets: [], error: err?.message || String(err) };
|
|
11345
11339
|
}
|
|
11346
11340
|
}
|
|
11347
11341
|
async ensureDatastore(cwd, create) {
|
|
11348
|
-
const cached = this.
|
|
11342
|
+
const cached = this.dsCache.get(cwd);
|
|
11349
11343
|
if (cached) return cached;
|
|
11350
11344
|
let folder = null;
|
|
11351
11345
|
try {
|
|
@@ -11357,11 +11351,13 @@ class TicketStore {
|
|
|
11357
11351
|
const folderDetail = await this.api.getFolder(folder.id);
|
|
11358
11352
|
const agentPage = findPageByTitle(folderDetail?.pages || [], AGENT_DATASTORE_PAGE_TITLE);
|
|
11359
11353
|
if (!agentPage?.id) return null;
|
|
11360
|
-
const
|
|
11354
|
+
const pageId = agentPage.id;
|
|
11355
|
+
const blockId = await this.findOrAdoptBlock(pageId);
|
|
11361
11356
|
if (blockId) {
|
|
11362
11357
|
await this.ensureColumns(blockId);
|
|
11363
|
-
|
|
11364
|
-
|
|
11358
|
+
const ds = { blockId, pageId };
|
|
11359
|
+
this.dsCache.set(cwd, ds);
|
|
11360
|
+
return ds;
|
|
11365
11361
|
}
|
|
11366
11362
|
if (!create) return null;
|
|
11367
11363
|
const columns = COLUMNS.map((c, i) => ({
|
|
@@ -11370,15 +11366,16 @@ class TicketStore {
|
|
|
11370
11366
|
type: c.type,
|
|
11371
11367
|
...c.options ? { options: c.options } : {}
|
|
11372
11368
|
}));
|
|
11373
|
-
const created = await this.api.createBlock(
|
|
11369
|
+
const created = await this.api.createBlock(pageId, {
|
|
11374
11370
|
type: "datastore",
|
|
11375
11371
|
title: DATASTORE_TITLE,
|
|
11376
11372
|
props: { columns, system_key: SYSTEM_KEY }
|
|
11377
11373
|
});
|
|
11378
11374
|
if (created?.id) {
|
|
11379
11375
|
log$1(`Created "${DATASTORE_TITLE}" datastore on Agent Datastore page for ${cwd}`);
|
|
11380
|
-
|
|
11381
|
-
|
|
11376
|
+
const ds = { blockId: created.id, pageId };
|
|
11377
|
+
this.dsCache.set(cwd, ds);
|
|
11378
|
+
return ds;
|
|
11382
11379
|
}
|
|
11383
11380
|
return null;
|
|
11384
11381
|
}
|
|
@@ -12297,6 +12294,12 @@ function createWindow() {
|
|
|
12297
12294
|
mainWindow.webContents.session.setPermissionRequestHandler((_wc, _permission, callback) => {
|
|
12298
12295
|
callback(true);
|
|
12299
12296
|
});
|
|
12297
|
+
mainWindow.webContents.on("did-attach-webview", (_e, contents) => {
|
|
12298
|
+
contents.setWindowOpenHandler(({ url }) => {
|
|
12299
|
+
setImmediate(() => contents.loadURL(url));
|
|
12300
|
+
return { action: "deny" };
|
|
12301
|
+
});
|
|
12302
|
+
});
|
|
12300
12303
|
if (process.env.ELECTRON_RENDERER_URL) {
|
|
12301
12304
|
mainWindow.loadURL(process.env.ELECTRON_RENDERER_URL);
|
|
12302
12305
|
} else {
|
|
@@ -12312,6 +12315,10 @@ electron.ipcMain.handle("clipboard:writeTable", (_event, html, text) => {
|
|
|
12312
12315
|
electron.clipboard.write({ html, text });
|
|
12313
12316
|
return { ok: true };
|
|
12314
12317
|
});
|
|
12318
|
+
electron.ipcMain.handle("clipboard:writeText", (_event, text) => {
|
|
12319
|
+
electron.clipboard.writeText(text);
|
|
12320
|
+
return { ok: true };
|
|
12321
|
+
});
|
|
12315
12322
|
electron.ipcMain.handle("pty:spawn", async (_event, tabId, agent, cwd) => {
|
|
12316
12323
|
await orchestrator.spawnAgent(tabId, agent, cwd);
|
|
12317
12324
|
return { ok: true };
|
package/out/preload/index.js
CHANGED
|
@@ -42,6 +42,7 @@ const api = {
|
|
|
42
42
|
browseCwd: () => electron.ipcRenderer.invoke("app:browseCwd"),
|
|
43
43
|
// Clipboard — write a rich-HTML table (+ plain-text fallback) for email paste.
|
|
44
44
|
copyEmailTable: (html, text) => electron.ipcRenderer.invoke("clipboard:writeTable", html, text),
|
|
45
|
+
copyText: (text) => electron.ipcRenderer.invoke("clipboard:writeText", text),
|
|
45
46
|
getVersion: () => electron.ipcRenderer.invoke("app:getVersion"),
|
|
46
47
|
getUpdateInfo: () => electron.ipcRenderer.invoke("app:getUpdateInfo"),
|
|
47
48
|
onUpdateInfo: (callback) => {
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { c as createWebWorker, l as languages } from "./index-
|
|
2
|
-
import { C as CompletionAdapter, H as HoverAdapter, D as DocumentHighlightAdapter, a as DefinitionAdapter, R as ReferenceAdapter, b as DocumentSymbolAdapter, c as RenameAdapter, d as DocumentColorAdapter, F as FoldingRangeAdapter, e as DiagnosticsAdapter, S as SelectionRangeAdapter, f as DocumentFormattingEditProvider, g as DocumentRangeFormattingEditProvider } from "./lspLanguageFeatures-
|
|
3
|
-
import { h, i, j, t, k } from "./lspLanguageFeatures-
|
|
1
|
+
import { c as createWebWorker, l as languages } from "./index-CMb4AqkZ.js";
|
|
2
|
+
import { C as CompletionAdapter, H as HoverAdapter, D as DocumentHighlightAdapter, a as DefinitionAdapter, R as ReferenceAdapter, b as DocumentSymbolAdapter, c as RenameAdapter, d as DocumentColorAdapter, F as FoldingRangeAdapter, e as DiagnosticsAdapter, S as SelectionRangeAdapter, f as DocumentFormattingEditProvider, g as DocumentRangeFormattingEditProvider } from "./lspLanguageFeatures-Czkp5sZb.js";
|
|
3
|
+
import { h, i, j, t, k } from "./lspLanguageFeatures-Czkp5sZb.js";
|
|
4
4
|
const STOP_WHEN_IDLE_FOR = 2 * 60 * 1e3;
|
|
5
5
|
class WorkerManager {
|
|
6
6
|
constructor(defaults) {
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { c as createWebWorker, l as languages } from "./index-
|
|
2
|
-
import { H as HoverAdapter, D as DocumentHighlightAdapter, h as DocumentLinkAdapter, F as FoldingRangeAdapter, b as DocumentSymbolAdapter, S as SelectionRangeAdapter, c as RenameAdapter, f as DocumentFormattingEditProvider, g as DocumentRangeFormattingEditProvider, C as CompletionAdapter } from "./lspLanguageFeatures-
|
|
3
|
-
import { a, e, d, R, i, j, t, k } from "./lspLanguageFeatures-
|
|
1
|
+
import { c as createWebWorker, l as languages } from "./index-CMb4AqkZ.js";
|
|
2
|
+
import { H as HoverAdapter, D as DocumentHighlightAdapter, h as DocumentLinkAdapter, F as FoldingRangeAdapter, b as DocumentSymbolAdapter, S as SelectionRangeAdapter, c as RenameAdapter, f as DocumentFormattingEditProvider, g as DocumentRangeFormattingEditProvider, C as CompletionAdapter } from "./lspLanguageFeatures-Czkp5sZb.js";
|
|
3
|
+
import { a, e, d, R, i, j, t, k } from "./lspLanguageFeatures-Czkp5sZb.js";
|
|
4
4
|
const STOP_WHEN_IDLE_FOR = 2 * 60 * 1e3;
|
|
5
5
|
class WorkerManager {
|
|
6
6
|
constructor(defaults) {
|
|
@@ -7915,13 +7915,88 @@ html, body, #root {
|
|
|
7915
7915
|
width: 100%;
|
|
7916
7916
|
height: 100%;
|
|
7917
7917
|
overflow: hidden;
|
|
7918
|
+
display: flex;
|
|
7919
|
+
flex-direction: column;
|
|
7920
|
+
}
|
|
7921
|
+
|
|
7922
|
+
.ctlsurf-addressbar {
|
|
7923
|
+
flex: 0 0 auto;
|
|
7924
|
+
display: flex;
|
|
7925
|
+
align-items: center;
|
|
7926
|
+
gap: 4px;
|
|
7927
|
+
padding: 5px 6px;
|
|
7928
|
+
background: #1a1b26;
|
|
7929
|
+
border-bottom: 1px solid #2a2b3d;
|
|
7930
|
+
}
|
|
7931
|
+
|
|
7932
|
+
.ctlsurf-nav-btn {
|
|
7933
|
+
flex: 0 0 auto;
|
|
7934
|
+
width: 24px;
|
|
7935
|
+
height: 24px;
|
|
7936
|
+
display: flex;
|
|
7937
|
+
align-items: center;
|
|
7938
|
+
justify-content: center;
|
|
7939
|
+
padding: 0;
|
|
7940
|
+
background: transparent;
|
|
7941
|
+
border: none;
|
|
7942
|
+
border-radius: 4px;
|
|
7943
|
+
color: #a9b1d6;
|
|
7944
|
+
font-size: 16px;
|
|
7945
|
+
line-height: 1;
|
|
7946
|
+
cursor: pointer;
|
|
7947
|
+
}
|
|
7948
|
+
|
|
7949
|
+
.ctlsurf-nav-btn:hover:not(:disabled) {
|
|
7950
|
+
background: #2a2b3d;
|
|
7951
|
+
}
|
|
7952
|
+
|
|
7953
|
+
.ctlsurf-nav-btn:disabled {
|
|
7954
|
+
color: #565f89;
|
|
7955
|
+
opacity: 0.5;
|
|
7956
|
+
cursor: default;
|
|
7957
|
+
}
|
|
7958
|
+
|
|
7959
|
+
.ctlsurf-address-form {
|
|
7960
|
+
flex: 1 1 auto;
|
|
7961
|
+
display: flex;
|
|
7962
|
+
min-width: 0;
|
|
7963
|
+
}
|
|
7964
|
+
|
|
7965
|
+
.ctlsurf-address-input {
|
|
7966
|
+
width: 100%;
|
|
7967
|
+
height: 26px;
|
|
7968
|
+
padding: 0 11px;
|
|
7969
|
+
background: #16161e;
|
|
7970
|
+
border: 1px solid #2a2b3d;
|
|
7971
|
+
border-radius: 13px;
|
|
7972
|
+
color: #c0caf5;
|
|
7973
|
+
font-size: 12px;
|
|
7974
|
+
outline: none;
|
|
7975
|
+
}
|
|
7976
|
+
|
|
7977
|
+
.ctlsurf-address-input:focus {
|
|
7978
|
+
border-color: #7aa2f7;
|
|
7918
7979
|
}
|
|
7919
7980
|
|
|
7920
|
-
.ctlsurf-
|
|
7981
|
+
.ctlsurf-webview-host {
|
|
7982
|
+
flex: 1 1 auto;
|
|
7983
|
+
position: relative;
|
|
7984
|
+
min-height: 0;
|
|
7985
|
+
}
|
|
7986
|
+
|
|
7987
|
+
.ctlsurf-webview-host webview {
|
|
7921
7988
|
width: 100%;
|
|
7922
7989
|
height: 100%;
|
|
7923
7990
|
}
|
|
7924
7991
|
|
|
7992
|
+
.ctlsurf-webview-loading {
|
|
7993
|
+
width: 100%;
|
|
7994
|
+
height: 100%;
|
|
7995
|
+
display: flex;
|
|
7996
|
+
align-items: center;
|
|
7997
|
+
justify-content: center;
|
|
7998
|
+
}
|
|
7999
|
+
|
|
7925
8000
|
/* Status bar */
|
|
7926
8001
|
.statusbar {
|
|
7927
8002
|
height: 24px;
|
|
@@ -8927,6 +9002,22 @@ html, body, #root {
|
|
|
8927
9002
|
font-size: 16px;
|
|
8928
9003
|
}
|
|
8929
9004
|
|
|
9005
|
+
/* About section inside the Settings dialog. */
|
|
9006
|
+
.settings-about {
|
|
9007
|
+
border-top: 1px solid #2a2b3d;
|
|
9008
|
+
padding-top: 16px;
|
|
9009
|
+
}
|
|
9010
|
+
.settings-about .about-name {
|
|
9011
|
+
color: #c0caf5;
|
|
9012
|
+
font-size: 14px;
|
|
9013
|
+
font-weight: 600;
|
|
9014
|
+
}
|
|
9015
|
+
.settings-about .about-copyright {
|
|
9016
|
+
color: #9aa5ce;
|
|
9017
|
+
font-size: 12px;
|
|
9018
|
+
margin: 6px 0 8px;
|
|
9019
|
+
}
|
|
9020
|
+
|
|
8930
9021
|
.settings-section {
|
|
8931
9022
|
margin-bottom: 20px;
|
|
8932
9023
|
}
|
|
@@ -9438,24 +9529,57 @@ html, body, #root {
|
|
|
9438
9529
|
.ticket-card {
|
|
9439
9530
|
display: flex;
|
|
9440
9531
|
align-items: stretch;
|
|
9441
|
-
gap: 10px;
|
|
9442
9532
|
background: #15161f;
|
|
9443
9533
|
border: 1px solid #262a3f;
|
|
9444
9534
|
border-radius: 10px;
|
|
9445
|
-
padding: 10px 11px 10px 0;
|
|
9446
|
-
cursor: pointer;
|
|
9447
|
-
text-align: left;
|
|
9448
|
-
font-family: inherit;
|
|
9449
9535
|
overflow: hidden;
|
|
9450
|
-
transition: border-color 0.13s ease, background 0.13s ease
|
|
9536
|
+
transition: border-color 0.13s ease, background 0.13s ease;
|
|
9451
9537
|
}
|
|
9452
9538
|
.ticket-card:hover {
|
|
9453
9539
|
border-color: #3d425f;
|
|
9454
9540
|
background: #181a26;
|
|
9455
9541
|
}
|
|
9456
|
-
|
|
9542
|
+
/* Clickable body of the card — opens the editor. */
|
|
9543
|
+
.ticket-card-open {
|
|
9544
|
+
flex: 1;
|
|
9545
|
+
min-width: 0;
|
|
9546
|
+
display: flex;
|
|
9547
|
+
align-items: stretch;
|
|
9548
|
+
gap: 10px;
|
|
9549
|
+
padding: 10px 11px 10px 0;
|
|
9550
|
+
background: transparent;
|
|
9551
|
+
border: none;
|
|
9552
|
+
color: inherit;
|
|
9553
|
+
cursor: pointer;
|
|
9554
|
+
text-align: left;
|
|
9555
|
+
font-family: inherit;
|
|
9556
|
+
transition: transform 0.06s ease;
|
|
9557
|
+
}
|
|
9558
|
+
.ticket-card-open:active {
|
|
9457
9559
|
transform: scale(0.99);
|
|
9458
9560
|
}
|
|
9561
|
+
/* Trailing button that dispatches the ticket to the coding terminal. */
|
|
9562
|
+
.ticket-card-send {
|
|
9563
|
+
flex-shrink: 0;
|
|
9564
|
+
width: 36px;
|
|
9565
|
+
align-self: stretch;
|
|
9566
|
+
display: flex;
|
|
9567
|
+
align-items: center;
|
|
9568
|
+
justify-content: center;
|
|
9569
|
+
background: transparent;
|
|
9570
|
+
border: none;
|
|
9571
|
+
border-left: 1px solid #262a3f;
|
|
9572
|
+
color: #565f89;
|
|
9573
|
+
cursor: pointer;
|
|
9574
|
+
transition: background 0.13s ease, color 0.13s ease, transform 0.06s ease;
|
|
9575
|
+
}
|
|
9576
|
+
.ticket-card-send:hover {
|
|
9577
|
+
background: #1b2030;
|
|
9578
|
+
color: #7aa2f7;
|
|
9579
|
+
}
|
|
9580
|
+
.ticket-card-send:active {
|
|
9581
|
+
transform: scale(0.9);
|
|
9582
|
+
}
|
|
9459
9583
|
.ticket-card-bar {
|
|
9460
9584
|
width: 3px;
|
|
9461
9585
|
border-radius: 3px;
|
|
@@ -9530,6 +9654,106 @@ html, body, #root {
|
|
|
9530
9654
|
.term-context-menu button:hover {
|
|
9531
9655
|
background: #2a2b3d;
|
|
9532
9656
|
}
|
|
9657
|
+
/* Always-visible utility button (top-right) — opens the snapshot panel. */
|
|
9658
|
+
.term-util-btn {
|
|
9659
|
+
position: absolute;
|
|
9660
|
+
top: 6px;
|
|
9661
|
+
right: 14px;
|
|
9662
|
+
z-index: 6;
|
|
9663
|
+
width: 26px;
|
|
9664
|
+
height: 26px;
|
|
9665
|
+
display: inline-flex;
|
|
9666
|
+
align-items: center;
|
|
9667
|
+
justify-content: center;
|
|
9668
|
+
border-radius: 6px;
|
|
9669
|
+
background: #2a2b3d;
|
|
9670
|
+
border: 1px solid #414868;
|
|
9671
|
+
color: #c0caf5;
|
|
9672
|
+
font-size: 16px;
|
|
9673
|
+
line-height: 1;
|
|
9674
|
+
cursor: pointer;
|
|
9675
|
+
opacity: 0.55;
|
|
9676
|
+
transition: opacity 0.13s ease, background 0.13s ease;
|
|
9677
|
+
}
|
|
9678
|
+
.term-util-btn:hover {
|
|
9679
|
+
opacity: 1;
|
|
9680
|
+
background: #343650;
|
|
9681
|
+
}
|
|
9682
|
+
|
|
9683
|
+
/* Snapshot panel: a frozen, selectable copy of the screen text. */
|
|
9684
|
+
.term-snapshot {
|
|
9685
|
+
position: absolute;
|
|
9686
|
+
inset: 0;
|
|
9687
|
+
z-index: 20;
|
|
9688
|
+
display: flex;
|
|
9689
|
+
flex-direction: column;
|
|
9690
|
+
background: #16161e;
|
|
9691
|
+
border: 1px solid #414868;
|
|
9692
|
+
}
|
|
9693
|
+
.term-snapshot-head {
|
|
9694
|
+
display: flex;
|
|
9695
|
+
align-items: center;
|
|
9696
|
+
justify-content: space-between;
|
|
9697
|
+
gap: 12px;
|
|
9698
|
+
padding: 8px 12px;
|
|
9699
|
+
background: #1f2335;
|
|
9700
|
+
border-bottom: 1px solid #414868;
|
|
9701
|
+
color: #9aa5ce;
|
|
9702
|
+
font-size: 12px;
|
|
9703
|
+
}
|
|
9704
|
+
.term-snapshot-x {
|
|
9705
|
+
background: transparent;
|
|
9706
|
+
border: none;
|
|
9707
|
+
color: #9aa5ce;
|
|
9708
|
+
font-size: 14px;
|
|
9709
|
+
cursor: pointer;
|
|
9710
|
+
padding: 2px 6px;
|
|
9711
|
+
border-radius: 4px;
|
|
9712
|
+
}
|
|
9713
|
+
.term-snapshot-x:hover {
|
|
9714
|
+
background: #2a2b3d;
|
|
9715
|
+
color: #c0caf5;
|
|
9716
|
+
}
|
|
9717
|
+
.term-snapshot-text {
|
|
9718
|
+
flex: 1;
|
|
9719
|
+
min-height: 0;
|
|
9720
|
+
overflow: auto;
|
|
9721
|
+
margin: 0;
|
|
9722
|
+
padding: 10px 12px;
|
|
9723
|
+
white-space: pre;
|
|
9724
|
+
user-select: text;
|
|
9725
|
+
cursor: text;
|
|
9726
|
+
color: #c0caf5;
|
|
9727
|
+
background: #16161e;
|
|
9728
|
+
font-family: 'JetBrains Mono', 'Fira Code', 'SF Mono', monospace;
|
|
9729
|
+
font-size: 13px;
|
|
9730
|
+
line-height: 1.4;
|
|
9731
|
+
}
|
|
9732
|
+
.term-snapshot-text::selection {
|
|
9733
|
+
background: #33467c;
|
|
9734
|
+
}
|
|
9735
|
+
.term-snapshot-actions {
|
|
9736
|
+
display: flex;
|
|
9737
|
+
gap: 6px;
|
|
9738
|
+
padding: 8px 12px;
|
|
9739
|
+
background: #1f2335;
|
|
9740
|
+
border-top: 1px solid #414868;
|
|
9741
|
+
}
|
|
9742
|
+
.term-snapshot-actions button {
|
|
9743
|
+
background: #2a2b3d;
|
|
9744
|
+
border: 1px solid #414868;
|
|
9745
|
+
border-radius: 5px;
|
|
9746
|
+
color: #c0caf5;
|
|
9747
|
+
font-size: 12.5px;
|
|
9748
|
+
padding: 6px 12px;
|
|
9749
|
+
cursor: pointer;
|
|
9750
|
+
}
|
|
9751
|
+
.term-snapshot-actions button:hover {
|
|
9752
|
+
background: #343650;
|
|
9753
|
+
}
|
|
9754
|
+
.term-snapshot-done {
|
|
9755
|
+
margin-left: auto;
|
|
9756
|
+
}
|
|
9533
9757
|
.term-toast {
|
|
9534
9758
|
position: fixed;
|
|
9535
9759
|
bottom: 24px;
|