aicodeman 1.40.0 → 1.41.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/README.md +25 -7
- package/dist/cli-agent.d.ts +225 -0
- package/dist/cli-agent.d.ts.map +1 -0
- package/dist/cli-agent.js +767 -0
- package/dist/cli-agent.js.map +1 -0
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +6 -30
- package/dist/cli.js.map +1 -1
- package/dist/codeman-credentials.d.ts +27 -0
- package/dist/codeman-credentials.d.ts.map +1 -0
- package/dist/codeman-credentials.js +66 -0
- package/dist/codeman-credentials.js.map +1 -0
- package/dist/config/cli-registry/schema.d.ts +2 -0
- package/dist/config/cli-registry/schema.d.ts.map +1 -1
- package/dist/config/cli-registry/schema.js +3 -0
- package/dist/config/cli-registry/schema.js.map +1 -1
- package/dist/config/cli-registry/stock.d.ts.map +1 -1
- package/dist/config/cli-registry/stock.js +5 -0
- package/dist/config/cli-registry/stock.js.map +1 -1
- package/dist/config/cli-registry/types.d.ts +12 -1
- package/dist/config/cli-registry/types.d.ts.map +1 -1
- package/dist/git-clone.d.ts.map +1 -1
- package/dist/git-clone.js +4 -0
- package/dist/git-clone.js.map +1 -1
- package/dist/image-watcher.d.ts.map +1 -1
- package/dist/image-watcher.js +6 -2
- package/dist/image-watcher.js.map +1 -1
- package/dist/mcp-sync-targets.d.ts +34 -0
- package/dist/mcp-sync-targets.d.ts.map +1 -0
- package/dist/mcp-sync-targets.js +55 -0
- package/dist/mcp-sync-targets.js.map +1 -0
- package/dist/mcp-sync.d.ts +2 -1
- package/dist/mcp-sync.d.ts.map +1 -1
- package/dist/mcp-sync.js +62 -1
- package/dist/mcp-sync.js.map +1 -1
- package/dist/session.d.ts.map +1 -1
- package/dist/session.js +10 -4
- package/dist/session.js.map +1 -1
- package/dist/subagent-watcher.d.ts.map +1 -1
- package/dist/subagent-watcher.js +29 -6
- package/dist/subagent-watcher.js.map +1 -1
- package/dist/templates/case-template.md +2 -0
- package/dist/tmux-manager.d.ts +5 -0
- package/dist/tmux-manager.d.ts.map +1 -1
- package/dist/tmux-manager.js +47 -22
- package/dist/tmux-manager.js.map +1 -1
- package/dist/tui/tui-client.d.ts +3 -17
- package/dist/tui/tui-client.d.ts.map +1 -1
- package/dist/tui/tui-client.js +3 -44
- package/dist/tui/tui-client.js.map +1 -1
- package/dist/utils/index.d.ts +1 -0
- package/dist/utils/index.d.ts.map +1 -1
- package/dist/utils/index.js +1 -0
- package/dist/utils/index.js.map +1 -1
- package/dist/utils/process-exit-wait.d.ts +40 -0
- package/dist/utils/process-exit-wait.d.ts.map +1 -0
- package/dist/utils/process-exit-wait.js +68 -0
- package/dist/utils/process-exit-wait.js.map +1 -0
- package/dist/web/paste-image-gc.d.ts +46 -6
- package/dist/web/paste-image-gc.d.ts.map +1 -1
- package/dist/web/paste-image-gc.js +106 -32
- package/dist/web/paste-image-gc.js.map +1 -1
- package/dist/web/public/admin-ui.js.gz +0 -0
- package/dist/web/public/api-client.c9b1cddc.js.gz +0 -0
- package/dist/web/public/app.6f8552e1.js +43 -0
- package/dist/web/public/app.6f8552e1.js.br +0 -0
- package/dist/web/public/app.6f8552e1.js.gz +0 -0
- package/dist/web/public/approvals-ui.js.gz +0 -0
- package/dist/web/public/{constants.c0f8f9de.js → constants.bc9c1f63.js} +295 -28
- package/dist/web/public/constants.bc9c1f63.js.br +0 -0
- package/dist/web/public/constants.bc9c1f63.js.gz +0 -0
- package/dist/web/public/cron-ui.js.gz +0 -0
- package/dist/web/public/entrance-animations.js +490 -43
- package/dist/web/public/entrance-animations.js.br +0 -0
- package/dist/web/public/entrance-animations.js.gz +0 -0
- package/dist/web/public/git-status-ui.js.gz +0 -0
- package/dist/web/public/home-sessions.js +2 -1
- package/dist/web/public/home-sessions.js.br +0 -0
- package/dist/web/public/home-sessions.js.gz +0 -0
- package/dist/web/public/host-wake-ui.js.gz +0 -0
- package/dist/web/public/i18n.a9840ea2.js +2 -0
- package/dist/web/public/i18n.a9840ea2.js.br +0 -0
- package/dist/web/public/i18n.a9840ea2.js.gz +0 -0
- package/dist/web/public/{image-input.898a538b.js → image-input.e00471c6.js} +3 -1
- package/dist/web/public/image-input.e00471c6.js.br +0 -0
- package/dist/web/public/image-input.e00471c6.js.gz +0 -0
- package/dist/web/public/index.html +128 -33
- package/dist/web/public/index.html.br +0 -0
- package/dist/web/public/index.html.gz +0 -0
- package/dist/web/public/input-cjk.8bc46081.js.gz +0 -0
- package/dist/web/public/keyboard-accessory.c1e7c7b7.js.gz +0 -0
- package/dist/web/public/mobile-handlers.0ef9c311.js.gz +0 -0
- package/dist/web/public/mobile-ime-preview.2dbaf262.js.gz +0 -0
- package/dist/web/public/mobile-overview.js.gz +0 -0
- package/dist/web/public/mobile.111b187d.css.gz +0 -0
- package/dist/web/public/{notification-manager.50bb87d8.js → notification-manager.feb82c04.js} +19 -2
- package/dist/web/public/notification-manager.feb82c04.js.br +0 -0
- package/dist/web/public/notification-manager.feb82c04.js.gz +0 -0
- package/dist/web/public/orchestrator-panel.js.gz +0 -0
- package/dist/web/public/{panels-ui.4735dfc2.js → panels-ui.71846fcb.js} +1 -1
- package/dist/web/public/panels-ui.71846fcb.js.br +0 -0
- package/dist/web/public/panels-ui.71846fcb.js.gz +0 -0
- package/dist/web/public/ralph-panel.b350e2ef.js.gz +0 -0
- package/dist/web/public/ralph-wizard.b6c0dd19.js.gz +0 -0
- package/dist/web/public/readmymind-ui.js.gz +0 -0
- package/dist/web/public/reboot-restore-ui.js.gz +0 -0
- package/dist/web/public/respawn-ui.ff0dae4c.js.gz +0 -0
- package/dist/web/public/sanitize-html.e7147e76.js.gz +0 -0
- package/dist/web/public/session-lineage.js.gz +0 -0
- package/dist/web/public/session-ui.f731602c.js.gz +0 -0
- package/dist/web/public/settings-ui.4e33390f.js +97 -0
- package/dist/web/public/settings-ui.4e33390f.js.br +0 -0
- package/dist/web/public/settings-ui.4e33390f.js.gz +0 -0
- package/dist/web/public/spreadsheet-preview-worker.js.gz +0 -0
- package/dist/web/public/spreadsheet-preview.af82fa2f.js.gz +0 -0
- package/dist/web/public/spreadsheet-xlsx-core.js.gz +0 -0
- package/dist/web/public/styles.81a72906.css +1 -0
- package/dist/web/public/styles.81a72906.css.br +0 -0
- package/dist/web/public/styles.81a72906.css.gz +0 -0
- package/dist/web/public/{subagent-windows.e6ca799f.js → subagent-windows.07277a58.js} +7 -5
- package/dist/web/public/subagent-windows.07277a58.js.br +0 -0
- package/dist/web/public/subagent-windows.07277a58.js.gz +0 -0
- package/dist/web/public/sw.js +2 -2
- package/dist/web/public/sw.js.br +0 -0
- package/dist/web/public/sw.js.gz +0 -0
- package/dist/web/public/tab-layout-browser.065fcba4.js.gz +0 -0
- package/dist/web/public/tab-rail-resize.a5867793.js.gz +0 -0
- package/dist/web/public/terminal-keycode229-recovery.af09f99a.js.gz +0 -0
- package/dist/web/public/terminal-split.js.gz +0 -0
- package/dist/web/public/terminal-tile.js +92 -15
- package/dist/web/public/terminal-tile.js.br +0 -0
- package/dist/web/public/terminal-tile.js.gz +0 -0
- package/dist/web/public/terminal-ui.b7267005.js +2 -0
- package/dist/web/public/terminal-ui.b7267005.js.br +0 -0
- package/dist/web/public/terminal-ui.b7267005.js.gz +0 -0
- package/dist/web/public/tile-grid.js +330 -119
- package/dist/web/public/tile-grid.js.br +0 -0
- package/dist/web/public/tile-grid.js.gz +0 -0
- package/dist/web/public/ultracode-panel.js.gz +0 -0
- package/dist/web/public/ultracode-windows.js +9 -6
- package/dist/web/public/ultracode-windows.js.br +0 -0
- package/dist/web/public/ultracode-windows.js.gz +0 -0
- package/dist/web/public/vendor/dompurify.min.js.gz +0 -0
- package/dist/web/public/vendor/exceljs.min.js.gz +0 -0
- package/dist/web/public/vendor/fflate.min.js.gz +0 -0
- package/dist/web/public/vendor/marked.min.js.gz +0 -0
- package/dist/web/public/vendor/xterm-addon-fit.min.js.gz +0 -0
- package/dist/web/public/vendor/xterm-addon-serialize.min.js.gz +0 -0
- package/dist/web/public/vendor/xterm-addon-unicode11.min.js.gz +0 -0
- package/dist/web/public/vendor/xterm-addon-webgl.min.js.gz +0 -0
- package/dist/web/public/vendor/xterm-predictive-echo.ae5e3d5d.js.gz +0 -0
- package/dist/web/public/vendor/xterm-zerolag-input.0b69623c.js +4 -0
- package/dist/web/public/vendor/xterm-zerolag-input.0b69623c.js.br +0 -0
- package/dist/web/public/vendor/xterm-zerolag-input.0b69623c.js.gz +0 -0
- package/dist/web/public/vendor/xterm.css.gz +0 -0
- package/dist/web/public/vendor/xterm.min.js.gz +0 -0
- package/dist/web/public/voice-input.2342c37b.js.gz +0 -0
- package/dist/web/public/voice-pcm-worklet.js.gz +0 -0
- package/dist/web/public/webview-tabs.js.gz +0 -0
- package/dist/web/routes/cli-registry-routes.d.ts +23 -4
- package/dist/web/routes/cli-registry-routes.d.ts.map +1 -1
- package/dist/web/routes/cli-registry-routes.js +92 -7
- package/dist/web/routes/cli-registry-routes.js.map +1 -1
- package/dist/web/routes/mcp-sync-routes.d.ts +7 -2
- package/dist/web/routes/mcp-sync-routes.d.ts.map +1 -1
- package/dist/web/routes/mcp-sync-routes.js +10 -3
- package/dist/web/routes/mcp-sync-routes.js.map +1 -1
- package/dist/web/routes/reboot-restore-routes.js +1 -1
- package/dist/web/routes/reboot-restore-routes.js.map +1 -1
- package/dist/web/routes/session-routes.d.ts.map +1 -1
- package/dist/web/routes/session-routes.js +68 -35
- package/dist/web/routes/session-routes.js.map +1 -1
- package/dist/web/schemas.d.ts +3 -0
- package/dist/web/schemas.d.ts.map +1 -1
- package/dist/web/schemas.js +9 -0
- package/dist/web/schemas.js.map +1 -1
- package/dist/web/server.d.ts +1 -1
- package/dist/web/server.d.ts.map +1 -1
- package/dist/web/server.js +15 -10
- package/dist/web/server.js.map +1 -1
- package/package.json +4 -4
- package/skills/codeman/SKILL.md +6 -0
- package/README.zh-CN.md +0 -1147
- package/dist/web/public/app.abadc970.js +0 -43
- package/dist/web/public/app.abadc970.js.br +0 -0
- package/dist/web/public/app.abadc970.js.gz +0 -0
- package/dist/web/public/constants.c0f8f9de.js.br +0 -0
- package/dist/web/public/constants.c0f8f9de.js.gz +0 -0
- package/dist/web/public/i18n.a31747a9.js +0 -2
- package/dist/web/public/i18n.a31747a9.js.br +0 -0
- package/dist/web/public/i18n.a31747a9.js.gz +0 -0
- package/dist/web/public/image-input.898a538b.js.br +0 -0
- package/dist/web/public/image-input.898a538b.js.gz +0 -0
- package/dist/web/public/notification-manager.50bb87d8.js.br +0 -0
- package/dist/web/public/notification-manager.50bb87d8.js.gz +0 -0
- package/dist/web/public/panels-ui.4735dfc2.js.br +0 -0
- package/dist/web/public/panels-ui.4735dfc2.js.gz +0 -0
- package/dist/web/public/settings-ui.a97ab16f.js +0 -97
- package/dist/web/public/settings-ui.a97ab16f.js.br +0 -0
- package/dist/web/public/settings-ui.a97ab16f.js.gz +0 -0
- package/dist/web/public/styles.8ee12666.css +0 -1
- package/dist/web/public/styles.8ee12666.css.br +0 -0
- package/dist/web/public/styles.8ee12666.css.gz +0 -0
- package/dist/web/public/subagent-windows.e6ca799f.js.br +0 -0
- package/dist/web/public/subagent-windows.e6ca799f.js.gz +0 -0
- package/dist/web/public/terminal-ui.5059b30e.js +0 -2
- package/dist/web/public/terminal-ui.5059b30e.js.br +0 -0
- package/dist/web/public/terminal-ui.5059b30e.js.gz +0 -0
- package/dist/web/public/vendor/xterm-zerolag-input.bbb5f84c.js +0 -4
- package/dist/web/public/vendor/xterm-zerolag-input.bbb5f84c.js.br +0 -0
- package/dist/web/public/vendor/xterm-zerolag-input.bbb5f84c.js.gz +0 -0
|
@@ -110,6 +110,9 @@ const TITLE_FLASH_INTERVAL_MS = 1500; // Title flash rate
|
|
|
110
110
|
const BROWSER_NOTIF_RATE_LIMIT_MS = 3000; // Rate limit for browser notifications
|
|
111
111
|
const MOBILE_RESIZE_RETRY_MS = 30000; // Small-viewport resize re-send while a desktop sizing claim is hot
|
|
112
112
|
const AUTO_CLOSE_NOTIFICATION_MS = 8000; // Auto-close browser notifications
|
|
113
|
+
const DEFAULT_TOAST_DURATION_MS = 3000; // How long a corner toast stays by default
|
|
114
|
+
const MIN_NOTIFICATION_DURATION_MS = 1000; // Shortest configurable toast / browser-notification time
|
|
115
|
+
const MAX_NOTIFICATION_DURATION_MS = 300000; // Longest configurable toast / browser-notification time
|
|
113
116
|
const THROTTLE_DELAY_MS = 100; // General UI throttle delay
|
|
114
117
|
const TERMINAL_CHUNK_SIZE = 32 * 1024; // 32KB chunks for terminal buffer loading
|
|
115
118
|
const TERMINAL_TAIL_SIZE = 1024 * 1024; // 1MB tail for initial load (more scrollback on tab switch)
|
|
@@ -1017,6 +1020,56 @@ function tabClusterNameSplit(name, label) {
|
|
|
1017
1020
|
return match[2].slice(1).toLowerCase() === label.toLowerCase() ? { shown: match[1], hidden: match[2] } : null;
|
|
1018
1021
|
}
|
|
1019
1022
|
|
|
1023
|
+
/**
|
|
1024
|
+
* Session-list search: the vertical rail's search box and the sidebar's filter
|
|
1025
|
+
* box. Trimmed, case-insensitive substring; a whitespace-only query is no query.
|
|
1026
|
+
* Lower-cased with toLowerCase(), never toLocaleLowerCase(): under a Turkish or
|
|
1027
|
+
* Azeri browser locale "API" lowers to "apı" and a search for "api" would miss it.
|
|
1028
|
+
* @param {unknown} query
|
|
1029
|
+
* @returns {string} the needle, '' when there is nothing to search for
|
|
1030
|
+
*/
|
|
1031
|
+
function tabSearchNeedle(query) {
|
|
1032
|
+
return typeof query === 'string' ? query.trim().toLowerCase() : '';
|
|
1033
|
+
}
|
|
1034
|
+
|
|
1035
|
+
/**
|
|
1036
|
+
* Which rows a search hides. Pure: the caller reads the rows off the list it
|
|
1037
|
+
* rendered and applies the result as classes, so the list itself (grouping,
|
|
1038
|
+
* order, Alt+N badges) is never rebuilt or reordered by a search.
|
|
1039
|
+
*
|
|
1040
|
+
* A row flagged `keep: true` is never hidden, matching or not (the caller keeps
|
|
1041
|
+
* a tab with an alert on screen: a prompt waiting on you is never hidden by a
|
|
1042
|
+
* view filter). It counts toward its section, so its group stays on screen with
|
|
1043
|
+
* it, but not toward `matchCount`.
|
|
1044
|
+
*
|
|
1045
|
+
* @param {Array<{key: unknown, text: string, section?: unknown, keep?: boolean}>} rows
|
|
1046
|
+
* in list order; `section` is the row's group or case box, null/undefined for none.
|
|
1047
|
+
* @param {unknown} query
|
|
1048
|
+
* @returns {{active: boolean, hidden: Set<unknown>, counts: Map<unknown, number>, matchCount: number}}
|
|
1049
|
+
* `counts` is the rows left showing per section (kept rows included), every
|
|
1050
|
+
* section seen, an emptied one as 0, so it can be hidden; `matchCount` is the
|
|
1051
|
+
* number of rows whose TEXT matched, so it can be 0 above a lone kept row.
|
|
1052
|
+
*/
|
|
1053
|
+
function filterTabSearchRows(rows, query) {
|
|
1054
|
+
const needle = tabSearchNeedle(query);
|
|
1055
|
+
const hidden = new Set();
|
|
1056
|
+
const counts = new Map();
|
|
1057
|
+
let matchCount = 0;
|
|
1058
|
+
for (const row of Array.isArray(rows) ? rows : []) {
|
|
1059
|
+
const hasSection = row.section !== null && row.section !== undefined;
|
|
1060
|
+
if (hasSection && !counts.has(row.section)) counts.set(row.section, 0);
|
|
1061
|
+
const text = typeof row.text === 'string' ? row.text.toLowerCase() : '';
|
|
1062
|
+
const matches = !needle || text.includes(needle);
|
|
1063
|
+
if (!matches && row.keep !== true) {
|
|
1064
|
+
hidden.add(row.key);
|
|
1065
|
+
continue;
|
|
1066
|
+
}
|
|
1067
|
+
if (matches) matchCount++;
|
|
1068
|
+
if (hasSection) counts.set(row.section, counts.get(row.section) + 1);
|
|
1069
|
+
}
|
|
1070
|
+
return { active: needle.length > 0, hidden, counts, matchCount };
|
|
1071
|
+
}
|
|
1072
|
+
|
|
1020
1073
|
// Terminal font stack — the single source for every xterm surface (the main
|
|
1021
1074
|
// terminal in terminal-ui.js, the log-viewer terminal in panels-ui.js).
|
|
1022
1075
|
// "Symbols Nerd Font Mono" is a bundled icons-only webfont (fonts/ +
|
|
@@ -1298,6 +1351,63 @@ function cleanCopiedSelection(text, options) {
|
|
|
1298
1351
|
return lines.join('\n');
|
|
1299
1352
|
}
|
|
1300
1353
|
|
|
1354
|
+
// ── Markdown heading anchors ────────────────────────────────────────────────
|
|
1355
|
+
// marked emits no `id` on headings, so a rendered document's own `[Install](#installation)` links had
|
|
1356
|
+
// nothing to jump to. And with `<base href="/">` a bare `#installation` href points at the dashboard's
|
|
1357
|
+
// root, not at the page, so letting the browser follow it navigates the app away. The click delegate
|
|
1358
|
+
// (`_bindResponseViewerInteractions`) therefore resolves in-document links itself, with the helpers
|
|
1359
|
+
// below. Anchors are `data-md-anchor` attributes, NOT `id`s: a heading titled "Settings" must not claim
|
|
1360
|
+
// the id of an element in the app's own DOM, and the lookup is scoped to the rendered document.
|
|
1361
|
+
|
|
1362
|
+
/**
|
|
1363
|
+
* GitHub's heading slug: lower-cased, anything that is not a letter, mark, number, `_`, `-` or space
|
|
1364
|
+
* dropped, each space a hyphen (`Why `codeman`? → `why-codeman`, `Über uns` → `über-uns`).
|
|
1365
|
+
*/
|
|
1366
|
+
function markdownHeadingSlug(text) {
|
|
1367
|
+
return String(text ?? '')
|
|
1368
|
+
.trim()
|
|
1369
|
+
.toLowerCase()
|
|
1370
|
+
.replace(/[^\p{L}\p{M}\p{N}_\- ]/gu, '')
|
|
1371
|
+
.replace(/ /g, '-');
|
|
1372
|
+
}
|
|
1373
|
+
|
|
1374
|
+
/** Give every h1..h6 under `root` its slug in `data-md-anchor`; a repeat gets `-1`, `-2`, ... as on GitHub. Idempotent. */
|
|
1375
|
+
function assignMarkdownHeadingAnchors(root) {
|
|
1376
|
+
const used = new Set();
|
|
1377
|
+
for (const heading of root.querySelectorAll('h1, h2, h3, h4, h5, h6')) {
|
|
1378
|
+
const base = markdownHeadingSlug(heading.textContent);
|
|
1379
|
+
let slug = base;
|
|
1380
|
+
for (let n = 1; used.has(slug); n += 1) slug = `${base}-${n}`;
|
|
1381
|
+
used.add(slug);
|
|
1382
|
+
heading.dataset.mdAnchor = slug;
|
|
1383
|
+
}
|
|
1384
|
+
}
|
|
1385
|
+
|
|
1386
|
+
/**
|
|
1387
|
+
* The element inside `root` that an in-document link (`#installation`, `#Installation`, `#my%20title`)
|
|
1388
|
+
* points at, or null. An empty fragment (`#`) means the top of the document. Headings are matched by
|
|
1389
|
+
* slug, then a heading the author wrote an explicit `<a id="...">`/`id` for, looked up INSIDE `root`
|
|
1390
|
+
* only (never `document.getElementById`, which could find an app element of the same name).
|
|
1391
|
+
*/
|
|
1392
|
+
function findMarkdownAnchorTarget(root, href) {
|
|
1393
|
+
let fragment = String(href ?? '').replace(/^#/, '');
|
|
1394
|
+
try {
|
|
1395
|
+
fragment = decodeURIComponent(fragment);
|
|
1396
|
+
} catch {
|
|
1397
|
+
/* a malformed escape: use it as written */
|
|
1398
|
+
}
|
|
1399
|
+
if (!fragment) return root;
|
|
1400
|
+
assignMarkdownHeadingAnchors(root);
|
|
1401
|
+
const wanted = [fragment.toLowerCase(), markdownHeadingSlug(fragment)];
|
|
1402
|
+
for (const heading of root.querySelectorAll('[data-md-anchor]')) {
|
|
1403
|
+
if (wanted.includes(heading.dataset.mdAnchor)) return heading;
|
|
1404
|
+
}
|
|
1405
|
+
for (const el of root.querySelectorAll('[id]')) {
|
|
1406
|
+
if (el.id === fragment) return el;
|
|
1407
|
+
}
|
|
1408
|
+
return null;
|
|
1409
|
+
}
|
|
1410
|
+
|
|
1301
1411
|
if (typeof window !== 'undefined') {
|
|
1302
1412
|
window.WEBGL_FALLBACK = WEBGL_FALLBACK;
|
|
1303
1413
|
window.evaluateWebGLLongTaskTrip = evaluateWebGLLongTaskTrip;
|
|
@@ -1355,6 +1465,10 @@ if (typeof window !== 'undefined') {
|
|
|
1355
1465
|
groupFor: tabTriageGroupFor,
|
|
1356
1466
|
layout: computeTabTriageLayout,
|
|
1357
1467
|
};
|
|
1468
|
+
window.CodemanTabSearch = {
|
|
1469
|
+
needle: tabSearchNeedle,
|
|
1470
|
+
filter: filterTabSearchRows,
|
|
1471
|
+
};
|
|
1358
1472
|
window.CodemanInputLimit = {
|
|
1359
1473
|
FRAME_MAX_CHARS: INPUT_FRAME_MAX_CHARS,
|
|
1360
1474
|
PASTE_MAX_CHARS: INPUT_PASTE_MAX_CHARS,
|
|
@@ -1367,6 +1481,11 @@ if (typeof window !== 'undefined') {
|
|
|
1367
1481
|
window.CodemanCopySelection = {
|
|
1368
1482
|
clean: cleanCopiedSelection,
|
|
1369
1483
|
};
|
|
1484
|
+
window.CodemanMarkdownAnchors = {
|
|
1485
|
+
slug: markdownHeadingSlug,
|
|
1486
|
+
assign: assignMarkdownHeadingAnchors,
|
|
1487
|
+
find: findMarkdownAnchorTarget,
|
|
1488
|
+
};
|
|
1370
1489
|
window.CodemanTerminalFont = {
|
|
1371
1490
|
DEFAULT_STACK: TERMINAL_FONT_DEFAULT_STACK,
|
|
1372
1491
|
resolve: resolveTerminalFontFamily,
|
|
@@ -2083,11 +2202,11 @@ function tileGridCapacity({ width, height }) {
|
|
|
2083
2202
|
}
|
|
2084
2203
|
|
|
2085
2204
|
/**
|
|
2086
|
-
* The stored grid (`codeman:tile-grid
|
|
2087
|
-
*
|
|
2088
|
-
* TILE_GRID_MAX, `focused` / `zoomed` must
|
|
2089
|
-
* must be 1 to 3 finite positive numbers.
|
|
2090
|
-
* (or its JSON) gives null.
|
|
2205
|
+
* The stored grid (`codeman:tile-grid`: session ids and the layout, never
|
|
2206
|
+
* content) made safe to apply: unknown, deleted, detached and duplicate ids
|
|
2207
|
+
* are dropped, the list is capped at TILE_GRID_MAX, `focused` / `zoomed` must
|
|
2208
|
+
* name a kept id, and track fractions must be 1 to 3 finite positive numbers.
|
|
2209
|
+
* Anything that is not a v1 object (or its JSON) gives null.
|
|
2091
2210
|
*
|
|
2092
2211
|
* The stored `ids` are the grid's CELLS in reading order, `null` for an empty
|
|
2093
2212
|
* one (a hole can be any cell). The old packed list (no nulls) reads as cells
|
|
@@ -2095,11 +2214,20 @@ function tileGridCapacity({ width, height }) {
|
|
|
2095
2214
|
* every list consumer wants) and `cells` keeps the holes: a dropped id (gone,
|
|
2096
2215
|
* detached, a duplicate, past the cap) becomes `null` there, never a shift.
|
|
2097
2216
|
*
|
|
2217
|
+
* `freed` names the cells whose session no longer exists (or was popped out
|
|
2218
|
+
* to its own window) since the grid was stored: the ranking fills those first
|
|
2219
|
+
* when the grid comes back (restoreTileGridCells). A hole the user left empty
|
|
2220
|
+
* is not freed. `count` is how many tiles the grid had after the user's own
|
|
2221
|
+
* last change (a session that went away by itself does not lower it), so the
|
|
2222
|
+
* grid comes back to that many when there are sessions to fill it; a value
|
|
2223
|
+
* stored before it existed, or a malformed one, reads as the number of
|
|
2224
|
+
* sessions the stored cells name.
|
|
2225
|
+
*
|
|
2098
2226
|
* @param {unknown} raw - the parsed value, or the stored JSON string
|
|
2099
2227
|
* @param {{has(id: string): boolean}|Iterable<string>} liveSessions - ids that exist now
|
|
2100
2228
|
* @param {{has(id: string): boolean}} [detachedIds] - sessions popped out to their own window
|
|
2101
|
-
* @returns {{v: 1, open: boolean, ids: string[], cells: (string|null)[],
|
|
2102
|
-
* zoomed: string|null, colFr: number[]|null, rowFr: number[]|null}|null}
|
|
2229
|
+
* @returns {{v: 1, open: boolean, ids: string[], cells: (string|null)[], freed: number[], count: number,
|
|
2230
|
+
* focused: string|null, zoomed: string|null, colFr: number[]|null, rowFr: number[]|null}|null}
|
|
2103
2231
|
*/
|
|
2104
2232
|
function sanitizeTileGridState(raw, liveSessions, detachedIds) {
|
|
2105
2233
|
let value = raw;
|
|
@@ -2110,11 +2238,16 @@ function sanitizeTileGridState(raw, liveSessions, detachedIds) {
|
|
|
2110
2238
|
const live = liveSessions && typeof liveSessions.has === 'function' ? liveSessions : new Set(liveSessions || []);
|
|
2111
2239
|
const ids = [];
|
|
2112
2240
|
const cells = [];
|
|
2241
|
+
const freed = [];
|
|
2242
|
+
const named = [];
|
|
2113
2243
|
for (const id of (Array.isArray(value.ids) ? value.ids : []).slice(0, TILE_LAYOUT_MAX)) {
|
|
2114
|
-
const
|
|
2115
|
-
|
|
2116
|
-
|
|
2244
|
+
const isId = typeof id === 'string' && id !== '';
|
|
2245
|
+
const present = isId && live.has(id) && !detachedIds?.has?.(id);
|
|
2246
|
+
const keep = present && !ids.includes(id) && ids.length < TILE_GRID_MAX;
|
|
2117
2247
|
if (keep) ids.push(id);
|
|
2248
|
+
// Its session went away since: the cell is freed for the ranking to fill.
|
|
2249
|
+
if (isId && !present && !named.includes(id)) freed.push(cells.length);
|
|
2250
|
+
if (isId && !named.includes(id)) named.push(id);
|
|
2118
2251
|
// A malformed entry (not a string, not null) is a hole too.
|
|
2119
2252
|
cells.push(keep ? id : null);
|
|
2120
2253
|
}
|
|
@@ -2122,11 +2255,17 @@ function sanitizeTileGridState(raw, liveSessions, detachedIds) {
|
|
|
2122
2255
|
if (!Array.isArray(fr) || fr.length < 1 || fr.length > 3) return null;
|
|
2123
2256
|
return fr.every((x) => typeof x === 'number' && Number.isFinite(x) && x > 0) ? fr.slice() : null;
|
|
2124
2257
|
};
|
|
2258
|
+
const count =
|
|
2259
|
+
Number.isInteger(value.count) && value.count >= 1 && value.count <= TILE_GRID_MAX
|
|
2260
|
+
? value.count
|
|
2261
|
+
: Math.min(named.length, TILE_GRID_MAX);
|
|
2125
2262
|
return {
|
|
2126
2263
|
v: 1,
|
|
2127
2264
|
open: value.open === true && ids.length > 0,
|
|
2128
2265
|
ids,
|
|
2129
2266
|
cells,
|
|
2267
|
+
freed,
|
|
2268
|
+
count,
|
|
2130
2269
|
focused: ids.includes(value.focused) ? value.focused : (ids[0] ?? null),
|
|
2131
2270
|
zoomed: ids.includes(value.zoomed) ? value.zoomed : null,
|
|
2132
2271
|
colFr: fractions(value.colFr),
|
|
@@ -2134,6 +2273,53 @@ function sanitizeTileGridState(raw, liveSessions, detachedIds) {
|
|
|
2134
2273
|
};
|
|
2135
2274
|
}
|
|
2136
2275
|
|
|
2276
|
+
/**
|
|
2277
|
+
* A stored grid as it comes back (the Tiles button, a page reload): its cells
|
|
2278
|
+
* exactly as stored, holes the user left included, and its focus (or the tile
|
|
2279
|
+
* it had zoomed). Only when it holds fewer tiles than its `count` (sessions
|
|
2280
|
+
* that went away since, or by themselves while it was open) does it fill, from
|
|
2281
|
+
* `ranked` (best first, never a session already in it): the freed cells first,
|
|
2282
|
+
* then the other empty cells, in reading order; more than the cells hold join
|
|
2283
|
+
* after them (the shape grows when the grid lays them out, reformTileCells).
|
|
2284
|
+
* A cell stays empty only when no other session is left to place. Never
|
|
2285
|
+
* trimmed to the window: a grid larger than the window fits shows its focused
|
|
2286
|
+
* tile alone until the window fits it again, and the arrangement stays.
|
|
2287
|
+
*
|
|
2288
|
+
* @param {{cells?: (string|null)[], ids?: string[], freed?: number[], count?: number,
|
|
2289
|
+
* focused?: string|null, zoomed?: string|null}|null} stored - sanitized (sanitizeTileGridState)
|
|
2290
|
+
* @param {string[]} ranked - the sessions that may fill a cell, best first (rankTileSessions)
|
|
2291
|
+
* @returns {{ids: string[], cells: (string|null)[], focusedId: string}|null} null when none of its sessions survive
|
|
2292
|
+
*/
|
|
2293
|
+
function restoreTileGridCells(stored, ranked) {
|
|
2294
|
+
const source = Array.isArray(stored?.cells) ? stored.cells : Array.isArray(stored?.ids) ? stored.ids : [];
|
|
2295
|
+
const cells = [];
|
|
2296
|
+
for (const id of source) cells.push(typeof id === 'string' && id && !cells.includes(id) ? id : null);
|
|
2297
|
+
const tiles = cells.filter(Boolean);
|
|
2298
|
+
if (tiles.length === 0) return null;
|
|
2299
|
+
const focusedId =
|
|
2300
|
+
[stored.zoomed, stored.focused].find((id) => typeof id === 'string' && tiles.includes(id)) ?? tiles[0];
|
|
2301
|
+
const target = Math.min(Math.max(tiles.length, Math.floor(Number(stored.count)) || 0), TILE_GRID_MAX);
|
|
2302
|
+
const fillers = [];
|
|
2303
|
+
for (const id of ranked || []) {
|
|
2304
|
+
if (typeof id === 'string' && id && !cells.includes(id) && !fillers.includes(id)) fillers.push(id);
|
|
2305
|
+
}
|
|
2306
|
+
const freed = new Set(Array.isArray(stored.freed) ? stored.freed : []);
|
|
2307
|
+
const empty = [];
|
|
2308
|
+
cells.forEach((id, k) => {
|
|
2309
|
+
if (id === null) empty.push(k);
|
|
2310
|
+
});
|
|
2311
|
+
// Freed cells first, each group in reading order.
|
|
2312
|
+
empty.sort((a, b) => Number(freed.has(b)) - Number(freed.has(a)) || a - b);
|
|
2313
|
+
let count = tiles.length;
|
|
2314
|
+
for (const k of empty) {
|
|
2315
|
+
if (count >= target || fillers.length === 0) break;
|
|
2316
|
+
cells[k] = fillers.shift();
|
|
2317
|
+
count++;
|
|
2318
|
+
}
|
|
2319
|
+
const extra = fillers.slice(0, Math.max(0, target - count));
|
|
2320
|
+
return { ids: [...cells.filter(Boolean), ...extra], cells, focusedId };
|
|
2321
|
+
}
|
|
2322
|
+
|
|
2137
2323
|
/**
|
|
2138
2324
|
* New track fractions after a divider drag (grid-template `fr` values): the two
|
|
2139
2325
|
* tracks either side of divider `index` trade `deltaPx` of size, each kept at
|
|
@@ -2166,7 +2352,8 @@ function dragTrackFractions(fr, index, deltaPx, totalPx, minPx) {
|
|
|
2166
2352
|
|
|
2167
2353
|
/**
|
|
2168
2354
|
* The sessions the Tiles button can open (case c of tileGridOpenSet, and the
|
|
2169
|
-
* ones a count fills a grid with), in
|
|
2355
|
+
* ones a count fills a grid with), in the order given (tab order, or the
|
|
2356
|
+
* ranking): live ones only, never a session popped out to
|
|
2170
2357
|
* its own window (that window owns its PTY size). A session with no PTY
|
|
2171
2358
|
* attached IS offered: its tile shows the Attach overlay.
|
|
2172
2359
|
*
|
|
@@ -2180,44 +2367,121 @@ function buildTilePickerSessions(sessions, sessionOrder, detachedIds) {
|
|
|
2180
2367
|
for (const id of sessionOrder) {
|
|
2181
2368
|
if (detachedIds?.has?.(id)) continue;
|
|
2182
2369
|
const session = sessions.get(id);
|
|
2183
|
-
if (!session) continue;
|
|
2370
|
+
if (!session || result.some((r) => r.id === id)) continue;
|
|
2184
2371
|
result.push({ id, label: session.name || 'Session' });
|
|
2185
2372
|
}
|
|
2186
2373
|
return result;
|
|
2187
2374
|
}
|
|
2188
2375
|
|
|
2376
|
+
// Ranking groups (rankTileSessions): working first, then the sessions waiting
|
|
2377
|
+
// on the user, then everything else.
|
|
2378
|
+
const TILE_RANK_GROUP = { working: 0, needs: 1, waiting: 1 };
|
|
2379
|
+
|
|
2380
|
+
/**
|
|
2381
|
+
* The stamp a session is ranked by inside its group. A WORKING session keys
|
|
2382
|
+
* off the pane's last Enter (`lastSubmitAt`) ONLY: a working pane repaints
|
|
2383
|
+
* about once a second, so its last-activity stamp is always "now", and one
|
|
2384
|
+
* that never submitted would otherwise claim the head of the group. 0 means
|
|
2385
|
+
* unknown. Every other state is the home screens' anchor (sessionActivityAnchor:
|
|
2386
|
+
* the last byte the pane printed, i.e. when it went quiet).
|
|
2387
|
+
*/
|
|
2388
|
+
function tileRankStamp(row) {
|
|
2389
|
+
if (row.state === 'working') return Number(row.lastSubmitAt) || 0;
|
|
2390
|
+
return sessionActivityAnchor(row);
|
|
2391
|
+
}
|
|
2392
|
+
|
|
2393
|
+
/**
|
|
2394
|
+
* Which open sessions the tile grid shows when nobody said which (the Tiles
|
|
2395
|
+
* button with no stored grid to bring back, and every place the grid fills a
|
|
2396
|
+
* tile on its own: a count picked in its menu, a freed cell), best first
|
|
2397
|
+
* (owner request: "prefer to load in tiles that are working and then the most
|
|
2398
|
+
* recent, so the oldest don't get opened"):
|
|
2399
|
+
* 1. WORKING, the most recently started turn first;
|
|
2400
|
+
* 2. then the ones that NEED INPUT, the red and yellow tab alerts (`needs`: a
|
|
2401
|
+
* permission or question dialog; `waiting`: a finished turn not seen
|
|
2402
|
+
* yet), most recent first;
|
|
2403
|
+
* 3. then every other one (idle, done, error), most recently active first.
|
|
2404
|
+
* Inside a group the newest stamp wins (tileRankStamp) and a session with no
|
|
2405
|
+
* stamp (0) sorts last; the final tiebreak is the tab order (`orderIndex`), so
|
|
2406
|
+
* the result never shuffles. The states are the home screens' own
|
|
2407
|
+
* (`_mobileOverviewState()`, mobile-overview.js), as are the stamps; only the
|
|
2408
|
+
* order differs: the home screens put the longest-running turn first, the grid
|
|
2409
|
+
* the most recent.
|
|
2410
|
+
*
|
|
2411
|
+
* Pure. Unit-tested in test/tile-grid-ranking.test.ts.
|
|
2412
|
+
*
|
|
2413
|
+
* @param {Array<{id: string, state: string, lastActivityAt?: number, lastSubmitAt?: number, orderIndex?: number}>} rows
|
|
2414
|
+
* @returns {string[]} the ids, best first, each once
|
|
2415
|
+
*/
|
|
2416
|
+
function rankTileSessions(rows) {
|
|
2417
|
+
const group = (row) => TILE_RANK_GROUP[row.state] ?? 2;
|
|
2418
|
+
const list = (Array.isArray(rows) ? rows : []).filter((row) => row && typeof row.id === 'string' && row.id);
|
|
2419
|
+
list.sort((a, b) => {
|
|
2420
|
+
const byGroup = group(a) - group(b);
|
|
2421
|
+
if (byGroup !== 0) return byGroup;
|
|
2422
|
+
const atA = tileRankStamp(a);
|
|
2423
|
+
const atB = tileRankStamp(b);
|
|
2424
|
+
if (atA !== atB) {
|
|
2425
|
+
if (!atA) return 1;
|
|
2426
|
+
if (!atB) return -1;
|
|
2427
|
+
return atB - atA;
|
|
2428
|
+
}
|
|
2429
|
+
const orderA = Number.isFinite(a.orderIndex) ? a.orderIndex : Number.MAX_SAFE_INTEGER;
|
|
2430
|
+
const orderB = Number.isFinite(b.orderIndex) ? b.orderIndex : Number.MAX_SAFE_INTEGER;
|
|
2431
|
+
return orderA - orderB;
|
|
2432
|
+
});
|
|
2433
|
+
const ids = [];
|
|
2434
|
+
for (const row of list) if (!ids.includes(row.id)) ids.push(row.id);
|
|
2435
|
+
return ids;
|
|
2436
|
+
}
|
|
2437
|
+
|
|
2189
2438
|
/**
|
|
2190
2439
|
* What the Tiles button and Ctrl+Shift+G open, at once and without asking
|
|
2191
2440
|
* (owner decision 8). In order:
|
|
2192
2441
|
* a. the grid this tab last had (`stored`, already sanitized: live, not
|
|
2193
|
-
* detached, at most the cap), if any of its sessions survive
|
|
2442
|
+
* detached, at most the cap), if any of its sessions survive, exactly
|
|
2443
|
+
* as it was (restoreTileGridCells: its cells and holes, a cell its
|
|
2444
|
+
* session freed filled from the ranking);
|
|
2194
2445
|
* b. else an open split's two sessions, Pane A focused;
|
|
2195
|
-
* c. else the open sessions in
|
|
2196
|
-
*
|
|
2197
|
-
*
|
|
2446
|
+
* c. else the open sessions in `ranked` order (rankTileSessions: working,
|
|
2447
|
+
* then needing input, then the most recent; tab order when no ranking is
|
|
2448
|
+
* given), detached ones never, up to `limit`, the active session always
|
|
2449
|
+
* among them and focused (when it ranks past the limit, the first
|
|
2450
|
+
* `limit - 1` others come with it).
|
|
2198
2451
|
* Null when there is nothing to open.
|
|
2199
2452
|
*
|
|
2200
|
-
* @param {{stored?: {ids: string[],
|
|
2201
|
-
*
|
|
2453
|
+
* @param {{stored?: {ids: string[], cells?: (string|null)[], freed?: number[], count?: number,
|
|
2454
|
+
* focused: string|null, zoomed: string|null}|null,
|
|
2455
|
+
* split?: string[]|null, ranked?: string[]|null, sessions: Map<string, object>, sessionOrder: string[],
|
|
2202
2456
|
* detachedIds?: {has(id: string): boolean}, activeId?: string|null, limit: number}} p
|
|
2203
|
-
* @returns {{source: 'stored'|'split'|'
|
|
2457
|
+
* @returns {{source: 'stored'|'split'|'ranked', ids: string[], cells?: (string|null)[],
|
|
2458
|
+
* focusedId: string|null}|null}
|
|
2204
2459
|
*/
|
|
2205
|
-
function tileGridOpenSet({
|
|
2206
|
-
|
|
2207
|
-
|
|
2208
|
-
|
|
2209
|
-
|
|
2460
|
+
function tileGridOpenSet({
|
|
2461
|
+
stored = null,
|
|
2462
|
+
split = null,
|
|
2463
|
+
ranked = null,
|
|
2464
|
+
sessions,
|
|
2465
|
+
sessionOrder,
|
|
2466
|
+
detachedIds,
|
|
2467
|
+
activeId = null,
|
|
2468
|
+
limit,
|
|
2469
|
+
}) {
|
|
2470
|
+
const all = buildTilePickerSessions(sessions, Array.isArray(ranked) ? ranked : sessionOrder, detachedIds).map(
|
|
2471
|
+
(c) => c.id
|
|
2472
|
+
);
|
|
2473
|
+
const restored = stored ? restoreTileGridCells(stored, all) : null;
|
|
2474
|
+
if (restored) return { source: 'stored', ...restored };
|
|
2210
2475
|
const usable = (id) => typeof id === 'string' && sessions.has(id) && !detachedIds?.has?.(id);
|
|
2211
2476
|
const pair = (split || []).filter(usable);
|
|
2212
2477
|
if (split && pair.length) return { source: 'split', ids: [...new Set(pair)], focusedId: pair[0] };
|
|
2213
2478
|
const max = Math.max(1, Math.min(Math.floor(Number(limit) || 0), TILE_GRID_MAX));
|
|
2214
|
-
const all = buildTilePickerSessions(sessions, sessionOrder, detachedIds).map((c) => c.id);
|
|
2215
2479
|
if (all.length === 0) return null;
|
|
2216
2480
|
let ids = all.slice(0, max);
|
|
2217
2481
|
if (all.includes(activeId) && !ids.includes(activeId)) {
|
|
2218
2482
|
ids = [...all.filter((id) => id !== activeId).slice(0, max - 1), activeId];
|
|
2219
2483
|
}
|
|
2220
|
-
return { source: '
|
|
2484
|
+
return { source: 'ranked', ids, focusedId: ids.includes(activeId) ? activeId : ids[0] };
|
|
2221
2485
|
}
|
|
2222
2486
|
|
|
2223
2487
|
/**
|
|
@@ -2237,8 +2501,9 @@ function sanitizeTileCount(raw) {
|
|
|
2237
2501
|
* `base` (what the grid would open, or what an open grid shows, in its order)
|
|
2238
2502
|
* trimmed or filled to `n` tiles: trimmed from the end, the session to focus
|
|
2239
2503
|
* (`keepId`) always kept (it takes the last place when it sat past `n`, as in
|
|
2240
|
-
* tileGridOpenSet's case c); filled from `all` (the open sessions
|
|
2241
|
-
* with the ones not in it yet.
|
|
2504
|
+
* tileGridOpenSet's case c); filled from `all` (the open sessions, best first:
|
|
2505
|
+
* the app passes the ranking, rankTileSessions) with the ones not in it yet.
|
|
2506
|
+
* Fewer sessions than `n` give fewer tiles.
|
|
2242
2507
|
*
|
|
2243
2508
|
* @param {string[]} base
|
|
2244
2509
|
* @param {string[]} all
|
|
@@ -2747,6 +3012,8 @@ if (typeof window !== 'undefined') {
|
|
|
2747
3012
|
fitTileCells,
|
|
2748
3013
|
cycleTile,
|
|
2749
3014
|
tileGridOpenSet,
|
|
3015
|
+
restoreTileGridCells,
|
|
3016
|
+
rankTileSessions,
|
|
2750
3017
|
sanitizeTileCount,
|
|
2751
3018
|
tileGridSetForCount,
|
|
2752
3019
|
tileCellCols,
|
|
Binary file
|
|
Binary file
|
|
Binary file
|