pinokiod 7.5.19 → 7.5.21

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 (34) hide show
  1. package/kernel/bin/index.js +0 -5
  2. package/kernel/bin/setup.js +0 -1
  3. package/kernel/index.js +0 -3
  4. package/package.json +1 -1
  5. package/server/public/common.js +1 -1
  6. package/server/public/style.css +34 -0
  7. package/server/public/urldropdown.css +272 -0
  8. package/server/public/urldropdown.js +44 -12
  9. package/server/views/app.ejs +190 -146
  10. package/server/views/autolaunch.ejs +10 -1
  11. package/server/views/columns.ejs +13 -1
  12. package/server/views/d.ejs +54 -137
  13. package/server/views/index.ejs +14 -6
  14. package/server/views/partials/app_autolaunch_modal_helpers.ejs +4 -0
  15. package/server/views/partials/d_column_subtitle.ejs +10 -0
  16. package/server/views/partials/d_menu_column.ejs +18 -0
  17. package/server/views/partials/d_terminal_column.ejs +2 -11
  18. package/server/views/partials/d_terminal_options.ejs +1 -18
  19. package/server/views/partials/d_tool_tab.ejs +32 -0
  20. package/server/views/partials/main_sidebar.ejs +3 -0
  21. package/server/views/rows.ejs +13 -1
  22. package/system/plugin/antigravity-cli/common.js +10 -16
  23. package/test/antigravity-cli-plugin.test.js +26 -19
  24. package/test/home-autolaunch-live-ui.test.js +70 -0
  25. package/test/launch-requirements-browser.test.js +259 -3
  26. package/test/plugin-dev-iframe.test.js +67 -1
  27. package/test/plugin-sources.test.js +1 -1
  28. package/test/url-split-modal.test.js +107 -0
  29. package/system/plugin/crush/crush.png +0 -0
  30. package/system/plugin/crush/pinokio.js +0 -15
  31. package/system/plugin/qwen/pinokio.js +0 -34
  32. package/system/plugin/qwen/qwen.png +0 -0
  33. package/system/plugin/windsurf/pinokio.js +0 -23
  34. package/system/plugin/windsurf/windsurf.png +0 -0
