pinokiod 7.3.1 → 7.3.4

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 (122) 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/git.js +9 -10
  7. package/kernel/bin/huggingface.js +1 -1
  8. package/kernel/bin/zip.js +9 -1
  9. package/kernel/connect/providers/github/README.md +5 -4
  10. package/kernel/environment.js +195 -92
  11. package/kernel/git.js +126 -19
  12. package/kernel/gitconfig_template +7 -0
  13. package/kernel/gpu/amd.js +72 -0
  14. package/kernel/gpu/apple.js +8 -0
  15. package/kernel/gpu/common.js +12 -0
  16. package/kernel/gpu/intel.js +47 -0
  17. package/kernel/gpu/nvidia.js +8 -0
  18. package/kernel/index.js +11 -1
  19. package/kernel/managed_skills.js +871 -0
  20. package/kernel/plugin.js +6 -58
  21. package/kernel/plugin_sources.js +316 -0
  22. package/kernel/resource_usage/gpu.js +349 -0
  23. package/kernel/resource_usage/index.js +322 -0
  24. package/kernel/resource_usage/macos_footprint.js +197 -0
  25. package/kernel/resource_usage/preferences.js +92 -0
  26. package/kernel/resource_usage/process_tree.js +303 -0
  27. package/kernel/scripts/git/create +4 -4
  28. package/kernel/scripts/git/fork +7 -8
  29. package/kernel/shell.js +23 -2
  30. package/kernel/shells.js +41 -0
  31. package/kernel/sysinfo.js +62 -9
  32. package/kernel/util.js +60 -0
  33. package/package.json +1 -1
  34. package/server/index.js +984 -156
  35. package/server/lib/app_log_report.js +543 -0
  36. package/server/lib/content_validation.js +55 -33
  37. package/server/lib/launcher_instruction_bootstrap.js +4 -96
  38. package/server/lib/terminal_session_helpers.js +0 -3
  39. package/server/public/common.js +77 -31
  40. package/server/public/create-launcher.js +4 -32
  41. package/server/public/logs.js +1428 -0
  42. package/server/public/nav.js +7 -0
  43. package/server/public/plugin-detail.js +93 -10
  44. package/server/public/privacy_filter_worker.js +391 -0
  45. package/server/public/style.css +1104 -154
  46. package/server/public/task-launcher.js +8 -29
  47. package/server/public/universal-launcher.css +8 -6
  48. package/server/public/universal-launcher.js +3 -27
  49. package/server/routes/apps.js +195 -1
  50. package/server/views/app.ejs +3041 -717
  51. package/server/views/autolaunch.ejs +917 -0
  52. package/server/views/bootstrap.ejs +7 -1
  53. package/server/views/d.ejs +408 -65
  54. package/server/views/editor.ejs +85 -19
  55. package/server/views/index.ejs +661 -111
  56. package/server/views/init/index.ejs +1 -1
  57. package/server/views/install.ejs +1 -1
  58. package/server/views/logs.ejs +164 -86
  59. package/server/views/net.ejs +7 -1
  60. package/server/views/partials/d_terminal_column.ejs +2 -2
  61. package/server/views/partials/d_terminal_options.ejs +0 -8
  62. package/server/views/partials/fs_status.ejs +47 -0
  63. package/server/views/partials/home_action_modal.ejs +86 -0
  64. package/server/views/partials/home_run_menu.ejs +87 -0
  65. package/server/views/partials/main_sidebar.ejs +2 -0
  66. package/server/views/partials/menu.ejs +1 -1
  67. package/server/views/plugin_detail.ejs +19 -4
  68. package/server/views/plugins.ejs +201 -3
  69. package/server/views/pre.ejs +1 -1
  70. package/server/views/pro.ejs +1 -1
  71. package/server/views/shell.ejs +40 -18
  72. package/server/views/skills.ejs +506 -0
  73. package/server/views/terminal.ejs +45 -19
  74. package/spec/INSTRUCTION_SYNC.md +20 -10
  75. package/system/plugin/antigravity-cli/antigravity.png +0 -0
  76. package/system/plugin/antigravity-cli/common.js +155 -0
  77. package/system/plugin/antigravity-cli/install.js +272 -0
  78. package/system/plugin/antigravity-cli/pinokio.js +13 -0
  79. package/system/plugin/antigravity-cli-auto/antigravity.png +0 -0
  80. package/system/plugin/antigravity-cli-auto/pinokio.js +13 -0
  81. package/system/plugin/claude/claude.png +0 -0
  82. package/system/plugin/claude/pinokio.js +47 -0
  83. package/system/plugin/claude-auto/claude.png +0 -0
  84. package/system/plugin/claude-auto/pinokio.js +58 -0
  85. package/system/plugin/claude-desktop/icon.jpeg +0 -0
  86. package/system/plugin/claude-desktop/pinokio.js +23 -0
  87. package/system/plugin/codex/openai.webp +0 -0
  88. package/system/plugin/codex/pinokio.js +42 -0
  89. package/system/plugin/codex-auto/openai.webp +0 -0
  90. package/system/plugin/codex-auto/pinokio.js +49 -0
  91. package/system/plugin/codex-desktop/icon.png +0 -0
  92. package/system/plugin/codex-desktop/pinokio.js +23 -0
  93. package/system/plugin/crush/crush.png +0 -0
  94. package/system/plugin/crush/pinokio.js +15 -0
  95. package/system/plugin/cursor/cursor.jpeg +0 -0
  96. package/system/plugin/cursor/pinokio.js +23 -0
  97. package/system/plugin/qwen/pinokio.js +34 -0
  98. package/system/plugin/qwen/qwen.png +0 -0
  99. package/system/plugin/vscode/pinokio.js +20 -0
  100. package/system/plugin/vscode/vscode.png +0 -0
  101. package/system/plugin/windsurf/pinokio.js +23 -0
  102. package/system/plugin/windsurf/windsurf.png +0 -0
  103. package/test/antigravity-cli-plugin.test.js +185 -0
  104. package/test/app-api.test.js +239 -0
  105. package/test/app-log-report.test.js +67 -0
  106. package/test/environment-cache-preflight.test.js +98 -0
  107. package/test/git-bin.test.js +59 -0
  108. package/test/git-defaults.test.js +150 -0
  109. package/test/github-api.test.js +158 -0
  110. package/test/github-connection.test.js +117 -0
  111. package/test/huggingface-bin.test.js +25 -0
  112. package/test/managed-skills.test.js +351 -0
  113. package/test/plugin-action-functions.test.js +337 -0
  114. package/test/plugin-dev-iframe.test.js +17 -0
  115. package/test/plugin-sources.test.js +203 -0
  116. package/test/privacy-filter-worker-heuristics.test.js +69 -0
  117. package/test/process-wait.test.js +169 -0
  118. package/test/script-api.test.js +97 -0
  119. package/test/shell-api.test.js +134 -0
  120. package/test/shell-run-template.test.js +209 -0
  121. package/test/storage-api.test.js +137 -0
  122. package/test/uri-api.test.js +100 -0
