pinokiod 7.3.0 → 7.3.3

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 (125) hide show
  1. package/kernel/api/github/index.js +444 -0
  2. package/kernel/api/index.js +199 -11
  3. package/kernel/api/process/index.js +124 -44
  4. package/kernel/api/shell_run_template.js +273 -0
  5. package/kernel/api/uri/index.js +51 -0
  6. package/kernel/bin/{conda-python.js → conda-pins.js} +23 -0
  7. package/kernel/bin/conda.js +15 -5
  8. package/kernel/bin/git.js +9 -10
  9. package/kernel/bin/huggingface.js +1 -1
  10. package/kernel/bin/index.js +5 -2
  11. package/kernel/bin/zip.js +9 -1
  12. package/kernel/connect/providers/github/README.md +5 -4
  13. package/kernel/environment.js +195 -92
  14. package/kernel/git.js +98 -19
  15. package/kernel/gitconfig_template +7 -0
  16. package/kernel/gpu/amd.js +72 -0
  17. package/kernel/gpu/apple.js +8 -0
  18. package/kernel/gpu/common.js +12 -0
  19. package/kernel/gpu/intel.js +47 -0
  20. package/kernel/gpu/nvidia.js +8 -0
  21. package/kernel/index.js +11 -1
  22. package/kernel/managed_skills.js +871 -0
  23. package/kernel/plugin.js +6 -58
  24. package/kernel/plugin_sources.js +316 -0
  25. package/kernel/resource_usage/gpu.js +349 -0
  26. package/kernel/resource_usage/index.js +322 -0
  27. package/kernel/resource_usage/macos_footprint.js +197 -0
  28. package/kernel/resource_usage/preferences.js +92 -0
  29. package/kernel/resource_usage/process_tree.js +303 -0
  30. package/kernel/scripts/git/create +4 -4
  31. package/kernel/scripts/git/fork +7 -8
  32. package/kernel/shell.js +23 -2
  33. package/kernel/shells.js +41 -0
  34. package/kernel/sysinfo.js +62 -9
  35. package/kernel/util.js +60 -0
  36. package/package.json +1 -1
  37. package/server/index.js +984 -156
  38. package/server/lib/app_log_report.js +543 -0
  39. package/server/lib/content_validation.js +55 -33
  40. package/server/lib/launcher_instruction_bootstrap.js +4 -96
  41. package/server/lib/terminal_session_helpers.js +0 -3
  42. package/server/public/common.js +77 -31
  43. package/server/public/create-launcher.js +4 -32
  44. package/server/public/logs.js +1428 -0
  45. package/server/public/nav.js +7 -0
  46. package/server/public/plugin-detail.js +93 -10
  47. package/server/public/privacy_filter_worker.js +391 -0
  48. package/server/public/style.css +1104 -154
  49. package/server/public/task-launcher.js +8 -29
  50. package/server/public/universal-launcher.css +8 -6
  51. package/server/public/universal-launcher.js +3 -27
  52. package/server/routes/apps.js +195 -1
  53. package/server/views/app.ejs +3041 -717
  54. package/server/views/autolaunch.ejs +917 -0
  55. package/server/views/bootstrap.ejs +7 -1
  56. package/server/views/d.ejs +408 -65
  57. package/server/views/editor.ejs +85 -19
  58. package/server/views/index.ejs +661 -111
  59. package/server/views/init/index.ejs +1 -1
  60. package/server/views/install.ejs +1 -1
  61. package/server/views/logs.ejs +164 -86
  62. package/server/views/net.ejs +7 -1
  63. package/server/views/partials/d_terminal_column.ejs +2 -2
  64. package/server/views/partials/d_terminal_options.ejs +0 -8
  65. package/server/views/partials/fs_status.ejs +47 -0
  66. package/server/views/partials/home_action_modal.ejs +86 -0
  67. package/server/views/partials/home_run_menu.ejs +87 -0
  68. package/server/views/partials/main_sidebar.ejs +2 -0
  69. package/server/views/partials/menu.ejs +1 -1
  70. package/server/views/plugin_detail.ejs +19 -4
  71. package/server/views/plugins.ejs +201 -3
  72. package/server/views/pre.ejs +1 -1
  73. package/server/views/pro.ejs +1 -1
  74. package/server/views/shell.ejs +40 -18
  75. package/server/views/skills.ejs +506 -0
  76. package/server/views/terminal.ejs +45 -19
  77. package/spec/INSTRUCTION_SYNC.md +20 -10
  78. package/system/plugin/antigravity-cli/antigravity.png +0 -0
  79. package/system/plugin/antigravity-cli/common.js +155 -0
  80. package/system/plugin/antigravity-cli/install.js +272 -0
  81. package/system/plugin/antigravity-cli/pinokio.js +13 -0
  82. package/system/plugin/antigravity-cli-auto/antigravity.png +0 -0
  83. package/system/plugin/antigravity-cli-auto/pinokio.js +13 -0
  84. package/system/plugin/claude/claude.png +0 -0
  85. package/system/plugin/claude/pinokio.js +47 -0
  86. package/system/plugin/claude-auto/claude.png +0 -0
  87. package/system/plugin/claude-auto/pinokio.js +58 -0
  88. package/system/plugin/claude-desktop/icon.jpeg +0 -0
  89. package/system/plugin/claude-desktop/pinokio.js +23 -0
  90. package/system/plugin/codex/openai.webp +0 -0
  91. package/system/plugin/codex/pinokio.js +42 -0
  92. package/system/plugin/codex-auto/openai.webp +0 -0
  93. package/system/plugin/codex-auto/pinokio.js +49 -0
  94. package/system/plugin/codex-desktop/icon.png +0 -0
  95. package/system/plugin/codex-desktop/pinokio.js +23 -0
  96. package/system/plugin/crush/crush.png +0 -0
  97. package/system/plugin/crush/pinokio.js +15 -0
  98. package/system/plugin/cursor/cursor.jpeg +0 -0
  99. package/system/plugin/cursor/pinokio.js +23 -0
  100. package/system/plugin/qwen/pinokio.js +34 -0
  101. package/system/plugin/qwen/qwen.png +0 -0
  102. package/system/plugin/vscode/pinokio.js +20 -0
  103. package/system/plugin/vscode/vscode.png +0 -0
  104. package/system/plugin/windsurf/pinokio.js +23 -0
  105. package/system/plugin/windsurf/windsurf.png +0 -0
  106. package/test/antigravity-cli-plugin.test.js +185 -0
  107. package/test/app-api.test.js +239 -0
  108. package/test/app-log-report.test.js +67 -0
  109. package/test/environment-cache-preflight.test.js +98 -0
  110. package/test/git-bin.test.js +59 -0
  111. package/test/git-defaults.test.js +97 -0
  112. package/test/github-api.test.js +158 -0
  113. package/test/github-connection.test.js +117 -0
  114. package/test/huggingface-bin.test.js +25 -0
  115. package/test/managed-skills.test.js +351 -0
  116. package/test/plugin-action-functions.test.js +337 -0
  117. package/test/plugin-dev-iframe.test.js +17 -0
  118. package/test/plugin-sources.test.js +203 -0
  119. package/test/privacy-filter-worker-heuristics.test.js +69 -0
  120. package/test/process-wait.test.js +169 -0
  121. package/test/script-api.test.js +97 -0
  122. package/test/shell-api.test.js +134 -0
  123. package/test/shell-run-template.test.js +209 -0
  124. package/test/storage-api.test.js +137 -0
  125. package/test/uri-api.test.js +100 -0
@@ -22,11 +22,11 @@
22
22
  <style>
23
23
  :root {
24
24
  --assist-surface-light: var(--pinokio-chrome-accent-bg-light);
25
- --assist-surface-light-open: rgba(255, 255, 255, 0.65);
25
+ --assist-surface-light-open: rgba(15, 23, 42, 0.92);
26
26
  --assist-text-light: var(--pinokio-chrome-accent-fg-light);
27
27
  --assist-text-light-open: #475569;
28
28
  --assist-surface-dark: var(--pinokio-chrome-accent-bg-dark);
29
- --assist-surface-dark-open: rgba(15, 17, 21, 0.7);
29
+ --assist-surface-dark-open: rgba(255, 255, 255, 0.14);
30
30
  --assist-text-dark: var(--pinokio-chrome-accent-fg-dark);
31
31
  --assist-text-dark-open: rgba(226, 232, 240, 0.84);
32
32
  }
@@ -34,6 +34,257 @@ body.dark #devtab.selected {
34
34
  border: none;
35
35
  background: rgb(27, 28, 29) !important;
36
36
  }
37
+ .app-header-identity {
38
+ position: relative;
39
+ flex: 1 1 auto;
40
+ display: flex;
41
+ align-items: center;
42
+ justify-content: center;
43
+ gap: 8px;
44
+ width: auto;
45
+ height: 30px;
46
+ padding: 0 14px;
47
+ min-width: 0;
48
+ max-width: none;
49
+ margin: 0 10px 0 4px;
50
+ box-sizing: border-box;
51
+ border: 1px solid rgba(15, 23, 42, 0.08);
52
+ border-radius: 999px;
53
+ background: rgba(255, 255, 255, 0.7);
54
+ box-shadow:
55
+ inset 0 1px 0 rgba(255, 255, 255, 0.9),
56
+ 0 1px 2px rgba(15, 23, 42, 0.04);
57
+ color: #0f172a;
58
+ font-size: 13px;
59
+ font-weight: 700;
60
+ line-height: 1.1;
61
+ pointer-events: none;
62
+ overflow: visible;
63
+ }
64
+ body.dark .app-header-identity {
65
+ border-color: rgba(255, 255, 255, 0.08);
66
+ background: rgba(16, 18, 24, 0.72);
67
+ box-shadow:
68
+ inset 0 1px 0 rgba(255, 255, 255, 0.06),
69
+ 0 1px 2px rgba(2, 6, 23, 0.35);
70
+ color: rgba(248, 250, 252, 0.94);
71
+ }
72
+ .app-header-identity-icon {
73
+ width: 18px;
74
+ height: 18px;
75
+ flex: 0 0 18px;
76
+ object-fit: contain;
77
+ display: block;
78
+ }
79
+ .app-header-identity-title {
80
+ min-width: 0;
81
+ overflow: hidden;
82
+ text-overflow: ellipsis;
83
+ white-space: nowrap;
84
+ }
85
+ .app-header-identity-separator {
86
+ width: 3px;
87
+ height: 3px;
88
+ flex: 0 0 3px;
89
+ border-radius: 999px;
90
+ background: rgba(100, 116, 139, 0.48);
91
+ }
92
+ body.dark .app-header-identity-separator {
93
+ background: rgba(148, 163, 184, 0.6);
94
+ }
95
+ body.app-page header.navheader h1 > .flexible {
96
+ flex: 0 0 0;
97
+ min-width: 0;
98
+ }
99
+ .resource-usage {
100
+ position: relative;
101
+ flex: 0 0 auto;
102
+ min-width: 0;
103
+ display: inline-flex;
104
+ align-items: center;
105
+ pointer-events: auto;
106
+ }
107
+ .resource-usage-trigger {
108
+ appearance: none;
109
+ border: 0;
110
+ background: transparent;
111
+ color: inherit;
112
+ display: inline-flex;
113
+ align-items: center;
114
+ gap: 8px;
115
+ height: 22px;
116
+ min-width: 0;
117
+ padding: 0 7px;
118
+ margin: 0;
119
+ border-radius: 999px;
120
+ font: inherit;
121
+ line-height: 1;
122
+ cursor: pointer;
123
+ transition: background 0.15s ease, color 0.15s ease;
124
+ }
125
+ .resource-usage-trigger::after {
126
+ content: "";
127
+ width: 0;
128
+ height: 0;
129
+ flex: 0 0 auto;
130
+ margin-left: -2px;
131
+ border-left: 3.5px solid transparent;
132
+ border-right: 3.5px solid transparent;
133
+ border-top: 4px solid rgba(71, 85, 105, 0.58);
134
+ transform-origin: 50% 40%;
135
+ transition: transform 0.15s ease, border-top-color 0.15s ease;
136
+ }
137
+ .resource-usage-trigger:hover,
138
+ .resource-usage-trigger[aria-expanded="true"] {
139
+ background: rgba(15, 23, 42, 0.055);
140
+ }
141
+ body.dark .resource-usage-trigger:hover,
142
+ body.dark .resource-usage-trigger[aria-expanded="true"] {
143
+ background: rgba(255, 255, 255, 0.07);
144
+ }
145
+ .resource-usage-trigger:hover::after,
146
+ .resource-usage-trigger[aria-expanded="true"]::after {
147
+ border-top-color: rgba(71, 85, 105, 0.82);
148
+ }
149
+ body.dark .resource-usage-trigger::after {
150
+ border-top-color: rgba(203, 213, 225, 0.58);
151
+ }
152
+ body.dark .resource-usage-trigger:hover::after,
153
+ body.dark .resource-usage-trigger[aria-expanded="true"]::after {
154
+ border-top-color: rgba(248, 250, 252, 0.84);
155
+ }
156
+ .resource-usage-trigger[aria-expanded="true"]::after {
157
+ transform: rotate(180deg);
158
+ }
159
+ .resource-usage-trigger:focus-visible {
160
+ outline: 2px solid rgba(10, 132, 255, 0.55);
161
+ outline-offset: 4px;
162
+ border-radius: 999px;
163
+ }
164
+ .resource-chip {
165
+ display: inline-flex;
166
+ align-items: center;
167
+ min-width: 0;
168
+ color: rgba(71, 85, 105, 0.76);
169
+ font-size: 11px;
170
+ font-weight: 650;
171
+ line-height: 1;
172
+ white-space: nowrap;
173
+ }
174
+ body.dark .resource-chip {
175
+ color: rgba(203, 213, 225, 0.7);
176
+ }
177
+ .resource-chip--cpu,
178
+ .resource-chip--ram,
179
+ .resource-chip--vram {
180
+ color: rgba(71, 85, 105, 0.62);
181
+ }
182
+ body.dark .resource-chip--cpu,
183
+ body.dark .resource-chip--ram,
184
+ body.dark .resource-chip--vram {
185
+ color: rgba(203, 213, 225, 0.62);
186
+ }
187
+ .resource-usage-popover {
188
+ position: absolute;
189
+ top: calc(100% + 9px);
190
+ right: 0;
191
+ width: 236px;
192
+ padding: 8px 0;
193
+ border: 1px solid rgba(15, 23, 42, 0.1);
194
+ border-radius: 10px;
195
+ background: rgba(255, 255, 255, 0.98);
196
+ box-shadow: 0 16px 42px rgba(15, 23, 42, 0.14);
197
+ color: #0f172a;
198
+ z-index: 10000020;
199
+ }
200
+ body.dark .resource-usage-popover {
201
+ border-color: rgba(255, 255, 255, 0.1);
202
+ background: rgba(20, 22, 28, 0.98);
203
+ box-shadow: 0 18px 46px rgba(0, 0, 0, 0.48);
204
+ color: rgba(248, 250, 252, 0.94);
205
+ }
206
+ .resource-usage-popover.hidden {
207
+ display: none;
208
+ }
209
+ .resource-usage-popover-head {
210
+ padding: 4px 12px 8px;
211
+ color: rgba(71, 85, 105, 0.72);
212
+ font-size: 11px;
213
+ font-weight: 700;
214
+ text-transform: uppercase;
215
+ letter-spacing: 0.03em;
216
+ }
217
+ body.dark .resource-usage-popover-head {
218
+ color: rgba(203, 213, 225, 0.7);
219
+ }
220
+ .resource-toggle {
221
+ appearance: none;
222
+ border: 0;
223
+ width: 100%;
224
+ display: flex;
225
+ align-items: center;
226
+ gap: 12px;
227
+ min-height: 34px;
228
+ padding: 0 12px;
229
+ margin: 0;
230
+ background: transparent;
231
+ color: inherit;
232
+ font: inherit;
233
+ text-align: left;
234
+ cursor: pointer;
235
+ }
236
+ .resource-toggle:hover {
237
+ background: rgba(15, 23, 42, 0.045);
238
+ }
239
+ body.dark .resource-toggle:hover {
240
+ background: rgba(255, 255, 255, 0.055);
241
+ }
242
+ .resource-toggle-label {
243
+ flex: 1 1 auto;
244
+ min-width: 0;
245
+ color: rgba(15, 23, 42, 0.88);
246
+ font-size: 13px;
247
+ font-weight: 650;
248
+ }
249
+ body.dark .resource-toggle-label {
250
+ color: rgba(248, 250, 252, 0.9);
251
+ }
252
+ .resource-toggle-track {
253
+ position: relative;
254
+ flex: 0 0 30px;
255
+ width: 30px;
256
+ height: 18px;
257
+ border-radius: 999px;
258
+ background: rgba(100, 116, 139, 0.26);
259
+ transition: background 0.15s ease;
260
+ }
261
+ .resource-toggle-thumb {
262
+ position: absolute;
263
+ left: 2px;
264
+ top: 2px;
265
+ width: 14px;
266
+ height: 14px;
267
+ border-radius: 999px;
268
+ background: #fff;
269
+ box-shadow: 0 1px 2px rgba(15, 23, 42, 0.22);
270
+ transition: transform 0.15s ease;
271
+ }
272
+ .resource-toggle[aria-checked="true"] .resource-toggle-track {
273
+ background: #25764a;
274
+ }
275
+ .resource-toggle[aria-checked="true"] .resource-toggle-thumb {
276
+ transform: translateX(12px);
277
+ }
278
+ .resource-usage-status {
279
+ padding: 8px 12px 3px;
280
+ color: rgba(100, 116, 139, 0.8);
281
+ font-size: 11px;
282
+ font-weight: 600;
283
+ line-height: 1.25;
284
+ }
285
+ body.dark .resource-usage-status {
286
+ color: rgba(148, 163, 184, 0.82);
287
+ }
37
288
  .config-info {
38
289
  display: flex;
39
290
  align-items: center;
@@ -46,19 +297,67 @@ body.dark .snapshot-comment {
46
297
  #editortab {
47
298
  color: royalblue;
48
299
  }
49
- body.dark #layout-toggle {
50
- color: white;
300
+ #sidebar-toggle {
301
+ color: #0f172a;
302
+ position: relative;
303
+ width: 48px;
304
+ height: 30px;
305
+ padding: 0;
306
+ border-radius: 9px;
307
+ box-sizing: border-box;
51
308
  }
52
- #layout-toggle {
53
- padding: 6px 10px;
54
- border: none;
55
- background: none;
56
- cursor: pointer;
57
- font-size: 16px;
309
+ body.dark #sidebar-toggle {
310
+ color: rgba(248, 250, 252, 0.92);
58
311
  }
59
- #layout-toggle:hover,
60
- #layout-toggle:hover i {
61
- color: royalblue;
312
+ #sidebar-toggle[aria-expanded="true"] {
313
+ background: rgba(15, 23, 42, 0.08);
314
+ box-shadow: inset 0 0 0 1px rgba(15, 23, 42, 0.06);
315
+ }
316
+ body.dark #sidebar-toggle[aria-expanded="true"] {
317
+ background: rgba(255, 255, 255, 0.1);
318
+ box-shadow: inset 0 0 0 1px rgba(255, 255, 255, 0.08);
319
+ }
320
+ .sidebar-toggle-glyph {
321
+ width: 19px;
322
+ height: 15px;
323
+ display: inline-flex;
324
+ align-items: center;
325
+ justify-content: center;
326
+ }
327
+ .sidebar-toggle-frame {
328
+ position: relative;
329
+ width: 18px;
330
+ height: 14px;
331
+ border: 1.7px solid currentColor;
332
+ border-radius: 3px;
333
+ box-sizing: border-box;
334
+ }
335
+ .sidebar-toggle-panel {
336
+ position: absolute;
337
+ left: 2px;
338
+ top: 2px;
339
+ bottom: 2px;
340
+ width: 4px;
341
+ border-radius: 1px;
342
+ background: currentColor;
343
+ transition: opacity 0.15s ease, background 0.15s ease, border-color 0.15s ease;
344
+ }
345
+ .sidebar-toggle-content {
346
+ position: absolute;
347
+ left: 8px;
348
+ right: 3px;
349
+ top: 3px;
350
+ height: 1.5px;
351
+ border-radius: 999px;
352
+ background: currentColor;
353
+ opacity: 0.32;
354
+ box-shadow: 0 3.8px 0 currentColor, 0 7.6px 0 currentColor;
355
+ }
356
+ #sidebar-toggle[aria-expanded="false"] .sidebar-toggle-panel {
357
+ background: transparent;
358
+ outline: 1.5px solid currentColor;
359
+ outline-offset: -1px;
360
+ opacity: 0.35;
62
361
  }
63
362
  #devtab {
64
363
  align-items: center;
@@ -492,49 +791,388 @@ body.dark .community-drawer-location-textarea {
492
791
  body.dark .community-drawer-empty {
493
792
  color: rgba(255,255,255,0.7);
494
793
  }
495
- .community-drawer-empty-head {
496
- display: flex;
497
- align-items: center;
498
- gap: 10px;
794
+ .community-drawer-empty-head {
795
+ display: flex;
796
+ align-items: center;
797
+ gap: 10px;
798
+ }
799
+ .community-drawer-empty-title {
800
+ font-size: 14px;
801
+ font-weight: 700;
802
+ color: rgba(15, 23, 42, 0.92);
803
+ }
804
+ body.dark .community-drawer-empty-title {
805
+ color: rgba(248, 250, 252, 0.95);
806
+ }
807
+ .community-drawer-empty-note {
808
+ font-size: 12px;
809
+ line-height: 1.4;
810
+ }
811
+ .community-drawer-empty-icon {
812
+ width: 28px;
813
+ height: 28px;
814
+ border-radius: 8px;
815
+ display: inline-flex;
816
+ align-items: center;
817
+ justify-content: center;
818
+ background: rgba(15, 23, 42, 0.08);
819
+ color: rgba(15, 23, 42, 0.6);
820
+ }
821
+ body.dark .community-drawer-empty-icon {
822
+ background: rgba(255, 255, 255, 0.08);
823
+ color: rgba(255, 255, 255, 0.7);
824
+ }
825
+ .community-drawer-empty .btn {
826
+ align-self: flex-start;
827
+ }
828
+ .community-frame {
829
+ width: 100%;
830
+ height: 100%;
831
+ flex: 1 1 auto;
832
+ border: 0;
833
+ background: white;
834
+ }
835
+ body.dark .community-frame {
836
+ background: #0b1220;
837
+ }
838
+ .app-logs-drawer {
839
+ display: none;
840
+ flex: 0 0 auto;
841
+ width: var(--app-logs-width, 460px);
842
+ min-width: 280px;
843
+ min-height: 0;
844
+ border-left: 1px solid rgba(0,0,0,0.08);
845
+ background: #f8fafc;
846
+ }
847
+ body.dark .app-logs-drawer {
848
+ border-left-color: rgba(255,255,255,0.08);
849
+ background: #0f1115;
850
+ }
851
+ .appcanvas.logs-open .app-logs-drawer {
852
+ display: flex;
853
+ }
854
+ .app-logs-drawer-inner {
855
+ display: flex;
856
+ flex-direction: column;
857
+ min-height: 0;
858
+ flex: 1 1 auto;
859
+ }
860
+ .app-logs-drawer-bar {
861
+ display: flex;
862
+ align-items: center;
863
+ justify-content: space-between;
864
+ gap: 10px;
865
+ padding: 8px 10px;
866
+ border-bottom: 1px solid rgba(0,0,0,0.08);
867
+ background: rgba(255,255,255,0.72);
868
+ backdrop-filter: blur(6px);
869
+ }
870
+ body.dark .app-logs-drawer-bar {
871
+ border-bottom-color: rgba(255,255,255,0.08);
872
+ background: rgba(15, 17, 21, 0.74);
873
+ }
874
+ .app-logs-drawer-title {
875
+ display: inline-flex;
876
+ align-items: center;
877
+ gap: 8px;
878
+ font-size: 12px;
879
+ font-weight: 700;
880
+ }
881
+ .app-logs-drawer-actions {
882
+ display: inline-flex;
883
+ align-items: center;
884
+ gap: 6px;
885
+ }
886
+ .app-logs-drawer-actions .btn2 {
887
+ padding: 4px;
888
+ font-size: 12px;
889
+ min-width: 28px;
890
+ height: 28px;
891
+ display: inline-flex;
892
+ align-items: center;
893
+ justify-content: center;
894
+ flex-direction: row;
895
+ }
896
+ .app-logs-drawer-actions .btn2 i {
897
+ margin: 0;
898
+ font-size: 12px;
899
+ }
900
+ .app-logs-body {
901
+ flex: 1 1 auto;
902
+ min-height: 0;
903
+ display: flex;
904
+ flex-direction: column;
905
+ gap: 10px;
906
+ padding: 10px;
907
+ overflow: hidden;
908
+ }
909
+ .app-logs-toolbar {
910
+ display: flex;
911
+ align-items: center;
912
+ gap: 8px;
913
+ flex-wrap: wrap;
914
+ }
915
+ .app-logs-toolbar .btn {
916
+ font-size: 12px;
917
+ padding: 6px 9px;
918
+ }
919
+ .app-logs-status {
920
+ font-size: 12px;
921
+ line-height: 1.4;
922
+ color: rgba(15, 23, 42, 0.72);
923
+ }
924
+ body.dark .app-logs-status {
925
+ color: rgba(248, 250, 252, 0.72);
926
+ }
927
+ .app-logs-error-callout {
928
+ display: flex;
929
+ align-items: flex-start;
930
+ gap: 9px;
931
+ padding: 9px;
932
+ border: 1px solid rgba(220, 38, 38, 0.18);
933
+ border-radius: 6px;
934
+ background: rgba(254, 242, 242, 0.85);
935
+ color: rgba(127, 29, 29, 0.95);
936
+ font-size: 12px;
937
+ line-height: 1.4;
938
+ }
939
+ .app-logs-error-callout i {
940
+ margin-top: 2px;
941
+ color: rgba(185, 28, 28, 0.9);
942
+ }
943
+ .app-logs-error-callout-title {
944
+ font-weight: 700;
945
+ }
946
+ .app-logs-error-callout-message {
947
+ overflow-wrap: anywhere;
948
+ }
949
+ body.dark .app-logs-error-callout {
950
+ border-color: rgba(248, 113, 113, 0.24);
951
+ background: rgba(127, 29, 29, 0.18);
952
+ color: rgba(254, 226, 226, 0.92);
953
+ }
954
+ body.dark .app-logs-error-callout i {
955
+ color: rgba(248, 113, 113, 0.92);
956
+ }
957
+ .app-logs-summary {
958
+ display: grid;
959
+ grid-template-columns: repeat(2, minmax(0, 1fr));
960
+ gap: 8px;
961
+ }
962
+ .app-logs-summary-item {
963
+ border: 1px solid rgba(15, 23, 42, 0.08);
964
+ border-radius: 6px;
965
+ padding: 8px;
966
+ background: rgba(255,255,255,0.68);
967
+ min-width: 0;
968
+ }
969
+ body.dark .app-logs-summary-item {
970
+ border-color: rgba(255,255,255,0.08);
971
+ background: rgba(255,255,255,0.04);
972
+ }
973
+ .app-logs-summary-label {
974
+ font-size: 10px;
975
+ text-transform: uppercase;
976
+ color: rgba(15, 23, 42, 0.56);
977
+ letter-spacing: 0;
978
+ }
979
+ body.dark .app-logs-summary-label {
980
+ color: rgba(248, 250, 252, 0.56);
981
+ }
982
+ .app-logs-summary-value {
983
+ margin-top: 3px;
984
+ font-size: 13px;
985
+ font-weight: 700;
986
+ overflow: hidden;
987
+ text-overflow: ellipsis;
988
+ white-space: nowrap;
989
+ }
990
+ .app-logs-section-list {
991
+ display: flex;
992
+ flex-wrap: wrap;
993
+ gap: 6px;
994
+ }
995
+ .app-logs-section-chip {
996
+ max-width: 100%;
997
+ border: 1px solid rgba(15, 23, 42, 0.12);
998
+ border-radius: 999px;
999
+ padding: 4px 7px;
1000
+ font-size: 11px;
1001
+ color: rgba(15, 23, 42, 0.74);
1002
+ background: rgba(255,255,255,0.72);
1003
+ overflow: hidden;
1004
+ text-overflow: ellipsis;
1005
+ white-space: nowrap;
1006
+ }
1007
+ body.dark .app-logs-section-chip {
1008
+ border-color: rgba(255,255,255,0.12);
1009
+ color: rgba(248, 250, 252, 0.74);
1010
+ background: rgba(255,255,255,0.05);
1011
+ }
1012
+ .app-logs-report-output {
1013
+ flex: 1 1 auto;
1014
+ min-height: 180px;
1015
+ margin: 0;
1016
+ padding: 10px;
1017
+ overflow: auto;
1018
+ border: 1px solid rgba(15, 23, 42, 0.1);
1019
+ border-radius: 6px;
1020
+ background: #ffffff;
1021
+ color: rgba(15, 23, 42, 0.9);
1022
+ font-size: 11px;
1023
+ line-height: 1.45;
1024
+ white-space: pre-wrap;
1025
+ overflow-wrap: anywhere;
1026
+ }
1027
+ body.dark .app-logs-report-output {
1028
+ border-color: rgba(255,255,255,0.1);
1029
+ background: #090b10;
1030
+ color: rgba(248, 250, 252, 0.9);
1031
+ }
1032
+ .appcanvas > aside .menu-actions #logs-toggle.selected,
1033
+ .appcanvas > aside .menu-actions #logs-toggle.selected:hover,
1034
+ .appcanvas > aside .menu-actions #logs-toggle.selected:focus-visible,
1035
+ .appcanvas.logs-open .menu-actions #logs-toggle,
1036
+ .appcanvas.logs-open .menu-actions #logs-toggle:hover,
1037
+ .appcanvas.logs-open .menu-actions #logs-toggle:focus-visible,
1038
+ .appcanvas.logs-open .menu-actions #logs-toggle.selected {
1039
+ background: var(--assist-surface-light-open) !important;
1040
+ color: #fff !important;
1041
+ box-shadow: none;
1042
+ }
1043
+ body.dark .appcanvas > aside .menu-actions #logs-toggle.selected,
1044
+ body.dark .appcanvas > aside .menu-actions #logs-toggle.selected:hover,
1045
+ body.dark .appcanvas > aside .menu-actions #logs-toggle.selected:focus-visible,
1046
+ body.dark .appcanvas.logs-open .menu-actions #logs-toggle,
1047
+ body.dark .appcanvas.logs-open .menu-actions #logs-toggle:hover,
1048
+ body.dark .appcanvas.logs-open .menu-actions #logs-toggle:focus-visible,
1049
+ body.dark .appcanvas.logs-open .menu-actions #logs-toggle.selected {
1050
+ background: var(--assist-surface-dark-open) !important;
1051
+ color: #fff !important;
1052
+ box-shadow: none;
499
1053
  }