@@ -872,144 +872,44 @@ body.dark .terminal-shell-options {
872
872
  </div>
873
873
 
874
874
  <%
875
- let index = 0;
876
- const userTerminal = dynamic && dynamic.length ? dynamic[0] : null;
877
- const cliMenu = dynamic.find((item) => item && item.title === "Terminal Apps");
878
- const ideMenu = dynamic.find((item) => item && item.title === "Desktop Apps");
875
+ const dynamicItems = Array.isArray(dynamic) ? dynamic : [];
876
+ const indexState = { value: 0 };
877
+ const getItemDescription = (item) => {
878
+ if (!item || typeof item !== "object") {
879
+ return "";
880
+ }
881
+ const candidates = [
882
+ item.description,
883
+ item.subtitle,
884
+ item.summary,
885
+ item.metadata && item.metadata.description,
886
+ item.meta && item.meta.description,
887
+ ];
888
+ for (const candidate of candidates) {
889
+ if (typeof candidate === "string" && candidate.trim()) {
890
+ return candidate.trim();
891
+ }
892
+ }
893
+ return "";
894
+ };
895
+ const userTerminal = dynamicItems.find((item) => item && (item.title === "Terminals" || item.title === "Project Shell"));
896
+ const cliMenu = dynamicItems.find((item) => item && item.title === "Terminal Apps");
897
+ const ideMenu = dynamicItems.find((item) => item && item.title === "Desktop Apps");
879
898
  const handled = new Set();
880
- const primaryColumnCount = [userTerminal, cliMenu, ideMenu].filter((item) => item && item.menu && item.menu.length).length;
881
899
  if (userTerminal) handled.add(userTerminal);
882
900
  if (cliMenu) handled.add(cliMenu);
883
901
  if (ideMenu) handled.add(ideMenu);
884
- const remaining = dynamic.filter((item) => !handled.has(item));
902
+ const remaining = dynamicItems.filter((item) => !handled.has(item));
885
903
  %>
886
904
 
887
- <div class='menu-grid <%= primaryColumnCount === 3 ? "menu-grid--triptych" : "" %>'>
888
- <% if (userTerminal && userTerminal.menu && userTerminal.menu.length) { %>
889
- <%- include('./partials/d_terminal_column', { userTerminal }) %>
890
- <% } %>
891
-
892
- <% if (cliMenu && cliMenu.menu && cliMenu.menu.length) { %>
893
- <div class='menu-container menu-column cli-column'>
894
- <div class='tab-header'>
895
- <h3><i class='<%= cliMenu.icon %>'></i> <%= cliMenu.title %></h3>
896
- </div>
897
- <% if (cliMenu.subtitle || cliMenu.subtitle_link_href) { %>
898
- <div class='column-subtitle'>
899
- <% if (cliMenu.subtitle) { %>
900
- <span><%= cliMenu.subtitle %></span><% if (cliMenu.subtitle_link_href && cliMenu.subtitle_link_label) { %> <% } %>
901
- <% } %>
902
- <% if (cliMenu.subtitle_link_href && cliMenu.subtitle_link_label) { %>
903
- <a class='column-subtitle-link' href="<%= cliMenu.subtitle_link_href %>" target="_parent"><%= cliMenu.subtitle_link_label %></a>
904
- <% } %>
905
- </div>
906
- <% } %>
907
- <div class='tab-content'>
908
- <% cliMenu.menu.forEach((i) => { %>
909
- <div class='tab' role="button" tabindex="0" data-index="<%= index++ %>" data-target="@<%= i.href %>" data-href="<%= i.href %>">
910
- <% if (i.image) { %>
911
- <img src="<%= i.image %>">
912
- <% } else if (i.icon) { %>
913
- <i class="img <%= i.icon %>"></i>
914
- <% } %>
915
- <div class='tab-copy'>
916
- <h2 class='tab-title'><%= i.title %></h2>
917
- <% if (i.subtitle) { %>
918
- <div class='tab-subtitle subtitle'><%= i.subtitle %></div>
919
- <% } %>
920
- </div>
921
- <div class='tab-actions'>
922
- <button class="tab-action-link" type="button" data-plugin-settings="true" data-href="<%= i.href %>" title="Manage plugin" data-tippy-content="Manage plugin" aria-label="Manage plugin">
923
- <i class="fa-solid fa-gear"></i>
924
- </button>
925
- <div class='disclosure-indicator' aria-hidden="true">
926
- <i class="fa-solid fa-chevron-right"></i>
927
- </div>
928
- </div>
929
- </div>
930
- <% }) %>
931
- </div>
932
- </div>
933
- <% } %>
934
-
935
- <% if (ideMenu && ideMenu.menu && ideMenu.menu.length) { %>
936
- <div class='menu-container menu-column ide-column'>
937
- <div class='tab-header'>
938
- <h3><i class='<%= ideMenu.icon %>'></i> <%= ideMenu.title %></h3>
939
- </div>
940
- <% if (ideMenu.subtitle || ideMenu.subtitle_link_href) { %>
941
- <div class='column-subtitle'>
942
- <% if (ideMenu.subtitle) { %>
943
- <span><%= ideMenu.subtitle %></span><% if (ideMenu.subtitle_link_href && ideMenu.subtitle_link_label) { %> <% } %>
944
- <% } %>
945
- <% if (ideMenu.subtitle_link_href && ideMenu.subtitle_link_label) { %>
946
- <a class='column-subtitle-link' href="<%= ideMenu.subtitle_link_href %>" target="_parent"><%= ideMenu.subtitle_link_label %></a>
947
- <% } %>
948
- </div>
949
- <% } %>
950
- <div class='tab-content'>
951
- <% ideMenu.menu.forEach((i) => { %>
952
- <div class='tab' role="button" tabindex="0" data-index="<%= index++ %>" data-target="@<%= i.href %>" data-href="<%= i.href %>">
953
- <% if (i.image) { %>
954
- <img src="<%= i.image %>">
955
- <% } else if (i.icon) { %>
956
- <i class="img <%= i.icon %>"></i>
957
- <% } %>
958
- <div class='tab-copy'>
959
- <h2 class='tab-title'><%= i.title %></h2>
960
- <% if (i.subtitle) { %>
961
- <div class='tab-subtitle subtitle'><%= i.subtitle %></div>
962
- <% } %>
963
- </div>
964
- <div class='tab-actions'>
965
- <button class="tab-action-link" type="button" data-plugin-settings="true" data-href="<%= i.href %>" title="Manage plugin" data-tippy-content="Manage plugin" aria-label="Manage plugin">
966
- <i class="fa-solid fa-gear"></i>
967
- </button>
968
- <div class='disclosure-indicator' aria-hidden="true">
969
- <i class="fa-solid fa-chevron-right"></i>
970
- </div>
971
- </div>
972
- </div>
973
- <% }) %>
974
- </div>
975
- </div>
976
- <% } %>
977
- </div>
978
-
979
- <% remaining.forEach((item) => { %>
980
- <% if (item.menu) { %>
981
- <div class='menu-container'>
982
- <div class='tab-header'>
983
- <h3><i class='<%= item.icon %>'></i> <%= item.title %></h3>
984
- </div>
985
- <div class='tab-content'>
986
- <% item.menu.forEach((i) => { %>
987
- <div class='tab' role="button" tabindex="0" data-index="<%= index++ %>" data-target="@<%= i.href %>" data-href="<%= i.href %>">
988
- <% if (i.image) { %>
989
- <img src="<%= i.image %>">
990
- <% } else if (i.icon) { %>
991
- <i class="img <%= i.icon %>"></i>
992
- <% } %>
993
- <div class='tab-copy'>
994
- <h2 class='tab-title'><%= i.title %></h2>
995
- <% if (i.subtitle) { %>
996
- <div class='tab-subtitle subtitle'><%= i.subtitle %></div>
997
- <% } %>
998
- </div>
999
- <div class='tab-actions'>
1000
- <button class="tab-action-link" type="button" data-plugin-settings="true" data-href="<%= i.href %>" title="Manage plugin" data-tippy-content="Manage plugin" aria-label="Manage plugin">
1001
- <i class="fa-solid fa-gear"></i>
1002
- </button>
1003
- <div class='disclosure-indicator' aria-hidden="true">
1004
- <i class="fa-solid fa-chevron-right"></i>
1005
- </div>
1006
- </div>
1007
- </div>
1008
- <% }) %>
1009
- </div>
1010
- </div>
1011
- <% } else { %>
1012
- <div class='tab' data-index="<%= index++ %>">
905
+ <div class='menu-grid'>
906
+ <%- include('./partials/d_menu_column', { section: cliMenu, indexState, getItemDescription, className: 'cli-column', showSettings: true }) %>
907
+ <%- include('./partials/d_menu_column', { section: ideMenu, indexState, getItemDescription, className: 'ide-column', showSettings: true }) %>
908
+ <% remaining.forEach((item) => { %>
909
+ <% if (item.menu) { %>
910
+ <%- include('./partials/d_menu_column', { section: item, indexState, getItemDescription, showSettings: true }) %>
911
+ <% } else { %>
912
+ <div class='tab' data-index="<%= indexState.value++ %>">
1013
913
  <% if (item.image) { %>
1014
914
  <img src="<%= item.image %>">
1015
915
  <% } else if (item.icon) { %>
@@ -1017,8 +917,9 @@ body.dark .terminal-shell-options {
1017
917
  <% } %>
1018
918
  <div class='col'>
1019
919
  <h2><%= item.title %></h2>
1020
- <% if (item.subtitle) { %>
1021
- <div class='subtitle'><%= item.subtitle %></div>
920
+ <% const itemDescription = getItemDescription(item); %>
921
+ <% if (itemDescription) { %>
922
+ <div class='subtitle'><%= itemDescription %></div>
1022
923
  <% } %>
1023
924
  <div class='btns'>
1024
925
  <% if (item.link) { %>
@@ -1028,8 +929,12 @@ body.dark .terminal-shell-options {
1028
929
  </div>
1029
930
  </div>
1030
931
  </div>
932
+ <% } %>
933
+ <% }) %>
934
+ <% if (userTerminal && userTerminal.menu && userTerminal.menu.length) { %>
935
+ <%- include('./partials/d_terminal_column', { userTerminal, indexState, getItemDescription }) %>
1031
936
  <% } %>
1032
- <% }) %>
937
+ </div>
1033
938
  <% if (retry) { %>
1034
939
  <div class='requirements'>
1035
940
  <div class='content'>
@@ -1287,12 +1192,24 @@ const getPluginInstallRedirect = async (rawHref) => {
1287
1192
  pluginInstallPreflightCache.set(pluginPath, redirectHref)
1288
1193
  return redirectHref
1289
1194
  }
1195
+ const navigateOutsideLauncher = (href) => {
1196
+ if (!href) {
1197
+ return
1198
+ }
1199
+ try {
1200
+ if (window.parent && window.parent !== window && window.parent !== window.top) {
1201
+ window.parent.location.href = href
1202
+ return
1203
+ }
1204
+ } catch (_) {}
1205
+ window.location.href = href
1206
+ }
1290
1207
  const redirectToPluginInstallIfNeeded = async (rawHref) => {
1291
1208
  const redirectHref = await getPluginInstallRedirect(rawHref)
1292
1209
  if (!redirectHref) {
1293
1210
  return false
1294
1211
  }
1295
- window.top.location.href = redirectHref
1212
+ navigateOutsideLauncher(redirectHref)
1296
1213
  return true
1297
1214
  }
1298
1215
  const pluginSettingsHrefFromLaunchHref = (rawHref) => {
@@ -1462,7 +1379,7 @@ document.addEventListener("click", async (e) => {
1462
1379
  e.stopPropagation()
1463
1380
  const settingsHref = pluginSettingsHrefFromLaunchHref(settingsTarget.getAttribute('data-href') || '')
1464
1381
  if (settingsHref) {
1465
- window.top.location.href = settingsHref
1382
+ navigateOutsideLauncher(settingsHref)
1466
1383
  }
1467
1384
  return
1468
1385
  }
@@ -2346,9 +2346,11 @@ const startHomeAutolaunchPolling = () => {
2346
2346
  updateHomeActionStatus(line, "Running")
2347
2347
  syncStopButtons(line, scripts)
2348
2348
  const runningSection = getOrCreateRunningSection()
2349
- if (runningSection) {
2349
+ if (runningSection && line.parentElement !== runningSection) {
2350
2350
  runningSection.appendChild(line)
2351
+ return true
2351
2352
  }
2353
+ return false
2352
2354
  }
2353
2355
  const getOrCreateNotRunningSection = () => {
2354
2356
  let section = document.querySelector(".not-running-apps")
@@ -2374,9 +2376,11 @@ const startHomeAutolaunchPolling = () => {
2374
2376
  syncStopButtons(line, [])
2375
2377
  activeHomeAutolaunchApps.delete(line.getAttribute("data-autolaunch-app"))
2376
2378
  const notRunningSection = getOrCreateNotRunningSection()
2377
- if (notRunningSection) {
2379
+ if (notRunningSection && line.parentElement !== notRunningSection) {
2378
2380
  notRunningSection.appendChild(line)
2381
+ return true
2379
2382
  }
2383
+ return false
2380
2384
  }
2381
2385
  const autolaunchDependencyLabel = (autolaunch, dependencyId) => {
2382
2386
  const apps = autolaunch && autolaunch.apps ? autolaunch.apps : {}
@@ -2472,18 +2476,19 @@ const startHomeAutolaunchPolling = () => {
2472
2476
  }
2473
2477
  const autolaunch = state.autolaunch || {}
2474
2478
  const apps = autolaunch.apps || {}
2479
+ let shouldReorder = false
2475
2480
  for (const line of lines) {
2476
2481
  const app = line.getAttribute("data-autolaunch-app")
2477
2482
  const autolaunchState = apps[app]
2478
2483
  const runningScripts = scriptsByApp.get(app) || []
2479
2484
  if (autolaunchState && !shouldDisplayAutolaunchStatus(app, autolaunchState, apps, runningApps)) {
2480
- markAutolaunchIdle(line)
2485
+ shouldReorder = markAutolaunchIdle(line) || shouldReorder
2481
2486
  } else if (isPreparingAutolaunchState(autolaunchState)) {
2482
2487
  updateAutolaunchWaitingVisuals(line, autolaunchState, autolaunch, runningScripts)
2483
2488
  } else if (runningApps.has(app)) {
2484
- markAutolaunchRunning(line, runningScripts)
2489
+ shouldReorder = markAutolaunchRunning(line, runningScripts) || shouldReorder
2485
2490
  } else if (state.launch_complete) {
2486
- markAutolaunchIdle(line)
2491
+ shouldReorder = markAutolaunchIdle(line) || shouldReorder
2487
2492
  } else {
2488
2493
  updateAutolaunchWaitingVisuals(line, autolaunchState, autolaunch, runningScripts)
2489
2494
  }
@@ -2491,9 +2496,12 @@ const startHomeAutolaunchPolling = () => {
2491
2496
  if (getStartingLines().length === 0) {
2492
2497
  if (hadStartingLines) {
2493
2498
  hadStartingLines = false
2494
- reorderHomeSectionsByPreference()
2499
+ shouldReorder = true
2495
2500
  }
2496
2501
  }
2502
+ if (shouldReorder) {
2503
+ reorderHomeSectionsByPreference()
2504
+ }
2497
2505
  }
2498
2506
  const requestHomeStatus = () => {
2499
2507
  return new Promise((resolve, reject) => {
@@ -180,6 +180,10 @@
180
180
  const setOpen = (open) => {
181
181
  root.classList.toggle("open", !!open)
182
182
  button.setAttribute("aria-expanded", open ? "true" : "false")
183
+ if (open && modal.parentElement !== document.body) {
184
+ // Avoid transformed sidebar ancestors trapping the fixed dialog.
185
+ document.body.appendChild(modal)
186
+ }
183
187
  modal.classList.toggle("hidden", !open)
184
188
  if (open) {
185
189
  if (!candidateState && !loadingCandidates) {
@@ -0,0 +1,10 @@
1
+ <% if (section && (section.subtitle || section.subtitle_link_href)) { %>
2
+ <div class='column-subtitle'>
3
+ <% if (section.subtitle) { %>
4
+ <span><%= section.subtitle %></span><% if (section.subtitle_link_href && section.subtitle_link_label) { %> <% } %>
5
+ <% } %>
6
+ <% if (section.subtitle_link_href && section.subtitle_link_label) { %>
7
+ <a class='column-subtitle-link' href="<%= section.subtitle_link_href %>" target="_parent"><%= section.subtitle_link_label %></a>
8
+ <% } %>
9
+ </div>
10
+ <% } %>
@@ -0,0 +1,18 @@
1
+ <%
2
+ const rows = section && Array.isArray(section.menu) ? section.menu : [];
3
+ const sectionClass = typeof className === "string" ? className : "";
4
+ const enableSettings = typeof showSettings === "boolean" ? showSettings : true;
5
+ %>
6
+ <% if (section && rows.length) { %>
7
+ <div class='menu-container menu-column <%= sectionClass %>'>
8
+ <div class='tab-header'>
9
+ <h3><i class='<%= section.icon %>'></i> <%= section.title %></h3>
10
+ </div>
11
+ <%- include('./d_column_subtitle', { section }) %>
12
+ <div class='tab-content'>
13
+ <% rows.forEach((i) => { %>
14
+ <%- include('./d_tool_tab', { i, indexState, getItemDescription, showSettings: enableSettings }) %>
15
+ <% }) %>
16
+ </div>
17
+ </div>
18
+ <% } %>
@@ -2,16 +2,7 @@
2
2
  <div class='tab-header'>
3
3
  <h3><i class='<%= userTerminal.icon %>'></i> <%= userTerminal.title === "Terminals" ? "Project Shell" : userTerminal.title %></h3>
4
4
  </div>
5
- <% if (userTerminal.subtitle || userTerminal.subtitle_link_href) { %>
6
- <div class='column-subtitle'>
7
- <% if (userTerminal.subtitle) { %>
8
- <span><%= userTerminal.subtitle %></span><% if (userTerminal.subtitle_link_href && userTerminal.subtitle_link_label) { %> <% } %>
9
- <% } %>
10
- <% if (userTerminal.subtitle_link_href && userTerminal.subtitle_link_label) { %>
11
- <a class='column-subtitle-link' href="<%= userTerminal.subtitle_link_href %>" target="_parent"><%= userTerminal.subtitle_link_label %></a>
12
- <% } %>
13
- </div>
14
- <% } %>
5
+ <%- include('./d_column_subtitle', { section: userTerminal }) %>
15
6
  <div class='tab-content'>
16
7
  <% userTerminal.menu.forEach((i) => { %>
17
8
  <div class='terminal-shell-group'>
@@ -29,7 +20,7 @@
29
20
  </div>
30
21
  </div>
31
22
  <div class='terminal-shell-options'>
32
- <%- include('./d_terminal_options', { options: i.menu || [] }) %>
23
+ <%- include('./d_terminal_options', { options: i.menu || [], indexState, getItemDescription }) %>
33
24
  </div>
34
25
  </div>
35
26
  <% }) %>
@@ -1,20 +1,3 @@
1
1
  <% options.forEach((i) => { %>
2
- <div class='tab terminal-option-tab' role="button" tabindex="0" data-terminal-option-tab="true" data-target="@<%= i.href %>" data-href="<%= i.href %>">
3
- <% if (i.image) { %>
4
- <img src="<%= i.image %>">
5
- <% } else if (i.icon) { %>
6
- <i class="img <%= i.icon %>"></i>
7
- <% } %>
8
- <div class='tab-copy'>
9
- <h2 class='tab-title'><%= i.title %></h2>
10
- <% if (i.subtitle) { %>
11
- <div class='tab-subtitle subtitle'><%= i.subtitle %></div>
12
- <% } %>
13
- </div>
14
- <div class='tab-actions'>
15
- <div class='disclosure-indicator' aria-hidden="true">
16
- <i class="fa-solid fa-chevron-right"></i>
17
- </div>
18
- </div>
19
- </div>
2
+ <%- include('./d_tool_tab', { i, indexState, getItemDescription, showSettings: false, terminalOption: true, className: 'terminal-option-tab' }) %>
20
3
  <% }) %>
@@ -0,0 +1,32 @@
1
+ <%
2
+ const href = i && typeof i.href === "string" ? i.href : "";
3
+ const target = href ? `@${href}` : "";
4
+ const tabIndex = typeof indexState !== "undefined" && indexState && typeof indexState.value === "number" ? indexState.value++ : 0;
5
+ const tabClass = ["tab", typeof className === "string" ? className : ""].filter(Boolean).join(" ");
6
+ const description = typeof getItemDescription === "function" ? getItemDescription(i) : (i && (i.description || i.subtitle || ""));
7
+ const enableSettings = typeof showSettings !== "undefined" && showSettings === true && href;
8
+ const isTerminalOption = typeof terminalOption !== "undefined" && terminalOption === true;
9
+ %>
10
+ <div class='<%= tabClass %>' role="button" tabindex="0" data-index="<%= tabIndex %>"<% if (isTerminalOption) { %> data-terminal-option-tab="true"<% } %> data-target="<%= target %>" data-href="<%= href %>">
11
+ <% if (i.image) { %>
12
+ <img src="<%= i.image %>">
13
+ <% } else if (i.icon) { %>
14
+ <i class="img <%= i.icon %>"></i>
15
+ <% } %>
16
+ <div class='tab-copy'>
17
+ <h2 class='tab-title'><%= i.title %></h2>
18
+ <% if (description) { %>
19
+ <div class='tab-subtitle subtitle'><%= description %></div>
20
+ <% } %>
21
+ </div>
22
+ <div class='tab-actions'>
23
+ <% if (enableSettings) { %>
24
+ <button class="tab-action-link" type="button" data-plugin-settings="true" data-href="<%= href %>" title="Manage plugin" data-tippy-content="Manage plugin" aria-label="Manage plugin">
25
+ <i class="fa-solid fa-gear"></i>
26
+ </button>
27
+ <% } %>
28
+ <div class='disclosure-indicator' aria-hidden="true">
29
+ <i class="fa-solid fa-chevron-right"></i>
30
+ </div>
31
+ </div>
32
+ </div>
@@ -193,6 +193,9 @@
193
193
  };
194
194
 
195
195
  setCollapsed(false);
196
+ requestAnimationFrame(() => {
197
+ body.classList.add("main-sidebar-motion-ready");
198
+ });
196
199
 
197
200
  headerToggle.addEventListener("click", (event) => {
198
201
  event.preventDefault();
@@ -321,8 +321,20 @@ body[data-agent='electron'] {
321
321
  }
322
322
  });
323
323
  window.addEventListener('message', (event) => {
324
+ if (event.data && event.data.e === "layout-state-request") {
325
+ const closable = Array.from(document.querySelectorAll('iframe')).some((iframe) => {
326
+ return event.source === iframe.contentWindow;
327
+ });
328
+ if (event.source && typeof event.source.postMessage === "function") {
329
+ event.source.postMessage({
330
+ e: "layout-state",
331
+ closable
332
+ }, "*");
333
+ }
334
+ return;
335
+ }
324
336
  if (event.data && event.data.e === "close") {
325
- // find the frame
337
+ // find the frame
326
338
  // Find which iframe sent the message
327
339
  const row0 = document.getElementById('row0');
328
340
  const row1 = document.getElementById('row1');
@@ -1,5 +1,6 @@
1
1
  const fs = require("fs")
2
2
  const path = require("path")
3
+ const installer = require("./install")
3
4
 
4
5
  const MANAGED_DIR_PARTS = ["bin", "antigravity-cli"]
5
6
 
@@ -46,24 +47,17 @@ function workspaceFor(context, kernel) {
46
47
  return args.cwd || kernel.path("home")
47
48
  }
48
49
 
49
- function installSteps(kernel) {
50
+ async function installSteps(kernel) {
51
+ await installer.install({
52
+ installDir: installDir(kernel),
53
+ managedDir: managedDir(kernel),
54
+ })
50
55
  return [{
51
- id: "install",
52
- method: "shell.run",
56
+ id: "installed",
57
+ method: "notify",
53
58
  params: {
54
- conda: { skip: true },
55
- message: {
56
- _: [
57
- process.execPath,
58
- installerPath(),
59
- "--install-dir",
60
- installDir(kernel),
61
- "--managed-dir",
62
- managedDir(kernel),
63
- ]
64
- },
65
- path: kernel.path(),
66
- buffer: 1024,
59
+ html: "Antigravity CLI installed in Pinokio.",
60
+ type: "success",
67
61
  }
68
62
  }]
69
63
  }
@@ -7,6 +7,7 @@ const test = require("node:test")
7
7
  const antigravityCli = require("../system/plugin/antigravity-cli/pinokio")
8
8
  const antigravityCliAuto = require("../system/plugin/antigravity-cli-auto/pinokio")
9
9
  const antigravityCommon = require("../system/plugin/antigravity-cli/common")
10
+ const antigravityInstaller = require("../system/plugin/antigravity-cli/install")
10
11
 
11
12
  function createKernel(root, platform = "darwin") {
12
13
  return {
@@ -25,28 +26,34 @@ test("Antigravity CLI plugins expose managed lifecycle actions", () => {
25
26
  }
26
27
  })
27
28
 
28
- test("Antigravity CLI install and update install agy into Pinokio bin", () => {
29
+ test("Antigravity CLI install and update call the bundled installer internally", async () => {
29
30
  const root = path.join(os.tmpdir(), "pinokio-antigravity-test")
30
31
  const kernel = createKernel(root)
32
+ const calls = []
33
+ const originalInstall = antigravityInstaller.install
34
+ antigravityInstaller.install = async (options) => {
35
+ calls.push(options)
36
+ }
31
37
 
32
- const install = antigravityCli.install(kernel, {})[0]
33
- const update = antigravityCli.update(kernel, {})[0]
34
-
35
- for (const step of [install, update]) {
36
- assert.equal(step.method, "shell.run")
37
- assert.equal(step.params.path, root)
38
- assert.equal(step.params.conda.skip, true)
39
- assert.equal(step.params.message._[0], process.execPath)
40
- assert.equal(step.params.message._[1], antigravityCommon.installerPath())
41
- assert.deepEqual(step.params.message._.slice(2), [
42
- "--install-dir",
43
- path.join(root, "bin"),
44
- "--managed-dir",
45
- path.join(root, "bin", "antigravity-cli"),
46
- ])
47
- assert.doesNotMatch(step.params.message._.join(" "), / -e /)
48
- assert.doesNotMatch(step.params.message._.join(" "), /api\.github\.com\/repos\/google-antigravity\/antigravity-cli\/releases\/latest/)
49
- assert.equal(Object.prototype.hasOwnProperty.call(step.params, "input"), false)
38
+ try {
39
+ const install = await antigravityCli.install(kernel, {})
40
+ const update = await antigravityCli.update(kernel, {})
41
+
42
+ assert.deepEqual(calls, [{
43
+ installDir: path.join(root, "bin"),
44
+ managedDir: path.join(root, "bin", "antigravity-cli"),
45
+ }, {
46
+ installDir: path.join(root, "bin"),
47
+ managedDir: path.join(root, "bin", "antigravity-cli"),
48
+ }])
49
+
50
+ for (const steps of [install, update]) {
51
+ assert.equal(steps.length, 1)
52
+ assert.equal(steps[0].method, "notify")
53
+ assert.equal(steps[0].params.type, "success")
54
+ }
55
+ } finally {
56
+ antigravityInstaller.install = originalInstall
50
57
  }
51
58
  })
52
59
 
@@ -82,6 +82,43 @@ function createHomeDomWithApps(appIds) {
82
82
  })
83
83
  }
84
84
 
85
+ function createHomeDomForIdleOrdering() {
86
+ const row = (appId, name, stopButton = false) => `
87
+ <div
88
+ class="line align-top home-app-line"
89
+ data-name="${name}"
90
+ data-autolaunch-app="${appId}"
91
+ data-autolaunch-starting="0"
92
+ data-autolaunch-script="start.js"
93
+ >
94
+ <h3><span class="title"><i class="fa-solid fa-circle"></i></span></h3>
95
+ <div class="menu-btns">
96
+ <button class="open-actions-modal" data-dialog-id="actions-${appId}" type="button">menu</button>
97
+ ${stopButton ? `
98
+ <button class="btn shutdown" type="button" data-src="api/${appId}/start.js">
99
+ <i class="fa-solid fa-stop" aria-hidden="true"></i>
100
+ <span>Stop start.js</span>
101
+ </button>
102
+ ` : ""}
103
+ </div>
104
+ </div>
105
+ <div id="actions-${appId}"><div class="home-actions-title-row"></div></div>
106
+ `
107
+ return new JSDOM(`
108
+ <!doctype html>
109
+ <body>
110
+ <div class="running-apps">${row("alpha", "Alpha", true)}</div>
111
+ <div class="not-running-apps">
112
+ ${row("beta", "Beta")}
113
+ ${row("gamma", "Gamma")}
114
+ </div>
115
+ </body>
116
+ `, {
117
+ runScripts: "outside-only",
118
+ url: "http://127.0.0.1:42000/home?sort=az"
119
+ })
120
+ }
121
+
85
122
  function installHomeStatusMocks(window, responses) {
86
123
  const timeouts = []
87
124
  const intervals = []
@@ -225,6 +262,39 @@ test("home one-shot status refresh can update a stopped app row", async () => {
225
262
  assert.equal(window.document.querySelector(".not-running-apps").contains(line), true)
226
263
  })
227
264
 
265
+ test("home one-shot status refresh preserves idle app sort order after moving stopped row", async () => {
266
+ const script = await homeAutolaunchScript({ launchComplete: true })
267
+ const dom = createHomeDomForIdleOrdering()
268
+ const { window } = dom
269
+ const responses = [{
270
+ launch_complete: true,
271
+ running_apps: [],
272
+ running_scripts: [],
273
+ autolaunch: { apps: {} }
274
+ }]
275
+ installHomeStatusMocks(window, responses)
276
+ let reorderCalls = 0
277
+ window.reorderHomeSectionsByPreference = () => {
278
+ reorderCalls += 1
279
+ for (const selector of [".running-apps", ".not-running-apps"]) {
280
+ const section = window.document.querySelector(selector)
281
+ const lines = Array.from(section.querySelectorAll(":scope > .line"))
282
+ lines.sort((a, b) => (a.getAttribute("data-name") || "").localeCompare(b.getAttribute("data-name") || ""))
283
+ for (const line of lines) {
284
+ section.appendChild(line)
285
+ }
286
+ }
287
+ }
288
+
289
+ window.eval(script)
290
+ await window.refreshHomeStatusOnce("alpha")
291
+
292
+ const idleNames = Array.from(window.document.querySelectorAll(".not-running-apps > .line"))
293
+ .map((line) => line.getAttribute("data-name"))
294
+ assert.deepEqual(idleNames, ["Alpha", "Beta", "Gamma"])
295
+ assert.equal(reorderCalls, 1)
296
+ })
297
+
228
298
  test("home live status discovers a running target even when preparation status has already cleared", async () => {
229
299
  const script = await homeAutolaunchScript()
230
300
  const dom = createHomeDom({ running: false, stopButton: false })