pinokiod 5.1.10 → 5.1.11

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.
Files changed (56) hide show
  1. package/kernel/api/fs/download_worker.js +158 -0
  2. package/kernel/api/fs/index.js +95 -91
  3. package/kernel/api/index.js +3 -0
  4. package/kernel/bin/index.js +5 -2
  5. package/kernel/environment.js +19 -2
  6. package/kernel/git.js +972 -1
  7. package/kernel/index.js +65 -30
  8. package/kernel/peer.js +1 -2
  9. package/kernel/plugin.js +0 -8
  10. package/kernel/procs.js +92 -36
  11. package/kernel/prototype.js +45 -22
  12. package/kernel/shells.js +30 -6
  13. package/kernel/sysinfo.js +33 -13
  14. package/kernel/util.js +61 -24
  15. package/kernel/workspace_status.js +131 -7
  16. package/package.json +1 -1
  17. package/pipe/index.js +1 -1
  18. package/server/index.js +1169 -350
  19. package/server/public/create-launcher.js +157 -2
  20. package/server/public/install.js +135 -41
  21. package/server/public/style.css +32 -1
  22. package/server/public/tab-link-popover.js +45 -14
  23. package/server/public/terminal-settings.js +51 -35
  24. package/server/public/urldropdown.css +89 -3
  25. package/server/socket.js +12 -7
  26. package/server/views/agents.ejs +4 -3
  27. package/server/views/app.ejs +798 -30
  28. package/server/views/bootstrap.ejs +2 -1
  29. package/server/views/checkpoints.ejs +1014 -0
  30. package/server/views/checkpoints_registry_beta.ejs +260 -0
  31. package/server/views/columns.ejs +4 -4
  32. package/server/views/connect.ejs +1 -0
  33. package/server/views/d.ejs +74 -4
  34. package/server/views/download.ejs +28 -28
  35. package/server/views/editor.ejs +4 -5
  36. package/server/views/env_editor.ejs +1 -1
  37. package/server/views/file_explorer.ejs +1 -1
  38. package/server/views/index.ejs +3 -1
  39. package/server/views/init/index.ejs +2 -1
  40. package/server/views/install.ejs +2 -1
  41. package/server/views/net.ejs +9 -7
  42. package/server/views/network.ejs +15 -14
  43. package/server/views/pro.ejs +5 -2
  44. package/server/views/prototype/index.ejs +2 -1
  45. package/server/views/registry_link.ejs +76 -0
  46. package/server/views/rows.ejs +4 -4
  47. package/server/views/screenshots.ejs +1 -0
  48. package/server/views/settings.ejs +1 -0
  49. package/server/views/shell.ejs +4 -6
  50. package/server/views/terminal.ejs +528 -38
  51. package/server/views/tools.ejs +1 -0
  52. package/undefined/logs/dev/plugin/cursor-agent.git/pinokio.js/1764297248545 +0 -45
  53. package/undefined/logs/dev/plugin/cursor-agent.git/pinokio.js/1764335557118 +0 -45
  54. package/undefined/logs/dev/plugin/cursor-agent.git/pinokio.js/1764335834126 +0 -45
  55. package/undefined/logs/dev/plugin/cursor-agent.git/pinokio.js/events +0 -12
  56. package/undefined/logs/dev/plugin/cursor-agent.git/pinokio.js/latest +0 -45
@@ -24,9 +24,30 @@ body.dark #devtab.selected {
24
24
  border: none;
25
25
  background: rgb(27, 28, 29) !important;
26
26
  }
27
+ .config-info {
28
+ cursor: pointer;
29
+ display: flex;
30
+ align-items: center;
31
+ font-size: 12px;
32
+ font-weight: bold;
33
+ }
34
+ body.dark .snapshot-comment {
35
+ color: white;
36
+ }
37
+ .config-info:hover, .config-info:hover i {
38
+ color: rgba(127, 91, 243, 0.95);
39
+ }
27
40
  #editortab {
28
41
  color: #7f5bf3;
29
42
  }
43
+ body.dark #layout-toggle {
44
+ color: white;
45
+ }
46
+ #layout-toggle {
47
+ padding: 10px;
48
+ border: none;
49
+ background: none;
50
+ }
30
51
  #devtab {
31
52
  align-items: center;
32
53
  justify-content: center;
@@ -133,6 +154,42 @@ body.dark .m {
133
154
  flex-direction: column;
134
155
  flex-grow: 1;
135
156
  }
