@dadado/agent-kit-cli 4.8.3 → 4.8.8

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;
@@ -503,6 +576,9 @@ a:focus-visible,
503
576
  background: var(--border);
504
577
  }
505
578
  .nav-more-group-label {
579
+ display: flex;
580
+ align-items: center;
581
+ gap: var(--mc-chrome-label-gap);
506
582
  padding: 6px 10px 2px;
507
583
  font-size: var(--mc-chrome-subtitle-size);
508
584
  font-weight: var(--mc-chrome-subtitle-weight);
@@ -623,6 +699,7 @@ a:focus-visible,
623
699
  .dot-blue { background: var(--blue); box-shadow: 0 0 6px var(--blue-bg); }
624
700
  .dot-purple { background: var(--purple); box-shadow: 0 0 6px var(--purple-bg); }
625
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); }
626
703
  .dot-gray { background: #3a4a5a; box-shadow: 0 0 6px rgba(58,74,90,0.3); }
627
704
  .dot-pulse::after {
628
705
  content: '';
@@ -636,6 +713,8 @@ a:focus-visible,
636
713
  .dot-green.dot-pulse::after { border-color: var(--green); }
637
714
  .dot-blue.dot-pulse::after { border-color: var(--blue); }
638
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); }
639
718
  @keyframes pulse {
640
719
  0% { opacity: 0.6; transform: scale(1); }
641
720
  100% { opacity: 0; transform: scale(2.5); }
@@ -756,29 +835,66 @@ a:focus-visible,
756
835
  outline-offset: 1px;
757
836
  }
758
837
 
759
- /* ===== Agent List ===== */
760
- .agent-list { display: flex; flex-direction: column; gap: 4px; }
761
- .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 {
762
855
  display: flex;
763
856
  align-items: center;
857
+ justify-content: space-between;
764
858
  gap: 8px;
765
- padding: 8px 10px;
766
- border-radius: 6px;
767
- font-size: 13px;
768
- color: var(--text-secondary);
769
- transition: all 0.15s;
770
859
  }
771
- .agent-item:hover { background: var(--bg-card-hover); color: var(--text-primary); }
772
- .agent-item .agent-icon {
773
- 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;
774
871
  border-radius: 4px;
775
- display: flex;
872
+ display: inline-flex;
776
873
  align-items: center;
777
874
  justify-content: center;
778
- font-size: 10px;
875
+ font-size: 9px;
779
876
  font-weight: 700;
877
+ background: var(--bg-card-hover);
878
+ color: var(--text-secondary);
780
879
  flex-shrink: 0;
781
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; }
782
898
 
783
899
  /* ===== Terminal List ===== */
784
900
  .terminal-list { display: flex; flex-direction: column; gap: 4px; }
@@ -800,25 +916,160 @@ a:focus-visible,
800
916
  }
801
917
  .terminal-item:hover { background: var(--bg-card-hover); }
802
918
 
