pinokiod 7.5.21 → 7.5.24

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.
@@ -727,27 +727,38 @@ body.dark .browserview-network-status .browserview-network-status-secondary:focu
727
727
  75% { transform: translate(0, 11px); }
728
728
  }
729
729
  .community-drawer {
730
- display: none;
731
- flex: 0 0 auto;
732
- width: var(--community-width, 360px);
733
- min-width: 240px;
730
+ display: flex;
731
+ flex: 0 0 0;
732
+ width: 0;
733
+ min-width: 0;
734
+ max-width: 0;
734
735
  min-height: 0;
735
- border-left: 1px solid rgba(0,0,0,0.08);
736
+ box-sizing: border-box;
737
+ border-left: 0 solid rgba(0,0,0,0.08);
736
738
  background: #f7f8fa;
739
+ opacity: 0;
740
+ overflow: hidden;
741
+ pointer-events: none;
742
+ transform: translateX(10px);
737
743
  }
738
744
  body.dark .community-drawer {
739
745
  border-left-color: rgba(255,255,255,0.08);
740
746
  background: #0f1115;
741
747
  }
742
748
  .community-resizer {
743
- display: none;
744
- flex: 0 0 7px;
749
+ display: flex;
750
+ flex: 0 0 0;
751
+ width: 0;
752
+ min-width: 0;
745
753
  align-items: stretch;
746
754
  justify-content: center;
747
755
  cursor: col-resize;
748
756
  touch-action: none;
749
757
  outline: none;
750
758
  background: transparent;
759
+ opacity: 0;
760
+ overflow: hidden;
761
+ pointer-events: none;
751
762
  }
