pinokiod 7.5.51 → 7.5.53

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pinokiod",
3
- "version": "7.5.51",
3
+ "version": "7.5.53",
4
4
  "description": "",
5
5
  "main": "index.js",
6
6
  "scripts": {
@@ -1530,6 +1530,40 @@ body.main-sidebar-page aside.main-sidebar .tab.selected .main-sidebar-action-ico
1530
1530
  white-space: nowrap;
1531
1531
  word-wrap: normal !important;
1532
1532
  }
1533
+ .main-sidebar .main-sidebar-status-badge {
1534
+ grid-column: 3;
1535
+ justify-self: end;
1536
+ min-width: 28px;
1537
+ height: 15px;
1538
+ padding: 0 5px;
1539
+ box-sizing: border-box;
1540
+ border-radius: 999px;
1541
+ display: inline-flex;
1542
+ align-items: center;
1543
+ justify-content: center;
1544
+ background: rgba(15, 23, 42, 0.07);
1545
+ color: rgba(31, 41, 55, 0.6);
1546
+ font-size: 9px;
1547
+ font-weight: 700;
1548
+ line-height: 1;
1549
+ letter-spacing: 0;
1550
+ white-space: nowrap;
1551
+ }
1552
+ .main-sidebar .main-sidebar-status-badge[hidden] {
1553
+ display: none !important;
1554
+ }
1555
+ .main-sidebar .main-sidebar-status-badge[data-state="on"] {
1556
+ background: rgba(45, 180, 92, 0.14);
1557
+ color: #247a43;
1558
+ }
1559
+ body.dark .main-sidebar .main-sidebar-status-badge {
1560
+ background: rgba(255, 255, 255, 0.08);
1561
+ color: rgba(229, 231, 235, 0.68);
1562
+ }
1563
+ body.dark .main-sidebar .main-sidebar-status-badge[data-state="on"] {
1564
+ background: rgba(74, 222, 128, 0.14);
1565
+ color: #86efac;
1566
+ }
1533
1567
  .main-sidebar .tab.submenu {
1534
1568
  padding-left: 14px !important;
1535
1569
  }
@@ -85,7 +85,7 @@ body.app-page > header.navheader h1 > .home-server-popover {
85
85
  }
