pinokiod 7.5.26 → 7.5.28

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 (38) hide show
  1. package/kernel/index.js +2 -5
  2. package/kernel/info.js +20 -0
  3. package/kernel/shell.js +2 -54
  4. package/kernel/sysinfo.js +2 -2
  5. package/package.json +1 -1
  6. package/server/public/common.js +42 -0
  7. package/server/public/style.css +142 -14
  8. package/server/public/task-launcher.css +11 -0
  9. package/server/views/app.ejs +173 -15
  10. package/server/views/app_search_test.ejs +3 -0
  11. package/server/views/connect/x.ejs +3 -0
  12. package/server/views/container.ejs +3 -0
  13. package/server/views/download.ejs +3 -0
  14. package/server/views/explore.ejs +3 -0
  15. package/server/views/form.ejs +3 -0
  16. package/server/views/frame.ejs +3 -0
  17. package/server/views/help.ejs +3 -0
  18. package/server/views/index.ejs +21 -66
  19. package/server/views/index2.ejs +3 -0
  20. package/server/views/init/index.ejs +3 -0
  21. package/server/views/mini.ejs +3 -0
  22. package/server/views/net.ejs +3 -0
  23. package/server/views/network.ejs +3 -0
  24. package/server/views/network2.ejs +3 -0
  25. package/server/views/old_network.ejs +3 -0
  26. package/server/views/partials/app_navheader.ejs +3 -0
  27. package/server/views/partials/home_action_modal.ejs +18 -31
  28. package/server/views/partials/main_sidebar.ejs +101 -4
  29. package/server/views/prototype/index.ejs +3 -0
  30. package/server/views/review.ejs +3 -0
  31. package/server/views/screenshots.ejs +3 -0
  32. package/server/views/settings.ejs +3 -0
  33. package/server/views/setup_home.ejs +3 -0
  34. package/server/views/terminal.ejs +3 -0
  35. package/server/views/terminals.ejs +3 -0
  36. package/test/info-ready.test.js +88 -0
  37. package/test/launch-settings-ui.test.js +3 -0
  38. package/test/shell-cleaned-log.test.js +66 -0
@@ -35,6 +35,7 @@
35
35
  <i class="fa-solid fa-bars" aria-hidden="true"></i>
36
36
  </button>
37
37
  <button type="button" class="main-sidebar-mobile-backdrop" data-main-sidebar-backdrop aria-label="Close navigation" hidden></button>
38
+ <button type="button" class="main-sidebar-peek-trigger" data-main-sidebar-peek-trigger aria-label="Show navigation" aria-controls="main-sidebar" aria-expanded="false"></button>
38
39
  <aside class="main-sidebar" id="main-sidebar" aria-label="Primary navigation">
39
40
  <nav class="main-sidebar-nav" aria-label="Pinokio navigation">
40
41
  <div class='main-sidebar-section main-sidebar-section-actions' aria-label="Actions">
@@ -155,6 +156,7 @@
155
156
 
156
157
  const headerToggle = document.getElementById("minimize-header");
157
158
  const sidebar = document.getElementById("main-sidebar");
159
+ const peekTrigger = document.querySelector("[data-main-sidebar-peek-trigger]");
158
160
  if (body && sidebar && headerToggle && !headerToggle.dataset.mainSidebarHeaderToggle) {
159
161
  headerToggle.dataset.mainSidebarHeaderToggle = "true";
160
162
  headerToggle.classList.add("sidebar-toggle", "main-sidebar-header-toggle");
@@ -182,17 +184,66 @@
182
184
  }
183
185
  };
184
186
 