500
- .community-drawer-empty-title {
501
- font-size: 14px;
502
- font-weight: 700;
503
- color: rgba(15, 23, 42, 0.92);
1054
+ .appcanvas > aside .menu-actions #logs-toggle .tab {
1055
+ width: 100%;
1056
+ position: relative;
1057
+ z-index: 1;
504
1058
  }
505
- body.dark .community-drawer-empty-title {
506
- color: rgba(248, 250, 252, 0.95);
1059
+ .appcanvas > aside .menu-actions #logs-toggle.has-new {
1060
+ position: relative;
1061
+ animation: logsAttentionFlashLight 1.05s steps(2, end) infinite;
507
1062
  }
508
- .community-drawer-empty-note {
509
- font-size: 12px;
510
- line-height: 1.4;
1063
+ body.dark .appcanvas > aside .menu-actions #logs-toggle.has-new {
1064
+ animation-name: logsAttentionFlashDark;
511
1065
  }
512
- .community-drawer-empty-icon {
513
- width: 28px;
514
- height: 28px;
515
- border-radius: 8px;
1066
+ .logs-new-badge {
516
1067
  display: inline-flex;
517
1068
  align-items: center;
518
1069
  justify-content: center;
519
- background: rgba(15, 23, 42, 0.08);
520
- color: rgba(15, 23, 42, 0.6);
1070
+ flex: 0 0 auto;
1071
+ box-sizing: border-box;
1072
+ min-width: 24px;
1073
+ height: 16px;
1074
+ max-height: 16px;
1075
+ margin-left: auto;
1076
+ border: 1px solid rgba(255,255,255,0.28);
1077
+ border-radius: 5px;
1078
+ padding: 0 5px;
1079
+ background: #b91c1c;
1080
+ color: #fff;
1081
+ font-size: 9px;
1082
+ line-height: 14px;
1083
+ font-weight: 750;
1084
+ letter-spacing: 0;
1085
+ text-transform: uppercase;
1086
+ box-shadow: none;
521
1087
  }
522
- body.dark .community-drawer-empty-icon {
523
- background: rgba(255, 255, 255, 0.08);
524
- color: rgba(255, 255, 255, 0.7);
1088
+ body.dark .logs-new-badge {
1089
+ background: #dc2626;
1090
+ color: #fff;
1091
+ border-color: rgba(255,255,255,0.22);
525
1092
  }
526
- .community-drawer-empty .btn {
527
- align-self: flex-start;
1093
+ .mobile-nav-logs.has-new {
1094
+ position: relative;
1095
+ animation: logsMobileAttentionFlashLight 1.05s steps(2, end) infinite;
528
1096
  }
529
- .community-frame {
530
- width: 100%;
531
- height: 100%;
532
- flex: 1 1 auto;
533
- border: 0;
534
- background: white;
1097
+ body.dark .mobile-nav-logs.has-new {
1098
+ animation-name: logsMobileAttentionFlashDark;
535
1099
  }
536
- body.dark .community-frame {
537
- background: #0b1220;
1100
+ .mobile-nav-logs.has-new::after {
1101
+ content: "";
1102
+ position: absolute;
1103
+ top: 7px;
1104
+ right: 7px;
1105
+ width: 7px;
1106
+ height: 7px;
1107
+ border-radius: 999px;
1108
+ background: #dc2626;
1109
+ box-shadow: 0 0 0 2px #fff;
1110
+ transform-origin: center;
1111
+ }
1112
+ body.dark .mobile-nav-logs.has-new::after {
1113
+ box-shadow: 0 0 0 2px #0f1115;
1114
+ }
1115
+ @keyframes logsAttentionFlashLight {
1116
+ 0%, 100% {
1117
+ --assist-surface-light: var(--pinokio-chrome-accent-bg-light);
1118
+ --assist-text-light: var(--pinokio-chrome-accent-fg-light);
1119
+ }
1120
+ 50% {
1121
+ --assist-surface-light: var(--pinokio-chrome-accent-fg-light);
1122
+ --assist-text-light: var(--pinokio-chrome-accent-bg-light);
1123
+ }
1124
+ }
1125
+ @keyframes logsAttentionFlashDark {
1126
+ 0%, 100% {
1127
+ --assist-surface-dark: var(--pinokio-chrome-accent-bg-dark);
1128
+ --assist-text-dark: var(--pinokio-chrome-accent-fg-dark);
1129
+ }
1130
+ 50% {
1131
+ --assist-surface-dark: var(--pinokio-chrome-accent-fg-dark);
1132
+ --assist-text-dark: var(--pinokio-chrome-accent-bg-dark);
1133
+ }
1134
+ }
1135
+ @keyframes logsMobileAttentionFlashLight {
1136
+ 0%, 100% {
1137
+ background: transparent;
1138
+ color: #64748b;
1139
+ }
1140
+ 50% {
1141
+ background: #64748b;
1142
+ color: #fff;
1143
+ }
1144
+ }
1145
+ @keyframes logsMobileAttentionFlashDark {
1146
+ 0%, 100% {
1147
+ background: transparent;
1148
+ color: rgba(148, 163, 184, 0.9);
1149
+ }
1150
+ 50% {
1151
+ background: rgba(148, 163, 184, 0.9);
1152
+ color: #0f1115;
1153
+ }
1154
+ }
1155
+ @media (prefers-reduced-motion: reduce) {
1156
+ .appcanvas > aside .menu-actions #logs-toggle.has-new,
1157
+ .mobile-nav-logs.has-new {
1158
+ animation: none;
1159
+ }
1160
+ .appcanvas > aside .menu-actions #logs-toggle.has-new {
1161
+ --assist-surface-light: var(--pinokio-chrome-accent-fg-light);
1162
+ --assist-text-light: var(--pinokio-chrome-accent-bg-light);
1163
+ }
1164
+ body.dark .appcanvas > aside .menu-actions #logs-toggle.has-new {
1165
+ --assist-surface-dark: var(--pinokio-chrome-accent-fg-dark);
1166
+ --assist-text-dark: var(--pinokio-chrome-accent-bg-dark);
1167
+ }
1168
+ .mobile-nav-logs.has-new {
1169
+ background: #64748b;
1170
+ color: #fff;
1171
+ }
1172
+ body.dark .mobile-nav-logs.has-new {
1173
+ background: rgba(148, 163, 184, 0.9);
1174
+ color: #0f1115;
1175
+ }
538
1176
  }
539
1177
  .ask-ai-drawer {
540
1178
  display: none;
@@ -681,7 +1319,7 @@ body.dark .ask-ai-drawer-empty {
681
1319
  }
682
1320
  .ask-ai-drawer-empty-title {
683
1321
  font-size: 14px;
684
- font-weight: 700;
1322
+ font-weight: 600;
685
1323
  color: rgba(15, 23, 42, 0.92);
686
1324
  }
687
1325
  body.dark .ask-ai-drawer-empty-title {
@@ -699,7 +1337,7 @@ body.dark .ask-ai-drawer-empty-title {
699
1337
  }
700
1338
  .ask-ai-drawer-picker-group-label {
701
1339
  font-size: 11px;
702
- font-weight: 700;
1340
+ font-weight: 600;
703
1341
  letter-spacing: 0.04em;
704
1342
  text-transform: uppercase;
705
1343
  opacity: 0.7;
@@ -711,8 +1349,8 @@ body.dark .ask-ai-drawer-empty-title {
711
1349
  gap: 10px;
712
1350
  text-align: left;
713
1351
  border: 1px solid rgba(0,0,0,0.12);
714
- border-radius: 8px;
715
- padding: 10px 12px;
1352
+ border-radius: 6px;
1353
+ padding: 8px 10px;
716
1354
  background: rgba(255,255,255,0.9);
717
1355
  color: rgba(15, 23, 42, 0.95);
718
1356
  cursor: pointer;
@@ -750,11 +1388,15 @@ body.dark .ask-ai-drawer-picker-option:hover {
750
1388
  }
751
1389
  .ask-ai-drawer-picker-copy {
752
1390
  min-width: 0;
1391
+ flex: 1 1 auto;
753
1392
  }
754
1393
  .ask-ai-drawer-picker-name {
755
1394
  font-size: 13px;
756
- font-weight: 700;
1395
+ font-weight: 500;
757
1396
  line-height: 1.2;
1397
+ overflow: hidden;
1398
+ text-overflow: ellipsis;
1399
+ white-space: nowrap;
758
1400
  }
759
1401
  .ask-ai-drawer-picker-meta {
760
1402
  font-size: 11px;
@@ -916,7 +1558,96 @@ body.dark .event-launch-frame {
916
1558
  }
917
1559
  .appcanvas.vertical {
918
1560
  flex-direction: row;
919
- --appcanvas-sidebar-width: 150px;
1561
+ --appcanvas-sidebar-width: 252px;
1562
+ }
1563
+ .appcanvas.vertical.dev-workspace {
1564
+ --appcanvas-sidebar-width: 260px;
1565
+ }
1566
+ .appcanvas.vertical.dev-workspace .menu-container {
1567
+ overflow: visible;
1568
+ }
1569
+ .appcanvas > aside .workspace-mode-switch {
1570
+ display: flex;
1571
+ align-items: center;
1572
+ justify-content: center;
1573
+ gap: 2px;
1574
+ flex: 0 0 auto;
1575
+ min-width: 0;
1576
+ width: calc(100% - 16px);
1577
+ margin: 0 8px 0 8px;
1578
+ padding: 3px;
1579
+ border-radius: 7px;
1580
+ border: 1px solid rgba(15, 23, 42, 0.055);
1581
+ background: rgba(15, 23, 42, 0.035);
1582
+ box-sizing: border-box;
1583
+ }
1584
+ body.dark .appcanvas > aside .workspace-mode-switch {
1585
+ border-color: rgba(255, 255, 255, 0.075);
1586
+ background: rgba(255, 255, 255, 0.045);
1587
+ }
1588
+ .workspace-mode-link {
1589
+ flex: 1 1 0;
1590
+ min-width: 0;
1591
+ min-height: 26px;
1592
+ display: inline-flex;
1593
+ align-items: center;
1594
+ justify-content: center;
1595
+ gap: 6px;
1596
+ padding: 0 8px;
1597
+ border-radius: 5px;
1598
+ color: var(--pinokio-sidebar-tab-muted);
1599
+ text-decoration: none;
1600
+ font-size: 12px;
1601
+ font-weight: 700;
1602
+ line-height: 1;
1603
+ box-sizing: border-box;
1604
+ }
1605
+ .workspace-mode-link:hover {
1606
+ color: #111827;
1607
+ background: rgba(255, 255, 255, 0.44);
1608
+ }
1609
+ .workspace-mode-link.selected {
1610
+ color: #111827;
1611
+ background: rgba(255, 255, 255, 0.76);
1612
+ box-shadow: inset 0 0 0 1px rgba(15, 23, 42, 0.055);
1613
+ }
1614
+ body.dark .workspace-mode-link.selected {
1615
+ color: rgba(248, 250, 252, 0.94);
1616
+ background: rgba(255, 255, 255, 0.09);
1617
+ box-shadow: inset 0 0 0 1px rgba(255, 255, 255, 0.075);
1618
+ }
1619
+ body.dark .workspace-mode-link:hover {
1620
+ color: rgba(248, 250, 252, 0.94);
1621
+ background: rgba(255, 255, 255, 0.07);
1622
+ }
1623
+ .workspace-mode-link i {
1624
+ font-size: 11px;
1625
+ line-height: 1;
1626
+ }
1627
+ .workspace-mode-community-proxy {
1628
+ display: none !important;
1629
+ }
1630
+ .appcanvas.vertical.dev-workspace #devtab,
1631
+ .appcanvas.vertical.dev-workspace #filestab {
1632
+ justify-content: flex-start;
1633
+ border-radius: 6px;
1634
+ color: var(--pinokio-sidebar-tab-muted);
1635
+ background: transparent !important;
1636
+ box-shadow: none;
1637
+ }
1638
+ .appcanvas.vertical.dev-workspace #devtab.selected,
1639
+ .appcanvas.vertical.dev-workspace #filestab.selected {
1640
+ color: var(--app-page-accent, var(--pinokio-sidebar-tab-active-color)) !important;
1641
+ background: var(--app-page-selected-item-bg, var(--pinokio-sidebar-tab-active-bg)) !important;
1642
+ box-shadow: 0 4px 12px var(--pinokio-sidebar-tab-shadow);
1643
+ }
1644
+ .appcanvas.vertical.dev-workspace #devtab .tab,
1645
+ .appcanvas.vertical.dev-workspace #filestab .tab {
1646
+ padding: 0;
1647
+ }
1648
+ .appcanvas.vertical.dev-workspace #devtab i,
1649
+ .appcanvas.vertical.dev-workspace #filestab i {
1650
+ color: inherit;
920
1651
  }
921
1652
  .appcanvas.vertical > aside .header-item {
922
1653
  border-radius: 0;
@@ -947,6 +1678,7 @@ body.dark .event-launch-frame {
947
1678
  flex-direction: column;
948
1679
  overflow: hidden;
949
1680
  width: var(--appcanvas-sidebar-width);
1681
+ box-sizing: border-box;
950
1682
  }
951
1683
  .appcanvas.vertical .config-info {
952
1684
  margin-bottom: 10px;
@@ -1010,6 +1742,8 @@ body.dark .appcanvas.vertical .appcanvas-resizer:focus-visible::before {
1010
1742
  }
1011
1743
  .appcanvas.vertical > aside .header-item {
1012
1744
  max-width: none;
1745
+ width: 100%;
1746
+ box-sizing: border-box;
1013
1747
  }
1014
1748
 
1015
1749
  .appcanvas > .container {
@@ -1047,18 +1781,22 @@ body.dark .appcanvas > aside {
1047
1781
  --sidebar-tab-outline: var(--pinokio-sidebar-tabbar-border);
1048
1782
  }
1049
1783
 
1050
- .appcanvas > aside.appcanvas-aside-animating {
1051
- will-change: height, opacity;
1052
- }
1053
-
1054
- .appcanvas > aside.appcanvas-aside-collapsed {
1055
- height: 0 !important;
1784
+ .appcanvas.sidebar-collapsed > aside {
1785
+ flex: 0 0 0;
1786
+ width: 0;
1787
+ min-width: 0;
1788
+ max-width: 0;
1056
1789
  opacity: 0;
1057
1790
  pointer-events: none;
1058
- padding-top: 0;
1059
- padding-bottom: 0;
1060
- margin-top: 0;
1061
- margin-bottom: 0;
1791
+ overflow: hidden;
1792
+ }
1793
+
1794
+ .appcanvas.sidebar-collapsed > aside .menu-container {
1795
+ width: 0;
1796
+ }
1797
+
1798
+ .appcanvas.sidebar-collapsed .appcanvas-resizer {
1799
+ display: none;
1062
1800
  }
1063
1801
 
1064
1802
  /*
@@ -1136,13 +1874,13 @@ body.dark .appcanvas > aside .menu-actions {
1136
1874
  padding: 4px 8px;
1137
1875
  border: 0 !important;
1138
1876
  background: var(--assist-surface-light) !important;
1139
- color: var(--assist-text-light) !important;
1877
+ color: #fff !important;
1140
1878
  box-shadow: none;
1141
1879
  transition: none;
1142
1880
  }
1143
1881
  body.dark .appcanvas > aside .menu-actions .header-item {
1144
1882
  background: var(--assist-surface-dark) !important;
1145
- color: var(--assist-text-dark) !important;
1883
+ color: #fff !important;
1146
1884
  }
1147
1885
  .appcanvas > aside .menu-actions .header-item .display {
1148
1886
  display: block;
@@ -1181,13 +1919,13 @@ body.dark .appcanvas > aside .menu-actions .header-item {
1181
1919
  .appcanvas > aside .menu-actions .header-item:hover,
1182
1920
  .appcanvas > aside .menu-actions .header-item:focus-visible {
1183
1921
  background: var(--assist-surface-light) !important;
1184
- color: var(--assist-text-light) !important;
1922
+ color: #fff !important;
1185
1923
  box-shadow: none;
1186
1924
  }
1187
1925
  body.dark .appcanvas > aside .menu-actions .header-item:hover,
1188
1926
  body.dark .appcanvas > aside .menu-actions .header-item:focus-visible {
1189
1927
  background: var(--assist-surface-dark) !important;
1190
- color: var(--assist-text-dark) !important;
1928
+ color: #fff !important;
1191
1929
  box-shadow: none;
1192
1930
  }
1193
1931
  .appcanvas.community-open .menu-actions #community-toggle,
@@ -1195,7 +1933,7 @@ body.dark .appcanvas > aside .menu-actions .header-item:focus-visible {
1195
1933
  .appcanvas.community-open .menu-actions #community-toggle:focus-visible,
1196
1934
  .appcanvas.community-open .menu-actions #community-toggle.selected {
1197
1935
  background: var(--assist-surface-light-open) !important;
1198
- color: var(--assist-text-light-open) !important;
1936
+ color: #fff !important;
1199
1937
  box-shadow: none;
1200
1938
  }
1201
1939
  body.dark .appcanvas.community-open .menu-actions #community-toggle,
@@ -1203,7 +1941,7 @@ body.dark .appcanvas.community-open .menu-actions #community-toggle:hover,
1203
1941
  body.dark .appcanvas.community-open .menu-actions #community-toggle:focus-visible,
1204
1942
  body.dark .appcanvas.community-open .menu-actions #community-toggle.selected {
1205
1943
  background: var(--assist-surface-dark-open) !important;
1206
- color: var(--assist-text-dark-open) !important;
1944
+ color: #fff !important;
1207
1945
  box-shadow: none;
1208
1946
  }
1209
1947
  .appcanvas.panel-open .menu-actions #ask-ai-tab,
@@ -1211,7 +1949,7 @@ body.dark .appcanvas.community-open .menu-actions #community-toggle.selected {
1211
1949
  .appcanvas.panel-open .menu-actions #ask-ai-tab:focus-visible,
1212
1950
  .appcanvas.panel-open .menu-actions #ask-ai-tab.selected {
1213
1951
  background: var(--assist-surface-light-open) !important;
1214
- color: var(--assist-text-light-open) !important;
1952
+ color: #fff !important;
1215
1953
  box-shadow: none;
1216
1954
  }
1217
1955
  body.dark .appcanvas.panel-open .menu-actions #ask-ai-tab,
@@ -1219,7 +1957,7 @@ body.dark .appcanvas.panel-open .menu-actions #ask-ai-tab:hover,
1219
1957
  body.dark .appcanvas.panel-open .menu-actions #ask-ai-tab:focus-visible,
1220
1958
  body.dark .appcanvas.panel-open .menu-actions #ask-ai-tab.selected {
1221
1959
  background: var(--assist-surface-dark-open) !important;
1222
- color: var(--assist-text-dark-open) !important;
1960
+ color: #fff !important;
1223
1961
  box-shadow: none;
1224
1962
  }
1225
1963
  .appcanvas.vertical .menu-scroller {
@@ -1242,7 +1980,7 @@ body.dark .appcanvas.panel-open .menu-actions #ask-ai-tab.selected {
1242
1980
  .appcanvas.vertical .menu-actions {
1243
1981
  flex-direction: column;
1244
1982
  align-items: stretch;
1245
- gap: 1px;
1983
+ gap: 0;
1246
1984
  margin-top: auto;
1247
1985
  padding: 0;
1248
1986
  width: 100%;
@@ -1252,6 +1990,119 @@ body.dark .appcanvas.panel-open .menu-actions #ask-ai-tab.selected {
1252
1990
  body.dark .appcanvas.vertical .menu-actions {
1253
1991
  border-top: none;
1254
1992
  }
1993
+ .appcanvas.vertical > aside .menu-actions .header-item + .header-item {
1994
+ box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.12) !important;
1995
+ }
1996
+ body.dark .appcanvas.vertical > aside .menu-actions .header-item + .header-item {
1997
+ box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.08) !important;
1998
+ }
1999
+ .appcanvas.vertical.dev-workspace .menu-actions.dev-menu-actions {
2000
+ gap: 0;
2001
+ padding: 0;
2002
+ border-left: 0;
2003
+ border-top: 0;
2004
+ overflow: visible;
2005
+ }
2006
+ .appcanvas.vertical.dev-workspace .menu-actions.dev-menu-actions #fs-status {
2007
+ display: flex;
2008
+ flex-direction: column;
2009
+ align-items: stretch;
2010
+ flex-wrap: nowrap;
2011
+ gap: 0;
2012
+ width: 100%;
2013
+ padding: 0;
2014
+ margin: 0;
2015
+ background: transparent !important;
2016
+ box-shadow: none;
2017
+ }
2018
+ .appcanvas.vertical.dev-workspace .menu-actions.dev-menu-actions #fs-changes-btn {
2019
+ margin-left: 0;
2020
+ }
2021
+ .appcanvas.vertical.dev-workspace .menu-actions.dev-menu-actions #fs-status .fs-status-dropdown,
2022
+ .appcanvas.vertical.dev-workspace .menu-actions.dev-menu-actions #fs-status .fs-status-btn {
2023
+ width: 100%;
2024
+ }
2025
+ .appcanvas.vertical.dev-workspace .menu-actions.dev-menu-actions #fs-status .fs-status-btn {
2026
+ min-height: 0;
2027
+ justify-content: flex-start;
2028
+ padding: 6px 8px 5px;
2029
+ border-radius: 0;
2030
+ gap: 8px;
2031
+ background: transparent !important;
2032
+ box-shadow: none;
2033
+ transition: none;
2034
+ color: var(--pinokio-sidebar-tab-muted);
2035
+ line-height: 1.25;
2036
+ }
2037
+ .appcanvas.vertical.dev-workspace .menu-actions.dev-menu-actions #fs-status .fs-status-btn:hover {
2038
+ background: var(--pinokio-sidebar-tab-hover) !important;
2039
+ color: var(--pinokio-sidebar-tab-active-color);
2040
+ }
2041
+ .appcanvas.vertical.dev-workspace .menu-actions.dev-menu-actions #fs-status .fs-status-btn:focus {
2042
+ outline: none;
2043
+ }
2044
+ .appcanvas.vertical.dev-workspace .menu-actions.dev-menu-actions #fs-status .fs-status-btn:focus-visible {
2045
+ outline: 2px solid var(--app-page-accent);
2046
+ outline-offset: -2px;
2047
+ }
2048
+ .appcanvas.vertical.dev-workspace .menu-actions.dev-menu-actions #fs-status .fs-status-label {
2049
+ flex: 1 1 auto;
2050
+ min-width: 0;
2051
+ justify-content: flex-start;
2052
+ text-align: left;
2053
+ gap: 6px;
2054
+ font-size: 13px;
2055
+ }
2056
+ .appcanvas.vertical.dev-workspace .menu-actions.dev-menu-actions #fs-status .fs-status-label i {
2057
+ width: 20px;
2058
+ flex: 0 0 20px;
2059
+ height: 20px;
2060
+ display: inline-flex;
2061
+ align-items: center;
2062
+ justify-content: center;
2063
+ margin-right: 0;
2064
+ text-align: center;
2065
+ padding: 0;
2066
+ }
2067
+ .appcanvas.vertical.dev-workspace .menu-actions.dev-menu-actions #fs-status .fs-status-title {
2068
+ min-width: 0;
2069
+ overflow: hidden;
2070
+ text-overflow: ellipsis;
2071
+ white-space: nowrap;
2072
+ }
2073
+ .appcanvas.vertical.dev-workspace .menu-actions.dev-menu-actions #fs-status .badge {
2074
+ flex: 0 0 auto;
2075
+ margin-left: auto;
2076
+ margin-right: 24px;
2077
+ }
2078
+ .appcanvas.vertical.dev-workspace .menu-actions.dev-menu-actions #fs-status .fs-dropdown-menu {
2079
+ position: static;
2080
+ width: 100%;
2081
+ min-width: 0;
2082
+ max-width: none;
2083
+ max-height: min(260px, 45vh);
2084
+ padding: 0;
2085
+ border: 0;
2086
+ border-radius: 0;
2087
+ box-shadow: none;
2088
+ background: transparent;
2089
+ overflow: auto;
2090
+ }
2091
+ .appcanvas.vertical.dev-workspace .menu-actions.dev-menu-actions #fs-status .git-fork .fs-dropdown-menu,
2092
+ .appcanvas.vertical.dev-workspace .menu-actions.dev-menu-actions #fs-status .git-publish .fs-dropdown-menu {
2093
+ width: 100%;
2094
+ max-width: none;
2095
+ }
2096
+ .appcanvas.vertical.dev-workspace .menu-actions.dev-menu-actions #fs-status .fs-dropdown-item {
2097
+ min-height: 0;
2098
+ padding: 6px 8px 6px 34px;
2099
+ border-radius: 0;
2100
+ gap: 8px;
2101
+ font-size: 12px;
2102
+ }
2103
+ .appcanvas.vertical.dev-workspace .menu-actions.dev-menu-actions #fs-status .fs-dropdown-item:hover {
2104
+ background: var(--app-page-surface-active);
2105
+ }
1255
2106
 
1256
2107
  .appcanvas > aside .menu-scroller::-webkit-scrollbar {
1257
2108
  height: 6px;
@@ -1331,33 +2182,27 @@ body.dark .appcanvas > aside .active-nested-path .header-item.is-current {
1331
2182
  margin-left: 0;
1332
2183
  }
1333
2184
 
1334
- .appcanvas > aside .m.menu .nested-menu {
1335
- position: static;
1336
- }
1337
-
1338
- .appcanvas > aside .m.menu .nested-menu > .submenu {
1339
- margin: 0;
1340
- padding: 8px 0;
1341
- border-radius: 0;
1342
- /*
1343
- border-radius: 12px;
1344
- */
1345
- border: 1px solid var(--pinokio-sidebar-tabbar-border);
1346
- background: #ffffff;
1347
- box-shadow: 0 18px 36px color-mix(in srgb, var(--pinokio-sidebar-tab-shadow) 85%, transparent);
2185
+ .appcanvas > aside .m.menu .nested-menu {
2186
+ position: static;
2187
+ }
2188
+
2189
+ .appcanvas > aside .m.menu .nested-menu > .submenu {
2190
+ margin: 2px 0 4px 14px;
2191
+ padding: 0 0 0 8px;
2192
+ border-left: 1px solid var(--pinokio-sidebar-tabbar-border);
2193
+ background: transparent;
2194
+ box-shadow: none;
1348
2195
  display: flex;
1349
2196
  flex-direction: column;
1350
- gap: 2px;
1351
- min-width: 220px;
1352
- max-height: min(60vh, 420px);
1353
- overflow-y: auto;
1354
- z-index: 90;
2197
+ gap: 1px;
2198
+ min-width: 0;
2199
+ max-height: none;
2200
+ overflow: visible;
2201
+ box-sizing: border-box;
1355
2202
  }
1356
2203
 
1357
2204
  body.dark .appcanvas > aside .m.menu .nested-menu > .submenu {
1358
- background: rgba(27, 28, 29, 0.96);
1359
2205
  border-color: rgba(67, 71, 85, 0.52);
1360
- box-shadow: 0 20px 42px color-mix(in srgb, var(--pinokio-sidebar-tab-shadow) 92%, transparent);
1361
2206
  }
1362
2207
 
1363
2208
  .appcanvas > aside .m.menu .nested-menu > .submenu.hidden {
@@ -1383,15 +2228,12 @@ body.dark .appcanvas > aside .m.menu .nested-menu.is-open > .reveal {
1383
2228
  width: 100%;
1384
2229
  max-width: none;
1385
2230
  min-width: 0;
1386
- padding: 7px 16px;
2231
+ padding: 6px 8px;
1387
2232
  border-radius: 0;
1388
- /*
1389
- border-radius: 8px;
1390
- */
1391
2233
  border: 1px solid transparent;
1392
2234
  background: transparent !important;
1393
2235
  color: var(--pinokio-sidebar-tab-muted);
1394
- gap: 10px;
2236
+ gap: 8px;
1395
2237
  overflow: hidden;
1396
2238
  box-sizing: border-box;
1397
2239
  }
@@ -1421,34 +2263,6 @@ body.dark .appcanvas > aside .m.menu .nested-menu > .submenu .frame-link:hover {
1421
2263
  margin-left: auto;
1422
2264
  }
1423
2265
 
1424
- .appcanvas > aside .m.menu .nested-menu .submenu .nested-menu > .reveal .loader {
1425
- width: 18px;
1426
- min-width: 18px;
1427
- height: 18px;
1428
- display: flex;
1429
- align-items: center;
1430
- justify-content: center;
1431
- position: relative;
1432
- }
1433
-
1434
- .appcanvas > aside .m.menu .nested-menu .submenu .nested-menu > .reveal .loader i.fa-angle-down,
1435
- .appcanvas > aside .m.menu .nested-menu .submenu .nested-menu > .reveal .loader i.fa-angle-up {
1436
- display: none;
1437
- }
1438
-
1439
- .appcanvas > aside .m.menu .nested-menu .submenu .nested-menu > .reveal .loader::before {
1440
- content: "\f105";
1441
- font-family: var(--fa-family-classic, "Font Awesome 7 Free");
1442
- font-weight: 900;
1443
- font-size: 13px;
1444
- color: currentColor;
1445
- transition: transform 0.18s ease;
1446
- }
1447
-
1448
- .appcanvas > aside .m.menu .nested-menu .submenu .nested-menu.is-open > .reveal .loader::before {
1449
- transform: rotate(90deg);
1450
- }
1451
-
1452
2266
  .appcanvas > aside .header-top {
1453
2267
  display: flex;
1454
2268
  align-items: center;
@@ -2408,28 +3222,392 @@ body.dark .disk-usage {
2408
3222
  border-right: 1px solid rgba(255,255,255,0.1);
2409
3223
  color: white;
2410
3224
  }
2411
- .disk-usage {
2412
- display: block;
2413
- cursor: pointer;
2414
- border-right: 1px solid rgba(0,0,0,0.1);
2415
- font-weight: bold;
2416
- color: black;
2417
- padding: 5px 10px;
2418
- border-radius: 4px;
3225
+ .disk-usage {
3226
+ display: block;
3227
+ cursor: pointer;
3228
+ border-right: 1px solid rgba(0,0,0,0.1);
3229
+ font-weight: bold;
3230
+ color: black;
3231
+ padding: 5px 10px;
3232
+ border-radius: 4px;
3233
+ }
3234
+ .disk-usage i {
3235
+ margin-right: 5px;
3236
+ font-size: 16px !important;
3237
+ }
3238
+ .appcanvas:not(.vertical) > aside .menu-scroller .config-info,
3239
+ .appcanvas:not(.vertical) > aside .menu-scroller .disk-usage {
3240
+ flex: 0 0 auto;
3241
+ white-space: nowrap;
3242
+ }
3243
+ .appcanvas:not(.vertical) > aside .menu-scroller .disk-usage {
3244
+ display: inline-flex;
3245
+ align-items: center;
3246
+ gap: 6px;
3247
+ }
3248
+
3249
+ .app-autolaunch {
3250
+ position: relative;
3251
+ flex: 0 0 auto;
3252
+ min-width: 0;
3253
+ }
3254
+ .app-autolaunch-row {
3255
+ appearance: none;
3256
+ display: flex;
3257
+ align-items: center;
3258
+ gap: 8px;
3259
+ width: 100%;
3260
+ min-height: 34px;
3261
+ border: 0;
3262
+ background: transparent;
3263
+ color: var(--pinokio-sidebar-tab-muted);
3264
+ border-radius: 0;
3265
+ padding: 7px 10px;
3266
+ font: inherit;
3267
+ font-size: 12px;
3268
+ font-weight: 700;
3269
+ line-height: 1.2;
3270
+ cursor: pointer;
3271
+ white-space: nowrap;
3272
+ max-width: 100%;
3273
+ box-sizing: border-box;
3274
+ text-align: left;
3275
+ }
3276
+ .app-autolaunch-row:hover,
3277
+ .app-autolaunch.open .app-autolaunch-row {
3278
+ background: var(--pinokio-sidebar-tab-hover);
3279
+ color: var(--pinokio-sidebar-tab-active-color);
3280
+ }
3281
+ .app-autolaunch-row:focus {
3282
+ outline: none;
3283
+ }
3284
+ .app-autolaunch-row:focus-visible {
3285
+ box-shadow: inset 0 0 0 2px rgba(59, 130, 246, 0.35);
3286
+ }
3287
+ .app-autolaunch-label {
3288
+ display: inline-flex;
3289
+ align-items: center;
3290
+ gap: 8px;
3291
+ min-width: 0;
3292
+ overflow: hidden;
3293
+ text-overflow: ellipsis;
3294
+ }
3295
+ .app-autolaunch-label i {
3296
+ width: 18px;
3297
+ flex: 0 0 18px;
3298
+ text-align: center;
3299
+ font-size: 16px;
3300
+ }
3301
+ .app-autolaunch-spacer {
3302
+ flex: 1 1 auto;
3303
+ min-width: 10px;
3304
+ }
3305
+ .app-autolaunch-status {
3306
+ flex: 0 0 auto;
3307
+ color: rgba(100, 116, 139, 0.95);
3308
+ font-size: 11px;
3309
+ font-weight: 800;
3310
+ }
3311
+ .app-autolaunch-row[data-enabled="true"] .app-autolaunch-status {
3312
+ color: rgba(22, 101, 52, 0.95);
3313
+ }
3314
+ .app-autolaunch-chevron {
3315
+ flex: 0 0 auto;
3316
+ color: rgba(100, 116, 139, 0.85);
3317
+ font-size: 10px;
3318
+ }
3319
+ body.dark .app-autolaunch-row {
3320
+ background: transparent;
3321
+ color: rgba(226, 232, 240, 0.9);
3322
+ }
3323
+ body.dark .app-autolaunch-row:hover,
3324
+ body.dark .app-autolaunch.open .app-autolaunch-row {
3325
+ background: rgba(148, 163, 184, 0.15);
3326
+ color: #f8fafc;
3327
+ }
3328
+ body.dark .app-autolaunch-status,
3329
+ body.dark .app-autolaunch-chevron {
3330
+ color: rgba(148, 163, 184, 0.9);
3331
+ }
3332
+ body.dark .app-autolaunch-row[data-enabled="true"] .app-autolaunch-status {
3333
+ color: rgba(134, 239, 172, 0.92);
3334
+ }
3335
+ .app-autolaunch-modal {
3336
+ position: fixed;
3337
+ inset: 0;
3338
+ display: flex;
3339
+ align-items: center;
3340
+ justify-content: center;
3341
+ padding: 24px;
3342
+ z-index: 10000002;
3343
+ background: rgba(2, 6, 23, 0.46);
3344
+ box-sizing: border-box;
3345
+ }
3346
+ .app-autolaunch-modal.hidden {
3347
+ display: none;
3348
+ }
3349
+ .app-autolaunch-panel {
3350
+ width: min(440px, calc(100vw - 32px));
3351
+ max-height: min(620px, calc(100vh - 48px));
3352
+ overflow: auto;
3353
+ border: 1px solid rgba(15, 23, 42, 0.14);
3354
+ border-radius: 8px;
3355
+ background: rgba(255,255,255,0.98);
3356
+ color: rgba(15, 23, 42, 0.92);
3357
+ box-shadow: 0 18px 54px rgba(15, 23, 42, 0.22);
3358
+ box-sizing: border-box;
3359
+ }
3360
+ body.dark .app-autolaunch-panel {
3361
+ background: rgba(15, 17, 21, 0.98);
3362
+ border-color: rgba(148, 163, 184, 0.26);
3363
+ color: rgba(248, 250, 252, 0.92);
3364
+ box-shadow: 0 22px 60px rgba(0, 0, 0, 0.55);
3365
+ }
3366
+ .app-autolaunch-modal-head {
3367
+ display: flex;
3368
+ align-items: flex-start;
3369
+ justify-content: space-between;
3370
+ gap: 12px;
3371
+ padding: 14px 14px 12px;
3372
+ border-bottom: 1px solid rgba(15, 23, 42, 0.08);
3373
+ }
3374
+ body.dark .app-autolaunch-modal-head {
3375
+ border-bottom-color: rgba(255,255,255,0.08);
3376
+ }
3377
+ .app-autolaunch-title {
3378
+ font-size: 13px;
3379
+ font-weight: 800;
3380
+ }
3381
+ .app-autolaunch-note,
3382
+ .app-autolaunch-feedback,
3383
+ .app-autolaunch-script-path,
3384
+ .app-autolaunch-empty {
3385
+ color: rgba(100, 116, 139, 0.95);
3386
+ font-size: 11px;
3387
+ line-height: 1.35;
3388
+ }
3389
+ body.dark .app-autolaunch-note,
3390
+ body.dark .app-autolaunch-feedback,
3391
+ body.dark .app-autolaunch-script-path,
3392
+ body.dark .app-autolaunch-empty {
3393
+ color: rgba(148, 163, 184, 0.92);
3394
+ }
3395
+ .app-autolaunch-modal-actions {
3396
+ display: inline-flex;
3397
+ align-items: center;
3398
+ gap: 8px;
3399
+ flex: 0 0 auto;
3400
+ }
3401
+ .app-autolaunch-close {
3402
+ display: inline-flex;
3403
+ align-items: center;
3404
+ justify-content: center;
3405
+ width: 28px;
3406
+ height: 28px;
3407
+ border: 0;
3408
+ border-radius: 6px;
3409
+ background: transparent;
3410
+ color: rgba(100, 116, 139, 0.95);
3411
+ cursor: pointer;
3412
+ }
3413
+ .app-autolaunch-close:hover {
3414
+ background: rgba(15, 23, 42, 0.08);
3415
+ color: rgba(15, 23, 42, 0.92);
3416
+ }
3417
+ body.dark .app-autolaunch-close {
3418
+ color: rgba(148, 163, 184, 0.95);
3419
+ }
3420
+ body.dark .app-autolaunch-close:hover {
3421
+ background: rgba(148, 163, 184, 0.16);
3422
+ color: rgba(248, 250, 252, 0.95);
3423
+ }
3424
+ .app-autolaunch-switch {
3425
+ display: inline-flex;
3426
+ align-items: center;
3427
+ justify-content: space-between;
3428
+ gap: 5px;
3429
+ width: 68px;
3430
+ height: 26px;
3431
+ padding: 3px;
3432
+ border: 1px solid rgba(100, 116, 139, 0.28);
3433
+ border-radius: 999px;
3434
+ background: rgba(148, 163, 184, 0.18);
3435
+ color: rgba(71, 85, 105, 0.95);
3436
+ cursor: pointer;
3437
+ flex: 0 0 auto;
3438
+ }
3439
+ .app-autolaunch-switch[aria-checked="true"] {
3440
+ background: rgba(34, 197, 94, 0.18);
3441
+ border-color: rgba(34, 197, 94, 0.42);
3442
+ color: rgba(22, 101, 52, 0.95);
3443
+ }
3444
+ .app-autolaunch-switch:disabled {
3445
+ opacity: 0.55;
3446
+ cursor: not-allowed;
3447
+ }
3448
+ .app-autolaunch-switch-label {
3449
+ flex: 1 1 auto;
3450
+ min-width: 0;
3451
+ order: 2;
3452
+ text-align: center;
3453
+ font-size: 10px;
3454
+ font-weight: 800;
3455
+ line-height: 1;
3456
+ }
3457
+ .app-autolaunch-switch[aria-checked="true"] .app-autolaunch-switch-label {
3458
+ order: 1;
3459
+ }
3460
+ .app-autolaunch-switch-thumb {
3461
+ order: 1;
3462
+ flex: 0 0 auto;
3463
+ width: 18px;
3464
+ height: 18px;
3465
+ border-radius: 999px;
3466
+ background: rgba(15, 23, 42, 0.72);
3467
+ }
3468
+ .app-autolaunch-switch[aria-checked="true"] .app-autolaunch-switch-thumb {
3469
+ order: 2;
3470
+ }
3471
+ body.dark .app-autolaunch-switch {
3472
+ color: rgba(203, 213, 225, 0.92);
3473
+ }
3474
+ body.dark .app-autolaunch-switch[aria-checked="true"] {
3475
+ color: rgba(134, 239, 172, 0.92);
3476
+ }
3477
+ body.dark .app-autolaunch-switch-thumb {
3478
+ background: rgba(248, 250, 252, 0.82);
3479
+ }
3480
+ .app-autolaunch-modal-section {
3481
+ padding: 12px 14px 0;
3482
+ }
3483
+ .app-autolaunch-section-label {
3484
+ display: flex;
3485
+ align-items: center;
3486
+ justify-content: space-between;
3487
+ gap: 8px;
3488
+ margin-bottom: 6px;
3489
+ font-size: 11px;
3490
+ font-weight: 800;
3491
+ text-transform: uppercase;
3492
+ letter-spacing: 0.04em;
3493
+ color: rgba(71, 85, 105, 0.92);
3494
+ }
3495
+ body.dark .app-autolaunch-section-label {
3496
+ color: rgba(203, 213, 225, 0.9);
3497
+ }
3498
+ .app-autolaunch-script-list {
3499
+ display: flex;
3500
+ flex-direction: column;
3501
+ gap: 4px;
3502
+ }
3503
+ .app-autolaunch-script-option {
3504
+ display: grid;
3505
+ grid-template-columns: auto 1fr;
3506
+ align-items: center;
3507
+ gap: 8px;
3508
+ padding: 7px 8px;
3509
+ border: 1px solid rgba(15, 23, 42, 0.08);
3510
+ border-radius: 6px;
3511
+ cursor: pointer;
3512
+ background: rgba(248, 250, 252, 0.78);
3513
+ }
3514
+ .app-autolaunch-script-option:hover,
3515
+ .app-autolaunch-script-option.selected {
3516
+ border-color: rgba(37, 99, 235, 0.35);
3517
+ background: rgba(37, 99, 235, 0.08);
3518
+ }
3519
+ .app-autolaunch-script-option > div {
3520
+ min-width: 0;
3521
+ }
3522
+ body.dark .app-autolaunch-script-option {
3523
+ border-color: rgba(148, 163, 184, 0.16);
3524
+ background: rgba(15, 23, 42, 0.72);
3525
+ }
3526
+ body.dark .app-autolaunch-script-option:hover,
3527
+ body.dark .app-autolaunch-script-option.selected {
3528
+ border-color: rgba(96, 165, 250, 0.42);
3529
+ background: rgba(37, 99, 235, 0.18);
3530
+ }
3531
+ .app-autolaunch-script-title {
3532
+ display: flex;
3533
+ align-items: center;
3534
+ gap: 6px;
3535
+ min-width: 0;
3536
+ font-size: 12px;
3537
+ font-weight: 700;
3538
+ }
3539
+ .app-autolaunch-script-title span:first-child,
3540
+ .app-autolaunch-script-path {
3541
+ overflow: hidden;
3542
+ text-overflow: ellipsis;
3543
+ white-space: nowrap;
3544
+ }
3545
+ .app-autolaunch-tag {
3546
+ flex: 0 0 auto;
3547
+ padding: 1px 5px;
3548
+ border-radius: 999px;
3549
+ background: rgba(148, 163, 184, 0.16);
3550
+ color: rgba(71, 85, 105, 0.92);
3551
+ font-size: 9px;
3552
+ font-weight: 800;
3553
+ text-transform: uppercase;
3554
+ }
3555
+ body.dark .app-autolaunch-tag {
3556
+ background: rgba(148, 163, 184, 0.18);
3557
+ color: rgba(203, 213, 225, 0.9);
3558
+ }
3559
+ .app-autolaunch-footer {
3560
+ display: flex;
3561
+ align-items: center;
3562
+ justify-content: space-between;
3563
+ gap: 8px;
3564
+ padding: 12px 14px 14px;
3565
+ }
3566
+ .app-autolaunch-link {
3567
+ color: royalblue;
3568
+ font-size: 12px;
3569
+ font-weight: 700;
3570
+ text-decoration: none;
3571
+ }
3572
+ .app-autolaunch-feedback.error {
3573
+ color: #b91c1c;
3574
+ }
3575
+ body.dark .app-autolaunch-feedback.error {
3576
+ color: #fca5a5;
3577
+ }
3578
+ .appcanvas.vertical .app-autolaunch {
3579
+ width: 100%;
3580
+ padding: 0;
3581
+ box-sizing: border-box;
3582
+ }
3583
+ .appcanvas.vertical .app-autolaunch-row {
3584
+ width: 100%;
3585
+ justify-content: space-between;
3586
+ min-height: 0;
3587
+ padding: 6px 8px 5px;
3588
+ line-height: 1.25;
2419
3589
  }
2420
- .disk-usage i {
2421
- margin-right: 5px;
2422
- font-size: 16px !important;
3590
+ .appcanvas.vertical .app-autolaunch-label {
3591
+ gap: 6px;
2423
3592
  }
2424
- .appcanvas:not(.vertical) > aside .menu-scroller .config-info,
2425
- .appcanvas:not(.vertical) > aside .menu-scroller .disk-usage {
2426
- flex: 0 0 auto;
3593
+ .appcanvas.vertical .app-autolaunch-label i {
3594
+ width: 20px;
3595
+ flex-basis: 20px;
3596
+ font-size: 15px;
3597
+ }
3598
+ .appcanvas:not(.vertical) > aside .menu-scroller .app-autolaunch {
2427
3599
  white-space: nowrap;
2428
3600
  }
2429
- .appcanvas:not(.vertical) > aside .menu-scroller .disk-usage {
2430
- display: inline-flex;
2431
- align-items: center;
2432
- gap: 6px;
3601
+ body.mobile-menu-open .appcanvas > aside .app-autolaunch {
3602
+ margin: -4px 0 0;
3603
+ padding: 0;
3604
+ }
3605
+ body.mobile-menu-open .appcanvas > aside .app-autolaunch-row {
3606
+ width: 100%;
3607
+ min-height: 40px;
3608
+ justify-content: space-between;
3609
+ padding: 12px 14px;
3610
+ border-radius: 14px;
2433
3611
  }
2434
3612
 
2435
3613
 
@@ -2825,6 +4003,9 @@ body.dark .fs-status-btn:hover {
2825
4003
  body.app-page .appcanvas {
2826
4004
  background: transparent;
2827
4005
  }
4006
+ body.app-page .appcanvas.vertical .menu-scroller {
4007
+ padding-top: 10px;
4008
+ }
2828
4009
  body.app-page .appcanvas > aside,
2829
4010
  body.dark.app-page .appcanvas > aside {
2830
4011
  background: transparent !important;
@@ -2843,6 +4024,27 @@ body.app-page .appcanvas_filler {
2843
4024
  body.app-page .disk-usage {
2844
4025
  border-right-color: color-mix(in srgb, var(--app-page-border) 85%, transparent);
2845
4026
  }
4027
+ body.app-page .app-header-identity .disk-usage.app-header-identity-disk {
4028
+ flex: 0 0 auto;
4029
+ display: inline-flex;
4030
+ align-items: center;
4031
+ min-width: 0;
4032
+ padding: 0;
4033
+ border: 0 !important;
4034
+ border-radius: 0;
4035
+ color: rgba(71, 85, 105, 0.86);
4036
+ font-size: 11px;
4037
+ font-weight: 650;
4038
+ line-height: 1;
4039
+ white-space: nowrap;
4040
+ cursor: default;
4041
+ }
4042
+ body.dark.app-page .app-header-identity .disk-usage.app-header-identity-disk {
4043
+ color: rgba(248, 250, 252, 0.86);
4044
+ }
4045
+ body.app-page .app-header-identity .disk-usage.app-header-identity-disk i {
4046
+ display: none;
4047
+ }
2846
4048
  body.app-page .appcanvas > aside .active-nested-path .header-item.is-current,
2847
4049
  body.app-page .appcanvas > aside .menu-scroller .header-item.selected,
2848
4050
  body.app-page .appcanvas > aside .menu-scroller .frame-link.selected,
@@ -2922,7 +4124,20 @@ body.app-page .appcanvas.vertical .appcanvas-resizer::before {
2922
4124
  }
2923
4125
  */
2924
4126
  body.app-page .appcanvas.vertical .appcanvas-resizer::before {
2925
- background: var(--app-page-selected-item-bg);
4127
+ background: transparent;
4128
+ transition: background 120ms ease;
4129
+ }
4130
+ body.app-page .appcanvas.vertical .appcanvas-resizer:hover::before {
4131
+ background: rgba(15, 23, 42, 0.16);
4132
+ }
4133
+ body.app-page .appcanvas.vertical .appcanvas-resizer:focus-visible::before {
4134
+ background: rgba(59, 130, 246, 0.45);
4135
+ }
4136
+ body.dark.app-page .appcanvas.vertical .appcanvas-resizer:hover::before {
4137
+ background: rgba(255, 255, 255, 0.22);
4138
+ }
4139
+ body.dark.app-page .appcanvas.vertical .appcanvas-resizer:focus-visible::before {
4140
+ background: rgba(96, 165, 250, 0.6);
2926
4141
  }
2927
4142
 
2928
4143
  /* Remote selection modal styles */
@@ -4691,21 +5906,18 @@ body.dark .snapshot-footer-input input {
4691
5906
  header.navheader h1 {
4692
5907
  position: relative;
4693
5908
  }
4694
- header.navheader .mode-selector {
4695
- position: absolute;
4696
- left: 50%;
4697
- top: 50%;
4698
- transform: translate(-50%, -50%);
4699
- flex-grow: 0;
4700
- margin: 0;
4701
- }
4702
- header.navheader .mode-selector .community-mode-toggle {
4703
- display: none;
5909
+ @media only screen and (max-width: 1100px) {
5910
+ .app-header-identity {
5911
+ width: auto;
5912
+ max-width: none;
5913
+ }
4704
5914
  }
4705
5915
  @media only screen and (max-width: 800px) {
4706
- header.navheader .mode-selector {
4707
- position: static;
4708
- transform: none;
5916
+ .app-header-identity {
5917
+ flex: 1 1 auto;
5918
+ justify-content: flex-start;
5919
+ width: auto;
5920
+ max-width: none;
4709
5921
  margin: 0 10px;
4710
5922
  }
4711
5923
  }
@@ -4735,7 +5947,7 @@ header.navheader .mode-selector .community-mode-toggle {
4735
5947
  opacity: 0 !important;
4736
5948
  pointer-events: none !important;
4737
5949
  }
4738
- header.navheader .mode-selector {
5950
+ header.navheader .app-header-identity {
4739
5951
  display: none !important;
4740
5952
  }
4741
5953
  body:not(.mobile-menu-open) .appcanvas > aside {
@@ -4744,9 +5956,6 @@ header.navheader .mode-selector .community-mode-toggle {
4744
5956
  body.mobile-menu-open #menu-mobile-close {
4745
5957
  display: inline-flex;
4746
5958
  }
4747
- body.mobile-menu-open #layout-toggle {
4748
- display: none;
4749
- }
4750
5959
  .browserview-shell {
4751
5960
  box-sizing: border-box;
4752
5961
  }
@@ -4806,56 +6015,100 @@ header.navheader .mode-selector .community-mode-toggle {
4806
6015
  object-fit: contain;
4807
6016
  display: block;
4808
6017
  }
4809
- .mobile-bottom-nav__modes {
6018
+ .mobile-bottom-nav__identity {
4810
6019
  flex: 1 1 auto;
4811
6020
  min-width: 0;
4812
6021
  display: flex;
4813
6022
  align-items: center;
4814
- gap: 0;
4815
- min-height: 26px;
4816
- padding: 0;
4817
- border-radius: 8px;
6023
+ justify-content: center;
6024
+ gap: 6px;
6025
+ height: 28px;
6026
+ padding: 0 10px;
6027
+ border-radius: 999px;
4818
6028
  border: 1px solid rgba(0,0,0,0.08);
4819
- background: rgba(15, 23, 42, 0.06);
6029
+ background: rgba(255, 255, 255, 0.78);
6030
+ box-sizing: border-box;
4820
6031
  }
4821
- body.dark .mobile-bottom-nav__modes {
6032
+ body.dark .mobile-bottom-nav__identity {
4822
6033
  border-color: rgba(255,255,255,0.08);
4823
6034
  background: rgba(255,255,255,0.06);
4824
6035
  }
4825
- .mobile-mode-segment {
4826
- flex: 1 1 0;
6036
+ .mobile-bottom-nav__identity-icon {
6037
+ width: 16px;
6038
+ height: 16px;
6039
+ flex: 0 0 16px;
6040
+ object-fit: contain;
6041
+ display: block;
6042
+ }
6043
+ .mobile-bottom-nav__identity-title {
4827
6044
  min-width: 0;
4828
- display: flex;
6045
+ overflow: hidden;
6046
+ text-overflow: ellipsis;
6047
+ white-space: nowrap;
6048
+ color: #0f172a;
6049
+ font-size: 12px;
6050
+ font-weight: 700;
6051
+ line-height: 1;
6052
+ }
6053
+ body.dark .mobile-bottom-nav__identity-title {
6054
+ color: rgba(248, 250, 252, 0.94);
6055
+ }
6056
+ .mobile-bottom-nav__identity-separator {
6057
+ width: 3px;
6058
+ height: 3px;
6059
+ flex: 0 0 3px;
6060
+ border-radius: 999px;
6061
+ background: rgba(100, 116, 139, 0.48);
6062
+ }
6063
+ body.dark .mobile-bottom-nav__identity-separator {
6064
+ background: rgba(148, 163, 184, 0.6);
6065
+ }
6066
+ .mobile-bottom-nav__identity-disk {
6067
+ flex: 0 0 auto;
6068
+ display: inline-flex;
4829
6069
  align-items: center;
4830
- justify-content: center;
4831
- min-height: 24px;
4832
- padding: 0 4px;
4833
- border-radius: 7px;
4834
- color: #475569;
4835
- text-decoration: none;
6070
+ min-width: 0;
6071
+ max-width: 78px;
6072
+ overflow: hidden;
6073
+ text-overflow: ellipsis;
6074
+ padding: 0;
6075
+ border: 0 !important;
6076
+ border-radius: 0;
6077
+ color: rgba(71, 85, 105, 0.74);
4836
6078
  font-size: 10px;
4837
- font-weight: 700;
6079
+ font-weight: 600;
4838
6080
  line-height: 1;
4839
- text-align: center;
4840
6081
  white-space: nowrap;
6082
+ cursor: default;
4841
6083
  }
4842
- .mobile-mode-segment.selected {
4843
- background: #111827;
4844
- color: #fff;
6084
+ body.dark .mobile-bottom-nav__identity-disk {
6085
+ color: rgba(203, 213, 225, 0.74);
4845
6086
  }
4846
- body.dark .mobile-mode-segment {
4847
- color: rgba(226, 232, 240, 0.88);
6087
+ .mobile-bottom-nav__identity-disk i {
6088
+ display: none;
4848
6089
  }
4849
- body.dark .mobile-mode-segment.selected {
4850
- background: #fff;
4851
- color: #0f1115;
6090
+ .mobile-bottom-nav .resource-usage {
6091
+ max-width: min(46vw, 190px);
4852
6092
  }
4853
- .appcanvas.community-mode .mobile-bottom-nav .mobile-mode-segment.selected {
4854
- background: transparent;
4855
- color: #475569;
6093
+ .mobile-bottom-nav .resource-usage-trigger {
6094
+ gap: 5px;
6095
+ overflow: hidden;
6096
+ }
6097
+ .mobile-bottom-nav .resource-chip {
6098
+ max-width: 82px;
6099
+ overflow: hidden;
6100
+ text-overflow: ellipsis;
6101
+ font-size: 10px;
6102
+ }
6103
+ .mobile-bottom-nav .resource-chip--cpu,
6104
+ .mobile-bottom-nav .resource-chip--ram,
6105
+ .mobile-bottom-nav .resource-chip--vram {
6106
+ display: none !important;
4856
6107
  }
4857
- body.dark .appcanvas.community-mode .mobile-bottom-nav .mobile-mode-segment.selected {
4858
- color: rgba(226, 232, 240, 0.88);
6108
+ .mobile-bottom-nav .resource-usage-popover {
6109
+ top: auto;
6110
+ bottom: calc(100% + 9px);
6111
+ right: -4px;
4859
6112
  }
4860
6113
  body.mobile-menu-open .appcanvas > aside {
4861
6114
  display: flex;
@@ -4868,6 +6121,16 @@ header.navheader .mode-selector .community-mode-toggle {
4868
6121
  z-index: 10000001;
4869
6122
  background: #f2f2f7;
4870
6123
  }
6124
+ body.mobile-menu-open .appcanvas.sidebar-collapsed > aside {
6125
+ width: 100%;
6126
+ min-width: 0;
6127
+ max-width: none;
6128
+ opacity: 1;
6129
+ pointer-events: auto;
6130
+ }
6131
+ body.mobile-menu-open .appcanvas.sidebar-collapsed > aside .menu-container {
6132
+ width: 100%;
6133
+ }
4871
6134
  body.dark.mobile-menu-open .appcanvas > aside {
4872
6135
  background: #0f1115;
4873
6136
  }
@@ -4876,6 +6139,7 @@ header.navheader .mode-selector .community-mode-toggle {
4876
6139
  flex-direction: column;
4877
6140
  align-items: stretch;
4878
6141
  gap: 8px;
6142
+ padding-top: 0;
4879
6143
  }
4880
6144
  body.mobile-menu-open .appcanvas > aside .config-info {
4881
6145
  padding: 6px 8px 2px;
@@ -4901,6 +6165,29 @@ header.navheader .mode-selector .community-mode-toggle {
4901
6165
  gap: 12px;
4902
6166
  box-sizing: border-box;
4903
6167
  }
6168
+ body.mobile-menu-open .appcanvas > aside .workspace-mode-switch,
6169
+ body.mobile-menu-open .mobile-menu-fallback .workspace-mode-switch {
6170
+ flex-direction: row;
6171
+ align-items: center;
6172
+ justify-content: center;
6173
+ gap: 2px;
6174
+ flex: 0 0 auto;
6175
+ width: 100%;
6176
+ min-height: 34px;
6177
+ margin: 0 0 8px;
6178
+ padding: 3px;
6179
+ border-radius: 8px;
6180
+ }
6181
+ body.mobile-menu-open .appcanvas > aside .workspace-mode-link,
6182
+ body.mobile-menu-open .mobile-menu-fallback .workspace-mode-link {
6183
+ flex: 1 1 0;
6184
+ width: auto;
6185
+ min-width: 0;
6186
+ min-height: 28px;
6187
+ padding: 0 8px;
6188
+ border-radius: 6px;
6189
+ font-size: 12px;
6190
+ }
4904
6191
  body.mobile-menu-open .appcanvas > aside .m {
4905
6192
  display: flex;
4906
6193
  flex-direction: column;
@@ -5049,6 +6336,21 @@ header.navheader .mode-selector .community-mode-toggle {
5049
6336
  padding: 0 12px 16px;
5050
6337
  box-sizing: border-box;
5051
6338
  }
6339
+ .mobile-menu-fallback .workspace-mode-switch {
6340
+ display: flex;
6341
+ align-items: center;
6342
+ width: 100%;
6343
+ margin: 0 0 12px;
6344
+ padding: 3px;
6345
+ border-radius: 7px;
6346
+ border: 1px solid rgba(15, 23, 42, 0.055);
6347
+ background: rgba(15, 23, 42, 0.035);
6348
+ box-sizing: border-box;
6349
+ }
6350
+ body.dark .mobile-menu-fallback .workspace-mode-switch {
6351
+ border-color: rgba(255, 255, 255, 0.075);
6352
+ background: rgba(255, 255, 255, 0.045);
6353
+ }
5052
6354
  body.mobile-menu-open .appcanvas .container {
5053
6355
  display: none;
5054
6356
  }
@@ -5056,6 +6358,7 @@ header.navheader .mode-selector .community-mode-toggle {
5056
6358
  display: none;
5057
6359
  }
5058
6360
  .appcanvas.panel-open .ask-ai-drawer,
6361
+ .appcanvas.logs-open .app-logs-drawer,
5059
6362
  .appcanvas.event-drawer-open .event-launch-drawer,
5060
6363
  .appcanvas.event-bottom-open .event-launch-drawer {
5061
6364
  display: flex;
@@ -5068,11 +6371,13 @@ header.navheader .mode-selector .community-mode-toggle {
5068
6371
  z-index: 10000000;
5069
6372
  }
5070
6373
  .appcanvas.panel-open .ask-ai-resizer,
6374
+ .appcanvas.logs-open .app-logs-resizer,
5071
6375
  .appcanvas.event-drawer-open .event-launch-resizer,
5072
6376
  .appcanvas.event-bottom-open .event-launch-resizer {
5073
6377
  display: none;
5074
6378
  }
5075
6379
  .appcanvas.panel-open main.browserview,
6380
+ .appcanvas.logs-open main.browserview,
5076
6381
  .appcanvas.event-drawer-open main.browserview,
5077
6382
  .appcanvas.event-bottom-open main.browserview {
5078
6383
  display: none;
@@ -5097,10 +6402,6 @@ header.navheader .mode-selector .community-mode-toggle {
5097
6402
  .appcanvas.community-mode .browserview-shell {
5098
6403
  flex: 1 1 auto;
5099
6404
  }
5100
- .appcanvas.community-mode header.navheader .mode-selector .btn2.selected:not(.community-mode-toggle) {
5101
- background: transparent;
5102
- color: inherit;
5103
- }
5104
6405
  }
5105
6406
  </style>
5106
6407
  <link href="/app.css" rel="stylesheet"/>
@@ -5153,8 +6454,13 @@ header.navheader .mode-selector .community-mode-toggle {
5153
6454
  <a class='home' href="/home">
5154
6455
  <img class='icon' src="/pinokio-black.png">
5155
6456
  </a>
5156
- <button class='btn2' id='minimize-header' data-tippy-content="fullscreen" title='fullscreen'>
5157
- <div><i class="fa-solid fa-expand"></i></div>
6457
+ <button class='btn2 sidebar-toggle' id='sidebar-toggle' type='button' data-tippy-content="Hide sidebar" title='Hide sidebar' aria-label="Hide sidebar" aria-controls="app-sidebar" aria-expanded="true">
6458
+ <span class="sidebar-toggle-glyph" aria-hidden="true">
6459
+ <span class="sidebar-toggle-frame">
6460
+ <span class="sidebar-toggle-panel"></span>
6461
+ <span class="sidebar-toggle-content"></span>
6462
+ </span>
6463
+ </span>
5158
6464
  </button>
5159
6465
  <button class='btn2' id='back' data-tippy-content="back"><div><i class="fa-solid fa-chevron-left"></i></div></button>
5160
6466
  <button class='btn2' id='forward' data-tippy-content="forward"><div><i class="fa-solid fa-chevron-right"></i></div></button>
@@ -5165,16 +6471,44 @@ header.navheader .mode-selector .community-mode-toggle {
5165
6471
  <div><i class="fa-solid fa-list"></i></div>
5166
6472
  </button>
5167
6473
  <div class='sep'></div>
5168
- <div class='mode-selector'>
5169
- <a class="btn2 <%=type === 'run' ? 'selected' : ''%>" href="<%=run_tab%>"><div><i class="fa-solid fa-circle-play"></i></div><div class='caption'>Run</div></a>
5170
- <a class="btn2 <%=type === 'browse' ? 'selected' : ''%>" href="<%=dev_tab%>"><div><i class="fa-solid fa-terminal"></i></div><div class='caption'>Dev</div></a>
5171
- <a class="btn2 <%=type === 'files' ? 'selected' : ''%>" href="<%=files_tab%>"><div><i class="fa-solid fa-file-lines"></i></div><div class='caption'>Files</div></a>
5172
- <% if (registryEnabled) { %>
5173
- <button type='button' id='community-mode-toggle' class="btn2 community-mode-toggle" data-tippy-content="Community" aria-pressed="false">
5174
- <div><i class="fa-solid fa-globe"></i></div><div class='caption'>Community</div>
5175
- </button>
6474
+ <div class='app-header-identity' title="<%=config.title || name%>">
6475
+ <% if (config.icon) { %>
6476
+ <img class='app-header-identity-icon' src="<%=config.icon%>" alt="" aria-hidden="true"/>
5176
6477
  <% } %>
6478
+ <div class='app-header-identity-title'><%=config.title || name%></div>
6479
+ <span class='app-header-identity-separator' aria-hidden="true"></span>
6480
+ <div class="resource-usage" data-resource-usage-root data-workspace="<%=name%>">
6481
+ <button type="button" class="resource-usage-trigger" data-resource-usage-trigger aria-haspopup="dialog" aria-expanded="false" aria-label="Resource usage">
6482
+ <span class="resource-chip resource-chip--disk">
6483
+ <span class="disk-usage app-header-identity-disk" data-path="/" data-filepath="<%=path%>" data-metric-label="Disk">--</span>
6484
+ </span>
6485
+ <span class="resource-chip resource-chip--cpu hidden" data-resource-chip="cpu">CPU --</span>
6486
+ <span class="resource-chip resource-chip--ram hidden" data-resource-chip="ram">RAM --</span>
6487
+ <span class="resource-chip resource-chip--vram hidden" data-resource-chip="vram">VRAM --</span>
6488
+ </button>
6489
+ <div class="resource-usage-popover hidden" data-resource-usage-popover role="dialog" aria-label="Resource usage settings">
6490
+ <div class="resource-usage-popover-head">Resources</div>
6491
+ <button type="button" class="resource-toggle" data-resource-toggle="show_ram" role="switch" aria-checked="true">
6492
+ <span class="resource-toggle-label">Show RAM</span>
6493
+ <span class="resource-toggle-track" aria-hidden="true"><span class="resource-toggle-thumb"></span></span>
6494
+ </button>
6495
+ <button type="button" class="resource-toggle" data-resource-toggle="show_vram" role="switch" aria-checked="true">
6496
+ <span class="resource-toggle-label">Show VRAM</span>
6497
+ <span class="resource-toggle-track" aria-hidden="true"><span class="resource-toggle-thumb"></span></span>
6498
+ </button>
6499
+ <button type="button" class="resource-toggle" data-resource-toggle="show_cpu" role="switch" aria-checked="false">
6500
+ <span class="resource-toggle-label">Show CPU</span>
6501
+ <span class="resource-toggle-track" aria-hidden="true"><span class="resource-toggle-thumb"></span></span>
6502
+ </button>
6503
+ <div class="resource-usage-status" data-resource-usage-status>Waiting for usage data</div>
6504
+ </div>
6505
+ </div>
5177
6506
  </div>
6507
+ <% if (registryEnabled) { %>
6508
+ <button type='button' id='community-mode-toggle' class="community-mode-toggle workspace-mode-community-proxy" aria-pressed="false" aria-hidden="true" tabindex="-1">
6509
+ <i class="fa-solid fa-globe"></i>
6510
+ </button>
6511
+ <% } %>
5178
6512
  <div class='flexible'></div>
5179
6513
  <a class='btn2' href="/columns" data-tippy-content="split into 2 columns">
5180
6514
  <div><i class="fa-solid fa-table-columns"></i></div>
@@ -5190,35 +6524,25 @@ header.navheader .mode-selector .community-mode-toggle {
5190
6524
  </button>
5191
6525
  </h1>
5192
6526
  </header>
5193
- <div class='appcanvas'>
5194
- <% if (error) { %>
5195
- <div class='error-message'>
5196
- <div>
5197
- <h1><i class="fa-solid fa-triangle-exclamation"></i> Error</h1>
5198
- <div><%=error%></div>
5199
- </div>
5200
- </div>
5201
- <% } else { %>
6527
+ <div class='appcanvas vertical <%=type === "browse" ? "dev-workspace" : ""%>' data-page-error="<%= error ? '1' : '0' %>">
5202
6528
  <% if (type !== 'files') { %>
5203
- <aside class='active'>
6529
+ <aside id='app-sidebar' class='active'>
5204
6530
  <div class='menu-container'>
5205
6531
  <div class='menu-scroller'>
5206
- <div class='config-info'>
5207
- <button type='button' id='menu-mobile-close' data-tippy-content="Back">
5208
- <i class="fa-solid fa-chevron-left"></i>
5209
- </button>
5210
- <button type='button' id='layout-toggle'>
5211
- <i class="fa-solid fa-bars"></i>
5212
- </button>
5213
- <% if (config.icon) { %>
5214
- <img class='meta-icon' src="<%=config.icon%>"/>
5215
- <% } %>
5216
- <% if (config.title) { %>
5217
- <div><%=config.title%></div>
5218
- <% } %>
6532
+ <div class='workspace-mode-switch' role="tablist" aria-label="Workspace mode">
6533
+ <a class="workspace-mode-link <%=type === 'run' ? 'selected' : ''%>" href="<%=run_tab%>" role="tab" aria-selected="<%=type === 'run' ? 'true' : 'false'%>">
6534
+ <i class="fa-solid fa-circle-play"></i>
6535
+ <span>Run</span>
6536
+ </a>
6537
+ <a class="workspace-mode-link <%=type === 'browse' || type === 'files' ? 'selected' : ''%>" href="<%=dev_tab%>" role="tab" aria-selected="<%=type === 'browse' || type === 'files' ? 'true' : 'false'%>">
6538
+ <i class="fa-solid fa-terminal"></i>
6539
+ <span>Dev</span>
6540
+ </a>
5219
6541
  </div>
5220
- <span class="disk-usage tab-metric__value" data-path="/" data-filepath="<%=path%>">--</span>
5221
6542
  <div class='mobile-sheet-actions' aria-label="Workspace actions">
6543
+ <button type='button' id='menu-mobile-close' class='btn mobile-sheet-action' data-tippy-content="Close menu" aria-label="Close menu" title="Close menu">
6544
+ <i class="fa-solid fa-xmark"></i>
6545
+ </button>
5222
6546
  <a class='btn mobile-sheet-action' href="/home" aria-label="Home" title="Home">
5223
6547
  <i class="fa-solid fa-house"></i>
5224
6548
  </a>
@@ -5244,11 +6568,16 @@ header.navheader .mode-selector .community-mode-toggle {
5244
6568
  <i class="fa-solid fa-xmark"></i>
5245
6569
  </button>
5246
6570
  </div>
5247
- <div class='m n system' data-type="n">
5248
- <%if (type==='browse') { %>
5249
- <a id='devtab' data-mode="refresh" target="<%=dev_link%>" href="<%=dev_link%>" class="btn frame-link selected" data-index="10">
6571
+ <%if (type==='browse') { %>
6572
+ <div class='m n system' data-type="n">
6573
+ <a id='devtab' data-mode="refresh" target="<%=dev_link%>" href="<%=dev_link%>" class="btn frame-link <%=dev_initial_tab === 'files' ? '' : 'selected'%>" data-index="10">
5250
6574
  <div class="tab">
5251
- <i class="fa-solid fa-bars"></i> Launchers
6575
+ <i class="fa-solid fa-bars"></i> Plugins
6576
+ </div>
6577
+ </a>
6578
+ <a id='filestab' target="pinokio-files" href="<%=editor_tab%>" class="btn frame-link <%=dev_initial_tab === 'files' ? 'selected' : ''%>" data-index="11">
6579
+ <div class="tab">
6580
+ <i class="fa-solid fa-file-lines"></i> Files
5252
6581
  </div>
5253
6582
  </a>
5254
6583
 
@@ -5259,9 +6588,48 @@ header.navheader .mode-selector .community-mode-toggle {
5259
6588
  <% } %>
5260
6589
  </div>
5261
6590
  </div>
5262
- <% } %>
5263
- </div>
6591
+ </div>
6592
+ <% } %>
5264
6593
  <%if (type === 'run') { %>
6594
+ <% if (typeof autolaunch_app !== 'undefined' && autolaunch_app) { %>
6595
+ <div class="app-autolaunch" data-app-autolaunch data-app-id="<%=name%>">
6596
+ <button type="button" class="app-autolaunch-row" data-app-autolaunch-button data-enabled="<%= autolaunch_app.autolaunch_enabled ? 'true' : 'false' %>" aria-haspopup="dialog" aria-expanded="false">
6597
+ <span class="app-autolaunch-label"><i class="fa-solid fa-power-off"></i><span>Auto</span></span>
6598
+ <span class="app-autolaunch-spacer" aria-hidden="true"></span>
6599
+ <span class="app-autolaunch-status" data-app-autolaunch-status><%= autolaunch_app.autolaunch_enabled ? 'ON' : 'OFF' %></span>
6600
+ <i class="fa-solid fa-angle-down app-autolaunch-chevron" aria-hidden="true"></i>
6601
+ </button>
6602
+ <div class="app-autolaunch-modal hidden" data-app-autolaunch-modal role="dialog" aria-modal="true" aria-label="Auto launch">
6603
+ <div class="app-autolaunch-panel" data-app-autolaunch-panel>
6604
+ <div class="app-autolaunch-modal-head">
6605
+ <div>
6606
+ <div class="app-autolaunch-title">Auto launch</div>
6607
+ <div class="app-autolaunch-note">Run this app when Pinokio starts.</div>
6608
+ </div>
6609
+ <div class="app-autolaunch-modal-actions">
6610
+ <button type="button" class="app-autolaunch-switch" role="switch" aria-checked="<%= autolaunch_app.autolaunch_enabled ? 'true' : 'false' %>" data-app-autolaunch-switch aria-label="Auto launch" title="Auto launch is <%= autolaunch_app.autolaunch_enabled ? 'on' : 'off' %>">
6611
+ <span class="app-autolaunch-switch-label" data-app-autolaunch-switch-label><%= autolaunch_app.autolaunch_enabled ? 'ON' : 'OFF' %></span>
6612
+ <span class="app-autolaunch-switch-thumb" aria-hidden="true"></span>
6613
+ </button>
6614
+ <button type="button" class="app-autolaunch-close" data-app-autolaunch-close aria-label="Close">
6615
+ <i class="fa-solid fa-xmark" aria-hidden="true"></i>
6616
+ </button>
6617
+ </div>
6618
+ </div>
6619
+ <div class="app-autolaunch-modal-section">
6620
+ <div class="app-autolaunch-section-label">Script</div>
6621
+ <div class="app-autolaunch-script-list" data-app-autolaunch-scripts>
6622
+ <div class="app-autolaunch-empty">Loading scripts...</div>
6623
+ </div>
6624
+ </div>
6625
+ <div class="app-autolaunch-footer">
6626
+ <div class="app-autolaunch-feedback" data-app-autolaunch-feedback></div>
6627
+ <a class="app-autolaunch-link" href="/autolaunch">Manage all</a>
6628
+ </div>
6629
+ </div>
6630
+ </div>
6631
+ </div>
6632
+ <% } %>
5265
6633
  <div class='m h menu' data-type='h'>
5266
6634
  <% if (config.menu) { %>
5267
6635
  <%- include('./partials/menu', { menu: config.menu, }) %>
@@ -5289,7 +6657,10 @@ header.navheader .mode-selector .community-mode-toggle {
5289
6657
  <%})%>
5290
6658
  </div>
5291
6659
  </div>
5292
- <div class='menu-actions'>
6660
+ <div class='menu-actions <%= type === "browse" ? "dev-menu-actions" : "" %>'>
6661
+ <% if (type === 'browse') { %>
6662
+ <%- include('./partials/fs_status') %>
6663
+ <% } %>
5293
6664
  <% if (type === 'run') { %>
5294
6665
  <button type='button' id='ask-ai-tab' class="btn header-item" data-static="ask-ai" data-workspace="<%=name%>" data-workspace-cwd="<%=path%>" data-ask-ai-trigger="true" data-tippy-content="Ask AI">
5295
6666
  <div class='tab'>
@@ -5299,6 +6670,14 @@ header.navheader .mode-selector .community-mode-toggle {
5299
6670
  </div>
5300
6671
  </button>
5301
6672
  <% } %>
6673
+ <button type='button' id='logs-toggle' class="btn header-item" data-tippy-content="logs" aria-expanded="false" aria-controls="app-logs-drawer">
6674
+ <div class='tab'>
6675
+ <i class="fa-solid fa-laptop-code menu-action-leading-icon"></i>
6676
+ <div class='display'>Logs</div>
6677
+ <div class='flexible'></div>
6678
+ <span class='logs-new-badge hidden' id='logs-new-badge'>New</span>
6679
+ </div>
6680
+ </button>
5302
6681
  <% if (registryEnabled) { %>
5303
6682
  <button type='button' id='community-toggle' class="btn header-item" data-tippy-content="community">
5304
6683
  <div class='tab'>
@@ -5317,63 +6696,7 @@ header.navheader .mode-selector .community-mode-toggle {
5317
6696
  <div class='appcanvas_filler'></div>
5318
6697
  <% } %>
5319
6698
  <div class='container'>
5320
- <% if (type === "browse") { %>
5321
- <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%>">
5322
- <!--
5323
- <div class='fs-status-dropdown nested-menu git blue'>
5324
- <button type='button' class='fs-status-btn frame-link reveal'>
5325
- <span class='fs-status-label'>
5326
- <i class="fa-brands fa-git-alt"></i>
5327
- Git
5328
- </span>
5329
- </button>
5330
- <div class='fs-dropdown-menu submenu hidden' id='git-repos'>
5331
- </div>
5332
- </div>
5333
- -->
5334
- <div class='fs-status-dropdown fs-open-explorer'>
5335
- <button class='fs-status-btn' data-filepath="<%=path%>" type='button'>
5336
- <span class='fs-status-label'>
5337
- <i class="fa-solid fa-folder-open"></i>
5338
- <span class='fs-status-title'>Open in File Explorer</span>
5339
- </span>
5340
- </button>
5341
- </div>
5342
- <div class='fs-status-dropdown fs-logs'>
5343
- <button class='fs-status-btn' type='button'>
5344
- <span class='fs-status-label'>
5345
- <i class="fa-solid fa-laptop-code"></i>
5346
- <span class='fs-status-title'>Logs</span>
5347
- </span>
5348
- </button>
5349
- </div>
5350
- <div class='fs-status-dropdown git-changes'>
5351
- <button id='fs-changes-btn' class='fs-status-btn revealer' data-group='#fs-changes-menu' type='button'>
5352
- <span class='fs-status-label'><i class="fa-solid fa-code-compare"></i> Changes</span>
5353
- <div class='badge'></div>
5354
- </button>
5355
- <div class='fs-dropdown-menu submenu hidden' id='fs-changes-menu'></div>
5356
- </div>
5357
- <div class='fs-status-dropdown git-fork'>
5358
- <button id='fs-fork-btn' class='fs-status-btn revealer' data-group='#fs-fork-menu' type='button'>
5359
- <span class='fs-status-label'>
5360
- <i class="fa-solid fa-code-branch"></i>
5361
- <span class='fs-status-title'>Fork</span>
5362
- </span>
5363
- </button>
5364
- <div class='fs-dropdown-menu submenu hidden' id='fs-fork-menu'></div>
5365
- </div>
5366
- <div class='fs-status-dropdown git-publish'>
5367
- <button id='fs-push-btn' class='fs-status-btn revealer' data-group='#fs-push-menu' type='button'>
5368
- <span class='fs-status-label'>
5369
- <i class="fa-brands fa-github"></i>
5370
- <span class='fs-status-title'>Publish</span>
5371
- </span>
5372
- </button>
5373
- <div class='fs-dropdown-menu submenu hidden' id='fs-push-menu'></div>
5374
- </div>
5375
- </div>
5376
- <% } else if (type === 'files') { %>
6699
+ <% if (type === 'files') { %>
5377
6700
  <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%>">
5378
6701
  <!--
5379
6702
  <div class='fs-status-dropdown nested-menu git blue'>
@@ -5387,7 +6710,6 @@ header.navheader .mode-selector .community-mode-toggle {
5387
6710
  </div>
5388
6711
  </div>
5389
6712
  -->
5390
- <span class="disk-usage tab-metric__value" data-path="/" data-filepath="<%=path%>">--</span>
5391
6713
  <div class='fs-status-dropdown fs-open-explorer'>
5392
6714
  <button class='fs-status-btn' data-filepath="<%=path%>" type='button'>
5393
6715
  <span class='fs-status-label'>
@@ -5465,9 +6787,9 @@ header.navheader .mode-selector .community-mode-toggle {
5465
6787
  </div>
5466
6788
  <div class='ask-ai-drawer-body'>
5467
6789
  <div class='ask-ai-drawer-empty'>
5468
- <div class='ask-ai-drawer-empty-title'>Pick a plugin to launch</div>
5469
- <div class='ask-ai-drawer-picker-list' role="list" aria-label="Ask AI plugins"></div>
5470
- <div class='ask-ai-drawer-picker-status muted'>Use the Ask AI button to load available plugins.</div>
6790
+ <div class='ask-ai-drawer-empty-title'>Pick a shell or plugin to launch</div>
6791
+ <div class='ask-ai-drawer-picker-list' role="list" aria-label="Ask AI launch options"></div>
6792
+ <div class='ask-ai-drawer-picker-status muted'>Use the Ask AI button to load launch options.</div>
5471
6793
  </div>
5472
6794
  <iframe class='ask-ai-frame hidden' title="Panel" allow="fullscreen *;" allowfullscreen></iframe>
5473
6795
  </div>
@@ -5498,6 +6820,53 @@ header.navheader .mode-selector .community-mode-toggle {
5498
6820
  </div>
5499
6821
  </aside>
5500
6822
  <% } %>
6823
+ <aside class='app-logs-drawer' id='app-logs-drawer' data-app-id="<%=name%>" data-report-url="/apps/logs/<%= encodeURIComponent(name) %>/report" data-full-logs-url="/logs?workspace=<%= encodeURIComponent(name) %>" data-community-available="<%= registryEnabled && community_url ? '1' : '0' %>" data-page-error="<%= error ? '1' : '0' %>" aria-hidden="true">
6824
+ <div class='app-logs-drawer-inner'>
6825
+ <div class='app-logs-drawer-bar'>
6826
+ <div class='app-logs-drawer-title'>
6827
+ <i class="fa-solid fa-laptop-code" aria-hidden="true"></i>
6828
+ <span>Logs</span>
6829
+ </div>
6830
+ <div class='app-logs-drawer-actions'>
6831
+ <button type='button' class='btn2 app-logs-refresh' data-tippy-content="Refresh" aria-label="Refresh report">
6832
+ <i class="fa-solid fa-rotate-right"></i>
6833
+ </button>
6834
+ <button type='button' class='btn2 app-logs-close' data-tippy-content="Close panel" aria-label="Close logs panel">
6835
+ <i class="fa-solid fa-xmark"></i>
6836
+ </button>
6837
+ </div>
6838
+ </div>
6839
+ <div class='app-logs-body'>
6840
+ <% if (error) { %>
6841
+ <div class='app-logs-error-callout' role="alert">
6842
+ <i class="fa-solid fa-triangle-exclamation" aria-hidden="true"></i>
6843
+ <div>
6844
+ <div class='app-logs-error-callout-title'>Error detected</div>
6845
+ <div class='app-logs-error-callout-message'><%=error%></div>
6846
+ </div>
6847
+ </div>
6848
+ <% } %>
6849
+ <div class='app-logs-toolbar'>
6850
+ <button type='button' class='btn app-logs-copy' disabled>
6851
+ <i class="fa-solid fa-copy"></i> Copy report
6852
+ </button>
6853
+ </div>
6854
+ <div class='app-logs-status' role="status" aria-live="polite">Ready.</div>
6855
+ <div class='app-logs-summary' aria-label="Log report summary">
6856
+ <div class='app-logs-summary-item'>
6857
+ <div class='app-logs-summary-label'>Sections</div>
6858
+ <div class='app-logs-summary-value' data-app-logs-summary="sections">--</div>
6859
+ </div>
6860
+ <div class='app-logs-summary-item'>
6861
+ <div class='app-logs-summary-label'>Redactions</div>
6862
+ <div class='app-logs-summary-value' data-app-logs-summary="redactions">--</div>
6863
+ </div>
6864
+ </div>
6865
+ <div class='app-logs-section-list' aria-label="Included log files"></div>
6866
+ <pre class='app-logs-report-output' tabindex="0"></pre>
6867
+ </div>
6868
+ </div>
6869
+ </aside>
5501
6870
  <% if (registryEnabled) { %>
5502
6871
  <div class='community-resizer' id='community-resizer' role="separator" aria-orientation="vertical" aria-valuemin="240" aria-valuemax="9999" aria-valuenow="360" tabindex="0" aria-label="Resize community panel"></div>
5503
6872
  <aside class='community-drawer' id='community-drawer' data-community-url="<%=community_url%>">
@@ -5569,22 +6938,53 @@ header.navheader .mode-selector .community-mode-toggle {
5569
6938
  </div>
5570
6939
  </div>
5571
6940
  </div>
5572
- <% } %>
5573
- </div>
5574
- <nav class='mobile-bottom-nav' aria-label="Mobile navigation">
5575
- <button type='button' class='btn2 mobile-bottom-nav__button mobile-nav-menu' data-mobile-proxy="#menu-mobile" aria-expanded="false" aria-label="Open Pinokio menu">
5576
- <img class='mobile-bottom-nav__logo' src="/pinokio-black.png" alt="" aria-hidden="true">
5577
- </button>
5578
- <div class='mobile-bottom-nav__modes' role="tablist" aria-label="Workspace mode">
5579
- <a class="mobile-mode-segment <%=type === 'run' ? 'selected' : ''%>" href="<%=run_tab%>" role="tab" data-mobile-mode="run" aria-selected="<%=type === 'run' ? 'true' : 'false'%>">Run</a>
5580
- <a class="mobile-mode-segment <%=type === 'browse' ? 'selected' : ''%>" href="<%=dev_tab%>" role="tab" data-mobile-mode="browse" aria-selected="<%=type === 'browse' ? 'true' : 'false'%>">Dev</a>
5581
- <a class="mobile-mode-segment <%=type === 'files' ? 'selected' : ''%>" href="<%=files_tab%>" role="tab" data-mobile-mode="files" aria-selected="<%=type === 'files' ? 'true' : 'false'%>">Files</a>
6941
+ <% } %>
6942
+ </div>
6943
+ <nav class='mobile-bottom-nav' aria-label="Mobile navigation">
6944
+ <button type='button' class='btn2 mobile-bottom-nav__button mobile-nav-menu' data-mobile-proxy="#menu-mobile" aria-expanded="false" aria-label="Open Pinokio menu">
6945
+ <img class='mobile-bottom-nav__logo' src="/pinokio-black.png" alt="" aria-hidden="true">
6946
+ </button>
6947
+ <div class='mobile-bottom-nav__identity' title="<%=config.title || name%>" aria-label="<%=config.title || name%>">
6948
+ <% if (config.icon) { %>
6949
+ <img class='mobile-bottom-nav__identity-icon' src="<%=config.icon%>" alt="" aria-hidden="true"/>
6950
+ <% } %>
6951
+ <span class='mobile-bottom-nav__identity-title'><%=config.title || name%></span>
6952
+ <span class='mobile-bottom-nav__identity-separator' aria-hidden="true"></span>
6953
+ <div class="resource-usage" data-resource-usage-root data-workspace="<%=name%>">
6954
+ <button type="button" class="resource-usage-trigger" data-resource-usage-trigger aria-haspopup="dialog" aria-expanded="false" aria-label="Resource usage">
6955
+ <span class="resource-chip resource-chip--disk">
6956
+ <span class="disk-usage mobile-bottom-nav__identity-disk" data-path="/" data-filepath="<%=path%>" data-metric-label="Disk">--</span>
6957
+ </span>
6958
+ <span class="resource-chip resource-chip--cpu hidden" data-resource-chip="cpu">CPU --</span>
6959
+ <span class="resource-chip resource-chip--ram hidden" data-resource-chip="ram">RAM --</span>
6960
+ <span class="resource-chip resource-chip--vram hidden" data-resource-chip="vram">VRAM --</span>
6961
+ </button>
6962
+ <div class="resource-usage-popover hidden" data-resource-usage-popover role="dialog" aria-label="Resource usage settings">
6963
+ <div class="resource-usage-popover-head">Resources</div>
6964
+ <button type="button" class="resource-toggle" data-resource-toggle="show_ram" role="switch" aria-checked="true">
6965
+ <span class="resource-toggle-label">Show RAM</span>
6966
+ <span class="resource-toggle-track" aria-hidden="true"><span class="resource-toggle-thumb"></span></span>
6967
+ </button>
6968
+ <button type="button" class="resource-toggle" data-resource-toggle="show_vram" role="switch" aria-checked="true">
6969
+ <span class="resource-toggle-label">Show VRAM</span>
6970
+ <span class="resource-toggle-track" aria-hidden="true"><span class="resource-toggle-thumb"></span></span>
6971
+ </button>
6972
+ <button type="button" class="resource-toggle" data-resource-toggle="show_cpu" role="switch" aria-checked="false">
6973
+ <span class="resource-toggle-label">Show CPU</span>
6974
+ <span class="resource-toggle-track" aria-hidden="true"><span class="resource-toggle-thumb"></span></span>
6975
+ </button>
6976
+ <div class="resource-usage-status" data-resource-usage-status>Waiting for usage data</div>
6977
+ </div>
6978
+ </div>
5582
6979
  </div>
5583
6980
  <% if (registryEnabled) { %>
5584
6981
  <button type='button' class='btn2 mobile-bottom-nav__button mobile-nav-community' data-mobile-proxy="#community-mode-toggle" data-mobile-close-menu="true" aria-pressed="false" aria-label="Community">
5585
6982
  <i class="fa-solid fa-globe"></i>
5586
6983
  </button>
5587
6984
  <% } %>
6985
+ <button type='button' class='btn2 mobile-bottom-nav__button mobile-nav-logs' data-mobile-proxy="#logs-toggle" data-mobile-close-menu="true" aria-pressed="false" aria-label="Logs">
6986
+ <i class="fa-solid fa-laptop-code"></i>
6987
+ </button>
5588
6988
  <% if (type === 'run') { %>
5589
6989
  <button type='button' class='btn2 mobile-bottom-nav__button mobile-nav-ask-ai' data-mobile-proxy="#ask-ai-tab" data-mobile-close-menu="true" aria-pressed="false" aria-label="Ask AI">
5590
6990
  <i class="fa-solid fa-robot"></i>
@@ -5595,12 +6995,22 @@ header.navheader .mode-selector .community-mode-toggle {
5595
6995
  <div class='mobile-menu-fallback' aria-hidden="true">
5596
6996
  <div class='mobile-menu-fallback__header'>
5597
6997
  <button type='button' class='btn2' data-mobile-proxy="#menu-mobile" aria-label="Close menu">
5598
- <i class="fa-solid fa-chevron-left"></i>
6998
+ <i class="fa-solid fa-xmark"></i>
5599
6999
  </button>
5600
7000
  <img class='mobile-menu-fallback__logo' src="/pinokio-black.png" alt="" aria-hidden="true">
5601
7001
  <div>Pinokio</div>
5602
7002
  </div>
5603
7003
  <div class='mobile-menu-fallback__body'>
7004
+ <div class='workspace-mode-switch' role="tablist" aria-label="Workspace mode">
7005
+ <a class="workspace-mode-link <%=type === 'run' ? 'selected' : ''%>" href="<%=run_tab%>" role="tab" aria-selected="<%=type === 'run' ? 'true' : 'false'%>">
7006
+ <i class="fa-solid fa-circle-play"></i>
7007
+ <span>Run</span>
7008
+ </a>
7009
+ <a class="workspace-mode-link <%=type === 'browse' || type === 'files' ? 'selected' : ''%>" href="<%=dev_tab%>" role="tab" aria-selected="<%=type === 'browse' || type === 'files' ? 'true' : 'false'%>">
7010
+ <i class="fa-solid fa-terminal"></i>
7011
+ <span>Dev</span>
7012
+ </a>
7013
+ </div>
5604
7014
  <div class='mobile-sheet-actions' aria-label="Workspace actions">
5605
7015
  <a class='btn mobile-sheet-action' href="/home" aria-label="Home" title="Home">
5606
7016
  <i class="fa-solid fa-house"></i>
@@ -5635,7 +7045,16 @@ header.navheader .mode-selector .community-mode-toggle {
5635
7045
  let cursorIndex = 0;
5636
7046
  let interacted = false
5637
7047
  let global_selector
7048
+ <% if (type === 'browse' && dev_initial_tab === 'files') { %>
7049
+ global_selector = "#filestab.frame-link"
7050
+ try {
7051
+ const devTabUrl = new URL(window.location.href)
7052
+ devTabUrl.searchParams.delete("pinokio_dev_tab")
7053
+ window.history.replaceState(window.history.state, "", `${devTabUrl.pathname}${devTabUrl.search}${devTabUrl.hash}`)
7054
+ } catch (_) {}
7055
+ <% } %>
5638
7056
  let pendingSelectionRetry = null
7057
+ let pendingHomeSelectionRetry = null
5639
7058
  let initialWorkspaceDiskUsageRequested = false
5640
7059
  const scheduleSelectionRetry = (delay = 100) => {
5641
7060
  if (pendingSelectionRetry !== null) {
@@ -5654,7 +7073,35 @@ header.navheader .mode-selector .community-mode-toggle {
5654
7073
  return false
5655
7074
  }
5656
7075
  })()
5657
- let ignorePersistedSelection = pluginLaunchActive
7076
+ const HOME_SELECTION_PARAM = "pinokio_home_select"
7077
+ const consumeInitialHomeSelectionPayload = () => {
7078
+ let params
7079
+ let raw
7080
+ try {
7081
+ params = new URLSearchParams(window.location.search)
7082
+ raw = params.get(HOME_SELECTION_PARAM)
7083
+ } catch (_) {
7084
+ return null
7085
+ }
7086
+ if (!raw) {
7087
+ return null
7088
+ }
7089
+ try {
7090
+ params.delete(HOME_SELECTION_PARAM)
7091
+ const query = params.toString()
7092
+ const nextUrl = `${window.location.pathname}${query ? `?${query}` : ""}${window.location.hash || ""}`
7093
+ window.history.replaceState(window.history.state, "", nextUrl)
7094
+ } catch (_) {}
7095
+ try {
7096
+ const parsed = JSON.parse(raw)
7097
+ return parsed && typeof parsed === "object" ? parsed : null
7098
+ } catch (_) {
7099
+ return null
7100
+ }
7101
+ }
7102
+ let initialHomeSelectionPayload = consumeInitialHomeSelectionPayload()
7103
+ let initialHomeSelectionRetries = 0
7104
+ let ignorePersistedSelection = pluginLaunchActive || Boolean(initialHomeSelectionPayload)
5658
7105
  let lastForegroundSignature = null
5659
7106
  const browserPopoutSurface = window.createBrowserPopoutSurface({
5660
7107
  onShow: () => {
@@ -5861,6 +7308,27 @@ header.navheader .mode-selector .community-mode-toggle {
5861
7308
  }
5862
7309
  return value.replace(/([ #;?%&,.+*~':"!^$\\\[\]()=>|\/])/g, '\\$1')
5863
7310
  }
7311
+ const escapeTabHtml = (value) => {
7312
+ if (value === null || value === undefined) {
7313
+ return ""
7314
+ }
7315
+ return String(value).replace(/[&<>"']/g, (match) => {
7316
+ switch (match) {
7317
+ case "&":
7318
+ return "&amp;"
7319
+ case "<":
7320
+ return "&lt;"
7321
+ case ">":
7322
+ return "&gt;"
7323
+ case "\"":
7324
+ return "&quot;"
7325
+ case "'":
7326
+ return "&#39;"
7327
+ default:
7328
+ return match
7329
+ }
7330
+ })
7331
+ }
5864
7332
  const previewClass = "tab-preview"
5865
7333
  const tabMainClass = "tab-main"
5866
7334
  const tabDetailsClass = "tab-details"
@@ -6089,11 +7557,66 @@ header.navheader .mode-selector .community-mode-toggle {
6089
7557
  if (!node && payload.target) {
6090
7558
  node = trySelector(`.frame-link[target='${escapeTargetSelector(payload.target)}']`)
6091
7559
  }
7560
+ const dataLookups = [
7561
+ ['dataTargetFull', 'data-target-full'],
7562
+ ['dataShell', 'data-shell'],
7563
+ ['dataScript', 'data-script'],
7564
+ ['dataAction', 'data-action'],
7565
+ ['dataRun', 'data-run'],
7566
+ ['dataCommand', 'data-command'],
7567
+ ['dataFilepath', 'data-filepath']
7568
+ ]
7569
+ for (const [key, attr] of dataLookups) {
7570
+ if (!node && payload[key]) {
7571
+ node = trySelector(`.frame-link[${attr}='${escapeTargetSelector(payload[key])}']`)
7572
+ }
7573
+ }
6092
7574
  if (!node && payload.href) {
6093
7575
  node = findLinkByAbsoluteHref(payload.href)
6094
7576
  }
7577
+ if (!node && payload.textValue) {
7578
+ node = Array.from(document.querySelectorAll('.frame-link')).find((candidate) => {
7579
+ const tab = candidate.querySelector('.tab')
7580
+ return tab && tab.textContent.trim() === payload.textValue
7581
+ }) || null
7582
+ }
6095
7583
  return node
6096
7584
  }
7585
+ const scheduleInitialHomeSelectionRetry = (delay = 100) => {
7586
+ if (pendingHomeSelectionRetry !== null) {
7587
+ return
7588
+ }
7589
+ pendingHomeSelectionRetry = setTimeout(() => {
7590
+ pendingHomeSelectionRetry = null
7591
+ dispatchInitialHomeSelection()
7592
+ }, delay)
7593
+ }
7594
+ const dispatchInitialHomeSelection = () => {
7595
+ if (!initialHomeSelectionPayload) {
7596
+ return false
7597
+ }
7598
+ const target = restorePersistedFrameLink(initialHomeSelectionPayload)
7599
+ if (!target) {
7600
+ initialHomeSelectionRetries += 1
7601
+ if (initialHomeSelectionRetries <= 20) {
7602
+ scheduleInitialHomeSelectionRetry()
7603
+ } else {
7604
+ initialHomeSelectionPayload = null
7605
+ ignorePersistedSelection = pluginLaunchActive
7606
+ renderSelection({ force: true })
7607
+ }
7608
+ return false
7609
+ }
7610
+ initialHomeSelectionPayload = null
7611
+ initialHomeSelectionRetries = 0
7612
+ ignorePersistedSelection = pluginLaunchActive
7613
+ target.dispatchEvent(new MouseEvent("click", {
7614
+ bubbles: true,
7615
+ cancelable: true,
7616
+ view: window
7617
+ }))
7618
+ return true
7619
+ }
6097
7620
  const persistTabStateStore = () => {
6098
7621
  const storage = getWindowStorage()
6099
7622
  if (!storage) {
@@ -6314,7 +7837,7 @@ header.navheader .mode-selector .community-mode-toggle {
6314
7837
  const href = typeof launch.href === "string" ? launch.href : ""
6315
7838
  try {
6316
7839
  const parsed = new URL(href, window.location.origin)
6317
- if (parsed.pathname.startsWith("/run/plugin/") || (parsed.pathname.startsWith("/run/api/") && /\/pinokio\.js$/i.test(parsed.pathname))) {
7840
+ if (parsed.pathname.startsWith("/run/plugin/") || parsed.pathname.startsWith("/pinokio/run/plugin/") || (parsed.pathname.startsWith("/run/api/") && /\/pinokio\.js$/i.test(parsed.pathname))) {
6318
7841
  const parts = parsed.pathname.split("/").filter(Boolean)
6319
7842
  const pluginSlug = parts.length >= 2 ? parts[parts.length - 2] : ""
6320
7843
  const pluginLabel = titleCaseSlug(pluginSlug)
@@ -7185,6 +8708,9 @@ const rerenderMenuSection = (container, html) => {
7185
8708
 
7186
8709
  if (!target) {
7187
8710
  syncActiveNestedPath()
8711
+ if (typeof syncLogsSelectedState === "function") {
8712
+ syncLogsSelectedState(null)
8713
+ }
7188
8714
  browserPopoutSurface.hide()
7189
8715
  document.querySelector(".container").classList.remove("active")
7190
8716
  document.querySelector("aside").classList.add("active")
@@ -7218,6 +8744,9 @@ const rerenderMenuSection = (container, html) => {
7218
8744
  })
7219
8745
  target.classList.add("selected")
7220
8746
  syncActiveNestedPath({ selectedLink: target })
8747
+ if (typeof syncLogsSelectedState === "function") {
8748
+ syncLogsSelectedState(target)
8749
+ }
7221
8750
  if (skipPersistedSelection && target.hasAttribute('data-default')) {
7222
8751
  ignorePersistedSelection = false
7223
8752
  }
@@ -7521,11 +9050,12 @@ const rerenderMenuSection = (container, html) => {
7521
9050
  tabs
7522
9051
  })
7523
9052
  })
7524
- }
9053
+ }
7525
9054
  const addTab = async (url, options = {}) => {
7526
9055
  const label = typeof options.label === 'string' && options.label.trim().length > 0 ? options.label.trim() : null
7527
9056
  const providedTarget = typeof options.target === 'string' && options.target.trim().length > 0 ? options.target.trim() : null
7528
- const shouldPersist = options.persist !== false
9057
+ const internalTab = options.internal === true
9058
+ const shouldPersist = options.persist !== false && !internalTab
7529
9059
  const isShellTab = (() => {
7530
9060
  try {
7531
9061
  return new URL(url, window.location.origin).pathname.startsWith("/shell/")
@@ -7543,6 +9073,12 @@ const rerenderMenuSection = (container, html) => {
7543
9073
  item.href = url
7544
9074
  item.setAttribute("data-index", index)
7545
9075
  item.className = "btn header-item frame-link"
9076
+ if (internalTab) {
9077
+ item.classList.add("hidden")
9078
+ item.dataset.internal = "true"
9079
+ item.setAttribute("aria-hidden", "true")
9080
+ item.tabIndex = -1
9081
+ }
7546
9082
  if (isShellTab) {
7547
9083
  item.dataset.shellTab = "1"
7548
9084
  }
@@ -7551,8 +9087,8 @@ const rerenderMenuSection = (container, html) => {
7551
9087
  }
7552
9088
  const displayText = label || url
7553
9089
  item.innerHTML = isShellTab
7554
- ? `<div class='tab'><i class="fa-solid fa-link"></i><div class='display'>${escapeHtml(displayText)}</div><div class='flexible'></div></div>`
7555
- : `<div class='tab'><i class="fa-solid fa-link"></i><div class='display'>${escapeHtml(displayText)}</div><div class='flexible'></div><button class='btn2 del'><i class="fa-solid fa-xmark"></i></button></div>`
9090
+ ? `<div class='tab'><i class="fa-solid fa-link"></i><div class='display'>${escapeTabHtml(displayText)}</div><div class='flexible'></div></div>`
9091
+ : `<div class='tab'><i class="fa-solid fa-link"></i><div class='display'>${escapeTabHtml(displayText)}</div><div class='flexible'></div><button class='btn2 del'><i class="fa-solid fa-xmark"></i></button></div>`
7556
9092
 
7557
9093
  document.querySelector(".temp-menu").appendChild(item)
7558
9094
 
@@ -7576,11 +9112,239 @@ const rerenderMenuSection = (container, html) => {
7576
9112
  }
7577
9113
 
7578
9114
 
7579
- return {
7580
- tab: item,
7581
- frame
7582
- }
9115
+ return {
9116
+ tab: item,
9117
+ frame
9118
+ }
7583
9119
 
9120
+ }
9121
+ const appLogsName = <%- JSON.stringify(name || "") %>
9122
+ const logsNotificationKey = `pinokio.logs.new:${appLogsName}`
9123
+ const logsToggleButton = document.getElementById("logs-toggle")
9124
+ const logsNewBadge = document.getElementById("logs-new-badge")
9125
+ const mobileLogsButton = document.querySelector(".mobile-nav-logs")
9126
+ const setLogsSelected = (active) => {
9127
+ const selected = Boolean(active)
9128
+ if (logsToggleButton) {
9129
+ logsToggleButton.classList.toggle("selected", selected)
9130
+ logsToggleButton.setAttribute("aria-expanded", selected ? "true" : "false")
9131
+ }
9132
+ if (mobileLogsButton) {
9133
+ mobileLogsButton.classList.toggle("selected", selected)
9134
+ mobileLogsButton.setAttribute("aria-pressed", selected ? "true" : "false")
9135
+ }
9136
+ }
9137
+ const setLogsNewBadge = (active) => {
9138
+ const show = Boolean(active)
9139
+ if (logsToggleButton) {
9140
+ logsToggleButton.classList.toggle("has-new", show)
9141
+ }
9142
+ if (logsNewBadge) {
9143
+ logsNewBadge.classList.toggle("hidden", !show)
9144
+ }
9145
+ if (mobileLogsButton) {
9146
+ mobileLogsButton.classList.toggle("has-new", show)
9147
+ mobileLogsButton.setAttribute("aria-label", show ? "Logs, new issue" : "Logs")
9148
+ }
9149
+ try {
9150
+ if (show) {
9151
+ window.sessionStorage.setItem(logsNotificationKey, "1")
9152
+ } else {
9153
+ window.sessionStorage.removeItem(logsNotificationKey)
9154
+ }
9155
+ } catch (_) {}
9156
+ }
9157
+ const buildEmbeddedLogsUrl = (view = "latest") => {
9158
+ const url = new URL("/logs", window.location.origin)
9159
+ url.searchParams.set("workspace", appLogsName)
9160
+ url.searchParams.set("embed", "1")
9161
+ url.searchParams.set("view", view === "raw" ? "raw" : "latest")
9162
+ return `${url.pathname}${url.search}`
9163
+ }
9164
+ const isLogsLink = (link) => {
9165
+ if (!link) return false
9166
+ try {
9167
+ const parsed = new URL(link.href, window.location.origin)
9168
+ return parsed.pathname === "/logs" && parsed.searchParams.get("workspace") === appLogsName
9169
+ } catch (_) {
9170
+ return false
9171
+ }
9172
+ }
9173
+ let lastNonLogsSelectedLink = null
9174
+ const isSelectableLogsFallbackLink = (link) => {
9175
+ return Boolean(
9176
+ link &&
9177
+ link.classList &&
9178
+ link.classList.contains("frame-link") &&
9179
+ !isLogsLink(link) &&
9180
+ !link.classList.contains("hidden") &&
9181
+ !link.closest(".hidden") &&
9182
+ !link.classList.contains("reveal") &&
9183
+ !link.hasAttribute("data-filepath") &&
9184
+ !link.hasAttribute("data-action") &&
9185
+ link.getAttribute("target") !== "_blank" &&
9186
+ link.href
9187
+ )
9188
+ }
9189
+ const rememberNonLogsSelection = (link) => {
9190
+ if (isSelectableLogsFallbackLink(link)) {
9191
+ lastNonLogsSelectedLink = link
9192
+ }
9193
+ }
9194
+ const findLogsFallbackLink = () => {
9195
+ if (lastNonLogsSelectedLink && document.contains(lastNonLogsSelectedLink) && isSelectableLogsFallbackLink(lastNonLogsSelectedLink)) {
9196
+ return lastNonLogsSelectedLink
9197
+ }
9198
+ const candidates = [
9199
+ document.querySelector(".frame-link.selected"),
9200
+ document.querySelector("[data-default].frame-link"),
9201
+ document.querySelector("#devtab.frame-link")
9202
+ ]
9203
+ for (const candidate of candidates) {
9204
+ if (isSelectableLogsFallbackLink(candidate)) {
9205
+ return candidate
9206
+ }
9207
+ }
9208
+ return Array.from(document.querySelectorAll("aside .frame-link")).find(isSelectableLogsFallbackLink) || null
9209
+ }
9210
+ const markLogsLinkInternal = (link) => {
9211
+ if (!link) return
9212
+ link.classList.add("hidden")
9213
+ link.dataset.internal = "true"
9214
+ link.dataset.persist = "false"
9215
+ link.setAttribute("aria-hidden", "true")
9216
+ link.tabIndex = -1
9217
+ }
9218
+ const syncLogsSelectedState = (selectedLink = undefined) => {
9219
+ const link = selectedLink === undefined ? document.querySelector(".frame-link.selected") : selectedLink
9220
+ const drawerOpen = !!(document.querySelector(".appcanvas") && document.querySelector(".appcanvas").classList.contains("logs-open"))
9221
+ const logsSelected = drawerOpen || isLogsLink(link)
9222
+ if (!logsSelected) {
9223
+ rememberNonLogsSelection(link)
9224
+ }
9225
+ setLogsSelected(logsSelected)
9226
+ }
9227
+ window.PinokioIsLogsPageSelected = () => isLogsLink(document.querySelector(".frame-link.selected"))
9228
+ window.PinokioSyncLogsSelectedState = syncLogsSelectedState
9229
+ Array.from(document.querySelectorAll('.temp-menu .frame-link')).filter(isLogsLink).forEach(markLogsLinkInternal)
9230
+ window.PinokioCloseLogsPage = async () => {
9231
+ const logsLinks = Array.from(document.querySelectorAll('.temp-menu .frame-link')).filter(isLogsLink)
9232
+ for (const link of logsLinks) {
9233
+ const frameName = link.getAttribute("target") || ""
9234
+ const escapedFrameName = escapeTargetSelector(frameName)
9235
+ const frame = escapedFrameName ? document.querySelector(`main.browserview iframe[name="${escapedFrameName}"]`) : null
9236
+ if (frame) {
9237
+ frame.remove()
9238
+ }
9239
+ link.remove()
9240
+ }
9241
+ setLogsSelected(false)
9242
+ await syncTabs()
9243
+ const fallbackLink = findLogsFallbackLink()
9244
+ if (fallbackLink) {
9245
+ await renderSelection({ target: fallbackLink, force: true })
9246
+ } else {
9247
+ await renderSelection({ force: true })
9248
+ }
9249
+ }
9250
+ window.PinokioOpenLogsPage = async (options = {}) => {
9251
+ const view = options && options.view === "raw" ? "raw" : "latest"
9252
+ const relativeUrl = buildEmbeddedLogsUrl(view)
9253
+ const existingLink = Array.from(document.querySelectorAll('.temp-menu .frame-link')).find(isLogsLink)
9254
+ rememberNonLogsSelection(document.querySelector(".frame-link.selected"))
9255
+ setLogsNewBadge(false)
9256
+ setLogsSelected(true)
9257
+ if (existingLink) {
9258
+ markLogsLinkInternal(existingLink)
9259
+ const display = existingLink.querySelector('.display')
9260
+ if (display) {
9261
+ display.textContent = 'Logs'
9262
+ }
9263
+ try {
9264
+ const frameName = existingLink.getAttribute("target") || ""
9265
+ const escapedFrameName = frameName && window.CSS && typeof window.CSS.escape === "function" ? window.CSS.escape(frameName) : frameName.replace(/["\\]/g, "\\$&")
9266
+ const frame = frameName ? document.querySelector(`main.browserview iframe[name="${escapedFrameName}"]`) : null
9267
+ if (frame) {
9268
+ frame.src = relativeUrl
9269
+ }
9270
+ existingLink.href = relativeUrl
9271
+ } catch (_) {
9272
+ existingLink.href = relativeUrl
9273
+ }
9274
+ existingLink.click()
9275
+ return
9276
+ }
9277
+ const { tab } = await addTab(relativeUrl, { label: 'Logs', target: 'pinokio-logs', persist: false, internal: true })
9278
+ requestAnimationFrame(() => {
9279
+ tab.click()
9280
+ })
9281
+ }
9282
+ try {
9283
+ setLogsNewBadge(window.sessionStorage.getItem(logsNotificationKey) === "1")
9284
+ } catch (_) {}
9285
+ if (document.querySelector(".appcanvas") && document.querySelector(".appcanvas").dataset.pageError === "1") {
9286
+ setLogsNewBadge(true)
9287
+ }
9288
+ const pluginInstallPreflightCache = new Map()
9289
+ const normalizePluginPathFromLaunchHref = (rawHref) => {
9290
+ if (!rawHref || typeof rawHref !== "string") {
9291
+ return ""
9292
+ }
9293
+ try {
9294
+ const parsed = new URL(rawHref, window.location.origin)
9295
+ if (parsed.origin !== window.location.origin) {
9296
+ return ""
9297
+ }
9298
+ if (parsed.pathname.startsWith("/pinokio/run/plugin/")) {
9299
+ return parsed.pathname
9300
+ }
9301
+ if (parsed.pathname.startsWith("/run/plugin/") || (parsed.pathname.startsWith("/run/api/") && /\/pinokio\.js$/i.test(parsed.pathname))) {
9302
+ return parsed.pathname.replace(/^\/run/, "")
9303
+ }
9304
+ } catch (_) {}
9305
+ return ""
9306
+ }
9307
+ const pluginInstallHrefFromState = (state, fallbackPath) => {
9308
+ const detailUrl = state && typeof state.detailUrl === "string" && state.detailUrl
9309
+ ? state.detailUrl
9310
+ : `/plugin?path=${encodeURIComponent(fallbackPath)}`
9311
+ try {
9312
+ const parsed = new URL(detailUrl, window.location.origin)
9313
+ parsed.searchParams.set("next", "install")
9314
+ return `${parsed.pathname}${parsed.search}${parsed.hash}`
9315
+ } catch (_) {
9316
+ const separator = detailUrl.includes("?") ? "&" : "?"
9317
+ return `${detailUrl}${separator}next=install`
9318
+ }
9319
+ }
9320
+ const getPluginInstallRedirect = async (rawHref) => {
9321
+ const pluginPath = normalizePluginPathFromLaunchHref(rawHref)
9322
+ if (!pluginPath) {
9323
+ return ""
9324
+ }
9325
+ if (pluginInstallPreflightCache.has(pluginPath)) {
9326
+ return pluginInstallPreflightCache.get(pluginPath)
9327
+ }
9328
+ let redirectHref = ""
9329
+ try {
9330
+ const response = await fetch(`/api/plugin/install-state?path=${encodeURIComponent(pluginPath)}`, {
9331
+ cache: "no-store"
9332
+ })
9333
+ const state = await response.json().catch(() => ({}))
9334
+ if (response.ok && state && state.ok && state.hasInstall && state.hasInstalledCheck && state.installed === false) {
9335
+ redirectHref = pluginInstallHrefFromState(state, pluginPath)
9336
+ }
9337
+ } catch (_) {}
9338
+ pluginInstallPreflightCache.set(pluginPath, redirectHref)
9339
+ return redirectHref
9340
+ }
9341
+ const redirectToPluginInstallIfNeeded = async (rawHref) => {
9342
+ const redirectHref = await getPluginInstallRedirect(rawHref)
9343
+ if (!redirectHref) {
9344
+ return false
9345
+ }
9346
+ window.location.href = redirectHref
9347
+ return true
7584
9348
  }
7585
9349
  const handleLaunchRequest = async (launch = {}, options = {}) => {
7586
9350
  const rawName = typeof launch.name === "string" ? launch.name.trim() : ""
@@ -7588,6 +9352,9 @@ const rerenderMenuSection = (container, html) => {
7588
9352
  if (!rawName || !rawHref) {
7589
9353
  return null
7590
9354
  }
9355
+ if (await redirectToPluginInstallIfNeeded(rawHref)) {
9356
+ return null
9357
+ }
7591
9358
  let link = findFrameLinkByTarget(rawName)
7592
9359
  if (!link) {
7593
9360
  const created = await addTab(rawHref, {
@@ -7666,10 +9433,12 @@ const rerenderMenuSection = (container, html) => {
7666
9433
  try {
7667
9434
  const pageUrl = new URL(window.location.href)
7668
9435
  const pluginPath = pageUrl.searchParams.get("plugin")
7669
- if (!pluginPath || (!pluginPath.startsWith("/plugin/") && !pluginPath.startsWith("/api/"))) {
9436
+ const isSystemPlugin = pluginPath && pluginPath.startsWith("/pinokio/run/plugin/")
9437
+ const isLocalPlugin = pluginPath && (pluginPath.startsWith("/plugin/") || pluginPath.startsWith("/api/"))
9438
+ if (!pluginPath || (!isSystemPlugin && !isLocalPlugin)) {
7670
9439
  return null
7671
9440
  }
7672
- const launchUrl = new URL(`/run${pluginPath}`, window.location.origin)
9441
+ const launchUrl = new URL(isSystemPlugin ? pluginPath : `/run${pluginPath}`, window.location.origin)
7673
9442
  if (appWorkspacePath && !launchUrl.searchParams.has("cwd")) {
7674
9443
  launchUrl.searchParams.set("cwd", appWorkspacePath)
7675
9444
  }
@@ -7753,7 +9522,6 @@ const rerenderMenuSection = (container, html) => {
7753
9522
  const communityModeActive = !!(appcanvas && appcanvas.classList.contains("community-mode"))
7754
9523
  const askAiOpen = !!(appcanvas && appcanvas.classList.contains("panel-open"))
7755
9524
  const mobileMenuFallback = document.querySelector(".mobile-menu-fallback")
7756
- const currentView = document.body.getAttribute("data-view")
7757
9525
  const closeSectionVisible = !!(closeWindowButton && !closeWindowButton.classList.contains("hidden"))
7758
9526
 
7759
9527
  document.querySelectorAll(".mobile-nav-menu").forEach((button) => {
@@ -7768,12 +9536,9 @@ const rerenderMenuSection = (container, html) => {
7768
9536
  button.classList.toggle("selected", askAiOpen)
7769
9537
  button.setAttribute("aria-pressed", askAiOpen ? "true" : "false")
7770
9538
  })
7771
- document.querySelectorAll(".mobile-mode-segment").forEach((segment) => {
7772
- const segmentMode = segment.getAttribute("data-mobile-mode")
7773
- const active = !communityModeActive && segmentMode === currentView
7774
- segment.classList.toggle("selected", active)
7775
- segment.setAttribute("aria-selected", active ? "true" : "false")
7776
- })
9539
+ if (typeof syncLogsSelectedState === "function") {
9540
+ syncLogsSelectedState()
9541
+ }
7777
9542
  document.querySelectorAll(".mobile-close-window-action").forEach((button) => {
7778
9543
  button.classList.toggle("hidden", !closeSectionVisible)
7779
9544
  button.setAttribute("aria-hidden", closeSectionVisible ? "false" : "true")
@@ -8041,8 +9806,6 @@ const rerenderMenuSection = (container, html) => {
8041
9806
  }
8042
9807
  }
8043
9808
 
8044
- const nestedMenuSelector = ".appcanvas > aside .m.menu .nested-menu"
8045
-
8046
9809
  const getDirectChild = (parent, selector) => {
8047
9810
  if (!parent) {
8048
9811
  return null
@@ -8051,73 +9814,15 @@ const rerenderMenuSection = (container, html) => {
8051
9814
  }
8052
9815
 
8053
9816
  const getSubmenu = (menu) => getDirectChild(menu, ".submenu")
8054
- const getToggle = (menu) => getDirectChild(menu, ".reveal")
8055
9817
  const syncActiveNestedPath = () => {}
8056
9818
 
8057
9819
  renderSelection()
8058
9820
 
8059
- const resetSubmenuPosition = (submenu) => {
8060
- if (!submenu) {
8061
- return
8062
- }
8063
- submenu.style.removeProperty("left")
8064
- submenu.style.removeProperty("top")
8065
- submenu.style.removeProperty("position")
8066
- submenu.style.removeProperty("visibility")
8067
- submenu.style.removeProperty("z-index")
8068
- }
8069
-
8070
- const getMenuDepth = (menu) => {
8071
- let depth = 0
8072
- let current = menu ? menu.parentElement : null
8073
- while (current) {
8074
- if (current.classList && current.classList.contains("submenu")) {
8075
- depth += 1
8076
- }
8077
- current = current.parentElement
8078
- }
8079
- return depth
8080
- }
8081
-
8082
- const positionSubmenu = (submenu, toggle) => {
8083
- if (!submenu || !toggle) {
9821
+ const toggleNestedMenu = (toggle) => {
9822
+ if (!toggle) {
8084
9823
  return
8085
9824
  }
8086
- const menu = toggle.closest(nestedMenuSelector)
8087
- const depth = getMenuDepth(menu)
8088
- const padding = 12
8089
- submenu.style.visibility = "hidden"
8090
- submenu.style.position = "fixed"
8091
- submenu.style.left = "0px"
8092
- submenu.style.top = "0px"
8093
- submenu.style.zIndex = String(90 + depth * 2)
8094
-
8095
- const bounds = submenu.getBoundingClientRect()
8096
- const anchor = toggle.getBoundingClientRect()
8097
- const nested = depth > 0
8098
-
8099
- let left = nested ? anchor.right - 6 : anchor.left
8100
- if (left + bounds.width > window.innerWidth - padding) {
8101
- left = nested ? anchor.left - bounds.width + 6 : window.innerWidth - bounds.width - padding
8102
- }
8103
- if (left < padding) {
8104
- left = padding
8105
- }
8106
-
8107
- let top = nested ? anchor.top : anchor.bottom + 6
8108
- if (top + bounds.height > window.innerHeight - padding) {
8109
- top = Math.max(window.innerHeight - bounds.height - padding, padding)
8110
- }
8111
- if (top < padding) {
8112
- top = padding
8113
- }
8114
-
8115
- submenu.style.left = `${Math.round(left)}px`
8116
- submenu.style.top = `${Math.round(top)}px`
8117
- submenu.style.visibility = ""
8118
- }
8119
-
8120
- const closeNestedMenu = (menu) => {
9825
+ const menu = toggle.closest(".nested-menu")
8121
9826
  if (!menu) {
8122
9827
  return
8123
9828
  }
@@ -8125,92 +9830,16 @@ const rerenderMenuSection = (container, html) => {
8125
9830
  if (!submenu) {
8126
9831
  return
8127
9832
  }
8128
-
8129
- const descendants = Array.from(submenu.querySelectorAll(".nested-menu.is-open"))
8130
- descendants.forEach((child) => closeNestedMenu(child))
8131
-
8132
- if (!submenu.classList.contains("hidden")) {
8133
- submenu.classList.add("hidden")
8134
- }
8135
- resetSubmenuPosition(submenu)
8136
- menu.classList.remove("is-open")
8137
- setDropdownState(getToggle(menu), false)
8138
- }
8139
-
8140
- const closeNestedSiblings = (menu) => {
8141
- if (!menu || !menu.parentElement) {
8142
- return
8143
- }
8144
- Array.from(menu.parentElement.children).forEach((sibling) => {
8145
- if (sibling !== menu && sibling.classList && sibling.classList.contains("nested-menu")) {
8146
- closeNestedMenu(sibling)
8147
- }
8148
- })
8149
- }
8150
-
8151
- const closeAllNestedMenus = (exception) => {
8152
- const skip = exception ? exception.closest ? exception.closest(nestedMenuSelector) : exception : null
8153
- document.querySelectorAll(`${nestedMenuSelector}.is-open`).forEach((menu) => {
8154
- if (skip && (menu === skip || menu.contains(skip))) {
8155
- return
8156
- }
8157
- closeNestedMenu(menu)
8158
- })
9833
+ const isHidden = submenu.classList.toggle("hidden")
9834
+ menu.classList.toggle("is-open", !isHidden)
9835
+ setDropdownState(toggle, !isHidden)
8159
9836
  }
8160
9837
 
8161
9838
  const closeFloatingMenus = () => {
8162
9839
  closeStatusDropdowns()
8163
- closeAllNestedMenus()
8164
9840
  hideTabLinkPopover({ immediate: true })
8165
9841
  }
8166
9842
 
8167
- const openNestedMenu = (menu, toggle) => {
8168
- if (!menu) {
8169
- return
8170
- }
8171
- const submenu = getSubmenu(menu)
8172
- if (!submenu) {
8173
- return
8174
- }
8175
- closeNestedSiblings(menu)
8176
- submenu.classList.remove("hidden")
8177
- positionSubmenu(submenu, toggle)
8178
- submenu.scrollTop = 0
8179
- menu.classList.add("is-open")
8180
- setDropdownState(toggle, true)
8181
- updateNestedMenuPositions()
8182
- }
8183
-
8184
- const toggleNestedMenu = (toggle) => {
8185
- if (!toggle) {
8186
- return
8187
- }
8188
- const menu = toggle.closest(nestedMenuSelector)
8189
- if (!menu) {
8190
- return
8191
- }
8192
- const submenu = getSubmenu(menu)
8193
- if (!submenu) {
8194
- return
8195
- }
8196
- const shouldOpen = submenu.classList.contains("hidden")
8197
- if (shouldOpen) {
8198
- closeAllNestedMenus(menu)
8199
- openNestedMenu(menu, toggle)
8200
- } else {
8201
- closeNestedMenu(menu)
8202
- }
8203
- }
8204
-
8205
- const updateNestedMenuPositions = () => {
8206
- document.querySelectorAll(`${nestedMenuSelector}.is-open`).forEach((menu) => {
8207
- const submenu = getSubmenu(menu)
8208
- const toggle = getToggle(menu)
8209
- if (submenu && toggle && !submenu.classList.contains("hidden")) {
8210
- positionSubmenu(submenu, toggle)
8211
- }
8212
- })
8213
- }
8214
9843
  const closeStatusDropdowns = (except) => {
8215
9844
  const exceptMenu = typeof except === "string" ? document.querySelector(except) : except
8216
9845
  document.querySelectorAll("#fs-status .fs-dropdown-menu").forEach((menu) => {
@@ -8429,6 +10058,15 @@ const rerenderMenuSection = (container, html) => {
8429
10058
  }
8430
10059
  }
8431
10060
 
10061
+ const pluginLaunchHref = target.getAttribute("href") || target.href || ""
10062
+ if (normalizePluginPathFromLaunchHref(pluginLaunchHref)) {
10063
+ e.preventDefault()
10064
+ if (await redirectToPluginInstallIfNeeded(pluginLaunchHref)) {
10065
+ e.stopPropagation()
10066
+ return
10067
+ }
10068
+ }
10069
+
8432
10070
  const dropdownMenu = target.closest(".fs-dropdown-menu")
8433
10071
  if (dropdownMenu) {
8434
10072
  dropdownMenu.classList.add("hidden")
@@ -8484,8 +10122,6 @@ const rerenderMenuSection = (container, html) => {
8484
10122
  return
8485
10123
  }
8486
10124
 
8487
- closeAllNestedMenus()
8488
-
8489
10125
  if (target.getAttribute("data-action")) {
8490
10126
  let actionStr = target.getAttribute("data-action")
8491
10127
  let action = JSON.parse(actionStr)
@@ -8535,7 +10171,12 @@ const rerenderMenuSection = (container, html) => {
8535
10171
  }
8536
10172
  renderSelection({ explicitTarget: link })
8537
10173
  }
8538
- const navContainers = [document.querySelector("aside"), document.querySelector("#fs-status")]
10174
+ const asideNav = document.querySelector("aside")
10175
+ const fsStatusNav = document.querySelector("#fs-status")
10176
+ const navContainers = [asideNav]
10177
+ if (fsStatusNav && !(asideNav && asideNav.contains(fsStatusNav))) {
10178
+ navContainers.push(fsStatusNav)
10179
+ }
8539
10180
  navContainers.forEach((container) => {
8540
10181
  if (container) {
8541
10182
  container.addEventListener("click", handleMenuClick)
@@ -8545,42 +10186,15 @@ const rerenderMenuSection = (container, html) => {
8545
10186
  })
8546
10187
  }
8547
10188
  })
10189
+ dispatchInitialHomeSelection()
8548
10190
 
8549
- const layoutToggleButton = document.getElementById("layout-toggle")
8550
- if (layoutToggleButton) {
8551
- const layoutToggleIcon = layoutToggleButton.querySelector("i.fa-bars")
8552
- const appcanvas = document.querySelector(".appcanvas")
8553
- const sidebarResizer = document.getElementById("appcanvas-resizer")
8554
- const sidebarContainer = document.querySelector(".appcanvas > aside .menu-container")
8555
-
8556
- if (appcanvas && typeof localStorage !== "undefined") {
8557
- const storedLayout = localStorage.getItem("pinokioLayoutVertical")
8558
- if (storedLayout === "1") {
8559
- appcanvas.classList.add("vertical")
8560
- if (layoutToggleIcon) {
8561
- layoutToggleIcon.classList.add("fa-rotate-90")
8562
- }
8563
- }
8564
- }
8565
-
8566
- layoutToggleButton.addEventListener("click", () => {
8567
- if (!appcanvas) {
8568
- return
8569
- }
8570
- const isVertical = appcanvas.classList.toggle("vertical")
8571
- if (layoutToggleIcon) {
8572
- if (isVertical) {
8573
- layoutToggleIcon.classList.add("fa-rotate-90")
8574
- } else {
8575
- layoutToggleIcon.classList.remove("fa-rotate-90")
8576
- }
8577
- }
8578
- if (typeof localStorage !== "undefined") {
8579
- localStorage.setItem("pinokioLayoutVertical", isVertical ? "1" : "0")
8580
- }
8581
- })
10191
+ const sidebarResizer = document.getElementById("appcanvas-resizer")
10192
+ const sidebarContainer = document.querySelector(".appcanvas > aside .menu-container")
10193
+ if (appcanvas) {
10194
+ appcanvas.classList.add("vertical")
10195
+ }
8582
10196
 
8583
- if (appcanvas && sidebarResizer && sidebarContainer) {
10197
+ if (appcanvas && sidebarResizer && sidebarContainer) {
8584
10198
  const SIDEBAR_MIN_WIDTH = 140
8585
10199
  const SIDEBAR_MAX_WIDTH = 560
8586
10200
  const sidebarMobileQuery = window.matchMedia ? window.matchMedia("(max-width: 768px)") : null
@@ -8737,7 +10351,6 @@ const rerenderMenuSection = (container, html) => {
8737
10351
  }
8738
10352
 
8739
10353
  initSidebarWidth()
8740
- }
8741
10354
  }
8742
10355
  setupTabLinkHover()
8743
10356
  document.addEventListener("click", (event) => {
@@ -8745,9 +10358,6 @@ const rerenderMenuSection = (container, html) => {
8745
10358
  return
8746
10359
  }
8747
10360
  closeStatusDropdowns()
8748
- if (!event.target.closest(nestedMenuSelector)) {
8749
- closeAllNestedMenus()
8750
- }
8751
10361
  })
8752
10362
 
8753
10363
  document.addEventListener("keydown", (event) => {
@@ -8765,31 +10375,11 @@ const rerenderMenuSection = (container, html) => {
8765
10375
  }, 0)
8766
10376
  })
8767
10377
 
8768
- window.addEventListener("resize", () => {
8769
- updateNestedMenuPositions()
8770
- })
8771
-
8772
- const nestedMenuScrollContainers = [
8773
- document.querySelector(".container"),
8774
- document.querySelector(".container .content"),
8775
- document.querySelector("main"),
8776
- document.querySelector(".appcanvas > aside .menu-container")
8777
- ].filter(Boolean)
8778
-
8779
- nestedMenuScrollContainers.forEach((node) => {
8780
- node.addEventListener("scroll", () => {
8781
- updateNestedMenuPositions()
8782
- }, { passive: true })
8783
- })
8784
-
8785
- window.addEventListener("scroll", () => {
8786
- updateNestedMenuPositions()
8787
- }, { passive: true })
8788
10378
  const refresh_du = (path) => {
8789
10379
  let url = path ? `/du/<%=name%>/${path}` : `/du/<%=name%>`
8790
10380
  let selector = `.disk-usage[data-path='/${path || ""}']`
8791
- let el = document.querySelector(selector)
8792
- if (el) {
10381
+ let els = Array.from(document.querySelectorAll(selector))
10382
+ if (els.length > 0) {
8793
10383
  fetch(url).then((res) => {
8794
10384
  return res.json()
8795
10385
  }).then((res) => {
@@ -8807,7 +10397,10 @@ const rerenderMenuSection = (container, html) => {
8807
10397
  // KB
8808
10398
  val = `${Math.floor(res.du/k * 100) / 100} KB`
8809
10399
  }
8810
- el.innerHTML = `<i class="fa-solid fa-database"></i> ${val}`
10400
+ els.forEach((el) => {
10401
+ const metricLabel = el.getAttribute("data-metric-label")
10402
+ el.innerHTML = `<i class="fa-solid fa-database"></i> ${metricLabel ? `${metricLabel} ` : ""}${val}`
10403
+ })
8811
10404
  } else {
8812
10405
  }
8813
10406
  })
@@ -8932,7 +10525,17 @@ const rerenderMenuSection = (container, html) => {
8932
10525
  if (String(port) === String(location.port) || /https:\/\/.*pinokio\..*(localhost|co)/.test(origin)) {
8933
10526
  //if (String(port) === "<%=port%>" || /https:\/\/pinokio\..*localhost/.test(origin)) {
8934
10527
  //if (String(port) === "<%=port%>") {
8935
- if (event.data) {
10528
+ if (event.data) {
10529
+ if (event.data.e === "pinokio:close-logs" || event.data.type === "pinokio:close-logs") {
10530
+ if (window.PinokioCloseLogsPage && typeof window.PinokioCloseLogsPage === "function") {
10531
+ await window.PinokioCloseLogsPage()
10532
+ }
10533
+ return
10534
+ }
10535
+ if (event.data.e === "pinokio:logs-new" || event.data.type === "pinokio:logs-new" || event.data.e === "pinokio:open-logs" || event.data.type === "pinokio:open-logs") {
10536
+ setLogsNewBadge(true)
10537
+ return
10538
+ }
8936
10539
  if (event.data.action) {
8937
10540
  if (event.data.action.type === "newtab") {
8938
10541
  addTab(event.data.action.url)
@@ -9129,20 +10732,34 @@ const rerenderMenuSection = (container, html) => {
9129
10732
  return statusUri.includes('?') ? `${statusUri}&force=1` : `${statusUri}?force=1`
9130
10733
  }
9131
10734
 
9132
- const buildWorkspaceLogsUrl = () => {
9133
- if (workspaceName && workspaceName.length > 0) {
9134
- return `/logs?workspace=${encodeURIComponent(workspaceName)}`
9135
- }
9136
- return '/logs'
9137
- }
10735
+ const buildWorkspaceLogsUrl = () => {
10736
+ if (workspaceName && workspaceName.length > 0) {
10737
+ return `/logs?workspace=${encodeURIComponent(workspaceName)}&embed=1&view=latest`
10738
+ }
10739
+ return '/logs?embed=1&view=raw'
10740
+ }
9138
10741
 
9139
- const focusOrCreateLogsTab = async () => {
9140
- const relativeUrl = buildWorkspaceLogsUrl()
9141
- const absoluteUrl = new URL(relativeUrl, window.location.origin).toString()
9142
- const existingLink = Array.from(document.querySelectorAll('.temp-menu .frame-link')).find((link) => {
9143
- return link.href === absoluteUrl
9144
- })
10742
+ const focusOrCreateLogsTab = async () => {
10743
+ if (window.PinokioOpenLogsPage && typeof window.PinokioOpenLogsPage === "function") {
10744
+ await window.PinokioOpenLogsPage({ view: "latest" })
10745
+ return
10746
+ }
10747
+ const relativeUrl = buildWorkspaceLogsUrl()
10748
+ const absoluteUrl = new URL(relativeUrl, window.location.origin).toString()
10749
+ const existingLink = Array.from(document.querySelectorAll('.temp-menu .frame-link')).find((link) => {
10750
+ try {
10751
+ const parsed = new URL(link.href, window.location.origin)
10752
+ return parsed.pathname === "/logs" && parsed.searchParams.get("workspace") === workspaceName
10753
+ } catch (_) {
10754
+ return link.href === absoluteUrl
10755
+ }
10756
+ })
9145
10757
  if (existingLink) {
10758
+ existingLink.classList.add("hidden")
10759
+ existingLink.dataset.internal = "true"
10760
+ existingLink.dataset.persist = "false"
10761
+ existingLink.setAttribute("aria-hidden", "true")
10762
+ existingLink.tabIndex = -1
9146
10763
  const display = existingLink.querySelector('.display')
9147
10764
  if (display) {
9148
10765
  display.textContent = 'Logs'
@@ -9150,7 +10767,7 @@ const rerenderMenuSection = (container, html) => {
9150
10767
  existingLink.click()
9151
10768
  return
9152
10769
  }
9153
- const { tab } = await addTab(relativeUrl, { label: 'Logs' })
10770
+ const { tab } = await addTab(relativeUrl, { label: 'Logs', persist: false, internal: true })
9154
10771
  requestAnimationFrame(() => {
9155
10772
  tab.click()
9156
10773
  })
@@ -9924,6 +11541,13 @@ const rerenderMenuSection = (container, html) => {
9924
11541
  if (!changesMenu) {
9925
11542
  return
9926
11543
  }
11544
+ const keepChangesMenuOpen = () => {
11545
+ if (!changesMenu || !changesBtn) {
11546
+ return
11547
+ }
11548
+ changesMenu.classList.remove('hidden')
11549
+ setDropdownState(changesBtn, true)
11550
+ }
9927
11551
  const items = changesMenu.querySelectorAll('.git-changes-item')
9928
11552
  items.forEach((item) => {
9929
11553
  item.addEventListener('click', async (event) => {
@@ -9941,11 +11565,13 @@ const rerenderMenuSection = (container, html) => {
9941
11565
  node.classList.remove('git-changes-item--active')
9942
11566
  }
9943
11567
  })
9944
- closeStatusDropdowns()
11568
+ keepChangesMenuOpen()
9945
11569
  try {
9946
11570
  await showGitDiffModal({ repoParam: repoKey, repoName })
9947
11571
  } catch (err) {
9948
11572
  console.error('Failed to open diff modal:', err)
11573
+ } finally {
11574
+ keepChangesMenuOpen()
9949
11575
  }
9950
11576
  })
9951
11577
  })
@@ -11984,7 +13610,7 @@ const rerenderMenuSection = (container, html) => {
11984
13610
  </div>
11985
13611
  <div class="pinokio-modal-footer pinokio-modal-footer--create">
11986
13612
  <button type="button" class="pinokio-modal-secondary-btn" data-create-cancel>Cancel</button>
11987
- <button type="button" class="pinokio-publish-close-btn" data-create-next>Next</button>
13613
+ <button type="button" class="pinokio-publish-close-btn" data-create-close>Close</button>
11988
13614
  </div>
11989
13615
  </div>
11990
13616
  `,
@@ -12003,11 +13629,11 @@ const rerenderMenuSection = (container, html) => {
12003
13629
  allowEscapeKey: false,
12004
13630
  focusConfirm: false,
12005
13631
  didOpen: (popup) => {
12006
- const nextBtn = popup.querySelector('[data-create-next]')
13632
+ const closeBtn = popup.querySelector('[data-create-close]')
12007
13633
  const cancelBtn = popup.querySelector('[data-create-cancel]')
12008
- if (nextBtn) {
12009
- nextBtn.addEventListener('click', () => {
12010
- finalize({ completed: true, action: 'next', name, isPrivate })
13634
+ if (closeBtn) {
13635
+ closeBtn.addEventListener('click', () => {
13636
+ finalize({ completed: true, action: 'close', name, isPrivate })
12011
13637
  Swal.close()
12012
13638
  })
12013
13639
  }
@@ -12519,152 +14145,249 @@ const rerenderMenuSection = (container, html) => {
12519
14145
  }, 1000)
12520
14146
 
12521
14147
  </script>
12522
- <% } %>
12523
14148
  <script>
12524
14149
  (() => {
12525
- const aside = document.querySelector(".appcanvas > aside")
12526
- if (!aside) {
14150
+ const workspaceName = <%- JSON.stringify(typeof name === "string" ? name : "") %>
14151
+ const roots = Array.from(document.querySelectorAll("[data-resource-usage-root]"))
14152
+ if (!workspaceName || roots.length === 0) {
12527
14153
  return
12528
14154
  }
12529
14155
 
12530
- const collapsedClass = "appcanvas-aside-collapsed"
12531
- const animatingClass = "appcanvas-aside-animating"
12532
- const collapseDuration = 420
12533
- const collapseEasing = "cubic-bezier(0.22, 1, 0.36, 1)"
12534
- const expandEasing = "cubic-bezier(0.18, 0.85, 0.4, 1)"
14156
+ let preferences = {
14157
+ show_ram: true,
14158
+ show_vram: true,
14159
+ show_cpu: false
14160
+ }
14161
+ let refreshTimer = null
14162
+ let refreshInFlight = false
12535
14163
 
12536
- let transitionHandler = null
12537
- let queuedFrame = null
14164
+ const closePopovers = (exceptRoot = null) => {
14165
+ roots.forEach((root) => {
14166
+ if (root === exceptRoot) return
14167
+ const trigger = root.querySelector("[data-resource-usage-trigger]")
14168
+ const popover = root.querySelector("[data-resource-usage-popover]")
14169
+ if (trigger) trigger.setAttribute("aria-expanded", "false")
14170
+ if (popover) popover.classList.add("hidden")
14171
+ })
14172
+ }
12538
14173
 
12539
- const clearAnimation = () => {
12540
- if (transitionHandler) {
12541
- aside.removeEventListener("transitionend", transitionHandler)
12542
- transitionHandler = null
12543
- }
12544
- if (queuedFrame !== null) {
12545
- cancelAnimationFrame(queuedFrame)
12546
- queuedFrame = null
14174
+ const applyPreferences = (nextPreferences = {}) => {
14175
+ preferences = {
14176
+ ...preferences,
14177
+ ...nextPreferences
12547
14178
  }
12548
- aside.classList.remove(animatingClass)
12549
- aside.style.transition = ""
12550
- aside.style.height = ""
12551
- aside.style.opacity = ""
14179
+ roots.forEach((root) => {
14180
+ root.querySelectorAll("[data-resource-toggle]").forEach((toggle) => {
14181
+ const key = toggle.getAttribute("data-resource-toggle")
14182
+ toggle.setAttribute("aria-checked", preferences[key] ? "true" : "false")
14183
+ })
14184
+ })
12552
14185
  }
12553
14186
 
12554
- const finish = (minimized) => {
12555
- aside.classList.toggle(collapsedClass, minimized)
12556
- aside.classList.remove(animatingClass)
12557
- aside.style.transition = ""
12558
- aside.style.height = ""
12559
- aside.style.opacity = ""
12560
- aside.dataset.asideState = minimized ? "collapsed" : "expanded"
14187
+ const setStatus = (text) => {
14188
+ roots.forEach((root) => {
14189
+ const status = root.querySelector("[data-resource-usage-status]")
14190
+ if (status) status.textContent = text
14191
+ })
14192
+ }
14193
+
14194
+ const setChip = (name, visible, text) => {
14195
+ roots.forEach((root) => {
14196
+ const chip = root.querySelector(`[data-resource-chip="${name}"]`)
14197
+ if (!chip) return
14198
+ chip.textContent = text
14199
+ chip.classList.toggle("hidden", !visible)
14200
+ })
12561
14201
  }
12562
14202
 
12563
- const collapse = (immediate) => {
12564
- if (aside.dataset.asideState === "collapsed" && !immediate) {
14203
+ const updateFromUsage = (usage) => {
14204
+ if (!usage || !usage.ok) {
14205
+ setStatus("Usage unavailable")
14206
+ setChip("cpu", false, "CPU --")
14207
+ setChip("ram", false, "RAM --")
14208
+ setChip("vram", false, "VRAM --")
12565
14209
  return
12566
14210
  }
12567
- clearAnimation()
12568
- if (immediate) {
12569
- finish(true)
12570
- return
14211
+ applyPreferences(usage.preferences || {})
14212
+
14213
+ const metrics = usage.metrics || {}
14214
+ const ram = metrics.ram || {}
14215
+ const cpu = metrics.cpu || {}
14216
+ const vram = metrics.vram || {}
14217
+
14218
+ setChip("ram", !!(ram.enabled && ram.available), `RAM ${ram.formatted || "--"}`)
14219
+ setChip("cpu", !!(cpu.enabled && cpu.available && cpu.percent != null), `CPU ${cpu.percent}%`)
14220
+ setChip("vram", !!(vram.enabled && vram.available), `VRAM ${vram.formatted || "--"}`)
14221
+
14222
+ if (!usage.running) {
14223
+ setStatus("No running app process")
14224
+ } else if (usage.stale) {
14225
+ setStatus("Showing last sampled usage")
14226
+ } else {
14227
+ setStatus("Updated just now")
12571
14228
  }
12572
- const startHeight = aside.getBoundingClientRect().height
12573
- if (startHeight <= 0.5) {
12574
- finish(true)
14229
+ }
14230
+
14231
+ const fetchUsage = async () => {
14232
+ if (refreshInFlight || document.hidden) {
12575
14233
  return
12576
14234
  }
14235
+ refreshInFlight = true
14236
+ try {
14237
+ const response = await fetch(`/resource-usage/workspace/${encodeURIComponent(workspaceName)}`, {
14238
+ cache: "no-store"
14239
+ })
14240
+ updateFromUsage(await response.json())
14241
+ } catch (_) {
14242
+ setStatus("Usage unavailable")
14243
+ } finally {
14244
+ refreshInFlight = false
14245
+ }
14246
+ }
12577
14247
 
12578
- aside.classList.add(animatingClass)
12579
- aside.style.height = `${startHeight}px`
12580
- aside.style.opacity = getComputedStyle(aside).opacity || "1"
12581
- aside.dataset.asideState = "animating"
12582
-
12583
- queuedFrame = requestAnimationFrame(() => {
12584
- queuedFrame = null
12585
- aside.style.transition = `height ${collapseDuration}ms ${collapseEasing}, opacity ${collapseDuration - 120}ms ease`
12586
- aside.style.height = "0px"
12587
- aside.style.opacity = "0"
12588
- })
12589
-
12590
- transitionHandler = (event) => {
12591
- if (event.target !== aside || event.propertyName !== "height") {
12592
- return
12593
- }
12594
- aside.removeEventListener("transitionend", transitionHandler)
12595
- transitionHandler = null
12596
- finish(true)
14248
+ const scheduleRefresh = () => {
14249
+ if (refreshTimer) {
14250
+ window.clearTimeout(refreshTimer)
12597
14251
  }
12598
- aside.addEventListener("transitionend", transitionHandler)
14252
+ refreshTimer = window.setTimeout(async () => {
14253
+ await fetchUsage()
14254
+ scheduleRefresh()
14255
+ }, 5000)
12599
14256
  }
12600
14257
 
12601
- const expand = (immediate) => {
12602
- if (aside.dataset.asideState === "expanded" && !immediate) {
14258
+ roots.forEach((root) => {
14259
+ const trigger = root.querySelector("[data-resource-usage-trigger]")
14260
+ const popover = root.querySelector("[data-resource-usage-popover]")
14261
+ if (trigger && popover) {
14262
+ trigger.addEventListener("click", (event) => {
14263
+ event.preventDefault()
14264
+ event.stopPropagation()
14265
+ const isOpen = !popover.classList.contains("hidden")
14266
+ closePopovers(root)
14267
+ popover.classList.toggle("hidden", isOpen)
14268
+ trigger.setAttribute("aria-expanded", isOpen ? "false" : "true")
14269
+ })
14270
+ }
14271
+ root.querySelectorAll("[data-resource-toggle]").forEach((toggle) => {
14272
+ toggle.addEventListener("click", async (event) => {
14273
+ event.preventDefault()
14274
+ event.stopPropagation()
14275
+ const key = toggle.getAttribute("data-resource-toggle")
14276
+ const previous = { ...preferences }
14277
+ const next = {
14278
+ ...preferences,
14279
+ [key]: !preferences[key]
14280
+ }
14281
+ applyPreferences(next)
14282
+ try {
14283
+ const response = await fetch("/resource-usage/preferences", {
14284
+ method: "POST",
14285
+ headers: { "Content-Type": "application/json" },
14286
+ body: JSON.stringify(next)
14287
+ })
14288
+ const result = await response.json()
14289
+ applyPreferences(result.preferences || next)
14290
+ await fetchUsage()
14291
+ } catch (_) {
14292
+ setStatus("Could not save resource settings")
14293
+ applyPreferences(previous)
14294
+ }
14295
+ })
14296
+ })
14297
+ })
14298
+
14299
+ document.addEventListener("pointerdown", (event) => {
14300
+ const target = event.target
14301
+ if (target && target.closest && target.closest("[data-resource-usage-root]")) {
12603
14302
  return
12604
14303
  }
12605
- clearAnimation()
12606
- if (immediate) {
12607
- finish(false)
12608
- return
14304
+ closePopovers()
14305
+ }, true)
14306
+ window.addEventListener("blur", () => {
14307
+ window.setTimeout(() => {
14308
+ const activeElement = document.activeElement
14309
+ if (activeElement && activeElement.tagName === "IFRAME") {
14310
+ closePopovers()
14311
+ }
14312
+ }, 0)
14313
+ })
14314
+ document.addEventListener("keydown", (event) => {
14315
+ if (event.key === "Escape") {
14316
+ closePopovers()
12609
14317
  }
14318
+ })
14319
+ document.addEventListener("visibilitychange", () => {
14320
+ if (!document.hidden) {
14321
+ fetchUsage()
14322
+ scheduleRefresh()
14323
+ }
14324
+ })
12610
14325
 
12611
- aside.classList.remove(collapsedClass)
12612
- const targetHeight = aside.scrollHeight
12613
- aside.classList.add(animatingClass)
12614
- aside.style.height = "0px"
12615
- aside.style.opacity = "0"
12616
- aside.dataset.asideState = "animating"
12617
-
12618
- aside.offsetHeight
14326
+ applyPreferences(preferences)
14327
+ fetchUsage()
14328
+ scheduleRefresh()
14329
+ })()
14330
+ </script>
14331
+ <script>
14332
+ (() => {
14333
+ const appcanvas = document.querySelector(".appcanvas")
14334
+ const aside = document.getElementById("app-sidebar")
14335
+ const toggle = document.getElementById("sidebar-toggle")
14336
+ if (!appcanvas || !aside || !toggle) {
14337
+ if (toggle) {
14338
+ toggle.classList.add("hidden")
14339
+ }
14340
+ return
14341
+ }
12619
14342
 
12620
- queuedFrame = requestAnimationFrame(() => {
12621
- queuedFrame = null
12622
- aside.style.transition = `height ${collapseDuration}ms ${expandEasing}, opacity ${collapseDuration - 140}ms ease`
12623
- aside.style.height = `${targetHeight}px`
12624
- aside.style.opacity = ""
12625
- })
14343
+ const workspaceName = <%- JSON.stringify(typeof name === "string" ? name : "") %>
14344
+ const storageKey = workspaceName
14345
+ ? `pinokio.sidebar-collapsed:${workspaceName}`
14346
+ : `pinokio.sidebar-collapsed:${location.pathname}`
12626
14347
 
12627
- transitionHandler = (event) => {
12628
- if (event.target !== aside || event.propertyName !== "height") {
12629
- return
12630
- }
12631
- aside.removeEventListener("transitionend", transitionHandler)
12632
- transitionHandler = null
12633
- finish(false)
14348
+ const updateToggle = (collapsed) => {
14349
+ const expanded = !collapsed
14350
+ const label = expanded ? "Hide sidebar" : "Show sidebar"
14351
+ toggle.setAttribute("aria-expanded", expanded ? "true" : "false")
14352
+ toggle.setAttribute("aria-label", label)
14353
+ toggle.setAttribute("title", label)
14354
+ toggle.setAttribute("data-tippy-content", label)
14355
+ toggle.dataset.sidebarState = expanded ? "expanded" : "collapsed"
14356
+ if (toggle._tippy && typeof toggle._tippy.setContent === "function") {
14357
+ toggle._tippy.setContent(label)
12634
14358
  }
12635
- aside.addEventListener("transitionend", transitionHandler)
12636
14359
  }
12637
14360
 
12638
- const setAside = (minimized, immediate) => {
12639
- if (minimized) {
12640
- collapse(immediate)
12641
- } else {
12642
- expand(immediate)
14361
+ const setCollapsed = (collapsed, options = {}) => {
14362
+ const next = !!collapsed
14363
+ appcanvas.classList.toggle("sidebar-collapsed", next)
14364
+ aside.setAttribute("aria-hidden", next ? "true" : "false")
14365
+ updateToggle(next)
14366
+ if (options.persist !== false) {
14367
+ try {
14368
+ window.localStorage.setItem(storageKey, next ? "1" : "0")
14369
+ } catch (_) {}
12643
14370
  }
14371
+ document.dispatchEvent(new CustomEvent("pinokio:sidebar-state", {
14372
+ detail: { collapsed: next }
14373
+ }))
12644
14374
  }
12645
14375
 
12646
- const header = document.querySelector("header.navheader")
12647
- const initialMinimized = !!(header && header.classList.contains("minimized"))
12648
- setAside(initialMinimized, true)
14376
+ let initialCollapsed = false
14377
+ try {
14378
+ initialCollapsed = window.localStorage.getItem(storageKey) === "1"
14379
+ } catch (_) {}
14380
+ setCollapsed(initialCollapsed, { persist: false })
12649
14381
 
12650
- document.addEventListener("pinokio:header-state", (event) => {
12651
- if (!event || !event.detail) {
12652
- return
12653
- }
12654
- const { minimized, phase } = event.detail
12655
- if (phase === "init") {
12656
- setAside(!!minimized, true)
12657
- return
12658
- }
12659
- if (phase === "start") {
12660
- setAside(!!minimized, false)
12661
- return
12662
- }
12663
- if (phase === "settled") {
12664
- clearAnimation()
12665
- finish(!!minimized)
12666
- }
14382
+ toggle.addEventListener("click", (event) => {
14383
+ event.preventDefault()
14384
+ setCollapsed(!appcanvas.classList.contains("sidebar-collapsed"))
12667
14385
  })
14386
+
14387
+ window.PinokioSidebar = {
14388
+ setCollapsed,
14389
+ isCollapsed: () => appcanvas.classList.contains("sidebar-collapsed")
14390
+ }
12668
14391
  })()
12669
14392
  </script>
12670
14393
  <script src="/tab-idle-notifier.js"></script>
@@ -12933,39 +14656,13 @@ document.addEventListener("DOMContentLoaded", () => {
12933
14656
  const defaultWorkspaceCwd = drawer.dataset.workspaceCwd || ""
12934
14657
  const widthKey = "pinokio.ask_ai.width"
12935
14658
  const ASK_AI_MIN_WIDTH = 240
12936
- const ASK_AI_FALLBACK_TOOLS = [
12937
- {
12938
- value: "claude",
12939
- label: "Claude Code",
12940
- iconSrc: "/asset/plugin/code/claude/claude.png",
12941
- href: "/run/plugin/code/claude/pinokio.js",
12942
- category: "CLI",
12943
- isDefault: true
12944
- },
12945
- {
12946
- value: "codex",
12947
- label: "OpenAI Codex",
12948
- iconSrc: "/asset/plugin/code/codex/openai.webp",
12949
- href: "/run/plugin/code/codex/pinokio.js",
12950
- category: "IDE",
12951
- isDefault: false
12952
- },
12953
- {
12954
- value: "gemini",
12955
- label: "Google Gemini CLI",
12956
- iconSrc: "/asset/plugin/code/gemini/gemini.jpeg",
12957
- href: "/run/plugin/code/gemini/pinokio.js",
12958
- category: "CLI",
12959
- isDefault: false
12960
- }
12961
- ]
12962
-
12963
14659
  let open = false
12964
14660
  let currentUrl = ""
12965
14661
  let askAiWidth = null
12966
14662
  let pickerRequestId = 0
12967
14663
  let askAiTools = null
12968
14664
  let askAiToolsPromise = null
14665
+ let askAiToolsKey = ""
12969
14666
 
12970
14667
  const setLocation = (value) => {
12971
14668
  if (!locationInput) return
@@ -13086,6 +14783,7 @@ document.addEventListener("DOMContentLoaded", () => {
13086
14783
  }
13087
14784
  if (
13088
14785
  parsed.pathname.startsWith("/run/plugin/")
14786
+ || parsed.pathname.startsWith("/pinokio/run/plugin/")
13089
14787
  || (parsed.pathname.startsWith("/run/api/") && /\/pinokio\.js$/i.test(parsed.pathname))
13090
14788
  ) {
13091
14789
  if (workspaceCwd && !parsed.searchParams.has("cwd")) {
@@ -13131,6 +14829,15 @@ document.addEventListener("DOMContentLoaded", () => {
13131
14829
  .replace(/^-+|-+$/g, "")
13132
14830
  }
13133
14831
 
14832
+ const buildPluginMenuUrl = (workspaceCwd) => {
14833
+ const parsed = new URL("/api/plugin/menu", window.location.origin)
14834
+ const normalizedWorkspace = typeof workspaceCwd === "string" ? workspaceCwd.trim() : ""
14835
+ if (normalizedWorkspace) {
14836
+ parsed.searchParams.set("workspace", normalizedWorkspace)
14837
+ }
14838
+ return `${parsed.pathname}${parsed.search}`
14839
+ }
14840
+
13134
14841
  const mapPluginMenuToAskAiTools = (menu) => {
13135
14842
  if (!Array.isArray(menu)) {
13136
14843
  return []
@@ -13150,6 +14857,7 @@ document.addEventListener("DOMContentLoaded", () => {
13150
14857
  return null
13151
14858
  }
13152
14859
  const isPluginLauncher = parsed.pathname.startsWith("/run/plugin/")
14860
+ || parsed.pathname.startsWith("/pinokio/run/plugin/")
13153
14861
  || (parsed.pathname.startsWith("/run/api/") && /\/pinokio\.js$/i.test(parsed.pathname))
13154
14862
  if (parsed.origin !== window.location.origin || !isPluginLauncher) {
13155
14863
  return null
@@ -13179,19 +14887,67 @@ document.addEventListener("DOMContentLoaded", () => {
13179
14887
  href,
13180
14888
  iconSrc: typeof plugin.image === "string" ? plugin.image : null,
13181
14889
  category,
14890
+ kind: "plugin",
13182
14891
  isDefault: plugin.default === true
13183
14892
  }
13184
14893
  }).filter(Boolean)
13185
14894
  }
13186
14895
 
13187
- const getAskAiTools = async () => {
13188
- if (Array.isArray(askAiTools) && askAiTools.length > 0) {
14896
+ const mapProjectShellToAskAiTools = (projectShell) => {
14897
+ const items = projectShell && Array.isArray(projectShell.items) ? projectShell.items : []
14898
+ return items.map((item) => {
14899
+ if (!item || typeof item !== "object") {
14900
+ return null
14901
+ }
14902
+ const href = typeof item.href === "string" ? item.href.trim() : ""
14903
+ if (!href) {
14904
+ return null
14905
+ }
14906
+ let parsed
14907
+ try {
14908
+ parsed = new URL(href, window.location.origin)
14909
+ } catch (_) {
14910
+ return null
14911
+ }
14912
+ if (parsed.origin !== window.location.origin || !parsed.pathname.startsWith("/shell/")) {
14913
+ return null
14914
+ }
14915
+ const label = typeof item.title === "string" && item.title.trim()
14916
+ ? item.title.trim()
14917
+ : (typeof item.text === "string" && item.text.trim() ? item.text.trim() : "Shell")
14918
+ const value = typeof item.shellId === "string" && item.shellId.trim()
14919
+ ? item.shellId.trim()
14920
+ : normalizeToolValue(href, label)
14921
+ return {
14922
+ value,
14923
+ label,
14924
+ href,
14925
+ target: typeof item.target === "string" && item.target.trim() ? item.target.trim() : `@${href}`,
14926
+ iconClass: typeof item.icon === "string" && item.icon.trim() ? item.icon.trim() : "fa-solid fa-terminal",
14927
+ description: typeof item.subtitle === "string" ? item.subtitle.trim() : "",
14928
+ category: "SHELL",
14929
+ kind: "shell"
14930
+ }
14931
+ }).filter(Boolean)
14932
+ }
14933
+
14934
+ const mapPluginPayloadToAskAiTools = (payload) => {
14935
+ const shellTools = mapProjectShellToAskAiTools(payload && payload.projectShell)
14936
+ const pluginTools = mapPluginMenuToAskAiTools(payload && Array.isArray(payload.menu) ? payload.menu : [])
14937
+ return shellTools.concat(pluginTools)
14938
+ }
14939
+
14940
+ const getAskAiTools = async (workspaceCwd = "") => {
14941
+ const cacheKey = typeof workspaceCwd === "string" ? workspaceCwd.trim() : ""
14942
+ if (askAiToolsKey === cacheKey && Array.isArray(askAiTools) && askAiTools.length > 0) {
13189
14943
  return askAiTools
13190
14944
  }
13191
- if (askAiToolsPromise) {
14945
+ if (askAiToolsKey === cacheKey && askAiToolsPromise) {
13192
14946
  return askAiToolsPromise
13193
14947
  }
13194
- askAiToolsPromise = fetch("/api/plugin/menu")
14948
+ const requestKey = cacheKey
14949
+ askAiToolsKey = requestKey
14950
+ const requestPromise = fetch(buildPluginMenuUrl(requestKey))
13195
14951
  .then((res) => {
13196
14952
  if (!res.ok) {
13197
14953
  throw new Error(`Failed to load plugin menu: ${res.status}`)
@@ -13199,18 +14955,90 @@ document.addEventListener("DOMContentLoaded", () => {
13199
14955
  return res.json()
13200
14956
  })
13201
14957
  .then((payload) => {
13202
- const mapped = mapPluginMenuToAskAiTools(payload && Array.isArray(payload.menu) ? payload.menu : [])
13203
- return mapped.length > 0 ? mapped : ASK_AI_FALLBACK_TOOLS.slice()
14958
+ return mapPluginPayloadToAskAiTools(payload || {})
13204
14959
  })
13205
- .catch(() => ASK_AI_FALLBACK_TOOLS.slice())
14960
+ .catch(() => [])
13206
14961
  .finally(() => {
13207
- askAiToolsPromise = null
14962
+ if (askAiToolsPromise === requestPromise) {
14963
+ askAiToolsPromise = null
14964
+ }
13208
14965
  })
13209
- const tools = await askAiToolsPromise
13210
- askAiTools = tools
14966
+ askAiToolsPromise = requestPromise
14967
+ const tools = await requestPromise
14968
+ if (askAiToolsKey === requestKey) {
14969
+ askAiTools = tools
14970
+ }
13211
14971
  return tools
13212
14972
  }
13213
14973
 
14974
+ const createAskAiLaunchSession = () => {
14975
+ if (typeof createLaunchSession === "function") {
14976
+ return createLaunchSession()
14977
+ }
14978
+ if (typeof crypto !== "undefined" && crypto.randomUUID) {
14979
+ return crypto.randomUUID()
14980
+ }
14981
+ return `${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 10)}`
14982
+ }
14983
+
14984
+ const appendLaunchSessionParam = (value, session) => {
14985
+ if (!value || typeof value !== "string" || !session || value.includes("session=")) {
14986
+ return value
14987
+ }
14988
+ const hasTargetPrefix = value.startsWith("@")
14989
+ const rawValue = hasTargetPrefix ? value.slice(1) : value
14990
+ const delimiter = rawValue.includes("?") ? "&" : "?"
14991
+ const next = `${rawValue}${delimiter}session=${encodeURIComponent(session)}`
14992
+ return hasTargetPrefix ? `@${next}` : next
14993
+ }
14994
+
14995
+ const ensureAskAiShellTab = async (tool, href, target) => {
14996
+ if (!href || !target) {
14997
+ return null
14998
+ }
14999
+ let link = findFrameLinkByTarget(target)
15000
+ if (link && !link.closest(".temp-menu")) {
15001
+ link = null
15002
+ }
15003
+ if (link) {
15004
+ if (link.getAttribute("href") !== href) {
15005
+ link.setAttribute("href", href)
15006
+ await syncTabs()
15007
+ }
15008
+ return link
15009
+ }
15010
+ const tempMenu = document.querySelector(".temp-menu")
15011
+ if (!tempMenu) {
15012
+ return null
15013
+ }
15014
+ const index = document.querySelectorAll("aside .s .frame-link").length
15015
+ const item = document.createElement("a")
15016
+ item.target = target
15017
+ item.href = href
15018
+ item.setAttribute("data-index", String(index))
15019
+ item.className = "btn header-item frame-link"
15020
+ item.dataset.shellTab = "1"
15021
+ item.dataset.askAiShellTab = "1"
15022
+ const displayText = tool && typeof tool.label === "string" && tool.label.trim()
15023
+ ? tool.label.trim()
15024
+ : "Shell"
15025
+ item.innerHTML = `<div class='tab'><i class="fa-solid fa-link"></i><div class='display'>${escapeTabHtml(displayText)}</div><div class='flexible'></div></div>`
15026
+ tempMenu.appendChild(item)
15027
+ await syncTabs()
15028
+ return item
15029
+ }
15030
+
15031
+ const openShellTool = async (tool) => {
15032
+ if (!tool || typeof tool.href !== "string") {
15033
+ return false
15034
+ }
15035
+ const session = createAskAiLaunchSession()
15036
+ const href = appendLaunchSessionParam(tool.href, session)
15037
+ const target = appendLaunchSessionParam(tool.target || `@${tool.href}`, session)
15038
+ await ensureAskAiShellTab(tool, href, target)
15039
+ return openWithUrl(href, { frameName: target })
15040
+ }
15041
+
13214
15042
  const createPickerOption = (tool, workspaceCwd) => {
13215
15043
  const button = document.createElement("button")
13216
15044
  button.type = "button"
@@ -13227,7 +15055,13 @@ document.addEventListener("DOMContentLoaded", () => {
13227
15055
  } else {
13228
15056
  const fallback = document.createElement("span")
13229
15057
  fallback.className = "ask-ai-drawer-picker-icon-fallback"
13230
- fallback.innerHTML = `<i class="fa-solid fa-robot" aria-hidden="true"></i>`
15058
+ const iconClass = typeof tool.iconClass === "string" && tool.iconClass.trim()
15059
+ ? tool.iconClass.trim()
15060
+ : "fa-solid fa-robot"
15061
+ const fallbackIcon = document.createElement("i")
15062
+ fallbackIcon.className = iconClass
15063
+ fallbackIcon.setAttribute("aria-hidden", "true")
15064
+ fallback.appendChild(fallbackIcon)
13231
15065
  button.appendChild(fallback)
13232
15066
  }
13233
15067
 
@@ -13236,14 +15070,26 @@ document.addEventListener("DOMContentLoaded", () => {
13236
15070
  const name = document.createElement("div")
13237
15071
  name.className = "ask-ai-drawer-picker-name"
13238
15072
  name.textContent = tool.label || tool.value || "Plugin"
13239
- const meta = document.createElement("div")
13240
- meta.className = "ask-ai-drawer-picker-meta"
13241
- meta.textContent = "Launch immediately"
13242
15073
  copy.appendChild(name)
13243
- copy.appendChild(meta)
15074
+ if (tool.description) {
15075
+ const meta = document.createElement("div")
15076
+ meta.className = "ask-ai-drawer-picker-meta"
15077
+ meta.textContent = tool.description
15078
+ copy.appendChild(meta)
15079
+ }
13244
15080
  button.appendChild(copy)
13245
15081
 
13246
- button.addEventListener("click", () => {
15082
+ button.addEventListener("click", async () => {
15083
+ if (tool.kind === "shell") {
15084
+ const opened = await openShellTool(tool)
15085
+ if (!opened) {
15086
+ setPickerStatus("Could not open this shell.")
15087
+ }
15088
+ return
15089
+ }
15090
+ if (await redirectToPluginInstallIfNeeded(tool.href)) {
15091
+ return
15092
+ }
13247
15093
  const opened = openWithUrl(tool.href, { workspaceCwd })
13248
15094
  if (!opened) {
13249
15095
  setPickerStatus("Could not launch this plugin.")
@@ -13255,7 +15101,7 @@ document.addEventListener("DOMContentLoaded", () => {
13255
15101
  const renderPicker = (tools, workspaceCwd) => {
13256
15102
  clearPickerList()
13257
15103
  if (!Array.isArray(tools) || tools.length === 0) {
13258
- setPickerStatus("No Ask AI plugins available.")
15104
+ setPickerStatus("No launch options available.")
13259
15105
  return
13260
15106
  }
13261
15107
  const groups = new Map()
@@ -13266,7 +15112,7 @@ document.addEventListener("DOMContentLoaded", () => {
13266
15112
  }
13267
15113
  groups.get(category).push(tool)
13268
15114
  })
13269
- const ordered = ["CLI", "IDE"].concat(Array.from(groups.keys()).filter((name) => name !== "CLI" && name !== "IDE"))
15115
+ const ordered = ["SHELL", "CLI", "IDE"].concat(Array.from(groups.keys()).filter((name) => !["SHELL", "CLI", "IDE"].includes(name)))
13270
15116
  let renderedCount = 0
13271
15117
  ordered.forEach((category) => {
13272
15118
  const items = (groups.get(category) || []).slice().sort((a, b) =>
@@ -13279,9 +15125,9 @@ document.addEventListener("DOMContentLoaded", () => {
13279
15125
  if (items.length === 0) {
13280
15126
  return
13281
15127
  }
13282
- const displayCategory = category === "IDE"
13283
- ? "Desktop Plugins"
13284
- : (category === "CLI" ? "Terminal Plugins" : category)
15128
+ const displayCategory = category === "SHELL"
15129
+ ? "Project Shell"
15130
+ : (category === "IDE" ? "Desktop Plugins" : (category === "CLI" ? "Terminal Plugins" : category))
13285
15131
  const group = document.createElement("div")
13286
15132
  group.className = "ask-ai-drawer-picker-group"
13287
15133
  const label = document.createElement("div")
@@ -13297,14 +15143,15 @@ document.addEventListener("DOMContentLoaded", () => {
13297
15143
  }
13298
15144
  })
13299
15145
  if (!renderedCount) {
13300
- setPickerStatus("No Ask AI plugins available.")
15146
+ setPickerStatus("No launch options available.")
13301
15147
  return
13302
15148
  }
13303
- setPickerStatus("Select a Terminal or Desktop plugin to launch.")
15149
+ setPickerStatus("Select a shell or plugin to launch.")
13304
15150
  }
13305
15151
 
13306
15152
  const openWithUrl = (rawUrl, opts = {}) => {
13307
15153
  const workspaceCwd = opts && typeof opts.workspaceCwd === "string" ? opts.workspaceCwd : ""
15154
+ const frameName = opts && typeof opts.frameName === "string" ? opts.frameName.trim() : ""
13308
15155
  const normalized = normalizeLaunchUrl(rawUrl, workspaceCwd)
13309
15156
  if (!normalized) {
13310
15157
  return false
@@ -13316,6 +15163,12 @@ document.addEventListener("DOMContentLoaded", () => {
13316
15163
  setEmptyState(false)
13317
15164
  if (frame) {
13318
15165
  frame.dataset.loaded = "1"
15166
+ if (frameName) {
15167
+ frame.name = frameName
15168
+ frame.setAttribute("name", frameName)
15169
+ } else {
15170
+ frame.removeAttribute("name")
15171
+ }
13319
15172
  frame.src = normalized
13320
15173
  }
13321
15174
  setOpen(true)
@@ -13341,8 +15194,8 @@ document.addEventListener("DOMContentLoaded", () => {
13341
15194
  setEmptyState(true)
13342
15195
  setOpen(true)
13343
15196
  clearPickerList()
13344
- setPickerStatus("Loading plugins...")
13345
- getAskAiTools().then((tools) => {
15197
+ setPickerStatus("Loading launch options...")
15198
+ getAskAiTools(workspaceCwd).then((tools) => {
13346
15199
  if (requestId !== pickerRequestId) {
13347
15200
  return
13348
15201
  }
@@ -13623,7 +15476,7 @@ document.addEventListener("DOMContentLoaded", () => {
13623
15476
  setLocation("")
13624
15477
  setActionState(false)
13625
15478
  setEmptyState(true)
13626
- setPickerStatus("Use the Ask AI button to load available plugins.")
15479
+ setPickerStatus("Use the Ask AI button to load launch options.")
13627
15480
  setOpen(false)
13628
15481
  window.PinokioAskAiDrawer = {
13629
15482
  openWithAgent,
@@ -14048,6 +15901,208 @@ document.addEventListener("DOMContentLoaded", () => {
14048
15901
  }
14049
15902
  })();
14050
15903
 
15904
+ (function() {
15905
+ const drawer = document.getElementById("app-logs-drawer")
15906
+ if (!drawer) {
15907
+ return
15908
+ }
15909
+ const appcanvas = document.querySelector(".appcanvas")
15910
+ const toggle = document.getElementById("logs-toggle")
15911
+ const mobileToggle = document.querySelector(".mobile-nav-logs")
15912
+ const closeBtn = drawer.querySelector(".app-logs-close")
15913
+ const refreshBtn = drawer.querySelector(".app-logs-refresh")
15914
+ const copyBtn = drawer.querySelector(".app-logs-copy")
15915
+ const statusEl = drawer.querySelector(".app-logs-status")
15916
+ const outputEl = drawer.querySelector(".app-logs-report-output")
15917
+ const sectionList = drawer.querySelector(".app-logs-section-list")
15918
+ const sectionsValue = drawer.querySelector('[data-app-logs-summary="sections"]')
15919
+ const redactionsValue = drawer.querySelector('[data-app-logs-summary="redactions"]')
15920
+ const reportUrl = drawer.dataset.reportUrl || ""
15921
+ let open = false
15922
+ let loading = false
15923
+ let report = null
15924
+
15925
+ const setStatus = (text) => {
15926
+ if (statusEl) {
15927
+ statusEl.textContent = text
15928
+ }
15929
+ }
15930
+
15931
+ const updateToggleState = () => {
15932
+ const logsPageSelected = !!(window.PinokioIsLogsPageSelected && window.PinokioIsLogsPageSelected())
15933
+ const selected = open || logsPageSelected
15934
+ if (toggle) {
15935
+ toggle.classList.toggle("selected", selected)
15936
+ toggle.setAttribute("aria-expanded", open ? "true" : "false")
15937
+ }
15938
+ if (mobileToggle) {
15939
+ mobileToggle.classList.toggle("selected", selected)
15940
+ mobileToggle.setAttribute("aria-pressed", selected ? "true" : "false")
15941
+ }
15942
+ }
15943
+
15944
+ const setOpen = (next, options = {}) => {
15945
+ open = !!next
15946
+ if (appcanvas) {
15947
+ appcanvas.classList.toggle("logs-open", open)
15948
+ }
15949
+ drawer.setAttribute("aria-hidden", open ? "false" : "true")
15950
+ updateToggleState()
15951
+ if (open) {
15952
+ loadReport(Boolean(options && options.force))
15953
+ }
15954
+ }
15955
+
15956
+ const countRedactions = (counts) => {
15957
+ return Object.values(counts || {}).reduce((total, value) => {
15958
+ const numeric = Number(value)
15959
+ return total + (Number.isFinite(numeric) ? numeric : 0)
15960
+ }, 0)
15961
+ }
15962
+
15963
+ const clearSections = () => {
15964
+ if (!sectionList) return
15965
+ while (sectionList.firstChild) {
15966
+ sectionList.removeChild(sectionList.firstChild)
15967
+ }
15968
+ }
15969
+
15970
+ const renderSections = (sections) => {
15971
+ clearSections()
15972
+ if (!sectionList) return
15973
+ for (const section of sections || []) {
15974
+ const chip = document.createElement("div")
15975
+ chip.className = "app-logs-section-chip"
15976
+ chip.textContent = section.file || section.script || section.source || "log"
15977
+ chip.title = chip.textContent
15978
+ sectionList.appendChild(chip)
15979
+ }
15980
+ }
15981
+
15982
+ const renderReport = (payload) => {
15983
+ const sections = Array.isArray(payload && payload.sections) ? payload.sections : []
15984
+ const redactionCount = countRedactions(payload && payload.redactions)
15985
+ if (sectionsValue) {
15986
+ sectionsValue.textContent = String(sections.length)
15987
+ }
15988
+ if (redactionsValue) {
15989
+ redactionsValue.textContent = redactionCount > 0 ? `${redactionCount} hidden` : "0"
15990
+ }
15991
+ renderSections(sections)
15992
+ if (outputEl) {
15993
+ outputEl.textContent = payload && payload.markdown ? payload.markdown : "No app log files were found."
15994
+ }
15995
+ if (copyBtn) {
15996
+ copyBtn.disabled = !(payload && payload.markdown)
15997
+ }
15998
+ setStatus(`Basic redaction applied. ${sections.length} latest log file${sections.length === 1 ? "" : "s"} included.`)
15999
+ }
16000
+
16001
+ const renderError = (message) => {
16002
+ if (sectionsValue) sectionsValue.textContent = "--"
16003
+ if (redactionsValue) redactionsValue.textContent = "--"
16004
+ clearSections()
16005
+ if (outputEl) {
16006
+ outputEl.textContent = message
16007
+ }
16008
+ if (copyBtn) {
16009
+ copyBtn.disabled = true
16010
+ }
16011
+ setStatus("Unable to build log report.")
16012
+ }
16013
+
16014
+ async function loadReport(force) {
16015
+ if (!reportUrl || loading) {
16016
+ return
16017
+ }
16018
+ if (report && !force) {
16019
+ renderReport(report)
16020
+ return
16021
+ }
16022
+ loading = true
16023
+ if (refreshBtn) {
16024
+ refreshBtn.disabled = true
16025
+ }
16026
+ setStatus("Building sanitized log report...")
16027
+ try {
16028
+ const response = await fetch(reportUrl, {
16029
+ headers: { "Accept": "application/json" },
16030
+ cache: "no-store"
16031
+ })
16032
+ const payload = await response.json().catch(() => null)
16033
+ if (!response.ok) {
16034
+ throw new Error(payload && payload.error ? payload.error : `HTTP ${response.status}`)
16035
+ }
16036
+ report = payload
16037
+ renderReport(report)
16038
+ } catch (error) {
16039
+ renderError(error && error.message ? error.message : String(error || "Unknown error"))
16040
+ } finally {
16041
+ loading = false
16042
+ if (refreshBtn) {
16043
+ refreshBtn.disabled = false
16044
+ }
16045
+ }
16046
+ }
16047
+
16048
+ const copyReport = async () => {
16049
+ if (!report || !report.markdown) {
16050
+ return
16051
+ }
16052
+ try {
16053
+ await navigator.clipboard.writeText(report.markdown)
16054
+ setStatus("Report copied.")
16055
+ } catch (_) {
16056
+ if (outputEl) {
16057
+ outputEl.focus()
16058
+ const selection = window.getSelection()
16059
+ const range = document.createRange()
16060
+ range.selectNodeContents(outputEl)
16061
+ selection.removeAllRanges()
16062
+ selection.addRange(range)
16063
+ }
16064
+ setStatus("Select the report text to copy.")
16065
+ }
16066
+ }
16067
+
16068
+ if (toggle) {
16069
+ toggle.addEventListener("click", (event) => {
16070
+ event.preventDefault()
16071
+ if (window.PinokioOpenLogsPage && typeof window.PinokioOpenLogsPage === "function") {
16072
+ window.PinokioOpenLogsPage({ view: "latest" })
16073
+ return
16074
+ }
16075
+ setOpen(!open)
16076
+ })
16077
+ }
16078
+ if (closeBtn) {
16079
+ closeBtn.addEventListener("click", () => setOpen(false))
16080
+ }
16081
+ if (refreshBtn) {
16082
+ refreshBtn.addEventListener("click", () => loadReport(true))
16083
+ }
16084
+ if (copyBtn) {
16085
+ copyBtn.addEventListener("click", copyReport)
16086
+ }
16087
+ const communityToggle = document.getElementById("community-toggle")
16088
+ if (communityToggle) {
16089
+ communityToggle.addEventListener("click", () => {
16090
+ if (open) {
16091
+ setOpen(false)
16092
+ }
16093
+ }, { capture: true })
16094
+ }
16095
+
16096
+ window.PinokioLogsDrawer = {
16097
+ open: (options = {}) => setOpen(true, options),
16098
+ close: () => setOpen(false),
16099
+ refresh: () => loadReport(true),
16100
+ isOpen: () => open
16101
+ }
16102
+
16103
+ setOpen(false)
16104
+ })();
16105
+
14051
16106
  (function() {
14052
16107
  const drawer = document.getElementById("community-drawer")
14053
16108
  if (!drawer) {
@@ -14765,6 +16820,275 @@ document.addEventListener("DOMContentLoaded", () => {
14765
16820
  }
14766
16821
  })()
14767
16822
 
16823
+ ;(function() {
16824
+ const root = document.querySelector("[data-app-autolaunch]")
16825
+ const initialState = <%- JSON.stringify((typeof autolaunch_app !== "undefined" && autolaunch_app) ? autolaunch_app : null).replace(/</g, "\\u003c") %>
16826
+ if (!root || !initialState) return
16827
+
16828
+ const appId = root.getAttribute("data-app-id") || initialState.id
16829
+ const button = root.querySelector("[data-app-autolaunch-button]")
16830
+ const status = root.querySelector("[data-app-autolaunch-status]")
16831
+ const modal = root.querySelector("[data-app-autolaunch-modal]")
16832
+ const switchButton = root.querySelector("[data-app-autolaunch-switch]")
16833
+ const switchLabel = root.querySelector("[data-app-autolaunch-switch-label]")
16834
+ const closeButton = root.querySelector("[data-app-autolaunch-close]")
16835
+ const scriptsEl = root.querySelector("[data-app-autolaunch-scripts]")
16836
+ const feedbackEl = root.querySelector("[data-app-autolaunch-feedback]")
16837
+ if (!appId || !button || !status || !modal || !switchButton || !switchLabel || !closeButton || !scriptsEl || !feedbackEl) return
16838
+
16839
+ let state = Object.assign({}, initialState)
16840
+ let candidateState = null
16841
+ let candidateError = ""
16842
+ let selectedScript = state.autolaunch || ""
16843
+ let loadingCandidates = false
16844
+ let saving = false
16845
+
16846
+ const escapeHtml = (value) => {
16847
+ if (value === null || value === undefined) return ""
16848
+ return String(value).replace(/[&<>"']/g, (match) => {
16849
+ switch (match) {
16850
+ case "&": return "&amp;"
16851
+ case "<": return "&lt;"
16852
+ case ">": return "&gt;"
16853
+ case "\"": return "&quot;"
16854
+ case "'": return "&#39;"
16855
+ default: return match
16856
+ }
16857
+ })
16858
+ }
16859
+ const allCandidates = () => {
16860
+ if (!candidateState) return []
16861
+ return [].concat(candidateState.menu || [], candidateState.other || [])
16862
+ }
16863
+ const chooseInitialScript = (data) => {
16864
+ const candidates = data ? [].concat(data.menu || [], data.other || []) : []
16865
+ if (data && data.current) return data.current
16866
+ const defaultCandidate = candidates.find((candidate) => candidate && candidate.menu_default)
16867
+ if (defaultCandidate) return defaultCandidate.script
16868
+ return candidates.length > 0 ? candidates[0].script : ""
16869
+ }
16870
+ const setFeedback = (message, isError = false) => {
16871
+ feedbackEl.textContent = message || ""
16872
+ feedbackEl.classList.toggle("error", !!isError)
16873
+ }
16874
+ const setBusy = (busy) => {
16875
+ saving = !!busy
16876
+ switchButton.disabled = saving || loadingCandidates
16877
+ button.toggleAttribute("aria-busy", saving)
16878
+ scriptsEl.querySelectorAll("input").forEach((input) => {
16879
+ input.disabled = saving
16880
+ })
16881
+ }
16882
+ const renderStatus = () => {
16883
+ const enabled = !!(state && state.autolaunch_enabled)
16884
+ status.textContent = enabled ? "ON" : "OFF"
16885
+ button.dataset.enabled = enabled ? "true" : "false"
16886
+ button.setAttribute("aria-label", `Auto launch ${enabled ? "On" : "Off"}`)
16887
+ switchButton.setAttribute("aria-checked", enabled ? "true" : "false")
16888
+ switchButton.title = `Auto launch is ${enabled ? "on" : "off"}`
16889
+ switchLabel.textContent = enabled ? "ON" : "OFF"
16890
+ root.classList.toggle("enabled", enabled)
16891
+ }
16892
+ const setOpen = (open) => {
16893
+ root.classList.toggle("open", !!open)
16894
+ button.setAttribute("aria-expanded", open ? "true" : "false")
16895
+ modal.classList.toggle("hidden", !open)
16896
+ if (open) {
16897
+ if (!candidateState && !loadingCandidates) {
16898
+ loadCandidates()
16899
+ }
16900
+ window.setTimeout(() => {
16901
+ if (!modal.classList.contains("hidden")) {
16902
+ switchButton.focus()
16903
+ }
16904
+ }, 0)
16905
+ } else {
16906
+ button.focus()
16907
+ }
16908
+ }
16909
+ const renderScripts = () => {
16910
+ if (loadingCandidates) {
16911
+ scriptsEl.innerHTML = '<div class="app-autolaunch-empty">Loading scripts...</div>'
16912
+ setBusy(false)
16913
+ return
16914
+ }
16915
+ if (candidateError) {
16916
+ scriptsEl.innerHTML = `<div class="app-autolaunch-empty">${escapeHtml(candidateError)}</div>`
16917
+ setBusy(false)
16918
+ return
16919
+ }
16920
+ if (!candidateState) {
16921
+ scriptsEl.innerHTML = '<div class="app-autolaunch-empty">Open to load scripts.</div>'
16922
+ setBusy(false)
16923
+ return
16924
+ }
16925
+ const candidates = allCandidates().slice()
16926
+ const current = candidateState.current || state.autolaunch || ""
16927
+ if (current && !candidates.some((candidate) => candidate && candidate.script === current)) {
16928
+ candidates.unshift({
16929
+ script: current,
16930
+ label: "Current",
16931
+ source: "current",
16932
+ current: true
16933
+ })
16934
+ }
16935
+ if (!selectedScript) {
16936
+ selectedScript = chooseInitialScript(candidateState)
16937
+ }
16938
+ if (candidates.length === 0) {
16939
+ scriptsEl.innerHTML = '<div class="app-autolaunch-empty">No local scripts were found for this app.</div>'
16940
+ setBusy(false)
16941
+ return
16942
+ }
16943
+ scriptsEl.innerHTML = candidates.map((candidate) => {
16944
+ const script = candidate && candidate.script ? candidate.script : ""
16945
+ if (!script) return ""
16946
+ const label = candidate.label || script
16947
+ const checked = script === selectedScript ? "checked" : ""
16948
+ const selected = script === selectedScript ? " selected" : ""
16949
+ const tags = [
16950
+ candidate.current ? '<span class="app-autolaunch-tag">Current</span>' : "",
16951
+ candidate.menu_default ? '<span class="app-autolaunch-tag">Default</span>' : "",
16952
+ candidate.has_params ? '<span class="app-autolaunch-tag">Params ignored</span>' : ""
16953
+ ].filter(Boolean).join("")
16954
+ const pathLine = label === script
16955
+ ? ""
16956
+ : `<div class="app-autolaunch-script-path" title="${escapeHtml(script)}">${escapeHtml(script)}</div>`
16957
+ return `
16958
+ <label class="app-autolaunch-script-option${selected}">
16959
+ <input type="radio" name="app-autolaunch-script" value="${escapeHtml(script)}" ${checked}>
16960
+ <div>
16961
+ <div class="app-autolaunch-script-title"><span>${escapeHtml(label)}</span>${tags}</div>
16962
+ ${pathLine}
16963
+ </div>
16964
+ </label>
16965
+ `
16966
+ }).join("")
16967
+ setBusy(false)
16968
+ }
16969
+ const syncState = (updated) => {
16970
+ if (updated) {
16971
+ state = Object.assign({}, state, updated)
16972
+ if (candidateState) {
16973
+ candidateState.app = Object.assign({}, candidateState.app || {}, updated)
16974
+ candidateState.current = updated.autolaunch || ""
16975
+ }
16976
+ if (updated.autolaunch) {
16977
+ selectedScript = updated.autolaunch
16978
+ }
16979
+ }
16980
+ renderStatus()
16981
+ }
16982
+ const loadCandidates = async () => {
16983
+ loadingCandidates = true
16984
+ candidateError = ""
16985
+ setFeedback("")
16986
+ renderScripts()
16987
+ try {
16988
+ const response = await fetch(`/autolaunch/candidates?app=${encodeURIComponent(appId)}`, {
16989
+ headers: { "Accept": "application/json" }
16990
+ })
16991
+ const data = await response.json()
16992
+ if (!response.ok || !data.ok) {
16993
+ throw new Error(data.error || "Failed to load scripts.")
16994
+ }
16995
+ candidateState = data
16996
+ syncState(data.app)
16997
+ selectedScript = chooseInitialScript(data)
16998
+ setFeedback("")
16999
+ } catch (error) {
17000
+ candidateError = error && error.message ? error.message : "Failed to load scripts."
17001
+ } finally {
17002
+ loadingCandidates = false
17003
+ renderScripts()
17004
+ }
17005
+ }
17006
+ const saveScript = async (script) => {
17007
+ if (saving) return
17008
+ setBusy(true)
17009
+ setFeedback(script ? "Saving..." : "Disabling...")
17010
+ try {
17011
+ const response = await fetch("/autolaunch", {
17012
+ method: "POST",
17013
+ headers: {
17014
+ "Content-Type": "application/json",
17015
+ "Accept": "application/json"
17016
+ },
17017
+ body: JSON.stringify({ app: appId, script: script || "" })
17018
+ })
17019
+ const data = await response.json()
17020
+ if (!response.ok || !data.ok) {
17021
+ throw new Error(data.error || "Failed to save autolaunch.")
17022
+ }
17023
+ syncState(data.app)
17024
+ setFeedback(data.app && data.app.autolaunch_enabled ? "Saved." : "Disabled.")
17025
+ renderScripts()
17026
+ } catch (error) {
17027
+ setFeedback(error && error.message ? error.message : "Failed to save autolaunch.", true)
17028
+ renderStatus()
17029
+ } finally {
17030
+ setBusy(false)
17031
+ }
17032
+ }
17033
+ const toggleAutolaunch = async () => {
17034
+ if (saving || loadingCandidates) return
17035
+ if (state.autolaunch_enabled) {
17036
+ await saveScript("")
17037
+ return
17038
+ }
17039
+ if (!candidateState) {
17040
+ await loadCandidates()
17041
+ }
17042
+ const script = selectedScript || chooseInitialScript(candidateState)
17043
+ if (!script) {
17044
+ setFeedback("Choose a script first.", true)
17045
+ return
17046
+ }
17047
+ selectedScript = script
17048
+ await saveScript(script)
17049
+ }
17050
+
17051
+ renderStatus()
17052
+ renderScripts()
17053
+
17054
+ button.addEventListener("click", (event) => {
17055
+ event.preventDefault()
17056
+ event.stopPropagation()
17057
+ setOpen(modal.classList.contains("hidden"))
17058
+ })
17059
+ closeButton.addEventListener("click", (event) => {
17060
+ event.preventDefault()
17061
+ event.stopPropagation()
17062
+ setOpen(false)
17063
+ })
17064
+ switchButton.addEventListener("click", (event) => {
17065
+ event.preventDefault()
17066
+ event.stopPropagation()
17067
+ toggleAutolaunch()
17068
+ })
17069
+ modal.addEventListener("click", (event) => {
17070
+ if (event.target === modal) {
17071
+ setOpen(false)
17072
+ }
17073
+ })
17074
+ scriptsEl.addEventListener("change", (event) => {
17075
+ const input = event.target && event.target.matches("input[name='app-autolaunch-script']")
17076
+ ? event.target
17077
+ : null
17078
+ if (!input) return
17079
+ selectedScript = input.value
17080
+ renderScripts()
17081
+ if (state.autolaunch_enabled && selectedScript !== state.autolaunch) {
17082
+ saveScript(selectedScript)
17083
+ }
17084
+ })
17085
+ document.addEventListener("keydown", (event) => {
17086
+ if (event.key === "Escape" && !modal.classList.contains("hidden")) {
17087
+ setOpen(false)
17088
+ }
17089
+ })
17090
+ })()
17091
+
14768
17092
  ;(function() {
14769
17093
  const scroller = document.querySelector(".appcanvas > aside .menu-scroller")
14770
17094
  if (!scroller) return