86
86
  .app-header-info {
87
87
  position: relative;
88
- flex: 1 1 auto;
88
+ flex: 0 1 auto;
89
89
  min-width: 0;
90
90
  display: inline-flex;
91
91
  align-items: center;
@@ -100,8 +100,7 @@ body.app-page > header.navheader h1 > .home-server-popover {
100
100
  align-items: center;
101
101
  gap: 8px;
102
102
  min-width: 0;
103
- width: 100%;
104
- max-width: none;
103
+ max-width: min(260px, 32vw);
105
104
  height: 22px;
106
105
  padding: 0 4px;
107
106
  margin: 0 -4px;
@@ -325,11 +324,20 @@ body.app-page header.navheader .nav-labeled-action:focus-visible {
325
324
  color: var(--pinokio-sidebar-tab-active-color);
326
325
  }
327
326
  @media only screen and (max-width: 1180px) {
328
- body.app-page header.navheader .app-header-identity-separator,
329
- body.app-page header.navheader .resource-chip--cpu,
330
- body.app-page header.navheader .resource-chip--ram,
331
- body.app-page header.navheader .resource-chip--vram {
332
- display: none !important;
327
+ body.app-page header.navheader .app-header-identity {
328
+ gap: 5px;
329
+ padding: 0 9px;
330
+ margin-right: 6px;
331
+ }
332
+ body.app-page header.navheader .app-header-info-trigger {
333
+ gap: 6px;
334
+ }
335
+ body.app-page header.navheader .resource-usage-trigger {
336
+ gap: 4px;
337
+ padding: 0 3px;
338
+ }
339
+ body.app-page header.navheader .resource-chip {
340
+ font-size: 10px;
333
341
  }
334
342
  }
335
343
  .resource-usage {
@@ -6884,10 +6892,10 @@ header.navheader h1 {
6884
6892
  min-width: 0;
6885
6893
  display: flex;
6886
6894
  align-items: center;
6887
- justify-content: flex-start;
6895
+ justify-content: center;
6888
6896
  align-content: center;
6889
6897
  flex-wrap: wrap;
6890
- gap: 2px 5px;
6898
+ gap: 2px 8px;
6891
6899
  min-height: 38px;
6892
6900
  padding: 2px 9px;
6893
6901
  border-radius: 999px;
@@ -6901,11 +6909,11 @@ header.navheader h1 {
6901
6909
  background: rgba(255,255,255,0.06);
6902
6910
  }
6903
6911
  .mobile-bottom-nav .app-header-info {
6904
- flex: 0 1 42%;
6905
- max-width: 42%;
6912
+ flex: 0 1 auto;
6913
+ max-width: min(260px, 42%);
6906
6914
  }
6907
6915
  .mobile-bottom-nav .app-header-info-trigger {
6908
- width: 100%;
6916
+ width: auto;
6909
6917
  max-width: 100%;
6910
6918
  height: auto;
6911
6919
  min-height: 18px;
@@ -6976,14 +6984,15 @@ header.navheader h1 {
6976
6984
  display: none;
6977
6985
  }
6978
6986
  .mobile-bottom-nav .resource-usage {
6979
- flex: 1 1 120px;
6980
- max-width: none;
6987
+ flex: 0 1 auto;
6988
+ max-width: 100%;
6981
6989
  }
6982
6990
  .mobile-bottom-nav .resource-usage-trigger {
6983
- width: 100%;
6991
+ width: auto;
6992
+ max-width: 100%;
6984
6993
  height: auto;
6985
6994
  min-height: 0;
6986
- justify-content: flex-start;
6995
+ justify-content: center;
6987
6996
  align-content: center;
6988
6997
  flex-wrap: wrap;
6989
6998
  gap: 2px 5px;
@@ -16623,8 +16632,10 @@ const rerenderMenuSection = (container, html) => {
16623
16632
  })
16624
16633
  aside.addEventListener("pointerleave", closePeekSoon)
16625
16634
  aside.addEventListener("focusout", closePeekSoon)
16626
- aside.addEventListener("click", () => {
16635
+ aside.addEventListener("click", (event) => {
16627
16636
  if (!isPeeking()) return
16637
+ const target = event.target
16638
+ if (target && target.closest && target.closest(".reveal, .revealer, [data-app-autolaunch-button]")) return
16628
16639
  window.setTimeout(() => setPeeking(false), 0)
16629
16640
  })
16630
16641
  document.addEventListener("pointerdown", (event) => {
@@ -999,6 +999,9 @@ body.dark .home-server-popover__qr-close:focus-visible {
999
999
  const render = (data) => {
1000
1000
  const status = data && data.status ? data.status : "off"
1001
1001
  setStatus(status)
1002
+ document.dispatchEvent(new CustomEvent("pinokio:home-server-status", {
1003
+ detail: { status, payload: data || { status } }
1004
+ }))
1002
1005
  if (!panel) {
1003
1006
  return
1004
1007
  }
@@ -35,7 +35,7 @@
35
35
 
36
36
  <div class='main-sidebar-section' aria-label="Configure">
37
37
  <div class="main-sidebar-section-title">Configure</div>
38
- <a href="/network" class="tab <%= sidebarSelected === 'network' ? 'selected' : '' %>"><i class="fa-solid fa-wifi"></i><div class='caption'>Home Server</div></a>
38
+ <a href="/network" class="tab <%= sidebarSelected === 'network' ? 'selected' : '' %>" data-main-sidebar-home-server-tab><i class="fa-solid fa-wifi"></i><div class='caption'>Home Server</div><span class="main-sidebar-status-badge" data-main-sidebar-home-server-status aria-hidden="true" hidden></span></a>
39
39
  <a class="tab <%= sidebarSelected === 'autolaunch' ? 'selected' : '' %>" href="/autolaunch"><i class="fa-solid fa-power-off"></i><div class='caption'>Autolaunch</div></a>
40
40
  <a href="/connect" class="tab <%= sidebarSelected === 'connect' ? 'selected' : '' %>"><i class="fa-solid fa-plug"></i><div class='caption'>Login</div></a>
41
41
  <a class="tab <%= sidebarSelected === 'settings' ? 'selected' : '' %>" href="/home?mode=settings"><i class="fa-solid fa-gear"></i><div class='caption'>Settings</div></a>
@@ -49,6 +49,51 @@
49
49
  const headerToggle = document.getElementById("minimize-header");
50
50
  const sidebar = document.getElementById("main-sidebar");
51
51
  const peekTrigger = document.querySelector("[data-main-sidebar-peek-trigger]");
52
+
53
+ const initHomeServerStatusBadge = () => {
54
+ if (!sidebar || window.__pinokioMainSidebarHomeServerStatusInit) return;
55
+ const homeServerTab = sidebar.querySelector("[data-main-sidebar-home-server-tab]");
56
+ const homeServerBadge = sidebar.querySelector("[data-main-sidebar-home-server-status]");
57
+ if (!homeServerTab || !homeServerBadge) return;
58
+
59
+ window.__pinokioMainSidebarHomeServerStatusInit = true;
60
+
61
+ const renderHomeServerStatus = (data) => {
62
+ const status = data && data.status ? String(data.status) : "off";
63
+ const badgeState = status === "on" ? "on" : "off";
64
+ const label = badgeState === "on" ? "ON" : "OFF";
65
+ homeServerBadge.textContent = label;
66
+ homeServerBadge.hidden = false;
67
+ homeServerBadge.dataset.state = badgeState;
68
+ homeServerTab.dataset.homeServerStatus = badgeState;
69
+ homeServerTab.setAttribute("aria-label", `Home Server, ${label}`);
70
+ homeServerTab.setAttribute("title", `Home Server ${label}`);
71
+ };
72
+
73
+ document.addEventListener("pinokio:home-server-status", (event) => {
74
+ const detail = event && event.detail ? event.detail : {};
75
+ renderHomeServerStatus(detail.payload || detail);
76
+ });
77
+
78
+ fetch("/info/home-server", { cache: "no-store" })
79
+ .then((response) => {
80
+ if (!response.ok) {
81
+ throw new Error(`Home Server status failed: ${response.status}`);
82
+ }
83
+ return response.json();
84
+ })
85
+ .then(renderHomeServerStatus)
86
+ .catch(() => {
87
+ homeServerBadge.hidden = true;
88
+ delete homeServerBadge.dataset.state;
89
+ delete homeServerTab.dataset.homeServerStatus;
90
+ homeServerTab.removeAttribute("aria-label");
91
+ homeServerTab.removeAttribute("title");
92
+ });
93
+ };
94
+
95
+ initHomeServerStatusBadge();
96
+
52
97
  if (body && sidebar && headerToggle && !headerToggle.dataset.mainSidebarHeaderToggle) {
53
98
  headerToggle.dataset.mainSidebarHeaderToggle = "true";
54
99
  headerToggle.classList.add("sidebar-toggle", "main-sidebar-header-toggle");
@@ -643,8 +643,23 @@ test("app navbar stays on one line when app identity and Home Server controls ar
643
643
  assert.match(appView, /body\.app-page > header\.navheader h1 > \.home,[\s\S]*body\.app-page > header\.navheader h1 > \.home-server-popover\s*\{[\s\S]*flex:\s*0 0 auto;/)
644
644
  assert.match(appView, /body\.app-page header\.navheader h1 > \.flexible\s*\{[\s\S]*flex:\s*0 0 0;[\s\S]*margin-left:\s*auto;/)
645
645
  assert.match(appView, /\.app-header-identity\s*\{[\s\S]*flex:\s*1 1 auto;[\s\S]*max-width:\s*none;/)
646
+ assert.match(appView, /\.app-header-info\s*\{[\s\S]*flex:\s*0 1 auto;/)
647
+ const identityTriggerStart = appView.indexOf(".app-header-info-trigger {")
648
+ const identityTriggerEnd = appView.indexOf("\n}", identityTriggerStart)
649
+ const identityTriggerRule = identityTriggerStart >= 0 && identityTriggerEnd > identityTriggerStart
650
+ ? appView.slice(identityTriggerStart, identityTriggerEnd)
651
+ : ""
652
+ assert.match(identityTriggerRule, /max-width:\s*min\(260px, 32vw\);/)
653
+ assert.doesNotMatch(identityTriggerRule, /width:\s*100%;/)
654
+ assert.doesNotMatch(identityTriggerRule, /max-width:\s*none;/)
646
655
  assert.doesNotMatch(appView, /nav-action-label\s*\{[^}]*display:\s*none;/)
647
- assert.match(appView, /@media only screen and \(max-width: 1180px\)\s*\{[\s\S]*\.resource-chip--vram[\s\S]*display:\s*none !important;/)
656
+ const compactRuleStart = appView.indexOf("@media only screen and (max-width: 1180px)")
657
+ const compactRuleEnd = appView.indexOf(".resource-usage {", compactRuleStart)
658
+ const compactHeaderRule = compactRuleStart >= 0 && compactRuleEnd > compactRuleStart
659
+ ? appView.slice(compactRuleStart, compactRuleEnd)
660
+ : ""
661
+ assert.match(compactHeaderRule, /body\.app-page header\.navheader \.resource-usage-trigger\s*\{[\s\S]*gap:\s*4px;[\s\S]*padding:\s*0 3px;/)
662
+ assert.doesNotMatch(compactHeaderRule, /\.resource-chip--(?:cpu|ram|vram)[\s\S]*display:\s*none !important;/)
648
663
  })
649
664
 
650
665
  test("download page header does not show browser chrome controls", async () => {
@@ -848,7 +863,7 @@ test("static guard: collapsed idle run page peeks without persisting sidebar sta
848
863
  assert.match(appView, /const hasVisibleBrowserSurface = \(\) => \{[\s\S]*browserview\.querySelector\("iframe:not\(\.hidden\)"\)[\s\S]*browserview-network-status[\s\S]*data-launch-requirements-status/)
849
864
  assert.match(appView, /if \(shouldAutoPeekOnIdle && initialCollapsed && !hasVisibleBrowserSurface\(\)\) \{[\s\S]*setPeeking\(true\)/)
850
865
  assert.match(appView, /setCollapsed\(initialCollapsed, \{ persist: false \}\)/)
851
- assert.match(appView, /aside\.addEventListener\("click", \(\) => \{[\s\S]*window\.setTimeout\(\(\) => setPeeking\(false\), 0\)/)
866
+ assert.match(appView, /aside\.addEventListener\("click", \(event\) => \{[\s\S]*target\.closest\("\.reveal, \.revealer, \[data-app-autolaunch-button\]"\)[\s\S]*window\.setTimeout\(\(\) => setPeeking\(false\), 0\)/)
852
867
  })
853
868
 
854
869
  test("app sidebar auto-peeks on collapsed idle run page and dismisses after click", async () => {
@@ -868,7 +883,12 @@ test("app sidebar auto-peeks on collapsed idle run page and dismisses after clic
868
883
  <button id="sidebar-toggle"></button>
869
884
  <div class="appcanvas vertical">
870
885
  <button type="button" data-app-sidebar-peek-trigger></button>
871
- <aside id="app-sidebar"><button type="button" id="sidebar-action">Start</button></aside>
886
+ <aside id="app-sidebar">
887
+ <button type="button" class="reveal" id="sidebar-reveal">Downloads</button>
888
+ <button type="button" class="revealer" id="sidebar-revealer">Changes</button>
889
+ <button type="button" data-app-autolaunch-button id="sidebar-autolaunch">Autolaunch</button>
890
+ <button type="button" id="sidebar-action">Start</button>
891
+ </aside>
872
892
  <main class="browserview">${browserSurface}</main>
873
893
  </div>
874
894
  <div id="browserview-network-status" hidden></div>
@@ -901,6 +921,18 @@ test("app sidebar auto-peeks on collapsed idle run page and dismisses after clic
901
921
  assert.equal(aside.getAttribute("aria-hidden"), "false")
902
922
  assert.equal(dom.window.localStorage.getItem(storageKey), "1")
903
923
 
924
+ aside.querySelector("#sidebar-reveal").dispatchEvent(new dom.window.MouseEvent("click", { bubbles: true }))
925
+ await wait(5)
926
+ assert.equal(appcanvas.classList.contains("sidebar-peeking"), true)
927
+
928
+ aside.querySelector("#sidebar-revealer").dispatchEvent(new dom.window.MouseEvent("click", { bubbles: true }))
929
+ await wait(5)
930
+ assert.equal(appcanvas.classList.contains("sidebar-peeking"), true)
931
+
932
+ aside.querySelector("#sidebar-autolaunch").dispatchEvent(new dom.window.MouseEvent("click", { bubbles: true }))
933
+ await wait(5)
934
+ assert.equal(appcanvas.classList.contains("sidebar-peeking"), true)
935
+
904
936
  aside.querySelector("#sidebar-action").dispatchEvent(new dom.window.MouseEvent("click", { bubbles: true }))
905
937
  await wait(5)
906
938
 
@@ -953,7 +985,30 @@ test("app identity popover exposes launcher metadata and remote browser link", a
953
985
  assert.match(appView, /window\.open\(href, "_blank"\)/)
954
986
  assert.match(appView, /window\.open\(href, "_blank", "browser"\)/)
955
987
  assert.match(appView, /No browser-openable remote detected/)
956
- assert.match(appView, /\.mobile-bottom-nav__identity \{[\s\S]*overflow: visible;/)
988
+ const cssRule = (selector) => {
989
+ const start = appView.indexOf(`${selector} {`)
990
+ if (start < 0) return ""
991
+ const tail = appView.slice(start)
992
+ const end = tail.match(/\n\s*\}/)
993
+ return end ? tail.slice(0, end.index + end[0].length) : ""
994
+ }
995
+ const mobileIdentityRule = cssRule(".mobile-bottom-nav__identity")
996
+ const mobileInfoRule = cssRule(".mobile-bottom-nav .app-header-info")
997
+ const mobileInfoTriggerRule = cssRule(".mobile-bottom-nav .app-header-info-trigger")
998
+ const mobileResourceRule = cssRule(".mobile-bottom-nav .resource-usage")
999
+ const mobileResourceTriggerRule = cssRule(".mobile-bottom-nav .resource-usage-trigger")
1000
+ assert.match(mobileIdentityRule, /justify-content:\s*center;/)
1001
+ assert.match(mobileIdentityRule, /overflow:\s*visible;/)
1002
+ assert.doesNotMatch(mobileIdentityRule, /justify-content:\s*flex-start;/)
1003
+ assert.match(mobileInfoRule, /flex:\s*0 1 auto;/)
1004
+ assert.match(mobileInfoRule, /max-width:\s*min\(260px, 42%\);/)
1005
+ assert.match(mobileInfoTriggerRule, /width:\s*auto;/)
1006
+ assert.doesNotMatch(mobileInfoTriggerRule, /(^|\n)\s*width:\s*100%;/)
1007
+ assert.match(mobileResourceRule, /flex:\s*0 1 auto;/)
1008
+ assert.match(mobileResourceRule, /max-width:\s*100%;/)
1009
+ assert.match(mobileResourceTriggerRule, /width:\s*auto;/)
1010
+ assert.match(mobileResourceTriggerRule, /justify-content:\s*center;/)
1011
+ assert.doesNotMatch(mobileResourceTriggerRule, /justify-content:\s*flex-start;/)
957
1012
  assert.match(appView, /\.mobile-bottom-nav \.app-header-info-popover \{[\s\S]*bottom: calc\(100% \+ 9px\);[\s\S]*width: min\(390px, calc\(100vw - 24px\)\);/)
958
1013
  assert.match(appView, /\.mobile-bottom-nav \.resource-usage-popover \{[\s\S]*bottom: calc\(100% \+ 9px\);/)
959
1014
  assert.doesNotMatch(appView, /Open launcher remote/)
@@ -21,11 +21,31 @@ test('main sidebar moves Home Server under Configure', async () => {
21
21
  assert.ok(configureIndex < homeServerIndex)
22
22
  assert.ok(homeServerIndex < autolaunchIndex)
23
23
  assert.match(source, /href="\/network"[\s\S]*Home Server/)
24
+ assert.match(source, /data-main-sidebar-home-server-tab/)
25
+ assert.match(source, /data-main-sidebar-home-server-status/)
26
+ assert.match(source, /fetch\("\/info\/home-server", \{ cache: "no-store" \}\)/)
27
+ assert.match(source, /status === "on" \? "on" : "off"/)
24
28
  assert.doesNotMatch(source, /aria-label="Computer"/)
25
29
  assert.doesNotMatch(source, />This machine</)
26
30
  assert.doesNotMatch(source, />Local network</)
27
31
  })
28
32
 
33
+ test('main sidebar styles the Home Server ON/OFF badge in the tab status column', async () => {
34
+ const style = await fs.readFile(path.resolve(__dirname, '..', 'server', 'public', 'style.css'), 'utf8')
35
+ const badgeRule = style.match(/\.main-sidebar \.main-sidebar-status-badge \{[\s\S]*?\n\}/)?.[0] || ''
36
+ const darkBadgeRule = style.match(/body\.dark \.main-sidebar \.main-sidebar-status-badge \{[\s\S]*?\n\}/)?.[0] || ''
37
+
38
+ assert.match(style, /\.main-sidebar \.main-sidebar-status-badge \{/)
39
+ assert.match(style, /grid-column:\s*3/)
40
+ assert.match(style, /\.main-sidebar \.main-sidebar-status-badge\[hidden\]/)
41
+ assert.match(style, /\.main-sidebar \.main-sidebar-status-badge\[data-state="on"\]/)
42
+ assert.match(style, /body\.dark \.main-sidebar \.main-sidebar-status-badge\[data-state="on"\]/)
43
+ assert.match(badgeRule, /background:\s*rgba\(15,\s*23,\s*42,\s*0\.07\)/)
44
+ assert.match(darkBadgeRule, /background:\s*rgba\(255,\s*255,\s*255,\s*0\.08\)/)
45
+ assert.doesNotMatch(`${badgeRule}\n${darkBadgeRule}`, /rgba\(207,\s*69,\s*69,\s*0\.12\)/)
46
+ assert.doesNotMatch(`${badgeRule}\n${darkBadgeRule}`, /#fca5a5/)
47
+ })
48
+
29
49
  test('main sidebar no longer renders peer rows or phone access modal', async () => {
30
50
  const source = await fs.readFile(sidebarFile, 'utf8')
31
51
  const style = await fs.readFile(path.resolve(__dirname, '..', 'server', 'public', 'style.css'), 'utf8')