185
- const setCollapsed = (collapsed) => {
187
+ const desktopPeekMq = window.matchMedia("(min-width: 769px) and (hover: hover) and (pointer: fine)");
188
+ const storageKey = "pinokio.main-sidebar-collapsed";
189
+ let peekCloseTimer = 0;
190
+
191
+ const isCollapsed = () => body.classList.contains("main-sidebar-collapsed");
192
+ const isPeeking = () => body.classList.contains("main-sidebar-peeking");
193
+ const syncSidebarVisibility = () => {
194
+ const peeking = isPeeking();
195
+ sidebar.setAttribute("aria-hidden", isCollapsed() && !peeking ? "true" : "false");
196
+ if (peekTrigger) {
197
+ peekTrigger.setAttribute("aria-expanded", peeking ? "true" : "false");
198
+ }
199
+ };
200
+ const canPeek = () => desktopPeekMq.matches && isCollapsed() && !body.classList.contains("main-sidebar-mobile-open");
201
+ const setPeeking = (peeking) => {
202
+ const next = !!peeking && canPeek();
203
+ window.clearTimeout(peekCloseTimer);
204
+ body.classList.toggle("main-sidebar-peeking", next);
205
+ syncSidebarVisibility();
206
+ };
207
+ const shouldKeepPeekOpen = () => {
208
+ const active = document.activeElement;
209
+ return (
210
+ (peekTrigger && peekTrigger.matches(":hover")) ||
211
+ sidebar.matches(":hover") ||
212
+ (peekTrigger && active === peekTrigger) ||
213
+ (active instanceof Node && sidebar.contains(active))
214
+ );
215
+ };
216
+ const closePeekSoon = () => {
217
+ window.clearTimeout(peekCloseTimer);
218
+ peekCloseTimer = window.setTimeout(() => {
219
+ if (shouldKeepPeekOpen()) return;
220
+ setPeeking(false);
221
+ }, 120);
222
+ };
223
+
224
+ const setCollapsed = (collapsed, options = {}) => {
186
225
  const next = !!collapsed;
226
+ if (!next) {
227
+ body.classList.remove("main-sidebar-peeking");
228
+ }
187
229
  body.classList.toggle("main-sidebar-collapsed", next);
188
- sidebar.setAttribute("aria-hidden", next ? "true" : "false");
230
+ syncSidebarVisibility();
189
231
  updateToggle(next);
232
+ if (options.persist !== false) {
233
+ try {
234
+ window.localStorage.setItem(storageKey, next ? "1" : "0");
235
+ } catch (_) {}
236
+ }
190
237
  document.dispatchEvent(new CustomEvent("pinokio:sidebar-state", {
191
238
  detail: { collapsed: next, source: "main-sidebar" }
192
239
  }));
193
240
  };
194
241
 
195
- setCollapsed(false);
242
+ let initialCollapsed = false;
243
+ try {
244
+ initialCollapsed = window.localStorage.getItem(storageKey) === "1";
245
+ } catch (_) {}
246
+ setCollapsed(initialCollapsed, { persist: false });
196
247
  requestAnimationFrame(() => {
197
248
  body.classList.add("main-sidebar-motion-ready");
198
249
  });
@@ -202,9 +253,55 @@
202
253
  setCollapsed(!body.classList.contains("main-sidebar-collapsed"));
203
254
  });
204
255
 
256
+ if (peekTrigger) {
257
+ peekTrigger.addEventListener("pointerenter", () => setPeeking(true));
258
+ peekTrigger.addEventListener("focus", () => setPeeking(true));
259
+ peekTrigger.addEventListener("click", () => setPeeking(true));
260
+ peekTrigger.addEventListener("pointerleave", closePeekSoon);
261
+ peekTrigger.addEventListener("blur", closePeekSoon);
262
+ sidebar.addEventListener("pointerenter", () => {
263
+ if (isPeeking()) {
264
+ window.clearTimeout(peekCloseTimer);
265
+ }
266
+ });
267
+ sidebar.addEventListener("focusin", () => {
268
+ if (isPeeking()) {
269
+ window.clearTimeout(peekCloseTimer);
270
+ }
271
+ });
272
+ sidebar.addEventListener("pointerleave", closePeekSoon);
273
+ sidebar.addEventListener("focusout", closePeekSoon);
274
+ document.addEventListener("pointerdown", (event) => {
275
+ if (!isPeeking()) return;
276
+ const target = event.target;
277
+ if (target instanceof Node && (sidebar.contains(target) || peekTrigger.contains(target))) return;
278
+ setPeeking(false);
279
+ });
280
+ document.addEventListener("keydown", (event) => {
281
+ if (event.key === "Escape" && isPeeking()) {
282
+ setPeeking(false);
283
+ if (typeof peekTrigger.focus === "function") {
284
+ peekTrigger.focus();
285
+ }
286
+ }
287
+ });
288
+ const handlePeekMediaChange = () => {
289
+ if (!desktopPeekMq.matches) {
290
+ setPeeking(false);
291
+ }
292
+ };
293
+ if (typeof desktopPeekMq.addEventListener === "function") {
294
+ desktopPeekMq.addEventListener("change", handlePeekMediaChange);
295
+ } else if (typeof desktopPeekMq.addListener === "function") {
296
+ desktopPeekMq.addListener(handlePeekMediaChange);
297
+ }
298
+ }
299
+
205
300
  window.PinokioMainSidebar = {
206
301
  setCollapsed,
207
- isCollapsed: () => body.classList.contains("main-sidebar-collapsed")
302
+ setPeeking,
303
+ isCollapsed,
304
+ isPeeking
208
305
  };
