aicodeman 1.18.0 → 1.18.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +4 -3
- package/README.zh-CN.md +3 -3
- package/dist/web/http-range.d.ts +44 -0
- package/dist/web/http-range.d.ts.map +1 -0
- package/dist/web/http-range.js +76 -0
- package/dist/web/http-range.js.map +1 -0
- 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.a13ac13b.js → app.445f1584.js} +4 -6
- package/dist/web/public/app.445f1584.js.br +0 -0
- package/dist/web/public/app.445f1584.js.gz +0 -0
- package/dist/web/public/approvals-ui.js.gz +0 -0
- package/dist/web/public/{constants.42b8bbc9.js → constants.822a486d.js} +132 -35
- package/dist/web/public/constants.822a486d.js.br +0 -0
- package/dist/web/public/constants.822a486d.js.gz +0 -0
- package/dist/web/public/cron-ui.js.gz +0 -0
- package/dist/web/public/entrance-animations.js.gz +0 -0
- package/dist/web/public/home-sessions.js.gz +0 -0
- package/dist/web/public/i18n.86757605.js.gz +0 -0
- package/dist/web/public/image-input.ee16ad88.js.gz +0 -0
- package/dist/web/public/index.html +13 -8
- package/dist/web/public/index.html.br +0 -0
- package/dist/web/public/index.html.gz +0 -0
- package/dist/web/public/input-cjk.63794d0b.js.gz +0 -0
- package/dist/web/public/keyboard-accessory.0454a3e9.js.gz +0 -0
- package/dist/web/public/{mobile-handlers.32fdd57f.js → mobile-handlers.bc45f976.js} +55 -9
- package/dist/web/public/mobile-handlers.bc45f976.js.br +0 -0
- package/dist/web/public/mobile-handlers.bc45f976.js.gz +0 -0
- package/dist/web/public/mobile-overview.js.gz +0 -0
- package/dist/web/public/mobile.1eef31fe.css.gz +0 -0
- package/dist/web/public/notification-manager.5d229063.js.gz +0 -0
- package/dist/web/public/orchestrator-panel.js.gz +0 -0
- package/dist/web/public/{panels-ui.4255e1bc.js → panels-ui.1e4cb4b2.js} +1 -1
- package/dist/web/public/panels-ui.1e4cb4b2.js.br +0 -0
- package/dist/web/public/panels-ui.1e4cb4b2.js.gz +0 -0
- package/dist/web/public/ralph-panel.6de2d0f8.js.gz +0 -0
- package/dist/web/public/ralph-wizard.13a1831e.js.gz +0 -0
- package/dist/web/public/readmymind-ui.js.gz +0 -0
- package/dist/web/public/respawn-ui.ff0dae4c.js.gz +0 -0
- package/dist/web/public/sanitize-html.bc7078d6.js.gz +0 -0
- package/dist/web/public/session-lineage.js +3 -1
- package/dist/web/public/session-lineage.js.br +0 -0
- package/dist/web/public/session-lineage.js.gz +0 -0
- package/dist/web/public/session-ui.20be58fa.js.gz +0 -0
- package/dist/web/public/settings-ui.eacb4f92.js.gz +0 -0
- package/dist/web/public/styles.70bf9c2a.css +1 -0
- package/dist/web/public/styles.70bf9c2a.css.br +0 -0
- package/dist/web/public/styles.70bf9c2a.css.gz +0 -0
- package/dist/web/public/subagent-windows.2a192c3e.js.gz +0 -0
- package/dist/web/public/sw.js.gz +0 -0
- package/dist/web/public/{terminal-ui.5046620b.js → terminal-ui.c182a68d.js} +1 -1
- package/dist/web/public/{terminal-ui.5046620b.js.br → terminal-ui.c182a68d.js.br} +0 -0
- package/dist/web/public/{terminal-ui.5046620b.js.gz → terminal-ui.c182a68d.js.gz} +0 -0
- package/dist/web/public/ultracode-panel.js.gz +0 -0
- package/dist/web/public/ultracode-windows.js.gz +0 -0
- package/dist/web/public/upload.html.gz +0 -0
- package/dist/web/public/vendor/dompurify.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.6cec0fba.js.gz +0 -0
- package/dist/web/public/vendor/xterm-zerolag-input.137ad9f0.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.4ec670b0.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/file-routes.d.ts.map +1 -1
- package/dist/web/routes/file-routes.js +51 -18
- package/dist/web/routes/file-routes.js.map +1 -1
- package/dist/web/routes/session-routes.d.ts.map +1 -1
- package/dist/web/routes/session-routes.js +16 -0
- package/dist/web/routes/session-routes.js.map +1 -1
- package/package.json +1 -1
- package/skills/codeman/SKILL.md +251 -717
- package/skills/codeman/reference/endpoints.md +1 -1
- package/skills/codeman/reference/recipes.md +24 -11
- package/skills/codeman/reference/verbs.md +665 -0
- package/dist/web/public/app.a13ac13b.js.br +0 -0
- package/dist/web/public/app.a13ac13b.js.gz +0 -0
- package/dist/web/public/constants.42b8bbc9.js.br +0 -0
- package/dist/web/public/constants.42b8bbc9.js.gz +0 -0
- package/dist/web/public/mobile-handlers.32fdd57f.js.br +0 -0
- package/dist/web/public/mobile-handlers.32fdd57f.js.gz +0 -0
- package/dist/web/public/panels-ui.4255e1bc.js.br +0 -0
- package/dist/web/public/panels-ui.4255e1bc.js.gz +0 -0
- package/dist/web/public/styles.b6e8b4ae.css +0 -1
- package/dist/web/public/styles.b6e8b4ae.css.br +0 -0
- package/dist/web/public/styles.b6e8b4ae.css.gz +0 -0
|
@@ -201,24 +201,43 @@ function computeTabScrollLeft(input) {
|
|
|
201
201
|
// spawned (a worker started through the codeman agent skill, which passes its own
|
|
202
202
|
// id as parentSessionId). Pure: the caller measures and appends, this decides.
|
|
203
203
|
//
|
|
204
|
-
//
|
|
205
|
-
// shape (tab-bottom → window-top) has nothing to aim at:
|
|
206
|
-
//
|
|
207
|
-
//
|
|
208
|
-
//
|
|
209
|
-
//
|
|
210
|
-
//
|
|
211
|
-
//
|
|
204
|
+
// ONE shape, because both endpoints live in the same horizontal strip and the subagent
|
|
205
|
+
// shape (tab-bottom → window-top) has nothing to aim at: a U-bridge HANGING BELOW the
|
|
206
|
+
// strip, from the parent's bottom edge to the child's bottom edge, so it reads as a
|
|
207
|
+
// bracket joining two tabs rather than as a line crossing them. The dip grows with
|
|
208
|
+
// horizontal distance and with `depth` (the child's index among its siblings), so
|
|
209
|
+
// several children of one parent nest instead of overprinting.
|
|
210
|
+
//
|
|
211
|
+
// ⚠ A WRAPPED STRIP USED TO GET ITS OWN SHAPE, AND THAT SHAPE WAS THE BUG. When the
|
|
212
|
+
// desktop strip wraps (`tabs-two-rows` / `tabs-auto-wrap`) a parent on row 1 and its
|
|
213
|
+
// child on row 2 are ~4px apart vertically, so the old parent-bottom → child-TOP bezier
|
|
214
|
+
// had a 4px span to work with and drew a flat horizontal line inside the row gap
|
|
215
|
+
// (reported as "they connect already, but the lines are straight and not easy visible"),
|
|
216
|
+
// and three siblings drew three of them on top of each other. Aiming BOTH ends at the
|
|
217
|
+
// tab BOTTOMS and putting the control points below the LOWER row gives the wrapped case
|
|
218
|
+
// the same bracket as the flat case: it leaves the parent downward, crosses the lower
|
|
219
|
+
// row once, and comes back up under the child. Same formula, no branch.
|
|
212
220
|
//
|
|
213
221
|
// Returns null when the edge must not be drawn: a missing/degenerate rect, or an
|
|
214
222
|
// endpoint scrolled outside the strip. `.session-tabs` is `overflow-x: auto`, so a
|
|
215
223
|
// scrolled-out tab still HAS a rect — one lying over the logo or the header
|
|
216
224
|
// buttons. Skipping is honest; clamping would point at a tab that isn't there.
|
|
225
|
+
// ⚠ THE DIP IS WHAT MAKES THE ARC AN ARC, and the first shipped numbers were tuned
|
|
226
|
+
// against two tabs sitting side by side. A worker the agent skill starts is appended
|
|
227
|
+
// to the END of the strip, so the real span between a lead and its worker is 800-1500px,
|
|
228
|
+
// not 200, and a 44px cap over 1300px of span is a 33px sag, i.e. a line that reads as
|
|
229
|
+
// STRAIGHT and crosses the terminal instead of bracketing under the strip. The dip now
|
|
230
|
+
// keeps growing with the span (0.085/px, ~3x steeper against the old cap) so the bracket
|
|
231
|
+
// survives the distance the feature is actually used at. The ceiling is what keeps a
|
|
232
|
+
// full-width pair out of the terminal's fourth line: 104 + the sibling step lands the
|
|
233
|
+
// deepest sag around y=140 on a 1080 screen, the same proportion two adjacent tabs get.
|
|
217
234
|
const LINEAGE_DIP_BASE_PX = 14;
|
|
218
|
-
const LINEAGE_DIP_PER_PX = 0.
|
|
219
|
-
const LINEAGE_DIP_MIN_PX =
|
|
220
|
-
const LINEAGE_DIP_MAX_PX =
|
|
221
|
-
|
|
235
|
+
const LINEAGE_DIP_PER_PX = 0.085;
|
|
236
|
+
const LINEAGE_DIP_MIN_PX = 22;
|
|
237
|
+
const LINEAGE_DIP_MAX_PX = 104;
|
|
238
|
+
// Siblings nest by this much. Widened with the stroke: at 2.5px plus its glow, arcs 6px
|
|
239
|
+
// apart bled into one thick band instead of reading as three separate lines.
|
|
240
|
+
const LINEAGE_SIBLING_STEP_PX = 8;
|
|
222
241
|
const LINEAGE_STRIP_TOLERANCE_PX = 4;
|
|
223
242
|
|
|
224
243
|
function computeLineagePath(input) {
|
|
@@ -250,29 +269,20 @@ function computeLineagePath(input) {
|
|
|
250
269
|
const cBottom = cTop + ch;
|
|
251
270
|
const sameRow = Math.abs(pTop + ph / 2 - (cTop + ch / 2)) <= Math.min(ph, ch) / 2;
|
|
252
271
|
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
const childBelow = cTop + ch / 2 > pTop + ph / 2;
|
|
268
|
-
const y1 = childBelow ? pBottom : pTop;
|
|
269
|
-
const y2 = childBelow ? cTop : cBottom;
|
|
270
|
-
const mid = (y1 + y2) / 2;
|
|
271
|
-
d = `M ${r1(px)} ${r1(y1)} C ${r1(px)} ${r1(mid)}, ${r1(cx)} ${r1(mid)}, ${r1(cx)} ${r1(y2)}`;
|
|
272
|
-
endX = cx;
|
|
273
|
-
endY = y2;
|
|
274
|
-
}
|
|
275
|
-
return { d, endX, endY, sameRow };
|
|
272
|
+
// Both ends anchor on the tab BOTTOM, and the control points hang below whichever
|
|
273
|
+
// row is lower, so one formula covers a flat strip and a wrapped one.
|
|
274
|
+
const span = Math.abs(cx - px);
|
|
275
|
+
const rowDrop = Math.abs(cBottom - pBottom);
|
|
276
|
+
// ⚠ A wrapped pair needs the dip measured from the LOWER row, or the bracket would
|
|
277
|
+
// only reach the row gap again. Adding the row offset also keeps the curve clear of
|
|
278
|
+
// the row it crosses instead of grazing its bottom edge.
|
|
279
|
+
const dip =
|
|
280
|
+
Math.min(LINEAGE_DIP_MAX_PX, Math.max(LINEAGE_DIP_MIN_PX, LINEAGE_DIP_BASE_PX + span * LINEAGE_DIP_PER_PX)) +
|
|
281
|
+
depth * LINEAGE_SIBLING_STEP_PX +
|
|
282
|
+
rowDrop;
|
|
283
|
+
const yc = Math.max(pBottom, cBottom) + dip;
|
|
284
|
+
const d = `M ${r1(px)} ${r1(pBottom)} C ${r1(px)} ${r1(yc)}, ${r1(cx)} ${r1(yc)}, ${r1(cx)} ${r1(cBottom)}`;
|
|
285
|
+
return { d, endX: cx, endY: cBottom, sameRow };
|
|
276
286
|
}
|
|
277
287
|
|
|
278
288
|
// One decimal is plenty for a screen-space path and keeps the `d` string short.
|
|
@@ -785,3 +795,90 @@ function escapeHtml(text) {
|
|
|
785
795
|
if (typeof text !== 'string') return '';
|
|
786
796
|
return text.replace(_htmlEscapePattern, (ch) => _htmlEscapeMap[ch]);
|
|
787
797
|
}
|
|
798
|
+
|
|
799
|
+
/**
|
|
800
|
+
* Human-readable byte size for the partial-history banner (#258).
|
|
801
|
+
*
|
|
802
|
+
* Deliberately coarse: the banner is telling the user roughly how much of a
|
|
803
|
+
* transcript they are looking at, not accounting for bytes. Sub-KB amounts read
|
|
804
|
+
* as "less than 1 KB" rather than an exact count nobody can act on.
|
|
805
|
+
*
|
|
806
|
+
* @param {number} bytes
|
|
807
|
+
* @returns {string}
|
|
808
|
+
*/
|
|
809
|
+
function formatHistoryBytes(bytes) {
|
|
810
|
+
const n = typeof bytes === 'number' && isFinite(bytes) && bytes > 0 ? bytes : 0;
|
|
811
|
+
if (n < 1024) return 'less than 1 KB';
|
|
812
|
+
if (n < 1024 * 1024) return `${Math.round(n / 1024)} KB`;
|
|
813
|
+
return `${(n / (1024 * 1024)).toFixed(1)} MB`;
|
|
814
|
+
}
|
|
815
|
+
|
|
816
|
+
/**
|
|
817
|
+
* Decide what the partial-history banner should say (#258).
|
|
818
|
+
*
|
|
819
|
+
* PURE so the three states can be tested without a DOM. They exist because one
|
|
820
|
+
* `truncated` boolean could not distinguish messages the user acts on very
|
|
821
|
+
* differently:
|
|
822
|
+
* - recoverable: we tailed for speed and the rest is still retained
|
|
823
|
+
* - atCeiling: the FULL capture itself hit the byte ceiling
|
|
824
|
+
* - exhausted: a full pull was refused as a downgrade, so this is all there is
|
|
825
|
+
*
|
|
826
|
+
* @param {{truncated?: boolean, reason?: string|null, source?: string|null,
|
|
827
|
+
* fullSize?: number, retainedBytes?: number, exhausted?: boolean}} state
|
|
828
|
+
* @returns {{visible: boolean, message: string, canLoadMore: boolean}}
|
|
829
|
+
*/
|
|
830
|
+
function computeHistoryTruncationNotice(state = {}) {
|
|
831
|
+
if (!state.truncated) return { visible: false, message: '', canLoadMore: false };
|
|
832
|
+
|
|
833
|
+
const retained = Math.max(0, state.retainedBytes || 0);
|
|
834
|
+
const dropped = Math.max(0, (state.fullSize || 0) - retained);
|
|
835
|
+
const shown = formatHistoryBytes(retained);
|
|
836
|
+
// A full-history capture that was STILL capped is already everything tmux
|
|
837
|
+
// holds, so the remainder is out of reach rather than one request away.
|
|
838
|
+
const atCeiling = state.source === 'mux-full-history' && state.reason === 'capped';
|
|
839
|
+
|
|
840
|
+
if (state.exhausted) {
|
|
841
|
+
return {
|
|
842
|
+
visible: true,
|
|
843
|
+
message: `Showing all ${shown} of retained history. Earlier output is no longer kept for this session.`,
|
|
844
|
+
canLoadMore: false,
|
|
845
|
+
};
|
|
846
|
+
}
|
|
847
|
+
if (atCeiling) {
|
|
848
|
+
return {
|
|
849
|
+
visible: true,
|
|
850
|
+
message: `Showing the most recent ${shown}. Earlier output exceeds the retained history limit and cannot be recovered.`,
|
|
851
|
+
canLoadMore: false,
|
|
852
|
+
};
|
|
853
|
+
}
|
|
854
|
+
return {
|
|
855
|
+
visible: true,
|
|
856
|
+
message: `Showing the most recent ${shown} of this session. ${formatHistoryBytes(dropped)} more may still be retained.`,
|
|
857
|
+
canLoadMore: true,
|
|
858
|
+
};
|
|
859
|
+
}
|
|
860
|
+
|
|
861
|
+
/**
|
|
862
|
+
* Where to land after a rewrite that REPLACES the whole buffer (#259).
|
|
863
|
+
*
|
|
864
|
+
* The backpressure refresh clears the terminal and reloads it from a freshly
|
|
865
|
+
* fetched capture, so an absolute viewportY captured beforehand means nothing
|
|
866
|
+
* afterwards: the line it pointed at may not even exist. Distance from the
|
|
867
|
+
* BOTTOM is the anchor that survives a rewrite, so a reader stays roughly
|
|
868
|
+
* where they were reading.
|
|
869
|
+
*
|
|
870
|
+
* Returns null when the user was following live output, which the caller reads
|
|
871
|
+
* as "scroll to bottom" — the historical behavior, kept for that case.
|
|
872
|
+
*
|
|
873
|
+
* @param {{linesFromBottom?: number, baseY?: number}} input
|
|
874
|
+
* @returns {number|null}
|
|
875
|
+
*/
|
|
876
|
+
function computeRewriteScrollLine(input) {
|
|
877
|
+
const linesFromBottom = input?.linesFromBottom || 0;
|
|
878
|
+
if (!(linesFromBottom > 0)) return null;
|
|
879
|
+
return Math.max(0, (input?.baseY || 0) - linesFromBottom);
|
|
880
|
+
}
|
|
881
|
+
|
|
882
|
+
if (typeof window !== 'undefined') {
|
|
883
|
+
window.CodemanHistoryFormat = { formatHistoryBytes, computeHistoryTruncationNotice, computeRewriteScrollLine };
|
|
884
|
+
}
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
<link rel="manifest" href="manifest.json">
|
|
17
17
|
<title>Codeman</title>
|
|
18
18
|
<link rel="icon" type="image/svg+xml" href="data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 32 32'%3E%3Cdefs%3E%3ClinearGradient id='g' x1='0%25' y1='0%25' x2='100%25' y2='100%25'%3E%3Cstop offset='0%25' stop-color='%2360a5fa'/%3E%3Cstop offset='100%25' stop-color='%233b82f6'/%3E%3C/linearGradient%3E%3C/defs%3E%3Crect width='32' height='32' rx='6' fill='%230a0a0a'/%3E%3Cpath d='M18 4L8 18h6l-2 10 10-14h-6z' fill='url(%23g)'/%3E%3C/svg%3E">
|
|
19
|
-
<link rel="stylesheet" href="styles.
|
|
19
|
+
<link rel="stylesheet" href="styles.70bf9c2a.css">
|
|
20
20
|
<link rel="stylesheet" href="mobile.1eef31fe.css" media="(max-width: 1023px)">
|
|
21
21
|
<!-- xterm.css loaded async — terminal won't display until xterm.js runs anyway -->
|
|
22
22
|
<link rel="preload" href="vendor/xterm.css" as="style" onload="this.onload=null;this.rel='stylesheet'">
|
|
@@ -24,8 +24,8 @@
|
|
|
24
24
|
<!-- Preload critical resources — lets browser discover these during HTML parse
|
|
25
25
|
instead of waiting until <script> tags at bottom-of-body are reached. -->
|
|
26
26
|
<link rel="preload" href="vendor/xterm.min.js" as="script">
|
|
27
|
-
<link rel="preload" href="constants.
|
|
28
|
-
<link rel="preload" href="app.
|
|
27
|
+
<link rel="preload" href="constants.822a486d.js" as="script">
|
|
28
|
+
<link rel="preload" href="app.445f1584.js" as="script">
|
|
29
29
|
<!-- Self-hosted xterm.js — eliminates CDN DNS/TLS latency (~100ms).
|
|
30
30
|
'defer' preserves execution order (xterm loads before fit addon). -->
|
|
31
31
|
<script defer src="vendor/xterm.min.js"></script>
|
|
@@ -310,6 +310,11 @@
|
|
|
310
310
|
<!-- Main Terminal Area -->
|
|
311
311
|
<main class="main">
|
|
312
312
|
<div class="terminal-wrap">
|
|
313
|
+
<!-- Partial-history notice (#258). Lives OUTSIDE the terminal on purpose:
|
|
314
|
+
the old notice was a grey line written into the scrollback, so it
|
|
315
|
+
scrolled away with the output it described and could not be acted
|
|
316
|
+
on. Populated by app.js _renderHistoryTruncationBanner(). -->
|
|
317
|
+
<div class="history-trunc-bar" id="historyTruncationBar" role="status" aria-live="polite" hidden></div>
|
|
313
318
|
<div class="terminal-container" id="terminalContainer"></div>
|
|
314
319
|
<textarea id="cjkInput" rows="1" placeholder="CJK input (Enter = send, Esc = clear)"
|
|
315
320
|
maxlength="65536" aria-label="CJK IME input field"
|
|
@@ -3187,23 +3192,23 @@
|
|
|
3187
3192
|
<div class="approvals-list"></div>
|
|
3188
3193
|
</div>
|
|
3189
3194
|
|
|
3190
|
-
<script defer src="constants.
|
|
3195
|
+
<script defer src="constants.822a486d.js"></script>
|
|
3191
3196
|
<script defer src="i18n.86757605.js"></script>
|
|
3192
|
-
<script defer src="mobile-handlers.
|
|
3197
|
+
<script defer src="mobile-handlers.bc45f976.js"></script>
|
|
3193
3198
|
<script defer src="voice-input.4ec670b0.js"></script>
|
|
3194
3199
|
<script defer src="notification-manager.5d229063.js"></script>
|
|
3195
3200
|
<script defer src="keyboard-accessory.0454a3e9.js"></script>
|
|
3196
3201
|
<script defer src="input-cjk.63794d0b.js"></script>
|
|
3197
3202
|
<!-- Hardened markdown HTML sanitizer (wires DOMPurify). Must precede app.js. -->
|
|
3198
3203
|
<script defer src="sanitize-html.bc7078d6.js"></script>
|
|
3199
|
-
<script defer src="app.
|
|
3200
|
-
<script defer src="terminal-ui.
|
|
3204
|
+
<script defer src="app.445f1584.js"></script>
|
|
3205
|
+
<script defer src="terminal-ui.c182a68d.js"></script>
|
|
3201
3206
|
<script defer src="respawn-ui.ff0dae4c.js"></script>
|
|
3202
3207
|
<script defer src="ralph-panel.6de2d0f8.js"></script>
|
|
3203
3208
|
<script defer src="orchestrator-panel.js"></script>
|
|
3204
3209
|
<script defer src="cron-ui.js"></script>
|
|
3205
3210
|
<script defer src="settings-ui.eacb4f92.js"></script>
|
|
3206
|
-
<script defer src="panels-ui.
|
|
3211
|
+
<script defer src="panels-ui.1e4cb4b2.js"></script>
|
|
3207
3212
|
<script defer src="readmymind-ui.js"></script>
|
|
3208
3213
|
<script defer src="ultracode-panel.js"></script>
|
|
3209
3214
|
<script defer src="approvals-ui.js"></script>
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
@@ -214,8 +214,13 @@ const KeyboardHandler = {
|
|
|
214
214
|
keyboardVisible: false,
|
|
215
215
|
initialViewportHeight: 0,
|
|
216
216
|
_viewportSettleTimer: null,
|
|
217
|
-
|
|
217
|
+
_settleRestoreScroll: false,
|
|
218
218
|
_settlePending: false,
|
|
219
|
+
// Scroll intent captured at the start of a settle cycle (#259). `true` =
|
|
220
|
+
// following live output, `false` = reading history and _settleAnchorY holds
|
|
221
|
+
// the top visible line to return to.
|
|
222
|
+
_settleFollowing: true,
|
|
223
|
+
_settleAnchorY: null,
|
|
219
224
|
|
|
220
225
|
/** Initialize keyboard handling */
|
|
221
226
|
init() {
|
|
@@ -284,8 +289,10 @@ const KeyboardHandler = {
|
|
|
284
289
|
clearTimeout(this._viewportSettleTimer);
|
|
285
290
|
this._viewportSettleTimer = null;
|
|
286
291
|
}
|
|
287
|
-
this.
|
|
292
|
+
this._settleRestoreScroll = false;
|
|
288
293
|
this._settlePending = false;
|
|
294
|
+
this._settleFollowing = true;
|
|
295
|
+
this._settleAnchorY = null;
|
|
289
296
|
},
|
|
290
297
|
|
|
291
298
|
/** Handle viewport resize (keyboard show/hide) */
|
|
@@ -427,7 +434,7 @@ const KeyboardHandler = {
|
|
|
427
434
|
|
|
428
435
|
// visualViewport emits multiple heights throughout the OS animation.
|
|
429
436
|
// Re-schedule on every event and fit only after the final height settles.
|
|
430
|
-
this._scheduleViewportSettle({
|
|
437
|
+
this._scheduleViewportSettle({ restoreScroll: true });
|
|
431
438
|
|
|
432
439
|
// Reposition subagent windows to stack from bottom (above keyboard)
|
|
433
440
|
if (typeof app !== 'undefined') app.relayoutMobileSubagentWindows();
|
|
@@ -442,7 +449,7 @@ const KeyboardHandler = {
|
|
|
442
449
|
|
|
443
450
|
this.resetLayout();
|
|
444
451
|
|
|
445
|
-
this._scheduleViewportSettle({
|
|
452
|
+
this._scheduleViewportSettle({ restoreScroll: true });
|
|
446
453
|
|
|
447
454
|
// Reposition subagent windows to stack from top (below header)
|
|
448
455
|
if (typeof app !== 'undefined') app.relayoutMobileSubagentWindows();
|
|
@@ -459,12 +466,46 @@ const KeyboardHandler = {
|
|
|
459
466
|
* fit against it resizes the PTY to transient dims and the SIGWINCH thrash
|
|
460
467
|
* garbles the transcript.
|
|
461
468
|
*/
|
|
462
|
-
_scheduleViewportSettle({
|
|
463
|
-
|
|
469
|
+
_scheduleViewportSettle({ restoreScroll = false } = {}) {
|
|
470
|
+
// Capture scroll intent on the FIRST event of a settle cycle, BEFORE any
|
|
471
|
+
// fit() has reflowed the buffer — a later capture reads an already-moved
|
|
472
|
+
// viewportY. Issue #259: this path used to force scrollToBottom
|
|
473
|
+
// unconditionally, so opening the keyboard yanked a user who was reading
|
|
474
|
+
// history down to the live output.
|
|
475
|
+
if (!this._settlePending) this._captureTerminalScrollIntent();
|
|
476
|
+
this._settleRestoreScroll = this._settleRestoreScroll || restoreScroll;
|
|
464
477
|
this._settlePending = true;
|
|
465
478
|
this._armViewportSettleTimer();
|
|
466
479
|
},
|
|
467
480
|
|
|
481
|
+
/**
|
|
482
|
+
* Record whether the terminal is following live output, and if not, the top
|
|
483
|
+
* visible line to return to. `_settleFollowing` defaults to true so a
|
|
484
|
+
* terminal we cannot read keeps the historical scroll-to-bottom behavior.
|
|
485
|
+
*/
|
|
486
|
+
_captureTerminalScrollIntent() {
|
|
487
|
+
this._settleFollowing = true;
|
|
488
|
+
this._settleAnchorY = null;
|
|
489
|
+
if (typeof app === 'undefined' || !app.terminal?.buffer?.active) return;
|
|
490
|
+
this._settleFollowing = app.isTerminalAtBottom();
|
|
491
|
+
if (!this._settleFollowing) this._settleAnchorY = app.terminal.buffer.active.viewportY;
|
|
492
|
+
},
|
|
493
|
+
|
|
494
|
+
/**
|
|
495
|
+
* Return to the captured anchor after the keyboard reflow. Reflow can rewrap
|
|
496
|
+
* lines, so the anchor is approximate by construction; it is clamped to the
|
|
497
|
+
* post-reflow buffer rather than trusted blindly.
|
|
498
|
+
*/
|
|
499
|
+
_restoreTerminalScrollIntent() {
|
|
500
|
+
const term = typeof app !== 'undefined' ? app.terminal : null;
|
|
501
|
+
const anchor = this._settleAnchorY;
|
|
502
|
+
if (typeof anchor !== 'number' || typeof term?.scrollToLine !== 'function' || !term.buffer?.active) {
|
|
503
|
+
term?.scrollToBottom?.();
|
|
504
|
+
return;
|
|
505
|
+
}
|
|
506
|
+
term.scrollToLine(Math.max(0, Math.min(anchor, term.buffer.active.baseY)));
|
|
507
|
+
},
|
|
508
|
+
|
|
468
509
|
/** Push a pending settle back while the viewport is still animating; no-op otherwise. */
|
|
469
510
|
_deferViewportSettle() {
|
|
470
511
|
if (!this._settlePending) return;
|
|
@@ -476,8 +517,8 @@ const KeyboardHandler = {
|
|
|
476
517
|
this._viewportSettleTimer = setTimeout(() => {
|
|
477
518
|
this._viewportSettleTimer = null;
|
|
478
519
|
this._settlePending = false;
|
|
479
|
-
const
|
|
480
|
-
this.
|
|
520
|
+
const shouldRestoreScroll = this._settleRestoreScroll;
|
|
521
|
+
this._settleRestoreScroll = false;
|
|
481
522
|
|
|
482
523
|
if (typeof app !== 'undefined' && app.terminal) {
|
|
483
524
|
if (app.fitAddon) {
|
|
@@ -486,7 +527,12 @@ const KeyboardHandler = {
|
|
|
486
527
|
} catch {}
|
|
487
528
|
}
|
|
488
529
|
if (this.keyboardVisible) this._shrinkPaddingToFit();
|
|
489
|
-
|
|
530
|
+
// Following live output → bottom, as before. Reading history → back to
|
|
531
|
+
// the pre-reflow anchor instead of being yanked down (#259).
|
|
532
|
+
if (shouldRestoreScroll) {
|
|
533
|
+
if (this._settleFollowing === false) this._restoreTerminalScrollIntent();
|
|
534
|
+
else app.terminal.scrollToBottom();
|
|
535
|
+
}
|
|
490
536
|
app._syncMobileHelperTextareaToCursor?.();
|
|
491
537
|
app._localEchoOverlay?.rerender?.();
|
|
492
538
|
this._sendTerminalResize();
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
@@ -228,7 +228,7 @@
|
|
|
228
228
|
${w}
|
|
229
229
|
${f}
|
|
230
230
|
</div>
|
|
231
|
-
`),o&&l&&a.children)for(const v of a.children)i(v,r+1)};for(const a of t)i(a,0);e.innerHTML=s.join(""),e.querySelectorAll(".file-tree-item").forEach(a=>{a.addEventListener("click",()=>{const r=a.dataset.path;a.dataset.type==="directory"?this.toggleFileBrowserFolder(r):this.openFilePreview(r)})})},hasMatchingChild(e,t){if(!e.children)return!1;for(const s of e.children)if(s.name.toLowerCase().includes(t)||s.type==="directory"&&this.hasMatchingChild(s,t))return!0;return!1},toggleFileBrowserFolder(e){this.fileBrowserExpandedDirs.has(e)?this.fileBrowserExpandedDirs.delete(e):this.fileBrowserExpandedDirs.add(e),this.renderFileBrowserTree()},filterFileBrowser(e){this.fileBrowserFilter=e,e&&this.expandAllDirectories(this.fileBrowserData?.tree||[]),this.renderFileBrowserTree()},expandAllDirectories(e){for(const t of e)t.type==="directory"&&(this.fileBrowserExpandedDirs.add(t.path),t.children&&this.expandAllDirectories(t.children))},collapseAllDirectories(){this.fileBrowserExpandedDirs.clear()},toggleFileBrowserExpand(){this.fileBrowserAllExpanded=!this.fileBrowserAllExpanded;const e=this.$("fileBrowserExpandBtn");this.fileBrowserAllExpanded?(this.expandAllDirectories(this.fileBrowserData?.tree||[]),e&&(e.innerHTML="\u229F")):(this.collapseAllDirectories(),e&&(e.innerHTML="\u229E")),this.renderFileBrowserTree()},refreshFileBrowser(){if(this.activeSessionId){this.fileBrowserExpandedDirs.clear(),this.fileBrowserFilter="",this.fileBrowserAllExpanded=!1;const e=this.$("fileBrowserSearch");e&&(e.value=""),this.loadFileBrowser(this.activeSessionId)}},toggleFileBrowserButton(){const t=this.$("fileBrowserPanel")?.classList.contains("visible"),s=document.querySelector(".btn-file-viewer");if(t){this.closeFileBrowserPanel(),s&&s.setAttribute("aria-expanded","false");return}if(!this.activeSessionId){this.showToast("Open a session to browse its files","info");return}const n=this.loadAppSettingsFromStorage();n.showFileBrowser=!0,this.saveAppSettingsToStorage(n);const i=document.getElementById("appSettingsShowFileBrowser");i&&(i.checked=!0),this.applyMonitorVisibility(),s&&s.setAttribute("aria-expanded","true")},closeFileBrowserPanel(){const e=this.$("fileBrowserPanel");if(e&&(e.classList.remove("visible"),e.style.left="",e.style.top="",e.style.bottom="",e.style.right=""),this.fileBrowserDragListeners){const i=this.fileBrowserDragListeners;document.removeEventListener("mousemove",i.move),document.removeEventListener("mouseup",i.up),document.removeEventListener("touchmove",i.touchMove),document.removeEventListener("touchend",i.up),document.removeEventListener("touchcancel",i.up),i.handle&&(i.handle.removeEventListener("mousedown",i.handleMouseDown),i.handle.removeEventListener("touchstart",i.handleTouchStart),i._onFirstDrag&&(i.handle.removeEventListener("mousedown",i._onFirstDrag),i.handle.removeEventListener("touchstart",i._onFirstDrag))),this.fileBrowserDragListeners=null}const t=this.loadAppSettingsFromStorage();t.showFileBrowser=!1,this.saveAppSettingsToStorage(t);const s=document.getElementById("appSettingsShowFileBrowser");s&&(s.checked=!1);const n=document.querySelector(".btn-file-viewer");n&&n.setAttribute("aria-expanded","false")},async openFilePreview(e,t=this.activeSessionId,s=null){if(!t||!e)return;const n=this.$("filePreviewOverlay"),i=this.$("filePreviewTitle"),a=this.$("filePreviewBody"),r=this.$("filePreviewFooter");if(!n||!a)return;this._resetFilePreviewEdit(),n.classList.add("visible"),i.textContent=e,a.innerHTML='<div class="binary-message">Loading...</div>',r.textContent="";const o=(e.split(".").pop()||"").toLowerCase();if(s){const l=`/api/sessions/${t}/attachments/${encodeURIComponent(s)}`,d=new Set(["png","jpg","jpeg","gif","webp","bmp","svg"]);if(r.textContent=o.toUpperCase(),d.has(o))a.innerHTML=`<img src="${escapeHtml(`${l}/raw`)}" alt="${escapeHtml(e)}">`;else if(o==="pdf")a.innerHTML=`<iframe src="${escapeHtml(`${l}/raw`)}" title="${escapeHtml(e)}"></iframe>`;else if(o==="docx"||o==="pptx")a.innerHTML=`<iframe src="${escapeHtml(`${l}/preview`)}" title="${escapeHtml(e)}"></iframe>`;else try{const c=await fetch(`${l}/raw`);if(!c.ok)throw new Error("Failed to load attachment");const m=await c.text();a.innerHTML=`<pre><code>${escapeHtml(m)}</code></pre>`}catch(c){a.innerHTML=`<div class="binary-message">Error: ${escapeHtml(c.message)}</div>`}return}if(o==="docx"||o==="pptx"){r.textContent=o.toUpperCase();const l=`/api/sessions/${t}/file-preview?path=${encodeURIComponent(e)}`;a.innerHTML=`<iframe src="${escapeHtml(l)}" title="${escapeHtml(e)}"></iframe>`;return}if(o==="pdf"){r.textContent="PDF";const l=`/api/sessions/${t}/file-raw?path=${encodeURIComponent(e)}`;a.innerHTML=`<iframe src="${escapeHtml(l)}" title="${escapeHtml(e)}"></iframe>`;return}if(o==="svg"){r.textContent="SVG";try{const l=await fetch(`/api/sessions/${t}/file-raw?path=${encodeURIComponent(e)}`);if(!l.ok)throw new Error("Failed to load image");const d=URL.createObjectURL(new Blob([await l.text()],{type:"image/svg+xml"}));a.innerHTML=`<img src="${d}" alt="${escapeHtml(e)}">`;const c=a.querySelector("img");c&&(c.onload=()=>URL.revokeObjectURL(d))}catch(l){a.innerHTML=`<div class="binary-message">Error: ${escapeHtml(l.message)}</div>`}return}try{const l=await fetch(`/api/sessions/${t}/file-content?path=${encodeURIComponent(e)}&lines=500`);if(!l.ok)throw new Error("Failed to load file");const d=await l.json();if(!d.success)throw new Error(d.error||"Failed to load file");const c=d.data;if(c.type==="image")a.innerHTML=`<img src="${c.url}" alt="${escapeHtml(e)}">`,r.textContent=`${this.formatFileSize(c.size)} \u2022 ${c.extension}`;else if(c.type==="video")a.innerHTML=`<video src="${c.url}" controls autoplay></video>`,r.textContent=`${this.formatFileSize(c.size)} \u2022 ${c.extension}`;else if(c.type==="audio")a.innerHTML=`<audio src="${c.url}" controls autoplay></audio>`,r.textContent=`${this.formatFileSize(c.size)} \u2022 ${c.extension}`;else if(c.type==="binary"){const m=`/api/sessions/${t}/file-raw?path=${encodeURIComponent(e)}&download=true`;a.innerHTML=`<div class="binary-message">Binary file (${this.formatFileSize(c.size)})<br>Cannot preview<br><a href="${escapeHtml(m)}" download>Download</a></div>`,r.textContent=c.extension||"binary"}else{this.filePreviewContent=c.content,a.innerHTML=`<pre><code>${escapeHtml(c.content)}</code></pre>`;const m=c.truncated?` (showing 500/${c.totalLines} lines)`:"";if(r.textContent=`${c.totalLines} lines \u2022 ${this.formatFileSize(c.size)}${m}`,c.editable){this.filePreviewEditTarget={sessionId:t,filePath:e};const u=this.$("filePreviewEditBtn");u&&(u.hidden=!1)}}}catch(l){console.error("Failed to preview file:",l),a.innerHTML=`<div class="binary-message">Error: ${escapeHtml(l.message)}</div>`}},closeFilePreview(){if(this.filePreviewEdit?.dirty&&!confirm("Discard unsaved changes?"))return;this._resetFilePreviewEdit();const e=this.$("filePreviewOverlay");e&&e.classList.remove("visible"),this.filePreviewContent=""},_resetFilePreviewEdit(){this.filePreviewEdit=null,this.filePreviewEditTarget=null;const e=this.$("filePreviewEditBtn");e&&(e.hidden=!0);const t=this.$("filePreviewEditBar");t&&(t.hidden=!0);const s=this.$("filePreviewDirty");s&&(s.hidden=!0);const n=this.$("filePreviewSaveBtn");n&&(n.disabled=!0,n.textContent="Save")},async enterFilePreviewEdit(){const e=this.filePreviewEditTarget;if(!e||this.filePreviewEdit)return;const t=this.$("filePreviewBody"),s=this.$("filePreviewFooter");if(!t)return;let n;try{const o=await fetch(`/api/sessions/${e.sessionId}/file-content?path=${encodeURIComponent(e.filePath)}&edit=1`),l=await o.json().catch(()=>null);if(!l||l.success!==!0)throw new Error(l?.error||`Failed to load file for editing (HTTP ${o.status})`);n=l.data}catch(o){this.showToast(o.message,"error");return}this.filePreviewEdit={sessionId:e.sessionId,filePath:e.filePath,baseHash:n.hash,eol:n.eol,original:n.content,dirty:!1,saving:!1};const i=document.createElement("textarea");i.className="file-preview-editor",i.spellcheck=!1,i.setAttribute("autocapitalize","off"),i.setAttribute("autocorrect","off"),i.setAttribute("autocomplete","off"),i.wrap="off",i.value=n.content,i.addEventListener("input",()=>this._onFilePreviewEditInput()),t.innerHTML="",t.appendChild(i);const a=this.$("filePreviewEditBtn");a&&(a.hidden=!0);const r=this.$("filePreviewEditBar");if(r&&(r.hidden=!1),s){const o=n.eol==="crlf"?" \u2022 CRLF":"";s.textContent=`Editing \u2022 ${n.totalLines} lines \u2022 ${this.formatFileSize(n.size)}${o}`}},_onFilePreviewEditInput(){const e=this.filePreviewEdit;if(!e)return;const t=this.$("filePreviewBody")?.querySelector("textarea.file-preview-editor");if(!t)return;e.dirty=t.value!==e.original;const s=this.$("filePreviewDirty");s&&(s.hidden=!e.dirty);const n=this.$("filePreviewSaveBtn");n&&(n.disabled=!e.dirty||e.saving)},cancelFilePreviewEdit(){const e=this.filePreviewEdit;if(!e||e.dirty&&!confirm("Discard unsaved changes?"))return;const{sessionId:t,filePath:s}=e;this._resetFilePreviewEdit(),this.openFilePreview(s,t)},async saveFilePreviewEdit(e=!1){const t=this.filePreviewEdit;if(!t||t.saving)return;const s=this.$("filePreviewBody")?.querySelector("textarea.file-preview-editor");if(!s)return;t.saving=!0;const n=this.$("filePreviewSaveBtn");n&&(n.disabled=!0,n.textContent="Saving\u2026");const i=()=>{t.saving=!1,n&&(n.textContent="Save"),this._onFilePreviewEditInput()};let a=null,r=0;try{const d=await fetch(`/api/sessions/${t.sessionId}/file-content`,{method:"PUT",headers:{"Content-Type":"application/json"},body:JSON.stringify({path:t.filePath,content:s.value,baseHash:t.baseHash,eol:t.eol??void 0,force:e||void 0})});r=d.status,a=await d.json().catch(()=>null)}catch(d){i(),this.showToast(`Save failed: ${d.message}`,"error");return}if(r===409||a?.errorCode==="CONFLICT"){i(),confirm(`File changed on disk since you loaded it.
|
|
231
|
+
`),o&&l&&a.children)for(const v of a.children)i(v,r+1)};for(const a of t)i(a,0);e.innerHTML=s.join(""),e.querySelectorAll(".file-tree-item").forEach(a=>{a.addEventListener("click",()=>{const r=a.dataset.path;a.dataset.type==="directory"?this.toggleFileBrowserFolder(r):this.openFilePreview(r)})})},hasMatchingChild(e,t){if(!e.children)return!1;for(const s of e.children)if(s.name.toLowerCase().includes(t)||s.type==="directory"&&this.hasMatchingChild(s,t))return!0;return!1},toggleFileBrowserFolder(e){this.fileBrowserExpandedDirs.has(e)?this.fileBrowserExpandedDirs.delete(e):this.fileBrowserExpandedDirs.add(e),this.renderFileBrowserTree()},filterFileBrowser(e){this.fileBrowserFilter=e,e&&this.expandAllDirectories(this.fileBrowserData?.tree||[]),this.renderFileBrowserTree()},expandAllDirectories(e){for(const t of e)t.type==="directory"&&(this.fileBrowserExpandedDirs.add(t.path),t.children&&this.expandAllDirectories(t.children))},collapseAllDirectories(){this.fileBrowserExpandedDirs.clear()},toggleFileBrowserExpand(){this.fileBrowserAllExpanded=!this.fileBrowserAllExpanded;const e=this.$("fileBrowserExpandBtn");this.fileBrowserAllExpanded?(this.expandAllDirectories(this.fileBrowserData?.tree||[]),e&&(e.innerHTML="\u229F")):(this.collapseAllDirectories(),e&&(e.innerHTML="\u229E")),this.renderFileBrowserTree()},refreshFileBrowser(){if(this.activeSessionId){this.fileBrowserExpandedDirs.clear(),this.fileBrowserFilter="",this.fileBrowserAllExpanded=!1;const e=this.$("fileBrowserSearch");e&&(e.value=""),this.loadFileBrowser(this.activeSessionId)}},toggleFileBrowserButton(){const t=this.$("fileBrowserPanel")?.classList.contains("visible"),s=document.querySelector(".btn-file-viewer");if(t){this.closeFileBrowserPanel(),s&&s.setAttribute("aria-expanded","false");return}if(!this.activeSessionId){this.showToast("Open a session to browse its files","info");return}const n=this.loadAppSettingsFromStorage();n.showFileBrowser=!0,this.saveAppSettingsToStorage(n);const i=document.getElementById("appSettingsShowFileBrowser");i&&(i.checked=!0),this.applyMonitorVisibility(),s&&s.setAttribute("aria-expanded","true")},closeFileBrowserPanel(){const e=this.$("fileBrowserPanel");if(e&&(e.classList.remove("visible"),e.style.left="",e.style.top="",e.style.bottom="",e.style.right=""),this.fileBrowserDragListeners){const i=this.fileBrowserDragListeners;document.removeEventListener("mousemove",i.move),document.removeEventListener("mouseup",i.up),document.removeEventListener("touchmove",i.touchMove),document.removeEventListener("touchend",i.up),document.removeEventListener("touchcancel",i.up),i.handle&&(i.handle.removeEventListener("mousedown",i.handleMouseDown),i.handle.removeEventListener("touchstart",i.handleTouchStart),i._onFirstDrag&&(i.handle.removeEventListener("mousedown",i._onFirstDrag),i.handle.removeEventListener("touchstart",i._onFirstDrag))),this.fileBrowserDragListeners=null}const t=this.loadAppSettingsFromStorage();t.showFileBrowser=!1,this.saveAppSettingsToStorage(t);const s=document.getElementById("appSettingsShowFileBrowser");s&&(s.checked=!1);const n=document.querySelector(".btn-file-viewer");n&&n.setAttribute("aria-expanded","false")},async openFilePreview(e,t=this.activeSessionId,s=null){if(!t||!e)return;const n=this.$("filePreviewOverlay"),i=this.$("filePreviewTitle"),a=this.$("filePreviewBody"),r=this.$("filePreviewFooter");if(!n||!a)return;this._resetFilePreviewEdit(),this._stopFilePreviewMedia(),n.classList.add("visible"),i.textContent=e,a.innerHTML='<div class="binary-message">Loading...</div>',r.textContent="";const o=(e.split(".").pop()||"").toLowerCase();if(s){const l=`/api/sessions/${t}/attachments/${encodeURIComponent(s)}`,d=new Set(["png","jpg","jpeg","gif","webp","bmp","svg"]);if(r.textContent=o.toUpperCase(),d.has(o))a.innerHTML=`<img src="${escapeHtml(`${l}/raw`)}" alt="${escapeHtml(e)}">`;else if(o==="pdf")a.innerHTML=`<iframe src="${escapeHtml(`${l}/raw`)}" title="${escapeHtml(e)}"></iframe>`;else if(o==="docx"||o==="pptx")a.innerHTML=`<iframe src="${escapeHtml(`${l}/preview`)}" title="${escapeHtml(e)}"></iframe>`;else try{const c=await fetch(`${l}/raw`);if(!c.ok)throw new Error("Failed to load attachment");const m=await c.text();a.innerHTML=`<pre><code>${escapeHtml(m)}</code></pre>`}catch(c){a.innerHTML=`<div class="binary-message">Error: ${escapeHtml(c.message)}</div>`}return}if(o==="docx"||o==="pptx"){r.textContent=o.toUpperCase();const l=`/api/sessions/${t}/file-preview?path=${encodeURIComponent(e)}`;a.innerHTML=`<iframe src="${escapeHtml(l)}" title="${escapeHtml(e)}"></iframe>`;return}if(o==="pdf"){r.textContent="PDF";const l=`/api/sessions/${t}/file-raw?path=${encodeURIComponent(e)}`;a.innerHTML=`<iframe src="${escapeHtml(l)}" title="${escapeHtml(e)}"></iframe>`;return}if(o==="svg"){r.textContent="SVG";try{const l=await fetch(`/api/sessions/${t}/file-raw?path=${encodeURIComponent(e)}`);if(!l.ok)throw new Error("Failed to load image");const d=URL.createObjectURL(new Blob([await l.text()],{type:"image/svg+xml"}));a.innerHTML=`<img src="${d}" alt="${escapeHtml(e)}">`;const c=a.querySelector("img");c&&(c.onload=()=>URL.revokeObjectURL(d))}catch(l){a.innerHTML=`<div class="binary-message">Error: ${escapeHtml(l.message)}</div>`}return}try{const l=await fetch(`/api/sessions/${t}/file-content?path=${encodeURIComponent(e)}&lines=500`);if(!l.ok)throw new Error("Failed to load file");const d=await l.json();if(!d.success)throw new Error(d.error||"Failed to load file");const c=d.data;if(c.type==="image")a.innerHTML=`<img src="${c.url}" alt="${escapeHtml(e)}">`,r.textContent=`${this.formatFileSize(c.size)} \u2022 ${c.extension}`;else if(c.type==="video")a.innerHTML=`<video src="${escapeHtml(c.url)}" controls autoplay playsinline preload="metadata"></video>`,r.textContent=`${this.formatFileSize(c.size)} \u2022 ${c.extension}`;else if(c.type==="audio")a.innerHTML=`<audio src="${escapeHtml(c.url)}" controls autoplay preload="metadata"></audio>`,r.textContent=`${this.formatFileSize(c.size)} \u2022 ${c.extension}`;else if(c.type==="binary"){const m=`/api/sessions/${t}/file-raw?path=${encodeURIComponent(e)}&download=true`;a.innerHTML=`<div class="binary-message">Binary file (${this.formatFileSize(c.size)})<br>Cannot preview<br><a href="${escapeHtml(m)}" download>Download</a></div>`,r.textContent=c.extension||"binary"}else{this.filePreviewContent=c.content,a.innerHTML=`<pre><code>${escapeHtml(c.content)}</code></pre>`;const m=c.truncated?` (showing 500/${c.totalLines} lines)`:"";if(r.textContent=`${c.totalLines} lines \u2022 ${this.formatFileSize(c.size)}${m}`,c.editable){this.filePreviewEditTarget={sessionId:t,filePath:e};const u=this.$("filePreviewEditBtn");u&&(u.hidden=!1)}}}catch(l){console.error("Failed to preview file:",l),a.innerHTML=`<div class="binary-message">Error: ${escapeHtml(l.message)}</div>`}},closeFilePreview(){if(this.filePreviewEdit?.dirty&&!confirm("Discard unsaved changes?"))return;this._resetFilePreviewEdit();const e=this.$("filePreviewOverlay");e&&e.classList.remove("visible"),this._stopFilePreviewMedia(),this.filePreviewContent=""},_stopFilePreviewMedia(){const e=this.$("filePreviewBody");if(e){for(const t of e.querySelectorAll("video, audio"))try{t.pause(),t.removeAttribute("src"),t.load()}catch(s){console.warn("Failed to stop preview media:",s)}e.innerHTML=""}},_resetFilePreviewEdit(){this.filePreviewEdit=null,this.filePreviewEditTarget=null;const e=this.$("filePreviewEditBtn");e&&(e.hidden=!0);const t=this.$("filePreviewEditBar");t&&(t.hidden=!0);const s=this.$("filePreviewDirty");s&&(s.hidden=!0);const n=this.$("filePreviewSaveBtn");n&&(n.disabled=!0,n.textContent="Save")},async enterFilePreviewEdit(){const e=this.filePreviewEditTarget;if(!e||this.filePreviewEdit)return;const t=this.$("filePreviewBody"),s=this.$("filePreviewFooter");if(!t)return;let n;try{const o=await fetch(`/api/sessions/${e.sessionId}/file-content?path=${encodeURIComponent(e.filePath)}&edit=1`),l=await o.json().catch(()=>null);if(!l||l.success!==!0)throw new Error(l?.error||`Failed to load file for editing (HTTP ${o.status})`);n=l.data}catch(o){this.showToast(o.message,"error");return}this.filePreviewEdit={sessionId:e.sessionId,filePath:e.filePath,baseHash:n.hash,eol:n.eol,original:n.content,dirty:!1,saving:!1};const i=document.createElement("textarea");i.className="file-preview-editor",i.spellcheck=!1,i.setAttribute("autocapitalize","off"),i.setAttribute("autocorrect","off"),i.setAttribute("autocomplete","off"),i.wrap="off",i.value=n.content,i.addEventListener("input",()=>this._onFilePreviewEditInput()),t.innerHTML="",t.appendChild(i);const a=this.$("filePreviewEditBtn");a&&(a.hidden=!0);const r=this.$("filePreviewEditBar");if(r&&(r.hidden=!1),s){const o=n.eol==="crlf"?" \u2022 CRLF":"";s.textContent=`Editing \u2022 ${n.totalLines} lines \u2022 ${this.formatFileSize(n.size)}${o}`}},_onFilePreviewEditInput(){const e=this.filePreviewEdit;if(!e)return;const t=this.$("filePreviewBody")?.querySelector("textarea.file-preview-editor");if(!t)return;e.dirty=t.value!==e.original;const s=this.$("filePreviewDirty");s&&(s.hidden=!e.dirty);const n=this.$("filePreviewSaveBtn");n&&(n.disabled=!e.dirty||e.saving)},cancelFilePreviewEdit(){const e=this.filePreviewEdit;if(!e||e.dirty&&!confirm("Discard unsaved changes?"))return;const{sessionId:t,filePath:s}=e;this._resetFilePreviewEdit(),this.openFilePreview(s,t)},async saveFilePreviewEdit(e=!1){const t=this.filePreviewEdit;if(!t||t.saving)return;const s=this.$("filePreviewBody")?.querySelector("textarea.file-preview-editor");if(!s)return;t.saving=!0;const n=this.$("filePreviewSaveBtn");n&&(n.disabled=!0,n.textContent="Saving\u2026");const i=()=>{t.saving=!1,n&&(n.textContent="Save"),this._onFilePreviewEditInput()};let a=null,r=0;try{const d=await fetch(`/api/sessions/${t.sessionId}/file-content`,{method:"PUT",headers:{"Content-Type":"application/json"},body:JSON.stringify({path:t.filePath,content:s.value,baseHash:t.baseHash,eol:t.eol??void 0,force:e||void 0})});r=d.status,a=await d.json().catch(()=>null)}catch(d){i(),this.showToast(`Save failed: ${d.message}`,"error");return}if(r===409||a?.errorCode==="CONFLICT"){i(),confirm(`File changed on disk since you loaded it.
|
|
232
232
|
OK overwrites it with your version; Cancel keeps your draft open.`)&&this.saveFilePreviewEdit(!0);return}if(!a||a.success!==!0){i(),this.showToast(`Save failed: ${a?.error||`HTTP ${r}`}`,"error");return}const{sessionId:o,filePath:l}=t;this._resetFilePreviewEdit(),this.showToast("Saved","success"),this.openFilePreview(l,o)},_onAttachmentDetected(e){if(console.log("[Attachment Detected]",e),this.addAttachmentCard(e),e.sessionId){const t=this.attachmentHistoryCounts.get(e.sessionId)??this.sessions.get(e.sessionId)?.attachmentHistory?.length??0;this.attachmentHistoryCounts.set(e.sessionId,Math.min(t+1,100)),e.sessionId===this.activeSessionId&&(this.updateAttachmentHistoryBadge(),this.attachmentHistoryDrawerOpen&&this._debouncedCall("attachmentHistoryRefresh",()=>{this.attachmentHistoryDrawerOpen&&this.activeSessionId===e.sessionId&&this.loadAttachmentHistory(e.sessionId)},250))}},ensureAttachmentCardStack(){let e=this.attachmentCardStack||document.getElementById("attachmentCardStack");return e||(e=document.createElement("div"),e.id="attachmentCardStack",e.className="attachment-card-stack",document.body.appendChild(e)),this.attachmentCardStack=e,e},openAttachmentInNewTab(e,t,s=null){const n=s?`/api/sessions/${e}/attachments/${encodeURIComponent(s)}/raw`:`/api/sessions/${e}/file-raw?path=${encodeURIComponent(t)}`;window.open(n,"_blank")},addAttachmentCard(e){const{sessionId:t,relativePath:s,fileName:n,timestamp:i,size:a,attachmentType:r,extension:o,attachmentId:l,rawUrl:d,previewUrl:c,thumbnailUrl:m}=e,u=s||n,h=l||`${t}-${i}-${n}`;if(this.attachmentCards.has(h)){this.attachmentCards.get(h).element.focus?.();return}if(this.attachmentCards.size>=10){const C=this.attachmentCards.keys().next().value;C&&this.closeAttachmentCard(C)}const w=this.ensureAttachmentCardStack(),f=this.sessions.get(t)?.name||t.substring(0,8),v=d||(l?`/api/sessions/${t}/attachments/${encodeURIComponent(l)}/raw`:`/api/sessions/${t}/file-raw?path=${encodeURIComponent(u)}`),$=c||(l?`/api/sessions/${t}/attachments/${encodeURIComponent(l)}/preview`:null),g=m||(l?`/api/sessions/${t}/attachments/${encodeURIComponent(l)}/thumbnail`:`/api/sessions/${t}/file-thumbnail?path=${encodeURIComponent(u)}`),S=l?`${v}?download=true`:`${v}&download=true`,T=(o||r||"file").toUpperCase(),y=document.createElement("article");y.className=`attachment-card attachment-${escapeHtml(r||"file")}`,y.tabIndex=0,y.dataset.attachmentId=h,y.dataset.previewUrl=$||"",y.innerHTML=`
|
|
233
233
|
<div class="attachment-thumbnail">
|
|
234
234
|
${g?`<img class="attachment-thumbnail-img" src="${escapeHtml(g)}" alt="">`:""}
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
@@ -148,7 +148,9 @@ Object.assign(CodemanApp.prototype, {
|
|
|
148
148
|
const dot = document.createElementNS('http://www.w3.org/2000/svg', 'circle');
|
|
149
149
|
dot.setAttribute('cx', String(geom.endX));
|
|
150
150
|
dot.setAttribute('cy', String(geom.endY));
|
|
151
|
-
dot.
|
|
151
|
+
// Resting radius; `lineage-dot-pulse` breathes it 3.5 → 4.5 while the child
|
|
152
|
+
// works, so the two have to be changed together.
|
|
153
|
+
dot.setAttribute('r', '3.5');
|
|
152
154
|
dot.setAttribute('class', 'lineage-line-dot' + working);
|
|
153
155
|
dot.setAttribute('data-child-tab', edge.childId);
|
|
154
156
|
svg.appendChild(dot);
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|