157
+ .appcanvas.vertical {
158
+ flex-direction: row;
159
+ --appcanvas-sidebar-width: 150px;
160
+ }
161
+ .appcanvas.vertical > aside .header-item {
162
+ border-radius: 0;
163
+ }
164
+ .appcanvas.vertical #devtab {
165
+ border-radius: 0;
166
+ }
167
+ .appcanvas.vertical img.meta-icon {
168
+ /*
169
+ padding: 10px;
170
+ display: block;
171
+ margin: 0 auto;
172
+ width: 50px;
173
+ height: 50px;
174
+ */
175
+ }
176
+ .appcanvas.vertical aside {
177
+ flex-direction: row;
178
+ }
179
+ .appcanvas.vertical > aside .submenu {
180
+ display: block;
181
+ }
182
+ .appcanvas.vertical > aside .m {
183
+ display: block;
184
+ }
185
+ .appcanvas.vertical .menu-container {
186
+ overflow: auto;
187
+ display: block;
188
+ width: var(--appcanvas-sidebar-width);
189
+ }
190
+ .appcanvas.vertical .config-info {
191
+ margin-bottom: 10px;
192
+ }
136
193
  .appcanvas {
137
194
  display: flex;
138
195
  flex-direction: column;
@@ -158,9 +215,37 @@ body.dark .appcanvas_filler {
158
215
  height: 5px;
159
216
  background: #F1F1F1 !important;
160
217
  }
218
+ .appcanvas-resizer {
219
+ display: none;
220
+ }
221
+ .appcanvas.vertical .appcanvas-resizer {
222
+ display: flex;
223
+ flex: 0 0 0px;
224
+ align-items: stretch;
225
+ justify-content: center;
226
+ cursor: grab;
227
+ touch-action: none;
228
+ }
229
+ .appcanvas.vertical .appcanvas-resizer:hover::before {
230
+ background: rgba(0, 0, 0, 0.15);
231
+ }
232
+ .appcanvas.vertical .appcanvas-resizer::before {
233
+ content: '';
234
+ width: 5px;
235
+ border-radius: 999px;
236
+ margin: 3px 0;
237
+ }
238
+ body.dark .appcanvas.vertical .appcanvas-resizer:hover::before {
239
+ background: rgba(255, 255, 255, 0.3);
240
+ }
241
+ .appcanvas.vertical > aside .header-item {
242
+ max-width: none;
243
+ }
161
244
 
162
245
  .appcanvas > .container {
246
+ /*
163
247
  order: 1;
248
+ */
164
249
  min-height: 0;
165
250
  flex-grow: 1;
166
251
  display: flex;
@@ -1185,11 +1270,15 @@ body.dark .submenu {
1185
1270
  font-size: 12px;
1186
1271
  font-weight: bold;
1187
1272
  }
1273
+ body.dark .disk-usage {
1274
+ border-right: 1px solid rgba(255,255,255,0.1);
1275
+ }
1188
1276
  .disk-usage {
1189
- flex-grow: 1;
1277
+ border-right: 1px solid rgba(0,0,0,0.1);
1190
1278
  font-weight: bold;
1191
- text-align: right;
1192
- opacity: 0.5;
1279
+ color: white;
1280
+ padding: 5px 10px;
1281
+ border-radius: 4px;
1193
1282
  }
1194
1283
  .disk-usage i {
1195
1284
  margin-right: 5px;
@@ -2375,6 +2464,7 @@ body.dark .pinokio-git-diff-viewer-header {
2375
2464
  background: var(--pinokio-modal-body-bg);
2376
2465
  color: var(--pinokio-modal-body-text-color);
2377
2466
  padding: 10px;
2467
+ font-size: 14px;
2378
2468
  }
2379
2469
 
2380
2470
  .pinokio-modal-body--history {
@@ -3227,7 +3317,63 @@ body.dark .pinokio-fork-dropdown-remote, body.dark .pinokio-publish-dropdown-rem
3227
3317
  .meta-icon {
3228
3318
  width: 25px;
3229
3319
  height: 25px;
3230
- padding: 0 0 0 10px;
3320
+ margin-right: 10px;
3321
+ }
3322
+ body.dark .snapshot-footer {
3323
+ color: white;
3324
+ border-top: 1px solid rgba(148, 163, 184, 0.2);
3325
+ }
3326
+ .snapshot-footer .btn {
3327
+ font-size: 14px;
3328
+ }
3329
+ .snapshot-footer {
3330
+ border-top: 1px solid rgba(148, 163, 184, 0.35);
3331
+ padding: 8px !important;
3332
+ display: flex;
3333
+ align-items: center;
3334
+ justify-content: flex-start;
3335
+ gap: 10px;
3336
+ font-size: 14px;
3337
+ flex-wrap: wrap;
3338
+ }
3339
+ .snapshot-footer .snapshot-btn {
3340
+ background: rgba(127, 91, 243, 0.95) !important;
3341
+ padding: 8px;
3342
+ }
3343
+ .snapshot-footer .caption {
3344
+ opacity: 0.8;
3345
+ }
3346
+ .snapshot-footer-save {
3347
+ display: flex;
3348
+ align-items: center;
3349
+ gap: 10px;
3350
+ flex-wrap: wrap;
3351
+ }
3352
+ .snapshot-footer-publish {
3353
+ flex-basis: 100%;
3354
+ display: flex;
3355
+ align-items: center;
3356
+ gap: 10px;
3357
+ }
3358
+ .snapshot-footer-publish-text {
3359
+ opacity: 0.8;
3360
+ }
3361
+ .snapshot-footer-input {
3362
+ flex-grow: 1;
3363
+ }
3364
+ body.dark .snapshot-footer-input input {
3365
+ background: rgba(255,255,255,0.07);
3366
+ }
3367
+ .snapshot-footer-input input {
3368
+ background: rgba(0,0,0,0.07);
3369
+ width: 100%;
3370
+ padding: 8px;
3371
+ border-radius: 2px;
3372
+ border: none;
3373
+ font-size: 12px;
3374
+ }
3375
+ .snapshot-footer-input input::placeholder {
3376
+ opacity: 0.7;
3231
3377
  }
3232
3378
  </style>
3233
3379
  <link href="/app.css" rel="stylesheet"/>
@@ -3304,9 +3450,17 @@ body.dark .pinokio-fork-dropdown-remote, body.dark .pinokio-publish-dropdown-rem
3304
3450
  <% if (type !== 'files') { %>
3305
3451
  <aside class='active'>
3306
3452
  <div class='menu-container'>
3307
- <% if (config.icon) { %>
3308
- <img class='meta-icon' src="<%=config.icon%>"/>
3309
- <% } %>
3453
+ <div class='config-info'>
3454
+ <button type='button' id='layout-toggle'>
3455
+ <i class="fa-solid fa-bars"></i>
3456
+ </button>
3457
+ <% if (config.icon) { %>
3458
+ <img class='meta-icon' src="<%=config.icon%>"/>
3459
+ <% } %>
3460
+ <% if (config.title) { %>
3461
+ <div><%=config.title%></div>
3462
+ <% } %>
3463
+ </div>
3310
3464
  <div class='m n system' data-type="n">
3311
3465
  <%if (type==='browse') { %>
3312
3466
  <a id='devtab' data-mode="refresh" target="<%=dev_link%>" href="<%=dev_link%>" class="btn frame-link selected" data-index="10">
@@ -3356,6 +3510,7 @@ body.dark .pinokio-fork-dropdown-remote, body.dark .pinokio-publish-dropdown-rem
3356
3510
  </div>
3357
3511
  </div>
3358
3512
  </aside>
3513
+ <div class='appcanvas-resizer' id="appcanvas-resizer" role="separator" aria-orientation="vertical" aria-valuemin="140" aria-valuemax="560" aria-valuenow="150" tabindex="0" aria-label="Resize navigation sidebar"></div>
3359
3514
  <% } %>
3360
3515
  <% if (type === "run") { %>
3361
3516
  <div class='appcanvas_filler'></div>
@@ -3375,6 +3530,7 @@ body.dark .pinokio-fork-dropdown-remote, body.dark .pinokio-publish-dropdown-rem
3375
3530
  </div>
3376
3531
  </div>
3377
3532
  -->
3533
+ <span class="disk-usage tab-metric__value" data-path="/">--</span>
3378
3534
  <div class='fs-status-dropdown fs-open-explorer'>
3379
3535
  <button class='fs-status-btn' data-filepath="<%=path%>" type='button'>
3380
3536
  <span class='fs-status-label'>
@@ -3419,11 +3575,6 @@ body.dark .pinokio-fork-dropdown-remote, body.dark .pinokio-publish-dropdown-rem
3419
3575
  </div>
3420
3576
  <% } else if (type === 'files') { %>
3421
3577
  <div id='fs-status' data-workspace="<%=name%>" data-create-uri="<%=git_create_url%>" data-history-uri="<%=git_history_url%>" data-status-uri="<%=git_status_url%>" data-uri="<%=git_monitor_url%>" data-push-uri="<%=git_push_url%>" data-fork-uri="<%=git_fork_url%>">
3422
- <div class="app-info" title="<%=config.title || name%>">
3423
- <div class="app-info-card">
3424
- <img src="<%= config.icon %>" onerror="this.onerror=null; this.src='/pinokio-black.png'" alt="Project icon">
3425
- </div>
3426
- </div>
3427
3578
  <!--
3428
3579
  <div class='fs-status-dropdown nested-menu git blue'>
3429
3580
  <button type='button' class='fs-status-btn frame-link reveal'>
@@ -3436,6 +3587,7 @@ body.dark .pinokio-fork-dropdown-remote, body.dark .pinokio-publish-dropdown-rem
3436
3587
  </div>
3437
3588
  </div>
3438
3589
  -->
3590
+ <span class="disk-usage tab-metric__value" data-path="/">--</span>
3439
3591
  <div class='fs-status-dropdown fs-open-explorer'>
3440
3592
  <button class='fs-status-btn' data-filepath="<%=path%>" type='button'>
3441
3593
  <span class='fs-status-label'>
@@ -3477,6 +3629,7 @@ body.dark .pinokio-fork-dropdown-remote, body.dark .pinokio-publish-dropdown-rem
3477
3629
  </button>
3478
3630
  <div class='fs-dropdown-menu submenu hidden' id='fs-push-menu'></div>
3479
3631
  </div>
3632
+ <div class='flexible'></div>
3480
3633
  </div>
3481
3634
  <% } %>
3482
3635
  <main class='browserview'>
@@ -3484,6 +3637,31 @@ body.dark .pinokio-fork-dropdown-remote, body.dark .pinokio-publish-dropdown-rem
3484
3637
  <iframe class='selected' src="<%=editor_tab%>"></iframe>
3485
3638
  <% } %>
3486
3639
  </main>
3640
+ <% const registryEnabled = typeof registryBetaEnabled === "undefined" ? false : registryBetaEnabled; %>
3641
+ <% const showSnapshotFooter = typeof hasSnapshots === "undefined" ? true : (!hasSnapshots || (registryEnabled && pendingSnapshotId)); %>
3642
+ <% if (showSnapshotFooter) { %>
3643
+ <div class='snapshot-footer' data-workspace="<%=name%>" data-pending-snapshot-id="<%= pendingSnapshotId ? pendingSnapshotId : '' %>" data-registry-beta-enabled="<%= registryEnabled ? '1' : '0' %>">
3644
+ <div class="snapshot-footer-save <%= registryEnabled && pendingSnapshotId ? 'hidden' : '' %>">
3645
+ <div class='caption' title="<%= registryEnabled ? 'Saves a checkpoint for this app (exact git commits). You can publish it after saving.' : 'Saves a checkpoint for this app (exact git commits).' %>">
3646
+ Save this exact state and restore later with 1-click
3647
+ </div>
3648
+ <div class="snapshot-footer-actions" style="display:flex; gap:10px; flex-wrap:wrap; align-items:center;">
3649
+ <button type="button" class="btn btn-primary snapshot-btn-save">
3650
+ <i class="fa-solid fa-circle-check"></i> Save checkpoint
3651
+ </button>
3652
+ </div>
3653
+ </div>
3654
+ <% if (registryEnabled) { %>
3655
+ <div class="snapshot-footer-publish <%= pendingSnapshotId ? '' : 'hidden' %>" aria-live="polite">
3656
+ <div class="snapshot-footer-publish-text">Local save success! Publish to the registry?</div>
3657
+ <div class="snapshot-footer-actions" style="display:flex; gap:10px; flex-wrap:wrap; align-items:center;">
3658
+ <button type="button" class="btn btn-primary snapshot-btn-publish"><i class="fa-solid fa-cloud-arrow-up"></i> Publish Checkpoint</button>
3659
+ <button type="button" class="btn snapshot-btn-later">Later</button>
3660
+ </div>
3661
+ </div>
3662
+ <% } %>
3663
+ </div>
3664
+ <% } %>
3487
3665
  </div>
3488
3666
  </div>
3489
3667
  <script>
@@ -5085,7 +5263,7 @@ const rerenderMenuSection = (container, html) => {
5085
5263
  Swal.close()
5086
5264
  if (res) {
5087
5265
  if (res.success) {
5088
- location.href = "/"
5266
+ location.href = "/home"
5089
5267
  } else if (res.error) {
5090
5268
  alert(res.error)
5091
5269
  }
@@ -5291,9 +5469,9 @@ const rerenderMenuSection = (container, html) => {
5291
5469
  e.stopPropagation()
5292
5470
  return
5293
5471
  }
5294
- if (target.id === 'fs-changes-btn') {
5295
- check_git()
5296
- }
5472
+ if (target.id === 'fs-changes-btn' || target.id === 'fs-push-btn' || target.id === 'fs-fork-btn') {
5473
+ check_git(true)
5474
+ }
5297
5475
  e.preventDefault()
5298
5476
  e.stopPropagation()
5299
5477
  const group = target.getAttribute("data-group")
@@ -5594,6 +5772,178 @@ const rerenderMenuSection = (container, html) => {
5594
5772
  })
5595
5773
  }
5596
5774
  })
5775
+
5776
+ const layoutToggleButton = document.querySelector(".config-info")
5777
+ if (layoutToggleButton) {
5778
+ const layoutToggleIcon = layoutToggleButton.querySelector("i.fa-bars")
5779
+ const appcanvas = document.querySelector(".appcanvas")
5780
+ const sidebarResizer = document.getElementById("appcanvas-resizer")
5781
+ const sidebarContainer = document.querySelector(".appcanvas > aside .menu-container")
5782
+
5783
+ if (appcanvas && typeof localStorage !== "undefined") {
5784
+ const storedLayout = localStorage.getItem("pinokioLayoutVertical")
5785
+ if (storedLayout === "1") {
5786
+ appcanvas.classList.add("vertical")
5787
+ if (layoutToggleIcon) {
5788
+ layoutToggleIcon.classList.add("fa-rotate-90")
5789
+ }
5790
+ }
5791
+ }
5792
+
5793
+ layoutToggleButton.addEventListener("click", () => {
5794
+ if (!appcanvas) {
5795
+ return
5796
+ }
5797
+ const isVertical = appcanvas.classList.toggle("vertical")
5798
+ if (layoutToggleIcon) {
5799
+ if (isVertical) {
5800
+ layoutToggleIcon.classList.add("fa-rotate-90")
5801
+ } else {
5802
+ layoutToggleIcon.classList.remove("fa-rotate-90")
5803
+ }
5804
+ }
5805
+ if (typeof localStorage !== "undefined") {
5806
+ localStorage.setItem("pinokioLayoutVertical", isVertical ? "1" : "0")
5807
+ }
5808
+ })
5809
+
5810
+ if (appcanvas && sidebarResizer && sidebarContainer) {
5811
+ const SIDEBAR_MIN_WIDTH = 140
5812
+ const SIDEBAR_MAX_WIDTH = 560
5813
+ const workspaceName = "<%= typeof name === 'string' ? name : '' %>"
5814
+ const sidebarWidthStorageKey = workspaceName
5815
+ ? `pinokio-app-sidebar-width:${workspaceName}`
5816
+ : "pinokio-app-sidebar-width"
5817
+
5818
+ let sidebarWidth = null
5819
+ let isResizing = false
5820
+ let pointerId = null
5821
+ let resizeState = null
5822
+ let handlePointerMove = null
5823
+ let handlePointerUp = null
5824
+
5825
+ const clampSidebarWidth = (value) => {
5826
+ const numeric = typeof value === "number" ? value : parseInt(value, 10)
5827
+ if (Number.isNaN(numeric)) {
5828
+ return SIDEBAR_MIN_WIDTH
5829
+ }
5830
+ return Math.min(Math.max(numeric, SIDEBAR_MIN_WIDTH), SIDEBAR_MAX_WIDTH)
5831
+ }
5832
+
5833
+ const readSidebarWidth = () => {
5834
+ try {
5835
+ const stored = window.localStorage.getItem(sidebarWidthStorageKey)
5836
+ if (!stored) {
5837
+ return null
5838
+ }
5839
+ const parsed = parseInt(stored, 10)
5840
+ if (Number.isNaN(parsed)) {
5841
+ return null
5842
+ }
5843
+ return parsed
5844
+ } catch (_) {
5845
+ return null
5846
+ }
5847
+ }
5848
+
5849
+ const persistSidebarWidth = (width) => {
5850
+ try {
5851
+ window.localStorage.setItem(sidebarWidthStorageKey, String(width))
5852
+ } catch (_) {}
5853
+ }
5854
+
5855
+ const applySidebarWidth = (value, persist) => {
5856
+ const width = clampSidebarWidth(value)
5857
+ sidebarWidth = width
5858
+ appcanvas.style.setProperty("--appcanvas-sidebar-width", `${width}px`)
5859
+ sidebarResizer.setAttribute("aria-valuenow", String(width))
5860
+ if (persist) {
5861
+ persistSidebarWidth(width)
5862
+ }
5863
+ }
5864
+
5865
+ const initSidebarWidth = () => {
5866
+ const stored = readSidebarWidth()
5867
+ const base = typeof stored === "number"
5868
+ ? stored
5869
+ : parseInt(getComputedStyle(appcanvas).getPropertyValue("--appcanvas-sidebar-width"), 10) || 150
5870
+ applySidebarWidth(base, false)
5871
+ }
5872
+
5873
+ const finishResizing = () => {
5874
+ if (!isResizing) {
5875
+ return
5876
+ }
5877
+ isResizing = false
5878
+ if (pointerId != null && sidebarResizer) {
5879
+ try {
5880
+ sidebarResizer.releasePointerCapture(pointerId)
5881
+ } catch (_) {}
5882
+ }
5883
+ pointerId = null
5884
+ resizeState = null
5885
+ window.removeEventListener("pointermove", handlePointerMove)
5886
+ window.removeEventListener("pointerup", handlePointerUp)
5887
+ if (sidebarWidth != null) {
5888
+ persistSidebarWidth(sidebarWidth)
5889
+ }
5890
+ }
5891
+
5892
+ const bindResizeListeners = () => {
5893
+ if (!handlePointerMove) {
5894
+ handlePointerMove = (event) => {
5895
+ if (!isResizing || !appcanvas.classList.contains("vertical")) {
5896
+ return
5897
+ }
5898
+ if (pointerId != null && event.pointerId !== pointerId) {
5899
+ return
5900
+ }
5901
+ event.preventDefault()
5902
+ const state = resizeState || {}
5903
+ const baseLeft = typeof state.left === "number"
5904
+ ? state.left
5905
+ : sidebarContainer.getBoundingClientRect().left
5906
+ const offset = typeof state.offset === "number" ? state.offset : 0
5907
+ const nextWidth = event.clientX - baseLeft - offset
5908
+ applySidebarWidth(nextWidth, false)
5909
+ }
5910
+ }
5911
+ if (!handlePointerUp) {
5912
+ handlePointerUp = (event) => {
5913
+ if (pointerId != null && event.pointerId !== pointerId) {
5914
+ return
5915
+ }
5916
+ finishResizing()
5917
+ }
5918
+ }
5919
+ window.addEventListener("pointermove", handlePointerMove)
5920
+ window.addEventListener("pointerup", handlePointerUp, { once: true })
5921
+ }
5922
+
5923
+ sidebarResizer.addEventListener("pointerdown", (event) => {
5924
+ if (!appcanvas.classList.contains("vertical")) {
5925
+ return
5926
+ }
5927
+ if (event.button !== 0 && event.pointerType !== "touch") {
5928
+ return
5929
+ }
5930
+ event.preventDefault()
5931
+ isResizing = true
5932
+ pointerId = event.pointerId
5933
+ try {
5934
+ sidebarResizer.setPointerCapture(event.pointerId)
5935
+ } catch (_) {}
5936
+ const rect = sidebarContainer.getBoundingClientRect()
5937
+ resizeState = {
5938
+ left: rect.left,
5939
+ offset: event.clientX - (rect.left + rect.width)
5940
+ }
5941
+ bindResizeListeners()
5942
+ })
5943
+
5944
+ initSidebarWidth()
5945
+ }
5946
+ }
5597
5947
  setupTabLinkHover()
5598
5948
  document.addEventListener("click", (event) => {
5599
5949
  if (event.target.closest("#fs-status .fs-dropdown-menu") || event.target.closest("#fs-status .fs-status-btn")) {
@@ -5654,7 +6004,7 @@ const rerenderMenuSection = (container, html) => {
5654
6004
  // KB
5655
6005
  val = `${Math.floor(res.du/k * 100) / 100} KB`
5656
6006
  }
5657
- el.innerHTML = val
6007
+ el.innerHTML = `<i class="fa-solid fa-database"></i> ${val}`
5658
6008
  } else {
5659
6009
  }
5660
6010
  })
@@ -5854,6 +6204,7 @@ const rerenderMenuSection = (container, html) => {
5854
6204
  let gitCommitUrl = null
5855
6205
  let activeRepoKey = null
5856
6206
  let gitStatusRequest = null
6207
+ let gitStatusRequestForce = false
5857
6208
 
5858
6209
  const fsStatusEl = document.querySelector('#fs-status')
5859
6210
  const changesDropdownContainer = document.querySelector('#fs-status .git-changes')
@@ -5885,6 +6236,15 @@ const rerenderMenuSection = (container, html) => {
5885
6236
  const historyUri = readDataAttr(fsStatusEl, 'data-history-uri')
5886
6237
  const defaultPushUri = readDataAttr(fsStatusEl, 'data-push-uri')
5887
6238
  const defaultForkUri = readDataAttr(fsStatusEl, 'data-fork-uri')
6239
+ const buildStatusUrl = (forceRefresh = false) => {
6240
+ if (!statusUri) {
6241
+ return null
6242
+ }
6243
+ if (!forceRefresh) {
6244
+ return statusUri
6245
+ }
6246
+ return statusUri.includes('?') ? `${statusUri}&force=1` : `${statusUri}?force=1`
6247
+ }
5888
6248
 
5889
6249
  const buildWorkspaceLogsUrl = () => {
5890
6250
  if (workspaceName && workspaceName.length > 0) {
@@ -6348,12 +6708,22 @@ const rerenderMenuSection = (container, html) => {
6348
6708
  }
6349
6709
  let repoData = repoStatusCache.get(repoParam) || null
6350
6710
  if (!repoData && typeof check_git === 'function') {
6351
- await check_git()
6711
+ await check_git(true)
6352
6712
  repoData = repoStatusCache.get(repoParam) || null
6353
6713
  }
6354
6714
  return repoData
6355
6715
  }
6356
6716
 
6717
+ async function refreshRepoData(repoParam) {
6718
+ if (!repoParam) {
6719
+ return null
6720
+ }
6721
+ if (typeof check_git === 'function') {
6722
+ await check_git(true)
6723
+ }
6724
+ return repoStatusCache.get(repoParam) || null
6725
+ }
6726
+
6357
6727
  const getRepoChangeCount = (repo) => {
6358
6728
  if (!repo) {
6359
6729
  return 0
@@ -6369,8 +6739,11 @@ const rerenderMenuSection = (container, html) => {
6369
6739
 
6370
6740
  async function ensureRemoteForPublish({ repoParam, repoName }) {
6371
6741
  const label = repoName || repoParam || 'Repository'
6372
- const initialRepo = await getRepoData(repoParam)
6373
- if (hasRemoteConfigured(initialRepo)) {
6742
+ let repoCandidate = await getRepoData(repoParam)
6743
+ if (!hasRemoteConfigured(repoCandidate)) {
6744
+ repoCandidate = await refreshRepoData(repoParam)
6745
+ }
6746
+ if (hasRemoteConfigured(repoCandidate)) {
6374
6747
  return { remoteReady: true, shouldOpenPublish: true }
6375
6748
  }
6376
6749
 
@@ -6381,7 +6754,7 @@ const rerenderMenuSection = (container, html) => {
6381
6754
  }
6382
6755
 
6383
6756
  if (typeof check_git === 'function') {
6384
- await check_git()
6757
+ await check_git(true)
6385
6758
  }
6386
6759
  const refreshedRepo = await getRepoData(repoParam)
6387
6760
  if (hasRemoteConfigured(refreshedRepo)) {
@@ -6468,7 +6841,7 @@ const rerenderMenuSection = (container, html) => {
6468
6841
  console.error('Failed to open commit modal before publish:', error)
6469
6842
  }
6470
6843
  if (typeof check_git === 'function') {
6471
- await check_git()
6844
+ await check_git(true)
6472
6845
  }
6473
6846
  return { proceed: true, action: 'commit', changeCount }
6474
6847
  }
@@ -6544,6 +6917,11 @@ const rerenderMenuSection = (container, html) => {
6544
6917
  }
6545
6918
 
6546
6919
  const runPublishFlow = async ({ repoParam, repoName }) => {
6920
+ if (typeof check_git === 'function') {
6921
+ try {
6922
+ await check_git(true)
6923
+ } catch (_) {}
6924
+ }
6547
6925
  let preflight = await fetchPublishPreflightState(repoParam)
6548
6926
  if (!preflight.ok) {
6549
6927
  handlePreflightFailure({ preflight, repoParam, repoName })
@@ -6783,24 +7161,29 @@ const rerenderMenuSection = (container, html) => {
6783
7161
  }
6784
7162
  }
6785
7163
 
6786
- const check_git = async () => {
7164
+ const check_git = async (forceRefresh = false) => {
6787
7165
  if (gitStatusRequest) {
7166
+ if (forceRefresh && !gitStatusRequestForce) {
7167
+ return gitStatusRequest.then(() => check_git(true))
7168
+ }
6788
7169
  return gitStatusRequest
6789
7170
  }
6790
7171
 
7172
+ const targetStatusUrl = buildStatusUrl(forceRefresh)
7173
+ gitStatusRequestForce = Boolean(forceRefresh)
6791
7174
  gitStatusRequest = (async () => {
6792
7175
  if (repoStatusCache.size === 0) {
6793
7176
  setChangesMenuMessage(statusUri ? 'Loading repositories...' : 'Loading changes...')
6794
7177
  updateChangesButtonState(false)
6795
7178
  }
6796
7179
 
6797
- if (!statusUri) {
7180
+ if (!targetStatusUrl) {
6798
7181
  await updateFromLegacyMonitor()
6799
7182
  return
6800
7183
  }
6801
7184
 
6802
7185
  try {
6803
- const response = await fetch(statusUri)
7186
+ const response = await fetch(targetStatusUrl)
6804
7187
  if (!response.ok) {
6805
7188
  throw new Error(`HTTP ${response.status}`)
6806
7189
  }
@@ -6857,6 +7240,7 @@ const rerenderMenuSection = (container, html) => {
6857
7240
  await gitStatusRequest
6858
7241
  } finally {
6859
7242
  gitStatusRequest = null
7243
+ gitStatusRequestForce = false
6860
7244
  }
6861
7245
  }
6862
7246
 
@@ -6922,7 +7306,7 @@ const rerenderMenuSection = (container, html) => {
6922
7306
  typeof event.data.callback === 'string' &&
6923
7307
  event.data.callback.startsWith('$')) {
6924
7308
  if (typeof check_git === 'function') {
6925
- check_git()
7309
+ check_git(true)
6926
7310
  }
6927
7311
  if (reopenDiffOnCallback && typeof showGitDiffModal === 'function') {
6928
7312
  reopenDiffOnCallback = false
@@ -7029,7 +7413,7 @@ const rerenderMenuSection = (container, html) => {
7029
7413
  }
7030
7414
 
7031
7415
  if (!repoParam) {
7032
- await check_git()
7416
+ await check_git(true)
7033
7417
  const fallback = repoStatusCache.values().next().value
7034
7418
  if (fallback) {
7035
7419
  repoParam = fallback.repoParam
@@ -7048,7 +7432,7 @@ const rerenderMenuSection = (container, html) => {
7048
7432
 
7049
7433
  let repoData = repoStatusCache.get(repoParam)
7050
7434
  if (!repoData) {
7051
- await check_git()
7435
+ await check_git(true)
7052
7436
  repoData = repoStatusCache.get(repoParam)
7053
7437
  }
7054
7438
 
@@ -8626,7 +9010,7 @@ const rerenderMenuSection = (container, html) => {
8626
9010
 
8627
9011
  let repo = repoStatusCache.get(key)
8628
9012
  if (!repo) {
8629
- await check_git()
9013
+ await check_git(true)
8630
9014
  repo = repoStatusCache.get(key)
8631
9015
  }
8632
9016
 
@@ -8864,7 +9248,7 @@ const rerenderMenuSection = (container, html) => {
8864
9248
  forkShellModalPromise.then(() => {
8865
9249
  setTimeout(() => {
8866
9250
  updateForkButton()
8867
- check_git()
9251
+ check_git(true)
8868
9252
  }, 250)
8869
9253
  })
8870
9254
  }
@@ -9033,6 +9417,39 @@ const rerenderMenuSection = (container, html) => {
9033
9417
  // Initialize the publish/create button
9034
9418
  updatePublishButton()
9035
9419
 
9420
+ // If this page was opened explicitly to publish from the Backups screen,
9421
+ // auto-open the publish flow after the button has been initialized.
9422
+ try {
9423
+ const searchParams = new URLSearchParams(window.location.search || "")
9424
+ if (searchParams.get("pinokio.publish") === "1" && pushBtn) {
9425
+ setTimeout(() => {
9426
+ try {
9427
+ pushBtn.click()
9428
+ setTimeout(() => {
9429
+ try {
9430
+ const menu = document.getElementById("fs-push-menu")
9431
+ if (!menu) return
9432
+ const items = menu.querySelectorAll(".pinokio-publish-dropdown-item")
9433
+ const target = Array.from(items).find((el) => {
9434
+ const dataName = (el.getAttribute("data-name") || "").trim()
9435
+ return dataName === workspaceName || dataName === (workspaceName + ".git")
9436
+ }) || items[0]
9437
+ if (target) {
9438
+ target.click()
9439
+ }
9440
+ } catch (_) {}
9441
+ }, 300)
9442
+ try {
9443
+ searchParams.delete("pinokio.publish")
9444
+ const qs = searchParams.toString()
9445
+ const newUrl = window.location.pathname + (qs ? `?${qs}` : "") + window.location.hash
9446
+ window.history.replaceState(null, "", newUrl)
9447
+ } catch (_) {}
9448
+ } catch (_) {}
9449
+ }, 500)
9450
+ }
9451
+ } catch (_) {}
9452
+
9036
9453
  check_git()
9037
9454
  setInterval(() => {
9038
9455
  if (typeof check_git === 'function') {
@@ -9228,5 +9645,356 @@ const rerenderMenuSection = (container, html) => {
9228
9645
  })()
9229
9646
  </script>
9230
9647
  <script src="/tab-idle-notifier.js"></script>
9648
+ <script>
9649
+ document.addEventListener("DOMContentLoaded", () => {
9650
+ const footer = document.querySelector(".snapshot-footer")
9651
+ if (!footer) return
9652
+ const saveBtn = footer.querySelector(".snapshot-btn-save")
9653
+ const savePanel = footer.querySelector(".snapshot-footer-save")
9654
+ const publishPanel = footer.querySelector(".snapshot-footer-publish")
9655
+ const publishBtn = footer.querySelector(".snapshot-btn-publish")
9656
+ const laterBtn = footer.querySelector(".snapshot-btn-later")
9657
+ const publishText = publishPanel ? publishPanel.querySelector(".snapshot-footer-publish-text") : null
9658
+ const publishActions = publishPanel ? publishPanel.querySelector(".snapshot-footer-actions") : null
9659
+ if (!saveBtn) return
9660
+ const registryBetaEnabled = footer.getAttribute("data-registry-beta-enabled") === "1"
9661
+ const workspace = footer.getAttribute("data-workspace")
9662
+ if (!workspace) {
9663
+ footer.classList.add("hidden")
9664
+ return
9665
+ }
9666
+ let savedSnapshotId = null
9667
+ const saveOriginal = saveBtn.innerHTML
9668
+ const publishOriginal = publishBtn ? publishBtn.innerHTML : ""
9669
+ const setSaveLoading = (isLoading, primaryText) => {
9670
+ saveBtn.disabled = isLoading
9671
+ if (primaryText) {
9672
+ saveBtn.innerHTML = primaryText
9673
+ }
9674
+ }
9675
+ const setPublishLoading = (isLoading, primaryText) => {
9676
+ if (publishBtn) publishBtn.disabled = isLoading
9677
+ if (laterBtn) laterBtn.disabled = isLoading
9678
+ if (primaryText && publishBtn) {
9679
+ publishBtn.innerHTML = primaryText
9680
+ }
9681
+ }
9682
+ const showPublishPanel = () => {
9683
+ if (!registryBetaEnabled) {
9684
+ footer.classList.add("hidden")
9685
+ return
9686
+ }
9687
+ if (publishPanel) publishPanel.classList.remove("hidden")
9688
+ if (savePanel) savePanel.classList.add("hidden")
9689
+ }
9690
+ const pendingSnapshotId = footer.getAttribute("data-pending-snapshot-id")
9691
+ if (pendingSnapshotId) {
9692
+ savedSnapshotId = pendingSnapshotId
9693
+ showPublishPanel()
9694
+ }
9695
+
9696
+ const waitForRegistryLink = async () => {
9697
+ const startedAt = Date.now()
9698
+ while (Date.now() - startedAt < 120000) {
9699
+ try {
9700
+ const s = await fetch("/api/registry/status", { method: "GET", headers: { "Accept": "application/json" } })
9701
+ if (s.ok) {
9702
+ const data = await s.json()
9703
+ if (data && data.linked) return true
9704
+ }
9705
+ } catch (_) {}
9706
+ await new Promise((r) => setTimeout(r, 2000))
9707
+ }
9708
+ return false
9709
+ }
9710
+
9711
+ const publishSnapshot = async (snapshotId) => {
9712
+ const qs = new URLSearchParams()
9713
+ qs.set("snapshotId", String(snapshotId))
9714
+ const res = await fetch(`/checkpoints/publish?${qs.toString()}`, { method: "POST", headers: { "Accept": "application/json" } })
9715
+ if (!res.ok) return { ok: false }
9716
+ try { return await res.json() } catch (_) { return { ok: false } }
9717
+ }
9718
+
9719
+ let isSaving = false
9720
+ let isPublishing = false
9721
+ const showPublishedLink = (publishUrl) => {
9722
+ showPublishPanel()
9723
+ if (publishText) publishText.textContent = "Published."
9724
+ if (publishActions) {
9725
+ publishActions.innerHTML = publishUrl
9726
+ ? `<a class="btn btn-primary" href="${escapePublishUrl(publishUrl)}" target="_blank" rel="noopener"><i class="fa-solid fa-arrow-up-right-from-square"></i> View published checkpoint</a>`
9727
+ : ''
9728
+ }
9729
+ }
9730
+ const escapePublishUrl = (value) => {
9731
+ if (value === null || value === undefined) {
9732
+ return ''
9733
+ }
9734
+ return String(value).replace(/[&<>"']/g, (match) => {
9735
+ switch (match) {
9736
+ case '&':
9737
+ return '&amp;'
9738
+ case '<':
9739
+ return '&lt;'
9740
+ case '>':
9741
+ return '&gt;'
9742
+ case '"':
9743
+ return '&quot;'
9744
+ case '\'':
9745
+ return '&#39;'
9746
+ default:
9747
+ return match
9748
+ }
9749
+ })
9750
+ }
9751
+
9752
+ const promptRegistryConnect = async (suggestedConnectUrl) => {
9753
+ const connectHtml = `
9754
+ <div class="pinokio-modal-surface">
9755
+ <div class="pinokio-modal-header">
9756
+ <div class="pinokio-modal-icon"><i class="fa-solid fa-link"></i></div>
9757
+ <div class="pinokio-modal-heading">
9758
+ <div class="pinokio-modal-title">Connect to Registry</div>
9759
+ <div class="pinokio-modal-subtitle">
9760
+ Enter your registry connect URL to link Pinokio, then we’ll retry the publish.
9761
+ </div>
9762
+ </div>
9763
+ </div>
9764
+ <div class="pinokio-modal-body">
9765
+ <div class="pinokio-modal-form">
9766
+ <div>
9767
+ <label class="pinokio-modal-label" for="pinokio-registry-connect-url">Registry URL</label>
9768
+ <input
9769
+ id="pinokio-registry-connect-url"
9770
+ class="pinokio-modal-input"
9771
+ type="url"
9772
+ placeholder="https://your-registry/connect/pinokio"
9773
+ autocomplete="off"
9774
+ />
9775
+ </div>
9776
+ </div>
9777
+ </div>
9778
+ </div>
9779
+ `
9780
+ const choice = await Swal.fire({
9781
+ html: connectHtml,
9782
+ backdrop: 'rgba(9,11,15,0.65)',
9783
+ width: 'min(520px, 92vw)',
9784
+ showCancelButton: true,
9785
+ showConfirmButton: true,
9786
+ confirmButtonText: "Open connect page",
9787
+ cancelButtonText: "Cancel",
9788
+ buttonsStyling: false,
9789
+ focusConfirm: false,
9790
+ customClass: {
9791
+ popup: 'pinokio-modern-modal',
9792
+ htmlContainer: 'pinokio-modern-html',
9793
+ closeButton: 'pinokio-modern-close',
9794
+ confirmButton: 'pinokio-modern-confirm',
9795
+ cancelButton: 'pinokio-modern-cancel'
9796
+ },
9797
+ didOpen: () => {
9798
+ const input = document.getElementById("pinokio-registry-connect-url")
9799
+ if (input) {
9800
+ input.value = suggestedConnectUrl || ""
9801
+ try { input.focus() } catch (_) {}
9802
+ try { input.select() } catch (_) {}
9803
+ }
9804
+ },
9805
+ preConfirm: () => {
9806
+ const input = document.getElementById("pinokio-registry-connect-url")
9807
+ const v = input && input.value != null ? String(input.value).trim() : ""
9808
+ if (!v) {
9809
+ Swal.showValidationMessage("Registry URL is required")
9810
+ return false
9811
+ }
9812
+ return v
9813
+ }
9814
+ })
9815
+ if (!choice || !choice.isConfirmed) return false
9816
+ const connectUrl = choice.value ? String(choice.value).trim() : ""
9817
+ if (!connectUrl) return false
9818
+ try {
9819
+ window.open(connectUrl, "pinokio-registry-connect")
9820
+ } catch (_) {
9821
+ window.location.href = connectUrl
9822
+ }
9823
+ const linked = await waitForRegistryLink()
9824
+ if (!linked) {
9825
+ Swal.fire({ icon: "error", title: "Not connected", text: "Could not confirm the registry connection." })
9826
+ return false
9827
+ }
9828
+ return true
9829
+ }
9830
+
9831
+ const handleSave = async (event) => {
9832
+ event.preventDefault()
9833
+ if (isSaving) return
9834
+ isSaving = true
9835
+ setSaveLoading(true, `<i class="fa-solid fa-circle-notch fa-spin"></i> Saving...`)
9836
+ try {
9837
+ const infoRes = await fetch(`/info/api/${encodeURIComponent(workspace)}`, {
9838
+ method: "GET",
9839
+ headers: { "Accept": "application/json" }
9840
+ })
9841
+ let repos = []
9842
+ if (infoRes.ok) {
9843
+ try {
9844
+ const payload = await infoRes.json()
9845
+ repos = Array.isArray(payload && payload.repos) ? payload.repos : []
9846
+ } catch (_) {}
9847
+ }
9848
+ const hasMainWithRemote = repos.some((repo) => repo && repo.main && repo.url)
9849
+ if (!hasMainWithRemote) {
9850
+ setSaveLoading(false, saveOriginal)
9851
+ const modalHtml = `
9852
+ <div class="pinokio-modal-surface">
9853
+ <div class="pinokio-modal-header">
9854
+ <div class="pinokio-modal-icon"><i class="fa-brands fa-github"></i></div>
9855
+ <div class="pinokio-modal-heading">
9856
+ <div class="pinokio-modal-title">Publish this workspace first</div>
9857
+ <div class="pinokio-modal-subtitle">
9858
+ Backups are indexed by git remote. Publish this workspace to GitHub (or add a remote) before creating a snapshot.
9859
+ </div>
9860
+ </div>
9861
+ </div>
9862
+ <div class="pinokio-modal-body">
9863
+ <p class="pinokio-modal-text">
9864
+ We couldn&apos;t find a Git remote for this workspace. Once you publish it, you&apos;ll be able to create snapshots and restore them from the Backups page.
9865
+ </p>
9866
+ </div>
9867
+ </div>
9868
+ `
9869
+ Swal.fire({
9870
+ html: modalHtml,
9871
+ customClass: {
9872
+ popup: 'pinokio-modern-modal',
9873
+ htmlContainer: 'pinokio-modern-html',
9874
+ closeButton: 'pinokio-modern-close',
9875
+ confirmButton: 'pinokio-modern-confirm',
9876
+ cancelButton: 'pinokio-modern-cancel'
9877
+ },
9878
+ backdrop: 'rgba(9,11,15,0.65)',
9879
+ width: 'min(520px, 90vw)',
9880
+ buttonsStyling: false,
9881
+ showCloseButton: false,
9882
+ showConfirmButton: true,
9883
+ showCancelButton: true,
9884
+ confirmButtonText: 'Open Publish',
9885
+ cancelButtonText: 'Cancel',
9886
+ focusConfirm: false,
9887
+ }).then((result) => {
9888
+ if (result && result.isConfirmed) {
9889
+ const params = new URLSearchParams(window.location.search || "")
9890
+ params.set("pinokio.publish", "1")
9891
+ const qs = params.toString()
9892
+ const url = `/p/${encodeURIComponent(workspace)}/dev${qs ? `?${qs}` : ""}`
9893
+ window.location.href = url
9894
+ }
9895
+ })
9896
+ return
9897
+ }
9898
+ const params = new URLSearchParams()
9899
+ params.set("workspace", workspace)
9900
+ const res = await fetch(`/checkpoints/snapshot?${params.toString()}`, {
9901
+ method: "POST",
9902
+ headers: { "Accept": "application/json" }
9903
+ })
9904
+ const payload = res.ok ? await res.json().catch(() => null) : null
9905
+ if (!payload || !payload.ok) {
9906
+ setSaveLoading(false, saveOriginal)
9907
+ Swal.fire({ icon: "error", title: "Error", text: "Save failed" })
9908
+ return
9909
+ }
9910
+ const snapshotId = payload && payload.created && payload.created.id ? payload.created.id : null
9911
+ if (!snapshotId) {
9912
+ setSaveLoading(false, saveOriginal)
9913
+ Swal.fire({ icon: "error", title: "Error", text: "Save failed" })
9914
+ return
9915
+ }
9916
+ savedSnapshotId = snapshotId
9917
+ setSaveLoading(false, saveOriginal)
9918
+ saveBtn.disabled = true
9919
+ showPublishPanel()
9920
+ } catch (_) {
9921
+ setSaveLoading(false, saveOriginal)
9922
+ Swal.fire({ icon: "error", title: "Error", text: "Save failed" })
9923
+ } finally {
9924
+ isSaving = false
9925
+ }
9926
+ }
9927
+
9928
+ const handlePublish = async (event) => {
9929
+ event.preventDefault()
9930
+ if (isPublishing || !savedSnapshotId) return
9931
+ isPublishing = true
9932
+ setPublishLoading(true, `<i class="fa-solid fa-circle-notch fa-spin"></i> Publishing...`)
9933
+ try {
9934
+ const published = await publishSnapshot(savedSnapshotId)
9935
+ if (published && published.publish && published.publish.ok) {
9936
+ const publishUrl = published && published.publish && published.publish.url ? String(published.publish.url) : ""
9937
+ showPublishedLink(publishUrl)
9938
+ return
9939
+ }
9940
+ if (published && published.publish && published.publish.code === "not_linked") {
9941
+ const suggestedConnectUrl = published.publish.connectUrl ? String(published.publish.connectUrl) : ""
9942
+ const linked = await promptRegistryConnect(suggestedConnectUrl)
9943
+ if (!linked) {
9944
+ setPublishLoading(false, publishOriginal)
9945
+ return
9946
+ }
9947
+ const retry = await publishSnapshot(savedSnapshotId)
9948
+ if (retry && retry.publish && retry.publish.ok) {
9949
+ footer.classList.add("hidden")
9950
+ return
9951
+ }
9952
+ const retryMsg = retry && retry.publish && retry.publish.error ? retry.publish.error : "Publish failed"
9953
+ Swal.fire({ icon: "error", title: "Error", text: retryMsg })
9954
+ setPublishLoading(false, publishOriginal)
9955
+ return
9956
+ }
9957
+ const msg = published && published.publish && published.publish.error ? published.publish.error : "Publish failed"
9958
+ Swal.fire({ icon: "error", title: "Error", text: msg })
9959
+ setPublishLoading(false, publishOriginal)
9960
+ } catch (_) {
9961
+ setPublishLoading(false, publishOriginal)
9962
+ Swal.fire({ icon: "error", title: "Error", text: "Publish failed" })
9963
+ } finally {
9964
+ isPublishing = false
9965
+ }
9966
+ }
9967
+
9968
+ const handleLater = async (event) => {
9969
+ event.preventDefault()
9970
+ if (!savedSnapshotId) {
9971
+ footer.classList.add("hidden")
9972
+ return
9973
+ }
9974
+ setPublishLoading(true)
9975
+ try {
9976
+ const qs = new URLSearchParams()
9977
+ qs.set("snapshotId", String(savedSnapshotId))
9978
+ qs.set("decision", "later")
9979
+ const res = await fetch(`/checkpoints/decision?${qs.toString()}`, {
9980
+ method: "POST",
9981
+ headers: { "Accept": "application/json" }
9982
+ })
9983
+ const payload = res.ok ? await res.json().catch(() => null) : null
9984
+ if (!payload || !payload.ok) {
9985
+ throw new Error("Failed to save decision")
9986
+ }
9987
+ footer.classList.add("hidden")
9988
+ } catch (_) {
9989
+ Swal.fire({ icon: "error", title: "Error", text: "Could not save decision" })
9990
+ setPublishLoading(false, publishOriginal)
9991
+ }
9992
+ }
9993
+
9994
+ saveBtn.addEventListener("click", handleSave)
9995
+ if (registryBetaEnabled && publishBtn) publishBtn.addEventListener("click", handlePublish)
9996
+ if (registryBetaEnabled && laterBtn) laterBtn.addEventListener("click", handleLater)
9997
+ })
9998
+ </script>
9231
9999
  </body>
9232
10000
  </html>