@dadado/agent-kit-cli 4.8.4 → 4.8.9
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +59 -0
- package/dashboard/dashboard-data.mjs +235 -77
- package/dashboard/dashboard.html +1505 -468
- package/dashboard/lib/semantic-model.mjs +173 -22
- package/dashboard/lib/terminal-snapshot.mjs +127 -0
- package/dist/index.js +1190 -333
- package/package.json +1 -1
package/dashboard/dashboard.html
CHANGED
|
@@ -67,6 +67,8 @@ html[data-dashboard-skin="legacy"] {
|
|
|
67
67
|
--mc-space-xl: 16px;
|
|
68
68
|
--mc-space-2xl: 24px;
|
|
69
69
|
--mc-card-padding: 16px;
|
|
70
|
+
/* Dense list-card padding (agent/command/skill/process cards, health rows, recent plans). */
|
|
71
|
+
--mc-card-padding-dense: 10px 12px;
|
|
70
72
|
--mc-header-pad-x: 24px;
|
|
71
73
|
/* Trailing pad tighter so More (...) sits closer to the IDE ellipsis column. */
|
|
72
74
|
--mc-header-pad-x-end: 8px;
|
|
@@ -135,6 +137,8 @@ a:focus-visible,
|
|
|
135
137
|
.top-nav-anchor:focus-visible,
|
|
136
138
|
.header-brand:focus-visible,
|
|
137
139
|
.header-home-btn:focus-visible,
|
|
140
|
+
.header-fullscreen-btn:focus-visible,
|
|
141
|
+
.fullscreen-exit-btn:focus-visible,
|
|
138
142
|
.section-home-back:focus-visible,
|
|
139
143
|
.nav-more-btn:focus-visible,
|
|
140
144
|
.nav-more-item:focus-visible,
|
|
@@ -343,6 +347,75 @@ a:focus-visible,
|
|
|
343
347
|
cursor: not-allowed;
|
|
344
348
|
}
|
|
345
349
|
|
|
350
|
+
/* ===== Fullscreen (zen) viewport mode =====
|
|
351
|
+
Hides the 32px app header so the grid/stack reclaims the full panel height.
|
|
352
|
+
Enter control lives in the header; a floating exit control (or Escape) restores chrome. */
|
|
353
|
+
.header-fullscreen-btn {
|
|
354
|
+
background: transparent;
|
|
355
|
+
border: none;
|
|
356
|
+
color: var(--text-secondary);
|
|
357
|
+
width: var(--mc-header-control-size);
|
|
358
|
+
height: var(--mc-header-control-size);
|
|
359
|
+
padding: 0;
|
|
360
|
+
border-radius: var(--mc-radius-chrome);
|
|
361
|
+
cursor: pointer;
|
|
362
|
+
font-family: inherit;
|
|
363
|
+
transition: background 0.2s, color 0.2s, opacity 0.2s;
|
|
364
|
+
flex-shrink: 0;
|
|
365
|
+
display: inline-flex;
|
|
366
|
+
align-items: center;
|
|
367
|
+
justify-content: center;
|
|
368
|
+
}
|
|
369
|
+
.header-fullscreen-btn:hover {
|
|
370
|
+
background: var(--bg-card-hover);
|
|
371
|
+
color: var(--text-primary);
|
|
372
|
+
}
|
|
373
|
+
.header-fullscreen-btn svg {
|
|
374
|
+
width: var(--mc-chrome-icon-size);
|
|
375
|
+
height: var(--mc-chrome-icon-size);
|
|
376
|
+
display: block;
|
|
377
|
+
flex-shrink: 0;
|
|
378
|
+
}
|
|
379
|
+
.fullscreen-exit-btn {
|
|
380
|
+
position: fixed;
|
|
381
|
+
top: var(--mc-space-md);
|
|
382
|
+
right: var(--mc-space-md);
|
|
383
|
+
z-index: 70;
|
|
384
|
+
width: var(--mc-header-control-size);
|
|
385
|
+
height: var(--mc-header-control-size);
|
|
386
|
+
padding: 0;
|
|
387
|
+
display: inline-flex;
|
|
388
|
+
align-items: center;
|
|
389
|
+
justify-content: center;
|
|
390
|
+
background: var(--bg-secondary);
|
|
391
|
+
border: 1px solid var(--border);
|
|
392
|
+
border-radius: var(--mc-radius-chrome);
|
|
393
|
+
color: var(--text-secondary);
|
|
394
|
+
cursor: pointer;
|
|
395
|
+
font-family: inherit;
|
|
396
|
+
transition: background 0.2s, color 0.2s;
|
|
397
|
+
}
|
|
398
|
+
.fullscreen-exit-btn:hover {
|
|
399
|
+
background: var(--bg-card-hover);
|
|
400
|
+
color: var(--text-primary);
|
|
401
|
+
}
|
|
402
|
+
.fullscreen-exit-btn[hidden] {
|
|
403
|
+
display: none;
|
|
404
|
+
}
|
|
405
|
+
.fullscreen-exit-btn svg {
|
|
406
|
+
width: var(--mc-chrome-icon-size);
|
|
407
|
+
height: var(--mc-chrome-icon-size);
|
|
408
|
+
display: block;
|
|
409
|
+
flex-shrink: 0;
|
|
410
|
+
}
|
|
411
|
+
body.mc-fullscreen .header {
|
|
412
|
+
display: none;
|
|
413
|
+
}
|
|
414
|
+
/* Reserve trailing space so the fixed exit control does not cover top-tabs. */
|
|
415
|
+
body.mc-fullscreen .top-tabs-row {
|
|
416
|
+
padding-right: calc(var(--mc-header-control-size) + var(--mc-space-md) * 2 + var(--mc-space-sm));
|
|
417
|
+
}
|
|
418
|
+
|
|
346
419
|
/* ===== Layout ===== */
|
|
347
420
|
.main {
|
|
348
421
|
display: flex;
|
|
@@ -626,6 +699,7 @@ a:focus-visible,
|
|
|
626
699
|
.dot-blue { background: var(--blue); box-shadow: 0 0 6px var(--blue-bg); }
|
|
627
700
|
.dot-purple { background: var(--purple); box-shadow: 0 0 6px var(--purple-bg); }
|
|
628
701
|
.dot-cyan { background: var(--cyan); box-shadow: 0 0 6px var(--cyan-bg); }
|
|
702
|
+
.dot-orange { background: var(--orange); box-shadow: 0 0 6px var(--orange-bg); }
|
|
629
703
|
.dot-gray { background: #3a4a5a; box-shadow: 0 0 6px rgba(58,74,90,0.3); }
|
|
630
704
|
.dot-pulse::after {
|
|
631
705
|
content: '';
|
|
@@ -639,6 +713,8 @@ a:focus-visible,
|
|
|
639
713
|
.dot-green.dot-pulse::after { border-color: var(--green); }
|
|
640
714
|
.dot-blue.dot-pulse::after { border-color: var(--blue); }
|
|
641
715
|
.dot-yellow.dot-pulse::after { border-color: var(--yellow); }
|
|
716
|
+
.dot-red.dot-pulse::after { border-color: var(--red); }
|
|
717
|
+
.dot-orange.dot-pulse::after { border-color: var(--orange); }
|
|
642
718
|
@keyframes pulse {
|
|
643
719
|
0% { opacity: 0.6; transform: scale(1); }
|
|
644
720
|
100% { opacity: 0; transform: scale(2.5); }
|
|
@@ -759,29 +835,66 @@ a:focus-visible,
|
|
|
759
835
|
outline-offset: 1px;
|
|
760
836
|
}
|
|
761
837
|
|
|
762
|
-
/* =====
|
|
763
|
-
.agent-
|
|
764
|
-
|
|
838
|
+
/* ===== Agents ===== */
|
|
839
|
+
.agent-grid {
|
|
840
|
+
display: grid;
|
|
841
|
+
grid-template-columns: repeat(auto-fill, minmax(240px, 1fr));
|
|
842
|
+
gap: 10px;
|
|
843
|
+
}
|
|
844
|
+
.agent-card {
|
|
845
|
+
display: flex;
|
|
846
|
+
flex-direction: column;
|
|
847
|
+
gap: 6px;
|
|
848
|
+
padding: var(--mc-card-padding-dense);
|
|
849
|
+
border-radius: var(--mc-radius);
|
|
850
|
+
background: var(--bg-card);
|
|
851
|
+
border: 1px solid var(--border);
|
|
852
|
+
}
|
|
853
|
+
.agent-card:hover { border-color: var(--border-active); }
|
|
854
|
+
.agent-card-head {
|
|
765
855
|
display: flex;
|
|
766
856
|
align-items: center;
|
|
857
|
+
justify-content: space-between;
|
|
767
858
|
gap: 8px;
|
|
768
|
-
padding: 8px 10px;
|
|
769
|
-
border-radius: 6px;
|
|
770
|
-
font-size: 13px;
|
|
771
|
-
color: var(--text-secondary);
|
|
772
|
-
transition: all 0.15s;
|
|
773
859
|
}
|
|
774
|
-
.agent-
|
|
775
|
-
|
|
776
|
-
|
|
860
|
+
.agent-name {
|
|
861
|
+
display: inline-flex;
|
|
862
|
+
align-items: center;
|
|
863
|
+
gap: 6px;
|
|
864
|
+
font-size: 12px;
|
|
865
|
+
color: var(--text-primary);
|
|
866
|
+
font-family: var(--mc-font-mono);
|
|
867
|
+
}
|
|
868
|
+
.agent-monogram {
|
|
869
|
+
width: 18px;
|
|
870
|
+
height: 18px;
|
|
777
871
|
border-radius: 4px;
|
|
778
|
-
display: flex;
|
|
872
|
+
display: inline-flex;
|
|
779
873
|
align-items: center;
|
|
780
874
|
justify-content: center;
|
|
781
|
-
font-size:
|
|
875
|
+
font-size: 9px;
|
|
782
876
|
font-weight: 700;
|
|
877
|
+
background: var(--bg-card-hover);
|
|
878
|
+
color: var(--text-secondary);
|
|
783
879
|
flex-shrink: 0;
|
|
784
880
|
}
|
|
881
|
+
.agent-desc {
|
|
882
|
+
font-size: 11px;
|
|
883
|
+
color: var(--text-secondary);
|
|
884
|
+
line-height: 1.4;
|
|
885
|
+
display: -webkit-box;
|
|
886
|
+
-webkit-line-clamp: 2;
|
|
887
|
+
-webkit-box-orient: vertical;
|
|
888
|
+
overflow: hidden;
|
|
889
|
+
}
|
|
890
|
+
.agent-file {
|
|
891
|
+
font-size: 10px;
|
|
892
|
+
color: var(--text-muted);
|
|
893
|
+
font-family: var(--mc-font-mono);
|
|
894
|
+
word-break: break-all;
|
|
895
|
+
}
|
|
896
|
+
.agent-actions { display: flex; flex-wrap: wrap; gap: 6px; margin-top: 2px; }
|
|
897
|
+
.agent-create-btn { margin-left: auto; }
|
|
785
898
|
|
|
786
899
|
/* ===== Terminal List ===== */
|
|
787
900
|
.terminal-list { display: flex; flex-direction: column; gap: 4px; }
|
|
@@ -816,8 +929,52 @@ a:focus-visible,
|
|
|
816
929
|
font-size: 11px;
|
|
817
930
|
color: var(--text-muted);
|
|
818
931
|
}
|
|
819
|
-
|
|
820
|
-
|
|
932
|
+
/* Presence liveness rides the shared .dot-pulse::after halo; no element-level
|
|
933
|
+
pulse (it inflated the solid dot 250% and faded it out each cycle). */
|
|
934
|
+
.health-vitals {
|
|
935
|
+
display: grid;
|
|
936
|
+
grid-template-columns: repeat(auto-fill, minmax(180px, 1fr));
|
|
937
|
+
gap: 8px;
|
|
938
|
+
}
|
|
939
|
+
.health-vital-card {
|
|
940
|
+
display: flex;
|
|
941
|
+
flex-direction: column;
|
|
942
|
+
gap: 6px;
|
|
943
|
+
padding: var(--mc-card-padding-dense);
|
|
944
|
+
border: 1px solid var(--border);
|
|
945
|
+
border-radius: var(--mc-radius);
|
|
946
|
+
background: var(--bg-card);
|
|
947
|
+
}
|
|
948
|
+
.health-vital-card[data-state="attention"] {
|
|
949
|
+
border-color: var(--border-active);
|
|
950
|
+
}
|
|
951
|
+
.health-vital-name {
|
|
952
|
+
font-size: 12px;
|
|
953
|
+
font-weight: 600;
|
|
954
|
+
color: var(--text-primary);
|
|
955
|
+
}
|
|
956
|
+
.health-vital-score {
|
|
957
|
+
font-size: 11px;
|
|
958
|
+
color: var(--text-muted);
|
|
959
|
+
}
|
|
960
|
+
.health-vital-state {
|
|
961
|
+
display: flex;
|
|
962
|
+
align-items: center;
|
|
963
|
+
gap: 6px;
|
|
964
|
+
font-size: 11px;
|
|
965
|
+
}
|
|
966
|
+
.health-vital-card[data-state="pass"] .health-vital-state { color: var(--green); }
|
|
967
|
+
.health-vital-card[data-state="attention"] .health-vital-state { color: var(--red); }
|
|
968
|
+
.health-group {
|
|
969
|
+
display: flex;
|
|
970
|
+
flex-direction: column;
|
|
971
|
+
gap: 8px;
|
|
972
|
+
}
|
|
973
|
+
.health-group-title {
|
|
974
|
+
font-size: 11px;
|
|
975
|
+
text-transform: uppercase;
|
|
976
|
+
letter-spacing: 0.04em;
|
|
977
|
+
color: var(--text-muted);
|
|
821
978
|
}
|
|
822
979
|
.health-grid {
|
|
823
980
|
display: grid;
|
|
@@ -828,7 +985,7 @@ a:focus-visible,
|
|
|
828
985
|
display: flex;
|
|
829
986
|
flex-direction: column;
|
|
830
987
|
gap: 0;
|
|
831
|
-
border-radius: var(--mc-radius
|
|
988
|
+
border-radius: var(--mc-radius);
|
|
832
989
|
background: var(--bg-card);
|
|
833
990
|
border: 1px solid var(--border);
|
|
834
991
|
overflow: hidden;
|
|
@@ -838,15 +995,11 @@ a:focus-visible,
|
|
|
838
995
|
.health-card.is-expanded {
|
|
839
996
|
border-color: var(--border-active);
|
|
840
997
|
}
|
|
841
|
-
.health-card[data-severity="ok"] { border-left: 3px solid var(--green, #3fb950); }
|
|
842
|
-
.health-card[data-severity="warning"] { border-left: 3px solid var(--yellow, #d29922); }
|
|
843
|
-
.health-card[data-severity="degraded"] { border-left: 3px solid var(--orange, #db6d28); }
|
|
844
|
-
.health-card[data-severity="error"] { border-left: 3px solid var(--red, #f85149); }
|
|
845
998
|
.health-item {
|
|
846
999
|
display: flex;
|
|
847
1000
|
align-items: center;
|
|
848
1001
|
gap: 8px;
|
|
849
|
-
padding:
|
|
1002
|
+
padding: var(--mc-card-padding-dense);
|
|
850
1003
|
font-size: 12px;
|
|
851
1004
|
color: var(--text-secondary);
|
|
852
1005
|
cursor: pointer;
|
|
@@ -870,6 +1023,10 @@ a:focus-visible,
|
|
|
870
1023
|
color: var(--text-muted);
|
|
871
1024
|
flex-shrink: 0;
|
|
872
1025
|
}
|
|
1026
|
+
.health-item-sev[data-sev="ok"] { color: var(--green); }
|
|
1027
|
+
.health-item-sev[data-sev="warning"] { color: var(--yellow); }
|
|
1028
|
+
.health-item-sev[data-sev="degraded"] { color: var(--orange); }
|
|
1029
|
+
.health-item-sev[data-sev="error"] { color: var(--red); }
|
|
873
1030
|
.health-item-chevron {
|
|
874
1031
|
color: var(--text-muted);
|
|
875
1032
|
font-size: 10px;
|
|
@@ -913,12 +1070,6 @@ a:focus-visible,
|
|
|
913
1070
|
.health-autofix-btn:hover {
|
|
914
1071
|
background: var(--bg-card-hover);
|
|
915
1072
|
}
|
|
916
|
-
html[data-dashboard-skin="cursor"] .health-card {
|
|
917
|
-
border-radius: 8px;
|
|
918
|
-
}
|
|
919
|
-
html[data-dashboard-skin="legacy"] .health-card {
|
|
920
|
-
border-radius: 4px;
|
|
921
|
-
}
|
|
922
1073
|
|
|
923
1074
|
/* ===== Git ===== */
|
|
924
1075
|
.git-info {
|
|
@@ -936,6 +1087,98 @@ html[data-dashboard-skin="legacy"] .health-card {
|
|
|
936
1087
|
.git-label { color: var(--text-muted); }
|
|
937
1088
|
.git-value { color: var(--text-secondary); font-family: var(--mc-font-mono); font-size: 12px; }
|
|
938
1089
|
|
|
1090
|
+
/* Git tab v2: promotion flow lanes, readable graph, staging hygiene */
|
|
1091
|
+
.git-flow {
|
|
1092
|
+
display: flex;
|
|
1093
|
+
flex-direction: column;
|
|
1094
|
+
gap: 6px;
|
|
1095
|
+
margin-bottom: 12px;
|
|
1096
|
+
}
|
|
1097
|
+
.git-flow-row {
|
|
1098
|
+
display: flex;
|
|
1099
|
+
align-items: center;
|
|
1100
|
+
gap: 10px;
|
|
1101
|
+
font-size: 12px;
|
|
1102
|
+
padding: 8px 10px;
|
|
1103
|
+
border: 1px solid var(--border);
|
|
1104
|
+
border-radius: 8px;
|
|
1105
|
+
background: var(--bg-card);
|
|
1106
|
+
flex-wrap: wrap;
|
|
1107
|
+
}
|
|
1108
|
+
.git-flow-lane {
|
|
1109
|
+
font-weight: 600;
|
|
1110
|
+
color: var(--text-primary);
|
|
1111
|
+
flex-shrink: 0;
|
|
1112
|
+
}
|
|
1113
|
+
.git-flow-arrow { color: var(--text-muted); flex-shrink: 0; }
|
|
1114
|
+
.git-flow-state {
|
|
1115
|
+
display: inline-flex;
|
|
1116
|
+
align-items: center;
|
|
1117
|
+
gap: 6px;
|
|
1118
|
+
color: var(--text-secondary);
|
|
1119
|
+
flex: 1;
|
|
1120
|
+
min-width: 0;
|
|
1121
|
+
}
|
|
1122
|
+
.git-flow-badge {
|
|
1123
|
+
font-size: 10px;
|
|
1124
|
+
font-weight: 600;
|
|
1125
|
+
padding: 1px 8px;
|
|
1126
|
+
border-radius: 999px;
|
|
1127
|
+
border: 1px solid var(--border);
|
|
1128
|
+
color: var(--text-secondary);
|
|
1129
|
+
background: var(--bg-secondary);
|
|
1130
|
+
font-family: var(--mc-font-mono);
|
|
1131
|
+
flex-shrink: 0;
|
|
1132
|
+
}
|
|
1133
|
+
.git-flow-badge.is-ahead { color: var(--yellow); border-color: rgba(234,179,8,0.3); }
|
|
1134
|
+
.git-flow-badge.is-behind { color: var(--orange); border-color: rgba(249,115,22,0.3); }
|
|
1135
|
+
.git-flow-badge.is-sync { color: var(--green); border-color: rgba(34,197,94,0.3); }
|
|
1136
|
+
.git-flow-cta { margin-left: auto; flex-shrink: 0; }
|
|
1137
|
+
.git-graph {
|
|
1138
|
+
margin: 0;
|
|
1139
|
+
padding: 10px 12px;
|
|
1140
|
+
border: 1px solid var(--border);
|
|
1141
|
+
border-radius: 8px;
|
|
1142
|
+
background: var(--bg-secondary);
|
|
1143
|
+
font-family: var(--mc-font-mono);
|
|
1144
|
+
font-size: 11px;
|
|
1145
|
+
line-height: 1.5;
|
|
1146
|
+
color: var(--text-secondary);
|
|
1147
|
+
overflow-x: hidden;
|
|
1148
|
+
white-space: pre-wrap;
|
|
1149
|
+
word-break: break-all;
|
|
1150
|
+
max-height: 420px;
|
|
1151
|
+
overflow-y: auto;
|
|
1152
|
+
}
|
|
1153
|
+
.git-graph-title {
|
|
1154
|
+
font-size: 12px;
|
|
1155
|
+
font-weight: 600;
|
|
1156
|
+
color: var(--text-secondary);
|
|
1157
|
+
margin: 12px 0 6px;
|
|
1158
|
+
}
|
|
1159
|
+
.git-hygiene {
|
|
1160
|
+
display: flex;
|
|
1161
|
+
align-items: flex-start;
|
|
1162
|
+
gap: 10px;
|
|
1163
|
+
font-size: 12px;
|
|
1164
|
+
padding: 10px 12px;
|
|
1165
|
+
border: 1px solid rgba(234,179,8,0.35);
|
|
1166
|
+
border-radius: 8px;
|
|
1167
|
+
background: var(--yellow-bg);
|
|
1168
|
+
color: var(--text-secondary);
|
|
1169
|
+
margin-bottom: 12px;
|
|
1170
|
+
flex-wrap: wrap;
|
|
1171
|
+
}
|
|
1172
|
+
.git-hygiene-body { flex: 1; min-width: 200px; }
|
|
1173
|
+
.git-hygiene-title { font-weight: 600; color: var(--yellow); margin-bottom: 2px; }
|
|
1174
|
+
.git-hygiene-paths {
|
|
1175
|
+
font-family: var(--mc-font-mono);
|
|
1176
|
+
font-size: 11px;
|
|
1177
|
+
color: var(--text-muted);
|
|
1178
|
+
margin-top: 4px;
|
|
1179
|
+
word-break: break-all;
|
|
1180
|
+
}
|
|
1181
|
+
|
|
939
1182
|
/* ===== Memory ===== */
|
|
940
1183
|
.memory-list {
|
|
941
1184
|
display: flex;
|
|
@@ -953,6 +1196,91 @@ html[data-dashboard-skin="legacy"] .health-card {
|
|
|
953
1196
|
}
|
|
954
1197
|
.memory-item:hover { background: var(--bg-card-hover); }
|
|
955
1198
|
|
|
1199
|
+
/* Memory tab v2: error-o-meter KPIs, green/red icon panels, interactive error rows */
|
|
1200
|
+
.memory-kpi-grid {
|
|
1201
|
+
display: grid;
|
|
1202
|
+
grid-template-columns: repeat(4, 1fr);
|
|
1203
|
+
gap: var(--mc-space-xl);
|
|
1204
|
+
margin-bottom: var(--mc-space-xl);
|
|
1205
|
+
}
|
|
1206
|
+
@media (max-width: 900px) { .memory-kpi-grid { grid-template-columns: 1fr 1fr; } }
|
|
1207
|
+
.memory-kpi {
|
|
1208
|
+
background: var(--bg-card);
|
|
1209
|
+
border: 1px solid var(--border);
|
|
1210
|
+
border-radius: var(--mc-radius-lg);
|
|
1211
|
+
padding: var(--mc-space-lg) var(--mc-card-padding);
|
|
1212
|
+
}
|
|
1213
|
+
.memory-kpi-label { font-size: 11px; text-transform: uppercase; letter-spacing: 0.06em; color: var(--text-muted); }
|
|
1214
|
+
.memory-kpi-value { font-size: 24px; font-weight: 700; color: var(--text-primary); margin-top: 4px; }
|
|
1215
|
+
.memory-kpi-sub { font-size: 11px; color: var(--text-muted); margin-top: 2px; }
|
|
1216
|
+
.memory-tag-row { display: flex; flex-wrap: wrap; gap: 6px; margin-top: 6px; }
|
|
1217
|
+
.memory-tag {
|
|
1218
|
+
font-size: 11px;
|
|
1219
|
+
padding: 2px 8px;
|
|
1220
|
+
border-radius: 999px;
|
|
1221
|
+
background: var(--red-bg);
|
|
1222
|
+
color: var(--red);
|
|
1223
|
+
font-family: var(--mc-font-mono);
|
|
1224
|
+
}
|
|
1225
|
+
.memory-panel { background: var(--bg-card); border: 1px solid var(--border); border-radius: var(--mc-radius-lg); padding: var(--mc-card-padding); }
|
|
1226
|
+
.memory-panel-header { display: flex; align-items: center; gap: 8px; margin-bottom: 10px; }
|
|
1227
|
+
.memory-panel-icon {
|
|
1228
|
+
display: inline-flex;
|
|
1229
|
+
align-items: center;
|
|
1230
|
+
justify-content: center;
|
|
1231
|
+
width: 22px;
|
|
1232
|
+
height: 22px;
|
|
1233
|
+
border-radius: 6px;
|
|
1234
|
+
font-size: 13px;
|
|
1235
|
+
flex-shrink: 0;
|
|
1236
|
+
}
|
|
1237
|
+
.memory-panel-icon-green { color: var(--green); background: var(--green-bg); }
|
|
1238
|
+
.memory-panel-icon-red { color: var(--red); background: var(--red-bg); }
|
|
1239
|
+
.memory-panel-title { font-size: 13px; font-weight: 600; color: var(--text-primary); }
|
|
1240
|
+
.memory-panel-sub { font-size: 11px; color: var(--text-muted); margin-left: auto; }
|
|
1241
|
+
.memory-error-card {
|
|
1242
|
+
border: 1px solid var(--border);
|
|
1243
|
+
border-radius: var(--mc-radius);
|
|
1244
|
+
margin-bottom: 6px;
|
|
1245
|
+
overflow: hidden;
|
|
1246
|
+
}
|
|
1247
|
+
.memory-error-head {
|
|
1248
|
+
display: flex;
|
|
1249
|
+
align-items: center;
|
|
1250
|
+
gap: 8px;
|
|
1251
|
+
width: 100%;
|
|
1252
|
+
padding: 8px 10px;
|
|
1253
|
+
background: transparent;
|
|
1254
|
+
border: none;
|
|
1255
|
+
cursor: pointer;
|
|
1256
|
+
text-align: left;
|
|
1257
|
+
color: var(--text-secondary);
|
|
1258
|
+
font-size: 12px;
|
|
1259
|
+
}
|
|
1260
|
+
.memory-error-head:hover { background: var(--bg-card-hover); }
|
|
1261
|
+
.memory-error-head:focus-visible { outline: 2px solid var(--accent, var(--green)); outline-offset: -2px; }
|
|
1262
|
+
.memory-error-title { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; color: var(--text-primary); }
|
|
1263
|
+
.memory-error-date { font-size: 11px; color: var(--text-muted); font-family: var(--mc-font-mono); flex-shrink: 0; }
|
|
1264
|
+
.memory-error-chevron { transition: transform 0.15s ease; color: var(--text-muted); flex-shrink: 0; }
|
|
1265
|
+
.memory-error-card.is-expanded .memory-error-chevron { transform: rotate(90deg); }
|
|
1266
|
+
.memory-error-detail { display: none; padding: 10px 12px; border-top: 1px solid var(--border); font-size: 12px; }
|
|
1267
|
+
.memory-error-card.is-expanded .memory-error-detail { display: block; }
|
|
1268
|
+
.memory-error-field { margin-bottom: 8px; }
|
|
1269
|
+
.memory-error-field-label { font-size: 11px; text-transform: uppercase; letter-spacing: 0.05em; color: var(--text-muted); margin-bottom: 2px; }
|
|
1270
|
+
.memory-error-field-body { color: var(--text-secondary); white-space: pre-wrap; word-break: break-word; }
|
|
1271
|
+
.memory-error-files { font-family: var(--mc-font-mono); font-size: 11px; color: var(--text-muted); word-break: break-word; }
|
|
1272
|
+
.memory-error-actions { display: flex; gap: 8px; margin-top: 10px; }
|
|
1273
|
+
.memory-error-copy-btn {
|
|
1274
|
+
font-size: 12px;
|
|
1275
|
+
padding: 5px 12px;
|
|
1276
|
+
border-radius: 6px;
|
|
1277
|
+
border: 1px solid var(--border);
|
|
1278
|
+
background: var(--bg-elevated, var(--bg-card));
|
|
1279
|
+
color: var(--text-primary);
|
|
1280
|
+
cursor: pointer;
|
|
1281
|
+
}
|
|
1282
|
+
.memory-error-copy-btn:hover { background: var(--bg-card-hover); }
|
|
1283
|
+
|
|
956
1284
|
/* ===== Activity Timeline ===== */
|
|
957
1285
|
.activity-list {
|
|
958
1286
|
display: flex;
|
|
@@ -1037,22 +1365,46 @@ html[data-dashboard-skin="legacy"] .health-card {
|
|
|
1037
1365
|
/* ===== Commands ===== */
|
|
1038
1366
|
.command-grid {
|
|
1039
1367
|
display: grid;
|
|
1040
|
-
grid-template-columns: repeat(auto-fill, minmax(
|
|
1041
|
-
gap:
|
|
1368
|
+
grid-template-columns: repeat(auto-fill, minmax(240px, 1fr));
|
|
1369
|
+
gap: 10px;
|
|
1042
1370
|
}
|
|
1043
|
-
.command-
|
|
1371
|
+
.command-card {
|
|
1044
1372
|
display: flex;
|
|
1045
|
-
|
|
1373
|
+
flex-direction: column;
|
|
1046
1374
|
gap: 6px;
|
|
1047
|
-
padding:
|
|
1048
|
-
border-radius:
|
|
1375
|
+
padding: var(--mc-card-padding-dense);
|
|
1376
|
+
border-radius: var(--mc-radius);
|
|
1049
1377
|
background: var(--bg-card);
|
|
1050
1378
|
border: 1px solid var(--border);
|
|
1379
|
+
}
|
|
1380
|
+
.command-card:hover { border-color: var(--border-active); }
|
|
1381
|
+
.command-card-head {
|
|
1382
|
+
display: flex;
|
|
1383
|
+
align-items: center;
|
|
1384
|
+
justify-content: space-between;
|
|
1385
|
+
gap: 8px;
|
|
1386
|
+
}
|
|
1387
|
+
.command-name {
|
|
1051
1388
|
font-size: 12px;
|
|
1052
|
-
color: var(--text-
|
|
1389
|
+
color: var(--text-primary);
|
|
1053
1390
|
font-family: var(--mc-font-mono);
|
|
1054
1391
|
}
|
|
1055
|
-
.command-
|
|
1392
|
+
.command-lock, .skill-lock, .agent-lock {
|
|
1393
|
+
display: inline-flex;
|
|
1394
|
+
align-items: center;
|
|
1395
|
+
gap: 4px;
|
|
1396
|
+
font-size: 10px;
|
|
1397
|
+
color: var(--text-muted);
|
|
1398
|
+
}
|
|
1399
|
+
.command-lock svg, .skill-lock svg, .agent-lock svg { display: block; }
|
|
1400
|
+
.command-file {
|
|
1401
|
+
font-size: 10px;
|
|
1402
|
+
color: var(--text-muted);
|
|
1403
|
+
font-family: var(--mc-font-mono);
|
|
1404
|
+
word-break: break-all;
|
|
1405
|
+
}
|
|
1406
|
+
.command-actions { display: flex; flex-wrap: wrap; gap: 6px; margin-top: 2px; }
|
|
1407
|
+
.command-create-btn { margin-left: auto; }
|
|
1056
1408
|
|
|
1057
1409
|
/* ===== Loading ===== */
|
|
1058
1410
|
.loading {
|
|
@@ -1084,7 +1436,7 @@ html[data-dashboard-skin="legacy"] .health-card {
|
|
|
1084
1436
|
gap: var(--mc-chrome-label-gap);
|
|
1085
1437
|
letter-spacing: 0;
|
|
1086
1438
|
}
|
|
1087
|
-
|
|
1439
|
+
/* Section titles carry no dots: identity comes from the label text (dot semantics table). */
|
|
1088
1440
|
.section-subtitle {
|
|
1089
1441
|
font-size: var(--mc-chrome-subtitle-size);
|
|
1090
1442
|
color: var(--text-muted);
|
|
@@ -1093,39 +1445,75 @@ html[data-dashboard-skin="legacy"] .health-card {
|
|
|
1093
1445
|
}
|
|
1094
1446
|
|
|
1095
1447
|
/* ===== Processes ===== */
|
|
1096
|
-
.processes-
|
|
1097
|
-
|
|
1098
|
-
|
|
1099
|
-
|
|
1100
|
-
|
|
1448
|
+
.processes-note {
|
|
1449
|
+
padding: 9px 12px;
|
|
1450
|
+
margin-bottom: 10px;
|
|
1451
|
+
border-radius: 8px;
|
|
1452
|
+
background: var(--blue-bg);
|
|
1453
|
+
border: 1px solid rgba(59, 130, 246, 0.25);
|
|
1454
|
+
color: var(--text-secondary);
|
|
1455
|
+
font-size: 11.5px;
|
|
1456
|
+
line-height: 1.5;
|
|
1101
1457
|
}
|
|
1102
|
-
.processes-
|
|
1103
|
-
|
|
1104
|
-
padding: 8px 10px;
|
|
1105
|
-
color: var(--text-muted);
|
|
1458
|
+
.processes-note strong {
|
|
1459
|
+
color: var(--blue);
|
|
1106
1460
|
font-weight: 600;
|
|
1107
|
-
border-bottom: 1px solid var(--border);
|
|
1108
|
-
font-size: 11px;
|
|
1109
|
-
text-transform: uppercase;
|
|
1110
|
-
letter-spacing: 0.05em;
|
|
1111
1461
|
}
|
|
1112
|
-
.
|
|
1113
|
-
|
|
1114
|
-
|
|
1115
|
-
|
|
1462
|
+
.process-list {
|
|
1463
|
+
display: flex;
|
|
1464
|
+
flex-direction: column;
|
|
1465
|
+
gap: 8px;
|
|
1116
1466
|
}
|
|
1117
|
-
.
|
|
1118
|
-
|
|
1467
|
+
.process-card {
|
|
1468
|
+
padding: var(--mc-card-padding-dense);
|
|
1469
|
+
border-radius: var(--mc-radius);
|
|
1470
|
+
background: var(--bg-card);
|
|
1471
|
+
border: 1px solid var(--border);
|
|
1119
1472
|
}
|
|
1120
|
-
.
|
|
1473
|
+
.process-card:hover {
|
|
1474
|
+
border-color: var(--border-active);
|
|
1475
|
+
}
|
|
1476
|
+
.process-card-head {
|
|
1121
1477
|
display: flex;
|
|
1122
1478
|
align-items: center;
|
|
1123
|
-
gap:
|
|
1479
|
+
gap: 10px;
|
|
1124
1480
|
}
|
|
1125
|
-
.
|
|
1126
|
-
|
|
1481
|
+
.process-label-badge {
|
|
1482
|
+
font-size: 10px;
|
|
1483
|
+
font-weight: 600;
|
|
1484
|
+
padding: 2px 8px;
|
|
1485
|
+
border-radius: 4px;
|
|
1486
|
+
background: var(--blue-bg);
|
|
1487
|
+
color: var(--blue);
|
|
1488
|
+
letter-spacing: 0.03em;
|
|
1489
|
+
}
|
|
1490
|
+
.process-card-head .process-actions {
|
|
1491
|
+
margin-left: auto;
|
|
1492
|
+
white-space: nowrap;
|
|
1493
|
+
}
|
|
1494
|
+
.process-desc {
|
|
1495
|
+
margin-top: 6px;
|
|
1496
|
+
color: var(--text-primary);
|
|
1497
|
+
font-size: 12px;
|
|
1498
|
+
line-height: 1.5;
|
|
1499
|
+
}
|
|
1500
|
+
.process-meta {
|
|
1501
|
+
margin-top: 4px;
|
|
1502
|
+
display: flex;
|
|
1503
|
+
gap: 12px;
|
|
1504
|
+
flex-wrap: wrap;
|
|
1505
|
+
color: var(--text-muted);
|
|
1506
|
+
font-size: 11px;
|
|
1507
|
+
font-family: var(--mc-font-mono);
|
|
1508
|
+
}
|
|
1509
|
+
.process-cmd {
|
|
1510
|
+
margin-top: 4px;
|
|
1511
|
+
color: var(--text-muted);
|
|
1512
|
+
font-size: 11px;
|
|
1513
|
+
font-family: var(--mc-font-mono);
|
|
1514
|
+
overflow: hidden;
|
|
1515
|
+
text-overflow: ellipsis;
|
|
1127
1516
|
white-space: nowrap;
|
|
1128
|
-
text-align: right;
|
|
1129
1517
|
}
|
|
1130
1518
|
.process-copy-btn {
|
|
1131
1519
|
font-size: 10px;
|
|
@@ -1145,61 +1533,45 @@ html[data-dashboard-skin="legacy"] .health-card {
|
|
|
1145
1533
|
}
|
|
1146
1534
|
|
|
1147
1535
|
/* ===== Skills ===== */
|
|
1148
|
-
.
|
|
1149
|
-
|
|
1150
|
-
|
|
1151
|
-
|
|
1152
|
-
font-size: 12px;
|
|
1153
|
-
font-weight: 600;
|
|
1154
|
-
color: var(--text-muted);
|
|
1155
|
-
text-transform: uppercase;
|
|
1156
|
-
letter-spacing: 0.06em;
|
|
1157
|
-
margin-bottom: 10px;
|
|
1158
|
-
padding: 0 2px;
|
|
1536
|
+
.skill-grid {
|
|
1537
|
+
display: grid;
|
|
1538
|
+
grid-template-columns: repeat(auto-fill, minmax(240px, 1fr));
|
|
1539
|
+
gap: 10px;
|
|
1159
1540
|
}
|
|
1160
|
-
.
|
|
1541
|
+
.skill-card {
|
|
1161
1542
|
display: flex;
|
|
1162
|
-
flex-
|
|
1543
|
+
flex-direction: column;
|
|
1163
1544
|
gap: 6px;
|
|
1164
|
-
|
|
1165
|
-
|
|
1166
|
-
display: inline-flex;
|
|
1167
|
-
align-items: center;
|
|
1168
|
-
gap: 5px;
|
|
1169
|
-
padding: 5px 10px;
|
|
1170
|
-
border-radius: 6px;
|
|
1545
|
+
padding: var(--mc-card-padding-dense);
|
|
1546
|
+
border-radius: var(--mc-radius);
|
|
1171
1547
|
background: var(--bg-card);
|
|
1172
1548
|
border: 1px solid var(--border);
|
|
1173
|
-
font-size: 12px;
|
|
1174
|
-
color: var(--text-secondary);
|
|
1175
|
-
cursor: default;
|
|
1176
|
-
transition: all 0.15s;
|
|
1177
|
-
max-width: 280px;
|
|
1178
1549
|
}
|
|
1179
|
-
.skill-
|
|
1180
|
-
|
|
1181
|
-
|
|
1550
|
+
.skill-card:hover { border-color: var(--border-active); }
|
|
1551
|
+
.skill-card-head {
|
|
1552
|
+
display: flex;
|
|
1553
|
+
align-items: center;
|
|
1554
|
+
justify-content: space-between;
|
|
1555
|
+
gap: 8px;
|
|
1182
1556
|
}
|
|
1183
|
-
.skill-
|
|
1184
|
-
|
|
1185
|
-
|
|
1186
|
-
border-radius: 50%;
|
|
1187
|
-
flex-shrink: 0;
|
|
1557
|
+
.skill-name {
|
|
1558
|
+
font-size: 12px;
|
|
1559
|
+
color: var(--text-primary);
|
|
1188
1560
|
}
|
|
1189
|
-
.skill-
|
|
1190
|
-
font-
|
|
1191
|
-
|
|
1192
|
-
|
|
1193
|
-
|
|
1561
|
+
.skill-cat {
|
|
1562
|
+
font-size: 10px;
|
|
1563
|
+
color: var(--text-muted);
|
|
1564
|
+
text-transform: uppercase;
|
|
1565
|
+
letter-spacing: 0.06em;
|
|
1194
1566
|
}
|
|
1195
|
-
.skill-
|
|
1196
|
-
font-size:
|
|
1197
|
-
font-weight: var(--mc-chrome-meta-weight);
|
|
1567
|
+
.skill-file {
|
|
1568
|
+
font-size: 10px;
|
|
1198
1569
|
color: var(--text-muted);
|
|
1199
|
-
|
|
1200
|
-
|
|
1201
|
-
text-overflow: ellipsis;
|
|
1570
|
+
font-family: var(--mc-font-mono);
|
|
1571
|
+
word-break: break-all;
|
|
1202
1572
|
}
|
|
1573
|
+
.skill-actions { display: flex; flex-wrap: wrap; gap: 6px; margin-top: 2px; }
|
|
1574
|
+
.skill-create-btn { margin-left: auto; }
|
|
1203
1575
|
|
|
1204
1576
|
/* ===== Context Packs ===== */
|
|
1205
1577
|
.context-pack-item {
|
|
@@ -1282,16 +1654,6 @@ html[data-dashboard-skin="legacy"] .health-card {
|
|
|
1282
1654
|
.live-indicator.live-reconnecting {
|
|
1283
1655
|
animation: pulse-text 1.5s ease-in-out infinite;
|
|
1284
1656
|
}
|
|
1285
|
-
.live-pulse-dot {
|
|
1286
|
-
width: 8px;
|
|
1287
|
-
height: 8px;
|
|
1288
|
-
border-radius: 50%;
|
|
1289
|
-
background: var(--green);
|
|
1290
|
-
position: relative;
|
|
1291
|
-
}
|
|
1292
|
-
.live-pulse-dot.live-dot-yellow {
|
|
1293
|
-
background: var(--yellow);
|
|
1294
|
-
}
|
|
1295
1657
|
/* Decorative ring pulses removed; header transport owns freshness */
|
|
1296
1658
|
.live-activity-feed {
|
|
1297
1659
|
max-height: 320px;
|
|
@@ -1378,6 +1740,21 @@ html[data-dashboard-skin="legacy"] .health-card {
|
|
|
1378
1740
|
width: 1em;
|
|
1379
1741
|
text-align: center;
|
|
1380
1742
|
}
|
|
1743
|
+
.live-activity-feed .monitor-row .monitor-row-avatar {
|
|
1744
|
+
display: inline-flex;
|
|
1745
|
+
align-items: center;
|
|
1746
|
+
justify-content: center;
|
|
1747
|
+
width: 18px;
|
|
1748
|
+
min-width: 18px;
|
|
1749
|
+
height: 18px;
|
|
1750
|
+
border-radius: 4px;
|
|
1751
|
+
font-size: 9px;
|
|
1752
|
+
font-weight: 700;
|
|
1753
|
+
line-height: 1;
|
|
1754
|
+
flex-shrink: 0;
|
|
1755
|
+
background: var(--bg-card-hover);
|
|
1756
|
+
color: var(--text-secondary);
|
|
1757
|
+
}
|
|
1381
1758
|
.live-activity-feed .monitor-row .feed-label {
|
|
1382
1759
|
flex: 1;
|
|
1383
1760
|
min-width: 0;
|
|
@@ -1387,6 +1764,32 @@ html[data-dashboard-skin="legacy"] .health-card {
|
|
|
1387
1764
|
color: var(--text-secondary);
|
|
1388
1765
|
font-size: 12px;
|
|
1389
1766
|
font-weight: 400;
|
|
1767
|
+
display: flex;
|
|
1768
|
+
align-items: baseline;
|
|
1769
|
+
gap: 6px;
|
|
1770
|
+
}
|
|
1771
|
+
/* Structured label spans: actor + verb never shrink; the plan filename is the
|
|
1772
|
+
low-signal segment and ellipsises first. */
|
|
1773
|
+
.live-activity-feed .monitor-row .feed-seg {
|
|
1774
|
+
flex-shrink: 0;
|
|
1775
|
+
white-space: nowrap;
|
|
1776
|
+
}
|
|
1777
|
+
.live-activity-feed .monitor-row .feed-sep {
|
|
1778
|
+
flex-shrink: 0;
|
|
1779
|
+
color: var(--text-muted);
|
|
1780
|
+
}
|
|
1781
|
+
.live-activity-feed .monitor-row .feed-seg-mid {
|
|
1782
|
+
flex-shrink: 1;
|
|
1783
|
+
min-width: 0;
|
|
1784
|
+
overflow: hidden;
|
|
1785
|
+
text-overflow: ellipsis;
|
|
1786
|
+
}
|
|
1787
|
+
.live-activity-feed .monitor-row .feed-seg-plan {
|
|
1788
|
+
flex-shrink: 3;
|
|
1789
|
+
min-width: 0;
|
|
1790
|
+
overflow: hidden;
|
|
1791
|
+
text-overflow: ellipsis;
|
|
1792
|
+
color: var(--text-muted);
|
|
1390
1793
|
}
|
|
1391
1794
|
.live-activity-feed .monitor-row .feed-time {
|
|
1392
1795
|
color: var(--text-muted);
|
|
@@ -1398,7 +1801,7 @@ html[data-dashboard-skin="legacy"] .health-card {
|
|
|
1398
1801
|
margin-left: auto;
|
|
1399
1802
|
}
|
|
1400
1803
|
.activity-feed-empty {
|
|
1401
|
-
padding:
|
|
1804
|
+
padding: var(--mc-space-2xl);
|
|
1402
1805
|
text-align: center;
|
|
1403
1806
|
color: var(--text-muted);
|
|
1404
1807
|
font-size: 12px;
|
|
@@ -1469,47 +1872,21 @@ html[data-dashboard-skin="legacy"] .health-card {
|
|
|
1469
1872
|
}
|
|
1470
1873
|
|
|
1471
1874
|
/* ===== Empty State ===== */
|
|
1875
|
+
/* Empty states scale with the card density ladder (viewport modes shrink
|
|
1876
|
+
--mc-card-padding), so they stay intentional from fullscreen to thin sidebar. */
|
|
1472
1877
|
.empty-state {
|
|
1473
1878
|
text-align: center;
|
|
1474
|
-
padding:
|
|
1879
|
+
padding: calc(var(--mc-card-padding) * 2.5) calc(var(--mc-card-padding) * 1.25);
|
|
1475
1880
|
color: var(--text-muted);
|
|
1476
1881
|
font-size: 13px;
|
|
1477
1882
|
}
|
|
1478
1883
|
|
|
1479
1884
|
/* ===== Clickable Cursors (Phase 1) ===== */
|
|
1480
1885
|
.plan-card { cursor: pointer; }
|
|
1481
|
-
.command-item { cursor: pointer; }
|
|
1482
|
-
.agent-item { cursor: pointer; }
|
|
1483
1886
|
.health-item { cursor: pointer; }
|
|
1484
1887
|
.terminal-item { cursor: pointer; }
|
|
1485
1888
|
.memory-item { cursor: pointer; }
|
|
1486
1889
|
|
|
1487
|
-
/* ===== Agent Expanded View ===== */
|
|
1488
|
-
.agent-details {
|
|
1489
|
-
max-height: 0;
|
|
1490
|
-
overflow: hidden;
|
|
1491
|
-
transition: max-height 0.3s ease, opacity 0.3s ease;
|
|
1492
|
-
opacity: 0;
|
|
1493
|
-
padding: 0 10px;
|
|
1494
|
-
}
|
|
1495
|
-
.agent-details.open {
|
|
1496
|
-
max-height: 200px;
|
|
1497
|
-
opacity: 1;
|
|
1498
|
-
padding: 8px 10px;
|
|
1499
|
-
}
|
|
1500
|
-
.agent-details p {
|
|
1501
|
-
font-size: 12px;
|
|
1502
|
-
color: var(--text-secondary);
|
|
1503
|
-
line-height: 1.5;
|
|
1504
|
-
}
|
|
1505
|
-
.agent-details .agent-path {
|
|
1506
|
-
font-size: 11px;
|
|
1507
|
-
color: var(--text-muted);
|
|
1508
|
-
font-family: var(--mc-font-mono);
|
|
1509
|
-
margin-top: 4px;
|
|
1510
|
-
word-break: break-all;
|
|
1511
|
-
}
|
|
1512
|
-
|
|
1513
1890
|
/* ===== Copied Toast ===== */
|
|
1514
1891
|
.copied-toast {
|
|
1515
1892
|
position: fixed;
|
|
@@ -1654,10 +2031,23 @@ html[data-dashboard-skin="legacy"] .health-card {
|
|
|
1654
2031
|
|
|
1655
2032
|
/* ===== Config form (session prefs) ===== */
|
|
1656
2033
|
.config-form {
|
|
1657
|
-
display:
|
|
1658
|
-
|
|
2034
|
+
display: grid;
|
|
2035
|
+
grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
|
|
1659
2036
|
gap: 16px;
|
|
1660
|
-
|
|
2037
|
+
}
|
|
2038
|
+
.config-actions {
|
|
2039
|
+
grid-column: 1 / -1;
|
|
2040
|
+
display: flex;
|
|
2041
|
+
align-items: center;
|
|
2042
|
+
gap: 12px;
|
|
2043
|
+
}
|
|
2044
|
+
.config-fieldset-wide {
|
|
2045
|
+
grid-column: 1 / -1;
|
|
2046
|
+
}
|
|
2047
|
+
.config-grid-2 {
|
|
2048
|
+
display: grid;
|
|
2049
|
+
grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
|
|
2050
|
+
gap: 4px 16px;
|
|
1661
2051
|
}
|
|
1662
2052
|
.config-fieldset {
|
|
1663
2053
|
border: 1px solid var(--border);
|
|
@@ -1666,11 +2056,28 @@ html[data-dashboard-skin="legacy"] .health-card {
|
|
|
1666
2056
|
padding: 12px 14px 14px;
|
|
1667
2057
|
margin: 0;
|
|
1668
2058
|
}
|
|
1669
|
-
.config-fieldset legend {
|
|
1670
|
-
padding: 0 6px;
|
|
1671
|
-
font-size: var(--mc-chrome-subtitle-size);
|
|
1672
|
-
font-weight: var(--mc-chrome-subtitle-weight);
|
|
1673
|
-
color: var(--text-muted);
|
|
2059
|
+
.config-fieldset legend {
|
|
2060
|
+
padding: 0 6px;
|
|
2061
|
+
font-size: var(--mc-chrome-subtitle-size);
|
|
2062
|
+
font-weight: var(--mc-chrome-subtitle-weight);
|
|
2063
|
+
color: var(--text-muted);
|
|
2064
|
+
}
|
|
2065
|
+
.btn-config-copy {
|
|
2066
|
+
align-self: flex-start;
|
|
2067
|
+
margin-top: 10px;
|
|
2068
|
+
background: none;
|
|
2069
|
+
border: 1px solid var(--border);
|
|
2070
|
+
color: var(--text-muted);
|
|
2071
|
+
padding: 3px 10px;
|
|
2072
|
+
border-radius: 4px;
|
|
2073
|
+
cursor: pointer;
|
|
2074
|
+
font-size: 11px;
|
|
2075
|
+
font-family: inherit;
|
|
2076
|
+
transition: all 0.15s;
|
|
2077
|
+
}
|
|
2078
|
+
.btn-config-copy:hover {
|
|
2079
|
+
color: var(--text-primary);
|
|
2080
|
+
border-color: var(--text-muted);
|
|
1674
2081
|
}
|
|
1675
2082
|
.config-row {
|
|
1676
2083
|
display: flex;
|
|
@@ -1889,12 +2296,12 @@ html[data-dashboard-skin="legacy"] .health-card {
|
|
|
1889
2296
|
/* ===== Phase 3: Empty State CTAs ===== */
|
|
1890
2297
|
.empty-state-cta {
|
|
1891
2298
|
text-align: center;
|
|
1892
|
-
padding:
|
|
2299
|
+
padding: calc(var(--mc-card-padding) * 2) calc(var(--mc-card-padding) * 1.25);
|
|
1893
2300
|
color: var(--text-muted);
|
|
1894
2301
|
font-size: 13px;
|
|
1895
2302
|
}
|
|
1896
2303
|
.empty-state-cta.compact {
|
|
1897
|
-
padding:
|
|
2304
|
+
padding: var(--mc-card-padding) var(--mc-space-lg);
|
|
1898
2305
|
font-size: 12px;
|
|
1899
2306
|
}
|
|
1900
2307
|
.empty-state-cta .empty-state-icon {
|
|
@@ -1942,7 +2349,7 @@ html[data-dashboard-skin="legacy"] .health-card {
|
|
|
1942
2349
|
background: var(--bg-card);
|
|
1943
2350
|
border: 1px solid var(--border);
|
|
1944
2351
|
color: var(--text-secondary);
|
|
1945
|
-
padding: var(--mc-space-md)
|
|
2352
|
+
padding: var(--mc-space-md) var(--mc-space-xl);
|
|
1946
2353
|
border-radius: var(--mc-radius-chrome);
|
|
1947
2354
|
cursor: pointer;
|
|
1948
2355
|
font-size: var(--mc-chrome-label-size);
|
|
@@ -2075,6 +2482,8 @@ html[data-dashboard-skin="legacy"] .health-card {
|
|
|
2075
2482
|
.plan-card-footer { flex-wrap: wrap; gap: var(--mc-space-xs); }
|
|
2076
2483
|
.health-grid { grid-template-columns: 1fr 1fr; }
|
|
2077
2484
|
.command-grid { grid-template-columns: 1fr 1fr; }
|
|
2485
|
+
.skill-grid { grid-template-columns: 1fr 1fr; }
|
|
2486
|
+
.agent-grid { grid-template-columns: 1fr 1fr; }
|
|
2078
2487
|
.git-row { flex-wrap: wrap; gap: var(--mc-space-xs); }
|
|
2079
2488
|
.terminal-popup { max-width: 95vw; padding: 14px; }
|
|
2080
2489
|
}
|
|
@@ -2093,12 +2502,60 @@ html[data-dashboard-skin="legacy"] .health-card {
|
|
|
2093
2502
|
.terminal-popup { max-width: 95vw; padding: 14px; }
|
|
2094
2503
|
}
|
|
2095
2504
|
|
|
2505
|
+
/* ===== Very thin sidebar (~<340px): tightest chrome, single-column card grids ===== */
|
|
2506
|
+
@media (max-width: 339px) {
|
|
2507
|
+
:root {
|
|
2508
|
+
--mc-card-padding: 10px;
|
|
2509
|
+
--mc-header-pad-x: 8px;
|
|
2510
|
+
--mc-header-pad-x-end: 2px;
|
|
2511
|
+
--mc-content-pad: 8px;
|
|
2512
|
+
}
|
|
2513
|
+
.header { gap: var(--mc-space-sm); }
|
|
2514
|
+
.header-right { gap: var(--mc-space-xs); }
|
|
2515
|
+
.header-workspace { display: none; }
|
|
2516
|
+
.health-grid,
|
|
2517
|
+
.command-grid,
|
|
2518
|
+
.skill-grid,
|
|
2519
|
+
.agent-grid { grid-template-columns: 1fr; }
|
|
2520
|
+
.live-activity-feed { max-height: 180px; }
|
|
2521
|
+
/* Flex feed-label: allow actor/verb to ellipsis so fixed spans do not hard-clip. */
|
|
2522
|
+
.live-activity-feed .monitor-row .feed-seg-actor,
|
|
2523
|
+
.live-activity-feed .monitor-row .feed-seg-verb {
|
|
2524
|
+
flex-shrink: 1;
|
|
2525
|
+
min-width: 0;
|
|
2526
|
+
overflow: hidden;
|
|
2527
|
+
text-overflow: ellipsis;
|
|
2528
|
+
}
|
|
2529
|
+
}
|
|
2530
|
+
|
|
2096
2531
|
.overview-stack {
|
|
2097
2532
|
display: flex;
|
|
2098
2533
|
flex-direction: column;
|
|
2099
2534
|
gap: 12px;
|
|
2100
2535
|
}
|
|
2101
2536
|
|
|
2537
|
+
/* ===== Mid panel (~701-1023px): two-column overview grid, IA row-major =====
|
|
2538
|
+
Collapse order follows the locked IA (Current mission -> Flight Log -> Checklist
|
|
2539
|
+
-> Crew monitor): Current mission spans row 1, Flight Log + Checklist share row 2,
|
|
2540
|
+
Crew monitor spans row 3. Page keeps scrolling; the one-fold lock starts at 1024px. */
|
|
2541
|
+
@media (min-width: 701px) and (max-width: 1023px) {
|
|
2542
|
+
#section-overview.active .overview-stack {
|
|
2543
|
+
display: grid;
|
|
2544
|
+
grid-template-columns: 1fr 1fr;
|
|
2545
|
+
grid-template-areas:
|
|
2546
|
+
"now now"
|
|
2547
|
+
"attention checklist"
|
|
2548
|
+
"monitor monitor";
|
|
2549
|
+
}
|
|
2550
|
+
#section-overview.active #now-execution-panel { grid-area: now; }
|
|
2551
|
+
#section-overview.active #attention-panel { grid-area: attention; }
|
|
2552
|
+
#section-overview.active #recent-plans-panel { grid-area: checklist; }
|
|
2553
|
+
#section-overview.active #hero-activity {
|
|
2554
|
+
grid-area: monitor;
|
|
2555
|
+
margin-bottom: 0;
|
|
2556
|
+
}
|
|
2557
|
+
}
|
|
2558
|
+
|
|
2102
2559
|
/* Desktop one-fold overview: 2x2 fixed-height grid; page does not scroll.
|
|
2103
2560
|
Additive under min-width 1024px only. Non-overview sections keep .content scroll.
|
|
2104
2561
|
Subheader (Cockpit anchors) hides: all four panels are visible; More lives in the header. */
|
|
@@ -2208,7 +2665,7 @@ html[data-dashboard-skin="legacy"] .health-card {
|
|
|
2208
2665
|
display: flex;
|
|
2209
2666
|
flex-direction: column;
|
|
2210
2667
|
gap: var(--mc-space-sm);
|
|
2211
|
-
padding:
|
|
2668
|
+
padding: var(--mc-card-padding-dense);
|
|
2212
2669
|
border: 1px solid var(--border);
|
|
2213
2670
|
border-radius: var(--radius);
|
|
2214
2671
|
background: var(--bg-card);
|
|
@@ -2437,6 +2894,25 @@ html[data-dashboard-skin="legacy"] .health-card {
|
|
|
2437
2894
|
margin: 0 0 8px;
|
|
2438
2895
|
line-height: 1.4;
|
|
2439
2896
|
}
|
|
2897
|
+
.plan-next-action {
|
|
2898
|
+
padding-top: 10px;
|
|
2899
|
+
font-size: var(--mc-chrome-meta-size);
|
|
2900
|
+
color: var(--text-secondary);
|
|
2901
|
+
line-height: 1.4;
|
|
2902
|
+
}
|
|
2903
|
+
.plan-next-action-label {
|
|
2904
|
+
color: var(--text-muted);
|
|
2905
|
+
font-weight: var(--mc-chrome-label-weight);
|
|
2906
|
+
}
|
|
2907
|
+
.plan-accordion-actions .plan-action-primary,
|
|
2908
|
+
.plan-accordion-actions .plan-action-primary:hover {
|
|
2909
|
+
background: var(--blue);
|
|
2910
|
+
border-color: transparent;
|
|
2911
|
+
color: #fff;
|
|
2912
|
+
}
|
|
2913
|
+
.plan-accordion-actions .plan-action-primary:hover {
|
|
2914
|
+
filter: brightness(1.08);
|
|
2915
|
+
}
|
|
2440
2916
|
.plan-todo-list-full {
|
|
2441
2917
|
display: flex;
|
|
2442
2918
|
flex-direction: column;
|
|
@@ -2448,7 +2924,7 @@ html[data-dashboard-skin="legacy"] .health-card {
|
|
|
2448
2924
|
|
|
2449
2925
|
/* Flight Log empty state reuses attention-empty className with empty-state-cta. */
|
|
2450
2926
|
.attention-empty {
|
|
2451
|
-
padding:
|
|
2927
|
+
padding: var(--mc-space-lg) var(--mc-space-md);
|
|
2452
2928
|
font-size: 12px;
|
|
2453
2929
|
color: var(--text-muted);
|
|
2454
2930
|
line-height: 1.45;
|
|
@@ -2456,7 +2932,7 @@ html[data-dashboard-skin="legacy"] .health-card {
|
|
|
2456
2932
|
.empty-state-cta.attention-empty,
|
|
2457
2933
|
.empty-state-cta.activity-feed-empty {
|
|
2458
2934
|
/* Shared renderer already sets padding; keep panel chrome size. */
|
|
2459
|
-
padding:
|
|
2935
|
+
padding: var(--mc-space-lg) var(--mc-space-md);
|
|
2460
2936
|
}
|
|
2461
2937
|
|
|
2462
2938
|
/* ===== Now execution panel (primary Current work) ===== */
|
|
@@ -2505,6 +2981,21 @@ html[data-dashboard-skin="legacy"] .health-card {
|
|
|
2505
2981
|
color: var(--text-muted);
|
|
2506
2982
|
background: var(--bg-card);
|
|
2507
2983
|
}
|
|
2984
|
+
/* Busy-outside-plan chip: live run-loop activity while the mission is not
|
|
2985
|
+
executing. Advice-family blue tokens (kind palette); text-only so the dot
|
|
2986
|
+
and icon contracts stay untouched. */
|
|
2987
|
+
.mc-busy-chip {
|
|
2988
|
+
display: inline-flex;
|
|
2989
|
+
align-items: center;
|
|
2990
|
+
font-size: var(--mc-chrome-meta-size);
|
|
2991
|
+
font-weight: var(--mc-chrome-meta-weight);
|
|
2992
|
+
letter-spacing: 0.02em;
|
|
2993
|
+
padding: 3px var(--mc-space-md);
|
|
2994
|
+
border-radius: var(--mc-radius-pill);
|
|
2995
|
+
color: var(--blue);
|
|
2996
|
+
background: var(--blue-bg);
|
|
2997
|
+
text-transform: uppercase;
|
|
2998
|
+
}
|
|
2508
2999
|
.now-plan-name {
|
|
2509
3000
|
margin-top: 10px;
|
|
2510
3001
|
font-size: 15px;
|
|
@@ -2753,8 +3244,8 @@ html[data-dashboard-skin="legacy"] .health-card {
|
|
|
2753
3244
|
width: 100%;
|
|
2754
3245
|
text-align: left;
|
|
2755
3246
|
margin: 0;
|
|
2756
|
-
padding:
|
|
2757
|
-
border-radius:
|
|
3247
|
+
padding: var(--mc-card-padding-dense);
|
|
3248
|
+
border-radius: var(--mc-radius);
|
|
2758
3249
|
border: 1px solid var(--border);
|
|
2759
3250
|
background: var(--bg-elevated, var(--bg-card));
|
|
2760
3251
|
color: var(--text-primary);
|
|
@@ -2762,10 +3253,10 @@ html[data-dashboard-skin="legacy"] .health-card {
|
|
|
2762
3253
|
font: inherit;
|
|
2763
3254
|
line-height: 1.4;
|
|
2764
3255
|
}
|
|
2765
|
-
/* Base hover/focus: keyboard-visible ring; kind rules below set colors
|
|
3256
|
+
/* Base hover/focus: keyboard-visible ring via --border-active (not invisible --border); kind rules below set colors. */
|
|
2766
3257
|
.flight-log-card:hover,
|
|
2767
3258
|
.flight-log-card:focus-visible {
|
|
2768
|
-
outline: 2px solid var(--border);
|
|
3259
|
+
outline: 2px solid var(--border-active);
|
|
2769
3260
|
outline-offset: 1px;
|
|
2770
3261
|
}
|
|
2771
3262
|
.flight-log-card-current {
|
|
@@ -2829,37 +3320,49 @@ html[data-dashboard-skin="legacy"] .health-card {
|
|
|
2829
3320
|
.flight-log-card-past.flight-log-kind-residual {
|
|
2830
3321
|
border-color: color-mix(in srgb, var(--yellow) 35%, var(--border));
|
|
2831
3322
|
}
|
|
2832
|
-
|
|
2833
|
-
.flight-log-card-past.flight-log-kind-residual:
|
|
3323
|
+
/* Earlier: muted color-mix on hover; full kind token on focus-visible for ≥3:1 keyboard ring. */
|
|
3324
|
+
.flight-log-card-past.flight-log-kind-residual:hover {
|
|
2834
3325
|
border-color: color-mix(in srgb, var(--yellow) 55%, var(--border));
|
|
2835
3326
|
outline-color: color-mix(in srgb, var(--yellow) 55%, var(--border));
|
|
2836
3327
|
}
|
|
3328
|
+
.flight-log-card-past.flight-log-kind-residual:focus-visible {
|
|
3329
|
+
border-color: var(--yellow);
|
|
3330
|
+
outline-color: var(--yellow);
|
|
3331
|
+
}
|
|
2837
3332
|
.flight-log-card-past.flight-log-kind-advice {
|
|
2838
3333
|
border-color: color-mix(in srgb, var(--blue) 35%, var(--border));
|
|
2839
3334
|
}
|
|
2840
|
-
.flight-log-card-past.flight-log-kind-advice:hover
|
|
2841
|
-
.flight-log-card-past.flight-log-kind-advice:focus-visible {
|
|
3335
|
+
.flight-log-card-past.flight-log-kind-advice:hover {
|
|
2842
3336
|
border-color: color-mix(in srgb, var(--blue) 55%, var(--border));
|
|
2843
3337
|
outline-color: color-mix(in srgb, var(--blue) 55%, var(--border));
|
|
2844
3338
|
}
|
|
3339
|
+
.flight-log-card-past.flight-log-kind-advice:focus-visible {
|
|
3340
|
+
border-color: var(--blue);
|
|
3341
|
+
outline-color: var(--blue);
|
|
3342
|
+
}
|
|
2845
3343
|
.flight-log-card-past.flight-log-kind-ok {
|
|
2846
3344
|
border-color: color-mix(in srgb, var(--green) 30%, var(--border));
|
|
2847
3345
|
}
|
|
2848
|
-
.flight-log-card-past.flight-log-kind-ok:hover
|
|
2849
|
-
.flight-log-card-past.flight-log-kind-ok:focus-visible {
|
|
3346
|
+
.flight-log-card-past.flight-log-kind-ok:hover {
|
|
2850
3347
|
border-color: color-mix(in srgb, var(--green) 50%, var(--border));
|
|
2851
3348
|
outline-color: color-mix(in srgb, var(--green) 50%, var(--border));
|
|
2852
3349
|
}
|
|
3350
|
+
.flight-log-card-past.flight-log-kind-ok:focus-visible {
|
|
3351
|
+
border-color: var(--green);
|
|
3352
|
+
outline-color: var(--green);
|
|
3353
|
+
}
|
|
2853
3354
|
.flight-log-card-past.flight-log-kind-warning {
|
|
2854
3355
|
border-color: color-mix(in srgb, var(--orange, var(--yellow)) 30%, var(--border));
|
|
2855
3356
|
}
|
|
2856
|
-
.flight-log-card-past.flight-log-kind-warning:hover
|
|
2857
|
-
.flight-log-card-past.flight-log-kind-warning:focus-visible {
|
|
3357
|
+
.flight-log-card-past.flight-log-kind-warning:hover {
|
|
2858
3358
|
border-color: color-mix(in srgb, var(--orange, var(--yellow)) 50%, var(--border));
|
|
2859
3359
|
outline-color: color-mix(in srgb, var(--orange, var(--yellow)) 50%, var(--border));
|
|
2860
3360
|
}
|
|
3361
|
+
.flight-log-card-past.flight-log-kind-warning:focus-visible {
|
|
3362
|
+
border-color: var(--orange, var(--yellow));
|
|
3363
|
+
outline-color: var(--orange, var(--yellow));
|
|
3364
|
+
}
|
|
2861
3365
|
.flight-log-card-warning {
|
|
2862
|
-
padding: 10px 12px;
|
|
2863
3366
|
border-color: color-mix(in srgb, var(--orange, var(--yellow)) 70%, var(--border));
|
|
2864
3367
|
background: color-mix(in srgb, var(--orange-bg, var(--yellow-bg)) 55%, var(--bg-elevated, var(--bg-card)));
|
|
2865
3368
|
font-size: 12px;
|
|
@@ -2987,6 +3490,7 @@ html[data-dashboard-skin="legacy"] .health-card {
|
|
|
2987
3490
|
<span class="last-updated" id="lastUpdated" hidden></span>
|
|
2988
3491
|
<button type="button" class="header-home-btn" id="headerHomeBtn" onclick="return goHome()" aria-label="Home, Overview" title="Home, Overview"></button>
|
|
2989
3492
|
<button class="btn-refresh" onclick="refresh()" id="refreshBtn" aria-label="Refresh" title="Refresh"><svg class="btn-refresh-icon" width="16" height="16" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><polyline points="14.5 2.75 14.5 6.75 10.5 6.75"/><path d="M13.25 10a5.5 5.5 0 1 1-1.3-5.72L14.5 6.75"/></svg></button>
|
|
3493
|
+
<button type="button" class="header-fullscreen-btn" id="fullscreenToggleBtn" onclick="return toggleMcFullscreen()" aria-pressed="false" aria-label="Enter fullscreen" title="Enter fullscreen"><svg width="16" height="16" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M9.5 2.5h4v4"/><path d="M13.5 2.5 9 7"/><path d="M6.5 13.5h-4v-4"/><path d="M2.5 13.5 7 9"/></svg></button>
|
|
2990
3494
|
<div class="nav-more">
|
|
2991
3495
|
<button type="button" class="nav-more-btn" id="navMoreBtn" aria-haspopup="menu" aria-expanded="false" aria-controls="navMoreMenu" title="More sections"></button>
|
|
2992
3496
|
<div class="nav-more-menu" id="navMoreMenu" role="menu" aria-label="More sections" hidden>
|
|
@@ -3026,12 +3530,10 @@ html[data-dashboard-skin="legacy"] .health-card {
|
|
|
3026
3530
|
</a>
|
|
3027
3531
|
<a class="top-tab nav-more-item" role="menuitem" href="#" data-section="terminals" tabindex="0" onclick="return showSection('terminals')" aria-label="Terminals section">
|
|
3028
3532
|
Terminals
|
|
3029
|
-
<span class="dot dot-blue" id="navTerminalsDot" aria-hidden="true"></span>
|
|
3030
3533
|
<span class="top-tab-badge" id="navTerminalsBadge">0</span>
|
|
3031
3534
|
</a>
|
|
3032
3535
|
<a class="top-tab nav-more-item" role="menuitem" href="#" data-section="processes" tabindex="0" onclick="return showSection('processes')" aria-label="Processes section">
|
|
3033
3536
|
Processes
|
|
3034
|
-
<span class="dot dot-green" id="navProcessesDot" aria-hidden="true"></span>
|
|
3035
3537
|
<span class="top-tab-badge" id="navProcessesBadge">0</span>
|
|
3036
3538
|
</a>
|
|
3037
3539
|
<a class="top-tab nav-more-item" role="menuitem" href="#" data-section="config" tabindex="0" onclick="return showSection('config')" aria-label="Config section">
|
|
@@ -3051,6 +3553,7 @@ html[data-dashboard-skin="legacy"] .health-card {
|
|
|
3051
3553
|
</div>
|
|
3052
3554
|
</div>
|
|
3053
3555
|
</div>
|
|
3556
|
+
<button type="button" class="fullscreen-exit-btn" id="fullscreenExitBtn" onclick="return toggleMcFullscreen(false)" aria-label="Exit fullscreen" title="Exit fullscreen (Esc)" hidden><svg width="16" height="16" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M13.5 6.5h-4v-4"/><path d="M9.5 6.5 14 2"/><path d="M2.5 9.5h4v4"/><path d="M6.5 9.5 2 14"/></svg></button>
|
|
3054
3557
|
|
|
3055
3558
|
<!-- Main -->
|
|
3056
3559
|
<div class="main">
|
|
@@ -3187,8 +3690,46 @@ function escapeJsString(value) {
|
|
|
3187
3690
|
}
|
|
3188
3691
|
|
|
3189
3692
|
// ===== Sections =====
|
|
3693
|
+
/** Navigable section ids (More menu + hash deep links). */
|
|
3694
|
+
const SECTION_IDS = [
|
|
3695
|
+
'overview',
|
|
3696
|
+
'plans',
|
|
3697
|
+
'activity',
|
|
3698
|
+
'agents',
|
|
3699
|
+
'skills',
|
|
3700
|
+
'commands',
|
|
3701
|
+
'health',
|
|
3702
|
+
'git',
|
|
3703
|
+
'memory',
|
|
3704
|
+
'terminals',
|
|
3705
|
+
'processes',
|
|
3706
|
+
'config',
|
|
3707
|
+
];
|
|
3708
|
+
|
|
3709
|
+
/** Read the active section from the URL hash (`#health` -> `health`). */
|
|
3710
|
+
function sectionFromHash() {
|
|
3711
|
+
try {
|
|
3712
|
+
const raw = (location.hash || '').replace(/^#\/?/, '').split('/')[0];
|
|
3713
|
+
return SECTION_IDS.includes(raw) ? raw : 'overview';
|
|
3714
|
+
} catch {
|
|
3715
|
+
return 'overview';
|
|
3716
|
+
}
|
|
3717
|
+
}
|
|
3718
|
+
|
|
3719
|
+
/** Keep the URL hash in sync so every tab is a shareable deep link. */
|
|
3720
|
+
function syncSectionHash(id) {
|
|
3721
|
+
try {
|
|
3722
|
+
const target = `#${id}`;
|
|
3723
|
+
if (location.hash !== target && history.replaceState) {
|
|
3724
|
+
history.replaceState(null, '', target);
|
|
3725
|
+
}
|
|
3726
|
+
} catch {
|
|
3727
|
+
// file:// or restricted contexts: hash sync is best-effort
|
|
3728
|
+
}
|
|
3729
|
+
}
|
|
3730
|
+
|
|
3190
3731
|
/** Active pane id (overview = Cockpit page; others = dropdown destinations). */
|
|
3191
|
-
let activeSectionId =
|
|
3732
|
+
let activeSectionId = sectionFromHash();
|
|
3192
3733
|
/** Last Cockpit in-page anchor while overview is active. */
|
|
3193
3734
|
let activeCockpitAnchor = 'now-execution-panel';
|
|
3194
3735
|
/** Activity tab source filter chip id (`all` or a source key). */
|
|
@@ -3215,6 +3756,25 @@ function goHome() {
|
|
|
3215
3756
|
return showSection('overview');
|
|
3216
3757
|
}
|
|
3217
3758
|
|
|
3759
|
+
/** Fullscreen (zen) viewport mode: hide the app header; Escape or the floating exit control restores chrome. */
|
|
3760
|
+
function toggleMcFullscreen(force) {
|
|
3761
|
+
const on = typeof force === 'boolean' ? force : !document.body.classList.contains('mc-fullscreen');
|
|
3762
|
+
document.body.classList.toggle('mc-fullscreen', on);
|
|
3763
|
+
const enterBtn = document.getElementById('fullscreenToggleBtn');
|
|
3764
|
+
if (enterBtn) enterBtn.setAttribute('aria-pressed', on ? 'true' : 'false');
|
|
3765
|
+
const exitBtn = document.getElementById('fullscreenExitBtn');
|
|
3766
|
+
if (exitBtn) exitBtn.hidden = !on;
|
|
3767
|
+
return false;
|
|
3768
|
+
}
|
|
3769
|
+
document.addEventListener('keydown', (e) => {
|
|
3770
|
+
if (e.key !== 'Escape' || !document.body.classList.contains('mc-fullscreen')) return;
|
|
3771
|
+
// Layered Escape: dismiss open menus first; only then exit fullscreen.
|
|
3772
|
+
if (typeof isNavMoreOpen === 'function' && isNavMoreOpen()) return;
|
|
3773
|
+
if (typeof openRecentPlanActionsKey !== 'undefined' && openRecentPlanActionsKey) return;
|
|
3774
|
+
e.preventDefault();
|
|
3775
|
+
toggleMcFullscreen(false);
|
|
3776
|
+
});
|
|
3777
|
+
|
|
3218
3778
|
/** Sync header Home chrome + section-title back control with active section. */
|
|
3219
3779
|
function updateHeaderHomeChrome() {
|
|
3220
3780
|
const onOverview = activeSectionId === 'overview';
|
|
@@ -3271,12 +3831,19 @@ function showSection(id) {
|
|
|
3271
3831
|
}
|
|
3272
3832
|
|
|
3273
3833
|
updateHeaderHomeChrome();
|
|
3834
|
+
syncSectionHash(activeSectionId);
|
|
3274
3835
|
|
|
3275
3836
|
const content = document.getElementById('content');
|
|
3276
3837
|
if (content) content.scrollTop = 0;
|
|
3277
3838
|
return false;
|
|
3278
3839
|
}
|
|
3279
3840
|
|
|
3841
|
+
/** Back/forward or pasted deep links switch tabs. */
|
|
3842
|
+
window.addEventListener('hashchange', () => {
|
|
3843
|
+
const id = sectionFromHash();
|
|
3844
|
+
if (id !== activeSectionId) showSection(id);
|
|
3845
|
+
});
|
|
3846
|
+
|
|
3280
3847
|
/**
|
|
3281
3848
|
* Anchor scroll still travelling. A snapshot re-render restores the scroll
|
|
3282
3849
|
* position captured when it started, which would strand a smooth scroll part
|
|
@@ -3536,14 +4103,15 @@ function progressColor(pct) {
|
|
|
3536
4103
|
return 'red';
|
|
3537
4104
|
}
|
|
3538
4105
|
|
|
4106
|
+
// Dot semantics: only state signals survive. completed = good, cancelled =
|
|
4107
|
+
// attention. pending / in_progress are steady states and get no dot (the
|
|
4108
|
+
// status text on the row stays the source of truth).
|
|
3539
4109
|
function statusDot(status) {
|
|
3540
4110
|
const map = {
|
|
3541
4111
|
completed: 'dot-green',
|
|
3542
|
-
in_progress: 'dot-blue dot-pulse',
|
|
3543
|
-
pending: 'dot-gray',
|
|
3544
4112
|
cancelled: 'dot-red',
|
|
3545
4113
|
};
|
|
3546
|
-
return map[status] ||
|
|
4114
|
+
return map[status] || null;
|
|
3547
4115
|
}
|
|
3548
4116
|
|
|
3549
4117
|
// ===== Client diagnostic activity =====
|
|
@@ -3621,11 +4189,12 @@ function semanticEventInfo(kind, commitType) {
|
|
|
3621
4189
|
const map = {
|
|
3622
4190
|
// Monitor live kinds: distinct resting color+icon (no kind-tag text, no left rail).
|
|
3623
4191
|
// run_plan follows .now-status-executing green; delivery uses cyan to avoid green wash.
|
|
3624
|
-
|
|
3625
|
-
|
|
3626
|
-
|
|
3627
|
-
|
|
3628
|
-
|
|
4192
|
+
// Gloss strings are default software lexicon display masks (kind ids unchanged).
|
|
4193
|
+
run_plan: { icon: '\u25b6', tag: 'tick', gloss: 'Tech Lead - live execution', color: 'var(--green)', bg: 'var(--green-bg)' },
|
|
4194
|
+
handoff: { icon: '\u23f8', tag: 'handoff', gloss: 'Scrum Master - awaiting gate', color: 'var(--yellow)', bg: 'var(--yellow-bg)' },
|
|
4195
|
+
agent_step: { icon: '\u25c9', tag: 'step', gloss: 'Full-Stack Developer - task unit', color: 'var(--purple)', bg: 'var(--purple-bg)' },
|
|
4196
|
+
plan_progress: { icon: '\u2691', tag: 'plan', gloss: 'Product Owner - milestone', color: 'var(--orange)', bg: 'var(--orange-bg)' },
|
|
4197
|
+
delivery: { icon: '\u2714', tag: 'delivery', gloss: 'DevOps Engineer - shipped unit', color: 'var(--cyan)', bg: 'var(--cyan-bg)' },
|
|
3629
4198
|
commit: { icon: '\u25cf', tag: 'commit', gloss: 'commit', color: 'var(--orange)', bg: 'var(--orange-bg)' },
|
|
3630
4199
|
agent: { icon: '\u25c6', tag: 'agent', gloss: 'agent', color: 'var(--purple)', bg: 'var(--purple-bg)' },
|
|
3631
4200
|
skill: { icon: '\u2726', tag: 'skill', gloss: 'skill', color: 'var(--orange)', bg: 'var(--orange-bg)' },
|
|
@@ -3637,12 +4206,12 @@ function semanticEventInfo(kind, commitType) {
|
|
|
3637
4206
|
// Locked BMP map (hotfix 2026-07-27): feat✦ fix⚙ docs✎ chore⚒ pr⑂ ship✈
|
|
3638
4207
|
if (kind === 'delivery' && commitType) {
|
|
3639
4208
|
const subtypes = {
|
|
3640
|
-
feat: { icon: '\u2726', tag: 'delivery', gloss: '
|
|
3641
|
-
fix: { icon: '\u2699', tag: 'delivery', gloss: '
|
|
3642
|
-
docs: { icon: '\u270e', tag: 'delivery', gloss: '
|
|
3643
|
-
chore: { icon: '\u2692', tag: 'delivery', gloss: '
|
|
3644
|
-
pr: { icon: '\u2442', tag: 'delivery', gloss: '
|
|
3645
|
-
ship: { icon: '\u2708', tag: 'delivery', gloss: '
|
|
4209
|
+
feat: { icon: '\u2726', tag: 'delivery', gloss: 'DevOps Engineer - feat', color: 'var(--purple)', bg: 'var(--purple-bg)' },
|
|
4210
|
+
fix: { icon: '\u2699', tag: 'delivery', gloss: 'DevOps Engineer - fix', color: 'var(--red)', bg: 'var(--red-bg)' },
|
|
4211
|
+
docs: { icon: '\u270e', tag: 'delivery', gloss: 'DevOps Engineer - docs', color: 'var(--blue)', bg: 'var(--blue-bg)' },
|
|
4212
|
+
chore: { icon: '\u2692', tag: 'delivery', gloss: 'DevOps Engineer - chore', color: 'var(--orange)', bg: 'var(--orange-bg)' },
|
|
4213
|
+
pr: { icon: '\u2442', tag: 'delivery', gloss: 'DevOps Engineer - PR', color: 'var(--cyan)', bg: 'var(--cyan-bg)' },
|
|
4214
|
+
ship: { icon: '\u2708', tag: 'delivery', gloss: 'DevOps Engineer - shipped unit', color: 'var(--cyan)', bg: 'var(--cyan-bg)' },
|
|
3646
4215
|
};
|
|
3647
4216
|
if (subtypes[commitType]) return subtypes[commitType];
|
|
3648
4217
|
}
|
|
@@ -3834,6 +4403,38 @@ function semanticEventTime(ev, _info) {
|
|
|
3834
4403
|
return '';
|
|
3835
4404
|
}
|
|
3836
4405
|
|
|
4406
|
+
/**
|
|
4407
|
+
* Crew feed time: source timestamp first, then the client first-seen stamp so
|
|
4408
|
+
* every row stays timestamped even when the emitter omits `at`.
|
|
4409
|
+
*/
|
|
4410
|
+
function crewEventTime(ev, info) {
|
|
4411
|
+
const source = semanticEventTime(ev, info);
|
|
4412
|
+
if (source) return source;
|
|
4413
|
+
const seenAt = ev && ev.id ? semanticSeenAt.get(ev.id) : null;
|
|
4414
|
+
return seenAt ? escapeHtml(relativeTime(seenAt)) : '';
|
|
4415
|
+
}
|
|
4416
|
+
|
|
4417
|
+
/**
|
|
4418
|
+
* Crew feed actor: kit agent id, else Engineering Manager for delivery, else
|
|
4419
|
+
* Squad when a plan is present (never the full plan filename), else Platform
|
|
4420
|
+
* Engineer. Default software lexicon masks; mirrors briefActivityActor.
|
|
4421
|
+
*/
|
|
4422
|
+
function crewEventActor(ev) {
|
|
4423
|
+
if (ev && ev.agent) return String(ev.agent);
|
|
4424
|
+
if (ev && ev.kind === 'delivery') return 'Engineering Manager';
|
|
4425
|
+
if (ev && ev.refs && ev.refs.plan) return 'Squad';
|
|
4426
|
+
return 'Platform Engineer';
|
|
4427
|
+
}
|
|
4428
|
+
|
|
4429
|
+
/** Avatar-style monogram: first letters of the first two kebab/space parts, else first two chars. */
|
|
4430
|
+
function agentInitials(id) {
|
|
4431
|
+
const clean = String(id || '').trim();
|
|
4432
|
+
if (!clean) return '';
|
|
4433
|
+
const parts = clean.split(/[-_\s.]+/).filter(Boolean);
|
|
4434
|
+
if (parts.length >= 2) return (parts[0][0] + parts[1][0]).toUpperCase();
|
|
4435
|
+
return clean.slice(0, 2).toUpperCase();
|
|
4436
|
+
}
|
|
4437
|
+
|
|
3837
4438
|
/**
|
|
3838
4439
|
* Mission Control is read-only: it copies text to the clipboard and a human
|
|
3839
4440
|
* pastes it. The panel cannot open a file, a chat, or an editor, so no label,
|
|
@@ -3984,6 +4585,59 @@ function checklistPlanActionCommands(plan) {
|
|
|
3984
4585
|
};
|
|
3985
4586
|
}
|
|
3986
4587
|
|
|
4588
|
+
/**
|
|
4589
|
+
* Plans tab v2: next actionable to-do (in_progress first, else first pending).
|
|
4590
|
+
* Terminal plans return null (no next step to surface).
|
|
4591
|
+
*/
|
|
4592
|
+
function planNextActionTodo(items) {
|
|
4593
|
+
if (!Array.isArray(items) || items.length === 0) return null;
|
|
4594
|
+
const current = items.find((t) => t.status === 'in_progress');
|
|
4595
|
+
if (current) return { id: current.id || '', content: current.content || '' };
|
|
4596
|
+
const next = items.find((t) => t.status === 'pending');
|
|
4597
|
+
if (next) return { id: next.id || '', content: next.content || '' };
|
|
4598
|
+
return null;
|
|
4599
|
+
}
|
|
4600
|
+
|
|
4601
|
+
/**
|
|
4602
|
+
* Plans tab v2: status-aware copy-only actions. Chat commands name the chat
|
|
4603
|
+
* input as paste destination; the path action names the file picker (open =
|
|
4604
|
+
* copy path). The first action is the row's primary next step per lifecycle:
|
|
4605
|
+
* active resumes, backlog runs, parked resumes, exhausted (incomplete)
|
|
4606
|
+
* resumes, completed opens/archives.
|
|
4607
|
+
*/
|
|
4608
|
+
function planTabActions(p) {
|
|
4609
|
+
const basename = checklistPlanBasename(p);
|
|
4610
|
+
const resume = { kind: 'chat', label: 'Copy resume prompt', command: `/continue-plan ${basename}` };
|
|
4611
|
+
const run = { kind: 'chat', label: 'Copy run command', command: `/run-plan ${basename}` };
|
|
4612
|
+
const edit = { kind: 'chat', label: 'Copy edit command', command: `/backlog-edit ${basename}` };
|
|
4613
|
+
const archive = { kind: 'chat', label: 'Copy archive command', command: `/archive-plan ${basename}` };
|
|
4614
|
+
const path = { kind: 'path', label: 'Copy plan path' };
|
|
4615
|
+
switch (p?.lifecycle) {
|
|
4616
|
+
case 'executing':
|
|
4617
|
+
case 'awaiting_user':
|
|
4618
|
+
return [resume, run, path];
|
|
4619
|
+
case 'backlog':
|
|
4620
|
+
return [run, edit, path];
|
|
4621
|
+
case 'parked':
|
|
4622
|
+
return [resume, path];
|
|
4623
|
+
case 'incomplete':
|
|
4624
|
+
return [resume, run, path];
|
|
4625
|
+
case 'completed':
|
|
4626
|
+
return [path, archive];
|
|
4627
|
+
default:
|
|
4628
|
+
return [resume, path];
|
|
4629
|
+
}
|
|
4630
|
+
}
|
|
4631
|
+
|
|
4632
|
+
function renderPlanTabActionButton(action, p, key, idx) {
|
|
4633
|
+
const primary = idx === 0 ? ' plan-action-primary' : '';
|
|
4634
|
+
const focusKey = `plan-act-${escapeAttr(key)}-${idx}`;
|
|
4635
|
+
if (action.kind === 'path') {
|
|
4636
|
+
return `<button type="button" class="status-bar-btn${primary}" data-focus-key="${focusKey}" onclick="event.stopPropagation();${copyRepoPathHandler(p.path)}" title="${escapeAttr(pathActionTitle(p.path))}">${escapeHtml(action.label)}</button>`;
|
|
4637
|
+
}
|
|
4638
|
+
return `<button type="button" class="status-bar-btn${primary}" data-focus-key="${focusKey}" onclick="event.stopPropagation();${copyForPasteHandler(action.command, action.command, 'chatInput')}" title="${escapeAttr(copyActionTitle(action.command, 'chatInput'))}">${escapeHtml(action.label)}</button>`;
|
|
4639
|
+
}
|
|
4640
|
+
|
|
3987
4641
|
let openRecentPlanActionsKey = null;
|
|
3988
4642
|
|
|
3989
4643
|
/** Anchor the fixed Actions menu above or below the trigger; escape panel overflow. */
|
|
@@ -4342,6 +4996,118 @@ function renderGitFileList(git) {
|
|
|
4342
4996
|
`;
|
|
4343
4997
|
}
|
|
4344
4998
|
|
|
4999
|
+
// ===== Git tab v2: promotion flow lanes + graph + staging hygiene (Phase 4) =====
|
|
5000
|
+
/** Divergence badge: sync (green), ahead (yellow), behind (orange). */
|
|
5001
|
+
function gitFlowBadge(div) {
|
|
5002
|
+
if (!div) return '<span class="git-flow-badge">no upstream</span>';
|
|
5003
|
+
const bits = [];
|
|
5004
|
+
if (div.ahead > 0) bits.push(`<span class="git-flow-badge is-ahead">ahead ${div.ahead}</span>`);
|
|
5005
|
+
if (div.behind > 0) bits.push(`<span class="git-flow-badge is-behind">behind ${div.behind}</span>`);
|
|
5006
|
+
if (!bits.length) bits.push('<span class="git-flow-badge is-sync">in sync</span>');
|
|
5007
|
+
return bits.join(' ');
|
|
5008
|
+
}
|
|
5009
|
+
|
|
5010
|
+
/**
|
|
5011
|
+
* State label + dot per Phase 1 dot semantics (state only, always paired
|
|
5012
|
+
* with a label). tone: green = good/in sync, yellow = important (promotion
|
|
5013
|
+
* pending or branch ahead), red never used here (git surface is read-only).
|
|
5014
|
+
*/
|
|
5015
|
+
function gitFlowStateLabel(div, { syncText, aheadText, behindText }) {
|
|
5016
|
+
if (!div) return { tone: null, text: 'upstream unavailable' };
|
|
5017
|
+
if (div.ahead === 0 && div.behind === 0) return { tone: 'green', text: syncText };
|
|
5018
|
+
if (div.ahead > 0 && div.behind === 0) return { tone: 'yellow', text: aheadText(div.ahead) };
|
|
5019
|
+
if (div.ahead === 0 && div.behind > 0) return { tone: 'yellow', text: behindText(div.behind) };
|
|
5020
|
+
return { tone: 'yellow', text: `diverged (ahead ${div.ahead}, behind ${div.behind})` };
|
|
5021
|
+
}
|
|
5022
|
+
|
|
5023
|
+
function gitFlowDot(tone) {
|
|
5024
|
+
return tone ? `<span class="dot dot-${tone}" aria-hidden="true"></span>` : '';
|
|
5025
|
+
}
|
|
5026
|
+
|
|
5027
|
+
function renderGitFlowRow({ from, to, div, texts, cta }) {
|
|
5028
|
+
const state = gitFlowStateLabel(div, texts);
|
|
5029
|
+
return `
|
|
5030
|
+
<div class="git-flow-row">
|
|
5031
|
+
<span class="git-flow-lane">${escapeHtml(from)}</span>
|
|
5032
|
+
<span class="git-flow-arrow" aria-hidden="true">→</span>
|
|
5033
|
+
<span class="git-flow-lane">${escapeHtml(to)}</span>
|
|
5034
|
+
<span class="git-flow-state">${gitFlowDot(state.tone)}<span>${escapeHtml(state.text)}</span>${gitFlowBadge(div)}</span>
|
|
5035
|
+
${cta || ''}
|
|
5036
|
+
</div>
|
|
5037
|
+
`;
|
|
5038
|
+
}
|
|
5039
|
+
|
|
5040
|
+
/** Promotion flow lanes: work -> staging -> main (what Cursor's git panel does not show). */
|
|
5041
|
+
function renderGitFlowCard(git) {
|
|
5042
|
+
const flow = git?.flow || {};
|
|
5043
|
+
const branch = git?.branch || 'HEAD';
|
|
5044
|
+
const rows = [];
|
|
5045
|
+
rows.push(renderGitFlowRow({
|
|
5046
|
+
from: branch,
|
|
5047
|
+
to: 'origin/staging',
|
|
5048
|
+
div: flow.vsStaging,
|
|
5049
|
+
texts: {
|
|
5050
|
+
syncText: 'Nothing to promote',
|
|
5051
|
+
aheadText: (n) => `${n} commit${n !== 1 ? 's' : ''} ready for staging`,
|
|
5052
|
+
behindText: (n) => `${n} commit${n !== 1 ? 's' : ''} behind staging`,
|
|
5053
|
+
},
|
|
5054
|
+
cta: flow.vsStaging && flow.vsStaging.ahead > 0
|
|
5055
|
+
? `<button type="button" class="git-action-btn primary git-flow-cta" onclick="copyGitStagingCommand()" title="${escapeAttr(copyActionTitle('/git-staging', 'chatInput'))}">Copy /git-staging</button>`
|
|
5056
|
+
: '',
|
|
5057
|
+
}));
|
|
5058
|
+
rows.push(renderGitFlowRow({
|
|
5059
|
+
from: 'origin/staging',
|
|
5060
|
+
to: 'origin/main',
|
|
5061
|
+
div: flow.stagingVsMain,
|
|
5062
|
+
texts: {
|
|
5063
|
+
syncText: 'main is current',
|
|
5064
|
+
aheadText: (n) => `${n} commit${n !== 1 ? 's' : ''} awaiting promotion to main`,
|
|
5065
|
+
behindText: (n) => `staging is ${n} commit${n !== 1 ? 's' : ''} behind main`,
|
|
5066
|
+
},
|
|
5067
|
+
cta: flow.stagingVsMain && flow.stagingVsMain.ahead > 0
|
|
5068
|
+
? `<button type="button" class="git-action-btn git-flow-cta" onclick="${copyForPasteHandler('/git-prod', '/git-prod', 'chatInput')}" title="${escapeAttr(copyActionTitle('/git-prod', 'chatInput'))}">Copy /git-prod</button>`
|
|
5069
|
+
: '',
|
|
5070
|
+
}));
|
|
5071
|
+
rows.push(renderGitFlowRow({
|
|
5072
|
+
from: branch,
|
|
5073
|
+
to: 'origin/main',
|
|
5074
|
+
div: flow.vsMain,
|
|
5075
|
+
texts: {
|
|
5076
|
+
syncText: 'In sync with main',
|
|
5077
|
+
aheadText: (n) => `${n} commit${n !== 1 ? 's' : ''} not yet in main`,
|
|
5078
|
+
behindText: (n) => `${n} commit${n !== 1 ? 's' : ''} behind main`,
|
|
5079
|
+
},
|
|
5080
|
+
cta: '',
|
|
5081
|
+
}));
|
|
5082
|
+
return `<div class="git-flow" aria-label="Promotion flow: work to staging to main">${rows.join('')}</div>`;
|
|
5083
|
+
}
|
|
5084
|
+
|
|
5085
|
+
/** Readable git graph: pre-rendered branch lanes + merges from git log --graph. */
|
|
5086
|
+
function renderGitGraphCard(git) {
|
|
5087
|
+
const lines = Array.isArray(git?.graph) ? git.graph : [];
|
|
5088
|
+
if (!lines.length) return '';
|
|
5089
|
+
return `
|
|
5090
|
+
<div class="git-graph-title">Recent history (all branches, ${lines.length} lines)</div>
|
|
5091
|
+
<pre class="git-graph" aria-label="Git commit graph with branch lanes and merges">${escapeHtml(lines.join('\n'))}</pre>
|
|
5092
|
+
`;
|
|
5093
|
+
}
|
|
5094
|
+
|
|
5095
|
+
/** Staging hygiene: untracked plan-monitor WIP (add-by-name only; ADR 2026-07-29 R14). */
|
|
5096
|
+
function renderGitHygieneHint(git) {
|
|
5097
|
+
const wip = git?.hygiene?.monitorWip || [];
|
|
5098
|
+
if (!wip.length) return '';
|
|
5099
|
+
return `
|
|
5100
|
+
<div class="git-hygiene" role="status">
|
|
5101
|
+
<div class="git-hygiene-body">
|
|
5102
|
+
<div class="git-hygiene-title"><span class="dot dot-yellow" aria-hidden="true"></span> Staging hygiene: ${wip.length} untracked plan-monitor file${wip.length !== 1 ? 's' : ''}</div>
|
|
5103
|
+
<div>Stage monitor files add-by-name only; never a broad <code>git add</code> of <code>.cursor/memory/</code>.</div>
|
|
5104
|
+
<div class="git-hygiene-paths">${wip.map((p) => escapeHtml(p)).join('<br>')}</div>
|
|
5105
|
+
</div>
|
|
5106
|
+
<button type="button" class="git-action-btn git-flow-cta" onclick="copyGitStagingCommand()" title="${escapeAttr(copyActionTitle('/git-staging', 'chatInput'))}">Copy /git-staging</button>
|
|
5107
|
+
</div>
|
|
5108
|
+
`;
|
|
5109
|
+
}
|
|
5110
|
+
|
|
4345
5111
|
// ===== Copy to clipboard + toast (Phase 1) =====
|
|
4346
5112
|
/** Fallback confirmation. Callers should pass a destination-aware message. */
|
|
4347
5113
|
function copyToastLabel(text) {
|
|
@@ -4359,7 +5125,9 @@ async function copyToClipboard(text, options) {
|
|
|
4359
5125
|
}
|
|
4360
5126
|
throw new Error('Clipboard API unavailable');
|
|
4361
5127
|
} catch {
|
|
4362
|
-
|
|
5128
|
+
const raw = String(text || '');
|
|
5129
|
+
const preview = raw.length > 120 ? `${raw.slice(0, 117)}...` : raw;
|
|
5130
|
+
showToast(`Copy failed - select manually: ${preview}`, true);
|
|
4363
5131
|
}
|
|
4364
5132
|
}
|
|
4365
5133
|
|
|
@@ -4441,10 +5209,14 @@ function renderConfigSection(d) {
|
|
|
4441
5209
|
return `
|
|
4442
5210
|
<div class="content-section" id="section-config">
|
|
4443
5211
|
<div class="section-title">
|
|
4444
|
-
|
|
5212
|
+
Config
|
|
4445
5213
|
<span class="section-subtitle">Session prefs in .cursor/context/config.json</span>
|
|
4446
5214
|
</div>
|
|
4447
5215
|
<form class="config-form" id="mission-config-form" onsubmit="return saveMissionConfig(event)">
|
|
5216
|
+
<div class="config-actions">
|
|
5217
|
+
<button type="submit" class="btn-config-save" id="config-save-btn" data-focus-key="config-save-btn">Save Configuration</button>
|
|
5218
|
+
<span class="config-hint">Writes to .cursor/context/config.json via the loopback allowlist. Write path unavailable (server down, non-loopback, read-only deploy)? Use a per-fieldset Copy snippet button and paste into the file by hand.</span>
|
|
5219
|
+
</div>
|
|
4448
5220
|
<fieldset class="config-fieldset">
|
|
4449
5221
|
<legend>Read-only</legend>
|
|
4450
5222
|
<p class="config-readonly">Onboarded: <strong>${escapeHtml(onboarded)}</strong> · Onboarding status: <strong>${escapeHtml(onboardingStatus)}</strong></p>
|
|
@@ -4459,7 +5231,7 @@ function renderConfigSection(d) {
|
|
|
4459
5231
|
<div class="config-row">
|
|
4460
5232
|
<label for="config-interTickCooldownMs">Inter-tick cooldown (ms)</label>
|
|
4461
5233
|
<input type="number" id="config-interTickCooldownMs" name="interTickCooldownMs" min="0" max="3600000" step="1" value="${escapeAttr(String(cooldown))}" data-focus-key="config-interTickCooldownMs" />
|
|
4462
|
-
<span class="config-hint">0 disables
|
|
5234
|
+
<span class="config-hint">0 disables; recommend 15000 on Auto continuous /run-plan.</span>
|
|
4463
5235
|
</div>
|
|
4464
5236
|
<div class="config-row config-row-inline">
|
|
4465
5237
|
<input type="checkbox" id="config-cadence-enabled" name="cadenceEnabled" ${cadenceEnabled ? 'checked' : ''} data-focus-key="config-cadence-enabled" />
|
|
@@ -4468,8 +5240,10 @@ function renderConfigSection(d) {
|
|
|
4468
5240
|
<div class="config-row">
|
|
4469
5241
|
<label for="config-cadence-threshold">Cadence tick threshold</label>
|
|
4470
5242
|
<input type="number" id="config-cadence-threshold" name="cadenceThreshold" min="1" max="100" step="1" value="${escapeAttr(String(cadenceThreshold))}" data-focus-key="config-cadence-threshold" />
|
|
4471
|
-
<span class="config-hint">Warn
|
|
5243
|
+
<span class="config-hint">Warn when unreviewed work remains after N ticks. Default 3.</span>
|
|
4472
5244
|
</div>
|
|
5245
|
+
<button type="button" class="btn-config-copy" data-focus-key="config-copy-session" onclick="return copyConfigSnippet('session')">Copy config.json snippet</button>
|
|
5246
|
+
<button type="button" class="btn-config-copy" data-focus-key="config-copy-dogfood" onclick="return copyStaticConfigSnippet('dogfood')" title="Copy-only; never writable via this tab">Copy dogfood.factoryRoot snippet</button>
|
|
4473
5247
|
</fieldset>
|
|
4474
5248
|
<fieldset class="config-fieldset">
|
|
4475
5249
|
<legend>Update check (opt-in)</legend>
|
|
@@ -4480,10 +5254,12 @@ function renderConfigSection(d) {
|
|
|
4480
5254
|
<div class="config-row">
|
|
4481
5255
|
<label for="config-updateCheck-intervalDays">Check interval (days)</label>
|
|
4482
5256
|
<input type="number" id="config-updateCheck-intervalDays" name="updateCheckIntervalDays" min="1" max="365" step="1" value="${escapeAttr(String(updateCheckInterval))}" data-focus-key="config-updateCheck-intervalDays" />
|
|
4483
|
-
<span class="config-hint">Notify only
|
|
5257
|
+
<span class="config-hint">Notify only; apply stays via the /update confirm. updateApply.auto is never writable here.</span>
|
|
4484
5258
|
</div>
|
|
5259
|
+
<button type="button" class="btn-config-copy" data-focus-key="config-copy-updateCheck" onclick="return copyConfigSnippet('updateCheck')">Copy config.json snippet</button>
|
|
5260
|
+
<button type="button" class="btn-config-copy" data-focus-key="config-copy-updateApply" onclick="return copyStaticConfigSnippet('updateApply')" title="Copy-only; never writable via this tab">Copy updateApply.auto snippet</button>
|
|
4485
5261
|
</fieldset>
|
|
4486
|
-
<fieldset class="config-fieldset">
|
|
5262
|
+
<fieldset class="config-fieldset config-fieldset-wide">
|
|
4487
5263
|
<legend>Audits (external plan review)</legend>
|
|
4488
5264
|
<div class="config-row config-row-inline">
|
|
4489
5265
|
<input type="checkbox" id="config-epr-enabled" name="eprEnabled" ${epr.enabled ? 'checked' : ''} data-focus-key="config-epr-enabled" />
|
|
@@ -4502,6 +5278,7 @@ function renderConfigSection(d) {
|
|
|
4502
5278
|
<select id="config-epr-backend" name="eprBackend" data-focus-key="config-epr-backend">
|
|
4503
5279
|
<option value="claude"${epr.backend === 'claude' || !epr.backend ? ' selected' : ''}>claude</option>
|
|
4504
5280
|
</select>
|
|
5281
|
+
<span class="config-hint">claude is the only backend today.</span>
|
|
4505
5282
|
</div>
|
|
4506
5283
|
<div class="config-row">
|
|
4507
5284
|
<label for="config-epr-mode">Arming mode</label>
|
|
@@ -4509,7 +5286,7 @@ function renderConfigSection(d) {
|
|
|
4509
5286
|
<option value="paste"${epr.mode !== 'autonomous' ? ' selected' : ''}>paste (legacy)</option>
|
|
4510
5287
|
<option value="autonomous"${epr.mode === 'autonomous' ? ' selected' : ''}>autonomous</option>
|
|
4511
5288
|
</select>
|
|
4512
|
-
<span class="config-hint">Missing mode keeps paste behavior
|
|
5289
|
+
<span class="config-hint">Missing mode keeps paste behavior. Autonomous auto-launches a visible review.</span>
|
|
4513
5290
|
</div>
|
|
4514
5291
|
<div class="config-row config-row-inline">
|
|
4515
5292
|
<input type="checkbox" id="config-epr-midBatch" name="eprMidBatch" ${epr.midBatchAudits ? 'checked' : ''} data-focus-key="config-epr-midBatch" />
|
|
@@ -4524,8 +5301,9 @@ function renderConfigSection(d) {
|
|
|
4524
5301
|
</select>
|
|
4525
5302
|
<span class="config-hint">Check owed/untriaged audits before plan-run commands. Missing key = off.</span>
|
|
4526
5303
|
</div>
|
|
5304
|
+
<button type="button" class="btn-config-copy" data-focus-key="config-copy-audits" onclick="return copyConfigSnippet('audits')">Copy config.json snippet</button>
|
|
4527
5305
|
</fieldset>
|
|
4528
|
-
<fieldset class="config-fieldset">
|
|
5306
|
+
<fieldset class="config-fieldset config-fieldset-wide">
|
|
4529
5307
|
<legend>Agent Personas</legend>
|
|
4530
5308
|
<div class="config-row">
|
|
4531
5309
|
<label for="config-persona-default">Default persona</label>
|
|
@@ -4534,19 +5312,17 @@ function renderConfigSection(d) {
|
|
|
4534
5312
|
</select>
|
|
4535
5313
|
<span class="config-hint">Chat/CLI tone only. Interface Skins stay in the More menu.</span>
|
|
4536
5314
|
</div>
|
|
4537
|
-
|
|
5315
|
+
<div class="config-grid-2">
|
|
5316
|
+
${modeRows}
|
|
5317
|
+
</div>
|
|
5318
|
+
<button type="button" class="btn-config-copy" data-focus-key="config-copy-personas" onclick="return copyConfigSnippet('personas')">Copy config.json snippet</button>
|
|
4538
5319
|
</fieldset>
|
|
4539
|
-
<button type="submit" class="btn-config-save" id="config-save-btn" data-focus-key="config-save-btn">Save</button>
|
|
4540
5320
|
</form>
|
|
4541
5321
|
</div>
|
|
4542
5322
|
`;
|
|
4543
5323
|
}
|
|
4544
5324
|
|
|
4545
|
-
|
|
4546
|
-
if (event && typeof event.preventDefault === 'function') event.preventDefault();
|
|
4547
|
-
const btn = document.getElementById('config-save-btn');
|
|
4548
|
-
if (btn) btn.disabled = true;
|
|
4549
|
-
|
|
5325
|
+
function collectMissionConfigPayload() {
|
|
4550
5326
|
const autoHandoff = !!document.getElementById('config-autoHandoff')?.checked;
|
|
4551
5327
|
const cooldownRaw = document.getElementById('config-interTickCooldownMs')?.value;
|
|
4552
5328
|
const interTickCooldownMs = Number.parseInt(String(cooldownRaw || '0'), 10);
|
|
@@ -4587,6 +5363,51 @@ async function saveMissionConfig(event) {
|
|
|
4587
5363
|
const el = document.getElementById(`config-persona-${mode.id}`);
|
|
4588
5364
|
if (el && el.value) payload.agentPersona.modes[mode.id] = el.value;
|
|
4589
5365
|
}
|
|
5366
|
+
return payload;
|
|
5367
|
+
}
|
|
5368
|
+
|
|
5369
|
+
/* Copy-only CRUD fallback: snippets reflect current form values and go to the
|
|
5370
|
+
clipboard only. No write surface is added; paste target is config.json. */
|
|
5371
|
+
const CONFIG_SNIPPET_GROUPS = {
|
|
5372
|
+
session: ['autoHandoff', 'interTickCooldownMs', 'fieldReportReviewCadence'],
|
|
5373
|
+
updateCheck: ['updateCheck'],
|
|
5374
|
+
audits: ['externalPlanReview'],
|
|
5375
|
+
personas: ['agentPersona'],
|
|
5376
|
+
};
|
|
5377
|
+
|
|
5378
|
+
function copyConfigSnippet(group) {
|
|
5379
|
+
const keys = CONFIG_SNIPPET_GROUPS[group];
|
|
5380
|
+
if (!keys) return false;
|
|
5381
|
+
const payload = collectMissionConfigPayload();
|
|
5382
|
+
const snippet = {};
|
|
5383
|
+
for (const key of keys) snippet[key] = payload[key];
|
|
5384
|
+
void copyToClipboard(JSON.stringify(snippet, null, 2), {
|
|
5385
|
+
toastMessage: 'Copied config snippet - paste into .cursor/context/config.json',
|
|
5386
|
+
});
|
|
5387
|
+
return false;
|
|
5388
|
+
}
|
|
5389
|
+
|
|
5390
|
+
/* Static copy-only snippets for knobs the tab can never write (allowlist unchanged). */
|
|
5391
|
+
const CONFIG_STATIC_SNIPPETS = {
|
|
5392
|
+
updateApply: { updateApply: { auto: false } },
|
|
5393
|
+
dogfood: { dogfood: { factoryRoot: '/absolute/path/to/agent-kit-dev' } },
|
|
5394
|
+
};
|
|
5395
|
+
|
|
5396
|
+
function copyStaticConfigSnippet(id) {
|
|
5397
|
+
const snippet = CONFIG_STATIC_SNIPPETS[id];
|
|
5398
|
+
if (!snippet) return false;
|
|
5399
|
+
void copyToClipboard(JSON.stringify(snippet, null, 2), {
|
|
5400
|
+
toastMessage: 'Copied config snippet - paste into .cursor/context/config.json (not writable via tab)',
|
|
5401
|
+
});
|
|
5402
|
+
return false;
|
|
5403
|
+
}
|
|
5404
|
+
|
|
5405
|
+
async function saveMissionConfig(event) {
|
|
5406
|
+
if (event && typeof event.preventDefault === 'function') event.preventDefault();
|
|
5407
|
+
const btn = document.getElementById('config-save-btn');
|
|
5408
|
+
if (btn) btn.disabled = true;
|
|
5409
|
+
|
|
5410
|
+
const payload = collectMissionConfigPayload();
|
|
4590
5411
|
|
|
4591
5412
|
try {
|
|
4592
5413
|
const res = await fetch('/api/config', {
|
|
@@ -4620,17 +5441,6 @@ async function saveMissionConfig(event) {
|
|
|
4620
5441
|
return false;
|
|
4621
5442
|
}
|
|
4622
5443
|
|
|
4623
|
-
// ===== Toggle agent details (Phase 1) =====
|
|
4624
|
-
function toggleAgentDetails(id) {
|
|
4625
|
-
const el = document.getElementById('agent-details-' + id);
|
|
4626
|
-
if (!el) return;
|
|
4627
|
-
el.classList.toggle('open');
|
|
4628
|
-
const arrow = document.getElementById('agent-arrow-' + id);
|
|
4629
|
-
if (arrow) {
|
|
4630
|
-
arrow.style.transform = el.classList.contains('open') ? 'rotate(180deg)' : 'rotate(0deg)';
|
|
4631
|
-
}
|
|
4632
|
-
}
|
|
4633
|
-
|
|
4634
5444
|
// ===== Healthcenter: per-check expand + Autofix map (copy-only) =====
|
|
4635
5445
|
/** Fixed v1 check set: plans, handoff, agents, commands, memory, git, config. */
|
|
4636
5446
|
const HEALTH_CHECK_META = {
|
|
@@ -4645,9 +5455,10 @@ const HEALTH_CHECK_META = {
|
|
|
4645
5455
|
autofix: { text: '/continue-plan', subject: '/continue-plan', destination: 'chatInput', label: 'Autofix' },
|
|
4646
5456
|
},
|
|
4647
5457
|
agents: {
|
|
4648
|
-
okDetail: '
|
|
4649
|
-
|
|
4650
|
-
|
|
5458
|
+
okDetail: 'Agents are optional in L0; empty .cursor/agents/ is healthy. Cataloged agents (if any) are listed in Agents.',
|
|
5459
|
+
// Intentionally unreachable: dashboard-data keeps agents.ok constant-true (L0-optional).
|
|
5460
|
+
failDetail: 'Intentionally unreachable (agents ok is constant-true; L0-optional inventory).',
|
|
5461
|
+
autofix: null,
|
|
4651
5462
|
},
|
|
4652
5463
|
commands: {
|
|
4653
5464
|
okDetail: 'Slash commands are available under .cursor/commands/.',
|
|
@@ -4671,6 +5482,14 @@ const HEALTH_CHECK_META = {
|
|
|
4671
5482
|
},
|
|
4672
5483
|
};
|
|
4673
5484
|
|
|
5485
|
+
/** Vitals grouping: checks grouped into vital systems for the diagnosis dashboard. */
|
|
5486
|
+
const HEALTH_VITAL_GROUPS = [
|
|
5487
|
+
{ label: 'Planning spine', checks: ['plans', 'handoff'] },
|
|
5488
|
+
{ label: 'Agent surface', checks: ['agents', 'commands'] },
|
|
5489
|
+
{ label: 'Memory loop', checks: ['memory'] },
|
|
5490
|
+
{ label: 'Workspace', checks: ['git', 'config'] },
|
|
5491
|
+
];
|
|
5492
|
+
|
|
4674
5493
|
function healthCheckSeverity(check, aggregateStatus) {
|
|
4675
5494
|
if (check && check.ok) return 'ok';
|
|
4676
5495
|
if (aggregateStatus === 'degraded') return 'degraded';
|
|
@@ -4678,19 +5497,25 @@ function healthCheckSeverity(check, aggregateStatus) {
|
|
|
4678
5497
|
return 'warning';
|
|
4679
5498
|
}
|
|
4680
5499
|
|
|
5500
|
+
/* Single severity chrome mapping (ok | warning | degraded | error): tone drives
|
|
5501
|
+
the dot class, label drives text/aria, token names the [data-sev] CSS color. */
|
|
5502
|
+
const HEALTH_SEVERITY_CHROME = {
|
|
5503
|
+
ok: { tone: 'green', label: 'ok', token: 'green' },
|
|
5504
|
+
warning: { tone: 'yellow', label: 'warn', token: 'yellow' },
|
|
5505
|
+
degraded: { tone: 'orange', label: 'degraded', token: 'orange' },
|
|
5506
|
+
error: { tone: 'red', label: 'error', token: 'red' },
|
|
5507
|
+
};
|
|
5508
|
+
|
|
5509
|
+
function healthSeverityChrome(sev) {
|
|
5510
|
+
return HEALTH_SEVERITY_CHROME[sev] || { tone: 'gray', label: sev || 'unknown', token: 'text-muted' };
|
|
5511
|
+
}
|
|
5512
|
+
|
|
4681
5513
|
function healthSeverityLabel(sev) {
|
|
4682
|
-
|
|
4683
|
-
if (sev === 'warning') return 'warn';
|
|
4684
|
-
if (sev === 'degraded') return 'degraded';
|
|
4685
|
-
if (sev === 'error') return 'error';
|
|
4686
|
-
return sev || 'unknown';
|
|
5514
|
+
return healthSeverityChrome(sev).label;
|
|
4687
5515
|
}
|
|
4688
5516
|
|
|
4689
5517
|
function healthDotClass(sev) {
|
|
4690
|
-
|
|
4691
|
-
if (sev === 'warning') return 'dot-yellow';
|
|
4692
|
-
if (sev === 'degraded') return 'dot-yellow';
|
|
4693
|
-
return 'dot-red';
|
|
5518
|
+
return `dot-${healthSeverityChrome(sev).tone}`;
|
|
4694
5519
|
}
|
|
4695
5520
|
|
|
4696
5521
|
function toggleHealthCheck(checkId) {
|
|
@@ -4708,6 +5533,57 @@ function healthCheckKeydown(event, checkId) {
|
|
|
4708
5533
|
}
|
|
4709
5534
|
}
|
|
4710
5535
|
|
|
5536
|
+
// ===== Memory tab: interactive error rows (expand + copy fix prompt) =====
|
|
5537
|
+
function toggleMemoryError(idx) {
|
|
5538
|
+
const card = document.getElementById('memory-error-' + idx);
|
|
5539
|
+
if (!card) return;
|
|
5540
|
+
const open = card.classList.toggle('is-expanded');
|
|
5541
|
+
const btn = card.querySelector('.memory-error-head');
|
|
5542
|
+
if (btn) btn.setAttribute('aria-expanded', open ? 'true' : 'false');
|
|
5543
|
+
}
|
|
5544
|
+
|
|
5545
|
+
/**
|
|
5546
|
+
* One expandable error row: header toggles detail (error / cause / solution /
|
|
5547
|
+
* files / tags), and a copy-only fix prompt names the chat input as destination.
|
|
5548
|
+
*/
|
|
5549
|
+
function renderMemoryErrorRow(entry, idx) {
|
|
5550
|
+
const title = entry.title || entry.id;
|
|
5551
|
+
const dateLabel = entry.date || (entry.modifiedAt ? entry.modifiedAt.slice(0, 10) : '');
|
|
5552
|
+
const fixPrompt = [
|
|
5553
|
+
`Fix this recorded problem class: ${title}`,
|
|
5554
|
+
entry.error ? `Symptom: ${entry.error}` : '',
|
|
5555
|
+
entry.solution ? `Known solution direction: ${entry.solution}` : '',
|
|
5556
|
+
entry.path || `.cursor/memory/errors/${entry.id}.md`,
|
|
5557
|
+
].filter(Boolean).join('\n');
|
|
5558
|
+
const fields = [
|
|
5559
|
+
['Error', entry.error],
|
|
5560
|
+
['Cause', entry.cause],
|
|
5561
|
+
['Solution', entry.solution],
|
|
5562
|
+
].filter(([, body]) => body);
|
|
5563
|
+
return `
|
|
5564
|
+
<div class="memory-error-card" id="memory-error-${idx}">
|
|
5565
|
+
<button type="button" class="memory-error-head" aria-expanded="false" aria-controls="memory-error-detail-${idx}" onclick="toggleMemoryError(${idx})">
|
|
5566
|
+
<span class="memory-error-title">${escapeHtml(title)}</span>
|
|
5567
|
+
${dateLabel ? `<span class="memory-error-date">${escapeHtml(dateLabel)}</span>` : ''}
|
|
5568
|
+
<span class="memory-error-chevron" aria-hidden="true">›</span>
|
|
5569
|
+
</button>
|
|
5570
|
+
<div class="memory-error-detail" id="memory-error-detail-${idx}">
|
|
5571
|
+
${fields.map(([label, body]) => `
|
|
5572
|
+
<div class="memory-error-field">
|
|
5573
|
+
<div class="memory-error-field-label">${escapeHtml(label)}</div>
|
|
5574
|
+
<div class="memory-error-field-body">${escapeHtml(body)}</div>
|
|
5575
|
+
</div>
|
|
5576
|
+
`).join('')}
|
|
5577
|
+
${entry.files ? `<div class="memory-error-field"><div class="memory-error-field-label">Files</div><div class="memory-error-files">${escapeHtml(entry.files)}</div></div>` : ''}
|
|
5578
|
+
${(entry.tags || []).length > 0 ? `<div class="memory-tag-row">${entry.tags.map(t => `<span class="memory-tag">${escapeHtml(t)}</span>`).join('')}</div>` : ''}
|
|
5579
|
+
<div class="memory-error-actions">
|
|
5580
|
+
<button type="button" class="memory-error-copy-btn" onclick="event.stopPropagation(); ${copyForPasteHandler(fixPrompt, 'fix prompt', 'chatInput')}" title="${escapeAttr(copyActionTitle('fix prompt', 'chatInput'))}">Copy fix prompt</button>
|
|
5581
|
+
</div>
|
|
5582
|
+
</div>
|
|
5583
|
+
</div>
|
|
5584
|
+
`;
|
|
5585
|
+
}
|
|
5586
|
+
|
|
4711
5587
|
/** @deprecated Prefer toggleHealthCheck; kept for any leftover callers. */
|
|
4712
5588
|
function showHealthInfo(el, ok) {
|
|
4713
5589
|
const parent = el.parentElement;
|
|
@@ -4797,7 +5673,8 @@ function updateTransportChrome() {
|
|
|
4797
5673
|
if (!statusDot || !statusLabel) return;
|
|
4798
5674
|
|
|
4799
5675
|
const healthStatus = data?.health?.status || 'ok';
|
|
4800
|
-
const
|
|
5676
|
+
const healthChrome = healthSeverityChrome(healthStatus);
|
|
5677
|
+
const healthTone = healthChrome.tone;
|
|
4801
5678
|
|
|
4802
5679
|
if (sseReconnecting) {
|
|
4803
5680
|
statusDot.className = 'dot dot-yellow dot-pulse reconnecting-pulse';
|
|
@@ -4813,10 +5690,14 @@ function updateTransportChrome() {
|
|
|
4813
5690
|
statusDot.className = healthStatus === 'ok'
|
|
4814
5691
|
? 'dot dot-green'
|
|
4815
5692
|
: `dot dot-${healthTone} dot-pulse`;
|
|
4816
|
-
statusLabel.textContent = healthStatus === 'ok'
|
|
5693
|
+
statusLabel.textContent = healthStatus === 'ok'
|
|
5694
|
+
? 'Live'
|
|
5695
|
+
: healthChrome.label.charAt(0).toUpperCase() + healthChrome.label.slice(1);
|
|
4817
5696
|
} else {
|
|
4818
5697
|
statusDot.className = `dot dot-${healthStatus === 'ok' ? 'yellow' : healthTone}`;
|
|
4819
|
-
statusLabel.textContent = healthStatus === 'ok'
|
|
5698
|
+
statusLabel.textContent = healthStatus === 'ok'
|
|
5699
|
+
? 'Polling'
|
|
5700
|
+
: healthChrome.label.charAt(0).toUpperCase() + healthChrome.label.slice(1);
|
|
4820
5701
|
}
|
|
4821
5702
|
|
|
4822
5703
|
if (sseTimer) {
|
|
@@ -4934,6 +5815,7 @@ function nowFingerprint(now) {
|
|
|
4934
5815
|
nextTodo: now.nextTodo?.id || null,
|
|
4935
5816
|
modifiedAt: now.modifiedAt,
|
|
4936
5817
|
lifecycle: now.lifecycle,
|
|
5818
|
+
busyOutsidePlan: now.busyOutsidePlan?.active === true,
|
|
4937
5819
|
});
|
|
4938
5820
|
}
|
|
4939
5821
|
|
|
@@ -4998,7 +5880,6 @@ function captureUiState() {
|
|
|
4998
5880
|
return {
|
|
4999
5881
|
scrollTop: contentEl ? contentEl.scrollTop : 0,
|
|
5000
5882
|
focusKey,
|
|
5001
|
-
openAgents: Array.from(document.querySelectorAll('.agent-details.open')).map((el) => el.id),
|
|
5002
5883
|
panelScrolls: capturePanelScrollOffsets(),
|
|
5003
5884
|
openRecentPlanActionsKey,
|
|
5004
5885
|
};
|
|
@@ -5006,14 +5887,6 @@ function captureUiState() {
|
|
|
5006
5887
|
|
|
5007
5888
|
function restoreUiState(state) {
|
|
5008
5889
|
if (!state) return;
|
|
5009
|
-
for (const id of state.openAgents || []) {
|
|
5010
|
-
const el = document.getElementById(id);
|
|
5011
|
-
if (!el) continue;
|
|
5012
|
-
el.classList.add('open');
|
|
5013
|
-
const arrowId = id.replace(/^agent-details-/, 'agent-arrow-');
|
|
5014
|
-
const arrow = document.getElementById(arrowId);
|
|
5015
|
-
if (arrow) arrow.style.transform = 'rotate(180deg)';
|
|
5016
|
-
}
|
|
5017
5890
|
const contentEl = document.getElementById('content');
|
|
5018
5891
|
if (contentEl) {
|
|
5019
5892
|
if (pendingAnchorScroll && Date.now() < pendingAnchorScroll.until) {
|
|
@@ -5096,8 +5969,20 @@ function mergePlansForUi(d) {
|
|
|
5096
5969
|
.split('/')
|
|
5097
5970
|
.pop();
|
|
5098
5971
|
const enriched = byKey.get(key) || {};
|
|
5099
|
-
|
|
5100
|
-
|
|
5972
|
+
// Live status bar data: frontmatter to-do items are the source of truth.
|
|
5973
|
+
// Summary fields only fill in when items are absent (malformed frontmatter).
|
|
5974
|
+
const items = Array.isArray(raw.todos?.items) ? raw.todos.items : [];
|
|
5975
|
+
const fromItems = items.length > 0;
|
|
5976
|
+
const completed = fromItems
|
|
5977
|
+
? items.filter((t) => t.status === 'completed').length
|
|
5978
|
+
: (raw.todos?.completed ?? enriched.progress?.completed ?? 0);
|
|
5979
|
+
const total = fromItems
|
|
5980
|
+
? items.length
|
|
5981
|
+
: (raw.todos?.total ?? enriched.progress?.total ?? 0);
|
|
5982
|
+
const inProgress = fromItems
|
|
5983
|
+
? items.filter((t) => t.status === 'in_progress').length
|
|
5984
|
+
: (raw.todos?.inProgress ?? 0);
|
|
5985
|
+
const nextActionTodo = planNextActionTodo(items);
|
|
5101
5986
|
let lifecycle = enriched.lifecycle;
|
|
5102
5987
|
if (!lifecycle) {
|
|
5103
5988
|
if (total > 0 && completed >= total && (raw.todos?.inProgress || 0) === 0) {
|
|
@@ -5113,14 +5998,16 @@ function mergePlansForUi(d) {
|
|
|
5113
5998
|
overview: raw.overview || enriched.overview || '',
|
|
5114
5999
|
modifiedAt: raw.modifiedAt || enriched.modifiedAt || null,
|
|
5115
6000
|
progressPct:
|
|
5116
|
-
|
|
5117
|
-
?
|
|
5118
|
-
:
|
|
5119
|
-
?
|
|
6001
|
+
total > 0
|
|
6002
|
+
? Math.round((completed / total) * 100)
|
|
6003
|
+
: typeof raw.progress === 'number'
|
|
6004
|
+
? raw.progress
|
|
5120
6005
|
: 0,
|
|
5121
|
-
progressLabel:
|
|
6006
|
+
progressLabel: `${completed} of ${total} complete`,
|
|
5122
6007
|
progressCompleted: completed,
|
|
5123
6008
|
progressTotal: total,
|
|
6009
|
+
progressInProgress: inProgress,
|
|
6010
|
+
nextActionTodo,
|
|
5124
6011
|
lifecycle,
|
|
5125
6012
|
// /run-plan-all layer from the semantic model (display-only; the panel
|
|
5126
6013
|
// never writes the queue). 'none' / null outside queue mode.
|
|
@@ -5383,21 +6270,26 @@ function renderPlansAccordion(d) {
|
|
|
5383
6270
|
${queueRolePill(p)}
|
|
5384
6271
|
<span class="recent-plan-progress">${escapeHtml(p.progressLabel)}</span>
|
|
5385
6272
|
</span>
|
|
5386
|
-
<span class="progress-bar plan-progress-bar" role="
|
|
6273
|
+
<span class="progress-bar plan-progress-bar" role="progressbar" aria-valuenow="${pct}" aria-valuemin="0" aria-valuemax="100" aria-valuetext="${escapeAttr(p.progressLabel)}" aria-label="Progress">
|
|
5387
6274
|
<span class="progress-fill plan-progress-fill ${color}${life.key === 'executing' ? ' is-executing' : ''}" style="width:${pct}%"></span>
|
|
5388
6275
|
</span>
|
|
5389
6276
|
<span class="plan-accordion-overview">${escapeHtml(p.overview || '\u2014')}</span>
|
|
5390
6277
|
<span class="plan-accordion-meta">
|
|
5391
6278
|
<span>${fmtDate(p.modifiedAt)}</span>
|
|
5392
6279
|
<span>${pct}%</span>
|
|
6280
|
+
${p.progressInProgress > 0 ? `<span>${p.progressInProgress} in progress</span>` : ''}
|
|
6281
|
+
${p.nextActionTodo ? `<span>Next: <code>${escapeHtml(p.nextActionTodo.id)}</code></span>` : ''}
|
|
5393
6282
|
</span>
|
|
5394
6283
|
</span>
|
|
5395
6284
|
<span class="plan-accordion-chevron" aria-hidden="true">\u25BC</span>
|
|
5396
6285
|
</button>
|
|
5397
6286
|
<div class="plan-accordion-panel" id="plan-panel-${escapeAttr(key)}" role="region" aria-labelledby="plan-trigger-${escapeAttr(key)}"${open ? '' : ' hidden'}>
|
|
5398
6287
|
<p class="plan-accordion-hint">Narrow panels open one plan at a time. Shift-click a header (or turn Multiple open on) to keep more expanded.</p>
|
|
6288
|
+
${p.nextActionTodo
|
|
6289
|
+
? `<div class="plan-next-action"><span class="plan-next-action-label">Next action:</span> <code>${escapeHtml(p.nextActionTodo.id)}</code>${p.nextActionTodo.content ? ` \u2014 ${escapeHtml(p.nextActionTodo.content)}` : ''}</div>`
|
|
6290
|
+
: ''}
|
|
5399
6291
|
<div class="plan-accordion-actions">
|
|
5400
|
-
|
|
6292
|
+
${planTabActions(p).map((action, idx) => renderPlanTabActionButton(action, p, key, idx)).join('')}
|
|
5401
6293
|
</div>
|
|
5402
6294
|
${todos.length > 0
|
|
5403
6295
|
? `<div class="plan-todo-list plan-todo-list-full" role="list">
|
|
@@ -5410,7 +6302,7 @@ function renderPlansAccordion(d) {
|
|
|
5410
6302
|
title="${escapeAttr(pathActionTitle(p.path))}"
|
|
5411
6303
|
onclick="${copyRepoPathHandler(p.path)}"
|
|
5412
6304
|
onkeydown="if(event.key==='Enter'||event.key===' '){event.preventDefault();${copyRepoPathHandler(p.path)}}">
|
|
5413
|
-
|
|
6305
|
+
${statusDot(t.status) ? `<span class="dot ${statusDot(t.status)}" aria-hidden="true"></span>` : ''}
|
|
5414
6306
|
<span style="flex:1"><code style="font-size:11px">${escapeHtml(t.id || '')}</code>${t.content ? ` \u2014 ${escapeHtml(t.content)}` : ''}</span>
|
|
5415
6307
|
<span style="color:var(--text-muted);font-size:11px">${escapeHtml(t.status)}</span>
|
|
5416
6308
|
</div>`,
|
|
@@ -5517,6 +6409,10 @@ function nowMetaIconSvg(kind, opts) {
|
|
|
5517
6409
|
* Space-theme Cockpit/nav icon set (inline SVG, no font/fetch/dependency).
|
|
5518
6410
|
* Shared chrome SVG shell with header refresh / nowMetaIconSvg:
|
|
5519
6411
|
* viewBox 0 0 16 16, stroke 1.5, round caps/joins, CSS box via --mc-chrome-icon-size.
|
|
6412
|
+
* Legibility floor (holds at every render size, hero included): no feature
|
|
6413
|
+
* smaller than the 1.5 stroke; stroke-edge clearance between adjacent
|
|
6414
|
+
* features >= 1 unit. Detail that cannot meet the floor is drawn filled
|
|
6415
|
+
* (fill="currentColor" stroke="none"), never as a sub-stroke stroked shape.
|
|
5520
6416
|
* Static path markup only; never concatenate untrusted text into the SVG.
|
|
5521
6417
|
* @param {'current-mission'|'monitor'|'field-report'|'checklist'|'more-sections'|'overview'|'plans'|'activity'|'agents'|'skills'|'skins'|'commands'|'health'|'git'|'memory'|'terminals'|'processes'|'config'} kind
|
|
5522
6418
|
* @param {{ decorative?: boolean }} [opts] decorative true (default): aria-hidden next to a visible label.
|
|
@@ -5546,18 +6442,16 @@ function spaceIconSvg(kind, opts) {
|
|
|
5546
6442
|
config: 'Config',
|
|
5547
6443
|
};
|
|
5548
6444
|
const paths = {
|
|
5549
|
-
// Rocket (mission)
|
|
6445
|
+
// Rocket (mission) — window is a solid dot (a stroked ring here would sit below the stroke floor)
|
|
5550
6446
|
'current-mission':
|
|
5551
6447
|
'<path d="M8 2c1.4 2 2.5 4 2.5 6a2.5 2.5 0 01-5 0C5.5 6 6.6 4 8 2z"/>' +
|
|
5552
|
-
'<circle cx="8" cy="7.5" r="
|
|
6448
|
+
'<circle cx="8" cy="7.5" r="1" fill="currentColor" stroke="none"/>' +
|
|
5553
6449
|
'<path d="M6 10.5l-1 2M10 10.5l1 2M7 13h2"/>',
|
|
5554
|
-
// Radar (monitor) —
|
|
6450
|
+
// Radar (monitor) — two rings + sweep wedge; a third ring or centre dot would sit below the stroke floor
|
|
5555
6451
|
monitor:
|
|
5556
6452
|
'<circle cx="8" cy="8" r="5.5"/>' +
|
|
5557
|
-
'<circle cx="8" cy="8" r="
|
|
5558
|
-
'<
|
|
5559
|
-
'<path d="M8 8L12.8 4.2A5.5 5.5 0 0 1 13.3 7.2Z"/>' +
|
|
5560
|
-
'<circle cx="8" cy="8" r=".55"/>',
|
|
6453
|
+
'<circle cx="8" cy="8" r="2.75"/>' +
|
|
6454
|
+
'<path d="M8 8L12.8 4.2A5.5 5.5 0 0 1 13.3 7.2Z"/>',
|
|
5561
6455
|
// Clipboard (Flight Log) — matches copy/source affordance; not PTT radio
|
|
5562
6456
|
'field-report':
|
|
5563
6457
|
'<rect x="4.5" y="3.5" width="7" height="10" rx="1.2"/>' +
|
|
@@ -5567,16 +6461,16 @@ function spaceIconSvg(kind, opts) {
|
|
|
5567
6461
|
checklist:
|
|
5568
6462
|
'<path d="M3.5 4.5h9M3.5 8h6.5M3.5 11.5h5.5"/>' +
|
|
5569
6463
|
'<path d="M10.5 10.5l1.2 1.2L14 9.5"/>',
|
|
5570
|
-
// Ellipsis (more sections) —
|
|
6464
|
+
// Ellipsis (more sections) — solid dots: stroked rings at 4px spacing land on exact tangency
|
|
5571
6465
|
'more-sections':
|
|
5572
|
-
'<circle cx="4" cy="8" r="1.
|
|
5573
|
-
'<circle cx="8" cy="8" r="1.
|
|
5574
|
-
'<circle cx="12" cy="8" r="1.
|
|
5575
|
-
// House (overview / home) — roof + walls + door as one coherent glyph
|
|
6466
|
+
'<circle cx="4" cy="8" r="1.1" fill="currentColor" stroke="none"/>' +
|
|
6467
|
+
'<circle cx="8" cy="8" r="1.1" fill="currentColor" stroke="none"/>' +
|
|
6468
|
+
'<circle cx="12" cy="8" r="1.1" fill="currentColor" stroke="none"/>',
|
|
6469
|
+
// House (overview / home) — roof + walls + door as one coherent glyph; door interior stays above the stroke floor
|
|
5576
6470
|
overview:
|
|
5577
6471
|
'<path d="M2.5 7.5L8 2.75l5.5 4.75"/>' +
|
|
5578
6472
|
'<path d="M4.25 7.75v6.5h7.5v-6.5"/>' +
|
|
5579
|
-
'<path d="M6.
|
|
6473
|
+
'<path d="M6.5 14.25v-3.25h3v3.25"/>',
|
|
5580
6474
|
// Document (plans)
|
|
5581
6475
|
plans:
|
|
5582
6476
|
'<path d="M4.5 2.5h5l4 4v8.5a1 1 0 01-1 1h-8a1 1 0 01-1-1v-12a1 1 0 011-1z"/>' +
|
|
@@ -5588,10 +6482,10 @@ function spaceIconSvg(kind, opts) {
|
|
|
5588
6482
|
agents:
|
|
5589
6483
|
'<circle cx="8" cy="5.5" r="2.5"/>' +
|
|
5590
6484
|
'<path d="M3 14.5c0-3 2.2-5 5-5s5 2 5 5"/>',
|
|
5591
|
-
// Gear (skills)
|
|
6485
|
+
// Gear (skills) — ring + 8 spokes outside it; arcs/teeth cut into the ring would sink below the stroke floor
|
|
5592
6486
|
skills:
|
|
5593
6487
|
'<circle cx="8" cy="8" r="2"/>' +
|
|
5594
|
-
'<path d="
|
|
6488
|
+
'<path d="M8 5V3.5M8 11v1.5M5 8H3.5M11 8h1.5M10.1 5.9l1.1-1.1M10.1 10.1l1.1 1.1M5.9 10.1l-1.1 1.1M5.9 5.9l-1.1-1.1"/>',
|
|
5595
6489
|
// Overlapping swatches (skins) — distinct from Skills gear
|
|
5596
6490
|
skins:
|
|
5597
6491
|
'<rect x="2.5" y="3.5" width="7" height="7" rx="1.25"/>' +
|
|
@@ -5609,11 +6503,11 @@ function spaceIconSvg(kind, opts) {
|
|
|
5609
6503
|
'<path d="M10.5 4.5v7a2 2 0 01-2 2h-2"/>' +
|
|
5610
6504
|
'<path d="M6.5 11.5a2 2 0 100-4 2 2 0 000 4z"/>' +
|
|
5611
6505
|
'<path d="M10.5 6.5v2"/>',
|
|
5612
|
-
// Chip (memory)
|
|
6506
|
+
// Chip (memory) — two internal lines 3 units apart; a third line would drop clearance below the stroke floor
|
|
5613
6507
|
memory:
|
|
5614
6508
|
'<path d="M4.5 2.5h7a1 1 0 011 1v9a1 1 0 01-1 1h-7a1 1 0 01-1-1v-9a1 1 0 011-1z"/>' +
|
|
5615
6509
|
'<path d="M6.5 1.5v2M9.5 1.5v2M6.5 12.5v2M9.5 12.5v2"/>' +
|
|
5616
|
-
'<path d="M6 6.5h4M6
|
|
6510
|
+
'<path d="M6 6.5h4M6 9.5h2.5"/>',
|
|
5617
6511
|
// Screen (terminals)
|
|
5618
6512
|
terminals:
|
|
5619
6513
|
'<path d="M1.5 3.5h13a1 1 0 011 1v7a1 1 0 01-1 1H1.5a1 1 0 01-1-1v-7a1 1 0 011-1z"/>' +
|
|
@@ -5735,9 +6629,10 @@ function flightLogKindClassName(kind) {
|
|
|
5735
6629
|
}
|
|
5736
6630
|
|
|
5737
6631
|
/**
|
|
5738
|
-
* Gaps card on Flight Log (
|
|
6632
|
+
* Gaps card on Flight Log (NOW large / Earlier smaller). Labels locked.
|
|
5739
6633
|
* Kind selects Mission Control palette tokens (typed notification chrome).
|
|
5740
|
-
*
|
|
6634
|
+
* One composed action button per entry (semantic model action; fallback compose).
|
|
6635
|
+
* @param {{ text?: string, at?: string|null, sourcePath?: string, variant?: string, kind?: string, action?: { label?: string, command?: string, sourcePath?: string } }} entry
|
|
5741
6636
|
* @param {number} idx
|
|
5742
6637
|
*/
|
|
5743
6638
|
function renderFlightLogCard(entry, idx) {
|
|
@@ -5748,7 +6643,7 @@ function renderFlightLogCard(entry, idx) {
|
|
|
5748
6643
|
? entry.sourcePath.trim()
|
|
5749
6644
|
: '.cursor/HANDOFF.md';
|
|
5750
6645
|
const variant = entry?.variant === 'past' ? 'past' : 'current';
|
|
5751
|
-
const label = variant === 'current' ? '
|
|
6646
|
+
const label = variant === 'current' ? 'NOW' : 'Earlier';
|
|
5752
6647
|
const kind = flightLogMessageKind(text, { kind: entry?.kind });
|
|
5753
6648
|
const kindClass = flightLogKindClassName(kind);
|
|
5754
6649
|
const metaBits = [];
|
|
@@ -5756,8 +6651,16 @@ function renderFlightLogCard(entry, idx) {
|
|
|
5756
6651
|
metaBits.push(escapeHtml(String(entry.at)));
|
|
5757
6652
|
}
|
|
5758
6653
|
metaBits.push(escapeHtml(sourcePath));
|
|
5759
|
-
const
|
|
5760
|
-
const
|
|
6654
|
+
const action = entry?.action && typeof entry.action === 'object' ? entry.action : null;
|
|
6655
|
+
const actionLabel =
|
|
6656
|
+
typeof action?.label === 'string' && action.label.trim()
|
|
6657
|
+
? action.label.trim()
|
|
6658
|
+
: 'Copy fix prompt';
|
|
6659
|
+
const actionCommand =
|
|
6660
|
+
typeof action?.command === 'string' && action.command.trim()
|
|
6661
|
+
? action.command
|
|
6662
|
+
: `${variant === 'current' ? 'Act on these open residuals:' : 'Act on these earlier residuals:'}\n${text}\n${sourcePath}`;
|
|
6663
|
+
const copyActionHandler = copyForPasteHandler(actionCommand, 'fix prompt', 'chatInput');
|
|
5761
6664
|
const cardClass =
|
|
5762
6665
|
variant === 'current'
|
|
5763
6666
|
? `flight-log-card flight-log-card-current ${kindClass}`
|
|
@@ -5769,8 +6672,7 @@ function renderFlightLogCard(entry, idx) {
|
|
|
5769
6672
|
<div class="flight-log-card-text">${escapeHtml(text)}</div>
|
|
5770
6673
|
<div class="flight-log-card-meta">${metaBits.join(' · ')}</div>
|
|
5771
6674
|
<div class="flight-log-card-actions">
|
|
5772
|
-
<button type="button" class="status-bar-btn" data-focus-key="flight-log-
|
|
5773
|
-
<button type="button" class="status-bar-btn" data-focus-key="flight-log-copy-path-${variant}-${idx}" onclick="${copyPathHandler}" style="font-size:11px;padding:3px 10px" title="${escapeAttr(pathActionTitle(sourcePath))}">${PATH_COPY_LABEL}</button>
|
|
6675
|
+
<button type="button" class="status-bar-btn" data-focus-key="flight-log-action-${variant}-${idx}" onclick="${copyActionHandler}" style="font-size:11px;padding:3px 10px" title="${escapeAttr(copyActionTitle('fix prompt', 'chatInput'))}">${escapeHtml(actionLabel)}</button>
|
|
5774
6676
|
</div>
|
|
5775
6677
|
</div>
|
|
5776
6678
|
`;
|
|
@@ -5778,8 +6680,9 @@ function renderFlightLogCard(entry, idx) {
|
|
|
5778
6680
|
|
|
5779
6681
|
/**
|
|
5780
6682
|
* Operator Warning card on Flight Log (API/usage or orchestrator heads-up).
|
|
5781
|
-
* No Review all / Resolve all / cadence CTAs.
|
|
5782
|
-
*
|
|
6683
|
+
* No Review all / Resolve all / cadence CTAs. One composed action button
|
|
6684
|
+
* per entry (semantic model action; fallback compose).
|
|
6685
|
+
* @param {{ id?: string, kind?: string, title?: string, text?: string, sourcePath?: string, action?: { label?: string, command?: string, sourcePath?: string } }} warning
|
|
5783
6686
|
* @param {number} idx
|
|
5784
6687
|
*/
|
|
5785
6688
|
function renderFlightLogWarningCard(warning, idx) {
|
|
@@ -5794,8 +6697,17 @@ function renderFlightLogWarningCard(warning, idx) {
|
|
|
5794
6697
|
? warning.sourcePath.trim()
|
|
5795
6698
|
: '.cursor/HANDOFF.md';
|
|
5796
6699
|
const kind = typeof warning?.kind === 'string' ? warning.kind : 'warning';
|
|
5797
|
-
const
|
|
5798
|
-
const
|
|
6700
|
+
const actionSubject = kind === 'api_limit' ? 'recovery prompt' : 'follow-up prompt';
|
|
6701
|
+
const action = warning?.action && typeof warning.action === 'object' ? warning.action : null;
|
|
6702
|
+
const actionLabel =
|
|
6703
|
+
typeof action?.label === 'string' && action.label.trim()
|
|
6704
|
+
? action.label.trim()
|
|
6705
|
+
: `Copy ${actionSubject}`;
|
|
6706
|
+
const actionCommand =
|
|
6707
|
+
typeof action?.command === 'string' && action.command.trim()
|
|
6708
|
+
? action.command
|
|
6709
|
+
: `${kind === 'api_limit' ? 'Resume after this quota pause:' : 'Act on this heads-up:'}\n${text}\n${sourcePath}`;
|
|
6710
|
+
const copyActionHandler = copyForPasteHandler(actionCommand, actionSubject, 'chatInput');
|
|
5799
6711
|
const aria = `${title}: ${text}`;
|
|
5800
6712
|
return `
|
|
5801
6713
|
<div class="flight-log-card flight-log-card-warning" role="listitem" tabindex="0" data-focus-key="flight-log-warning-${idx}" data-warning-kind="${escapeAttr(kind)}" aria-label="${escapeAttr(aria)}">
|
|
@@ -5803,8 +6715,7 @@ function renderFlightLogWarningCard(warning, idx) {
|
|
|
5803
6715
|
<div class="flight-log-card-text">${escapeHtml(text)}</div>
|
|
5804
6716
|
<div class="flight-log-card-meta">${escapeHtml(sourcePath)}</div>
|
|
5805
6717
|
<div class="flight-log-card-actions">
|
|
5806
|
-
<button type="button" class="status-bar-btn" data-focus-key="flight-log-warning-
|
|
5807
|
-
<button type="button" class="status-bar-btn" data-focus-key="flight-log-warning-copy-path-${idx}" onclick="${copyPathHandler}" style="font-size:11px;padding:3px 10px" title="${escapeAttr(pathActionTitle(sourcePath))}">${PATH_COPY_LABEL}</button>
|
|
6718
|
+
<button type="button" class="status-bar-btn" data-focus-key="flight-log-warning-action-${idx}" onclick="${copyActionHandler}" style="font-size:11px;padding:3px 10px" title="${escapeAttr(copyActionTitle(actionSubject, 'chatInput'))}">${escapeHtml(actionLabel)}</button>
|
|
5808
6719
|
</div>
|
|
5809
6720
|
</div>
|
|
5810
6721
|
`;
|
|
@@ -5812,9 +6723,9 @@ function renderFlightLogWarningCard(warning, idx) {
|
|
|
5812
6723
|
|
|
5813
6724
|
/**
|
|
5814
6725
|
* Quiet-state open-triage row (untriaged external review). Prompt chrome;
|
|
5815
|
-
*
|
|
6726
|
+
* one composed triage-command button per row. No Review all / Resolve all.
|
|
5816
6727
|
* Kind class from flightLogKindClassName('prompt') (SoT map; not a literal).
|
|
5817
|
-
* @param {{ id?: string, label?: string, sourcePath?: string, action?: { target?: string, label?: string, subject?: string } }} item
|
|
6728
|
+
* @param {{ id?: string, label?: string, sourcePath?: string, action?: { target?: string, label?: string, subject?: string, command?: string } }} item
|
|
5818
6729
|
* @param {number} idx
|
|
5819
6730
|
*/
|
|
5820
6731
|
function renderFlightLogQuietOpenTriageCard(item, idx) {
|
|
@@ -5831,8 +6742,19 @@ function renderFlightLogQuietOpenTriageCard(item, idx) {
|
|
|
5831
6742
|
typeof item?.action?.target === 'string' && item.action.target.trim()
|
|
5832
6743
|
? item.action.target.trim()
|
|
5833
6744
|
: `/plan-review-triage ${sourcePath}`;
|
|
5834
|
-
const
|
|
5835
|
-
|
|
6745
|
+
const actionSubject =
|
|
6746
|
+
typeof item?.action?.subject === 'string' && item.action.subject.trim()
|
|
6747
|
+
? item.action.subject.trim()
|
|
6748
|
+
: 'triage command';
|
|
6749
|
+
const actionLabel =
|
|
6750
|
+
typeof item?.action?.label === 'string' && item.action.label.trim()
|
|
6751
|
+
? item.action.label.trim()
|
|
6752
|
+
: 'Copy triage command';
|
|
6753
|
+
const actionCommand =
|
|
6754
|
+
typeof item?.action?.command === 'string' && item.action.command.trim()
|
|
6755
|
+
? item.action.command
|
|
6756
|
+
: triageCmd;
|
|
6757
|
+
const copyActionHandler = copyForPasteHandler(actionCommand, actionSubject, 'chatInput');
|
|
5836
6758
|
const aria = `Review: ${label}`;
|
|
5837
6759
|
const kindClass = flightLogKindClassName('prompt');
|
|
5838
6760
|
return `
|
|
@@ -5841,8 +6763,7 @@ function renderFlightLogQuietOpenTriageCard(item, idx) {
|
|
|
5841
6763
|
<div class="flight-log-card-text">${escapeHtml(label)}</div>
|
|
5842
6764
|
<div class="flight-log-card-meta">${escapeHtml(sourcePath)}</div>
|
|
5843
6765
|
<div class="flight-log-card-actions">
|
|
5844
|
-
<button type="button" class="status-bar-btn" data-focus-key="flight-log-open-triage-
|
|
5845
|
-
<button type="button" class="status-bar-btn" data-focus-key="flight-log-open-triage-path-${idx}" onclick="${copyPathHandler}" style="font-size:11px;padding:3px 10px" title="${escapeAttr(pathActionTitle(sourcePath))}">${PATH_COPY_LABEL}</button>
|
|
6766
|
+
<button type="button" class="status-bar-btn" data-focus-key="flight-log-open-triage-action-${idx}" onclick="${copyActionHandler}" style="font-size:11px;padding:3px 10px" title="${escapeAttr(copyActionTitle(actionSubject, 'chatInput'))}">${escapeHtml(actionLabel)}</button>
|
|
5846
6767
|
</div>
|
|
5847
6768
|
</div>
|
|
5848
6769
|
`;
|
|
@@ -5922,6 +6843,7 @@ function renderAttentionPanel(d, attentionChanged) {
|
|
|
5922
6843
|
sourcePath: e.sourcePath || sourcePath,
|
|
5923
6844
|
variant: 'past',
|
|
5924
6845
|
kind: e.kind || null,
|
|
6846
|
+
action: e.action && typeof e.action === 'object' ? e.action : null,
|
|
5925
6847
|
},
|
|
5926
6848
|
idx,
|
|
5927
6849
|
),
|
|
@@ -5967,11 +6889,15 @@ function renderAttentionPanel(d, attentionChanged) {
|
|
|
5967
6889
|
sourcePath,
|
|
5968
6890
|
variant: 'current',
|
|
5969
6891
|
kind: typeof fl?.currentKind === 'string' ? fl.currentKind : null,
|
|
6892
|
+
action:
|
|
6893
|
+
fl?.currentAction && typeof fl.currentAction === 'object'
|
|
6894
|
+
? fl.currentAction
|
|
6895
|
+
: null,
|
|
5970
6896
|
},
|
|
5971
6897
|
0,
|
|
5972
6898
|
)
|
|
5973
|
-
: `<div class="flight-log-card flight-log-card-current flight-log-kind-ok" role="status" data-flight-log-kind="ok" aria-label="No
|
|
5974
|
-
<span class="flight-log-card-label">
|
|
6899
|
+
: `<div class="flight-log-card flight-log-card-current flight-log-kind-ok" role="status" data-flight-log-kind="ok" aria-label="No Gaps now">
|
|
6900
|
+
<span class="flight-log-card-label">NOW</span>
|
|
5975
6901
|
<div class="flight-log-card-text" style="color:var(--text-muted)">Quiet · nothing live</div>
|
|
5976
6902
|
</div>`;
|
|
5977
6903
|
body = `${warningsBlock}<div class="flight-log-stack" role="list">${currentCard}${pastCards}</div>`;
|
|
@@ -5995,6 +6921,7 @@ function renderAttentionPanel(d, attentionChanged) {
|
|
|
5995
6921
|
<div class="card${attentionChanged ? ' card-flash' : ''}" id="attention-panel">
|
|
5996
6922
|
<div class="card-header now-panel-header">
|
|
5997
6923
|
<span class="card-title">${spaceIconSvg('field-report')}Flight Log</span>
|
|
6924
|
+
${renderBusyOutsideChip(d.missionControl?.now)}
|
|
5998
6925
|
<span class="section-subtitle" style="margin-left:auto">${escapeHtml(countLabel)}</span>
|
|
5999
6926
|
</div>
|
|
6000
6927
|
<div class="panel-scroll-body">${body}</div>
|
|
@@ -6092,7 +7019,9 @@ function flightLogFingerprint(d) {
|
|
|
6092
7019
|
)
|
|
6093
7020
|
.join('\n')
|
|
6094
7021
|
: '';
|
|
6095
|
-
|
|
7022
|
+
// Busy-outside-plan header chip must re-render the card on flip.
|
|
7023
|
+
const busy = (d.missionControl?.now ?? d.now)?.busyOutsidePlan?.active === true;
|
|
7024
|
+
return `${current}\0${past}\0${warnings}\0${openTriages}\0${busy ? 'busy' : ''}`;
|
|
6096
7025
|
}
|
|
6097
7026
|
|
|
6098
7027
|
// Step progress bar: one segment per plan step (completed / current / remaining).
|
|
@@ -6228,6 +7157,14 @@ function renderNowStepper(now, handoff, status, meta) {
|
|
|
6228
7157
|
`;
|
|
6229
7158
|
}
|
|
6230
7159
|
|
|
7160
|
+
// Live "busy outside the plan" header chip (Current mission + Flight Log).
|
|
7161
|
+
// Text-only on the advice-family blue tokens; not a Flight Log entry, so the
|
|
7162
|
+
// Gaps voice, kind, and NOW/Earlier/All clear label contracts stay untouched.
|
|
7163
|
+
function renderBusyOutsideChip(now) {
|
|
7164
|
+
if (!now?.busyOutsidePlan?.active) return '';
|
|
7165
|
+
return `<span class="mc-busy-chip" role="status" aria-label="Agents working outside the active plan" title="Run-loop activity outside the active plan">Busy \u00b7 outside plan</span>`;
|
|
7166
|
+
}
|
|
7167
|
+
|
|
6231
7168
|
function renderNowExecutionPanel(d, nowChanged) {
|
|
6232
7169
|
const now = d.missionControl?.now || null;
|
|
6233
7170
|
const handoff = d.system?.handoff || null;
|
|
@@ -6246,6 +7183,7 @@ function renderNowExecutionPanel(d, nowChanged) {
|
|
|
6246
7183
|
const updatedSource = now?.modifiedAt || handoff?.lastUpdated || null;
|
|
6247
7184
|
|
|
6248
7185
|
const statusClass = `now-status now-status-${meta.key}${meta.live ? ' now-status-live' : ''}`;
|
|
7186
|
+
const busyChip = renderBusyOutsideChip(now);
|
|
6249
7187
|
|
|
6250
7188
|
// True idle: no HANDOFF plan reference (or non-mission status). Reserve empty IDLE for that case.
|
|
6251
7189
|
if (!showMission) {
|
|
@@ -6258,6 +7196,7 @@ function renderNowExecutionPanel(d, nowChanged) {
|
|
|
6258
7196
|
<span class="now-status-mark" aria-hidden="true">${meta.mark}</span>
|
|
6259
7197
|
${escapeHtml(meta.label)}
|
|
6260
7198
|
</span>
|
|
7199
|
+
${busyChip}
|
|
6261
7200
|
</div>
|
|
6262
7201
|
<div class="panel-scroll-body">
|
|
6263
7202
|
${renderEmptyStateCta({
|
|
@@ -6289,6 +7228,7 @@ function renderNowExecutionPanel(d, nowChanged) {
|
|
|
6289
7228
|
<span class="now-status-mark" aria-hidden="true">${meta.mark}</span>
|
|
6290
7229
|
${escapeHtml(meta.label)}
|
|
6291
7230
|
</span>
|
|
7231
|
+
${busyChip}
|
|
6292
7232
|
</div>
|
|
6293
7233
|
<div class="panel-scroll-body">
|
|
6294
7234
|
<div class="now-plan-name">${escapeHtml(planName)}</div>
|
|
@@ -6465,14 +7405,7 @@ function renderUnsafe() {
|
|
|
6465
7405
|
gitDot.className = 'dot dot-green';
|
|
6466
7406
|
}
|
|
6467
7407
|
|
|
6468
|
-
// Terminals / processes
|
|
6469
|
-
const termDot = document.getElementById('navTerminalsDot');
|
|
6470
|
-
termDot.className = `dot dot-${d.terminals.length > 0 ? 'blue' : 'gray'}`;
|
|
6471
|
-
|
|
6472
|
-
const procDot = document.getElementById('navProcessesDot');
|
|
6473
|
-
if (procDot) {
|
|
6474
|
-
procDot.className = `dot dot-${d.processes.length > 0 ? 'green' : 'gray'}`;
|
|
6475
|
-
}
|
|
7408
|
+
// Terminals / processes nav items carry count badges only (no decorative dots).
|
|
6476
7409
|
|
|
6477
7410
|
// Build full HTML string once
|
|
6478
7411
|
const parts = [];
|
|
@@ -6520,15 +7453,42 @@ function renderUnsafe() {
|
|
|
6520
7453
|
const info = semanticEventInfo(ev.kind, ev.refs?.commitType);
|
|
6521
7454
|
const gloss = info.gloss || info.tag || ev.kind || 'event';
|
|
6522
7455
|
const staggerIdx = Math.min(idx, 5);
|
|
7456
|
+
const actor = crewEventActor(ev);
|
|
7457
|
+
const initials = agentInitials(actor);
|
|
6523
7458
|
const actionAttrs = activityTargetAttributes(ev, 'monitor-activity', { kindGloss: gloss });
|
|
6524
7459
|
const rowA11y = actionAttrs
|
|
6525
7460
|
? actionAttrs
|
|
6526
7461
|
: ` aria-label="${escapeAttr(`${gloss}: ${ev.label || ''}`)}"`;
|
|
7462
|
+
// Structured label spans: actor + verb stay fixed; later segments
|
|
7463
|
+
// shrink, and the low-signal plan filename ellipsises first.
|
|
7464
|
+
// Prefer refs.plan over sniffing the rendered string (truncation-safe).
|
|
7465
|
+
const planRef = ev.refs && ev.refs.plan ? String(ev.refs.plan) : '';
|
|
7466
|
+
const planBase = planRef.replace(/\.plan\.md$/i, '');
|
|
7467
|
+
const feedSegs = String(ev.label || '').split(' · ');
|
|
7468
|
+
const feedLabelHtml = feedSegs
|
|
7469
|
+
.map((seg, i) => {
|
|
7470
|
+
const isPlanSeg = Boolean(planRef) && (
|
|
7471
|
+
seg === planRef ||
|
|
7472
|
+
seg === planBase ||
|
|
7473
|
+
/\.plan\.md/i.test(seg)
|
|
7474
|
+
);
|
|
7475
|
+
const cls = i === 0
|
|
7476
|
+
? 'feed-seg feed-seg-actor'
|
|
7477
|
+
: i === 1
|
|
7478
|
+
? 'feed-seg feed-seg-verb'
|
|
7479
|
+
: isPlanSeg
|
|
7480
|
+
? 'feed-seg feed-seg-plan'
|
|
7481
|
+
: 'feed-seg feed-seg-mid';
|
|
7482
|
+
return `<span class="${cls}">${escapeHtml(seg)}</span>`;
|
|
7483
|
+
})
|
|
7484
|
+
.join('<span class="feed-sep" aria-hidden="true"> · </span>');
|
|
7485
|
+
const feedTitle = ev.labelFull || ev.label || '';
|
|
6527
7486
|
return `
|
|
6528
7487
|
<div class="monitor-row stagger-fade stagger-${staggerIdx}"${rowA11y}>
|
|
7488
|
+
<span class="monitor-row-avatar" title="${escapeAttr(actor)}" aria-hidden="true">${escapeHtml(initials)}</span>
|
|
6529
7489
|
<span class="monitor-row-chip" style="background:${info.bg};color:${info.color}" title="${escapeAttr(gloss)}" aria-hidden="true"><span class="monitor-row-icon">${info.icon}</span></span>
|
|
6530
|
-
<span class="feed-label"
|
|
6531
|
-
<span class="feed-time">${
|
|
7490
|
+
<span class="feed-label" title="${escapeAttr(feedTitle)}">${feedLabelHtml}</span>
|
|
7491
|
+
<span class="feed-time">${crewEventTime(ev, info)}</span>
|
|
6532
7492
|
</div>`;
|
|
6533
7493
|
}).join('')}
|
|
6534
7494
|
</div>
|
|
@@ -6541,7 +7501,7 @@ function renderUnsafe() {
|
|
|
6541
7501
|
parts.push(`
|
|
6542
7502
|
<div class="content-section" id="section-plans">
|
|
6543
7503
|
<div class="section-title">
|
|
6544
|
-
|
|
7504
|
+
Plans
|
|
6545
7505
|
<span class="section-subtitle">${d.plans.length} total</span>
|
|
6546
7506
|
</div>
|
|
6547
7507
|
${renderPlansAccordion(d)}
|
|
@@ -6549,13 +7509,18 @@ function renderUnsafe() {
|
|
|
6549
7509
|
`);
|
|
6550
7510
|
|
|
6551
7511
|
// ===== Agents =====
|
|
7512
|
+
const agents = d.agents || [];
|
|
7513
|
+
const agentLockSvg = `<svg width="11" height="11" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><rect x="3" y="7" width="10" height="7" rx="1.5"/><path d="M5.5 7V5a2.5 2.5 0 0 1 5 0v2"/></svg>`;
|
|
7514
|
+
const agentCreatePrompt = 'Create a new agent definition under .cursor/agents/ (markdown file with a description plus the behavior body). Ask me for the name and behavior first.';
|
|
7515
|
+
const agentEditableCount = agents.filter(a => a.kitManaged !== true).length;
|
|
6552
7516
|
parts.push(`
|
|
6553
7517
|
<div class="content-section" id="section-agents">
|
|
6554
7518
|
<div class="section-title">
|
|
6555
|
-
|
|
6556
|
-
<span class="section-subtitle">${
|
|
7519
|
+
Agents
|
|
7520
|
+
<span class="section-subtitle">${agents.length} agents · ${agentEditableCount} editable</span>
|
|
7521
|
+
<button type="button" class="git-action-btn primary agent-create-btn" data-focus-key="cta-agent-create" onclick="${copyForPasteHandler(agentCreatePrompt, 'create prompt', 'chatInput')}" title="${escapeAttr(copyActionTitle('create prompt', 'chatInput'))}">Copy create prompt</button>
|
|
6557
7522
|
</div>
|
|
6558
|
-
${
|
|
7523
|
+
${agents.length === 0
|
|
6559
7524
|
? renderEmptyStateCta({
|
|
6560
7525
|
headline: 'No agents aboard',
|
|
6561
7526
|
supportHtml: 'Add <code>.md</code> files under <code>.cursor/agents/</code>.',
|
|
@@ -6568,34 +7533,49 @@ function renderUnsafe() {
|
|
|
6568
7533
|
},
|
|
6569
7534
|
})
|
|
6570
7535
|
: ''}
|
|
6571
|
-
<div class="agent-
|
|
6572
|
-
${
|
|
6573
|
-
const
|
|
6574
|
-
const
|
|
6575
|
-
|
|
7536
|
+
<div class="agent-grid">
|
|
7537
|
+
${agents.map(a => {
|
|
7538
|
+
const kitManaged = a.kitManaged === true;
|
|
7539
|
+
const lockBadge = kitManaged
|
|
7540
|
+
? `<span class="agent-lock" title="Kit managed: owned by kit updates, read-only here">${agentLockSvg}Kit managed</span>`
|
|
7541
|
+
: '';
|
|
7542
|
+
const agentPath = a.path || `.cursor/agents/${a.file || `${a.id}.md`}`;
|
|
7543
|
+
const usePrompt = `Use this agent definition when it matches the task:\n${agentPath}`;
|
|
7544
|
+
const useBtn = `<button type="button" class="git-action-btn primary" onclick="${copyForPasteHandler(usePrompt, 'use prompt', 'chatInput')}" title="${escapeAttr(copyActionTitle('use prompt', 'chatInput'))}">Copy use prompt</button>`;
|
|
7545
|
+
const editPrompt = `Edit this agent definition file (markdown body):\n${agentPath}`;
|
|
7546
|
+
const editBtn = kitManaged
|
|
7547
|
+
? ''
|
|
7548
|
+
: `<button type="button" class="git-action-btn" onclick="${copyForPasteHandler(editPrompt, 'edit prompt', 'chatInput')}" title="${escapeAttr(copyActionTitle('edit prompt', 'chatInput'))}">Copy edit prompt</button>`;
|
|
7549
|
+
const deletePrompt = `Delete this agent definition file after confirming with me:\n${agentPath}`;
|
|
7550
|
+
const deleteBtn = kitManaged
|
|
7551
|
+
? ''
|
|
7552
|
+
: `<button type="button" class="git-action-btn" onclick="${copyForPasteHandler(deletePrompt, 'delete prompt', 'chatInput')}" title="${escapeAttr(copyActionTitle('delete prompt', 'chatInput'))}">Copy delete prompt</button>`;
|
|
6576
7553
|
return `
|
|
6577
|
-
|
|
6578
|
-
|
|
6579
|
-
<span
|
|
6580
|
-
|
|
6581
|
-
<span style="font-size:10px;color:var(--text-muted);transition:transform 0.2s" id="agent-arrow-${escapeAttr(agentId)}">\u25bc</span>
|
|
6582
|
-
</div>
|
|
6583
|
-
<div class="agent-details" id="agent-details-${escapeAttr(agentId)}">
|
|
6584
|
-
<p>${escapeHtml(a.description || 'No description available.')}</p>
|
|
6585
|
-
<div class="agent-path">${escapeHtml(a.path || 'Path not available')}</div>
|
|
7554
|
+
<div class="agent-card"${kitManaged ? ' data-kit-managed="true"' : ''}>
|
|
7555
|
+
<div class="agent-card-head">
|
|
7556
|
+
<span class="agent-name"><span class="agent-monogram" aria-hidden="true">${escapeHtml(agentInitials(a.id))}</span>${escapeHtml(a.id)}</span>
|
|
7557
|
+
${lockBadge}
|
|
6586
7558
|
</div>
|
|
6587
|
-
|
|
7559
|
+
<div class="agent-desc">${escapeHtml(a.description || 'No description available.')}</div>
|
|
7560
|
+
<div class="agent-file">${escapeHtml(agentPath)}</div>
|
|
7561
|
+
<div class="agent-actions">${useBtn}${editBtn}${deleteBtn}</div>
|
|
7562
|
+
</div>
|
|
7563
|
+
`;
|
|
6588
7564
|
}).join('')}
|
|
6589
7565
|
</div>
|
|
6590
7566
|
</div>
|
|
6591
7567
|
`);
|
|
6592
7568
|
|
|
6593
7569
|
// ===== Commands =====
|
|
7570
|
+
const commandLockSvg = `<svg width="11" height="11" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><rect x="3" y="7" width="10" height="7" rx="1.5"/><path d="M5.5 7V5a2.5 2.5 0 0 1 5 0v2"/></svg>`;
|
|
7571
|
+
const commandCreatePrompt = 'Create a new slash command under .cursor/commands/ (plain markdown body, no frontmatter). Ask me for the name and behavior first.';
|
|
7572
|
+
const commandEditableCount = d.commands.filter(c => c.kitManaged !== true).length;
|
|
6594
7573
|
parts.push(`
|
|
6595
7574
|
<div class="content-section" id="section-commands">
|
|
6596
7575
|
<div class="section-title">
|
|
6597
|
-
|
|
6598
|
-
<span class="section-subtitle">${d.commands.length} slash commands</span>
|
|
7576
|
+
Commands
|
|
7577
|
+
<span class="section-subtitle">${d.commands.length} slash commands · ${commandEditableCount} editable</span>
|
|
7578
|
+
<button type="button" class="git-action-btn primary command-create-btn" data-focus-key="cta-command-create" onclick="${copyForPasteHandler(commandCreatePrompt, 'create prompt', 'chatInput')}" title="${escapeAttr(copyActionTitle('create prompt', 'chatInput'))}">Copy create prompt</button>
|
|
6599
7579
|
</div>
|
|
6600
7580
|
${d.commands.length === 0
|
|
6601
7581
|
? renderEmptyStateCta({
|
|
@@ -6614,10 +7594,28 @@ function renderUnsafe() {
|
|
|
6614
7594
|
<div class="command-grid">
|
|
6615
7595
|
${d.commands.map(c => {
|
|
6616
7596
|
const slash = `/${c.id}`;
|
|
7597
|
+
const cmdPath = c.path || `.cursor/commands/${c.file || `${c.id}.md`}`;
|
|
7598
|
+
const kitManaged = c.kitManaged === true;
|
|
7599
|
+
const lockBadge = kitManaged
|
|
7600
|
+
? `<span class="command-lock" title="Kit managed: owned by kit updates, read-only here">${commandLockSvg}Kit managed</span>`
|
|
7601
|
+
: '';
|
|
7602
|
+
const runBtn = `<button type="button" class="git-action-btn primary" onclick="${copyForPasteHandler(slash, slash, 'chatInput')}" title="${escapeAttr(copyActionTitle(slash, 'chatInput'))}">Copy run command</button>`;
|
|
7603
|
+
const editPrompt = `Edit this slash command file (plain markdown body, no frontmatter):\n${cmdPath}`;
|
|
7604
|
+
const editBtn = kitManaged
|
|
7605
|
+
? ''
|
|
7606
|
+
: `<button type="button" class="git-action-btn" onclick="${copyForPasteHandler(editPrompt, 'edit prompt', 'chatInput')}" title="${escapeAttr(copyActionTitle('edit prompt', 'chatInput'))}">Copy edit prompt</button>`;
|
|
7607
|
+
const deletePrompt = `Delete this slash command file after confirming with me:\n${cmdPath}`;
|
|
7608
|
+
const deleteBtn = kitManaged
|
|
7609
|
+
? ''
|
|
7610
|
+
: `<button type="button" class="git-action-btn" onclick="${copyForPasteHandler(deletePrompt, 'delete prompt', 'chatInput')}" title="${escapeAttr(copyActionTitle('delete prompt', 'chatInput'))}">Copy delete prompt</button>`;
|
|
6617
7611
|
return `
|
|
6618
|
-
<div class="command-
|
|
6619
|
-
<
|
|
6620
|
-
|
|
7612
|
+
<div class="command-card"${kitManaged ? ' data-kit-managed="true"' : ''}>
|
|
7613
|
+
<div class="command-card-head">
|
|
7614
|
+
<span class="command-name">${escapeHtml(slash)}</span>
|
|
7615
|
+
${lockBadge}
|
|
7616
|
+
</div>
|
|
7617
|
+
<div class="command-file">${escapeHtml(cmdPath)}</div>
|
|
7618
|
+
<div class="command-actions">${runBtn}${editBtn}${deleteBtn}</div>
|
|
6621
7619
|
</div>
|
|
6622
7620
|
`;
|
|
6623
7621
|
}).join('')}
|
|
@@ -6631,7 +7629,7 @@ function renderUnsafe() {
|
|
|
6631
7629
|
const passing = checks.filter(c => c.ok).length;
|
|
6632
7630
|
const isSnapshotError = healthStatus === 'error' && checks.length === 0;
|
|
6633
7631
|
const isOffline = !isSnapshotError && checks.length === 0;
|
|
6634
|
-
const presenceTone = healthStatus
|
|
7632
|
+
const presenceTone = healthSeverityChrome(healthStatus).tone;
|
|
6635
7633
|
const presencePulse = healthStatus !== 'ok' ? ' dot-pulse' : '';
|
|
6636
7634
|
const presenceLabel = isSnapshotError
|
|
6637
7635
|
? 'Snapshot error'
|
|
@@ -6679,44 +7677,73 @@ function renderUnsafe() {
|
|
|
6679
7677
|
},
|
|
6680
7678
|
});
|
|
6681
7679
|
} else {
|
|
6682
|
-
|
|
6683
|
-
|
|
6684
|
-
|
|
6685
|
-
|
|
6686
|
-
|
|
6687
|
-
|
|
6688
|
-
|
|
6689
|
-
|
|
6690
|
-
|
|
6691
|
-
|
|
6692
|
-
|
|
6693
|
-
|
|
6694
|
-
|
|
6695
|
-
|
|
6696
|
-
|
|
6697
|
-
|
|
6698
|
-
|
|
7680
|
+
const checkId = (c) => c.id || c.label || 'check';
|
|
7681
|
+
const renderCheckCard = (c) => {
|
|
7682
|
+
const id = checkId(c);
|
|
7683
|
+
const meta = HEALTH_CHECK_META[id] || {
|
|
7684
|
+
okDetail: 'Check is passing.',
|
|
7685
|
+
failDetail: 'Check is failing. Review configuration for this item.',
|
|
7686
|
+
autofix: null,
|
|
7687
|
+
};
|
|
7688
|
+
const sev = healthCheckSeverity(c, healthStatus);
|
|
7689
|
+
const detail = c.ok ? meta.okDetail : meta.failDetail;
|
|
7690
|
+
const autofix = !c.ok && meta.autofix ? meta.autofix : null;
|
|
7691
|
+
const fixPrompt = [`Fix this failing health check: ${c.label || id}`, detail].join('\n');
|
|
7692
|
+
const fixBtn = `<button type="button" class="health-autofix-btn" data-focus-key="health-fix-${escapeAttr(id)}" onclick="event.stopPropagation();${copyForPasteHandler(fixPrompt, 'fix prompt', 'chatInput')}" title="${escapeAttr(copyActionTitle('fix prompt', 'chatInput'))}">Copy fix prompt</button>`;
|
|
7693
|
+
const autofixBtn = autofix
|
|
7694
|
+
? `<button type="button" class="health-autofix-btn" data-focus-key="health-autofix-${escapeAttr(id)}" onclick="event.stopPropagation();${copyForPasteHandler(autofix.text, autofix.subject, autofix.destination)}" title="${escapeAttr(copyActionTitle(autofix.subject, autofix.destination))}">${escapeHtml(autofix.label || 'Autofix')}</button>`
|
|
7695
|
+
: '';
|
|
7696
|
+
const actions = c.ok
|
|
7697
|
+
? `<span class="health-detail-copy" style="margin:0">No action needed.</span>`
|
|
7698
|
+
: `${fixBtn}${autofixBtn}`;
|
|
7699
|
+
return `
|
|
7700
|
+
<div class="health-card" role="listitem" id="health-card-${escapeAttr(id)}" data-check-id="${escapeAttr(id)}" data-severity="${escapeAttr(sev)}">
|
|
7701
|
+
<button type="button" class="health-item" aria-expanded="false" aria-controls="health-detail-${escapeAttr(id)}" aria-label="${escapeAttr(c.label)}: ${escapeAttr(healthSeverityLabel(sev))}" onclick="toggleHealthCheck('${escapeJsString(id)}')" onkeydown="healthCheckKeydown(event, '${escapeJsString(id)}')">
|
|
7702
|
+
<span class="dot ${healthDotClass(sev)}"></span>
|
|
7703
|
+
<span class="health-item-label">${escapeHtml(c.label)}</span>
|
|
7704
|
+
<span class="health-item-sev" data-sev="${escapeAttr(sev)}">${escapeHtml(healthSeverityLabel(sev))}</span>
|
|
7705
|
+
<span class="health-item-chevron" aria-hidden="true">▾</span>
|
|
7706
|
+
</button>
|
|
7707
|
+
<div class="health-detail" id="health-detail-${escapeAttr(id)}">
|
|
7708
|
+
<p class="health-detail-copy">${escapeHtml(detail)}</p>
|
|
7709
|
+
<div class="health-detail-actions">${actions}</div>
|
|
7710
|
+
</div>
|
|
7711
|
+
</div>`;
|
|
7712
|
+
};
|
|
7713
|
+
const grouped = HEALTH_VITAL_GROUPS.map(g => ({
|
|
7714
|
+
label: g.label,
|
|
7715
|
+
items: g.checks.map(id => checks.find(c => checkId(c) === id)).filter(Boolean),
|
|
7716
|
+
})).filter(g => g.items.length > 0);
|
|
7717
|
+
const knownIds = new Set(HEALTH_VITAL_GROUPS.flatMap(g => g.checks));
|
|
7718
|
+
const others = checks.filter(c => !knownIds.has(checkId(c)));
|
|
7719
|
+
if (others.length > 0) grouped.push({ label: 'Other checks', items: others });
|
|
7720
|
+
const vitalsStrip = `
|
|
7721
|
+
<div class="health-vitals" role="list" aria-label="Vital systems">
|
|
7722
|
+
${grouped.map(g => {
|
|
7723
|
+
const total = g.items.length;
|
|
7724
|
+
const okCount = g.items.filter(c => c.ok).length;
|
|
7725
|
+
const state = okCount === total ? 'pass' : 'attention';
|
|
6699
7726
|
return `
|
|
6700
|
-
<div class="health-card" role="listitem"
|
|
6701
|
-
<
|
|
6702
|
-
|
|
6703
|
-
|
|
6704
|
-
<span class="health-item-sev">${escapeHtml(healthSeverityLabel(sev))}</span>
|
|
6705
|
-
<span class="health-item-chevron" aria-hidden="true">▾</span>
|
|
6706
|
-
</button>
|
|
6707
|
-
<div class="health-detail" id="health-detail-${escapeAttr(id)}">
|
|
6708
|
-
<p class="health-detail-copy">${escapeHtml(detail)}</p>
|
|
6709
|
-
<div class="health-detail-actions">${autofixBtn}</div>
|
|
6710
|
-
</div>
|
|
7727
|
+
<div class="health-vital-card" role="listitem" data-state="${state}">
|
|
7728
|
+
<span class="health-vital-name">${escapeHtml(g.label)}</span>
|
|
7729
|
+
<span class="health-vital-score">${okCount}/${total} passing</span>
|
|
7730
|
+
<span class="health-vital-state"><span class="dot ${state === 'pass' ? 'dot-green' : 'dot-red'}"></span>${state === 'pass' ? 'Pass' : 'Attention'}</span>
|
|
6711
7731
|
</div>`;
|
|
6712
7732
|
}).join('')}
|
|
6713
7733
|
</div>`;
|
|
7734
|
+
healthBody = `${vitalsStrip}
|
|
7735
|
+
${grouped.map(g => `
|
|
7736
|
+
<section class="health-group" aria-label="${escapeAttr(g.label)}">
|
|
7737
|
+
<div class="health-group-title">${escapeHtml(g.label)}</div>
|
|
7738
|
+
<div class="health-grid" role="list">
|
|
7739
|
+
${g.items.map(renderCheckCard).join('')}
|
|
7740
|
+
</div>
|
|
7741
|
+
</section>`).join('')}`;
|
|
6714
7742
|
}
|
|
6715
7743
|
|
|
6716
7744
|
parts.push(`
|
|
6717
7745
|
<div class="content-section" id="section-health">
|
|
6718
7746
|
<div class="section-title">
|
|
6719
|
-
<span class="dot dot-${presenceTone}${healthStatus !== 'ok' ? ' dot-pulse' : ''}"></span>
|
|
6720
7747
|
Health
|
|
6721
7748
|
<span class="section-subtitle">${escapeHtml(subtitle)}</span>
|
|
6722
7749
|
</div>
|
|
@@ -6724,7 +7751,6 @@ function renderUnsafe() {
|
|
|
6724
7751
|
<div class="healthcenter-presence" role="status" aria-live="polite">
|
|
6725
7752
|
<span class="dot dot-${presenceTone}${presencePulse}"></span>
|
|
6726
7753
|
<span>${escapeHtml(presenceLabel)}</span>
|
|
6727
|
-
<span>· same seven checks · copy-only Autofix</span>
|
|
6728
7754
|
</div>
|
|
6729
7755
|
${healthBody}
|
|
6730
7756
|
</div>
|
|
@@ -6735,9 +7761,8 @@ function renderUnsafe() {
|
|
|
6735
7761
|
parts.push(`
|
|
6736
7762
|
<div class="content-section" id="section-git">
|
|
6737
7763
|
<div class="section-title">
|
|
6738
|
-
<span class="dot dot-${d.git?.dirty ? 'yellow' : 'green'}"></span>
|
|
6739
7764
|
Git
|
|
6740
|
-
<span class="section-subtitle">${escapeHtml(d.git?.branch || '\u2014')}</span>
|
|
7765
|
+
<span class="section-subtitle">${escapeHtml(d.git?.branch || '\u2014')}${d.git?.branch ? (d.git?.dirty ? ` · ${d.git.dirtyCount} changed` : ' · clean') : ''}</span>
|
|
6741
7766
|
</div>
|
|
6742
7767
|
${!d.git?.branch
|
|
6743
7768
|
? renderEmptyStateCta({
|
|
@@ -6746,28 +7771,15 @@ function renderUnsafe() {
|
|
|
6746
7771
|
})
|
|
6747
7772
|
: ''}
|
|
6748
7773
|
<div class="card" style="${d.git?.branch ? '' : 'display:none'}">
|
|
7774
|
+
${renderGitHygieneHint(d.git)}
|
|
7775
|
+
${renderGitFlowCard(d.git)}
|
|
6749
7776
|
<div class="git-info">
|
|
6750
|
-
<div class="git-row">
|
|
6751
|
-
<span class="git-label">Branch</span>
|
|
6752
|
-
<span class="git-value">${escapeHtml(d.git?.branch || '\u2014')}</span>
|
|
6753
|
-
</div>
|
|
6754
|
-
<div class="git-row">
|
|
6755
|
-
<span class="git-label">Working tree</span>
|
|
6756
|
-
<span class="git-value">${d.git?.dirty ? `${d.git.dirtyCount} file${d.git.dirtyCount !== 1 ? 's' : ''} modified` : 'Clean'}</span>
|
|
6757
|
-
</div>
|
|
6758
7777
|
<div class="git-row">
|
|
6759
7778
|
<span class="git-label">Last commit</span>
|
|
6760
7779
|
<span class="git-value">${escapeHtml(d.git?.lastCommit || '\u2014')}</span>
|
|
6761
7780
|
</div>
|
|
6762
|
-
<div class="git-row">
|
|
6763
|
-
<span class="git-label">Ahead of origin/main</span>
|
|
6764
|
-
<span class="git-value">${d.git?.ahead ?? '\u2014'}</span>
|
|
6765
|
-
</div>
|
|
6766
|
-
<div class="git-row">
|
|
6767
|
-
<span class="git-label">Behind origin/main</span>
|
|
6768
|
-
<span class="git-value">${d.git?.behind ?? '\u2014'}</span>
|
|
6769
|
-
</div>
|
|
6770
7781
|
</div>
|
|
7782
|
+
${renderGitGraphCard(d.git)}
|
|
6771
7783
|
${d.git?.dirty ? renderGitFileList(d.git) : ''}
|
|
6772
7784
|
</div>
|
|
6773
7785
|
</div>
|
|
@@ -6775,49 +7787,79 @@ function renderUnsafe() {
|
|
|
6775
7787
|
|
|
6776
7788
|
// ===== Memory =====
|
|
6777
7789
|
const recentDecisions = d.memory?.recentDecisions || [];
|
|
7790
|
+
const recentErrors = d.memory?.recentErrors || [];
|
|
7791
|
+
const errorStats = d.memory?.errorStats || null;
|
|
6778
7792
|
parts.push(`
|
|
6779
7793
|
<div class="content-section" id="section-memory">
|
|
6780
7794
|
<div class="section-title">
|
|
6781
|
-
|
|
7795
|
+
Memory
|
|
6782
7796
|
<span class="section-subtitle">${d.memory.errors || 0} errors \u00b7 ${d.memory.decisions || 0} decisions</span>
|
|
6783
7797
|
</div>
|
|
6784
|
-
<div class="grid-
|
|
6785
|
-
<div class="
|
|
6786
|
-
<div class="
|
|
6787
|
-
|
|
6788
|
-
|
|
6789
|
-
</div>
|
|
6790
|
-
<div class="card-value">${d.memory.errors || 0}</div>
|
|
6791
|
-
<div class="card-subtitle">resolved errors in memory</div>
|
|
7798
|
+
<div class="memory-kpi-grid" aria-label="Error-o-meter: memory error statistics">
|
|
7799
|
+
<div class="memory-kpi">
|
|
7800
|
+
<div class="memory-kpi-label">Error-o-meter</div>
|
|
7801
|
+
<div class="memory-kpi-value">${errorStats ? errorStats.total : (d.memory.errors || 0)}</div>
|
|
7802
|
+
<div class="memory-kpi-sub">resolved errors recorded</div>
|
|
6792
7803
|
</div>
|
|
6793
|
-
<div class="
|
|
6794
|
-
<div class="
|
|
6795
|
-
|
|
6796
|
-
|
|
7804
|
+
<div class="memory-kpi">
|
|
7805
|
+
<div class="memory-kpi-label">Last 30 days</div>
|
|
7806
|
+
<div class="memory-kpi-value">${errorStats ? errorStats.last30d : 0}</div>
|
|
7807
|
+
<div class="memory-kpi-sub">new error entries</div>
|
|
7808
|
+
</div>
|
|
7809
|
+
<div class="memory-kpi">
|
|
7810
|
+
<div class="memory-kpi-label">Weekly rate</div>
|
|
7811
|
+
<div class="memory-kpi-value">${errorStats ? errorStats.weeklyRate : 0}</div>
|
|
7812
|
+
<div class="memory-kpi-sub">errors per week (30d avg)</div>
|
|
7813
|
+
</div>
|
|
7814
|
+
<div class="memory-kpi">
|
|
7815
|
+
<div class="memory-kpi-label">Top tags</div>
|
|
7816
|
+
<div class="memory-tag-row">
|
|
7817
|
+
${errorStats && errorStats.topTags.length > 0
|
|
7818
|
+
? errorStats.topTags.map(t => `<span class="memory-tag" title="${escapeAttr(`${t.count} entries tagged ${t.tag}`)}">${escapeHtml(t.tag)} ×${t.count}</span>`).join('')
|
|
7819
|
+
: '<span class="memory-kpi-sub">no tags recorded</span>'}
|
|
6797
7820
|
</div>
|
|
6798
|
-
<div class="card-value">${d.memory.decisions || 0}</div>
|
|
6799
|
-
<div class="card-subtitle">architectural decisions</div>
|
|
6800
7821
|
</div>
|
|
6801
7822
|
</div>
|
|
6802
|
-
|
|
6803
|
-
<div class="
|
|
6804
|
-
<div class="
|
|
6805
|
-
<span class="
|
|
7823
|
+
<div class="grid-2">
|
|
7824
|
+
<div class="memory-panel">
|
|
7825
|
+
<div class="memory-panel-header">
|
|
7826
|
+
<span class="memory-panel-icon memory-panel-icon-green" aria-hidden="true">✓</span>
|
|
7827
|
+
<span class="memory-panel-title">Healthy memory</span>
|
|
7828
|
+
<span class="memory-panel-sub">${d.memory.decisions || 0} decisions recorded</span>
|
|
6806
7829
|
</div>
|
|
6807
|
-
|
|
6808
|
-
|
|
6809
|
-
|
|
6810
|
-
|
|
6811
|
-
|
|
6812
|
-
|
|
6813
|
-
<
|
|
6814
|
-
|
|
6815
|
-
|
|
6816
|
-
|
|
6817
|
-
|
|
7830
|
+
${recentDecisions.length > 0 ? `
|
|
7831
|
+
<div class="memory-list">
|
|
7832
|
+
${recentDecisions.map(dd => {
|
|
7833
|
+
const memPath = dd.path || `.cursor/memory/decisions/${dd.id || dd}.md`;
|
|
7834
|
+
const memId = dd.id || dd;
|
|
7835
|
+
return `
|
|
7836
|
+
<div class="memory-item" role="button" tabindex="0" aria-label="Copy path for ${escapeAttr(memId)}" onclick="${copyRepoPathHandler(memPath)}" title="${escapeAttr(pathActionTitle(memPath))}">
|
|
7837
|
+
<span>${escapeHtml(memId)}</span>
|
|
7838
|
+
</div>
|
|
7839
|
+
`;
|
|
7840
|
+
}).join('')}
|
|
7841
|
+
</div>
|
|
7842
|
+
` : renderEmptyStateCta({
|
|
7843
|
+
headline: 'No decisions yet',
|
|
7844
|
+
support: 'Accepted tradeoffs land in .cursor/memory/decisions/ as ADR-lite entries.',
|
|
7845
|
+
compact: true,
|
|
7846
|
+
})}
|
|
7847
|
+
</div>
|
|
7848
|
+
<div class="memory-panel">
|
|
7849
|
+
<div class="memory-panel-header">
|
|
7850
|
+
<span class="memory-panel-icon memory-panel-icon-red" aria-hidden="true">⚠</span>
|
|
7851
|
+
<span class="memory-panel-title">Recent errors</span>
|
|
7852
|
+
<span class="memory-panel-sub">${recentErrors.length} of ${d.memory.errors || 0}</span>
|
|
6818
7853
|
</div>
|
|
7854
|
+
${recentErrors.length > 0
|
|
7855
|
+
? recentErrors.map((entry, idx) => renderMemoryErrorRow(entry, idx)).join('')
|
|
7856
|
+
: renderEmptyStateCta({
|
|
7857
|
+
headline: 'No errors recorded',
|
|
7858
|
+
support: 'Investigated errors with cause and fix land in .cursor/memory/errors/.',
|
|
7859
|
+
compact: true,
|
|
7860
|
+
})}
|
|
6819
7861
|
</div>
|
|
6820
|
-
|
|
7862
|
+
</div>
|
|
6821
7863
|
</div>
|
|
6822
7864
|
`);
|
|
6823
7865
|
|
|
@@ -6826,7 +7868,6 @@ function renderUnsafe() {
|
|
|
6826
7868
|
parts.push(`
|
|
6827
7869
|
<div class="content-section" id="section-terminals">
|
|
6828
7870
|
<div class="section-title">
|
|
6829
|
-
<span class="dot dot-${d.terminals.length > 0 ? 'blue dot-pulse' : 'gray'}"></span>
|
|
6830
7871
|
Terminals
|
|
6831
7872
|
<span class="section-subtitle">${d.terminals.length} active</span>
|
|
6832
7873
|
</div>
|
|
@@ -6872,55 +7913,40 @@ function renderUnsafe() {
|
|
|
6872
7913
|
parts.push(`
|
|
6873
7914
|
<div class="content-section" id="section-processes">
|
|
6874
7915
|
<div class="section-title">
|
|
6875
|
-
<span class="dot dot-${processes.length > 0 ? 'green' : 'gray'}"></span>
|
|
6876
7916
|
Processes
|
|
6877
7917
|
<span class="section-subtitle">${processes.length} running</span>
|
|
6878
7918
|
</div>
|
|
7919
|
+
<div class="processes-note">
|
|
7920
|
+
<span><strong>Live ps snapshot.</strong> Agent chats spawned inside the IDE do not appear here; the Crew monitor on the Overview tab tracks that activity.</span>
|
|
7921
|
+
</div>
|
|
6879
7922
|
${processes.length === 0
|
|
6880
7923
|
? renderEmptyStateCta({
|
|
6881
7924
|
headline: 'All quiet',
|
|
6882
|
-
support: 'No relevant processes running.',
|
|
7925
|
+
support: 'No relevant processes running. Node, git, and dashboard processes appear here while active.',
|
|
6883
7926
|
})
|
|
6884
7927
|
: `
|
|
6885
|
-
<div class="
|
|
6886
|
-
|
|
6887
|
-
|
|
6888
|
-
|
|
6889
|
-
|
|
6890
|
-
<
|
|
6891
|
-
|
|
6892
|
-
|
|
6893
|
-
|
|
6894
|
-
|
|
6895
|
-
|
|
6896
|
-
|
|
6897
|
-
|
|
6898
|
-
|
|
6899
|
-
|
|
6900
|
-
|
|
6901
|
-
|
|
6902
|
-
|
|
6903
|
-
|
|
6904
|
-
|
|
6905
|
-
|
|
6906
|
-
|
|
6907
|
-
<span class="process-label">
|
|
6908
|
-
<span class="dot ${dotClass}"></span>
|
|
6909
|
-
<span>${escapeHtml(p.label || 'other')}</span>
|
|
6910
|
-
</span>
|
|
6911
|
-
</td>
|
|
6912
|
-
<td style="color:var(--text-muted)">${escapeHtml(pidStr)}</td>
|
|
6913
|
-
<td>${escapeHtml(p.cpu || '\u2014')}</td>
|
|
6914
|
-
<td>${escapeHtml(p.mem || '\u2014')}</td>
|
|
6915
|
-
<td style="color:var(--text-muted);max-width:300px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap" title="${escapeAttr(p.command || '\u2014')}">${escapeHtml(p.command || '\u2014')}</td>
|
|
6916
|
-
<td class="process-actions">
|
|
6917
|
-
<button type="button" class="process-copy-btn" onclick="event.stopPropagation();${copyForPasteHandler(pidStr, `PID ${pidStr}`, 'terminal')}" title="${escapeAttr(copyActionTitle(`PID ${pidStr}`, 'terminal'))}">Copy PID</button>
|
|
6918
|
-
</td>
|
|
6919
|
-
</tr>
|
|
6920
|
-
`;
|
|
6921
|
-
}).join('')}
|
|
6922
|
-
</tbody>
|
|
6923
|
-
</table>
|
|
7928
|
+
<div class="process-list">
|
|
7929
|
+
${processes.map(p => {
|
|
7930
|
+
const pidStr = String(p.pid ?? '');
|
|
7931
|
+
return `
|
|
7932
|
+
<div class="process-card">
|
|
7933
|
+
<div class="process-card-head">
|
|
7934
|
+
<span class="process-label-badge">${escapeHtml(p.label || 'other')}</span>
|
|
7935
|
+
<span class="process-actions">
|
|
7936
|
+
<button type="button" class="process-copy-btn" onclick="event.stopPropagation();${copyForPasteHandler(pidStr, `PID ${pidStr}`, 'terminal')}" title="${escapeAttr(copyActionTitle(`PID ${pidStr}`, 'terminal'))}">Copy PID</button>
|
|
7937
|
+
</span>
|
|
7938
|
+
</div>
|
|
7939
|
+
${p.description ? `<div class="process-desc">${escapeHtml(p.description)}</div>` : ''}
|
|
7940
|
+
<div class="process-meta">
|
|
7941
|
+
<span>PID ${escapeHtml(pidStr)}</span>
|
|
7942
|
+
<span>CPU ${escapeHtml(p.cpu || '\u2014')}%</span>
|
|
7943
|
+
<span>MEM ${escapeHtml(p.mem || '\u2014')}%</span>
|
|
7944
|
+
${p.etime ? `<span>up ${escapeHtml(p.etime)}</span>` : ''}
|
|
7945
|
+
</div>
|
|
7946
|
+
<div class="process-cmd" title="${escapeAttr(p.command || '\u2014')}">${escapeHtml(p.command || '\u2014')}</div>
|
|
7947
|
+
</div>
|
|
7948
|
+
`;
|
|
7949
|
+
}).join('')}
|
|
6924
7950
|
</div>
|
|
6925
7951
|
`}
|
|
6926
7952
|
</div>
|
|
@@ -6928,19 +7954,15 @@ function renderUnsafe() {
|
|
|
6928
7954
|
|
|
6929
7955
|
// ===== Skills =====
|
|
6930
7956
|
const skills = d.skills || [];
|
|
6931
|
-
const
|
|
6932
|
-
skills
|
|
6933
|
-
|
|
6934
|
-
if (!categories[cat]) categories[cat] = [];
|
|
6935
|
-
categories[cat].push(s);
|
|
6936
|
-
});
|
|
6937
|
-
const catColors = ['#a855f7','#3b82f6','#22c55e','#eab308','#f97316','#06b6d4','#ec4899','#ef4444'];
|
|
6938
|
-
let catIdx = 0;
|
|
7957
|
+
const skillLockSvg = `<svg width="11" height="11" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><rect x="3" y="7" width="10" height="7" rx="1.5"/><path d="M5.5 7V5a2.5 2.5 0 0 1 5 0v2"/></svg>`;
|
|
7958
|
+
const skillCreatePrompt = 'Create a new Agent Skill under .cursor/skills/ (directory with a SKILL.md markdown body). Ask me for the name and behavior first.';
|
|
7959
|
+
const skillEditableCount = skills.filter(s => s.kitManaged !== true).length;
|
|
6939
7960
|
parts.push(`
|
|
6940
7961
|
<div class="content-section" id="section-skills">
|
|
6941
7962
|
<div class="section-title">
|
|
6942
|
-
|
|
6943
|
-
<span class="section-subtitle">${skills.length}
|
|
7963
|
+
Skills
|
|
7964
|
+
<span class="section-subtitle">${skills.length} skills · ${skillEditableCount} editable</span>
|
|
7965
|
+
<button type="button" class="git-action-btn primary skill-create-btn" data-focus-key="cta-skill-create" onclick="${copyForPasteHandler(skillCreatePrompt, 'create prompt', 'chatInput')}" title="${escapeAttr(copyActionTitle('create prompt', 'chatInput'))}">Copy create prompt</button>
|
|
6944
7966
|
</div>
|
|
6945
7967
|
${skills.length === 0
|
|
6946
7968
|
? renderEmptyStateCta({
|
|
@@ -6955,23 +7977,38 @@ function renderUnsafe() {
|
|
|
6955
7977
|
focusKey: 'cta-skills-path',
|
|
6956
7978
|
},
|
|
6957
7979
|
})
|
|
6958
|
-
:
|
|
6959
|
-
|
|
6960
|
-
|
|
6961
|
-
|
|
6962
|
-
|
|
6963
|
-
|
|
6964
|
-
|
|
6965
|
-
|
|
6966
|
-
|
|
6967
|
-
|
|
6968
|
-
|
|
6969
|
-
|
|
6970
|
-
|
|
7980
|
+
: ''}
|
|
7981
|
+
<div class="skill-grid">
|
|
7982
|
+
${skills.map(s => {
|
|
7983
|
+
const skillFile = s.file || `.cursor/skills/${s.id}/SKILL.md`;
|
|
7984
|
+
const skillDir = skillFile.replace(/\/SKILL\.md$/, '');
|
|
7985
|
+
const kitManaged = s.kitManaged === true;
|
|
7986
|
+
const lockBadge = kitManaged
|
|
7987
|
+
? `<span class="skill-lock" title="Kit managed: owned by kit updates, read-only here">${skillLockSvg}Kit managed</span>`
|
|
7988
|
+
: '';
|
|
7989
|
+
const usePrompt = `Read this skill file and follow its instructions:\n${skillFile}`;
|
|
7990
|
+
const useBtn = `<button type="button" class="git-action-btn primary" onclick="${copyForPasteHandler(usePrompt, 'use prompt', 'chatInput')}" title="${escapeAttr(copyActionTitle('use prompt', 'chatInput'))}">Copy use prompt</button>`;
|
|
7991
|
+
const editPrompt = `Edit this skill file (SKILL.md markdown body):\n${skillFile}`;
|
|
7992
|
+
const editBtn = kitManaged
|
|
7993
|
+
? ''
|
|
7994
|
+
: `<button type="button" class="git-action-btn" onclick="${copyForPasteHandler(editPrompt, 'edit prompt', 'chatInput')}" title="${escapeAttr(copyActionTitle('edit prompt', 'chatInput'))}">Copy edit prompt</button>`;
|
|
7995
|
+
const deletePrompt = `Delete this skill directory after confirming with me:\n${skillDir}`;
|
|
7996
|
+
const deleteBtn = kitManaged
|
|
7997
|
+
? ''
|
|
7998
|
+
: `<button type="button" class="git-action-btn" onclick="${copyForPasteHandler(deletePrompt, 'delete prompt', 'chatInput')}" title="${escapeAttr(copyActionTitle('delete prompt', 'chatInput'))}">Copy delete prompt</button>`;
|
|
7999
|
+
return `
|
|
8000
|
+
<div class="skill-card"${kitManaged ? ' data-kit-managed="true"' : ''} title="${escapeAttr(s.description || '')}">
|
|
8001
|
+
<div class="skill-card-head">
|
|
8002
|
+
<span class="skill-name">${escapeHtml(s.title)}</span>
|
|
8003
|
+
${lockBadge}
|
|
6971
8004
|
</div>
|
|
8005
|
+
<span class="skill-cat">${escapeHtml(s.category || 'root')}</span>
|
|
8006
|
+
<div class="skill-file">${escapeHtml(skillFile)}</div>
|
|
8007
|
+
<div class="skill-actions">${useBtn}${editBtn}${deleteBtn}</div>
|
|
6972
8008
|
</div>
|
|
6973
8009
|
`;
|
|
6974
|
-
|
|
8010
|
+
}).join('')}
|
|
8011
|
+
</div>
|
|
6975
8012
|
</div>
|
|
6976
8013
|
`);
|
|
6977
8014
|
|
|
@@ -7011,7 +8048,7 @@ function renderUnsafe() {
|
|
|
7011
8048
|
parts.push(`
|
|
7012
8049
|
<div class="content-section" id="section-activity">
|
|
7013
8050
|
<div class="section-title">
|
|
7014
|
-
|
|
8051
|
+
Activity
|
|
7015
8052
|
<span class="section-subtitle">${escapeHtml(activityCountLabel)} · Last refreshed ${escapeHtml(relativeTime(d.generatedAt))}</span>
|
|
7016
8053
|
</div>
|
|
7017
8054
|
${renderActivityFilterChips()}
|