209
306
  }
210
307
 
@@ -1010,6 +1010,9 @@ body.dark .appcanvas {
1010
1010
  <button class='btn2' id='minimize-header' data-tippy-content="fullscreen" title='fullscreen'>
1011
1011
  <div><i class="fa-solid fa-expand"></i></div>
1012
1012
  </button>
1013
+ <a class='btn2 pinokio-explore-nav-button' href="/home?mode=explore" data-pinokio-explore-nav="true" data-tippy-content="Explore" title="Explore" aria-label="Explore">
1014
+ <div><i class="fa-solid fa-globe" aria-hidden="true"></i></div>
1015
+ </a>
1013
1016
  <button class='btn2' id='back' data-tippy-content="back"><div><i class="fa-solid fa-chevron-left"></i></div></button>
1014
1017
  <button class='btn2' id='forward' data-tippy-content="forward"><div><i class="fa-solid fa-chevron-right"></i></div></button>
1015
1018
  <button class='btn2' id='refresh-page' data-tippy-content="refresh"><div><i class="fa-solid fa-rotate-right"></i></div></button>
@@ -941,6 +941,9 @@ body.dark .top-menu .btn2.selected {
941
941
  <button class='btn2' id='minimize-header' data-tippy-content="fullscreen" title='fullscreen'>
942
942
  <div><i class="fa-solid fa-expand"></i></div>
943
943
  </button>
944
+ <a class='btn2 pinokio-explore-nav-button' href="/home?mode=explore" data-pinokio-explore-nav="true" data-tippy-content="Explore" title="Explore" aria-label="Explore">
945
+ <div><i class="fa-solid fa-globe" aria-hidden="true"></i></div>
946
+ </a>
944
947
  <button class='btn2' id='back' data-tippy-content="back"><div><i class="fa-solid fa-chevron-left"></i></div></button>
945
948
  <button class='btn2' id='forward' data-tippy-content="forward"><div><i class="fa-solid fa-chevron-right"></i></div></button>
946
949
  <button class='btn2' id='refresh-page' data-tippy-content="refresh"><div><i class="fa-solid fa-rotate-right"></i></div></button>
@@ -873,6 +873,9 @@ document.addEventListener('DOMContentLoaded', function() {
873
873
  <button class='btn2' id='minimize-header' data-tippy-content="fullscreen" title='fullscreen'>
874
874
  <div><i class="fa-solid fa-expand"></i></div>
875
875
  </button>
876
+ <a class='btn2 pinokio-explore-nav-button' href="/home?mode=explore" data-pinokio-explore-nav="true" data-tippy-content="Explore" title="Explore" aria-label="Explore">
877
+ <div><i class="fa-solid fa-globe" aria-hidden="true"></i></div>
878
+ </a>
876
879
  <button class='btn2' id='back' data-tippy-content="back"><div><i class="fa-solid fa-chevron-left"></i></div></button>
877
880
  <button class='btn2' id='forward' data-tippy-content="forward"><div><i class="fa-solid fa-chevron-right"></i></div></button>
878
881
  <button class='btn2' id='refresh-page' data-tippy-content="refresh"><div><i class="fa-solid fa-rotate-right"></i></div></button>
@@ -1011,6 +1011,9 @@ document.addEventListener('DOMContentLoaded', function() {
1011
1011
  <button class='btn2' id='minimize-header' data-tippy-content="fullscreen" title='fullscreen'>
1012
1012
  <div><i class="fa-solid fa-expand"></i></div>
1013
1013
  </button>
1014
+ <a class='btn2 pinokio-explore-nav-button' href="/home?mode=explore" data-pinokio-explore-nav="true" data-tippy-content="Explore" title="Explore" aria-label="Explore">
1015
+ <div><i class="fa-solid fa-globe" aria-hidden="true"></i></div>
1016
+ </a>
1014
1017
  <button class='btn2' id='back' data-tippy-content="back"><div><i class="fa-solid fa-chevron-left"></i></div></button>
1015
1018
  <button class='btn2' id='forward' data-tippy-content="forward"><div><i class="fa-solid fa-chevron-right"></i></div></button>
1016
1019
  <button class='btn2' id='refresh-page' data-tippy-content="refresh"><div><i class="fa-solid fa-rotate-right"></i></div></button>
@@ -155,6 +155,9 @@ body.dark .card {
155
155
  <button class='btn2' id='minimize-header' data-tippy-content="fullscreen" title='fullscreen'>
156
156
  <div><i class="fa-solid fa-expand"></i></div>
157
157
  </button>
158
+ <a class='btn2 pinokio-explore-nav-button' href="/home?mode=explore" data-pinokio-explore-nav="true" data-tippy-content="Explore" title="Explore" aria-label="Explore">
159
+ <div><i class="fa-solid fa-globe" aria-hidden="true"></i></div>
160
+ </a>
158
161
  <button class='btn2' id='screenshot' data-tippy-content="screen capture"><i class="fa-solid fa-camera"></i></button>
159
162
  <button class='btn2' id='inspector' data-tippy-content="X-ray mode"><i class="fa-solid fa-eye"></i></button>
160
163
  <div class='flexible'></div>
@@ -3403,6 +3403,9 @@ const reloadMemory = async () => {
3403
3403
  <button class="btn2" id="minimize-header" data-tippy-content="fullscreen" title="fullscreen">
3404
3404
  <div><i class="fa-solid fa-expand"></i></div>
3405
3405
  </button>
3406
+ <a class='btn2 pinokio-explore-nav-button' href="/home?mode=explore" data-pinokio-explore-nav="true" data-tippy-content="Explore" title="Explore" aria-label="Explore">
3407
+ <div><i class="fa-solid fa-globe" aria-hidden="true"></i></div>
3408
+ </a>
3406
3409
  <button class="btn2" id="back" data-tippy-content="back"><div><i class="fa-solid fa-chevron-left"></i></div></button>
3407
3410
  <button class="btn2" id="forward" data-tippy-content="forward"><div><i class="fa-solid fa-chevron-right"></i></div></button>
3408
3411
  <button class="btn2" id="refresh-page" data-tippy-content="refresh"><div><i class="fa-solid fa-rotate-right"></i></div></button>
@@ -4554,6 +4554,9 @@ body.dark .pinokio-git-commit-item {
4554
4554
  <button class="btn2" id="minimize-header" data-tippy-content="fullscreen" title="fullscreen">
4555
4555
  <div><i class="fa-solid fa-expand"></i></div>
4556
4556
  </button>
4557
+ <a class='btn2 pinokio-explore-nav-button' href="/home?mode=explore" data-pinokio-explore-nav="true" data-tippy-content="Explore" title="Explore" aria-label="Explore">
4558
+ <div><i class="fa-solid fa-globe" aria-hidden="true"></i></div>
4559
+ </a>
4557
4560
  <button class="btn2" id="back" data-tippy-content="back"><div><i class="fa-solid fa-chevron-left"></i></div></button>
4558
4561
  <button class="btn2" id="forward" data-tippy-content="forward"><div><i class="fa-solid fa-chevron-right"></i></div></button>
4559
4562
  <button class="btn2" id="refresh-page" data-tippy-content="refresh"><div><i class="fa-solid fa-rotate-right"></i></div></button>
@@ -0,0 +1,88 @@
1
+ const assert = require("node:assert/strict")
2
+ const fs = require("node:fs")
3
+ const os = require("node:os")
4
+ const path = require("node:path")
5
+ const test = require("node:test")
6
+
7
+ const Info = require("../kernel/info")
8
+
9
+ const createInfo = ({ callerFile, scriptResolve, readyPaths = [] }) => {
10
+ const ready = new Set(readyPaths.map((item) => path.resolve(item)))
11
+ const info = new Info({
12
+ script: {
13
+ resolve: scriptResolve || (() => false)
14
+ },
15
+ isScriptReady: (scriptPath) => ready.has(path.resolve(scriptPath))
16
+ })
17
+ info.caller = () => callerFile
18
+ return info
19
+ }
20
+
21
+ test("info.ready checks caller-relative script paths first", (t) => {
22
+ const root = fs.mkdtempSync(path.join(os.tmpdir(), "pinokio-info-ready-"))
23
+ t.after(() => fs.rmSync(root, { recursive: true, force: true }))
24
+
25
+ const appRoot = path.join(root, "app")
26
+ const callerFile = path.join(appRoot, "pinokio.js")
27
+ const startScript = path.join(appRoot, "start.js")
28
+ fs.mkdirSync(appRoot, { recursive: true })
29
+ fs.writeFileSync(callerFile, "module.exports = {}")
30
+ fs.writeFileSync(startScript, "module.exports = {}")
31
+
32
+ let resolverCalls = 0
33
+ const info = createInfo({
34
+ callerFile,
35
+ scriptResolve: () => {
36
+ resolverCalls += 1
37
+ return false
38
+ },
39
+ readyPaths: [startScript]
40
+ })
41
+
42
+ assert.equal(info.ready("start.js"), true)
43
+ assert.equal(resolverCalls, 0)
44
+ })
45
+
46
+ test("info.ready falls back to Pinokio script URI resolution", (t) => {
47
+ const root = fs.mkdtempSync(path.join(os.tmpdir(), "pinokio-info-ready-"))
48
+ t.after(() => fs.rmSync(root, { recursive: true, force: true }))
49
+
50
+ const callerFile = path.join(root, "caller", "pinokio.js")
51
+ const remoteRoot = path.join(root, "api", "remote.git")
52
+ const remoteStart = path.join(remoteRoot, "start.js")
53
+ const remoteUri = "https://github.com/example/remote.git"
54
+ fs.mkdirSync(path.dirname(callerFile), { recursive: true })
55
+ fs.mkdirSync(remoteRoot, { recursive: true })
56
+ fs.writeFileSync(callerFile, "module.exports = {}")
57
+ fs.writeFileSync(remoteStart, "module.exports = {}")
58
+
59
+ const info = createInfo({
60
+ callerFile,
61
+ scriptResolve: (...args) => {
62
+ if (args[0] === remoteUri) {
63
+ return path.resolve(remoteRoot, ...args.slice(1))
64
+ }
65
+ return false
66
+ },
67
+ readyPaths: [remoteStart]
68
+ })
69
+
70
+ assert.equal(info.ready(remoteUri, "start.js"), true)
71
+ })
72
+
73
+ test("info.ready returns false when neither relative nor URI path is ready", (t) => {
74
+ const root = fs.mkdtempSync(path.join(os.tmpdir(), "pinokio-info-ready-"))
75
+ t.after(() => fs.rmSync(root, { recursive: true, force: true }))
76
+
77
+ const appRoot = path.join(root, "app")
78
+ const callerFile = path.join(appRoot, "pinokio.js")
79
+ fs.mkdirSync(appRoot, { recursive: true })
80
+ fs.writeFileSync(callerFile, "module.exports = {}")
81
+
82
+ const info = createInfo({
83
+ callerFile,
84
+ scriptResolve: () => false
85
+ })
86
+
87
+ assert.equal(info.ready("missing.js"), false)
88
+ })
@@ -369,6 +369,9 @@ test("static guard: open without launching is not wired to launch requirement st
369
369
 
370
370
  assert.match(homeActionModal, /Open without launching/)
371
371
  assert.match(homeActionModal, /class='home-mode-command home-browse' data-src='<%= item\.view_url %>'/)
372
+ assert.match(homeActionModal, /Open dev mode/)
373
+ assert.doesNotMatch(homeActionModal, /home-actions-tabs|home-actions-tab|role='tab'|role='tabpanel'/)
374
+ assert.doesNotMatch(homeActionModal, /Open files mode/)
372
375
  assert.doesNotMatch(homeActionModal, /launch.requirements|data-launch-requirements|frame-link/)
373
376
  assert.match(homeView, /if \(target\) \{[\s\S]*window\.PinokioHomeGuardNavigate\(src\)[\s\S]*location\.href = src/)
374
377
  assert.match(statusClient, /const status = data && data\.ok \? data\.status : null/)
@@ -0,0 +1,66 @@
1
+ const assert = require('node:assert/strict')
2
+ const fs = require('node:fs/promises')
3
+ const os = require('node:os')
4
+ const path = require('node:path')
5
+ const test = require('node:test')
6
+
7
+ const Kernel = require('../kernel')
8
+
9
+ async function exists(targetPath) {
10
+ try {
11
+ await fs.access(targetPath)
12
+ return true
13
+ } catch (_) {
14
+ return false
15
+ }
16
+ }
17
+
18
+ test('kernel shell logs write cleaned output without creating info logs', async () => {
19
+ const root = await fs.mkdtemp(path.join(os.tmpdir(), 'pinokio-shell-cleaned-log-'))
20
+ try {
21
+ const kernel = Object.create(Kernel.prototype)
22
+ kernel.homedir = root
23
+
24
+ const group = path.join(root, 'api', 'demo', 'start.js')
25
+ await Kernel.prototype._log.call(kernel, {
26
+ cleaned: 'hello from cleaned log\n'
27
+ }, group, {
28
+ index: 3
29
+ })
30
+
31
+ const relativeGroup = path.relative(root, group)
32
+ const cleanedLog = path.join(root, 'logs', 'shell', 'cleaned', `${relativeGroup}.3.txt`)
33
+ const infoLog = path.join(root, 'logs', 'shell', 'info', `${relativeGroup}.3.txt`)
34
+
35
+ assert.equal(await exists(cleanedLog), true)
36
+ assert.equal(await fs.readFile(cleanedLog, 'utf8'), 'hello from cleaned log\n')
37
+ assert.equal(await exists(infoLog), false)
38
+ } finally {
39
+ await fs.rm(root, { recursive: true, force: true })
40
+ }
41
+ })
42
+
43
+ test('kernel shell log clearing removes cleaned files and legacy info files', async () => {
44
+ const root = await fs.mkdtemp(path.join(os.tmpdir(), 'pinokio-shell-cleaned-clear-'))
45
+ try {
46
+ const kernel = Object.create(Kernel.prototype)
47
+ kernel.homedir = root
48
+
49
+ const group = path.join(root, 'api', 'demo', 'start.js')
50
+ const relativeGroup = path.relative(root, group)
51
+ const cleanedLog = path.join(root, 'logs', 'shell', 'cleaned', `${relativeGroup}.3.txt`)
52
+ const infoLog = path.join(root, 'logs', 'shell', 'info', `${relativeGroup}.3.txt`)
53
+
54
+ await fs.mkdir(path.dirname(cleanedLog), { recursive: true })
55
+ await fs.mkdir(path.dirname(infoLog), { recursive: true })
56
+ await fs.writeFile(cleanedLog, 'cleaned\n')
57
+ await fs.writeFile(infoLog, 'legacy info\n')
58
+
59
+ await Kernel.prototype.clearLog.call(kernel, group)
60
+
61
+ assert.equal(await exists(cleanedLog), false)
62
+ assert.equal(await exists(infoLog), false)
63
+ } finally {
64
+ await fs.rm(root, { recursive: true, force: true })
65
+ }
66
+ })