@@ -236,17 +236,17 @@ body.dark .context-menu-section-title {
236
236
  body.dark .context-menu-divider {
237
237
  background: rgba(255,255,255,0.08);
238
238
  }
239
- body.dark .open-menu, body.dark .browse, body.dark .toggle-star {
239
+ body.dark .open-menu, body.dark .browse {
240
240
  border: none !important;
241
241
  /*
242
242
  background: black !important;
243
243
  */
244
244
  color: white !important;
245
245
  }
246
- .open-menu:hover, .browse:hover, .toggle-star:hover, body.dark .open-menu:hover, body.dark .browse:hover, body.dark .toggle-star:hover {
246
+ .open-menu:hover, .browse:hover, body.dark .open-menu:hover, body.dark .browse:hover {
247
247
  color: royalblue !important;
248
248
  }
249
- .open-menu, .browse, .toggle-star {
249
+ .open-menu, .browse {
250
250
  /*
251
251
  width: 80px;
252
252
  */
@@ -706,6 +706,382 @@ body.dark .home-search-form {
706
706
  font-weight: 400;
707
707
  line-height: 1;
708
708
  }
709
+ .home-app-line {
710
+ color: var(--light-color);
711
+ cursor: pointer;
712
+ }
713
+ body.dark .home-app-line {
714
+ color: var(--dark-color);
715
+ }
716
+ .home-app-line:focus-visible {
717
+ outline: 2px solid color-mix(in srgb, var(--home-page-accent) 55%, transparent);
718
+ outline-offset: -2px;
719
+ }
720
+ .home-app-line > h3 {
721
+ align-items: flex-start;
722
+ gap: 0;
723
+ }
724
+ .home-app-line .item-icon {
725
+ align-self: flex-start;
726
+ flex: 0 0 auto;
727
+ }
728
+ .menu-btns .home-icon-btn {
729
+ width: 32px;
730
+ height: 22px;
731
+ min-width: 32px;
732
+ min-height: 22px;
733
+ display: inline-flex !important;
734
+ align-items: center;
735
+ justify-content: center;
736
+ padding: 4px 8px !important;
737
+ margin: 0 0px 5px 0 !important;
738
+ border: none !important;
739
+ border-radius: 0 !important;
740
+ background: rgba(0,0,0,0.04) !important;
741
+ color: rgba(15, 23, 42, 0.68) !important;
742
+ font-size: 13px;
743
+ line-height: 1;
744
+ cursor: pointer;
745
+ transition: color 0.12s ease;
746
+ vertical-align: top;
747
+ box-sizing: border-box;
748
+ }
749
+ .menu-btns .home-icon-btn:hover,
750
+ .menu-btns .home-icon-btn:focus-visible {
751
+ background: rgba(0,0,0,0.04) !important;
752
+ color: var(--home-page-accent) !important;
753
+ outline: none;
754
+ }
755
+ body.dark .menu-btns .home-icon-btn {
756
+ border-color: transparent !important;
757
+ background: rgba(255,255,255,0.05) !important;
758
+ color: rgba(226, 232, 240, 0.72) !important;
759
+ }
760
+ body.dark .menu-btns .home-icon-btn:hover,
761
+ body.dark .menu-btns .home-icon-btn:focus-visible {
762
+ background: rgba(255,255,255,0.05) !important;
763
+ color: var(--home-page-accent) !important;
764
+ }
765
+ .menu-btns .home-icon-btn i {
766
+ color: inherit;
767
+ }
768
+ .menu-btns .toggle-star.is-starred {
769
+ color: #b8860b !important;
770
+ }
771
+ body.dark .menu-btns .toggle-star.is-starred {
772
+ color: #ffcc66 !important;
773
+ }
774
+ .home-actions-dialog {
775
+ width: min(760px, calc(100vw - 32px));
776
+ max-height: min(680px, calc(100vh - 32px));
777
+ padding: 0;
778
+ border: 1px solid rgba(15, 23, 42, 0.14);
779
+ border-radius: 8px;
780
+ background: rgba(255, 255, 255, 0.98);
781
+ color: rgba(15, 23, 42, 0.94);
782
+ box-shadow: 0 24px 70px rgba(15, 23, 42, 0.24);
783
+ }
784
+ .home-actions-dialog:not([open]) {
785
+ display: none;
786
+ }
787
+ .home-actions-dialog[open] {
788
+ position: fixed;
789
+ top: 50%;
790
+ left: 50%;
791
+ display: block;
792
+ margin: 0;
793
+ transform: translate(-50%, -50%);
794
+ z-index: 1000;
795
+ }
796
+ .home-actions-dialog::backdrop {
797
+ background: rgba(15, 23, 42, 0.32);
798
+ }
799
+ body.dark .home-actions-dialog {
800
+ border-color: rgba(255, 255, 255, 0.12);
801
+ background: rgba(18, 20, 25, 0.98);
802
+ color: rgba(248, 250, 252, 0.94);
803
+ box-shadow: 0 24px 80px rgba(0, 0, 0, 0.55);
804
+ }
805
+ body.dark .home-actions-dialog::backdrop {
806
+ background: rgba(0, 0, 0, 0.58);
807
+ }
808
+ .home-actions-shell {
809
+ display: flex;
810
+ flex-direction: column;
811
+ min-height: min(560px, calc(100vh - 48px));
812
+ overflow: hidden;
813
+ }
814
+ .home-actions-header {
815
+ display: flex;
816
+ align-items: center;
817
+ gap: 12px;
818
+ padding: 16px 18px;
819
+ border-bottom: 1px solid rgba(15, 23, 42, 0.09);
820
+ }
821
+ body.dark .home-actions-header {
822
+ border-bottom-color: rgba(255, 255, 255, 0.08);
823
+ }
824
+ .home-actions-icon {
825
+ width: 38px;
826
+ height: 38px;
827
+ flex: 0 0 auto;
828
+ }
829
+ .home-actions-icon img {
830
+ width: 38px;
831
+ height: 38px;
832
+ border-radius: 6px;
833
+ object-fit: cover;
834
+ }
835
+ .home-actions-heading {
836
+ min-width: 0;
837
+ flex: 1 1 auto;
838
+ }
839
+ .home-actions-title-row {
840
+ display: flex;
841
+ align-items: center;
842
+ gap: 8px;
843
+ min-width: 0;
844
+ }
845
+ .home-actions-title-row h2 {
846
+ margin: 0;
847
+ min-width: 0;
848
+ overflow: hidden;
849
+ text-overflow: ellipsis;
850
+ white-space: nowrap;
851
+ font-size: 15px;
852
+ line-height: 1.2;
853
+ }
854
+ .home-actions-path {
855
+ margin-top: 3px;
856
+ overflow: hidden;
857
+ text-overflow: ellipsis;
858
+ white-space: nowrap;
859
+ font-size: 12px;
860
+ color: rgba(15, 23, 42, 0.58);
861
+ }
862
+ body.dark .home-actions-path {
863
+ color: rgba(226, 232, 240, 0.58);
864
+ }
865
+ .home-actions-status,
866
+ .home-run-menu-status,
867
+ .home-actions-count {
868
+ display: inline-flex;
869
+ align-items: center;
870
+ flex: 0 0 auto;
871
+ height: 20px;
872
+ padding: 0 7px;
873
+ border-radius: 999px;
874
+ background: var(--pinokio-chrome-accent-bg-light);
875
+ color: var(--pinokio-chrome-accent-fg-light);
876
+ font-size: 11px;
877
+ font-weight: 700;
878
+ line-height: 1;
879
+ }
880
+ body.dark .home-actions-status,
881
+ body.dark .home-run-menu-status,
882
+ body.dark .home-actions-count {
883
+ background: var(--pinokio-chrome-accent-bg-dark);
884
+ color: var(--pinokio-chrome-accent-fg-dark);
885
+ }
886
+ .home-actions-close {
887
+ width: 30px;
888
+ height: 30px;
889
+ display: inline-flex;
890
+ align-items: center;
891
+ justify-content: center;
892
+ flex: 0 0 auto;
893
+ border: none;
894
+ border-radius: 5px;
895
+ background: transparent;
896
+ color: inherit;
897
+ cursor: pointer;
898
+ }
899
+ .home-actions-close:hover,
900
+ .home-actions-close:focus-visible {
901
+ background: rgba(15, 23, 42, 0.07);
902
+ outline: none;
903
+ }
904
+ body.dark .home-actions-close:hover,
905
+ body.dark .home-actions-close:focus-visible {
906
+ background: rgba(255, 255, 255, 0.08);
907
+ }
908
+ .home-actions-tabs {
909
+ display: flex;
910
+ gap: 6px;
911
+ padding: 10px 18px 0;
912
+ border-bottom: 1px solid rgba(15, 23, 42, 0.09);
913
+ }
914
+ body.dark .home-actions-tabs {
915
+ border-bottom-color: rgba(255, 255, 255, 0.08);
916
+ }
917
+ .home-actions-tab {
918
+ display: inline-flex;
919
+ align-items: center;
920
+ gap: 7px;
921
+ height: 34px;
922
+ padding: 0 12px;
923
+ border: 1px solid transparent;
924
+ border-bottom: none;
925
+ border-radius: 6px 6px 0 0;
926
+ background: transparent;
927
+ color: rgba(15, 23, 42, 0.68);
928
+ font-size: 12px;
929
+ font-weight: 700;
930
+ cursor: pointer;
931
+ }
932
+ .home-actions-tab.is-active {
933
+ border-color: rgba(15, 23, 42, 0.11);
934
+ background: rgba(248, 250, 252, 0.9);
935
+ color: rgba(15, 23, 42, 0.94);
936
+ }
937
+ body.dark .home-actions-tab {
938
+ color: rgba(226, 232, 240, 0.68);
939
+ }
940
+ body.dark .home-actions-tab.is-active {
941
+ border-color: rgba(255, 255, 255, 0.1);
942
+ background: rgba(255, 255, 255, 0.055);
943
+ color: rgba(248, 250, 252, 0.94);
944
+ }
945
+ .home-actions-body {
946
+ min-height: 0;
947
+ flex: 1 1 auto;
948
+ overflow: auto;
949
+ padding: 14px 18px 18px;
950
+ }
951
+ .home-actions-panel[hidden] {
952
+ display: none;
953
+ }
954
+ .home-run-menu-list,
955
+ .home-actions-command-list,
956
+ .home-run-menu-children {
957
+ display: flex;
958
+ flex-direction: column;
959
+ gap: 6px;
960
+ }
961
+ .home-run-menu-group {
962
+ border: 1px solid rgba(15, 23, 42, 0.09);
963
+ border-radius: 7px;
964
+ overflow: hidden;
965
+ }
966
+ body.dark .home-run-menu-group {
967
+ border-color: rgba(255, 255, 255, 0.08);
968
+ }
969
+ .home-run-menu-summary {
970
+ display: flex;
971
+ align-items: center;
972
+ justify-content: space-between;
973
+ min-height: 38px;
974
+ padding: 0 12px;
975
+ cursor: pointer;
976
+ font-size: 12px;
977
+ font-weight: 700;
978
+ list-style: none;
979
+ }
980
+ .home-run-menu-summary::-webkit-details-marker {
981
+ display: none;
982
+ }
983
+ .home-run-menu-group[open] > .home-run-menu-summary i {
984
+ transform: rotate(180deg);
985
+ }
986
+ .home-run-menu-children {
987
+ padding: 0 8px 8px;
988
+ }
989
+ .home-run-menu-row {
990
+ display: grid;
991
+ grid-template-columns: minmax(0, 1fr) auto;
992
+ gap: 6px;
993
+ }
994
+ .home-run-command,
995
+ .home-mode-command {
996
+ min-height: 38px;
997
+ display: flex;
998
+ align-items: center;
999
+ gap: 9px;
1000
+ width: 100%;
1001
+ padding: 0 11px;
1002
+ border: 1px solid rgba(15, 23, 42, 0.09);
1003
+ border-radius: 6px;
1004
+ background: rgba(248, 250, 252, 0.72);
1005
+ color: inherit;
1006
+ text-align: left;
1007
+ text-decoration: none;
1008
+ font-size: 12px;
1009
+ font-weight: 700;
1010
+ box-sizing: border-box;
1011
+ cursor: pointer;
1012
+ }
1013
+ button.home-run-command,
1014
+ button.home-mode-command {
1015
+ font-family: inherit;
1016
+ }
1017
+ body.dark .home-run-command,
1018
+ body.dark .home-mode-command {
1019
+ border-color: rgba(255, 255, 255, 0.08);
1020
+ background: rgba(255, 255, 255, 0.045);
1021
+ }
1022
+ .home-run-command:hover,
1023
+ .home-run-command:focus-visible,
1024
+ .home-mode-command:hover,
1025
+ .home-mode-command:focus-visible,
1026
+ .home-run-stop:hover,
1027
+ .home-run-stop:focus-visible {
1028
+ border-color: color-mix(in srgb, var(--home-page-accent) 34%, transparent);
1029
+ background: color-mix(in srgb, var(--home-page-accent) 9%, transparent);
1030
+ outline: none;
1031
+ }
1032
+ .home-run-menu-label,
1033
+ .home-mode-command span:first-of-type {
1034
+ min-width: 0;
1035
+ flex: 1 1 auto;
1036
+ overflow: hidden;
1037
+ text-overflow: ellipsis;
1038
+ white-space: nowrap;
1039
+ }
1040
+ .home-run-menu-label i,
1041
+ .home-mode-command i {
1042
+ width: 16px;
1043
+ text-align: center;
1044
+ }
1045
+ .home-run-command > i:last-child {
1046
+ flex: 0 0 auto;
1047
+ font-size: 10px;
1048
+ opacity: 0.6;
1049
+ }
1050
+ .home-run-stop {
1051
+ width: 38px;
1052
+ min-height: 38px;
1053
+ display: inline-flex;
1054
+ align-items: center;
1055
+ justify-content: center;
1056
+ border: 1px solid rgba(15, 23, 42, 0.09);
1057
+ border-radius: 6px;
1058
+ background: rgba(248, 250, 252, 0.72);
1059
+ color: rgba(185, 28, 28, 0.82);
1060
+ cursor: pointer;
1061
+ }
1062
+ body.dark .home-run-stop {
1063
+ border-color: rgba(255, 255, 255, 0.08);
1064
+ background: rgba(255, 255, 255, 0.045);
1065
+ color: rgba(252, 165, 165, 0.86);
1066
+ }
1067
+ .home-mode-command.is-danger {
1068
+ color: rgba(185, 28, 28, 0.86);
1069
+ }
1070
+ body.dark .home-mode-command.is-danger {
1071
+ color: rgba(252, 165, 165, 0.9);
1072
+ }
1073
+ .home-actions-empty {
1074
+ padding: 18px 12px;
1075
+ border: 1px dashed rgba(15, 23, 42, 0.16);
1076
+ border-radius: 7px;
1077
+ color: rgba(15, 23, 42, 0.55);
1078
+ font-size: 12px;
1079
+ font-weight: 700;
1080
+ }
1081
+ body.dark .home-actions-empty {
1082
+ border-color: rgba(255, 255, 255, 0.12);
1083
+ color: rgba(226, 232, 240, 0.58);
1084
+ }
709
1085
  #suggestion {
710
1086
  padding: 20px;
711
1087
  display: flex;
@@ -754,12 +1130,30 @@ body.dark aside .current.selected {
754
1130
  header .flexible {
755
1131
  min-width: unset;
756
1132
  }
1133
+ .home-actions-dialog {
1134
+ width: 100vw;
1135
+ max-width: none;
1136
+ height: 100vh;
1137
+ max-height: none;
1138
+ border-radius: 0;
1139
+ border-left: none;
1140
+ border-right: none;
1141
+ }
1142
+ .home-actions-shell {
1143
+ min-height: 100vh;
1144
+ }
757
1145
  }
758
1146
  @media only screen and (max-width: 480px) {
759
1147
  .btn2 {
760
1148
  padding: 5px;
761
1149
  font-size: 11px;
762
1150
  }
1151
+ .menu-btns .home-icon-btn {
1152
+ width: 32px;
1153
+ height: 22px;
1154
+ min-width: 32px;
1155
+ min-height: 22px;
1156
+ }
763
1157
  .nav-btns {
764
1158
  flex-grow: 1;
765
1159
  justify-content: center;
@@ -893,7 +1287,7 @@ body.dark aside .current.selected {
893
1287
  <% if (running.length > 0) { %>
894
1288
  <div class='running-apps'>
895
1289
  <% running.forEach((item, index) => { %>
896
- <a target="<%=item.target || '_self'%>" href="<%=item.browser_url%>" data-description="<%=item.description%>" data-index="<%=item.index%>" data-name="<%=item.name%>" data-title="<%=item.name%>" data-uri="<%=item.uri%>" data-icon="<%=item.icon%>" data-path="<%=item.path%>" data-iconpath="<%=item.iconpath ? item.iconpath : ''%>" data-starred="<%=item.starred ? "1" : "0"%>" data-launch-count-total="<%=item.launch_count_total || 0%>" data-last-launch-at="<%=item.last_launch_at || ''%>" class='line align-top'>
1290
+ <div data-href="<%=item.browser_url%>" data-target="<%=item.target || '_self'%>" data-description="<%=item.description%>" data-index="<%=item.index%>" data-name="<%=item.name%>" data-title="<%=item.name%>" data-uri="<%=item.uri%>" data-icon="<%=item.icon%>" data-path="<%=item.path%>" data-iconpath="<%=item.iconpath ? item.iconpath : ''%>" data-starred="<%=item.starred ? "1" : "0"%>" data-launch-count-total="<%=item.launch_count_total || 0%>" data-last-launch-at="<%=item.last_launch_at || ''%>" class='line align-top home-app-line' role='link' tabindex='0'>
897
1291
  <h3>
898
1292
  <div class='item-icon'>
899
1293
  <% if (item.icon) { %>
@@ -914,67 +1308,42 @@ body.dark aside .current.selected {
914
1308
  <div class='uri'><%=item.filepath%></div>
915
1309
  <div class='description'><%=item.description%></div>
916
1310
  <div class='menu-btns'>
917
- <% const runningTerminalOnlineCount = Number(item.terminal_online_count || 0) %>
918
- <% const runningTerminalLabel = runningTerminalOnlineCount > 0 ? `${runningTerminalOnlineCount} terminal${runningTerminalOnlineCount === 1 ? "" : "s"} online` : "Terminals" %>
919
1311
  <button
920
- class='btn toggle-star <%=item.starred ? "is-starred" : ""%>'
1312
+ type='button'
1313
+ class='btn home-icon-btn toggle-star <%=item.starred ? "is-starred" : ""%>'
921
1314
  data-app-id="<%=item.uri%>"
922
1315
  data-starred="<%=item.starred ? "1" : "0"%>"
923
1316
  title="<%=item.starred ? "Unstar app" : "Star app"%>"
1317
+ aria-label="<%=item.starred ? "Unstar app" : "Star app"%>"
924
1318
  >
925
- <i class="fa-<%=item.starred ? "solid" : "regular"%> fa-star"></i>
926
- <span><%=item.starred ? "Starred" : "Star"%></span>
1319
+ <i class="fa-<%=item.starred ? "solid" : "regular"%> fa-star" aria-hidden="true"></i>
927
1320
  </button>
928
- <button class='btn open-menu' type='button' popovertarget="context-menu-running-<%=index%>" popovertargetaction="toggle" aria-haspopup="menu">
929
- <i class="fa-solid fa-bars"></i><span> Menu</span>
930
- </button>
931
- <div id='context-menu-running-<%=index%>' class='context-menu' popover>
932
- <div class='context-menu-wrapper'>
933
- <div class='context-menu-section-title'>File</div>
934
- <button class='btn copy-menu'>
935
- <i class='fa-solid fa-copy'></i> Copy
936
- </button>
937
- <button class='btn move-menu' data-disable="To move the folder, the app should not be running.">
938
- <i class="fa-solid fa-right-to-bracket"></i> Move
939
- </button>
940
- <button class='btn edit-menu'>
941
- <i class='fa-solid fa-pen-to-square'></i> Edit
942
- </button>
943
- <button class='btn del' data-src="<%=item.url%>" data-disable="To delete the folder, the app should not be running.">
944
- <i class="fa-solid fa-trash-can"></i> Delete
945
- </button>
946
- <div class='context-menu-divider'></div>
947
- <div class='context-menu-section-title'>Open</div>
948
- <button class='btn browse' data-src="<%=item.view_url%>">
949
- <i class="fa-regular fa-eye"></i> Open without launching
950
- </button>
951
- <button class='btn browse' data-src="<%=item.dev_url%>">
952
- <i class="fa-solid fa-code"></i> Dev mode
953
- </button>
954
- <button class='btn browse' data-src="<%=item.files_url%>">
955
- <i class="fa-solid fa-file-lines"></i> Files mode
956
- </button>
957
- <button class='btn' data-filepath="<%=item.filepath%>">
958
- <i class="fa-solid fa-folder-open"></i> Open in file explorer
959
- </button>
960
- </div>
961
- </div>
962
- <button class='btn browse terminals-link <%=runningTerminalOnlineCount > 0 ? "is-online" : ""%>' data-src="<%=item.dev_url%>" title="<%=runningTerminalOnlineCount > 0 ? runningTerminalLabel : 'Open terminals'%>">
963
- <i class="fa-solid fa-terminal"></i>
964
- <span><%=runningTerminalLabel%></span>
965
- <i class="fa-solid <%=runningTerminalOnlineCount > 0 ? "fa-circle-notch fa-spin" : "fa-chevron-right"%> terminals-link-indicator" aria-hidden="true"></i>
1321
+ <button class='btn open-menu open-actions-modal' type='button' data-dialog-id="home-actions-running-<%=index%>" aria-haspopup="dialog" title="Actions" aria-label="Actions">
1322
+ <i class="fa-solid fa-ellipsis" aria-hidden="true"></i><span> menu</span>
966
1323
  </button>
1324
+ <% const runningTerminalOnlineCount = Number(item.terminal_online_count || 0) %>
1325
+ <% const runningTerminalLabel = runningTerminalOnlineCount > 0 ? `${runningTerminalOnlineCount} terminal${runningTerminalOnlineCount === 1 ? "" : "s"} online` : "Terminals" %>
1326
+ <% if (runningTerminalOnlineCount > 0) { %>
1327
+ <button class='btn browse terminals-link is-online' type='button' data-src="<%=item.dev_url%>" title="<%=runningTerminalLabel%>">
1328
+ <i class="fa-solid fa-terminal" aria-hidden="true"></i>
1329
+ <span><%=runningTerminalLabel%></span>
1330
+ <i class="fa-solid fa-circle-notch fa-spin terminals-link-indicator" aria-hidden="true"></i>
1331
+ </button>
1332
+ <% } %>
967
1333
  <% if (item.running_scripts) { %>
968
1334
  <% item.running_scripts.forEach((s) => { %>
969
- <% if (s.id) { %>
970
- <button class='btn shutdown' data-id="<%=s.id%>" data-type="<%=s.type ? s.type : ''%>">
971
- <i class="fa-solid fa-stop"></i> Stop <%=s.name%>&nbsp;
972
- <i class='fa-solid fa-spin fa-circle-notch'></i>
1335
+ <% const runningScriptName = s.name || "script" %>
1336
+ <% if (s.id) { %>
1337
+ <button class='btn shutdown' type='button' data-id="<%=s.id%>" data-type="<%=s.type ? s.type : ''%>" title="Stop <%=runningScriptName%>" aria-label="Stop <%=runningScriptName%>">
1338
+ <i class="fa-solid fa-stop" aria-hidden="true"></i>
1339
+ <span>Stop <%=runningScriptName%></span>
1340
+ <i class='fa-solid fa-spin fa-circle-notch' aria-hidden='true'></i>
973
1341
  </button>
974
1342
  <% } else { %>
975
- <button class='btn shutdown' data-src="<%=s.path%>">
976
- <i class="fa-solid fa-stop"></i> Stop <%=s.name%>&nbsp;
977
- <i class='fa-solid fa-spin fa-circle-notch'></i>
1343
+ <button class='btn shutdown' type='button' data-src="<%=s.path%>" title="Stop <%=runningScriptName%>" aria-label="Stop <%=runningScriptName%>">
1344
+ <i class="fa-solid fa-stop" aria-hidden="true"></i>
1345
+ <span>Stop <%=runningScriptName%></span>
1346
+ <i class='fa-solid fa-spin fa-circle-notch' aria-hidden='true'></i>
978
1347
  </button>
979
1348
  <% } %>
980
1349
  <% }) %>
@@ -991,14 +1360,15 @@ body.dark aside .current.selected {
991
1360
  <% } %>
992
1361
  </div>
993
1362
  </h3>
994
- </a>
1363
+ <%- include('partials/home_action_modal', { item, id: `home-actions-running-${index}` }) %>
1364
+ </div>
995
1365
  <% }) %>
996
1366
  </div>
997
1367
  <% } %>
998
1368
  <% if (notRunning.length > 0) { %>
999
1369
  <div class='not-running-apps'>
1000
1370
  <% notRunning.forEach((item, index) => { %>
1001
- <a target="<%=item.target || '_self'%>" href="<%=item.browser_url%>" data-description="<%=item.description%>" data-index="<%=item.index%>" data-name="<%=item.name%>" data-title="<%=item.name%>" data-uri="<%=item.uri%>" data-icon="<%=item.icon%>" data-path="<%=item.path%>" data-iconpath="<%=item.iconpath ? item.iconpath : ''%>" data-starred="<%=item.starred ? "1" : "0"%>" data-launch-count-total="<%=item.launch_count_total || 0%>" data-last-launch-at="<%=item.last_launch_at || ''%>" class='line align-top'>
1371
+ <div data-href="<%=item.browser_url%>" data-target="<%=item.target || '_self'%>" data-description="<%=item.description%>" data-index="<%=item.index%>" data-name="<%=item.name%>" data-title="<%=item.name%>" data-uri="<%=item.uri%>" data-icon="<%=item.icon%>" data-path="<%=item.path%>" data-iconpath="<%=item.iconpath ? item.iconpath : ''%>" data-starred="<%=item.starred ? "1" : "0"%>" data-launch-count-total="<%=item.launch_count_total || 0%>" data-last-launch-at="<%=item.last_launch_at || ''%>" class='line align-top home-app-line' role='link' tabindex='0'>
1002
1372
  <h3>
1003
1373
  <div class='item-icon'>
1004
1374
  <% if (item.icon) { %>
@@ -1018,57 +1388,29 @@ body.dark aside .current.selected {
1018
1388
  </div>
1019
1389
  <div class='uri'><%=item.filepath%></div>
1020
1390
  <div class='description'><%=item.description%></div>
1391
+ <% const notRunningTerminalOnlineCount = Number(item.terminal_online_count || 0) %>
1021
1392
  <div class='menu-btns'>
1022
- <% const notRunningTerminalOnlineCount = Number(item.terminal_online_count || 0) %>
1023
- <% const notRunningTerminalLabel = notRunningTerminalOnlineCount > 0 ? `${notRunningTerminalOnlineCount} terminal${notRunningTerminalOnlineCount === 1 ? "" : "s"} online` : "Terminals" %>
1024
1393
  <button
1025
- class='btn toggle-star <%=item.starred ? "is-starred" : ""%>'
1394
+ type='button'
1395
+ class='btn home-icon-btn toggle-star <%=item.starred ? "is-starred" : ""%>'
1026
1396
  data-app-id="<%=item.uri%>"
1027
1397
  data-starred="<%=item.starred ? "1" : "0"%>"
1028
1398
  title="<%=item.starred ? "Unstar app" : "Star app"%>"
1399
+ aria-label="<%=item.starred ? "Unstar app" : "Star app"%>"
1029
1400
  >
1030
- <i class="fa-<%=item.starred ? "solid" : "regular"%> fa-star"></i>
1031
- <span><%=item.starred ? "Starred" : "Star"%></span>
1032
- </button>
1033
- <button class='btn open-menu' type='button' popovertarget="context-menu-not-running-<%=index%>" popovertargetaction="toggle" aria-haspopup="menu">
1034
- <i class="fa-solid fa-bars"></i><span> Menu</span>
1401
+ <i class="fa-<%=item.starred ? "solid" : "regular"%> fa-star" aria-hidden="true"></i>
1035
1402
  </button>
1036
- <div id='context-menu-not-running-<%=index%>' class='context-menu' popover>
1037
- <div class='context-menu-wrapper'>
1038
- <div class='context-menu-section-title'>File</div>
1039
- <button class='btn copy-menu'>
1040
- <i class='fa-solid fa-copy'></i> Copy
1041
- </button>
1042
- <button class='btn move-menu'>
1043
- <i class="fa-solid fa-right-to-bracket"></i> Move
1044
- </button>
1045
- <button class='btn edit-menu'>
1046
- <i class='fa-solid fa-pen-to-square'></i> Edit
1047
- </button>
1048
- <button class='btn del' data-src="<%=item.url%>">
1049
- <i class="fa-solid fa-trash-can"></i> Delete
1050
- </button>
1051
- <div class='context-menu-divider'></div>
1052
- <div class='context-menu-section-title'>Open</div>
1053
- <button class='btn browse' data-src="<%=item.view_url%>">
1054
- <i class="fa-regular fa-eye"></i> Open without launching
1055
- </button>
1056
- <button class='btn browse' data-src="<%=item.dev_url%>">
1057
- <i class="fa-solid fa-code"></i> Dev mode
1058
- </button>
1059
- <button class='btn browse' data-src="<%=item.files_url%>">
1060
- <i class="fa-solid fa-file-lines"></i> Files mode
1061
- </button>
1062
- <button class='btn' data-filepath="<%=item.filepath%>">
1063
- <i class="fa-solid fa-folder-open"></i> Open in file explorer
1064
- </button>
1065
- </div>
1066
- </div>
1067
- <button class='btn browse terminals-link <%=notRunningTerminalOnlineCount > 0 ? "is-online" : ""%>' data-src="<%=item.dev_url%>" title="<%=notRunningTerminalOnlineCount > 0 ? notRunningTerminalLabel : 'Open terminals'%>">
1068
- <i class="fa-solid fa-terminal"></i>
1069
- <span><%=notRunningTerminalLabel%></span>
1070
- <i class="fa-solid <%=notRunningTerminalOnlineCount > 0 ? "fa-circle-notch fa-spin" : "fa-chevron-right"%> terminals-link-indicator" aria-hidden="true"></i>
1403
+ <button class='btn open-menu open-actions-modal' type='button' data-dialog-id="home-actions-not-running-<%=index%>" aria-haspopup="dialog" title="Actions" aria-label="Actions">
1404
+ <i class="fa-solid fa-ellipsis" aria-hidden="true"></i><span> menu</span>
1071
1405
  </button>
1406
+ <% if (notRunningTerminalOnlineCount > 0) { %>
1407
+ <% const notRunningTerminalLabel = notRunningTerminalOnlineCount > 0 ? `${notRunningTerminalOnlineCount} terminal${notRunningTerminalOnlineCount === 1 ? "" : "s"} online` : "Terminals" %>
1408
+ <button class='btn browse terminals-link is-online' type='button' data-src="<%=item.dev_url%>" title="<%=notRunningTerminalLabel%>">
1409
+ <i class="fa-solid fa-terminal" aria-hidden="true"></i>
1410
+ <span><%=notRunningTerminalLabel%></span>
1411
+ <i class="fa-solid fa-circle-notch fa-spin terminals-link-indicator" aria-hidden="true"></i>
1412
+ </button>
1413
+ <% } %>
1072
1414
  </div>
1073
1415
  <div class='fiexible'></div>
1074
1416
  <% if (item.shortcuts && item.shortcuts.length > 0) { %>
@@ -1082,7 +1424,8 @@ body.dark aside .current.selected {
1082
1424
  <% } %>
1083
1425
  </div>
1084
1426
  </h3>
1085
- </a>
1427
+ <%- include('partials/home_action_modal', { item, id: `home-actions-not-running-${index}` }) %>
1428
+ </div>
1086
1429
  <% }) %>
1087
1430
  </div>
1088
1431
  <% } %>
@@ -1634,20 +1977,132 @@ const closeContextMenuForNode = (node) => {
1634
1977
  const contextMenu = node && typeof node.closest === 'function'
1635
1978
  ? node.closest('.context-menu')
1636
1979
  : null
1637
- if (!contextMenu) {
1980
+ if (contextMenu) {
1981
+ const hasNativePopover = supportsNativePopovers && typeof contextMenu.hidePopover === 'function'
1982
+ if (hasNativePopover) {
1983
+ if (contextMenu.matches(':popover-open')) {
1984
+ contextMenu.hidePopover()
1985
+ }
1986
+ } else {
1987
+ contextMenu.style.display = 'none'
1988
+ contextMenu.dataset.open = 'false'
1989
+ }
1990
+ }
1991
+ const dialog = node && typeof node.closest === 'function'
1992
+ ? node.closest('.home-actions-dialog')
1993
+ : null
1994
+ if (dialog && dialog.open && typeof dialog.close === 'function') {
1995
+ dialog.close()
1996
+ }
1997
+ }
1998
+ window.PinokioHomeCloseContextMenu = closeContextMenuForNode
1999
+ const setHomeActionsTab = (dialog, tabName) => {
2000
+ if (!dialog || !tabName) {
1638
2001
  return
1639
2002
  }
1640
- const hasNativePopover = supportsNativePopovers && typeof contextMenu.hidePopover === 'function'
1641
- if (hasNativePopover) {
1642
- if (contextMenu.matches(':popover-open')) {
1643
- contextMenu.hidePopover()
1644
- }
2003
+ dialog.querySelectorAll('.home-actions-tab[data-tab]').forEach((tab) => {
2004
+ const isActive = tab.getAttribute('data-tab') === tabName
2005
+ tab.classList.toggle('is-active', isActive)
2006
+ tab.setAttribute('aria-selected', isActive ? 'true' : 'false')
2007
+ })
2008
+ dialog.querySelectorAll('.home-actions-panel').forEach((panel) => {
2009
+ const isActive = panel.getAttribute('data-panel') === tabName
2010
+ panel.classList.toggle('is-active', isActive)
2011
+ panel.hidden = !isActive
2012
+ })
2013
+ }
2014
+ const openHomeActionsDialog = (button) => {
2015
+ if (!button) {
2016
+ return
2017
+ }
2018
+ const dialogId = button.getAttribute('data-dialog-id')
2019
+ const dialog = dialogId ? document.getElementById(dialogId) : null
2020
+ if (!dialog) {
2021
+ return
2022
+ }
2023
+ setHomeActionsTab(dialog, button.getAttribute('data-default-tab') || 'run')
2024
+ if (typeof dialog.showModal === 'function') {
2025
+ dialog.showModal()
1645
2026
  } else {
1646
- contextMenu.style.display = 'none'
1647
- contextMenu.dataset.open = 'false'
2027
+ dialog.setAttribute('open', '')
1648
2028
  }
1649
2029
  }
1650
- window.PinokioHomeCloseContextMenu = closeContextMenuForNode
2030
+ const navigateHomeAppLine = (line) => {
2031
+ if (!line) {
2032
+ return
2033
+ }
2034
+ const href = line.getAttribute('data-href')
2035
+ if (!href) {
2036
+ return
2037
+ }
2038
+ const target = line.getAttribute('data-target')
2039
+ if (target && target !== '_self') {
2040
+ window.open(href, target)
2041
+ return
2042
+ }
2043
+ if (window.PinokioHomeGuardNavigate) {
2044
+ window.PinokioHomeGuardNavigate(href)
2045
+ } else {
2046
+ location.href = href
2047
+ }
2048
+ }
2049
+ const shouldIgnoreHomeLineNavigation = (event) => {
2050
+ if (!event || event.defaultPrevented) {
2051
+ return true
2052
+ }
2053
+ const target = event.target
2054
+ if (!(target instanceof Element)) {
2055
+ return false
2056
+ }
2057
+ return Boolean(target.closest('button, a, input, select, textarea, dialog, summary, [role="tab"]'))
2058
+ }
2059
+ const stopHomeRunMenuItem = (target) => {
2060
+ const shellId = target.getAttribute('data-shell-id')
2061
+ const scriptId = target.getAttribute('data-script-id')
2062
+ const href = target.getAttribute('data-href')
2063
+ let method
2064
+ let params
2065
+ if (shellId) {
2066
+ method = "kernel.bin.shell_kill"
2067
+ params = { id: shellId }
2068
+ } else if (scriptId) {
2069
+ method = "kernel.api.stop"
2070
+ params = { id: scriptId }
2071
+ } else if (href) {
2072
+ method = "kernel.api.stop"
2073
+ params = { uri: "~" + new URL(href, location.href).pathname }
2074
+ }
2075
+ if (!method) {
2076
+ return
2077
+ }
2078
+ target.disabled = true
2079
+ const icon = target.querySelector('i')
2080
+ if (icon) {
2081
+ icon.className = 'fa-solid fa-circle-notch fa-spin'
2082
+ }
2083
+ const stopSocket = new Socket()
2084
+ stopSocket.run({ method, params }, (packet) => {
2085
+ if (packet.type === "result") {
2086
+ stopSocket.close()
2087
+ if (icon) {
2088
+ icon.className = 'fa-solid fa-check'
2089
+ }
2090
+ setTimeout(() => {
2091
+ location.href = location.href
2092
+ }, 250)
2093
+ } else if (packet.type === "error") {
2094
+ stopSocket.close()
2095
+ target.disabled = false
2096
+ if (icon) {
2097
+ icon.className = 'fa-solid fa-stop'
2098
+ }
2099
+ n.Noty({
2100
+ text: packet.data || "Failed to stop",
2101
+ silent: true
2102
+ })
2103
+ }
2104
+ })
2105
+ }
1651
2106
  const parseLineStarSortMeta = (line) => {
1652
2107
  if (!line) {
1653
2108
  return {
@@ -1795,6 +2250,78 @@ const showHomeStarToast = () => {
1795
2250
  })
1796
2251
  }
1797
2252
  document.addEventListener("click", async (e) => {
2253
+ let target
2254
+ if (e.target.classList.contains("open-actions-modal")) {
2255
+ target = e.target
2256
+ } else {
2257
+ target = e.target.closest(".open-actions-modal")
2258
+ }
2259
+ if (target) {
2260
+ e.preventDefault()
2261
+ e.stopPropagation()
2262
+ openHomeActionsDialog(target)
2263
+ return
2264
+ }
2265
+ if (e.target.classList.contains("home-actions-close")) {
2266
+ target = e.target
2267
+ } else {
2268
+ target = e.target.closest(".home-actions-close")
2269
+ }
2270
+ if (target) {
2271
+ e.preventDefault()
2272
+ e.stopPropagation()
2273
+ const dialog = target.closest(".home-actions-dialog")
2274
+ if (dialog && typeof dialog.close === "function") {
2275
+ dialog.close()
2276
+ }
2277
+ return
2278
+ }
2279
+ if (e.target.classList.contains("home-actions-dialog")) {
2280
+ e.target.close()
2281
+ return
2282
+ }
2283
+ if (e.target.classList.contains("home-actions-tab")) {
2284
+ target = e.target
2285
+ } else {
2286
+ target = e.target.closest(".home-actions-tab")
2287
+ }
2288
+ if (target && target.hasAttribute("data-tab")) {
2289
+ e.preventDefault()
2290
+ e.stopPropagation()
2291
+ setHomeActionsTab(target.closest(".home-actions-dialog"), target.getAttribute("data-tab"))
2292
+ return
2293
+ }
2294
+ if (e.target.classList.contains("home-run-stop")) {
2295
+ target = e.target
2296
+ } else {
2297
+ target = e.target.closest(".home-run-stop")
2298
+ }
2299
+ if (target) {
2300
+ e.preventDefault()
2301
+ e.stopPropagation()
2302
+ stopHomeRunMenuItem(target)
2303
+ return
2304
+ }
2305
+ if (e.target.classList.contains("home-run-command")) {
2306
+ target = e.target
2307
+ } else {
2308
+ target = e.target.closest(".home-run-command")
2309
+ }
2310
+ if (target) {
2311
+ const confirmText = target.getAttribute("data-confirm")
2312
+ if (confirmText && !confirm(confirmText)) {
2313
+ e.preventDefault()
2314
+ e.stopPropagation()
2315
+ return
2316
+ }
2317
+ }
2318
+ target = e.target.closest(".home-app-line")
2319
+ if (target && !shouldIgnoreHomeLineNavigation(e)) {
2320
+ e.preventDefault()
2321
+ e.stopPropagation()
2322
+ navigateHomeAppLine(target)
2323
+ return
2324
+ }
1798
2325
  if (e.target.classList.contains("toggle-star")) {
1799
2326
  target = e.target
1800
2327
  } else {
@@ -1835,6 +2362,9 @@ document.addEventListener("click", async (e) => {
1835
2362
  if (icon) {
1836
2363
  icon.className = payload.preference.starred ? "fa-solid fa-star" : "fa-regular fa-star"
1837
2364
  }
2365
+ const starLabel = payload.preference.starred ? "Unstar app" : "Star app"
2366
+ target.setAttribute("title", starLabel)
2367
+ target.setAttribute("aria-label", starLabel)
1838
2368
  const label = target.querySelector("span")
1839
2369
  if (label) {
1840
2370
  label.textContent = payload.preference.starred ? "Starred" : "Star"
@@ -1868,6 +2398,15 @@ document.addEventListener("click", async (e) => {
1868
2398
  let id = target.getAttribute('data-id')
1869
2399
  let type = target.getAttribute('data-type')
1870
2400
  console.log({ id, src, type })
2401
+ target.disabled = true
2402
+ const stopIcon = target.querySelector("i.fa-stop")
2403
+ const spinIcon = target.querySelector("i.fa-spin")
2404
+ if (stopIcon) {
2405
+ stopIcon.classList.add("hidden")
2406
+ }
2407
+ if (spinIcon) {
2408
+ spinIcon.classList.remove("hidden")
2409
+ }
1871
2410
 
1872
2411
  let params = {}
1873
2412
  if (id) params.id = id
@@ -2091,10 +2630,10 @@ document.addEventListener("click", async (e) => {
2091
2630
 
2092
2631
 
2093
2632
 
2094
- if (e.target.classList.contains("browse")) {
2633
+ if (e.target.classList.contains("browse") || e.target.classList.contains("home-browse")) {
2095
2634
  target = e.target
2096
2635
  } else {
2097
- target = e.target.closest(".browse")
2636
+ target = e.target.closest(".browse, .home-browse")
2098
2637
  }
2099
2638
  if (target) {
2100
2639
  e.preventDefault()
@@ -2233,6 +2772,17 @@ document.addEventListener("click", async (e) => {
2233
2772
  */
2234
2773
 
2235
2774
 
2775
+ })
2776
+ document.addEventListener("keydown", (e) => {
2777
+ if (e.key !== "Enter" && e.key !== " ") {
2778
+ return
2779
+ }
2780
+ const line = e.target && e.target.closest ? e.target.closest(".home-app-line") : null
2781
+ if (!line || shouldIgnoreHomeLineNavigation(e)) {
2782
+ return
2783
+ }
2784
+ e.preventDefault()
2785
+ navigateHomeAppLine(line)
2236
2786
  })
2237
2787
  <% if (proxy) { %>
2238
2788
  if (document.querySelector("#wifi-qr")) {