@integrity-labs/agt-cli 0.28.521 → 0.28.522

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.
@@ -42,7 +42,7 @@ import {
42
42
  resolveConnectivityProbe,
43
43
  worseConnectivityOutcome,
44
44
  wrapScheduledTaskPrompt
45
- } from "./chunk-STNEJC5S.js";
45
+ } from "./chunk-4GTDD2I5.js";
46
46
  import {
47
47
  parsePsRows
48
48
  } from "./chunk-XWVM4KPK.js";
@@ -5317,7 +5317,7 @@ function exchangeFailureKind(err) {
5317
5317
  }
5318
5318
 
5319
5319
  // src/lib/api-client.ts
5320
- var agtCliVersion = true ? "0.28.521" : "dev";
5320
+ var agtCliVersion = true ? "0.28.522" : "dev";
5321
5321
  var lastConfigHash = null;
5322
5322
  function setConfigHash(hash) {
5323
5323
  lastConfigHash = hash && hash.length > 0 ? hash : null;
@@ -8629,4 +8629,4 @@ export {
8629
8629
  managerInstallSystemUnitCommand,
8630
8630
  managerUninstallSystemUnitCommand
8631
8631
  };
8632
- //# sourceMappingURL=chunk-UAHIRLPO.js.map
8632
+ //# sourceMappingURL=chunk-WIQCLSZY.js.map
@@ -100,7 +100,7 @@ async function spawnPairSession(session) {
100
100
  return { ok: true };
101
101
  } catch {
102
102
  }
103
- const { resolveClaudeBinary } = await import("./persistent-session-DNENZXT4.js");
103
+ const { resolveClaudeBinary } = await import("./persistent-session-5GMZKV37.js");
104
104
  const claudeBin = resolveClaudeBinary();
105
105
  const pairEnv = {
106
106
  ...process.env,
@@ -373,4 +373,4 @@ export {
373
373
  startClaudePair,
374
374
  submitClaudePairCode
375
375
  };
376
- //# sourceMappingURL=claude-pair-runtime-PFMWZ66R.js.map
376
+ //# sourceMappingURL=claude-pair-runtime-7W7LSW73.js.map
@@ -53,7 +53,7 @@ import {
53
53
  safeWriteJsonAtomic,
54
54
  setConfigHash,
55
55
  tripClass
56
- } from "../chunk-UAHIRLPO.js";
56
+ } from "../chunk-WIQCLSZY.js";
57
57
  import {
58
58
  getProjectDir as getProjectDir2,
59
59
  getReadyTasks,
@@ -177,7 +177,7 @@ import {
177
177
  toOpencodeModel,
178
178
  transcriptActivityAgeSeconds,
179
179
  writeEgressAllowlist
180
- } from "../chunk-STNEJC5S.js";
180
+ } from "../chunk-4GTDD2I5.js";
181
181
  import {
182
182
  reapOrphanChannelMcps
183
183
  } from "../chunk-XWVM4KPK.js";
@@ -10106,7 +10106,7 @@ var agentRestartTimezoneInputs = /* @__PURE__ */ new Map();
10106
10106
  var lastVersionCheckAt = 0;
10107
10107
  var VERSION_CHECK_INTERVAL_MS = 5 * 60 * 1e3;
10108
10108
  var lastResponsivenessProbeAt = 0;
10109
- var agtCliVersion = true ? "0.28.521" : "dev";
10109
+ var agtCliVersion = true ? "0.28.522" : "dev";
10110
10110
  function resolveBrewPath(execFileSync2) {
10111
10111
  try {
10112
10112
  const out = execFileSync2("which", ["brew"], { timeout: 5e3 }).toString().trim();
@@ -11314,7 +11314,7 @@ function flushRestartedAgentDiagnostics(hostId, codeNames) {
11314
11314
  if (codeNames.length === 0) return;
11315
11315
  void (async () => {
11316
11316
  try {
11317
- const { collectDiagnostics } = await import("../persistent-session-DNENZXT4.js");
11317
+ const { collectDiagnostics } = await import("../persistent-session-5GMZKV37.js");
11318
11318
  await api.post("/host/heartbeat", {
11319
11319
  host_id: hostId,
11320
11320
  agent_diagnostics: collectDiagnostics(codeNames, quarantineEntriesFor, claudeMdSizeFor)
@@ -11421,7 +11421,7 @@ async function pollCycle() {
11421
11421
  }
11422
11422
  try {
11423
11423
  const { detectHostSecurity } = await import("../host-security-6PDFG7F5.js");
11424
- const { collectDiagnostics } = await import("../persistent-session-DNENZXT4.js");
11424
+ const { collectDiagnostics } = await import("../persistent-session-5GMZKV37.js");
11425
11425
  const diagCodeNames = [...agentState.persistentSessionAgents];
11426
11426
  const agentDiagnostics = diagCodeNames.length > 0 ? collectDiagnostics(diagCodeNames, quarantineEntriesFor, claudeMdSizeFor) : void 0;
11427
11427
  let tailscaleHostname;
@@ -11536,7 +11536,7 @@ async function pollCycle() {
11536
11536
  collectResponsivenessProbes,
11537
11537
  collectPanelessActivityProbes,
11538
11538
  getResponsivenessIntervalMs
11539
- } = await import("../responsiveness-probe-V7CP5ML3.js");
11539
+ } = await import("../responsiveness-probe-52IZOP2M.js");
11540
11540
  const probeIntervalMs = getResponsivenessIntervalMs();
11541
11541
  if (now - lastResponsivenessProbeAt > probeIntervalMs) {
11542
11542
  const probeCodeNames = [...agentState.persistentSessionAgents];
@@ -11600,7 +11600,7 @@ async function pollCycle() {
11600
11600
  collectResponsivenessProbes,
11601
11601
  livePendingInboundOldestAgeSeconds,
11602
11602
  parkPendingInbound
11603
- } = await import("../responsiveness-probe-V7CP5ML3.js");
11603
+ } = await import("../responsiveness-probe-52IZOP2M.js");
11604
11604
  const { getProjectDir: wedgeProjectDir } = await import("../scheduler-engine-NDP36U7O.js");
11605
11605
  const wedgeNow = /* @__PURE__ */ new Date();
11606
11606
  const liveAgents = agentState.persistentSessionAgents;
@@ -15108,7 +15108,7 @@ async function handleRestartDoorbell(agentId, requestedAt, restartReason) {
15108
15108
  void api.post("/host/restart-ack", { host_id: hostId, agent_id: agentId, restart_requested_at: requestedAt }).catch((err) => log(`[restart-lane] ack failed for '${codeName}': ${err.message}`));
15109
15109
  void (async () => {
15110
15110
  try {
15111
- const { collectDiagnostics } = await import("../persistent-session-DNENZXT4.js");
15111
+ const { collectDiagnostics } = await import("../persistent-session-5GMZKV37.js");
15112
15112
  await api.post("/host/heartbeat", {
15113
15113
  host_id: hostId,
15114
15114
  agent_diagnostics: collectDiagnostics([codeName], quarantineEntriesFor, claudeMdSizeFor)
@@ -15158,7 +15158,7 @@ async function respawnAgentAfterMcpStop(codeName, reason) {
15158
15158
  }
15159
15159
  try {
15160
15160
  const hostId = await getHostId();
15161
- const { collectDiagnostics } = await import("../persistent-session-DNENZXT4.js");
15161
+ const { collectDiagnostics } = await import("../persistent-session-5GMZKV37.js");
15162
15162
  await api.post("/host/heartbeat", {
15163
15163
  host_id: hostId,
15164
15164
  agent_diagnostics: collectDiagnostics([codeName], quarantineEntriesFor, claudeMdSizeFor)
@@ -15676,7 +15676,7 @@ async function processClaudePairSessions(agents) {
15676
15676
  killPairSession,
15677
15677
  pairTmuxSession,
15678
15678
  finalizeClaudePairOnboarding
15679
- } = await import("../claude-pair-runtime-PFMWZ66R.js");
15679
+ } = await import("../claude-pair-runtime-7W7LSW73.js");
15680
15680
  for (const pairId of pendingResp.cancelled_pair_ids ?? []) {
15681
15681
  log(`[claude-pair] sweeping orphan tmux session for pair ${pairId.slice(0, 8)}`);
15682
15682
  const killed = await killPairSession(pairTmuxSession(pairId));
@@ -32140,6 +32140,11 @@ var MARKUP_BRIDGE_SCRIPT = `<script>(function(){
32140
32140
  var commentEnabled = false; // armed when the viewer may comment (ENG-6847 right-click)
32141
32141
  var editing = null; // the element currently in edit mode
32142
32142
  var bar = null; // the Save/Cancel toolbar
32143
+ // ENG-8477: the viewer has explicitly turned editing ON from the shell. Only
32144
+ // then does the bridge look underneath the artefact's own overlays. See the
32145
+ // long note on the window-capture click handler for why this is a mode and
32146
+ // not just always-on.
32147
+ var editMode = false;
32143
32148
 
32144
32149
  // Element-child indices from <body> down to el. Counts ELEMENT children only
32145
32150
  // (children, not childNodes), matching how the server walks source_html; our
@@ -32194,6 +32199,55 @@ var MARKUP_BRIDGE_SCRIPT = `<script>(function(){
32194
32199
  return null;
32195
32200
  }catch(e){ return null; }
32196
32201
  }
32202
+ // ENG-8477: the topmost element under the pointer that we could actually edit,
32203
+ // looking THROUGH anything the artefact has laid over its own content.
32204
+ //
32205
+ // elementsFromPoint returns the full hit stack, front to back. We walk it from
32206
+ // the front and take the first genuinely editable element, skipping our own
32207
+ // injected UI. An artefact overlay is skipped for free: it is an empty div, so
32208
+ // editable() rejects it on the text check and we keep walking.
32209
+ //
32210
+ // Returns null when there is nothing editable under the pointer at all, which
32211
+ // is the signal to leave the click completely alone.
32212
+ function editableAtPoint(x,y){
32213
+ var stack;
32214
+ try{ stack=document.elementsFromPoint(x,y)||[]; }catch(e){ return null; }
32215
+ for(var i=0;i<stack.length;i++){
32216
+ var el=stack[i];
32217
+ if(!el || el.nodeType!==1) continue;
32218
+ if(el.closest && el.closest('[data-al-noedit]')) continue; // our toolbar, not content
32219
+ if(editable(el)||inlineOnlyEditable(el)) return el;
32220
+ }
32221
+ return null;
32222
+ }
32223
+ // The caret probe (caretRangeFromPoint) reports on the TOPMOST element at the
32224
+ // point, so an overlay swallows it exactly as it swallows the click - it hands
32225
+ // back the overlay div, not the text underneath. Neutralise everything stacked
32226
+ // above the target for the length of the probe, then put it back. The restore
32227
+ // is in a finally and runs before the browser can paint, so nothing flickers
32228
+ // and no artefact state is left modified.
32229
+ // This re-queries elementsFromPoint rather than reusing the caller's stack, so
32230
+ // target is NOT guaranteed to still be in it. Find target's index first and
32231
+ // mute only the prefix in front of it: a not-found target must fall back to the
32232
+ // plain probe, because muting the whole stack would neutralise target and its
32233
+ // ancestors too - a wider style mutation than intended, for a probe whose
32234
+ // result the caller then rejects anyway.
32235
+ function textNodeAtPointThrough(x,y,target){
32236
+ var stack;
32237
+ try{ stack=document.elementsFromPoint(x,y)||[]; }catch(e){ return textNodeAtPoint(x,y); }
32238
+ var cut=-1, i;
32239
+ for(i=0;i<stack.length;i++){ if(stack[i]===target){ cut=i; break; } }
32240
+ if(cut<0) return textNodeAtPoint(x,y);
32241
+ var muted=[];
32242
+ for(i=0;i<cut;i++){
32243
+ muted.push([stack[i], stack[i].style ? stack[i].style.pointerEvents : '']);
32244
+ try{ stack[i].style.pointerEvents='none'; }catch(e){}
32245
+ }
32246
+ try{ return textNodeAtPoint(x,y); }
32247
+ finally{
32248
+ for(i=0;i<muted.length;i++){ try{ muted[i][0].style.pointerEvents=muted[i][1]||''; }catch(e){} }
32249
+ }
32250
+ }
32197
32251
  // Index of a text node among its parent's child TEXT nodes, in order - the same
32198
32252
  // counting the server uses to re-locate it in source_html (ENG-6856).
32199
32253
  function textIndexOf(container, tn){
@@ -32303,6 +32357,105 @@ var MARKUP_BRIDGE_SCRIPT = `<script>(function(){
32303
32357
  e.preventDefault(); e.stopPropagation(); startEditTextNode(tn, el);
32304
32358
  }
32305
32359
  }, true);
32360
+ // ENG-8477: reach content the artefact covers with its OWN overlay.
32361
+ //
32362
+ // A deck built from the canonical template lays two full-viewport click zones
32363
+ // over its slides (.clickzone, position:fixed, z-index:40) to page forward and
32364
+ // back. The template's own comment says it plainly: "the click zones sit above
32365
+ // slide content by design, so any interactive element inside a slide needs
32366
+ // position:relative + a higher z-index to stay clickable". Slide TEXT is not an
32367
+ // interactive element and never gets one.
32368
+ //
32369
+ // So every click a viewer aimed at a paragraph landed on #zone-prev/#zone-next.
32370
+ // The handler above read e.target, got an empty div, editable() rejected it on
32371
+ // the text check, and no editor opened - while the zone's own handler paged the
32372
+ // deck. A single-page artefact has no zones and the identical bridge works
32373
+ // perfectly on it, which is precisely why editing "works in web page formats
32374
+ // but not decks". Verified on real published content, not just the template.
32375
+ //
32376
+ // Why a MODE rather than always-on. One press cannot mean both "page the deck"
32377
+ // and "edit this text". The zones are the deck's primary navigation, so
32378
+ // silently stealing clicks from them would trade one broken gesture for
32379
+ // another. The shell gives an armed viewer an explicit Edit-text toggle; until
32380
+ // they turn it on, every line below is inert and the artefact behaves exactly
32381
+ // as it does today. Anonymous viewers never arm at all.
32382
+ //
32383
+ // Why window + capture. Same phase argument as ENG-8500: the artefact's script
32384
+ // is appended before the bridge, so registration order cannot be won, but the
32385
+ // capture path runs window -> document -> ... -> target and therefore beats a
32386
+ // handler bound to the zone itself no matter when it was registered.
32387
+ // stopImmediatePropagation then keeps the deck from also paging.
32388
+ //
32389
+ // Deliberately general: nothing here knows about .clickzone or any class name.
32390
+ // It fixes any artefact that covers its own content.
32391
+ window.addEventListener('click', function(e){
32392
+ if(!editEnabled || !editMode || editing) return;
32393
+ var sel=document.getSelection(); if(sel && String(sel).trim()) return; // selection => comment flow
32394
+ var t=e.target;
32395
+ // Our own toolbar (Save/Cancel) must keep its clicks.
32396
+ if(t && t.closest && t.closest('[data-al-noedit]')) return;
32397
+ // Already directly on editable content: the document-level handler above
32398
+ // covers it, and duplicating the work here would start the edit twice.
32399
+ if(editable(t)||inlineOnlyEditable(t)) return;
32400
+ var el=editableAtPoint(e.clientX, e.clientY);
32401
+ // Nothing editable under the pointer - leave the click completely alone so
32402
+ // click-to-page still works while the viewer is moving between slides.
32403
+ if(!el) return;
32404
+ // Suppress ONLY once an edit is actually starting. Suppressing up front left
32405
+ // the inline-only fallback below as a dead click: no editor opened AND the
32406
+ // deck could not page, because the event was already stopped.
32407
+ if(editable(el)){ e.preventDefault(); e.stopImmediatePropagation(); startEdit(el); return; }
32408
+ var tn=textNodeAtPointThrough(e.clientX, e.clientY, el);
32409
+ if(tn && tn.parentElement===el && (tn.nodeValue||'').trim()){
32410
+ e.preventDefault(); e.stopImmediatePropagation(); startEditTextNode(tn, el); return;
32411
+ }
32412
+ // An inline-only container whose exact text run we could not resolve (no
32413
+ // caret API, or the point fell between runs). Editing the whole container
32414
+ // would flatten its nested markup, so leave the click to the artefact and
32415
+ // let it page as it normally would.
32416
+ }, true);
32417
+ // ENG-8477: the same deck template pulls focus back to the document on every
32418
+ // pointer press, to keep its arrow keys working when the iframe loses focus:
32419
+ //
32420
+ // function grabFocus(){ try{ window.focus(); document.body.focus(); }catch(e){} }
32421
+ // addEventListener('pointerdown', grabFocus);
32422
+ //
32423
+ // A bare addEventListener at artefact top level is window + bubble phase, so a
32424
+ // window CAPTURE listener still gets there first. Without this guard, clicking
32425
+ // into an open editor to place the caret hands focus straight to <body> and the
32426
+ // viewer cannot type - a second, independent breaker that would have survived
32427
+ // the overlay fix on its own.
32428
+ //
32429
+ // Scoped as tightly as it can be: only while an edit is open, and only for a
32430
+ // press that lands inside the editor. A press anywhere else is the viewer
32431
+ // leaving, and the artefact keeps it.
32432
+ window.addEventListener('pointerdown', function(e){
32433
+ if(!editing) return;
32434
+ var t=e.target;
32435
+ if(t!==editing && !(editing.contains && t && t.nodeType && editing.contains(t))) return;
32436
+ e.stopImmediatePropagation();
32437
+ }, true);
32438
+ // ENG-8477: the hover affordance has the same blind spot as the click did -
32439
+ // mouseover reports the overlay, so a viewer in edit mode would get no cue that
32440
+ // slide text can be edited at all. Probing the stack on every mousemove would
32441
+ // be wasteful, so this only runs in edit mode, only when the direct target is
32442
+ // not already editable (that case the mouseover handler above still covers),
32443
+ // and no more than once per ~80ms.
32444
+ var hoverProbeAt=0, hoverEl=null;
32445
+ function clearDeepHover(){
32446
+ if(hoverEl && hoverEl!==editing && hoverEl.__alHover){ try{ hoverEl.style.outline=''; }catch(e){} hoverEl.__alHover=0; }
32447
+ hoverEl=null;
32448
+ }
32449
+ document.addEventListener('mousemove', function(e){
32450
+ if(!editEnabled || !editMode || editing){ clearDeepHover(); return; }
32451
+ var t=e.target;
32452
+ if(editable(t)||inlineOnlyEditable(t)){ clearDeepHover(); return; }
32453
+ var now=Date.now(); if(now-hoverProbeAt<80) return; hoverProbeAt=now;
32454
+ var el=editableAtPoint(e.clientX, e.clientY);
32455
+ if(el===hoverEl) return;
32456
+ clearDeepHover();
32457
+ if(el){ try{ el.style.outline=el.style.outline||'1px dashed rgba(110,231,183,.6)'; }catch(e){} el.__alHover=1; hoverEl=el; }
32458
+ });
32306
32459
  // ENG-8500: while the inline editor is focused, the artefact's OWN keyboard
32307
32460
  // handlers must not see the keystroke.
32308
32461
  //
@@ -32394,6 +32547,13 @@ var MARKUP_BRIDGE_SCRIPT = `<script>(function(){
32394
32547
  var d=e.data; if(!d || d['${MARKUP_CONTROL_MARKER}']!==true) return;
32395
32548
  if(d.type==='enable-edit'){ editEnabled=true; }
32396
32549
  else if(d.type==='enable-comment'){ commentEnabled=true; }
32550
+ // ENG-8477: the viewer toggled Edit text in the shell. Turning it OFF must
32551
+ // also close anything open and drop the hover cue, or the artefact is left
32552
+ // wearing our outlines with no way to clear them.
32553
+ else if(d.type==='set-edit-mode'){
32554
+ editMode = !!d.on;
32555
+ if(!editMode){ if(editing) endEdit(false); clearDeepHover(); }
32556
+ }
32397
32557
  else if(d.type==='edit-result'){
32398
32558
  if(d.ok){ /* republish hot-swaps the iframe; nothing to do */ }
32399
32559
  else { /* leave the (now non-editable) text; the published version is the source of truth */ }
@@ -38478,6 +38478,11 @@ var MARKUP_BRIDGE_SCRIPT = `<script>(function(){
38478
38478
  var commentEnabled = false; // armed when the viewer may comment (ENG-6847 right-click)
38479
38479
  var editing = null; // the element currently in edit mode
38480
38480
  var bar = null; // the Save/Cancel toolbar
38481
+ // ENG-8477: the viewer has explicitly turned editing ON from the shell. Only
38482
+ // then does the bridge look underneath the artefact's own overlays. See the
38483
+ // long note on the window-capture click handler for why this is a mode and
38484
+ // not just always-on.
38485
+ var editMode = false;
38481
38486
 
38482
38487
  // Element-child indices from <body> down to el. Counts ELEMENT children only
38483
38488
  // (children, not childNodes), matching how the server walks source_html; our
@@ -38532,6 +38537,55 @@ var MARKUP_BRIDGE_SCRIPT = `<script>(function(){
38532
38537
  return null;
38533
38538
  }catch(e){ return null; }
38534
38539
  }
38540
+ // ENG-8477: the topmost element under the pointer that we could actually edit,
38541
+ // looking THROUGH anything the artefact has laid over its own content.
38542
+ //
38543
+ // elementsFromPoint returns the full hit stack, front to back. We walk it from
38544
+ // the front and take the first genuinely editable element, skipping our own
38545
+ // injected UI. An artefact overlay is skipped for free: it is an empty div, so
38546
+ // editable() rejects it on the text check and we keep walking.
38547
+ //
38548
+ // Returns null when there is nothing editable under the pointer at all, which
38549
+ // is the signal to leave the click completely alone.
38550
+ function editableAtPoint(x,y){
38551
+ var stack;
38552
+ try{ stack=document.elementsFromPoint(x,y)||[]; }catch(e){ return null; }
38553
+ for(var i=0;i<stack.length;i++){
38554
+ var el=stack[i];
38555
+ if(!el || el.nodeType!==1) continue;
38556
+ if(el.closest && el.closest('[data-al-noedit]')) continue; // our toolbar, not content
38557
+ if(editable(el)||inlineOnlyEditable(el)) return el;
38558
+ }
38559
+ return null;
38560
+ }
38561
+ // The caret probe (caretRangeFromPoint) reports on the TOPMOST element at the
38562
+ // point, so an overlay swallows it exactly as it swallows the click - it hands
38563
+ // back the overlay div, not the text underneath. Neutralise everything stacked
38564
+ // above the target for the length of the probe, then put it back. The restore
38565
+ // is in a finally and runs before the browser can paint, so nothing flickers
38566
+ // and no artefact state is left modified.
38567
+ // This re-queries elementsFromPoint rather than reusing the caller's stack, so
38568
+ // target is NOT guaranteed to still be in it. Find target's index first and
38569
+ // mute only the prefix in front of it: a not-found target must fall back to the
38570
+ // plain probe, because muting the whole stack would neutralise target and its
38571
+ // ancestors too - a wider style mutation than intended, for a probe whose
38572
+ // result the caller then rejects anyway.
38573
+ function textNodeAtPointThrough(x,y,target){
38574
+ var stack;
38575
+ try{ stack=document.elementsFromPoint(x,y)||[]; }catch(e){ return textNodeAtPoint(x,y); }
38576
+ var cut=-1, i;
38577
+ for(i=0;i<stack.length;i++){ if(stack[i]===target){ cut=i; break; } }
38578
+ if(cut<0) return textNodeAtPoint(x,y);
38579
+ var muted=[];
38580
+ for(i=0;i<cut;i++){
38581
+ muted.push([stack[i], stack[i].style ? stack[i].style.pointerEvents : '']);
38582
+ try{ stack[i].style.pointerEvents='none'; }catch(e){}
38583
+ }
38584
+ try{ return textNodeAtPoint(x,y); }
38585
+ finally{
38586
+ for(i=0;i<muted.length;i++){ try{ muted[i][0].style.pointerEvents=muted[i][1]||''; }catch(e){} }
38587
+ }
38588
+ }
38535
38589
  // Index of a text node among its parent's child TEXT nodes, in order - the same
38536
38590
  // counting the server uses to re-locate it in source_html (ENG-6856).
38537
38591
  function textIndexOf(container, tn){
@@ -38641,6 +38695,105 @@ var MARKUP_BRIDGE_SCRIPT = `<script>(function(){
38641
38695
  e.preventDefault(); e.stopPropagation(); startEditTextNode(tn, el);
38642
38696
  }
38643
38697
  }, true);
38698
+ // ENG-8477: reach content the artefact covers with its OWN overlay.
38699
+ //
38700
+ // A deck built from the canonical template lays two full-viewport click zones
38701
+ // over its slides (.clickzone, position:fixed, z-index:40) to page forward and
38702
+ // back. The template's own comment says it plainly: "the click zones sit above
38703
+ // slide content by design, so any interactive element inside a slide needs
38704
+ // position:relative + a higher z-index to stay clickable". Slide TEXT is not an
38705
+ // interactive element and never gets one.
38706
+ //
38707
+ // So every click a viewer aimed at a paragraph landed on #zone-prev/#zone-next.
38708
+ // The handler above read e.target, got an empty div, editable() rejected it on
38709
+ // the text check, and no editor opened - while the zone's own handler paged the
38710
+ // deck. A single-page artefact has no zones and the identical bridge works
38711
+ // perfectly on it, which is precisely why editing "works in web page formats
38712
+ // but not decks". Verified on real published content, not just the template.
38713
+ //
38714
+ // Why a MODE rather than always-on. One press cannot mean both "page the deck"
38715
+ // and "edit this text". The zones are the deck's primary navigation, so
38716
+ // silently stealing clicks from them would trade one broken gesture for
38717
+ // another. The shell gives an armed viewer an explicit Edit-text toggle; until
38718
+ // they turn it on, every line below is inert and the artefact behaves exactly
38719
+ // as it does today. Anonymous viewers never arm at all.
38720
+ //
38721
+ // Why window + capture. Same phase argument as ENG-8500: the artefact's script
38722
+ // is appended before the bridge, so registration order cannot be won, but the
38723
+ // capture path runs window -> document -> ... -> target and therefore beats a
38724
+ // handler bound to the zone itself no matter when it was registered.
38725
+ // stopImmediatePropagation then keeps the deck from also paging.
38726
+ //
38727
+ // Deliberately general: nothing here knows about .clickzone or any class name.
38728
+ // It fixes any artefact that covers its own content.
38729
+ window.addEventListener('click', function(e){
38730
+ if(!editEnabled || !editMode || editing) return;
38731
+ var sel=document.getSelection(); if(sel && String(sel).trim()) return; // selection => comment flow
38732
+ var t=e.target;
38733
+ // Our own toolbar (Save/Cancel) must keep its clicks.
38734
+ if(t && t.closest && t.closest('[data-al-noedit]')) return;
38735
+ // Already directly on editable content: the document-level handler above
38736
+ // covers it, and duplicating the work here would start the edit twice.
38737
+ if(editable(t)||inlineOnlyEditable(t)) return;
38738
+ var el=editableAtPoint(e.clientX, e.clientY);
38739
+ // Nothing editable under the pointer - leave the click completely alone so
38740
+ // click-to-page still works while the viewer is moving between slides.
38741
+ if(!el) return;
38742
+ // Suppress ONLY once an edit is actually starting. Suppressing up front left
38743
+ // the inline-only fallback below as a dead click: no editor opened AND the
38744
+ // deck could not page, because the event was already stopped.
38745
+ if(editable(el)){ e.preventDefault(); e.stopImmediatePropagation(); startEdit(el); return; }
38746
+ var tn=textNodeAtPointThrough(e.clientX, e.clientY, el);
38747
+ if(tn && tn.parentElement===el && (tn.nodeValue||'').trim()){
38748
+ e.preventDefault(); e.stopImmediatePropagation(); startEditTextNode(tn, el); return;
38749
+ }
38750
+ // An inline-only container whose exact text run we could not resolve (no
38751
+ // caret API, or the point fell between runs). Editing the whole container
38752
+ // would flatten its nested markup, so leave the click to the artefact and
38753
+ // let it page as it normally would.
38754
+ }, true);
38755
+ // ENG-8477: the same deck template pulls focus back to the document on every
38756
+ // pointer press, to keep its arrow keys working when the iframe loses focus:
38757
+ //
38758
+ // function grabFocus(){ try{ window.focus(); document.body.focus(); }catch(e){} }
38759
+ // addEventListener('pointerdown', grabFocus);
38760
+ //
38761
+ // A bare addEventListener at artefact top level is window + bubble phase, so a
38762
+ // window CAPTURE listener still gets there first. Without this guard, clicking
38763
+ // into an open editor to place the caret hands focus straight to <body> and the
38764
+ // viewer cannot type - a second, independent breaker that would have survived
38765
+ // the overlay fix on its own.
38766
+ //
38767
+ // Scoped as tightly as it can be: only while an edit is open, and only for a
38768
+ // press that lands inside the editor. A press anywhere else is the viewer
38769
+ // leaving, and the artefact keeps it.
38770
+ window.addEventListener('pointerdown', function(e){
38771
+ if(!editing) return;
38772
+ var t=e.target;
38773
+ if(t!==editing && !(editing.contains && t && t.nodeType && editing.contains(t))) return;
38774
+ e.stopImmediatePropagation();
38775
+ }, true);
38776
+ // ENG-8477: the hover affordance has the same blind spot as the click did -
38777
+ // mouseover reports the overlay, so a viewer in edit mode would get no cue that
38778
+ // slide text can be edited at all. Probing the stack on every mousemove would
38779
+ // be wasteful, so this only runs in edit mode, only when the direct target is
38780
+ // not already editable (that case the mouseover handler above still covers),
38781
+ // and no more than once per ~80ms.
38782
+ var hoverProbeAt=0, hoverEl=null;
38783
+ function clearDeepHover(){
38784
+ if(hoverEl && hoverEl!==editing && hoverEl.__alHover){ try{ hoverEl.style.outline=''; }catch(e){} hoverEl.__alHover=0; }
38785
+ hoverEl=null;
38786
+ }
38787
+ document.addEventListener('mousemove', function(e){
38788
+ if(!editEnabled || !editMode || editing){ clearDeepHover(); return; }
38789
+ var t=e.target;
38790
+ if(editable(t)||inlineOnlyEditable(t)){ clearDeepHover(); return; }
38791
+ var now=Date.now(); if(now-hoverProbeAt<80) return; hoverProbeAt=now;
38792
+ var el=editableAtPoint(e.clientX, e.clientY);
38793
+ if(el===hoverEl) return;
38794
+ clearDeepHover();
38795
+ if(el){ try{ el.style.outline=el.style.outline||'1px dashed rgba(110,231,183,.6)'; }catch(e){} el.__alHover=1; hoverEl=el; }
38796
+ });
38644
38797
  // ENG-8500: while the inline editor is focused, the artefact's OWN keyboard
38645
38798
  // handlers must not see the keystroke.
38646
38799
  //
@@ -38732,6 +38885,13 @@ var MARKUP_BRIDGE_SCRIPT = `<script>(function(){
38732
38885
  var d=e.data; if(!d || d['${MARKUP_CONTROL_MARKER}']!==true) return;
38733
38886
  if(d.type==='enable-edit'){ editEnabled=true; }
38734
38887
  else if(d.type==='enable-comment'){ commentEnabled=true; }
38888
+ // ENG-8477: the viewer toggled Edit text in the shell. Turning it OFF must
38889
+ // also close anything open and drop the hover cue, or the artefact is left
38890
+ // wearing our outlines with no way to clear them.
38891
+ else if(d.type==='set-edit-mode'){
38892
+ editMode = !!d.on;
38893
+ if(!editMode){ if(editing) endEdit(false); clearDeepHover(); }
38894
+ }
38735
38895
  else if(d.type==='edit-result'){
38736
38896
  if(d.ok){ /* republish hot-swaps the iframe; nothing to do */ }
38737
38897
  else { /* leave the (now non-editable) text; the published version is the source of truth */ }