@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.
@@ -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
- /* ===== Agent List ===== */
763
- .agent-list { display: flex; flex-direction: column; gap: 4px; }
764
- .agent-item {
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-item:hover { background: var(--bg-card-hover); color: var(--text-primary); }
775
- .agent-item .agent-icon {
776
- width: 20px; height: 20px;
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: 10px;
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
- .healthcenter-presence .dot-pulse {
820
- animation: pulse 1.6s ease-in-out infinite;
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-sm, 6px);
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: 10px 12px;
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(160px, 1fr));
1041
- gap: 6px;
1368
+ grid-template-columns: repeat(auto-fill, minmax(240px, 1fr));
1369
+ gap: 10px;
1042
1370
  }
1043
- .command-item {
1371
+ .command-card {
1044
1372
  display: flex;
1045
- align-items: center;
1373
+ flex-direction: column;
1046
1374
  gap: 6px;
1047
- padding: 6px 10px;
1048
- border-radius: 6px;
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-secondary);
1389
+ color: var(--text-primary);
1053
1390
  font-family: var(--mc-font-mono);
1054
1391
  }
1055
- .command-item:hover { border-color: var(--border-active); color: var(--text-primary); }
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
- .section-title .dot { width: 12px; height: 12px; }
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-table {
1097
- width: 100%;
1098
- border-collapse: collapse;
1099
- font-size: 12px;
1100
- font-family: var(--mc-font-mono);
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-table th {
1103
- text-align: left;
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
- .processes-table td {
1113
- padding: 7px 10px;
1114
- border-bottom: 1px solid var(--border);
1115
- color: var(--text-secondary);
1462
+ .process-list {
1463
+ display: flex;
1464
+ flex-direction: column;
1465
+ gap: 8px;
1116
1466
  }
1117
- .processes-table tr:hover td {
1118
- background: var(--bg-card-hover);
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
- .processes-table .process-label {
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: 6px;
1479
+ gap: 10px;
1124
1480
  }
1125
- .processes-table .process-label .dot { width: 8px; height: 8px; }
1126
- .processes-table .process-actions {
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
- .skills-category {
1149
- margin-bottom: 20px;
1150
- }
1151
- .skills-category-title {
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
- .skills-tags {
1541
+ .skill-card {
1161
1542
  display: flex;
1162
- flex-wrap: wrap;
1543
+ flex-direction: column;
1163
1544
  gap: 6px;
1164
- }
1165
- .skill-tag {
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-tag:hover {
1180
- border-color: var(--border-active);
1181
- color: var(--text-primary);
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-tag .skill-cat-dot {
1184
- width: 6px;
1185
- height: 6px;
1186
- border-radius: 50%;
1187
- flex-shrink: 0;
1557
+ .skill-name {
1558
+ font-size: 12px;
1559
+ color: var(--text-primary);
1188
1560
  }
1189
- .skill-tag .skill-title {
1190
- font-weight: 500;
1191
- white-space: nowrap;
1192
- overflow: hidden;
1193
- text-overflow: ellipsis;
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-tag .skill-desc {
1196
- font-size: var(--mc-chrome-meta-size);
1197
- font-weight: var(--mc-chrome-meta-weight);
1567
+ .skill-file {
1568
+ font-size: 10px;
1198
1569
  color: var(--text-muted);
1199
- white-space: nowrap;
1200
- overflow: hidden;
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: 24px;
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: 40px 20px;
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: flex;
1658
- flex-direction: column;
2034
+ display: grid;
2035
+ grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
1659
2036
  gap: 16px;
1660
- max-width: 520px;
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: 32px 20px;
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: 16px 10px;
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) 18px;
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: 10px var(--mc-space-lg);
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: 14px 10px;
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: 14px 10px;
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: 10px 12px;
2757
- border-radius: 8px;
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 (no unset --accent → yellow). */
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
- .flight-log-card-past.flight-log-kind-residual:hover,
2833
- .flight-log-card-past.flight-log-kind-residual:focus-visible {
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 = 'overview';
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] || 'dot-gray';
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
- run_plan: { icon: '\u25b6', tag: 'tick', gloss: 'tick - live execution', color: 'var(--green)', bg: 'var(--green-bg)' },
3625
- handoff: { icon: '\u23f8', tag: 'handoff', gloss: 'handoff - awaiting gate', color: 'var(--yellow)', bg: 'var(--yellow-bg)' },
3626
- agent_step: { icon: '\u25c9', tag: 'step', gloss: 'step - agent/task unit', color: 'var(--purple)', bg: 'var(--purple-bg)' },
3627
- plan_progress: { icon: '\u2691', tag: 'plan', gloss: 'plan - milestone', color: 'var(--orange)', bg: 'var(--orange-bg)' },
3628
- delivery: { icon: '\u2714', tag: 'delivery', gloss: 'delivery - shipped unit', color: 'var(--cyan)', bg: 'var(--cyan-bg)' },
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: 'delivery - feat', color: 'var(--purple)', bg: 'var(--purple-bg)' },
3641
- fix: { icon: '\u2699', tag: 'delivery', gloss: 'delivery - fix', color: 'var(--red)', bg: 'var(--red-bg)' },
3642
- docs: { icon: '\u270e', tag: 'delivery', gloss: 'delivery - docs', color: 'var(--blue)', bg: 'var(--blue-bg)' },
3643
- chore: { icon: '\u2692', tag: 'delivery', gloss: 'delivery - chore', color: 'var(--orange)', bg: 'var(--orange-bg)' },
3644
- pr: { icon: '\u2442', tag: 'delivery', gloss: 'delivery - PR', color: 'var(--cyan)', bg: 'var(--cyan-bg)' },
3645
- ship: { icon: '\u2708', tag: 'delivery', gloss: 'delivery - shipped unit', color: 'var(--cyan)', bg: 'var(--cyan-bg)' },
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">&rarr;</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
- showToast(`Copy failed - select manually: ${text}`, true);
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
- <span class="dot dot-blue"></span> Config
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 cooldown (default for named-model runs). On Auto continuous /run-plan, recommend 15000 ms.</span>
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 after this many completed /run-plan ticks (or each /run-plan-all queue complete) when unreviewed work remains. Default 3.</span>
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. Apply stays via /update Ask confirm. updateApply.auto is never editable here.</span>
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 for existing installs. Autonomous auto-launches a visible review (Phase 2+).</span>
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
- ${modeRows}
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
- async function saveMissionConfig(event) {
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: 'At least one agent is cataloged under .cursor/agents/.',
4649
- failDetail: 'No agents found. Kit install or onboard may be incomplete.',
4650
- autofix: { text: '/agent-kit-onboard', subject: '/agent-kit-onboard', destination: 'chatInput', label: 'Autofix' },
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
- if (sev === 'ok') return 'ok';
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
- if (sev === 'ok') return 'dot-green';
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 healthTone = healthStatus === 'ok' ? 'green' : healthStatus === 'warning' ? 'yellow' : 'red';
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' ? 'Live' : healthStatus === 'warning' ? 'Warning' : 'Degraded';
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' ? 'Polling' : healthStatus === 'warning' ? 'Warning' : 'Degraded';
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
- const completed = raw.todos?.completed ?? enriched.progress?.completed ?? 0;
5100
- const total = raw.todos?.total ?? enriched.progress?.total ?? 0;
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
- typeof raw.progress === 'number'
5117
- ? raw.progress
5118
- : total > 0
5119
- ? Math.round((completed / total) * 100)
6001
+ total > 0
6002
+ ? Math.round((completed / total) * 100)
6003
+ : typeof raw.progress === 'number'
6004
+ ? raw.progress
5120
6005
  : 0,
5121
- progressLabel: enriched.progress?.label || `${completed} of ${total}`,
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="img" aria-label="Progress ${escapeAttr(p.progressLabel)}">
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
- <button type="button" class="status-bar-btn" data-focus-key="plan-copy-${escapeAttr(key)}" onclick="event.stopPropagation();${copyRepoPathHandler(p.path)}" title="${escapeAttr(pathActionTitle(p.path))}" style="font-size:11px;padding:3px 10px">Copy plan path</button>
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
- <span class="dot ${statusDot(t.status)}"></span>
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=".9"/>' +
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) — concentric rings + sweep wedge (not a gauge needle)
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="3.5"/>' +
5558
- '<circle cx="8" cy="8" r="1.5"/>' +
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) — radii sized for stroke 1.5
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.25"/>' +
5573
- '<circle cx="8" cy="8" r="1.25"/>' +
5574
- '<circle cx="12" cy="8" r="1.25"/>',
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.75 14.25v-3.25h2.5v3.25"/>',
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="M10.5 8a2.5 2.5 0 000-2.5l.8-.8M5.5 8a2.5 2.5 0 010 2.5l-.8.8M8 5.5l-.5-1M8 10.5l-.5 1M10.5 6.5l1-.5M5.5 9.5l-1 .5M10.5 9.5l1 .5M5.5 6.5l-1-.5"/>',
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 8.5h4M6 10.5h2.5"/>',
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 (Live large / Earlier smaller). Labels locked.
6632
+ * Gaps card on Flight Log (NOW large / Earlier smaller). Labels locked.
5739
6633
  * Kind selects Mission Control palette tokens (typed notification chrome).
5740
- * @param {{ text?: string, at?: string|null, sourcePath?: string, variant?: string, kind?: string }} entry
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' ? 'Live' : 'Earlier';
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 copyTextHandler = copyForPasteHandler(text, 'Gaps text', 'chatInput');
5760
- const copyPathHandler = copyRepoPathHandler(sourcePath);
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-copy-text-${variant}-${idx}" onclick="${copyTextHandler}" style="font-size:11px;padding:3px 10px" title="${escapeAttr(copyActionTitle('Gaps text', 'chatInput'))}">Copy text</button>
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
- * @param {{ id?: string, kind?: string, title?: string, text?: string, sourcePath?: string }} warning
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 copyTextHandler = copyForPasteHandler(text, 'Warning text', 'chatInput');
5798
- const copyPathHandler = copyRepoPathHandler(sourcePath);
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-copy-text-${idx}" onclick="${copyTextHandler}" style="font-size:11px;padding:3px 10px" title="${escapeAttr(copyActionTitle('Warning text', 'chatInput'))}">Copy text</button>
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
- * per-row Copy triage + path only. No Review all / Resolve all.
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 copyTriageHandler = copyForPasteHandler(triageCmd, 'triage command', 'chatInput');
5835
- const copyPathHandler = copyRepoPathHandler(sourcePath);
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-cmd-${idx}" onclick="${copyTriageHandler}" style="font-size:11px;padding:3px 10px" title="${escapeAttr(copyActionTitle('triage command', 'chatInput'))}">Copy triage command</button>
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 live Gaps">
5974
- <span class="flight-log-card-label">Live</span>
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
- return `${current}\0${past}\0${warnings}\0${openTriages}`;
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: steady state color only
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">${escapeHtml(ev.label)}</span>
6531
- <span class="feed-time">${semanticEventTime(ev, info)}</span>
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
- <span class="dot dot-green"></span> Plans
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
- <span class="dot dot-purple"></span> Agents
6556
- <span class="section-subtitle">${d.agents.length} available</span>
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
- ${d.agents.length === 0
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-list">
6572
- ${d.agents.map(a => {
6573
- const colors = ['#3b82f6','#a855f7','#22c55e','#eab308','#ef4444','#06b6d4','#f97316','#ec4899'];
6574
- const c = colors[a.id.length % colors.length];
6575
- const agentId = a.id.replace(/[^a-zA-Z0-9_-]/g, '_');
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
- <div class="agent-item" role="button" tabindex="0" aria-label="Toggle details for ${escapeAttr(a.id)}" onclick="toggleAgentDetails('${escapeJsString(agentId)}')">
6578
- <span class="agent-icon" style="background:${c}20;color:${c}">${escapeHtml(a.id.slice(0, 2).toUpperCase())}</span>
6579
- <span style="flex:1">${escapeHtml(a.id)}</span>
6580
- <span style="font-size:11px;color:var(--text-muted)">${escapeHtml(a.description || '')}</span>
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
- <span class="dot" style="background:var(--cyan)"></span> Commands
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-item" role="button" tabindex="0" aria-label="Copy ${escapeAttr(slash)}" onclick="${copyForPasteHandler(slash, slash, 'chatInput')}" title="${escapeAttr(copyActionTitle(slash, 'chatInput'))}">
6619
- <span class="dot" style="background:var(--cyan);width:6px;height:6px"></span>
6620
- ${escapeHtml(slash)}
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 === 'ok' ? 'green' : healthStatus === 'warning' ? 'yellow' : 'red';
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
- healthBody = `
6683
- <div class="health-grid" role="list">
6684
- ${checks.map(c => {
6685
- const id = c.id || c.label || 'check';
6686
- const meta = HEALTH_CHECK_META[id] || {
6687
- okDetail: 'Check is passing.',
6688
- failDetail: 'Check is failing. Review configuration for this item.',
6689
- autofix: null,
6690
- };
6691
- const sev = healthCheckSeverity(c, healthStatus);
6692
- const detail = c.ok ? meta.okDetail : meta.failDetail;
6693
- const autofix = !c.ok && meta.autofix ? meta.autofix : null;
6694
- const autofixBtn = autofix
6695
- ? `<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>`
6696
- : (c.ok
6697
- ? `<span class="health-detail-copy" style="margin:0">No Autofix needed.</span>`
6698
- : `<span class="health-detail-copy" style="margin:0">No Autofix mapped for this check.</span>`);
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" id="health-card-${escapeAttr(id)}" data-check-id="${escapeAttr(id)}" data-severity="${escapeAttr(sev)}">
6701
- <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)}')">
6702
- <span class="dot ${healthDotClass(sev)}"></span>
6703
- <span class="health-item-label">${escapeHtml(c.label)}</span>
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
- <span class="dot dot-yellow"></span> Memory
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-2">
6785
- <div class="card">
6786
- <div class="card-header">
6787
- <span class="card-title">Error Records</span>
6788
- <span class="dot dot-red"></span>
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="card">
6794
- <div class="card-header">
6795
- <span class="card-title">Decisions</span>
6796
- <span class="dot dot-yellow"></span>
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
- ${recentDecisions.length > 0 ? `
6803
- <div class="card" style="margin-top:16px">
6804
- <div class="card-header">
6805
- <span class="card-title">Recent Decisions</span>
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
- <div class="memory-list">
6808
- ${recentDecisions.map(dd => {
6809
- const memPath = dd.path || `.cursor/memory/decisions/${dd.id || dd}.md`;
6810
- const memId = dd.id || dd;
6811
- return `
6812
- <div class="memory-item" role="button" tabindex="0" aria-label="Copy path for ${escapeAttr(memId)}" onclick="${copyRepoPathHandler(memPath)}" title="${escapeAttr(pathActionTitle(memPath))}">
6813
- <span class="dot dot-yellow"></span>
6814
- <span>${escapeHtml(memId)}</span>
6815
- </div>
6816
- `;
6817
- }).join('')}
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="card" style="overflow-x:auto">
6886
- <table class="processes-table">
6887
- <thead>
6888
- <tr>
6889
- <th>Process</th>
6890
- <th>PID</th>
6891
- <th>CPU%</th>
6892
- <th>MEM%</th>
6893
- <th>Command</th>
6894
- <th></th>
6895
- </tr>
6896
- </thead>
6897
- <tbody>
6898
- ${processes.map(p => {
6899
- const dotClass = p.label === 'dashboard-server' ? 'dot-green dot-pulse'
6900
- : p.label === 'node' ? 'dot-blue'
6901
- : p.label === 'git' ? 'dot-yellow'
6902
- : 'dot-gray';
6903
- const pidStr = String(p.pid ?? '');
6904
- return `
6905
- <tr>
6906
- <td>
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 categories = {};
6932
- skills.forEach(s => {
6933
- const cat = s.category || 'other';
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
- <span class="dot dot-orange" style="background:var(--orange)"></span> Skills
6943
- <span class="section-subtitle">${skills.length} discovered</span>
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
- : Object.entries(categories).map(([cat, items]) => {
6959
- const catColor = catColors[catIdx++ % catColors.length];
6960
- return `
6961
- <div class="skills-category">
6962
- <div class="skills-category-title">${escapeHtml(cat)}</div>
6963
- <div class="skills-tags">
6964
- ${items.map(s => `
6965
- <div class="skill-tag" title="${escapeAttr(s.description || '')}">
6966
- <span class="skill-cat-dot" style="background:${catColor}"></span>
6967
- <span class="skill-title">${escapeHtml(s.title)}</span>
6968
- ${s.description ? `<span class="skill-desc">\u2014 ${escapeHtml(s.description)}</span>` : ''}
6969
- </div>
6970
- `).join('')}
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
- }).join('')}
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
- <span class="dot" style="background:var(--cyan)"></span> Activity
8051
+ Activity
7015
8052
  <span class="section-subtitle">${escapeHtml(activityCountLabel)} · Last refreshed ${escapeHtml(relativeTime(d.generatedAt))}</span>
7016
8053
  </div>
7017
8054
  ${renderActivityFilterChips()}