803
- /* ===== Health ===== */
804
- .health-grid {
919
+ /* ===== Healthcenter ===== */
920
+ .healthcenter {
921
+ display: flex;
922
+ flex-direction: column;
923
+ gap: 10px;
924
+ }
925
+ .healthcenter-presence {
926
+ display: flex;
927
+ align-items: center;
928
+ gap: 8px;
929
+ font-size: 11px;
930
+ color: var(--text-muted);
931
+ }
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 {
805
935
  display: grid;
806
- grid-template-columns: repeat(auto-fill, minmax(140px, 1fr));
936
+ grid-template-columns: repeat(auto-fill, minmax(180px, 1fr));
807
937
  gap: 8px;
808
938
  }
809
- .health-item {
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 {
810
961
  display: flex;
811
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;
812
971
  gap: 8px;
813
- padding: 8px 10px;
814
- border-radius: 6px;
972
+ }
973
+ .health-group-title {
974
+ font-size: 11px;
975
+ text-transform: uppercase;
976
+ letter-spacing: 0.04em;
977
+ color: var(--text-muted);
978
+ }
979
+ .health-grid {
980
+ display: grid;
981
+ grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
982
+ gap: 8px;
983
+ }
984
+ .health-card {
985
+ display: flex;
986
+ flex-direction: column;
987
+ gap: 0;
988
+ border-radius: var(--mc-radius);
815
989
  background: var(--bg-card);
816
990
  border: 1px solid var(--border);
991
+ overflow: hidden;
992
+ transition: border-color 0.2s ease, box-shadow 0.2s ease;
993
+ }
994
+ .health-card:hover,
995
+ .health-card.is-expanded {
996
+ border-color: var(--border-active);
997
+ }
998
+ .health-item {
999
+ display: flex;
1000
+ align-items: center;
1001
+ gap: 8px;
1002
+ padding: var(--mc-card-padding-dense);
817
1003
  font-size: 12px;
818
1004
  color: var(--text-secondary);
819
- transition: opacity 0.3s ease, transform 0.3s ease, border-color 0.2s;
1005
+ cursor: pointer;
1006
+ background: transparent;
1007
+ border: none;
1008
+ width: 100%;
1009
+ text-align: left;
1010
+ font-family: inherit;
1011
+ transition: opacity 0.3s ease, transform 0.3s ease, background 0.15s ease;
1012
+ }
1013
+ .health-item:hover { background: var(--bg-card-hover); }
1014
+ .health-item-label {
1015
+ flex: 1;
1016
+ min-width: 0;
1017
+ color: var(--text-primary);
1018
+ }
1019
+ .health-item-sev {
1020
+ font-size: 10px;
1021
+ text-transform: uppercase;
1022
+ letter-spacing: 0.04em;
1023
+ color: var(--text-muted);
1024
+ flex-shrink: 0;
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); }
1030
+ .health-item-chevron {
1031
+ color: var(--text-muted);
1032
+ font-size: 10px;
1033
+ transition: transform 0.15s ease;
1034
+ flex-shrink: 0;
1035
+ }
1036
+ .health-card.is-expanded .health-item-chevron {
1037
+ transform: rotate(180deg);
1038
+ }
1039
+ .health-detail {
1040
+ display: none;
1041
+ padding: 0 12px 12px;
1042
+ border-top: 1px solid var(--border);
1043
+ background: var(--bg-secondary);
1044
+ }
1045
+ .health-card.is-expanded .health-detail {
1046
+ display: block;
1047
+ animation: fadeIn 0.2s ease;
1048
+ }
1049
+ .health-detail-copy {
1050
+ font-size: 11px;
1051
+ color: var(--text-secondary);
1052
+ margin: 10px 0 8px;
1053
+ line-height: 1.4;
1054
+ }
1055
+ .health-detail-actions {
1056
+ display: flex;
1057
+ flex-wrap: wrap;
1058
+ gap: 6px;
1059
+ }
1060
+ .health-autofix-btn {
1061
+ font-size: 11px;
1062
+ padding: 4px 10px;
1063
+ border-radius: 4px;
1064
+ border: 1px solid var(--border-active);
1065
+ background: var(--bg-card);
1066
+ color: var(--text-primary);
1067
+ cursor: pointer;
1068
+ font-family: inherit;
1069
+ }
1070
+ .health-autofix-btn:hover {
1071
+ background: var(--bg-card-hover);
820
1072
  }
821
- .health-item:hover { border-color: var(--border-active); }
822
1073
 
823
1074
  /* ===== Git ===== */
824
1075
  .git-info {
@@ -836,6 +1087,98 @@ a:focus-visible,
836
1087
  .git-label { color: var(--text-muted); }
837
1088
  .git-value { color: var(--text-secondary); font-family: var(--mc-font-mono); font-size: 12px; }
838
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
+
839
1182
  /* ===== Memory ===== */
840
1183
  .memory-list {
841
1184
  display: flex;
@@ -849,9 +1192,94 @@ a:focus-visible,
849
1192
  padding: 6px 10px;
850
1193
  border-radius: 6px;
851
1194
  font-size: 12px;
852
- color: var(--text-secondary);
1195
+ color: var(--text-secondary);
1196
+ }
1197
+ .memory-item:hover { background: var(--bg-card-hover); }
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;
853
1281
  }
854
- .memory-item:hover { background: var(--bg-card-hover); }
1282
+ .memory-error-copy-btn:hover { background: var(--bg-card-hover); }
855
1283
 
856
1284
  /* ===== Activity Timeline ===== */
857
1285
  .activity-list {
@@ -937,22 +1365,46 @@ a:focus-visible,
937
1365
  /* ===== Commands ===== */
938
1366
  .command-grid {
939
1367
  display: grid;
940
- grid-template-columns: repeat(auto-fill, minmax(160px, 1fr));
941
- gap: 6px;
1368
+ grid-template-columns: repeat(auto-fill, minmax(240px, 1fr));
1369
+ gap: 10px;
942
1370
  }
943
- .command-item {
1371
+ .command-card {
944
1372
  display: flex;
945
- align-items: center;
1373
+ flex-direction: column;
946
1374
  gap: 6px;
947
- padding: 6px 10px;
948
- border-radius: 6px;
1375
+ padding: var(--mc-card-padding-dense);
1376
+ border-radius: var(--mc-radius);
949
1377
  background: var(--bg-card);
950
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 {
951
1388
  font-size: 12px;
952
- color: var(--text-secondary);
1389
+ color: var(--text-primary);
1390
+ font-family: var(--mc-font-mono);
1391
+ }
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);
953
1403
  font-family: var(--mc-font-mono);
1404
+ word-break: break-all;
954
1405
  }
955
- .command-item:hover { border-color: var(--border-active); color: var(--text-primary); }
1406
+ .command-actions { display: flex; flex-wrap: wrap; gap: 6px; margin-top: 2px; }
1407
+ .command-create-btn { margin-left: auto; }
956
1408
 
957
1409
  /* ===== Loading ===== */
958
1410
  .loading {
@@ -984,7 +1436,7 @@ a:focus-visible,
984
1436
  gap: var(--mc-chrome-label-gap);
985
1437
  letter-spacing: 0;
986
1438
  }
987
- .section-title .dot { width: 12px; height: 12px; }
1439
+ /* Section titles carry no dots: identity comes from the label text (dot semantics table). */
988
1440
  .section-subtitle {
989
1441
  font-size: var(--mc-chrome-subtitle-size);
990
1442
  color: var(--text-muted);
@@ -993,39 +1445,75 @@ a:focus-visible,
993
1445
  }
994
1446
 
995
1447
  /* ===== Processes ===== */
996
- .processes-table {
997
- width: 100%;
998
- border-collapse: collapse;
999
- font-size: 12px;
1000
- 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;
1001
1457
  }
1002
- .processes-table th {
1003
- text-align: left;
1004
- padding: 8px 10px;
1005
- color: var(--text-muted);
1458
+ .processes-note strong {
1459
+ color: var(--blue);
1006
1460
  font-weight: 600;
1007
- border-bottom: 1px solid var(--border);
1008
- font-size: 11px;
1009
- text-transform: uppercase;
1010
- letter-spacing: 0.05em;
1011
1461
  }
1012
- .processes-table td {
1013
- padding: 7px 10px;
1014
- border-bottom: 1px solid var(--border);
1015
- color: var(--text-secondary);
1462
+ .process-list {
1463
+ display: flex;
1464
+ flex-direction: column;
1465
+ gap: 8px;
1016
1466
  }
1017
- .processes-table tr:hover td {
1018
- 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);
1472
+ }
1473
+ .process-card:hover {
1474
+ border-color: var(--border-active);
1019
1475
  }
1020
- .processes-table .process-label {
1476
+ .process-card-head {
1021
1477
  display: flex;
1022
1478
  align-items: center;
1023
- gap: 6px;
1479
+ gap: 10px;
1480
+ }
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);
1024
1508
  }
1025
- .processes-table .process-label .dot { width: 8px; height: 8px; }
1026
- .processes-table .process-actions {
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;
1027
1516
  white-space: nowrap;
1028
- text-align: right;
1029
1517
  }
1030
1518
  .process-copy-btn {
1031
1519
  font-size: 10px;
@@ -1045,61 +1533,45 @@ a:focus-visible,
1045
1533
  }
1046
1534
 
1047
1535
  /* ===== Skills ===== */
1048
- .skills-category {
1049
- margin-bottom: 20px;
1050
- }
1051
- .skills-category-title {
1052
- font-size: 12px;
1053
- font-weight: 600;
1054
- color: var(--text-muted);
1055
- text-transform: uppercase;
1056
- letter-spacing: 0.06em;
1057
- margin-bottom: 10px;
1058
- padding: 0 2px;
1536
+ .skill-grid {
1537
+ display: grid;
1538
+ grid-template-columns: repeat(auto-fill, minmax(240px, 1fr));
1539
+ gap: 10px;
1059
1540
  }
1060
- .skills-tags {
1541
+ .skill-card {
1061
1542
  display: flex;
1062
- flex-wrap: wrap;
1543
+ flex-direction: column;
1063
1544
  gap: 6px;
1064
- }
1065
- .skill-tag {
1066
- display: inline-flex;
1067
- align-items: center;
1068
- gap: 5px;
1069
- padding: 5px 10px;
1070
- border-radius: 6px;
1545
+ padding: var(--mc-card-padding-dense);
1546
+ border-radius: var(--mc-radius);
1071
1547
  background: var(--bg-card);
1072
1548
  border: 1px solid var(--border);
1073
- font-size: 12px;
1074
- color: var(--text-secondary);
1075
- cursor: default;
1076
- transition: all 0.15s;
1077
- max-width: 280px;
1078
1549
  }
1079
- .skill-tag:hover {
1080
- border-color: var(--border-active);
1081
- 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;
1082
1556
  }
1083
- .skill-tag .skill-cat-dot {
1084
- width: 6px;
1085
- height: 6px;
1086
- border-radius: 50%;
1087
- flex-shrink: 0;
1557
+ .skill-name {
1558
+ font-size: 12px;
1559
+ color: var(--text-primary);
1088
1560
  }
1089
- .skill-tag .skill-title {
1090
- font-weight: 500;
1091
- white-space: nowrap;
1092
- overflow: hidden;
1093
- text-overflow: ellipsis;
1561
+ .skill-cat {
1562
+ font-size: 10px;
1563
+ color: var(--text-muted);
1564
+ text-transform: uppercase;
1565
+ letter-spacing: 0.06em;
1094
1566
  }
1095
- .skill-tag .skill-desc {
1096
- font-size: var(--mc-chrome-meta-size);
1097
- font-weight: var(--mc-chrome-meta-weight);
1567
+ .skill-file {
1568
+ font-size: 10px;
1098
1569
  color: var(--text-muted);
1099
- white-space: nowrap;
1100
- overflow: hidden;
1101
- text-overflow: ellipsis;
1570
+ font-family: var(--mc-font-mono);
1571
+ word-break: break-all;
1102
1572
  }
1573
+ .skill-actions { display: flex; flex-wrap: wrap; gap: 6px; margin-top: 2px; }
1574
+ .skill-create-btn { margin-left: auto; }
1103
1575
 
1104
1576
  /* ===== Context Packs ===== */
1105
1577
  .context-pack-item {
@@ -1182,16 +1654,6 @@ a:focus-visible,
1182
1654
  .live-indicator.live-reconnecting {
1183
1655
  animation: pulse-text 1.5s ease-in-out infinite;
1184
1656
  }
1185
- .live-pulse-dot {
1186
- width: 8px;
1187
- height: 8px;
1188
- border-radius: 50%;
1189
- background: var(--green);
1190
- position: relative;
1191
- }
1192
- .live-pulse-dot.live-dot-yellow {
1193
- background: var(--yellow);
1194
- }
1195
1657
  /* Decorative ring pulses removed; header transport owns freshness */
1196
1658
  .live-activity-feed {
1197
1659
  max-height: 320px;
@@ -1278,6 +1740,21 @@ a:focus-visible,
1278
1740
  width: 1em;
1279
1741
  text-align: center;
1280
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
+ }
1281
1758
  .live-activity-feed .monitor-row .feed-label {
1282
1759
  flex: 1;
1283
1760
  min-width: 0;
@@ -1287,6 +1764,32 @@ a:focus-visible,
1287
1764
  color: var(--text-secondary);
1288
1765
  font-size: 12px;
1289
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);
1290
1793
  }
1291
1794
  .live-activity-feed .monitor-row .feed-time {
1292
1795
  color: var(--text-muted);
@@ -1298,7 +1801,7 @@ a:focus-visible,
1298
1801
  margin-left: auto;
1299
1802
  }
1300
1803
  .activity-feed-empty {
1301
- padding: 24px;
1804
+ padding: var(--mc-space-2xl);
1302
1805
  text-align: center;
1303
1806
  color: var(--text-muted);
1304
1807
  font-size: 12px;
@@ -1369,47 +1872,21 @@ a:focus-visible,
1369
1872
  }
1370
1873
 
1371
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. */
1372
1877
  .empty-state {
1373
1878
  text-align: center;
1374
- padding: 40px 20px;
1879
+ padding: calc(var(--mc-card-padding) * 2.5) calc(var(--mc-card-padding) * 1.25);
1375
1880
  color: var(--text-muted);
1376
1881
  font-size: 13px;
1377
1882
  }
1378
1883
 
1379
1884
  /* ===== Clickable Cursors (Phase 1) ===== */
1380
1885
  .plan-card { cursor: pointer; }
1381
- .command-item { cursor: pointer; }
1382
- .agent-item { cursor: pointer; }
1383
1886
  .health-item { cursor: pointer; }
1384
1887
  .terminal-item { cursor: pointer; }
1385
1888
  .memory-item { cursor: pointer; }
1386
1889
 
1387
- /* ===== Agent Expanded View ===== */
1388
- .agent-details {
1389
- max-height: 0;
1390
- overflow: hidden;
1391
- transition: max-height 0.3s ease, opacity 0.3s ease;
1392
- opacity: 0;
1393
- padding: 0 10px;
1394
- }
1395
- .agent-details.open {
1396
- max-height: 200px;
1397
- opacity: 1;
1398
- padding: 8px 10px;
1399
- }
1400
- .agent-details p {
1401
- font-size: 12px;
1402
- color: var(--text-secondary);
1403
- line-height: 1.5;
1404
- }
1405
- .agent-details .agent-path {
1406
- font-size: 11px;
1407
- color: var(--text-muted);
1408
- font-family: var(--mc-font-mono);
1409
- margin-top: 4px;
1410
- word-break: break-all;
1411
- }
1412
-
1413
1890
  /* ===== Copied Toast ===== */
1414
1891
  .copied-toast {
1415
1892
  position: fixed;
@@ -1448,7 +1925,7 @@ a:focus-visible,
1448
1925
  vertical-align: middle;
1449
1926
  }
1450
1927
 
1451
- /* ===== Health Investigation Message ===== */
1928
+ /* ===== Health Investigation Message (legacy; Healthcenter uses .health-detail) ===== */
1452
1929
  .health-message {
1453
1930
  font-size: 11px;
1454
1931
  color: var(--text-secondary);
@@ -1554,10 +2031,23 @@ a:focus-visible,
1554
2031
 
1555
2032
  /* ===== Config form (session prefs) ===== */
1556
2033
  .config-form {
1557
- display: flex;
1558
- flex-direction: column;
2034
+ display: grid;
2035
+ grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
1559
2036
  gap: 16px;
1560
- 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;
1561
2051
  }
1562
2052
  .config-fieldset {
1563
2053
  border: 1px solid var(--border);
@@ -1572,6 +2062,23 @@ a:focus-visible,
1572
2062
  font-weight: var(--mc-chrome-subtitle-weight);
1573
2063
  color: var(--text-muted);
1574
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);
2081
+ }
1575
2082
  .config-row {
1576
2083
  display: flex;
1577
2084
  flex-direction: column;
@@ -1789,12 +2296,12 @@ a:focus-visible,
1789
2296
  /* ===== Phase 3: Empty State CTAs ===== */
1790
2297
  .empty-state-cta {
1791
2298
  text-align: center;
1792
- padding: 32px 20px;
2299
+ padding: calc(var(--mc-card-padding) * 2) calc(var(--mc-card-padding) * 1.25);
1793
2300
  color: var(--text-muted);
1794
2301
  font-size: 13px;
1795
2302
  }
1796
2303
  .empty-state-cta.compact {
1797
- padding: 16px 10px;
2304
+ padding: var(--mc-card-padding) var(--mc-space-lg);
1798
2305
  font-size: 12px;
1799
2306
  }
1800
2307
  .empty-state-cta .empty-state-icon {
@@ -1842,7 +2349,7 @@ a:focus-visible,
1842
2349
  background: var(--bg-card);
1843
2350
  border: 1px solid var(--border);
1844
2351
  color: var(--text-secondary);
1845
- padding: var(--mc-space-md) 18px;
2352
+ padding: var(--mc-space-md) var(--mc-space-xl);
1846
2353
  border-radius: var(--mc-radius-chrome);
1847
2354
  cursor: pointer;
1848
2355
  font-size: var(--mc-chrome-label-size);
@@ -1975,6 +2482,8 @@ a:focus-visible,
1975
2482
  .plan-card-footer { flex-wrap: wrap; gap: var(--mc-space-xs); }
1976
2483
  .health-grid { grid-template-columns: 1fr 1fr; }
1977
2484
  .command-grid { grid-template-columns: 1fr 1fr; }
2485
+ .skill-grid { grid-template-columns: 1fr 1fr; }
2486
+ .agent-grid { grid-template-columns: 1fr 1fr; }
1978
2487
  .git-row { flex-wrap: wrap; gap: var(--mc-space-xs); }
1979
2488
  .terminal-popup { max-width: 95vw; padding: 14px; }
1980
2489
  }
@@ -1993,12 +2502,60 @@ a:focus-visible,
1993
2502
  .terminal-popup { max-width: 95vw; padding: 14px; }
1994
2503
  }
1995
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
+
1996
2531
  .overview-stack {
1997
2532
  display: flex;
1998
2533
  flex-direction: column;
1999
2534
  gap: 12px;
2000
2535
  }
2001
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
+
2002
2559
  /* Desktop one-fold overview: 2x2 fixed-height grid; page does not scroll.
2003
2560
  Additive under min-width 1024px only. Non-overview sections keep .content scroll.
2004
2561
  Subheader (Cockpit anchors) hides: all four panels are visible; More lives in the header. */
@@ -2031,8 +2588,8 @@ a:focus-visible,
2031
2588
  grid-template-columns: 1fr 1fr;
2032
2589
  grid-template-rows: minmax(0, 1fr) minmax(0, 1fr);
2033
2590
  grid-template-areas:
2034
- "now monitor"
2035
- "attention checklist";
2591
+ "now attention"
2592
+ "checklist monitor";
2036
2593
  }
2037
2594
 
2038
2595
  #section-overview.active #now-execution-panel,
@@ -2108,7 +2665,7 @@ a:focus-visible,
2108
2665
  display: flex;
2109
2666
  flex-direction: column;
2110
2667
  gap: var(--mc-space-sm);
2111
- padding: 10px var(--mc-space-lg);
2668
+ padding: var(--mc-card-padding-dense);
2112
2669
  border: 1px solid var(--border);
2113
2670
  border-radius: var(--radius);
2114
2671
  background: var(--bg-card);
@@ -2337,6 +2894,25 @@ a:focus-visible,
2337
2894
  margin: 0 0 8px;
2338
2895
  line-height: 1.4;
2339
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
+ }
2340
2916
  .plan-todo-list-full {
2341
2917
  display: flex;
2342
2918
  flex-direction: column;
@@ -2348,7 +2924,7 @@ a:focus-visible,
2348
2924
 
2349
2925
  /* Flight Log empty state reuses attention-empty className with empty-state-cta. */
2350
2926
  .attention-empty {
2351
- padding: 14px 10px;
2927
+ padding: var(--mc-space-lg) var(--mc-space-md);
2352
2928
  font-size: 12px;
2353
2929
  color: var(--text-muted);
2354
2930
  line-height: 1.45;
@@ -2356,7 +2932,7 @@ a:focus-visible,
2356
2932
  .empty-state-cta.attention-empty,
2357
2933
  .empty-state-cta.activity-feed-empty {
2358
2934
  /* Shared renderer already sets padding; keep panel chrome size. */
2359
- padding: 14px 10px;
2935
+ padding: var(--mc-space-lg) var(--mc-space-md);
2360
2936
  }
2361
2937
 
2362
2938
  /* ===== Now execution panel (primary Current work) ===== */
@@ -2405,6 +2981,21 @@ a:focus-visible,
2405
2981
  color: var(--text-muted);
2406
2982
  background: var(--bg-card);
2407
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
+ }
2408
2999
  .now-plan-name {
2409
3000
  margin-top: 10px;
2410
3001
  font-size: 15px;
@@ -2653,8 +3244,8 @@ a:focus-visible,
2653
3244
  width: 100%;
2654
3245
  text-align: left;
2655
3246
  margin: 0;
2656
- padding: 10px 12px;
2657
- border-radius: 8px;
3247
+ padding: var(--mc-card-padding-dense);
3248
+ border-radius: var(--mc-radius);
2658
3249
  border: 1px solid var(--border);
2659
3250
  background: var(--bg-elevated, var(--bg-card));
2660
3251
  color: var(--text-primary);
@@ -2662,10 +3253,10 @@ a:focus-visible,
2662
3253
  font: inherit;
2663
3254
  line-height: 1.4;
2664
3255
  }
3256
+ /* Base hover/focus: keyboard-visible ring via --border-active (not invisible --border); kind rules below set colors. */
2665
3257
  .flight-log-card:hover,
2666
3258
  .flight-log-card:focus-visible {
2667
- border-color: var(--accent, var(--yellow));
2668
- outline: 2px solid var(--accent, var(--yellow));
3259
+ outline: 2px solid var(--border-active);
2669
3260
  outline-offset: 1px;
2670
3261
  }
2671
3262
  .flight-log-card-current {
@@ -2679,6 +3270,11 @@ a:focus-visible,
2679
3270
  .flight-log-card-current.flight-log-kind-residual .flight-log-card-label {
2680
3271
  color: var(--yellow);
2681
3272
  }
3273
+ .flight-log-card-current.flight-log-kind-residual:hover,
3274
+ .flight-log-card-current.flight-log-kind-residual:focus-visible {
3275
+ border-color: var(--yellow);
3276
+ outline-color: var(--yellow);
3277
+ }
2682
3278
  .flight-log-card-current.flight-log-kind-advice {
2683
3279
  border-color: var(--blue);
2684
3280
  background: var(--blue-bg);
@@ -2686,6 +3282,11 @@ a:focus-visible,
2686
3282
  .flight-log-card-current.flight-log-kind-advice .flight-log-card-label {
2687
3283
  color: var(--blue);
2688
3284
  }
3285
+ .flight-log-card-current.flight-log-kind-advice:hover,
3286
+ .flight-log-card-current.flight-log-kind-advice:focus-visible {
3287
+ border-color: var(--blue);
3288
+ outline-color: var(--blue);
3289
+ }
2689
3290
  .flight-log-card-current.flight-log-kind-ok {
2690
3291
  border-color: color-mix(in srgb, var(--green) 55%, var(--border));
2691
3292
  background: color-mix(in srgb, var(--green-bg) 40%, var(--bg-elevated, var(--bg-card)));
@@ -2693,6 +3294,11 @@ a:focus-visible,
2693
3294
  .flight-log-card-current.flight-log-kind-ok .flight-log-card-label {
2694
3295
  color: var(--green);
2695
3296
  }
3297
+ .flight-log-card-current.flight-log-kind-ok:hover,
3298
+ .flight-log-card-current.flight-log-kind-ok:focus-visible {
3299
+ border-color: var(--green);
3300
+ outline-color: var(--green);
3301
+ }
2696
3302
  .flight-log-card-current.flight-log-kind-warning {
2697
3303
  border-color: color-mix(in srgb, var(--orange, var(--yellow)) 70%, var(--border));
2698
3304
  background: color-mix(in srgb, var(--orange-bg, var(--yellow-bg)) 55%, var(--bg-elevated, var(--bg-card)));
@@ -2700,6 +3306,11 @@ a:focus-visible,
2700
3306
  .flight-log-card-current.flight-log-kind-warning .flight-log-card-label {
2701
3307
  color: var(--orange, var(--yellow));
2702
3308
  }
3309
+ .flight-log-card-current.flight-log-kind-warning:hover,
3310
+ .flight-log-card-current.flight-log-kind-warning:focus-visible {
3311
+ border-color: var(--orange, var(--yellow));
3312
+ outline-color: var(--orange, var(--yellow));
3313
+ }
2703
3314
  .flight-log-card-past {
2704
3315
  padding: 8px 10px;
2705
3316
  font-size: 11px;
@@ -2709,17 +3320,49 @@ a:focus-visible,
2709
3320
  .flight-log-card-past.flight-log-kind-residual {
2710
3321
  border-color: color-mix(in srgb, var(--yellow) 35%, var(--border));
2711
3322
  }
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 {
3325
+ border-color: color-mix(in srgb, var(--yellow) 55%, var(--border));
3326
+ outline-color: color-mix(in srgb, var(--yellow) 55%, var(--border));
3327
+ }
3328
+ .flight-log-card-past.flight-log-kind-residual:focus-visible {
3329
+ border-color: var(--yellow);
3330
+ outline-color: var(--yellow);
3331
+ }
2712
3332
  .flight-log-card-past.flight-log-kind-advice {
2713
3333
  border-color: color-mix(in srgb, var(--blue) 35%, var(--border));
2714
3334
  }
3335
+ .flight-log-card-past.flight-log-kind-advice:hover {
3336
+ border-color: color-mix(in srgb, var(--blue) 55%, var(--border));
3337
+ outline-color: color-mix(in srgb, var(--blue) 55%, var(--border));
3338
+ }
3339
+ .flight-log-card-past.flight-log-kind-advice:focus-visible {
3340
+ border-color: var(--blue);
3341
+ outline-color: var(--blue);
3342
+ }
2715
3343
  .flight-log-card-past.flight-log-kind-ok {
2716
3344
  border-color: color-mix(in srgb, var(--green) 30%, var(--border));
2717
3345
  }
3346
+ .flight-log-card-past.flight-log-kind-ok:hover {
3347
+ border-color: color-mix(in srgb, var(--green) 50%, var(--border));
3348
+ outline-color: color-mix(in srgb, var(--green) 50%, var(--border));
3349
+ }
3350
+ .flight-log-card-past.flight-log-kind-ok:focus-visible {
3351
+ border-color: var(--green);
3352
+ outline-color: var(--green);
3353
+ }
2718
3354
  .flight-log-card-past.flight-log-kind-warning {
2719
3355
  border-color: color-mix(in srgb, var(--orange, var(--yellow)) 30%, var(--border));
2720
3356
  }
3357
+ .flight-log-card-past.flight-log-kind-warning:hover {
3358
+ border-color: color-mix(in srgb, var(--orange, var(--yellow)) 50%, var(--border));
3359
+ outline-color: color-mix(in srgb, var(--orange, var(--yellow)) 50%, var(--border));
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
+ }
2721
3365
  .flight-log-card-warning {
2722
- padding: 10px 12px;
2723
3366
  border-color: color-mix(in srgb, var(--orange, var(--yellow)) 70%, var(--border));
2724
3367
  background: color-mix(in srgb, var(--orange-bg, var(--yellow-bg)) 55%, var(--bg-elevated, var(--bg-card)));
2725
3368
  font-size: 12px;
@@ -2728,6 +3371,11 @@ a:focus-visible,
2728
3371
  .flight-log-card-warning .flight-log-card-label {
2729
3372
  color: var(--orange, var(--yellow));
2730
3373
  }
3374
+ .flight-log-card-warning:hover,
3375
+ .flight-log-card-warning:focus-visible {
3376
+ border-color: var(--orange, var(--yellow));
3377
+ outline-color: var(--orange, var(--yellow));
3378
+ }
2731
3379
  .flight-log-card-label {
2732
3380
  display: block;
2733
3381
  font-weight: 600;
@@ -2842,6 +3490,7 @@ a:focus-visible,
2842
3490
  <span class="last-updated" id="lastUpdated" hidden></span>
2843
3491
  <button type="button" class="header-home-btn" id="headerHomeBtn" onclick="return goHome()" aria-label="Home, Overview" title="Home, Overview"></button>
2844
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>
2845
3494
  <div class="nav-more">
2846
3495
  <button type="button" class="nav-more-btn" id="navMoreBtn" aria-haspopup="menu" aria-expanded="false" aria-controls="navMoreMenu" title="More sections"></button>
2847
3496
  <div class="nav-more-menu" id="navMoreMenu" role="menu" aria-label="More sections" hidden>
@@ -2881,12 +3530,10 @@ a:focus-visible,
2881
3530
  </a>
2882
3531
  <a class="top-tab nav-more-item" role="menuitem" href="#" data-section="terminals" tabindex="0" onclick="return showSection('terminals')" aria-label="Terminals section">
2883
3532
  Terminals
2884
- <span class="dot dot-blue" id="navTerminalsDot" aria-hidden="true"></span>
2885
3533
  <span class="top-tab-badge" id="navTerminalsBadge">0</span>
2886
3534
  </a>
2887
3535
  <a class="top-tab nav-more-item" role="menuitem" href="#" data-section="processes" tabindex="0" onclick="return showSection('processes')" aria-label="Processes section">
2888
3536
  Processes
2889
- <span class="dot dot-green" id="navProcessesDot" aria-hidden="true"></span>
2890
3537
  <span class="top-tab-badge" id="navProcessesBadge">0</span>
2891
3538
  </a>
2892
3539
  <a class="top-tab nav-more-item" role="menuitem" href="#" data-section="config" tabindex="0" onclick="return showSection('config')" aria-label="Config section">
@@ -2894,7 +3541,7 @@ a:focus-visible,
2894
3541
  </a>
2895
3542
  <div class="nav-more-separator" role="separator"></div>
2896
3543
  <div class="nav-more-group" role="group" aria-labelledby="navSkinsLabel">
2897
- <div class="nav-more-group-label" id="navSkinsLabel">Skins</div>
3544
+ <div class="nav-more-group-label" id="navSkinsLabel" data-icon="skins">Skins</div>
2898
3545
  <button type="button" class="nav-more-item" role="menuitemradio" aria-checked="true" data-dashboard-skin-option="legacy" tabindex="0" onclick="return setDashboardSkin('legacy')">
2899
3546
  Legacy
2900
3547
  </button>
@@ -2906,6 +3553,7 @@ a:focus-visible,
2906
3553
  </div>
2907
3554
  </div>
2908
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>
2909
3557
 
2910
3558
  <!-- Main -->
2911
3559
  <div class="main">
@@ -2914,18 +3562,18 @@ a:focus-visible,
2914
3562
  <div class="top-tabs-row">
2915
3563
  <div class="cockpit-anchors" role="navigation" aria-label="Cockpit sections">
2916
3564
  <a class="top-nav-anchor active" href="#now-execution-panel" data-anchor="now-execution-panel" data-icon="current-mission" onclick="return goCockpitAnchor('now-execution-panel')" aria-current="true">
2917
- <span class="top-nav-anchor-label">Cockpit</span>
3565
+ <span class="top-nav-anchor-label">Current mission</span>
2918
3566
  <span class="top-tab-badge" id="navOverviewBadge">0</span>
2919
3567
  </a>
2920
- <a class="top-nav-anchor" href="#hero-activity" data-anchor="hero-activity" data-icon="monitor" onclick="return goCockpitAnchor('hero-activity')">
2921
- <span class="top-nav-anchor-label">Crew Monitor</span>
2922
- </a>
2923
3568
  <a class="top-nav-anchor" href="#attention-panel" data-anchor="attention-panel" data-icon="field-report" onclick="return goCockpitAnchor('attention-panel')">
2924
3569
  <span class="top-nav-anchor-label">Flight Log</span>
2925
3570
  </a>
2926
3571
  <a class="top-nav-anchor" href="#recent-plans-panel" data-anchor="recent-plans-panel" data-icon="checklist" onclick="return goCockpitAnchor('recent-plans-panel')">
2927
3572
  <span class="top-nav-anchor-label">Checklist</span>
2928
3573
  </a>
3574
+ <a class="top-nav-anchor" href="#hero-activity" data-anchor="hero-activity" data-icon="monitor" onclick="return goCockpitAnchor('hero-activity')">
3575
+ <span class="top-nav-anchor-label">Crew Monitor</span>
3576
+ </a>
2929
3577
  </div>
2930
3578
  </div>
2931
3579
  </nav>
@@ -3042,8 +3690,46 @@ function escapeJsString(value) {
3042
3690
  }
3043
3691
 
3044
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
+
3045
3731
  /** Active pane id (overview = Cockpit page; others = dropdown destinations). */
3046
- let activeSectionId = 'overview';
3732
+ let activeSectionId = sectionFromHash();
3047
3733
  /** Last Cockpit in-page anchor while overview is active. */
3048
3734
  let activeCockpitAnchor = 'now-execution-panel';
3049
3735
  /** Activity tab source filter chip id (`all` or a source key). */
@@ -3070,6 +3756,25 @@ function goHome() {
3070
3756
  return showSection('overview');
3071
3757
  }
3072
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
+
3073
3778
  /** Sync header Home chrome + section-title back control with active section. */
3074
3779
  function updateHeaderHomeChrome() {
3075
3780
  const onOverview = activeSectionId === 'overview';
@@ -3126,12 +3831,19 @@ function showSection(id) {
3126
3831
  }
3127
3832
 
3128
3833
  updateHeaderHomeChrome();
3834
+ syncSectionHash(activeSectionId);
3129
3835
 
3130
3836
  const content = document.getElementById('content');
3131
3837
  if (content) content.scrollTop = 0;
3132
3838
  return false;
3133
3839
  }
3134
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
+
3135
3847
  /**
3136
3848
  * Anchor scroll still travelling. A snapshot re-render restores the scroll
3137
3849
  * position captured when it started, which would strand a smooth scroll part
@@ -3295,6 +4007,11 @@ function injectCockpitNavIcons() {
3295
4007
  if (!kind) return;
3296
4008
  el.insertAdjacentHTML('afterbegin', spaceIconSvg(kind, { decorative: true }));
3297
4009
  });
4010
+ // Skins group label: palette glyph (distinct from Skills gear).
4011
+ const skinsLabel = document.getElementById('navSkinsLabel');
4012
+ if (skinsLabel && !skinsLabel.querySelector('.space-icon')) {
4013
+ skinsLabel.insertAdjacentHTML('afterbegin', spaceIconSvg('skins', { decorative: true }));
4014
+ }
3298
4015
  }
3299
4016
 
3300
4017
  // ===== Cockpit nav + more-menu keyboard / outside click =====
@@ -3386,14 +4103,15 @@ function progressColor(pct) {
3386
4103
  return 'red';
3387
4104
  }
3388
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).
3389
4109
  function statusDot(status) {
3390
4110
  const map = {
3391
4111
  completed: 'dot-green',
3392
- in_progress: 'dot-blue dot-pulse',
3393
- pending: 'dot-gray',
3394
4112
  cancelled: 'dot-red',
3395
4113
  };
3396
- return map[status] || 'dot-gray';
4114
+ return map[status] || null;
3397
4115
  }
3398
4116
 
3399
4117
  // ===== Client diagnostic activity =====
@@ -3471,11 +4189,12 @@ function semanticEventInfo(kind, commitType) {
3471
4189
  const map = {
3472
4190
  // Monitor live kinds: distinct resting color+icon (no kind-tag text, no left rail).
3473
4191
  // run_plan follows .now-status-executing green; delivery uses cyan to avoid green wash.
3474
- run_plan: { icon: '\u25b6', tag: 'tick', gloss: 'tick - live execution', color: 'var(--green)', bg: 'var(--green-bg)' },
3475
- handoff: { icon: '\u23f8', tag: 'handoff', gloss: 'handoff - awaiting gate', color: 'var(--yellow)', bg: 'var(--yellow-bg)' },
3476
- agent_step: { icon: '\u25c9', tag: 'step', gloss: 'step - agent/task unit', color: 'var(--purple)', bg: 'var(--purple-bg)' },
3477
- plan_progress: { icon: '\u2691', tag: 'plan', gloss: 'plan - milestone', color: 'var(--orange)', bg: 'var(--orange-bg)' },
3478
- 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)' },
3479
4198
  commit: { icon: '\u25cf', tag: 'commit', gloss: 'commit', color: 'var(--orange)', bg: 'var(--orange-bg)' },
3480
4199
  agent: { icon: '\u25c6', tag: 'agent', gloss: 'agent', color: 'var(--purple)', bg: 'var(--purple-bg)' },
3481
4200
  skill: { icon: '\u2726', tag: 'skill', gloss: 'skill', color: 'var(--orange)', bg: 'var(--orange-bg)' },
@@ -3487,12 +4206,12 @@ function semanticEventInfo(kind, commitType) {
3487
4206
  // Locked BMP map (hotfix 2026-07-27): feat✦ fix⚙ docs✎ chore⚒ pr⑂ ship✈
3488
4207
  if (kind === 'delivery' && commitType) {
3489
4208
  const subtypes = {
3490
- feat: { icon: '\u2726', tag: 'delivery', gloss: 'delivery - feat', color: 'var(--purple)', bg: 'var(--purple-bg)' },
3491
- fix: { icon: '\u2699', tag: 'delivery', gloss: 'delivery - fix', color: 'var(--red)', bg: 'var(--red-bg)' },
3492
- docs: { icon: '\u270e', tag: 'delivery', gloss: 'delivery - docs', color: 'var(--blue)', bg: 'var(--blue-bg)' },
3493
- chore: { icon: '\u2692', tag: 'delivery', gloss: 'delivery - chore', color: 'var(--orange)', bg: 'var(--orange-bg)' },
3494
- pr: { icon: '\u2442', tag: 'delivery', gloss: 'delivery - PR', color: 'var(--cyan)', bg: 'var(--cyan-bg)' },
3495
- 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)' },
3496
4215
  };
3497
4216
  if (subtypes[commitType]) return subtypes[commitType];
3498
4217
  }
@@ -3684,6 +4403,38 @@ function semanticEventTime(ev, _info) {
3684
4403
  return '';
3685
4404
  }
3686
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
+
3687
4438
  /**
3688
4439
  * Mission Control is read-only: it copies text to the clipboard and a human
3689
4440
  * pastes it. The panel cannot open a file, a chat, or an editor, so no label,
@@ -3834,6 +4585,59 @@ function checklistPlanActionCommands(plan) {
3834
4585
  };
3835
4586
  }
3836
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
+
3837
4641
  let openRecentPlanActionsKey = null;
3838
4642
 
3839
4643
  /** Anchor the fixed Actions menu above or below the trigger; escape panel overflow. */
@@ -4192,6 +4996,118 @@ function renderGitFileList(git) {
4192
4996
  `;
4193
4997
  }
4194
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
+
4195
5111
  // ===== Copy to clipboard + toast (Phase 1) =====
4196
5112
  /** Fallback confirmation. Callers should pass a destination-aware message. */
4197
5113
  function copyToastLabel(text) {
@@ -4209,7 +5125,9 @@ async function copyToClipboard(text, options) {
4209
5125
  }
4210
5126
  throw new Error('Clipboard API unavailable');
4211
5127
  } catch {
4212
- 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);
4213
5131
  }
4214
5132
  }
4215
5133
 
@@ -4291,10 +5209,14 @@ function renderConfigSection(d) {
4291
5209
  return `
4292
5210
  <div class="content-section" id="section-config">
4293
5211
  <div class="section-title">
4294
- <span class="dot dot-blue"></span> Config
5212
+ Config
4295
5213
  <span class="section-subtitle">Session prefs in .cursor/context/config.json</span>
4296
5214
  </div>
4297
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>
4298
5220
  <fieldset class="config-fieldset">
4299
5221
  <legend>Read-only</legend>
4300
5222
  <p class="config-readonly">Onboarded: <strong>${escapeHtml(onboarded)}</strong> · Onboarding status: <strong>${escapeHtml(onboardingStatus)}</strong></p>
@@ -4309,7 +5231,7 @@ function renderConfigSection(d) {
4309
5231
  <div class="config-row">
4310
5232
  <label for="config-interTickCooldownMs">Inter-tick cooldown (ms)</label>
4311
5233
  <input type="number" id="config-interTickCooldownMs" name="interTickCooldownMs" min="0" max="3600000" step="1" value="${escapeAttr(String(cooldown))}" data-focus-key="config-interTickCooldownMs" />
4312
- <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>
4313
5235
  </div>
4314
5236
  <div class="config-row config-row-inline">
4315
5237
  <input type="checkbox" id="config-cadence-enabled" name="cadenceEnabled" ${cadenceEnabled ? 'checked' : ''} data-focus-key="config-cadence-enabled" />
@@ -4318,8 +5240,10 @@ function renderConfigSection(d) {
4318
5240
  <div class="config-row">
4319
5241
  <label for="config-cadence-threshold">Cadence tick threshold</label>
4320
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" />
4321
- <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>
4322
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>
4323
5247
  </fieldset>
4324
5248
  <fieldset class="config-fieldset">
4325
5249
  <legend>Update check (opt-in)</legend>
@@ -4330,10 +5254,12 @@ function renderConfigSection(d) {
4330
5254
  <div class="config-row">
4331
5255
  <label for="config-updateCheck-intervalDays">Check interval (days)</label>
4332
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" />
4333
- <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>
4334
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>
4335
5261
  </fieldset>
4336
- <fieldset class="config-fieldset">
5262
+ <fieldset class="config-fieldset config-fieldset-wide">
4337
5263
  <legend>Audits (external plan review)</legend>
4338
5264
  <div class="config-row config-row-inline">
4339
5265
  <input type="checkbox" id="config-epr-enabled" name="eprEnabled" ${epr.enabled ? 'checked' : ''} data-focus-key="config-epr-enabled" />
@@ -4352,6 +5278,7 @@ function renderConfigSection(d) {
4352
5278
  <select id="config-epr-backend" name="eprBackend" data-focus-key="config-epr-backend">
4353
5279
  <option value="claude"${epr.backend === 'claude' || !epr.backend ? ' selected' : ''}>claude</option>
4354
5280
  </select>
5281
+ <span class="config-hint">claude is the only backend today.</span>
4355
5282
  </div>
4356
5283
  <div class="config-row">
4357
5284
  <label for="config-epr-mode">Arming mode</label>
@@ -4359,7 +5286,7 @@ function renderConfigSection(d) {
4359
5286
  <option value="paste"${epr.mode !== 'autonomous' ? ' selected' : ''}>paste (legacy)</option>
4360
5287
  <option value="autonomous"${epr.mode === 'autonomous' ? ' selected' : ''}>autonomous</option>
4361
5288
  </select>
4362
- <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>
4363
5290
  </div>
4364
5291
  <div class="config-row config-row-inline">
4365
5292
  <input type="checkbox" id="config-epr-midBatch" name="eprMidBatch" ${epr.midBatchAudits ? 'checked' : ''} data-focus-key="config-epr-midBatch" />
@@ -4374,8 +5301,9 @@ function renderConfigSection(d) {
4374
5301
  </select>
4375
5302
  <span class="config-hint">Check owed/untriaged audits before plan-run commands. Missing key = off.</span>
4376
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>
4377
5305
  </fieldset>
4378
- <fieldset class="config-fieldset">
5306
+ <fieldset class="config-fieldset config-fieldset-wide">
4379
5307
  <legend>Agent Personas</legend>
4380
5308
  <div class="config-row">
4381
5309
  <label for="config-persona-default">Default persona</label>
@@ -4384,19 +5312,17 @@ function renderConfigSection(d) {
4384
5312
  </select>
4385
5313
  <span class="config-hint">Chat/CLI tone only. Interface Skins stay in the More menu.</span>
4386
5314
  </div>
4387
- ${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>
4388
5319
  </fieldset>
4389
- <button type="submit" class="btn-config-save" id="config-save-btn" data-focus-key="config-save-btn">Save</button>
4390
5320
  </form>
4391
5321
  </div>
4392
5322
  `;
4393
5323
  }
4394
5324
 
4395
- async function saveMissionConfig(event) {
4396
- if (event && typeof event.preventDefault === 'function') event.preventDefault();
4397
- const btn = document.getElementById('config-save-btn');
4398
- if (btn) btn.disabled = true;
4399
-
5325
+ function collectMissionConfigPayload() {
4400
5326
  const autoHandoff = !!document.getElementById('config-autoHandoff')?.checked;
4401
5327
  const cooldownRaw = document.getElementById('config-interTickCooldownMs')?.value;
4402
5328
  const interTickCooldownMs = Number.parseInt(String(cooldownRaw || '0'), 10);
@@ -4437,6 +5363,51 @@ async function saveMissionConfig(event) {
4437
5363
  const el = document.getElementById(`config-persona-${mode.id}`);
4438
5364
  if (el && el.value) payload.agentPersona.modes[mode.id] = el.value;
4439
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();
4440
5411
 
4441
5412
  try {
4442
5413
  const res = await fetch('/api/config', {
@@ -4470,20 +5441,151 @@ async function saveMissionConfig(event) {
4470
5441
  return false;
4471
5442
  }
4472
5443
 
4473
- // ===== Toggle agent details (Phase 1) =====
4474
- function toggleAgentDetails(id) {
4475
- const el = document.getElementById('agent-details-' + id);
4476
- if (!el) return;
4477
- el.classList.toggle('open');
4478
- const arrow = document.getElementById('agent-arrow-' + id);
4479
- if (arrow) {
4480
- arrow.style.transform = el.classList.contains('open') ? 'rotate(180deg)' : 'rotate(0deg)';
5444
+ // ===== Healthcenter: per-check expand + Autofix map (copy-only) =====
5445
+ /** Fixed v1 check set: plans, handoff, agents, commands, memory, git, config. */
5446
+ const HEALTH_CHECK_META = {
5447
+ plans: {
5448
+ okDetail: 'Plans directory is present with at least one plan file.',
5449
+ failDetail: 'No plans under .cursor/plans/, or the plans directory is missing.',
5450
+ autofix: { text: '/start-project', subject: '/start-project', destination: 'chatInput', label: 'Autofix' },
5451
+ },
5452
+ handoff: {
5453
+ okDetail: 'HANDOFF.md is present and parseable (idle Plan is still healthy).',
5454
+ failDetail: 'HANDOFF.md is missing or not parseable.',
5455
+ autofix: { text: '/continue-plan', subject: '/continue-plan', destination: 'chatInput', label: 'Autofix' },
5456
+ },
5457
+ agents: {
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,
5462
+ },
5463
+ commands: {
5464
+ okDetail: 'Slash commands are available under .cursor/commands/.',
5465
+ failDetail: 'No commands under .cursor/commands/.',
5466
+ autofix: { text: '.cursor/commands/', subject: 'path .cursor/commands/', destination: 'filePicker', label: 'Copy path' },
5467
+ },
5468
+ memory: {
5469
+ okDetail: 'Memory has recorded errors and/or decisions.',
5470
+ failDetail: 'Memory index has no errors or decisions yet (optional until first WRITE).',
5471
+ autofix: { text: '/agent-kit-onboard', subject: '/agent-kit-onboard', destination: 'chatInput', label: 'Autofix' },
5472
+ },
5473
+ git: {
5474
+ okDetail: 'Git branch signal is available.',
5475
+ failDetail: 'Git repository signal missing (no branch).',
5476
+ autofix: { text: 'git status', subject: 'git status', destination: 'terminal', label: 'Autofix' },
5477
+ },
5478
+ config: {
5479
+ okDetail: 'Workspace config is loaded.',
5480
+ failDetail: 'Workspace config is missing or not loaded.',
5481
+ autofix: { text: 'agent-kit doctor', subject: 'agent-kit doctor', destination: 'terminal', label: 'Autofix' },
5482
+ },
5483
+ };
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
+
5493
+ function healthCheckSeverity(check, aggregateStatus) {
5494
+ if (check && check.ok) return 'ok';
5495
+ if (aggregateStatus === 'degraded') return 'degraded';
5496
+ if (aggregateStatus === 'error') return 'error';
5497
+ return 'warning';
5498
+ }
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
+
5513
+ function healthSeverityLabel(sev) {
5514
+ return healthSeverityChrome(sev).label;
5515
+ }
5516
+
5517
+ function healthDotClass(sev) {
5518
+ return `dot-${healthSeverityChrome(sev).tone}`;
5519
+ }
5520
+
5521
+ function toggleHealthCheck(checkId) {
5522
+ const card = document.getElementById('health-card-' + checkId);
5523
+ if (!card) return;
5524
+ const open = card.classList.toggle('is-expanded');
5525
+ const btn = card.querySelector('.health-item');
5526
+ if (btn) btn.setAttribute('aria-expanded', open ? 'true' : 'false');
5527
+ }
5528
+
5529
+ function healthCheckKeydown(event, checkId) {
5530
+ if (event.key === 'Enter' || event.key === ' ') {
5531
+ event.preventDefault();
5532
+ toggleHealthCheck(checkId);
4481
5533
  }
4482
5534
  }
4483
5535
 
4484
- // ===== Health check info (Phase 1) =====
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
+
5587
+ /** @deprecated Prefer toggleHealthCheck; kept for any leftover callers. */
4485
5588
  function showHealthInfo(el, ok) {
4486
- // Remove any existing message siblings
4487
5589
  const parent = el.parentElement;
4488
5590
  const existing = parent.querySelector('.health-message');
4489
5591
  if (existing) { existing.remove(); return; }
@@ -4571,7 +5673,8 @@ function updateTransportChrome() {
4571
5673
  if (!statusDot || !statusLabel) return;
4572
5674
 
4573
5675
  const healthStatus = data?.health?.status || 'ok';
4574
- const healthTone = healthStatus === 'ok' ? 'green' : healthStatus === 'warning' ? 'yellow' : 'red';
5676
+ const healthChrome = healthSeverityChrome(healthStatus);
5677
+ const healthTone = healthChrome.tone;
4575
5678
 
4576
5679
  if (sseReconnecting) {
4577
5680
  statusDot.className = 'dot dot-yellow dot-pulse reconnecting-pulse';
@@ -4587,10 +5690,14 @@ function updateTransportChrome() {
4587
5690
  statusDot.className = healthStatus === 'ok'
4588
5691
  ? 'dot dot-green'
4589
5692
  : `dot dot-${healthTone} dot-pulse`;
4590
- 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);
4591
5696
  } else {
4592
5697
  statusDot.className = `dot dot-${healthStatus === 'ok' ? 'yellow' : healthTone}`;
4593
- 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);
4594
5701
  }
4595
5702
 
4596
5703
  if (sseTimer) {
@@ -4708,6 +5815,7 @@ function nowFingerprint(now) {
4708
5815
  nextTodo: now.nextTodo?.id || null,
4709
5816
  modifiedAt: now.modifiedAt,
4710
5817
  lifecycle: now.lifecycle,
5818
+ busyOutsidePlan: now.busyOutsidePlan?.active === true,
4711
5819
  });
4712
5820
  }
4713
5821
 
@@ -4772,7 +5880,6 @@ function captureUiState() {
4772
5880
  return {
4773
5881
  scrollTop: contentEl ? contentEl.scrollTop : 0,
4774
5882
  focusKey,
4775
- openAgents: Array.from(document.querySelectorAll('.agent-details.open')).map((el) => el.id),
4776
5883
  panelScrolls: capturePanelScrollOffsets(),
4777
5884
  openRecentPlanActionsKey,
4778
5885
  };
@@ -4780,14 +5887,6 @@ function captureUiState() {
4780
5887
 
4781
5888
  function restoreUiState(state) {
4782
5889
  if (!state) return;
4783
- for (const id of state.openAgents || []) {
4784
- const el = document.getElementById(id);
4785
- if (!el) continue;
4786
- el.classList.add('open');
4787
- const arrowId = id.replace(/^agent-details-/, 'agent-arrow-');
4788
- const arrow = document.getElementById(arrowId);
4789
- if (arrow) arrow.style.transform = 'rotate(180deg)';
4790
- }
4791
5890
  const contentEl = document.getElementById('content');
4792
5891
  if (contentEl) {
4793
5892
  if (pendingAnchorScroll && Date.now() < pendingAnchorScroll.until) {
@@ -4870,8 +5969,20 @@ function mergePlansForUi(d) {
4870
5969
  .split('/')
4871
5970
  .pop();
4872
5971
  const enriched = byKey.get(key) || {};
4873
- const completed = raw.todos?.completed ?? enriched.progress?.completed ?? 0;
4874
- 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);
4875
5986
  let lifecycle = enriched.lifecycle;
4876
5987
  if (!lifecycle) {
4877
5988
  if (total > 0 && completed >= total && (raw.todos?.inProgress || 0) === 0) {
@@ -4887,14 +5998,16 @@ function mergePlansForUi(d) {
4887
5998
  overview: raw.overview || enriched.overview || '',
4888
5999
  modifiedAt: raw.modifiedAt || enriched.modifiedAt || null,
4889
6000
  progressPct:
4890
- typeof raw.progress === 'number'
4891
- ? raw.progress
4892
- : total > 0
4893
- ? Math.round((completed / total) * 100)
6001
+ total > 0
6002
+ ? Math.round((completed / total) * 100)
6003
+ : typeof raw.progress === 'number'
6004
+ ? raw.progress
4894
6005
  : 0,
4895
- progressLabel: enriched.progress?.label || `${completed} of ${total}`,
6006
+ progressLabel: `${completed} of ${total} complete`,
4896
6007
  progressCompleted: completed,
4897
6008
  progressTotal: total,
6009
+ progressInProgress: inProgress,
6010
+ nextActionTodo,
4898
6011
  lifecycle,
4899
6012
  // /run-plan-all layer from the semantic model (display-only; the panel
4900
6013
  // never writes the queue). 'none' / null outside queue mode.
@@ -5157,21 +6270,26 @@ function renderPlansAccordion(d) {
5157
6270
  ${queueRolePill(p)}
5158
6271
  <span class="recent-plan-progress">${escapeHtml(p.progressLabel)}</span>
5159
6272
  </span>
5160
- <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">
5161
6274
  <span class="progress-fill plan-progress-fill ${color}${life.key === 'executing' ? ' is-executing' : ''}" style="width:${pct}%"></span>
5162
6275
  </span>
5163
6276
  <span class="plan-accordion-overview">${escapeHtml(p.overview || '\u2014')}</span>
5164
6277
  <span class="plan-accordion-meta">
5165
6278
  <span>${fmtDate(p.modifiedAt)}</span>
5166
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>` : ''}
5167
6282
  </span>
5168
6283
  </span>
5169
6284
  <span class="plan-accordion-chevron" aria-hidden="true">\u25BC</span>
5170
6285
  </button>
5171
6286
  <div class="plan-accordion-panel" id="plan-panel-${escapeAttr(key)}" role="region" aria-labelledby="plan-trigger-${escapeAttr(key)}"${open ? '' : ' hidden'}>
5172
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
+ : ''}
5173
6291
  <div class="plan-accordion-actions">
5174
- <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('')}
5175
6293
  </div>
5176
6294
  ${todos.length > 0
5177
6295
  ? `<div class="plan-todo-list plan-todo-list-full" role="list">
@@ -5184,7 +6302,7 @@ function renderPlansAccordion(d) {
5184
6302
  title="${escapeAttr(pathActionTitle(p.path))}"
5185
6303
  onclick="${copyRepoPathHandler(p.path)}"
5186
6304
  onkeydown="if(event.key==='Enter'||event.key===' '){event.preventDefault();${copyRepoPathHandler(p.path)}}">
5187
- <span class="dot ${statusDot(t.status)}"></span>
6305
+ ${statusDot(t.status) ? `<span class="dot ${statusDot(t.status)}" aria-hidden="true"></span>` : ''}
5188
6306
  <span style="flex:1"><code style="font-size:11px">${escapeHtml(t.id || '')}</code>${t.content ? ` \u2014 ${escapeHtml(t.content)}` : ''}</span>
5189
6307
  <span style="color:var(--text-muted);font-size:11px">${escapeHtml(t.status)}</span>
5190
6308
  </div>`,
@@ -5290,9 +6408,13 @@ function nowMetaIconSvg(kind, opts) {
5290
6408
  /**
5291
6409
  * Space-theme Cockpit/nav icon set (inline SVG, no font/fetch/dependency).
5292
6410
  * Shared chrome SVG shell with header refresh / nowMetaIconSvg:
5293
- * viewBox 0 0 16 16, stroke 0.5, round caps/joins, CSS box via --mc-chrome-icon-size.
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.
5294
6416
  * Static path markup only; never concatenate untrusted text into the SVG.
5295
- * @param {'current-mission'|'monitor'|'field-report'|'checklist'|'more-sections'|'overview'|'plans'|'activity'|'agents'|'skills'|'commands'|'health'|'git'|'memory'|'terminals'|'processes'|'config'} kind
6417
+ * @param {'current-mission'|'monitor'|'field-report'|'checklist'|'more-sections'|'overview'|'plans'|'activity'|'agents'|'skills'|'skins'|'commands'|'health'|'git'|'memory'|'terminals'|'processes'|'config'} kind
5296
6418
  * @param {{ decorative?: boolean }} [opts] decorative true (default): aria-hidden next to a visible label.
5297
6419
  * decorative false: role=img + aria-label + title for icon-only controls (e.g. more-sections).
5298
6420
  */
@@ -5310,6 +6432,7 @@ function spaceIconSvg(kind, opts) {
5310
6432
  activity: 'Activity',
5311
6433
  agents: 'Agents',
5312
6434
  skills: 'Skills',
6435
+ skins: 'Skins',
5313
6436
  commands: 'Commands',
5314
6437
  health: 'Health',
5315
6438
  git: 'Git',
@@ -5319,37 +6442,35 @@ function spaceIconSvg(kind, opts) {
5319
6442
  config: 'Config',
5320
6443
  };
5321
6444
  const paths = {
5322
- // Rocket (mission) — thin stroke matching helmet aesthetic
6445
+ // Rocket (mission) — window is a solid dot (a stroked ring here would sit below the stroke floor)
5323
6446
  'current-mission':
5324
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"/>' +
5325
- '<circle cx="8" cy="7.5" r=".9"/>' +
6448
+ '<circle cx="8" cy="7.5" r="1" fill="currentColor" stroke="none"/>' +
5326
6449
  '<path d="M6 10.5l-1 2M10 10.5l1 2M7 13h2"/>',
5327
- // 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
5328
6451
  monitor:
5329
6452
  '<circle cx="8" cy="8" r="5.5"/>' +
5330
- '<circle cx="8" cy="8" r="3.5"/>' +
5331
- '<circle cx="8" cy="8" r="1.5"/>' +
5332
- '<path d="M8 8L12.8 4.2A5.5 5.5 0 0 1 13.3 7.2Z"/>' +
5333
- '<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"/>',
5334
6455
  // Clipboard (Flight Log) — matches copy/source affordance; not PTT radio
5335
6456
  'field-report':
5336
6457
  '<rect x="4.5" y="3.5" width="7" height="10" rx="1.2"/>' +
5337
6458
  '<path d="M6.5 3.5V2.5a1.5 1.5 0 013 0v1"/>' +
5338
6459
  '<path d="M6.5 7h3M6.5 9.5h3"/>',
5339
- // Checklist lines — thin
6460
+ // Checklist lines
5340
6461
  checklist:
5341
6462
  '<path d="M3.5 4.5h9M3.5 8h6.5M3.5 11.5h5.5"/>' +
5342
6463
  '<path d="M10.5 10.5l1.2 1.2L14 9.5"/>',
5343
- // Constellation dots (more sections) — outline circles matching thin stroke
6464
+ // Ellipsis (more sections) — solid dots: stroked rings at 4px spacing land on exact tangency
5344
6465
  'more-sections':
5345
- '<circle cx="4" cy="8" r=".8"/>' +
5346
- '<circle cx="8" cy="8" r=".8"/>' +
5347
- '<circle cx="12" cy="8" r=".8"/>',
5348
- // House (overview / home)
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
5349
6470
  overview:
5350
- '<path d="M2.5 8.5L8 3l5.5 5.5"/>' +
5351
- '<path d="M4.5 10v4h3M11.5 10v4h-3"/>' +
5352
- '<path d="M4.5 14h7"/>',
6471
+ '<path d="M2.5 7.5L8 2.75l5.5 4.75"/>' +
6472
+ '<path d="M4.25 7.75v6.5h7.5v-6.5"/>' +
6473
+ '<path d="M6.5 14.25v-3.25h3v3.25"/>',
5353
6474
  // Document (plans)
5354
6475
  plans:
5355
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"/>' +
@@ -5361,10 +6482,14 @@ function spaceIconSvg(kind, opts) {
5361
6482
  agents:
5362
6483
  '<circle cx="8" cy="5.5" r="2.5"/>' +
5363
6484
  '<path d="M3 14.5c0-3 2.2-5 5-5s5 2 5 5"/>',
5364
- // Gear (skills)
6485
+ // Gear (skills) — ring + 8 spokes outside it; arcs/teeth cut into the ring would sink below the stroke floor
5365
6486
  skills:
5366
6487
  '<circle cx="8" cy="8" r="2"/>' +
5367
- '<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"/>',
6489
+ // Overlapping swatches (skins) — distinct from Skills gear
6490
+ skins:
6491
+ '<rect x="2.5" y="3.5" width="7" height="7" rx="1.25"/>' +
6492
+ '<rect x="6.5" y="5.5" width="7" height="7" rx="1.25"/>',
5368
6493
  // Terminal (commands)
5369
6494
  commands:
5370
6495
  '<path d="M2.5 3.5l5 5-5 5"/>' +
@@ -5378,11 +6503,11 @@ function spaceIconSvg(kind, opts) {
5378
6503
  '<path d="M10.5 4.5v7a2 2 0 01-2 2h-2"/>' +
5379
6504
  '<path d="M6.5 11.5a2 2 0 100-4 2 2 0 000 4z"/>' +
5380
6505
  '<path d="M10.5 6.5v2"/>',
5381
- // Chip (memory)
6506
+ // Chip (memory) — two internal lines 3 units apart; a third line would drop clearance below the stroke floor
5382
6507
  memory:
5383
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"/>' +
5384
6509
  '<path d="M6.5 1.5v2M9.5 1.5v2M6.5 12.5v2M9.5 12.5v2"/>' +
5385
- '<path d="M6 6.5h4M6 8.5h4M6 10.5h2.5"/>',
6510
+ '<path d="M6 6.5h4M6 9.5h2.5"/>',
5386
6511
  // Screen (terminals)
5387
6512
  terminals:
5388
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"/>' +
@@ -5405,7 +6530,7 @@ function spaceIconSvg(kind, opts) {
5405
6530
  const a11y = decorative
5406
6531
  ? 'aria-hidden="true"'
5407
6532
  : `role="img" aria-label="${label}" title="${label}"`;
5408
- return `<svg class="space-icon" width="16" height="16" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="0.5" stroke-linecap="round" stroke-linejoin="round" ${a11y}>${body}</svg>`;
6533
+ return `<svg class="space-icon" width="16" height="16" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" ${a11y}>${body}</svg>`;
5409
6534
  }
5410
6535
 
5411
6536
  /**
@@ -5504,9 +6629,10 @@ function flightLogKindClassName(kind) {
5504
6629
  }
5505
6630
 
5506
6631
  /**
5507
- * Gaps card on Flight Log (Live large / Earlier smaller). Labels locked.
6632
+ * Gaps card on Flight Log (NOW large / Earlier smaller). Labels locked.
5508
6633
  * Kind selects Mission Control palette tokens (typed notification chrome).
5509
- * @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
5510
6636
  * @param {number} idx
5511
6637
  */
5512
6638
  function renderFlightLogCard(entry, idx) {
@@ -5517,7 +6643,7 @@ function renderFlightLogCard(entry, idx) {
5517
6643
  ? entry.sourcePath.trim()
5518
6644
  : '.cursor/HANDOFF.md';
5519
6645
  const variant = entry?.variant === 'past' ? 'past' : 'current';
5520
- const label = variant === 'current' ? 'Live' : 'Earlier';
6646
+ const label = variant === 'current' ? 'NOW' : 'Earlier';
5521
6647
  const kind = flightLogMessageKind(text, { kind: entry?.kind });
5522
6648
  const kindClass = flightLogKindClassName(kind);
5523
6649
  const metaBits = [];
@@ -5525,8 +6651,16 @@ function renderFlightLogCard(entry, idx) {
5525
6651
  metaBits.push(escapeHtml(String(entry.at)));
5526
6652
  }
5527
6653
  metaBits.push(escapeHtml(sourcePath));
5528
- const copyTextHandler = copyForPasteHandler(text, 'Gaps text', 'chatInput');
5529
- 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');
5530
6664
  const cardClass =
5531
6665
  variant === 'current'
5532
6666
  ? `flight-log-card flight-log-card-current ${kindClass}`
@@ -5538,8 +6672,7 @@ function renderFlightLogCard(entry, idx) {
5538
6672
  <div class="flight-log-card-text">${escapeHtml(text)}</div>
5539
6673
  <div class="flight-log-card-meta">${metaBits.join(' · ')}</div>
5540
6674
  <div class="flight-log-card-actions">
5541
- <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>
5542
- <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>
5543
6676
  </div>
5544
6677
  </div>
5545
6678
  `;
@@ -5547,8 +6680,9 @@ function renderFlightLogCard(entry, idx) {
5547
6680
 
5548
6681
  /**
5549
6682
  * Operator Warning card on Flight Log (API/usage or orchestrator heads-up).
5550
- * No Review all / Resolve all / cadence CTAs.
5551
- * @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
5552
6686
  * @param {number} idx
5553
6687
  */
5554
6688
  function renderFlightLogWarningCard(warning, idx) {
@@ -5563,8 +6697,17 @@ function renderFlightLogWarningCard(warning, idx) {
5563
6697
  ? warning.sourcePath.trim()
5564
6698
  : '.cursor/HANDOFF.md';
5565
6699
  const kind = typeof warning?.kind === 'string' ? warning.kind : 'warning';
5566
- const copyTextHandler = copyForPasteHandler(text, 'Warning text', 'chatInput');
5567
- 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');
5568
6711
  const aria = `${title}: ${text}`;
5569
6712
  return `
5570
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)}">
@@ -5572,8 +6715,7 @@ function renderFlightLogWarningCard(warning, idx) {
5572
6715
  <div class="flight-log-card-text">${escapeHtml(text)}</div>
5573
6716
  <div class="flight-log-card-meta">${escapeHtml(sourcePath)}</div>
5574
6717
  <div class="flight-log-card-actions">
5575
- <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>
5576
- <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>
5577
6719
  </div>
5578
6720
  </div>
5579
6721
  `;
@@ -5581,9 +6723,9 @@ function renderFlightLogWarningCard(warning, idx) {
5581
6723
 
5582
6724
  /**
5583
6725
  * Quiet-state open-triage row (untriaged external review). Prompt chrome;
5584
- * per-row Copy triage + path only. No Review all / Resolve all.
6726
+ * one composed triage-command button per row. No Review all / Resolve all.
5585
6727
  * Kind class from flightLogKindClassName('prompt') (SoT map; not a literal).
5586
- * @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
5587
6729
  * @param {number} idx
5588
6730
  */
5589
6731
  function renderFlightLogQuietOpenTriageCard(item, idx) {
@@ -5600,8 +6742,19 @@ function renderFlightLogQuietOpenTriageCard(item, idx) {
5600
6742
  typeof item?.action?.target === 'string' && item.action.target.trim()
5601
6743
  ? item.action.target.trim()
5602
6744
  : `/plan-review-triage ${sourcePath}`;
5603
- const copyTriageHandler = copyForPasteHandler(triageCmd, 'triage command', 'chatInput');
5604
- 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');
5605
6758
  const aria = `Review: ${label}`;
5606
6759
  const kindClass = flightLogKindClassName('prompt');
5607
6760
  return `
@@ -5610,8 +6763,7 @@ function renderFlightLogQuietOpenTriageCard(item, idx) {
5610
6763
  <div class="flight-log-card-text">${escapeHtml(label)}</div>
5611
6764
  <div class="flight-log-card-meta">${escapeHtml(sourcePath)}</div>
5612
6765
  <div class="flight-log-card-actions">
5613
- <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>
5614
- <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>
5615
6767
  </div>
5616
6768
  </div>
5617
6769
  `;
@@ -5691,6 +6843,7 @@ function renderAttentionPanel(d, attentionChanged) {
5691
6843
  sourcePath: e.sourcePath || sourcePath,
5692
6844
  variant: 'past',
5693
6845
  kind: e.kind || null,
6846
+ action: e.action && typeof e.action === 'object' ? e.action : null,
5694
6847
  },
5695
6848
  idx,
5696
6849
  ),
@@ -5736,11 +6889,15 @@ function renderAttentionPanel(d, attentionChanged) {
5736
6889
  sourcePath,
5737
6890
  variant: 'current',
5738
6891
  kind: typeof fl?.currentKind === 'string' ? fl.currentKind : null,
6892
+ action:
6893
+ fl?.currentAction && typeof fl.currentAction === 'object'
6894
+ ? fl.currentAction
6895
+ : null,
5739
6896
  },
5740
6897
  0,
5741
6898
  )
5742
- : `<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">
5743
- <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>
5744
6901
  <div class="flight-log-card-text" style="color:var(--text-muted)">Quiet · nothing live</div>
5745
6902
  </div>`;
5746
6903
  body = `${warningsBlock}<div class="flight-log-stack" role="list">${currentCard}${pastCards}</div>`;
@@ -5764,6 +6921,7 @@ function renderAttentionPanel(d, attentionChanged) {
5764
6921
  <div class="card${attentionChanged ? ' card-flash' : ''}" id="attention-panel">
5765
6922
  <div class="card-header now-panel-header">
5766
6923
  <span class="card-title">${spaceIconSvg('field-report')}Flight Log</span>
6924
+ ${renderBusyOutsideChip(d.missionControl?.now)}
5767
6925
  <span class="section-subtitle" style="margin-left:auto">${escapeHtml(countLabel)}</span>
5768
6926
  </div>
5769
6927
  <div class="panel-scroll-body">${body}</div>
@@ -5861,7 +7019,9 @@ function flightLogFingerprint(d) {
5861
7019
  )
5862
7020
  .join('\n')
5863
7021
  : '';
5864
- 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' : ''}`;
5865
7025
  }
5866
7026
 
5867
7027
  // Step progress bar: one segment per plan step (completed / current / remaining).
@@ -5997,6 +7157,14 @@ function renderNowStepper(now, handoff, status, meta) {
5997
7157
  `;
5998
7158
  }
5999
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
+
6000
7168
  function renderNowExecutionPanel(d, nowChanged) {
6001
7169
  const now = d.missionControl?.now || null;
6002
7170
  const handoff = d.system?.handoff || null;
@@ -6015,6 +7183,7 @@ function renderNowExecutionPanel(d, nowChanged) {
6015
7183
  const updatedSource = now?.modifiedAt || handoff?.lastUpdated || null;
6016
7184
 
6017
7185
  const statusClass = `now-status now-status-${meta.key}${meta.live ? ' now-status-live' : ''}`;
7186
+ const busyChip = renderBusyOutsideChip(now);
6018
7187
 
6019
7188
  // True idle: no HANDOFF plan reference (or non-mission status). Reserve empty IDLE for that case.
6020
7189
  if (!showMission) {
@@ -6027,6 +7196,7 @@ function renderNowExecutionPanel(d, nowChanged) {
6027
7196
  <span class="now-status-mark" aria-hidden="true">${meta.mark}</span>
6028
7197
  ${escapeHtml(meta.label)}
6029
7198
  </span>
7199
+ ${busyChip}
6030
7200
  </div>
6031
7201
  <div class="panel-scroll-body">
6032
7202
  ${renderEmptyStateCta({
@@ -6058,6 +7228,7 @@ function renderNowExecutionPanel(d, nowChanged) {
6058
7228
  <span class="now-status-mark" aria-hidden="true">${meta.mark}</span>
6059
7229
  ${escapeHtml(meta.label)}
6060
7230
  </span>
7231
+ ${busyChip}
6061
7232
  </div>
6062
7233
  <div class="panel-scroll-body">
6063
7234
  <div class="now-plan-name">${escapeHtml(planName)}</div>
@@ -6234,14 +7405,7 @@ function renderUnsafe() {
6234
7405
  gitDot.className = 'dot dot-green';
6235
7406
  }
6236
7407
 
6237
- // Terminals / processes: steady state color only
6238
- const termDot = document.getElementById('navTerminalsDot');
6239
- termDot.className = `dot dot-${d.terminals.length > 0 ? 'blue' : 'gray'}`;
6240
-
6241
- const procDot = document.getElementById('navProcessesDot');
6242
- if (procDot) {
6243
- procDot.className = `dot dot-${d.processes.length > 0 ? 'green' : 'gray'}`;
6244
- }
7408
+ // Terminals / processes nav items carry count badges only (no decorative dots).
6245
7409
 
6246
7410
  // Build full HTML string once
6247
7411
  const parts = [];
@@ -6266,6 +7430,10 @@ function renderUnsafe() {
6266
7430
  <div class="overview-stack">
6267
7431
  ${renderNowExecutionPanel(d, !!(nowChanged || handoffChanged))}
6268
7432
 
7433
+ ${renderAttentionPanel(d, !!attentionChanged)}
7434
+
7435
+ ${renderRecentPlanCards(d)}
7436
+
6269
7437
  <div class="section-hero" id="hero-activity">
6270
7438
  <div class="live-activity-header">
6271
7439
  <span class="card-title">${spaceIconSvg('monitor')}Crew Monitor</span>
@@ -6285,23 +7453,46 @@ function renderUnsafe() {
6285
7453
  const info = semanticEventInfo(ev.kind, ev.refs?.commitType);
6286
7454
  const gloss = info.gloss || info.tag || ev.kind || 'event';
6287
7455
  const staggerIdx = Math.min(idx, 5);
7456
+ const actor = crewEventActor(ev);
7457
+ const initials = agentInitials(actor);
6288
7458
  const actionAttrs = activityTargetAttributes(ev, 'monitor-activity', { kindGloss: gloss });
6289
7459
  const rowA11y = actionAttrs
6290
7460
  ? actionAttrs
6291
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 || '';
6292
7486
  return `
6293
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>
6294
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>
6295
- <span class="feed-label">${escapeHtml(ev.label)}</span>
6296
- <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>
6297
7492
  </div>`;
6298
7493
  }).join('')}
6299
7494
  </div>
6300
7495
  </div>
6301
-
6302
- ${renderAttentionPanel(d, !!attentionChanged)}
6303
-
6304
- ${renderRecentPlanCards(d)}
6305
7496
  </div>
6306
7497
  </div>
6307
7498
  `);
@@ -6310,7 +7501,7 @@ function renderUnsafe() {
6310
7501
  parts.push(`
6311
7502
  <div class="content-section" id="section-plans">
6312
7503
  <div class="section-title">
6313
- <span class="dot dot-green"></span> Plans
7504
+ Plans
6314
7505
  <span class="section-subtitle">${d.plans.length} total</span>
6315
7506
  </div>
6316
7507
  ${renderPlansAccordion(d)}
@@ -6318,13 +7509,18 @@ function renderUnsafe() {
6318
7509
  `);
6319
7510
 
6320
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;
6321
7516
  parts.push(`
6322
7517
  <div class="content-section" id="section-agents">
6323
7518
  <div class="section-title">
6324
- <span class="dot dot-purple"></span> Agents
6325
- <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>
6326
7522
  </div>
6327
- ${d.agents.length === 0
7523
+ ${agents.length === 0
6328
7524
  ? renderEmptyStateCta({
6329
7525
  headline: 'No agents aboard',
6330
7526
  supportHtml: 'Add <code>.md</code> files under <code>.cursor/agents/</code>.',
@@ -6337,34 +7533,49 @@ function renderUnsafe() {
6337
7533
  },
6338
7534
  })
6339
7535
  : ''}
6340
- <div class="agent-list">
6341
- ${d.agents.map(a => {
6342
- const colors = ['#3b82f6','#a855f7','#22c55e','#eab308','#ef4444','#06b6d4','#f97316','#ec4899'];
6343
- const c = colors[a.id.length % colors.length];
6344
- 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>`;
6345
7553
  return `
6346
- <div class="agent-item" role="button" tabindex="0" aria-label="Toggle details for ${escapeAttr(a.id)}" onclick="toggleAgentDetails('${escapeJsString(agentId)}')">
6347
- <span class="agent-icon" style="background:${c}20;color:${c}">${escapeHtml(a.id.slice(0, 2).toUpperCase())}</span>
6348
- <span style="flex:1">${escapeHtml(a.id)}</span>
6349
- <span style="font-size:11px;color:var(--text-muted)">${escapeHtml(a.description || '')}</span>
6350
- <span style="font-size:10px;color:var(--text-muted);transition:transform 0.2s" id="agent-arrow-${escapeAttr(agentId)}">\u25bc</span>
6351
- </div>
6352
- <div class="agent-details" id="agent-details-${escapeAttr(agentId)}">
6353
- <p>${escapeHtml(a.description || 'No description available.')}</p>
6354
- <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}
6355
7558
  </div>
6356
- `;
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
+ `;
6357
7564
  }).join('')}
6358
7565
  </div>
6359
7566
  </div>
6360
7567
  `);
6361
7568
 
6362
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;
6363
7573
  parts.push(`
6364
7574
  <div class="content-section" id="section-commands">
6365
7575
  <div class="section-title">
6366
- <span class="dot" style="background:var(--cyan)"></span> Commands
6367
- <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>
6368
7579
  </div>
6369
7580
  ${d.commands.length === 0
6370
7581
  ? renderEmptyStateCta({
@@ -6383,10 +7594,28 @@ function renderUnsafe() {
6383
7594
  <div class="command-grid">
6384
7595
  ${d.commands.map(c => {
6385
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>`;
6386
7611
  return `
6387
- <div class="command-item" role="button" tabindex="0" aria-label="Copy ${escapeAttr(slash)}" onclick="${copyForPasteHandler(slash, slash, 'chatInput')}" title="${escapeAttr(copyActionTitle(slash, 'chatInput'))}">
6388
- <span class="dot" style="background:var(--cyan);width:6px;height:6px"></span>
6389
- ${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>
6390
7619
  </div>
6391
7620
  `;
6392
7621
  }).join('')}
@@ -6394,30 +7623,136 @@ function renderUnsafe() {
6394
7623
  </div>
6395
7624
  `);
6396
7625
 
6397
- // ===== Health =====
7626
+ // ===== Healthcenter (More → Health) =====
6398
7627
  const checks = health.checks || [];
7628
+ const healthStatus = health.status || 'ok';
7629
+ const passing = checks.filter(c => c.ok).length;
7630
+ const isSnapshotError = healthStatus === 'error' && checks.length === 0;
7631
+ const isOffline = !isSnapshotError && checks.length === 0;
7632
+ const presenceTone = healthSeverityChrome(healthStatus).tone;
7633
+ const presencePulse = healthStatus !== 'ok' ? ' dot-pulse' : '';
7634
+ const presenceLabel = isSnapshotError
7635
+ ? 'Snapshot error'
7636
+ : isOffline
7637
+ ? 'Health offline'
7638
+ : healthStatus === 'ok'
7639
+ ? 'Live'
7640
+ : healthStatus === 'warning'
7641
+ ? 'Warning'
7642
+ : healthStatus === 'degraded'
7643
+ ? 'Degraded'
7644
+ : 'Error';
7645
+ const subtitle = isSnapshotError
7646
+ ? 'Snapshot unavailable'
7647
+ : isOffline
7648
+ ? 'No health data'
7649
+ : `${passing}/${checks.length} checks passing`;
7650
+
7651
+ let healthBody = '';
7652
+ if (isSnapshotError) {
7653
+ healthBody = renderEmptyStateCta({
7654
+ headline: 'Health snapshot error',
7655
+ support: (typeof d.error === 'string' && d.error)
7656
+ || 'The dashboard could not load health checks (serve/snapshot failure). This is not a per-check fail.',
7657
+ iconKind: 'health',
7658
+ cta: {
7659
+ text: 'agent-kit doctor',
7660
+ subject: 'agent-kit doctor',
7661
+ destination: 'terminal',
7662
+ label: 'Autofix: copy agent-kit doctor',
7663
+ focusKey: 'cta-health-snapshot-error',
7664
+ },
7665
+ });
7666
+ } else if (isOffline) {
7667
+ healthBody = renderEmptyStateCta({
7668
+ headline: 'Health offline',
7669
+ support: 'No health data available yet. Refresh after the snapshot loads, or run doctor if the panel stays empty.',
7670
+ iconKind: 'health',
7671
+ cta: {
7672
+ text: 'agent-kit doctor',
7673
+ subject: 'agent-kit doctor',
7674
+ destination: 'terminal',
7675
+ label: 'Autofix: copy agent-kit doctor',
7676
+ focusKey: 'cta-health-offline',
7677
+ },
7678
+ });
7679
+ } else {
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';
7726
+ return `
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>
7731
+ </div>`;
7732
+ }).join('')}
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('')}`;
7742
+ }
7743
+
6399
7744
  parts.push(`
6400
7745
  <div class="content-section" id="section-health">
6401
7746
  <div class="section-title">
6402
- <span class="dot dot-${health.status === 'ok' ? 'green' : health.status === 'warning' ? 'yellow' : 'red'}"></span>
6403
7747
  Health
6404
- <span class="section-subtitle">${checks.filter(c => c.ok).length}/${checks.length} checks passing</span>
7748
+ <span class="section-subtitle">${escapeHtml(subtitle)}</span>
6405
7749
  </div>
6406
- ${checks.length === 0
6407
- ? renderEmptyStateCta({
6408
- headline: 'Health offline',
6409
- support: 'No health data available.',
6410
- })
6411
- : ''}
6412
- <div class="health-grid">
6413
- ${checks.map(c => `
6414
- <div style="position:relative">
6415
- <div class="health-item" role="button" tabindex="0" aria-label="Show info for ${escapeAttr(c.label)}" onclick="showHealthInfo(this, ${c.ok})">
6416
- <span class="dot dot-${c.ok ? 'green' : 'red'}"></span>
6417
- <span>${escapeHtml(c.label)}</span>
6418
- </div>
6419
- </div>
6420
- `).join('')}
7750
+ <div class="healthcenter">
7751
+ <div class="healthcenter-presence" role="status" aria-live="polite">
7752
+ <span class="dot dot-${presenceTone}${presencePulse}"></span>
7753
+ <span>${escapeHtml(presenceLabel)}</span>
7754
+ </div>
7755
+ ${healthBody}
6421
7756
  </div>
6422
7757
  </div>
6423
7758
  `);
@@ -6426,9 +7761,8 @@ function renderUnsafe() {
6426
7761
  parts.push(`
6427
7762
  <div class="content-section" id="section-git">
6428
7763
  <div class="section-title">
6429
- <span class="dot dot-${d.git?.dirty ? 'yellow' : 'green'}"></span>
6430
7764
  Git
6431
- <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>
6432
7766
  </div>
6433
7767
  ${!d.git?.branch
6434
7768
  ? renderEmptyStateCta({
@@ -6437,28 +7771,15 @@ function renderUnsafe() {
6437
7771
  })
6438
7772
  : ''}
6439
7773
  <div class="card" style="${d.git?.branch ? '' : 'display:none'}">
7774
+ ${renderGitHygieneHint(d.git)}
7775
+ ${renderGitFlowCard(d.git)}
6440
7776
  <div class="git-info">
6441
- <div class="git-row">
6442
- <span class="git-label">Branch</span>
6443
- <span class="git-value">${escapeHtml(d.git?.branch || '\u2014')}</span>
6444
- </div>
6445
- <div class="git-row">
6446
- <span class="git-label">Working tree</span>
6447
- <span class="git-value">${d.git?.dirty ? `${d.git.dirtyCount} file${d.git.dirtyCount !== 1 ? 's' : ''} modified` : 'Clean'}</span>
6448
- </div>
6449
7777
  <div class="git-row">
6450
7778
  <span class="git-label">Last commit</span>
6451
7779
  <span class="git-value">${escapeHtml(d.git?.lastCommit || '\u2014')}</span>
6452
7780
  </div>
6453
- <div class="git-row">
6454
- <span class="git-label">Ahead of origin/main</span>
6455
- <span class="git-value">${d.git?.ahead ?? '\u2014'}</span>
6456
- </div>
6457
- <div class="git-row">
6458
- <span class="git-label">Behind origin/main</span>
6459
- <span class="git-value">${d.git?.behind ?? '\u2014'}</span>
6460
- </div>
6461
7781
  </div>
7782
+ ${renderGitGraphCard(d.git)}
6462
7783
  ${d.git?.dirty ? renderGitFileList(d.git) : ''}
6463
7784
  </div>
6464
7785
  </div>
@@ -6466,49 +7787,79 @@ function renderUnsafe() {
6466
7787
 
6467
7788
  // ===== Memory =====
6468
7789
  const recentDecisions = d.memory?.recentDecisions || [];
7790
+ const recentErrors = d.memory?.recentErrors || [];
7791
+ const errorStats = d.memory?.errorStats || null;
6469
7792
  parts.push(`
6470
7793
  <div class="content-section" id="section-memory">
6471
7794
  <div class="section-title">
6472
- <span class="dot dot-yellow"></span> Memory
7795
+ Memory
6473
7796
  <span class="section-subtitle">${d.memory.errors || 0} errors \u00b7 ${d.memory.decisions || 0} decisions</span>
6474
7797
  </div>
6475
- <div class="grid-2">
6476
- <div class="card">
6477
- <div class="card-header">
6478
- <span class="card-title">Error Records</span>
6479
- <span class="dot dot-red"></span>
6480
- </div>
6481
- <div class="card-value">${d.memory.errors || 0}</div>
6482
- <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>
6483
7803
  </div>
6484
- <div class="card">
6485
- <div class="card-header">
6486
- <span class="card-title">Decisions</span>
6487
- <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>'}
6488
7820
  </div>
6489
- <div class="card-value">${d.memory.decisions || 0}</div>
6490
- <div class="card-subtitle">architectural decisions</div>
6491
7821
  </div>
6492
7822
  </div>
6493
- ${recentDecisions.length > 0 ? `
6494
- <div class="card" style="margin-top:16px">
6495
- <div class="card-header">
6496
- <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>
6497
7829
  </div>
6498
- <div class="memory-list">
6499
- ${recentDecisions.map(dd => {
6500
- const memPath = dd.path || `.cursor/memory/decisions/${dd.id || dd}.md`;
6501
- const memId = dd.id || dd;
6502
- return `
6503
- <div class="memory-item" role="button" tabindex="0" aria-label="Copy path for ${escapeAttr(memId)}" onclick="${copyRepoPathHandler(memPath)}" title="${escapeAttr(pathActionTitle(memPath))}">
6504
- <span class="dot dot-yellow"></span>
6505
- <span>${escapeHtml(memId)}</span>
6506
- </div>
6507
- `;
6508
- }).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>
6509
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
+ })}
6510
7861
  </div>
6511
- ` : ''}
7862
+ </div>
6512
7863
  </div>
6513
7864
  `);
6514
7865
 
@@ -6517,7 +7868,6 @@ function renderUnsafe() {
6517
7868
  parts.push(`
6518
7869
  <div class="content-section" id="section-terminals">
6519
7870
  <div class="section-title">
6520
- <span class="dot dot-${d.terminals.length > 0 ? 'blue dot-pulse' : 'gray'}"></span>
6521
7871
  Terminals
6522
7872
  <span class="section-subtitle">${d.terminals.length} active</span>
6523
7873
  </div>
@@ -6563,55 +7913,40 @@ function renderUnsafe() {
6563
7913
  parts.push(`
6564
7914
  <div class="content-section" id="section-processes">
6565
7915
  <div class="section-title">
6566
- <span class="dot dot-${processes.length > 0 ? 'green' : 'gray'}"></span>
6567
7916
  Processes
6568
7917
  <span class="section-subtitle">${processes.length} running</span>
6569
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>
6570
7922
  ${processes.length === 0
6571
7923
  ? renderEmptyStateCta({
6572
7924
  headline: 'All quiet',
6573
- support: 'No relevant processes running.',
7925
+ support: 'No relevant processes running. Node, git, and dashboard processes appear here while active.',
6574
7926
  })
6575
7927
  : `
6576
- <div class="card" style="overflow-x:auto">
6577
- <table class="processes-table">
6578
- <thead>
6579
- <tr>
6580
- <th>Process</th>
6581
- <th>PID</th>
6582
- <th>CPU%</th>
6583
- <th>MEM%</th>
6584
- <th>Command</th>
6585
- <th></th>
6586
- </tr>
6587
- </thead>
6588
- <tbody>
6589
- ${processes.map(p => {
6590
- const dotClass = p.label === 'dashboard-server' ? 'dot-green dot-pulse'
6591
- : p.label === 'node' ? 'dot-blue'
6592
- : p.label === 'git' ? 'dot-yellow'
6593
- : 'dot-gray';
6594
- const pidStr = String(p.pid ?? '');
6595
- return `
6596
- <tr>
6597
- <td>
6598
- <span class="process-label">
6599
- <span class="dot ${dotClass}"></span>
6600
- <span>${escapeHtml(p.label || 'other')}</span>
6601
- </span>
6602
- </td>
6603
- <td style="color:var(--text-muted)">${escapeHtml(pidStr)}</td>
6604
- <td>${escapeHtml(p.cpu || '\u2014')}</td>
6605
- <td>${escapeHtml(p.mem || '\u2014')}</td>
6606
- <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>
6607
- <td class="process-actions">
6608
- <button type="button" class="process-copy-btn" onclick="event.stopPropagation();${copyForPasteHandler(pidStr, `PID ${pidStr}`, 'terminal')}" title="${escapeAttr(copyActionTitle(`PID ${pidStr}`, 'terminal'))}">Copy PID</button>
6609
- </td>
6610
- </tr>
6611
- `;
6612
- }).join('')}
6613
- </tbody>
6614
- </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('')}
6615
7950
  </div>
6616
7951
  `}
6617
7952
  </div>
@@ -6619,19 +7954,15 @@ function renderUnsafe() {
6619
7954
 
6620
7955
  // ===== Skills =====
6621
7956
  const skills = d.skills || [];
6622
- const categories = {};
6623
- skills.forEach(s => {
6624
- const cat = s.category || 'other';
6625
- if (!categories[cat]) categories[cat] = [];
6626
- categories[cat].push(s);
6627
- });
6628
- const catColors = ['#a855f7','#3b82f6','#22c55e','#eab308','#f97316','#06b6d4','#ec4899','#ef4444'];
6629
- 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;
6630
7960
  parts.push(`
6631
7961
  <div class="content-section" id="section-skills">
6632
7962
  <div class="section-title">
6633
- <span class="dot dot-orange" style="background:var(--orange)"></span> Skills
6634
- <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>
6635
7966
  </div>
6636
7967
  ${skills.length === 0
6637
7968
  ? renderEmptyStateCta({
@@ -6646,23 +7977,38 @@ function renderUnsafe() {
6646
7977
  focusKey: 'cta-skills-path',
6647
7978
  },
6648
7979
  })
6649
- : Object.entries(categories).map(([cat, items]) => {
6650
- const catColor = catColors[catIdx++ % catColors.length];
6651
- return `
6652
- <div class="skills-category">
6653
- <div class="skills-category-title">${escapeHtml(cat)}</div>
6654
- <div class="skills-tags">
6655
- ${items.map(s => `
6656
- <div class="skill-tag" title="${escapeAttr(s.description || '')}">
6657
- <span class="skill-cat-dot" style="background:${catColor}"></span>
6658
- <span class="skill-title">${escapeHtml(s.title)}</span>
6659
- ${s.description ? `<span class="skill-desc">\u2014 ${escapeHtml(s.description)}</span>` : ''}
6660
- </div>
6661
- `).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}
6662
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>
6663
8008
  </div>
6664
8009
  `;
6665
- }).join('')}
8010
+ }).join('')}
8011
+ </div>
6666
8012
  </div>
6667
8013
  `);
6668
8014
 
@@ -6702,7 +8048,7 @@ function renderUnsafe() {
6702
8048
  parts.push(`
6703
8049
  <div class="content-section" id="section-activity">
6704
8050
  <div class="section-title">
6705
- <span class="dot" style="background:var(--cyan)"></span> Activity
8051
+ Activity
6706
8052
  <span class="section-subtitle">${escapeHtml(activityCountLabel)} · Last refreshed ${escapeHtml(relativeTime(d.generatedAt))}</span>
6707
8053
  </div>
6708
8054
  ${renderActivityFilterChips()}