752
763
  .community-resizer::before {
753
764
  content: '';
@@ -773,13 +784,27 @@ body.dark .community-resizer:focus-visible::before {
773
784
  background: var(--pinokio-sidebar-separator-focus, rgba(96, 165, 250, 0.6));
774
785
  }
775
786
  .appcanvas.community-open .community-resizer {
776
- display: flex;
787
+ flex: 0 0 7px;
788
+ width: 7px;
789
+ min-width: 7px;
790
+ opacity: 1;
791
+ pointer-events: auto;
777
792
  }
778
793
  .appcanvas.community-open .community-drawer {
779
- display: flex;
794
+ flex: 0 0 var(--community-width, 360px);
795
+ width: var(--community-width, 360px);
796
+ max-width: var(--community-width, 360px);
797
+ border-left-width: 1px;
798
+ opacity: 1;
799
+ pointer-events: auto;
800
+ transform: translateX(0);
780
801
  }
781
802
  .appcanvas.community-expanded .community-resizer {
782
- display: none;
803
+ flex-basis: 0;
804
+ width: 0;
805
+ min-width: 0;
806
+ opacity: 0;
807
+ pointer-events: none;
783
808
  }
784
809
  .appcanvas.community-expanded .community-drawer {
785
810
  position: absolute;
@@ -1362,27 +1387,38 @@ body.dark .mobile-nav-logs.has-new::after {
1362
1387
  }
1363
1388
  }
1364
1389
  .ask-ai-drawer {
1365
- display: none;
1366
- flex: 0 0 auto;
1367
- width: var(--ask-ai-width, 560px);
1368
- min-width: 240px;
1390
+ display: flex;
1391
+ flex: 0 0 0;
1392
+ width: 0;
1393
+ min-width: 0;
1394
+ max-width: 0;
1369
1395
  min-height: 0;
1370
- border-left: 1px solid rgba(0,0,0,0.08);
1396
+ box-sizing: border-box;
1397
+ border-left: 0 solid rgba(0,0,0,0.08);
1371
1398
  background: #f8fafc;
1399
+ opacity: 0;
1400
+ overflow: hidden;
1401
+ pointer-events: none;
1402
+ transform: translateX(10px);
1372
1403
  }
1373
1404
  body.dark .ask-ai-drawer {
1374
1405
  border-left-color: rgba(255,255,255,0.08);
1375
1406
  background: #0f1115;
1376
1407
  }
1377
1408
  .ask-ai-resizer {
1378
- display: none;
1379
- flex: 0 0 7px;
1409
+ display: flex;
1410
+ flex: 0 0 0;
1411
+ width: 0;
1412
+ min-width: 0;
1380
1413
  align-items: stretch;
1381
1414
  justify-content: center;
1382
1415
  cursor: col-resize;
1383
1416
  touch-action: none;
1384
1417
  outline: none;
1385
1418
  background: transparent;
1419
+ opacity: 0;
1420
+ overflow: hidden;
1421
+ pointer-events: none;
1386
1422
  }
1387
1423
  .ask-ai-resizer::before {
1388
1424
  content: '';
@@ -1408,10 +1444,20 @@ body.dark .ask-ai-resizer:focus-visible::before {
1408
1444
  background: var(--pinokio-sidebar-separator-focus, rgba(96, 165, 250, 0.6));
1409
1445
  }
1410
1446
  .appcanvas.panel-open .ask-ai-resizer {
1411
- display: flex;
1447
+ flex: 0 0 7px;
1448
+ width: 7px;
1449
+ min-width: 7px;
1450
+ opacity: 1;
1451
+ pointer-events: auto;
1412
1452
  }
1413
1453
  .appcanvas.panel-open .ask-ai-drawer {
1414
- display: flex;
1454
+ flex: 0 0 var(--ask-ai-width, 560px);
1455
+ width: var(--ask-ai-width, 560px);
1456
+ max-width: var(--ask-ai-width, 560px);
1457
+ border-left-width: 1px;
1458
+ opacity: 1;
1459
+ pointer-events: auto;
1460
+ transform: translateX(0);
1415
1461
  }
1416
1462
  .ask-ai-drawer-inner {
1417
1463
  display: flex;
@@ -2088,12 +2134,26 @@ body.dark .appcanvas > aside {
2088
2134
  transition: flex-basis 190ms cubic-bezier(0.25, 1, 0.5, 1), width 190ms cubic-bezier(0.25, 1, 0.5, 1), max-width 190ms cubic-bezier(0.25, 1, 0.5, 1), margin 190ms cubic-bezier(0.25, 1, 0.5, 1), opacity 140ms cubic-bezier(0.25, 1, 0.5, 1), transform 190ms cubic-bezier(0.25, 1, 0.5, 1);
2089
2135
  }
2090
2136
 
2137
+ .appcanvas.right-panel-motion-ready .ask-ai-drawer,
2138
+ .appcanvas.right-panel-motion-ready .ask-ai-resizer,
2139
+ .appcanvas.right-panel-motion-ready .community-drawer,
2140
+ .appcanvas.right-panel-motion-ready .community-resizer {
2141
+ transition: flex-basis 190ms cubic-bezier(0.25, 1, 0.5, 1), width 190ms cubic-bezier(0.25, 1, 0.5, 1), max-width 190ms cubic-bezier(0.25, 1, 0.5, 1), min-width 190ms cubic-bezier(0.25, 1, 0.5, 1), opacity 140ms cubic-bezier(0.25, 1, 0.5, 1), transform 190ms cubic-bezier(0.25, 1, 0.5, 1);
2142
+ }
2143
+
2091
2144
  .appcanvas.sidebar-resizing > aside,
2092
2145
  .appcanvas.sidebar-resizing > aside .menu-container,
2093
2146
  .appcanvas.sidebar-resizing .appcanvas-resizer {
2094
2147
  transition: none !important;
2095
2148
  }
2096
2149
 
2150
+ .appcanvas.right-panel-resizing .ask-ai-drawer,
2151
+ .appcanvas.right-panel-resizing .ask-ai-resizer,
2152
+ .appcanvas.right-panel-resizing .community-drawer,
2153
+ .appcanvas.right-panel-resizing .community-resizer {
2154
+ transition: none !important;
2155
+ }
2156
+
2097
2157
  .appcanvas.sidebar-collapsed > aside {
2098
2158
  flex: 0 0 0;
2099
2159
  width: 0;
@@ -2125,7 +2185,11 @@ body.dark .appcanvas > aside {
2125
2185
  .sidebar-toggle-content,
2126
2186
  .appcanvas.sidebar-motion-ready > aside,
2127
2187
  .appcanvas.sidebar-motion-ready > aside .menu-container,
2128
- .appcanvas.sidebar-motion-ready .appcanvas-resizer {
2188
+ .appcanvas.sidebar-motion-ready .appcanvas-resizer,
2189
+ .appcanvas.right-panel-motion-ready .ask-ai-drawer,
2190
+ .appcanvas.right-panel-motion-ready .ask-ai-resizer,
2191
+ .appcanvas.right-panel-motion-ready .community-drawer,
2192
+ .appcanvas.right-panel-motion-ready .community-resizer {
2129
2193
  transition-duration: 0.01ms !important;
2130
2194
  }
2131
2195
  }
@@ -7147,6 +7211,9 @@ header.navheader h1 {
7147
7211
  }
7148
7212
  .appcanvas.community-mode .community-drawer {
7149
7213
  display: flex;
7214
+ opacity: 1;
7215
+ pointer-events: auto;
7216
+ transform: translateX(0);
7150
7217
  width: 100%;
7151
7218
  max-width: none;
7152
7219
  border-left: 0;
@@ -16461,9 +16528,20 @@ document.addEventListener("DOMContentLoaded", () => {
16461
16528
  askAiTrigger.setAttribute("aria-hidden", "false")
16462
16529
  askAiTrigger.setAttribute("aria-expanded", open ? "true" : "false")
16463
16530
  }
16531
+ if (!open && drawer.contains(document.activeElement) && askAiTrigger) {
16532
+ try {
16533
+ askAiTrigger.focus({ preventScroll: true })
16534
+ } catch (_) {
16535
+ askAiTrigger.focus()
16536
+ }
16537
+ }
16464
16538
  drawer.setAttribute("aria-hidden", open ? "false" : "true")
16539
+ if ("inert" in drawer) {
16540
+ drawer.inert = !open
16541
+ }
16465
16542
  if (resizer) {
16466
16543
  resizer.setAttribute("aria-hidden", open ? "false" : "true")
16544
+ resizer.tabIndex = open ? 0 : -1
16467
16545
  }
16468
16546
  }
16469
16547
 
@@ -17174,6 +17252,9 @@ document.addEventListener("DOMContentLoaded", () => {
17174
17252
  let handlePointerUp = null
17175
17253
 
17176
17254
  const finishResizing = () => {
17255
+ if (appcanvas) {
17256
+ appcanvas.classList.remove("right-panel-resizing")
17257
+ }
17177
17258
  if (!isResizing) {
17178
17259
  return
17179
17260
  }
@@ -17220,6 +17301,9 @@ document.addEventListener("DOMContentLoaded", () => {
17220
17301
  }
17221
17302
  event.preventDefault()
17222
17303
  isResizing = true
17304
+ if (appcanvas) {
17305
+ appcanvas.classList.add("right-panel-resizing")
17306
+ }
17223
17307
  pointerId = event.pointerId
17224
17308
  startX = event.clientX
17225
17309
  startWidth = drawer.getBoundingClientRect().width
@@ -17938,6 +18022,16 @@ document.addEventListener("DOMContentLoaded", () => {
17938
18022
  }
17939
18023
  appcanvas.classList.toggle("community-mode", communityMode)
17940
18024
  if (communityMode) {
18025
+ const ensureOpen = () => {
18026
+ if (communityMode && !open) {
18027
+ setOpen(true, { persist: false })
18028
+ }
18029
+ }
18030
+ if (window.queueMicrotask) {
18031
+ window.queueMicrotask(ensureOpen)
18032
+ } else {
18033
+ setTimeout(ensureOpen, 0)
18034
+ }
17941
18035
  setExpanded(false)
17942
18036
  }
17943
18037
  if (communityModeToggle) {
@@ -18059,9 +18153,20 @@ document.addEventListener("DOMContentLoaded", () => {
18059
18153
  toggle.classList.toggle("selected", open)
18060
18154
  toggle.setAttribute("aria-expanded", open ? "true" : "false")
18061
18155
  }
18156
+ if (!open && drawer.contains(document.activeElement) && toggle) {
18157
+ try {
18158
+ toggle.focus({ preventScroll: true })
18159
+ } catch (_) {
18160
+ toggle.focus()
18161
+ }
18162
+ }
18062
18163
  drawer.setAttribute("aria-hidden", open ? "false" : "true")
18164
+ if ("inert" in drawer) {
18165
+ drawer.inert = !open
18166
+ }
18063
18167
  if (resizer) {
18064
18168
  resizer.setAttribute("aria-hidden", open ? "false" : "true")
18169
+ resizer.tabIndex = open ? 0 : -1
18065
18170
  }
18066
18171
  if (persist) {
18067
18172
  try {
@@ -18541,6 +18646,9 @@ document.addEventListener("DOMContentLoaded", () => {
18541
18646
  let handlePointerUp = null
18542
18647
 
18543
18648
  const finishResizing = () => {
18649
+ if (appcanvas) {
18650
+ appcanvas.classList.remove("right-panel-resizing")
18651
+ }
18544
18652
  if (!isResizing) {
18545
18653
  return
18546
18654
  }
@@ -18593,6 +18701,9 @@ document.addEventListener("DOMContentLoaded", () => {
18593
18701
  }
18594
18702
  event.preventDefault()
18595
18703
  isResizing = true
18704
+ if (appcanvas) {
18705
+ appcanvas.classList.add("right-panel-resizing")
18706
+ }
18596
18707
  pointerId = event.pointerId
18597
18708
  startX = event.clientX
18598
18709
  startWidth = drawer.getBoundingClientRect().width
@@ -18604,6 +18715,16 @@ document.addEventListener("DOMContentLoaded", () => {
18604
18715
  }
18605
18716
  })()
18606
18717
 
18718
+ ;(function() {
18719
+ const appcanvas = document.querySelector(".appcanvas")
18720
+ if (!appcanvas) {
18721
+ return
18722
+ }
18723
+ requestAnimationFrame(() => {
18724
+ appcanvas.classList.add("right-panel-motion-ready")
18725
+ })
18726
+ })()
18727
+
18607
18728
  <%- include('partials/launch_requirements_status_client') %>
18608
18729
 
18609
18730
  ;(function() {
@@ -18,15 +18,35 @@
18
18
  </head>
19
19
  <body class="<%= theme %> main-sidebar-page task-launcher-page task-page plugin-page plugin-detail-page" data-agent="<%= agent %>">
20
20
  <% const currentPluginCwd = typeof pluginCwd === "string" ? pluginCwd : ""; %>
21
+ <%
22
+ const pluginDetailHref = plugin.pluginPath
23
+ ? `/plugin?path=${encodeURIComponent(plugin.pluginPath)}`
24
+ : "/plugin"
25
+ const githubStatusValue = !shareState.manageable
26
+ ? "Read only"
27
+ : shareState.remoteWebUrl && !shareState.hasPublished
28
+ ? "Remote created"
29
+ : shareState.remoteWebUrl && ((Number(shareState.changeCount || 0) > 0) || (Number(shareState.aheadCount || 0) > 0))
30
+ ? "Out of date"
31
+ : shareState.remoteWebUrl
32
+ ? "Connected"
33
+ : shareState.gitInitialized
34
+ ? "Local only"
35
+ : "Setup needed"
36
+ const canDeletePlugin = Boolean(plugin.pluginPath && shareState && shareState.ownership === "local" && shareState.dir)
37
+ const showHeaderWebsite = Boolean(!pluginUi.showSidebar && plugin.link)
38
+ %>
21
39
  <%- include('partials/app_navheader', { agent }) %>
22
40
  <main>
23
41
  <div class="task-container">
24
42
  <section class="task-shell">
25
43
  <header class="task-shell-header">
26
44
  <div class="task-shell-header-main">
27
- <div class="task-shell-topline">
28
- <p class="task-eyebrow">Plugins</p>
29
- </div>
45
+ <nav class="task-breadcrumb plugin-detail-breadcrumb" aria-label="Plugin breadcrumb">
46
+ <a class="task-breadcrumb-link" href="/plugins">Plugins</a>
47
+ <span class="task-breadcrumb-separator" aria-hidden="true">&gt;</span>
48
+ <a class="task-breadcrumb-link task-breadcrumb-current" href="<%= pluginDetailHref %>" aria-current="page"><%= plugin.title %></a>
49
+ </nav>
30
50
  <div class="plugin-detail-title-row">
31
51
  <% if (plugin.image) { %>
32
52
  <img class="plugin-detail-title-art" src="<%= plugin.image %>" alt="<%= plugin.title %> icon">
@@ -48,16 +68,26 @@
48
68
  </div>
49
69
  <% } %>
50
70
  </div>
71
+ <% if (showHeaderWebsite || canDeletePlugin) { %>
51
72
  <div class="task-header-actions">
52
- <a class="task-link-button" href="/plugins">
53
- <i class="fa-solid fa-arrow-left" aria-hidden="true"></i>
54
- <span>Back</span>
73
+ <% if (showHeaderWebsite) { %>
74
+ <a class="task-link-button" href="<%= plugin.link %>" target="_blank" rel="noopener noreferrer">
75
+ <i class="fa-solid fa-arrow-up-right-from-square" aria-hidden="true"></i>
76
+ <span>Website</span>
55
77
  </a>
78
+ <% } %>
79
+ <% if (canDeletePlugin) { %>
80
+ <button class="task-button danger" type="button" data-plugin-delete aria-label="Delete <%= plugin.title %> plugin">
81
+ <i class="fa-solid fa-trash" aria-hidden="true"></i>
82
+ <span>Delete</span>
83
+ </button>
84
+ <% } %>
56
85
  </div>
86
+ <% } %>
57
87
  </header>
58
88
 
59
89
  <div class="task-shell-body">
60
- <div class="task-detail-layout">
90
+ <div class="task-detail-layout <%= pluginUi.showSidebar ? '' : 'task-detail-layout-single' %>">
61
91
  <div class="task-detail-main">
62
92
  <section class="task-section plugin-detail-actions-section" data-plugin-actions-section>
63
93
  <div class="task-section-head">
@@ -160,19 +190,24 @@
160
190
  <% } %>
161
191
  </div>
162
192
 
193
+ <% if (pluginUi.showSidebar) { %>
163
194
  <aside class="task-share-section task-detail-sidebar">
164
195
  <div class="task-section-head">
165
196
  <h2 class="task-section-title">Plugin status</h2>
166
197
  </div>
167
198
  <div class="task-side-stack">
168
- <div class="task-meta-row task-meta-row-compact">
169
- <div class="task-meta-chip">
170
- <span class="task-meta-chip-label"><%= pluginUi.sourceLabel %></span>
171
- <span class="task-meta-chip-value"><%= pluginUi.sourceValue %></span>
199
+ <div class="plugin-detail-status-list">
200
+ <div class="plugin-detail-status-row">
201
+ <span class="plugin-detail-status-label"><%= pluginUi.sourceLabel %></span>
202
+ <span class="plugin-detail-status-value"><%= pluginUi.sourceValue %></span>
203
+ </div>
204
+ <div class="plugin-detail-status-row">
205
+ <span class="plugin-detail-status-label"><%= pluginUi.statusLabel %></span>
206
+ <span class="plugin-detail-status-value"><%= pluginUi.statusValue %></span>
172
207
  </div>
173
- <div class="task-meta-chip">
174
- <span class="task-meta-chip-label"><%= pluginUi.statusLabel %></span>
175
- <span class="task-meta-chip-value"><%= pluginUi.statusValue %></span>
208
+ <div class="plugin-detail-status-row">
209
+ <span class="plugin-detail-status-label"><%= pluginUi.githubPanelTitle %></span>
210
+ <span class="plugin-detail-status-value" data-plugin-share-note><%= githubStatusValue %></span>
176
211
  </div>
177
212
  </div>
178
213
  <div class="task-actions task-detail-status-actions">
@@ -192,10 +227,6 @@
192
227
  </div>
193
228
 
194
229
  <section class="task-side-group">
195
- <div class="task-section-head">
196
- <h2 class="task-section-title"><%= pluginUi.githubPanelTitle %></h2>
197
- <div class="task-section-note" data-plugin-share-note></div>
198
- </div>
199
230
  <% if (pluginUi.canManageSource) { %>
200
231
  <div class="task-share-next-step-main">
201
232
  <h3 class="task-share-next-step-title" data-plugin-share-next-title></h3>
@@ -224,12 +255,12 @@
224
255
  </div>
225
256
  </div>
226
257
  </form>
227
- <div class="task-actions plugin-share-static-actions">
258
+ <div class="task-actions plugin-share-static-actions <%= shareState.remoteWebUrl || (shareState.remoteUrl && shareState.githubConnected) ? '' : 'task-hidden' %>" data-plugin-share-static-actions>
228
259
  <a class="task-link-button <%= shareState.remoteWebUrl ? '' : 'task-hidden' %>" href="<%= shareState.remoteWebUrl || '#' %>" target="_blank" rel="noopener noreferrer" data-plugin-share-remote-link>
229
260
  <i class="fa-brands fa-github" aria-hidden="true"></i>
230
261
  <span>View on GitHub</span>
231
262
  </a>
232
- <button class="task-button" type="button" data-plugin-share-refresh>
263
+ <button class="task-button <%= shareState.remoteUrl && shareState.githubConnected ? '' : 'task-hidden' %>" type="button" data-plugin-share-refresh>
233
264
  <i class="fa-solid fa-rotate-right" aria-hidden="true"></i>
234
265
  <span>Check again</span>
235
266
  </button>
@@ -247,6 +278,7 @@
247
278
  <% } %>
248
279
  </section>
249
280
  </aside>
281
+ <% } %>
250
282
  </div>
251
283
  </div>
252
284
  </section>
@@ -869,12 +869,25 @@ body.dark {
869
869
  <h1 class='task-title'>Plugins</h1>
870
870
  <p class='task-description'>Open desktop editors externally or launch coding tools inside Pinokio.</p>
871
871
  </div>
872
+ <div class="task-header-actions">
873
+ <button type="button" class="task-link-button primary" data-universal-launcher-open="create_plugin">
874
+ <i class="fa-solid fa-plus" aria-hidden="true"></i>
875
+ <span>Create plugin</span>
876
+ </button>
877
+ <button type="button" class="task-link-button" data-universal-launcher-open="create_plugin" data-universal-launcher-mode="download">
878
+ <i class="fa-solid fa-download" aria-hidden="true"></i>
879
+ <span>Download</span>
880
+ </button>
881
+ </div>
872
882
  </header>
873
883
  <div class='task-shell-body plugin-shell-body'>
874
884
  <% if (plugins.length === 0) { %>
875
885
  <div class='plugin-empty task-empty-state'>
876
886
  <p>No plugins found. Clone or add plugin definitions under <code>~/pinokio/plugin</code> to see them listed here.</p>
877
- <a class='task-link-button primary' href="/home?mode=download"><i class="fa-solid fa-download"></i> Download plugins</a>
887
+ <button type="button" class="task-button primary" data-universal-launcher-open="create_plugin" data-universal-launcher-mode="download">
888
+ <i class="fa-solid fa-download" aria-hidden="true"></i>
889
+ <span>Download plugins</span>
890
+ </button>
878
891
  </div>
879
892
  <% } else { %>
880
893
  <div class='plugin-category-layout'>
@@ -1910,6 +1910,8 @@ document.addEventListener("DOMContentLoaded", async () => {
1910
1910
  payload.id = runId
1911
1911
  payload.useId = true
1912
1912
  }
1913
+ this.error = false
1914
+ this.eventPanelStopPending = false
1913
1915
  this.resizeSync.reset()
1914
1916
  this.socket.run(payload, async (packet) => {
1915
1917
  if (packet.type === 'start') {
@@ -1973,6 +1975,14 @@ document.addEventListener("DOMContentLoaded", async () => {
1973
1975
  refreshParent(packet)
1974
1976
  pluginTerminalDiscoveryRefresher.clear()
1975
1977
  reloadMemory()
1978
+ if (this.eventPanelStopPending) {
1979
+ this.eventPanelStopPending = false
1980
+ postMessageToAncestors({
1981
+ e: "pinokio:event-panel-status",
1982
+ success: !this.error,
1983
+ status: "stopped"
1984
+ })
1985
+ }
1976
1986
  this.flushPendingTerminalWrite()
1977
1987
  this.term.write("\r\nDisconnected...\r\n")
1978
1988
  document.querySelector("#status-window").innerHTML = "<b>Ready</b>"
@@ -2339,11 +2349,7 @@ document.addEventListener("DOMContentLoaded", async () => {
2339
2349
  } else if (packet.type === "event") {
2340
2350
  if (packet.data === "stop") {
2341
2351
  if (window.__pinokioEventPanel) {
2342
- postMessageToAncestors({
2343
- e: "pinokio:event-panel-status",
2344
- success: !this.error,
2345
- status: "stopped"
2346
- })
2352
+ this.eventPanelStopPending = true
2347
2353
  }
2348
2354
  document.querySelector("#status-window").innerHTML = "<b>Ready</b>"
2349
2355
  setTimeout(() => {
@@ -1153,6 +1153,33 @@ test("api.stop stops the canonical script path when the visible run id is decora
1153
1153
  })
1154
1154
  })
1155
1155
 
1156
+ test("api.stop disconnects when the script was deleted before stop cleanup", async () => {
1157
+ await withApiStopFixture(async ({ api, launchPath, killedGroups, stopped, packets }) => {
1158
+ const decoratedId = `${launchPath}?cwd=undefined`
1159
+ api.resolveScript = async () => {
1160
+ const error = new Error("missing script")
1161
+ error.code = "ENOENT"
1162
+ throw error
1163
+ }
1164
+ api.running[launchPath] = true
1165
+ api.running[decoratedId] = true
1166
+ api.kernel.memory.local[launchPath] = { live: true }
1167
+ api.kernel.memory.local[decoratedId] = { live: true }
1168
+
1169
+ await api.stop({ params: { id: decoratedId } })
1170
+
1171
+ assert.equal(api.running[launchPath], undefined)
1172
+ assert.equal(api.running[decoratedId], undefined)
1173
+ assert.equal(api.kernel.memory.local[launchPath], undefined)
1174
+ assert.equal(api.kernel.memory.local[decoratedId], undefined)
1175
+ assert.ok(killedGroups.includes(launchPath), "canonical script group must be killed")
1176
+ assert.ok(killedGroups.includes(decoratedId), "visible run id group must remain stopped")
1177
+ assert.equal(stopped.length, 1)
1178
+ assert.equal(stopped[0].scriptPath, launchPath)
1179
+ assert.ok(packets.some((packet) => packet.type === "disconnect" && packet.id === decoratedId))
1180
+ })
1181
+ })
1182
+
1156
1183
  test("startup launch request does not block on its own startup status row", async () => {
1157
1184
  await withFixtureApps({
1158
1185
  target: {}