pinokiod 8.0.78 → 8.0.80

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.
@@ -402,6 +402,7 @@ const PAGE_SIZE = 500
402
402
  const state = {
403
403
  data: null,
404
404
  candidateSize: defaultCandidateSize,
405
+ persistedCandidateSize: defaultCandidateSize,
405
406
  candidateSizeInitialized: false,
406
407
  view: "all",
407
408
  sourceId: SCOPE_ID,
@@ -546,6 +547,52 @@ const post = async (payload) => {
546
547
  if (!result) throw new Error(COPY.action_request_failed.replace("{status}", response.status))
547
548
  return result
548
549
  }
550
+ let candidateSizeSaveTail = Promise.resolve(true)
551
+ let candidateSizeSaveGeneration = 0
552
+ const saveCandidateSize = (size) => {
553
+ if (!candidateSizeOptions.includes(size)) return candidateSizeSaveTail
554
+ const generation = ++candidateSizeSaveGeneration
555
+ state.candidateSize = size
556
+ state.candidateSizeInitialized = true
557
+ renderCandidateSizeControl()
558
+ const save = candidateSizeSaveTail.then(async () => {
559
+ const result = await post({
560
+ action: "set_candidate_size",
561
+ scope_id: SCOPE_ID,
562
+ candidate_size: size
563
+ })
564
+ if (result.error) throw new Error(result.error)
565
+ state.persistedCandidateSize = size
566
+ return true
567
+ })
568
+ candidateSizeSaveTail = save.catch(async (error) => {
569
+ const isCurrent = () => generation === candidateSizeSaveGeneration
570
+ if (isCurrent() && candidateSize() === size) {
571
+ state.candidateSizeInitialized = false
572
+ const refreshed = await refresh(true)
573
+ if (!refreshed && isCurrent() && !state.candidateSizeInitialized) {
574
+ state.candidateSize = state.persistedCandidateSize
575
+ state.candidateSizeInitialized = true
576
+ renderCandidateSizeControl()
577
+ }
578
+ }
579
+ if (isCurrent()) {
580
+ state.feedback = {
581
+ error: true,
582
+ message: error && error.message ? error.message : String(error)
583
+ }
584
+ renderFeedback()
585
+ }
586
+ return false
587
+ })
588
+ const pending = candidateSizeSaveTail
589
+ pending.finally(() => {
590
+ if (candidateSizeSaveTail === pending) {
591
+ candidateSizeSaveTail = Promise.resolve(true)
592
+ }
593
+ })
594
+ return candidateSizeSaveTail
595
+ }
549
596
  const openGlobalWorkspace = () => {
550
597
  window.parent.location.assign("/vault")
551
598
  }
@@ -2846,10 +2893,13 @@ const settleFolderDiscoveryStart = () => {
2846
2893
  }
2847
2894
  const applyFullData = (data) => {
2848
2895
  if (!state.candidateSizeInitialized) {
2849
- const published = Number(data && data.global_candidate_min_bytes)
2850
- state.candidateSize = candidateSizeOptions.includes(published)
2851
- ? published
2896
+ const saved = Number(data && data.candidate_min_bytes)
2897
+ const fallback = Number(data && data.global_candidate_min_bytes)
2898
+ const selected = candidateSizeOptions.includes(saved) ? saved : fallback
2899
+ state.candidateSize = candidateSizeOptions.includes(selected)
2900
+ ? selected
2852
2901
  : defaultCandidateSize
2902
+ state.persistedCandidateSize = state.candidateSize
2853
2903
  state.candidateSizeInitialized = true
2854
2904
  }
2855
2905
  const scanning = scanActive(data.scan)
@@ -3228,10 +3278,16 @@ const startFolderDiscovery = async (folderPath) => {
3228
3278
  renderFolderDiscovery()
3229
3279
  try {
3230
3280
  resetFolderDiscoveryChoices()
3281
+ if (!await candidateSizeSaveTail) {
3282
+ state.folderDiscoveryStarting = false
3283
+ state.folderDiscoveryChoosingRoot = true
3284
+ renderFolderDiscovery()
3285
+ focusFolderDiscoveryDialog()
3286
+ return
3287
+ }
3231
3288
  const result = await post({
3232
3289
  action: "find_folders",
3233
- path: folderPath,
3234
- candidate_size: candidateSize()
3290
+ path: folderPath
3235
3291
  })
3236
3292
  if (result.error) throw new Error(result.error)
3237
3293
  if (!result.started && !result.already_running) {
@@ -3354,10 +3410,8 @@ document.addEventListener("click", async (event) => {
3354
3410
  if (target.hasAttribute("data-candidate-size")) {
3355
3411
  const size = Number(target.dataset.candidateSize)
3356
3412
  if (candidateSizeOptions.includes(size)) {
3357
- state.candidateSize = size
3358
- state.candidateSizeInitialized = true
3359
3413
  closeScanSizeMenu()
3360
- renderCandidateSizeControl()
3414
+ await saveCandidateSize(size)
3361
3415
  }
3362
3416
  return
3363
3417
  }
@@ -3768,10 +3822,13 @@ document.addEventListener("click", async (event) => {
3768
3822
  state.scanPreviewOpen = false
3769
3823
  state.feedback = null
3770
3824
  try {
3825
+ if (!await candidateSizeSaveTail) {
3826
+ state.scanRequested = false
3827
+ return
3828
+ }
3771
3829
  const result = await post({
3772
3830
  action: "scan",
3773
- scope_id: SCOPE_ID,
3774
- candidate_size: candidateSize()
3831
+ scope_id: SCOPE_ID
3775
3832
  })
3776
3833
  if (result.error) throw new Error(result.error)
3777
3834
  await refresh()
@@ -4227,11 +4284,9 @@ document.addEventListener("change", async (event) => {
4227
4284
  if (["vault-candidate-size", "vault-find-candidate-size"]
4228
4285
  .includes(event.target.id)) {
4229
4286
  const size = Number(event.target.value)
4230
- state.candidateSize = candidateSizeOptions.includes(size)
4287
+ await saveCandidateSize(candidateSizeOptions.includes(size)
4231
4288
  ? size
4232
- : defaultCandidateSize
4233
- state.candidateSizeInitialized = true
4234
- renderCandidateSizeControl()
4289
+ : defaultCandidateSize)
4235
4290
  return
4236
4291
  }
4237
4292
  if (event.target.id !== "vault-status-filter") return
@@ -202,52 +202,148 @@ body.dark .app-header-info-popover-title {
202
202
  body.dark .app-header-info-popover-description {
203
203
  color: rgba(203, 213, 225, 0.78);
204
204
  }
205
- .app-header-info-popover-link,
206
- .app-header-info-popover-link--muted {
205
+ .app-header-info-popover-meta {
207
206
  margin-top: 12px;
208
- min-height: 32px;
209
- display: flex;
210
- align-items: center;
211
- justify-content: space-between;
212
- gap: 10px;
213
- padding: 0 10px;
214
- border-radius: 7px;
207
+ overflow: hidden;
215
208
  border: 1px solid rgba(15, 23, 42, 0.1);
209
+ border-radius: 7px;
216
210
  background: rgba(15, 23, 42, 0.035);
211
+ }
212
+ body.dark .app-header-info-popover-meta {
213
+ border-color: rgba(255, 255, 255, 0.1);
214
+ background: rgba(255, 255, 255, 0.06);
215
+ }
216
+ .app-header-info-popover-row {
217
+ min-height: 48px;
218
+ display: grid;
219
+ grid-template-columns: 26px minmax(0, 1fr) auto;
220
+ align-items: center;
221
+ gap: 10px;
222
+ padding: 7px 9px;
217
223
  color: rgba(15, 23, 42, 0.88);
218
- font-size: 12px;
219
- font-weight: 650;
220
- line-height: 1.2;
221
- text-decoration: none;
222
224
  box-sizing: border-box;
223
225
  }
224
- .app-header-info-popover-link:hover {
226
+ .app-header-info-popover-row + .app-header-info-popover-row {
227
+ border-top: 1px solid rgba(15, 23, 42, 0.1);
228
+ }
229
+ body.dark .app-header-info-popover-row {
230
+ color: rgba(248, 250, 252, 0.9);
231
+ }
232
+ body.dark .app-header-info-popover-row + .app-header-info-popover-row {
233
+ border-top-color: rgba(255, 255, 255, 0.1);
234
+ }
235
+ .app-header-info-popover-row--link {
236
+ text-decoration: none;
237
+ }
238
+ .app-header-info-popover-row--link:hover {
225
239
  background: rgba(15, 23, 42, 0.07);
226
240
  }
227
- .app-header-info-popover-link span {
241
+ body.dark .app-header-info-popover-row--link:hover {
242
+ background: rgba(255, 255, 255, 0.09);
243
+ }
244
+ .app-header-info-popover-row--link:focus-visible {
245
+ outline: 2px solid rgba(10, 132, 255, 0.55);
246
+ outline-offset: -2px;
247
+ }
248
+ .app-header-info-popover-row-icon {
249
+ width: 26px;
250
+ display: inline-flex;
251
+ align-items: center;
252
+ justify-content: center;
253
+ color: rgba(71, 85, 105, 0.78);
254
+ font-size: 18px;
255
+ }
256
+ body.dark .app-header-info-popover-row-icon {
257
+ color: rgba(226, 232, 240, 0.82);
258
+ }
259
+ .app-header-info-popover-row-copy {
228
260
  min-width: 0;
261
+ }
262
+ .app-header-info-popover-row-label {
263
+ display: block;
264
+ color: rgba(15, 23, 42, 0.9);
265
+ font-size: 12px;
266
+ font-weight: 700;
267
+ line-height: 1.2;
268
+ }
269
+ body.dark .app-header-info-popover-row-label {
270
+ color: rgba(248, 250, 252, 0.92);
271
+ }
272
+ .app-header-info-popover-row-value {
273
+ display: block;
274
+ margin-top: 3px;
275
+ color: rgba(71, 85, 105, 0.8);
276
+ font-size: 11px;
277
+ font-weight: 500;
278
+ line-height: 1.2;
229
279
  overflow: hidden;
230
280
  text-overflow: ellipsis;
231
281
  white-space: nowrap;
232
282
  }
233
- .app-header-info-popover-link:focus-visible {
283
+ body.dark .app-header-info-popover-row-value {
284
+ color: rgba(203, 213, 225, 0.76);
285
+ }
286
+ .app-header-info-popover-row-actions {
287
+ display: inline-flex;
288
+ align-items: center;
289
+ gap: 4px;
290
+ }
291
+ .app-header-info-popover-row-action {
292
+ appearance: none;
293
+ width: 28px;
294
+ height: 28px;
295
+ display: inline-flex;
296
+ align-items: center;
297
+ justify-content: center;
298
+ margin: 0;
299
+ padding: 0;
300
+ border: 1px solid rgba(15, 23, 42, 0.1);
301
+ border-radius: 6px;
302
+ background: rgba(255, 255, 255, 0.5);
303
+ color: rgba(71, 85, 105, 0.82);
304
+ font: inherit;
305
+ cursor: pointer;
306
+ }
307
+ .app-header-info-popover-row-action:hover {
308
+ border-color: rgba(15, 23, 42, 0.16);
309
+ background: rgba(15, 23, 42, 0.065);
310
+ color: rgba(15, 23, 42, 0.9);
311
+ }
312
+ .app-header-info-popover-row-action:focus-visible {
234
313
  outline: 2px solid rgba(10, 132, 255, 0.55);
235
314
  outline-offset: 2px;
236
315
  }
237
- body.dark .app-header-info-popover-link,
238
- body.dark .app-header-info-popover-link--muted {
316
+ .app-header-info-popover-row-action.is-copied {
317
+ color: #15803d;
318
+ }
319
+ body.dark .app-header-info-popover-row-action {
239
320
  border-color: rgba(255, 255, 255, 0.1);
240
- background: rgba(255, 255, 255, 0.06);
241
- color: rgba(248, 250, 252, 0.9);
321
+ background: rgba(255, 255, 255, 0.035);
322
+ color: rgba(226, 232, 240, 0.8);
242
323
  }
243
- body.dark .app-header-info-popover-link:hover {
324
+ body.dark .app-header-info-popover-row-action:hover {
325
+ border-color: rgba(255, 255, 255, 0.16);
244
326
  background: rgba(255, 255, 255, 0.09);
327
+ color: rgba(248, 250, 252, 0.94);
245
328
  }
246
- .app-header-info-popover-link--muted {
247
- justify-content: flex-start;
329
+ body.dark .app-header-info-popover-row-action.is-copied {
330
+ color: #86efac;
331
+ }
332
+ .app-header-info-popover-row-trailing {
333
+ width: 28px;
334
+ height: 28px;
335
+ display: inline-flex;
336
+ align-items: center;
337
+ justify-content: center;
338
+ color: rgba(71, 85, 105, 0.72);
339
+ }
340
+ body.dark .app-header-info-popover-row-trailing {
341
+ color: rgba(226, 232, 240, 0.76);
342
+ }
343
+ .app-header-info-popover-row--muted .app-header-info-popover-row-value {
248
344
  color: rgba(100, 116, 139, 0.78);
249
345
  }
250
- body.dark .app-header-info-popover-link--muted {
346
+ body.dark .app-header-info-popover-row--muted .app-header-info-popover-row-value {
251
347
  color: rgba(148, 163, 184, 0.86);
252
348
  }
253
349
  .app-header-identity-icon {
@@ -8281,6 +8377,10 @@ body.dark .pinokio-custom-terminal-header {
8281
8377
  <%
8282
8378
  const appInfoRemoteUrl = typeof launcher_remote_url === "string" ? launcher_remote_url : ""
8283
8379
  const appInfoRemoteDisplayUrl = appInfoRemoteUrl.replace(/^https?:\/\//i, "")
8380
+ const appInfoFolderPath = typeof path === "string" ? path : ""
8381
+ const appInfoFolderDisplayPath = typeof launcher_path_display === "string" && launcher_path_display
8382
+ ? launcher_path_display
8383
+ : appInfoFolderPath
8284
8384
  %>
8285
8385
  <body class='<%= [theme, "app-page"].filter(Boolean).join(" ") %>' data-platform="<%=platform%>" data-agent="<%=agent%>" data-view="<%=type%>">
8286
8386
  <header class='navheader grabbable'>
@@ -8327,14 +8427,43 @@ body.dark .pinokio-custom-terminal-header {
8327
8427
  <div class="app-header-info-popover-description"><%=config.description || 'No description provided.'%></div>
8328
8428
  </div>
8329
8429
  </div>
8330
- <% if (appInfoRemoteUrl) { %>
8331
- <a class="app-header-info-popover-link" href="<%=appInfoRemoteUrl%>" title="<%=appInfoRemoteUrl%>" rel="noopener noreferrer" data-app-info-external-link>
8332
- <span><%=appInfoRemoteDisplayUrl%></span>
8333
- <i class="fa-solid fa-arrow-up-right-from-square" aria-hidden="true"></i>
8334
- </a>
8335
- <% } else { %>
8336
- <div class="app-header-info-popover-link--muted">No browser-openable remote detected</div>
8337
- <% } %>
8430
+ <div class="app-header-info-popover-meta">
8431
+ <% if (appInfoFolderPath) { %>
8432
+ <div class="app-header-info-popover-row">
8433
+ <span class="app-header-info-popover-row-icon" aria-hidden="true"><i class="fa-regular fa-folder"></i></span>
8434
+ <span class="app-header-info-popover-row-copy">
8435
+ <span class="app-header-info-popover-row-label">App folder</span>
8436
+ <span class="app-header-info-popover-row-value" title="<%=appInfoFolderPath%>"><%=appInfoFolderDisplayPath%></span>
8437
+ </span>
8438
+ <span class="app-header-info-popover-row-actions">
8439
+ <button type="button" class="app-header-info-popover-row-action" title="Copy app folder" aria-label="Copy app folder" data-app-info-copy-path="<%=appInfoFolderPath%>">
8440
+ <i class="fa-regular fa-copy" aria-hidden="true"></i>
8441
+ </button>
8442
+ <button type="button" class="app-header-info-popover-row-action" title="Reveal app folder" aria-label="Reveal app folder" data-filepath="<%=appInfoFolderPath%>" data-app-info-reveal-path>
8443
+ <i class="fa-regular fa-folder-open" aria-hidden="true"></i>
8444
+ </button>
8445
+ </span>
8446
+ </div>
8447
+ <% } %>
8448
+ <% if (appInfoRemoteUrl) { %>
8449
+ <a class="app-header-info-popover-row app-header-info-popover-row--link" href="<%=appInfoRemoteUrl%>" title="<%=appInfoRemoteUrl%>" rel="noopener noreferrer" data-app-info-external-link>
8450
+ <span class="app-header-info-popover-row-icon" aria-hidden="true"><i class="fa-brands fa-github"></i></span>
8451
+ <span class="app-header-info-popover-row-copy">
8452
+ <span class="app-header-info-popover-row-label">Source</span>
8453
+ <span class="app-header-info-popover-row-value"><%=appInfoRemoteDisplayUrl%></span>
8454
+ </span>
8455
+ <span class="app-header-info-popover-row-trailing" aria-hidden="true"><i class="fa-solid fa-arrow-up-right-from-square"></i></span>
8456
+ </a>
8457
+ <% } else { %>
8458
+ <div class="app-header-info-popover-row app-header-info-popover-row--muted">
8459
+ <span class="app-header-info-popover-row-icon" aria-hidden="true"><i class="fa-brands fa-github"></i></span>
8460
+ <span class="app-header-info-popover-row-copy">
8461
+ <span class="app-header-info-popover-row-label">Source</span>
8462
+ <span class="app-header-info-popover-row-value">No browser-openable remote detected</span>
8463
+ </span>
8464
+ </div>
8465
+ <% } %>
8466
+ </div>
8338
8467
  </div>
8339
8468
  </div>
8340
8469
  <span class='app-header-identity-separator' aria-hidden="true"></span>
@@ -8913,14 +9042,43 @@ body.dark .pinokio-custom-terminal-header {
8913
9042
  <div class="app-header-info-popover-description"><%=config.description || 'No description provided.'%></div>
8914
9043
  </div>
8915
9044
  </div>
8916
- <% if (appInfoRemoteUrl) { %>
8917
- <a class="app-header-info-popover-link" href="<%=appInfoRemoteUrl%>" title="<%=appInfoRemoteUrl%>" rel="noopener noreferrer" data-app-info-external-link>
8918
- <span><%=appInfoRemoteDisplayUrl%></span>
8919
- <i class="fa-solid fa-arrow-up-right-from-square" aria-hidden="true"></i>
8920
- </a>
8921
- <% } else { %>
8922
- <div class="app-header-info-popover-link--muted">No browser-openable remote detected</div>
8923
- <% } %>
9045
+ <div class="app-header-info-popover-meta">
9046
+ <% if (appInfoFolderPath) { %>
9047
+ <div class="app-header-info-popover-row">
9048
+ <span class="app-header-info-popover-row-icon" aria-hidden="true"><i class="fa-regular fa-folder"></i></span>
9049
+ <span class="app-header-info-popover-row-copy">
9050
+ <span class="app-header-info-popover-row-label">App folder</span>
9051
+ <span class="app-header-info-popover-row-value" title="<%=appInfoFolderPath%>"><%=appInfoFolderDisplayPath%></span>
9052
+ </span>
9053
+ <span class="app-header-info-popover-row-actions">
9054
+ <button type="button" class="app-header-info-popover-row-action" title="Copy app folder" aria-label="Copy app folder" data-app-info-copy-path="<%=appInfoFolderPath%>">
9055
+ <i class="fa-regular fa-copy" aria-hidden="true"></i>
9056
+ </button>
9057
+ <button type="button" class="app-header-info-popover-row-action" title="Reveal app folder" aria-label="Reveal app folder" data-filepath="<%=appInfoFolderPath%>" data-app-info-reveal-path>
9058
+ <i class="fa-regular fa-folder-open" aria-hidden="true"></i>
9059
+ </button>
9060
+ </span>
9061
+ </div>
9062
+ <% } %>
9063
+ <% if (appInfoRemoteUrl) { %>
9064
+ <a class="app-header-info-popover-row app-header-info-popover-row--link" href="<%=appInfoRemoteUrl%>" title="<%=appInfoRemoteUrl%>" rel="noopener noreferrer" data-app-info-external-link>
9065
+ <span class="app-header-info-popover-row-icon" aria-hidden="true"><i class="fa-brands fa-github"></i></span>
9066
+ <span class="app-header-info-popover-row-copy">
9067
+ <span class="app-header-info-popover-row-label">Source</span>
9068
+ <span class="app-header-info-popover-row-value"><%=appInfoRemoteDisplayUrl%></span>
9069
+ </span>
9070
+ <span class="app-header-info-popover-row-trailing" aria-hidden="true"><i class="fa-solid fa-arrow-up-right-from-square"></i></span>
9071
+ </a>
9072
+ <% } else { %>
9073
+ <div class="app-header-info-popover-row app-header-info-popover-row--muted">
9074
+ <span class="app-header-info-popover-row-icon" aria-hidden="true"><i class="fa-brands fa-github"></i></span>
9075
+ <span class="app-header-info-popover-row-copy">
9076
+ <span class="app-header-info-popover-row-label">Source</span>
9077
+ <span class="app-header-info-popover-row-value">No browser-openable remote detected</span>
9078
+ </span>
9079
+ </div>
9080
+ <% } %>
9081
+ </div>
8924
9082
  </div>
8925
9083
  </div>
8926
9084
  <span class='mobile-bottom-nav__identity-separator' aria-hidden="true"></span>
@@ -16638,6 +16796,48 @@ const rerenderMenuSection = (container, html) => {
16638
16796
  popover.classList.toggle("hidden", isOpen)
16639
16797
  trigger.setAttribute("aria-expanded", isOpen ? "false" : "true")
16640
16798
  })
16799
+ root.querySelectorAll("[data-app-info-copy-path]").forEach((button) => {
16800
+ button.addEventListener("click", async (event) => {
16801
+ event.preventDefault()
16802
+ event.stopPropagation()
16803
+ const value = button.getAttribute("data-app-info-copy-path") || ""
16804
+ if (!value) {
16805
+ return
16806
+ }
16807
+ try {
16808
+ if (navigator.clipboard && typeof navigator.clipboard.writeText === "function") {
16809
+ await navigator.clipboard.writeText(value)
16810
+ } else {
16811
+ const input = document.createElement("textarea")
16812
+ input.value = value
16813
+ input.setAttribute("readonly", "")
16814
+ input.style.position = "fixed"
16815
+ input.style.opacity = "0"
16816
+ document.body.appendChild(input)
16817
+ input.select()
16818
+ document.execCommand("copy")
16819
+ input.remove()
16820
+ }
16821
+ const icon = button.querySelector("i")
16822
+ const originalIconClass = icon ? icon.className : ""
16823
+ button.classList.add("is-copied")
16824
+ button.setAttribute("title", "Copied")
16825
+ button.setAttribute("aria-label", "App folder copied")
16826
+ if (icon) icon.className = "fa-solid fa-check"
16827
+ window.setTimeout(() => {
16828
+ button.classList.remove("is-copied")
16829
+ button.setAttribute("title", "Copy app folder")
16830
+ button.setAttribute("aria-label", "Copy app folder")
16831
+ if (icon) icon.className = originalIconClass
16832
+ }, 1400)
16833
+ } catch (_) {}
16834
+ })
16835
+ })
16836
+ root.querySelectorAll("[data-app-info-reveal-path]").forEach((button) => {
16837
+ button.addEventListener("click", () => {
16838
+ closeAppInfoPopovers()
16839
+ })
16840
+ })
16641
16841
  root.querySelectorAll("[data-app-info-external-link]").forEach((link) => {
16642
16842
  link.addEventListener("click", (event) => {
16643
16843
  const href = link.getAttribute("href") || ""
@@ -998,9 +998,12 @@ test("app identity popover exposes launcher metadata and remote browser link", a
998
998
  assert.ok(helperIndex > existingCommunityGitConfigIndex)
999
999
  assert.ok(helperIndex < launcherRemoteIndex)
1000
1000
  assert.ok(renderPropIndex > launcherRemoteIndex)
1001
- assert.match(server, /kernel\.api\.parentGitURI\(this\.kernel\.path\("api", name\)\)/)
1001
+ assert.match(server, /const launcherPath = this\.kernel\.path\("api", name\)/)
1002
+ assert.match(server, /const launcherDisplayPath = this\.formatLogsDisplayPath\(launcherPath\)/)
1003
+ assert.match(server, /kernel\.api\.parentGitURI\(launcherPath\)/)
1002
1004
  assert.match(server, /launcherRemoteUrl = buildGitRemoteWebUrl\(launcherRemote\)/)
1003
1005
  assert.match(server, /launcher_remote_url: launcherRemoteUrl/)
1006
+ assert.match(server, /launcher_path_display: launcherDisplayPath/)
1004
1007
  assert.match(server, /const buildGitRemoteWebUrl = \(value\) =>/)
1005
1008
  assert.match(server, /return `https:\/\/\$\{sshUrlMatch\[1\]\}\/\$\{sshUrlMatch\[2\]\}`/)
1006
1009
  assert.match(server, /return `https:\/\/\$\{scpMatch\[1\]\}\/\$\{scpMatch\[2\]\}`/)
@@ -1011,8 +1014,17 @@ test("app identity popover exposes launcher metadata and remote browser link", a
1011
1014
  assert.match(appView, /class="app-header-info-popover-icon" src="<%=config\.icon \|\| '\/pinokio-black\.png'%>"/)
1012
1015
  assert.match(appView, /const appInfoRemoteUrl = typeof launcher_remote_url === "string" \? launcher_remote_url : ""/)
1013
1016
  assert.ok(appView.includes('const appInfoRemoteDisplayUrl = appInfoRemoteUrl.replace(/^https?:\\/\\//i, "")'))
1017
+ assert.match(appView, /const appInfoFolderPath = typeof path === "string" \? path : ""/)
1018
+ assert.match(appView, /const appInfoFolderDisplayPath = typeof launcher_path_display === "string"/)
1019
+ assert.match(appView, /data-app-info-copy-path="<%=appInfoFolderPath%>"/)
1020
+ assert.match(appView, /data-filepath="<%=appInfoFolderPath%>" data-app-info-reveal-path/)
1021
+ assert.match(appView, /fa-regular fa-folder/)
1022
+ assert.match(appView, /fa-brands fa-github/)
1023
+ assert.match(appView, /app-header-info-popover-row-label">App folder</)
1024
+ assert.match(appView, /app-header-info-popover-row-label">Source</)
1014
1025
  assert.match(appView, /href="<%=appInfoRemoteUrl%>" title="<%=appInfoRemoteUrl%>" rel="noopener noreferrer" data-app-info-external-link/)
1015
- assert.match(appView, /<span><%=appInfoRemoteDisplayUrl%><\/span>/)
1026
+ assert.match(appView, /app-header-info-popover-row-value"><%=appInfoRemoteDisplayUrl%><\/span>/)
1027
+ assert.match(appView, /grid-template-columns: 26px minmax\(0, 1fr\) auto/)
1016
1028
  assert.match(appView, /text-overflow: ellipsis/)
1017
1029
  assert.match(appView, /white-space: nowrap/)
1018
1030
  assert.match(appView, /window\.open\(href, "_blank"\)/)
@@ -1046,7 +1058,7 @@ test("app identity popover exposes launcher metadata and remote browser link", a
1046
1058
  assert.match(appView, /\.mobile-bottom-nav \.resource-usage-popover \{[\s\S]*bottom: calc\(100% \+ 9px\);/)
1047
1059
  assert.doesNotMatch(appView, /Open launcher remote/)
1048
1060
  assert.doesNotMatch(appView, /fetch\("\/go"/)
1049
- assert.doesNotMatch(appView, /app-header-info-popover-link"[^>]*features="browser"/)
1061
+ assert.doesNotMatch(appView, /data-app-info-external-link[^>]*features="browser"/)
1050
1062
  assert.doesNotMatch(appView, /appInfoGitHistoryUrl|hydrateAppInfoGithubUrl|buildAppInfoGithubUrl|launcher_github_url|Open launcher on GitHub|No GitHub remote detected/)
1051
1063
 
1052
1064
  const start = appView.indexOf('<script>\n(() => {\n const appInfoRoots = Array.from(document.querySelectorAll("[data-app-info-root]"))')
@@ -1059,7 +1071,9 @@ test("app identity popover exposes launcher metadata and remote browser link", a
1059
1071
  <div data-app-info-root>
1060
1072
  <button type="button" data-app-header-info-trigger aria-expanded="false">Demo</button>
1061
1073
  <div class="hidden" data-app-header-info-popover>
1062
- <a class="app-header-info-popover-link" href="https://gitlab.com/octocat/demo" title="https://gitlab.com/octocat/demo" data-app-info-external-link>gitlab.com/octocat/demo</a>
1074
+ <button type="button" data-app-info-copy-path="/tmp/demo"><i class="fa-regular fa-copy"></i></button>
1075
+ <button type="button" data-filepath="/tmp/demo" data-app-info-reveal-path>Reveal</button>
1076
+ <a class="app-header-info-popover-row app-header-info-popover-row--link" href="https://gitlab.com/octocat/demo" title="https://gitlab.com/octocat/demo" data-app-info-external-link>gitlab.com/octocat/demo</a>
1063
1077
  </div>
1064
1078
  </div>
1065
1079
  <iframe></iframe>
@@ -1069,6 +1083,13 @@ test("app identity popover exposes launcher metadata and remote browser link", a
1069
1083
  pretendToBeVisual: true
1070
1084
  })
1071
1085
  const openedUrls = []
1086
+ const copiedPaths = []
1087
+ Object.defineProperty(dom.window.navigator, "clipboard", {
1088
+ configurable: true,
1089
+ value: {
1090
+ writeText: async (value) => copiedPaths.push(value)
1091
+ }
1092
+ })
1072
1093
  dom.window.open = (url, target, features) => {
1073
1094
  openedUrls.push({ url, target, features })
1074
1095
  }
@@ -1080,7 +1101,7 @@ test("app identity popover exposes launcher metadata and remote browser link", a
1080
1101
 
1081
1102
  assert.equal(popover.classList.contains("hidden"), true)
1082
1103
  assert.equal(trigger.getAttribute("aria-expanded"), "false")
1083
- const remoteLink = dom.window.document.querySelector(".app-header-info-popover-link")
1104
+ const remoteLink = dom.window.document.querySelector(".app-header-info-popover-row--link")
1084
1105
  assert.ok(remoteLink)
1085
1106
  assert.equal(remoteLink.getAttribute("href"), "https://gitlab.com/octocat/demo")
1086
1107
  assert.equal(remoteLink.getAttribute("title"), "https://gitlab.com/octocat/demo")
@@ -1088,6 +1109,17 @@ test("app identity popover exposes launcher metadata and remote browser link", a
1088
1109
  assert.equal(remoteLink.hasAttribute("features"), false)
1089
1110
  assert.equal(remoteLink.textContent.trim(), "gitlab.com/octocat/demo")
1090
1111
 
1112
+ click()
1113
+ const copyButton = dom.window.document.querySelector("[data-app-info-copy-path]")
1114
+ copyButton.dispatchEvent(new dom.window.MouseEvent("click", { bubbles: true, cancelable: true }))
1115
+ await new Promise((resolve) => setImmediate(resolve))
1116
+ assert.deepEqual(copiedPaths, ["/tmp/demo"])
1117
+ assert.equal(copyButton.classList.contains("is-copied"), true)
1118
+ assert.equal(copyButton.getAttribute("aria-label"), "App folder copied")
1119
+ assert.equal(popover.classList.contains("hidden"), false)
1120
+ dom.window.document.querySelector("[data-app-info-reveal-path]").click()
1121
+ assert.equal(popover.classList.contains("hidden"), true)
1122
+
1091
1123
  click()
1092
1124
  assert.equal(popover.classList.contains("hidden"), false)
1093
1125
  assert.equal(trigger.getAttribute("aria-expanded"), "true")