privateboard 0.1.36 → 0.1.38

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/public/index.html CHANGED
@@ -69,6 +69,9 @@
69
69
  system-ui, sans-serif;
70
70
 
71
71
  --sidebar-w: 280px;
72
+ /* Collapsed icon-rail width · matches the ChatGPT folded sidebar.
73
+ Wide enough for a centered 32px hit target with breathing room. */
74
+ --mini-sidebar-w: 56px;
72
75
  }
73
76
 
74
77
  /* Logo stays on Inter so the brandmark reads consistently
@@ -84,7 +87,7 @@
84
87
  background: var(--bg);
85
88
  color: var(--text);
86
89
  font-family: var(--mono);
87
- font-size: 13px;
90
+ font-size: 14px;
88
91
  line-height: 1.5;
89
92
  -webkit-font-smoothing: antialiased;
90
93
  height: 100vh;
@@ -324,7 +327,7 @@
324
327
  .migrate-deck {
325
328
  margin: 0;
326
329
  font-family: var(--font-human, system-ui, sans-serif);
327
- font-size: 13px;
330
+ font-size: 14px;
328
331
  line-height: 1.55;
329
332
  color: var(--text-soft);
330
333
  }
@@ -670,14 +673,6 @@
670
673
  html.is-electron-mac .room-head .head-cast img {
671
674
  -webkit-app-region: no-drag;
672
675
  }
673
- /* The expand-btn's ::after halo paints OUTSIDE the button rect
674
- (inset: -16px), so it doesn't inherit the button's no-drag and
675
- gets eaten by `.room-head { drag }` above. Stamp no-drag on the
676
- pseudo directly so the 48×48 hit zone keeps registering clicks.
677
- (Same shape as the sidebar-collapse-btn::after override.) */
678
- html.is-electron-mac .room-head .room-head-expand::after {
679
- -webkit-app-region: no-drag;
680
- }
681
676
  /* ─── macOS Electron · top-strip drag coverage for non-room views.
682
677
  When the room view is hidden (All Reports / All Notes / Search /
683
678
  Agent Profile) or the room view is shown with no current room (the
@@ -839,22 +834,28 @@
839
834
  room. Single column = main fills the whole body-grid cleanly.
840
835
  The user's preferred --sidebar-w stays in localStorage so
841
836
  expanding restores the same width. */
837
+ /* Collapsed · the full sidebar + resizer drop out and a fixed-width
838
+ mini icon rail takes column 1. Source order [sidebar, resizer,
839
+ mini, main] + both hidden siblings → grid auto-places mini in
840
+ col 1, main in col 2. */
842
841
  body.sidebar-collapsed .body-grid {
843
- grid-template-columns: 1fr !important;
842
+ grid-template-columns: var(--mini-sidebar-w) 1fr !important;
844
843
  }
845
844
  body.sidebar-collapsed .sidebar,
846
845
  body.sidebar-collapsed .col-resizer {
847
846
  display: none;
848
847
  }
848
+ body.sidebar-collapsed .mini-sidebar {
849
+ display: flex;
850
+ }
849
851
  /* ─── Hover peek · floating sidebar while collapsed ───
850
- When the user hovers the collapsed-state trigger (the floating
851
- `.sidebar-expand-btn` or the in-header `.room-head-expand`), JS
852
- adds `.sidebar-peek` to body. The sidebar reappears as a floating
853
- overlay anchored to the body-grid's top-left edge so it sits ABOVE
854
- the chat content without re-flowing the grid. Mouseleave (with
855
- short grace) drops the class and the sidebar disappears again.
856
- This is purely an addition to the collapsed state — the column
857
- never reclaims its grid track. */
852
+ When the cursor reaches the viewport's left edge (over the mini
853
+ rail), JS adds `.sidebar-peek` to body. The full sidebar reappears
854
+ as a floating overlay anchored to the body-grid's top-left edge so
855
+ it sits ABOVE the chat content (and the mini rail) without
856
+ re-flowing the grid. Mouseleave drops the class and the float
857
+ disappears again, leaving the mini rail. This is purely an addition
858
+ to the collapsed state — the column never reclaims its grid track. */
858
859
  body.sidebar-collapsed.sidebar-peek .sidebar {
859
860
  display: flex;
860
861
  position: absolute;
@@ -913,12 +914,14 @@
913
914
  appearance: none;
914
915
  padding: 0;
915
916
  }
916
- /* Show floating button only when sidebar is collapsed AND we're
917
- in the empty / no-room state. When a room is loaded, the
918
- in-header `.room-head-expand` button takes over instead, so
919
- the icon doesn't sit on top of the room title. */
917
+ /* Retired · the persistent mini icon rail now owns the collapsed
918
+ state, and its logo button expands. The old floating round chip
919
+ would sit on top of the rail's top icon (both pin to the body-grid
920
+ top-left), so it stays hidden in every state. Kept in the DOM (and
921
+ this rule kept as `display: none`) so the edge-peek mousemove guard
922
+ that references `[data-sidebar-expand]` still resolves cleanly. */
920
923
  html.no-room body.sidebar-collapsed .sidebar-expand-btn {
921
- display: inline-flex;
924
+ display: none;
922
925
  }
923
926
  .sidebar-expand-btn:hover {
924
927
  color: var(--lime);
@@ -961,67 +964,230 @@
961
964
  -webkit-app-region: no-drag;
962
965
  }
963
966
 
964
- /* ─── In-header expand button ───
965
- Lives at the leading edge of `.room-head` (rendered by
966
- renderHeader() in app.js). Visual twin of `.sidebar-collapse-btn`
967
- so the collapse/expand pair reads as the SAME control across
968
- the two places it can appear (in-sidebar when open · in-header
969
- when sidebar collapsed AND a room is loaded). Icon-only · no
970
- chrome · same Lucide PanelLeft mask · same hover-lime cascade.
971
- Always rendered in the mirrored variant (panel folded to the
972
- right edge) since this button only ever appears in the collapsed
973
- state · clicking it expands the sidebar back. */
974
- .room-head-expand {
975
- appearance: none;
967
+ /* ─── Mini (collapsed) sidebar · ChatGPT-style icon rail ───
968
+ A persistent narrow rail shown while collapsed. `display: none`
969
+ by default; flipped to `flex` by the collapsed-state rule above.
970
+ Shares the sidebar chrome surface (`--sidebar-bg`) + a hairline
971
+ right edge so it reads as the same column the full sidebar used. */
972
+ .mini-sidebar {
973
+ display: none;
974
+ flex-direction: column;
975
+ align-items: center;
976
+ width: var(--mini-sidebar-w);
977
+ height: 100%;
978
+ min-height: 0;
979
+ background: var(--sidebar-bg);
980
+ border-right: 1px solid var(--line);
981
+ padding: 10px 0;
982
+ /* `visible` (not `hidden`) so the per-icon hover tooltips can
983
+ escape the 56px rail and pop out to the right. The icon set is
984
+ short enough that it never overflows the rail vertically, so
985
+ there's nothing to clip anyway. The enclosing `.body-grid`
986
+ still bounds everything. */
987
+ overflow: visible;
988
+ }
989
+ .mini-top {
990
+ display: flex;
991
+ flex-direction: column;
992
+ align-items: center;
993
+ gap: 4px;
994
+ flex: 1 1 auto;
995
+ min-height: 0;
996
+ width: 100%;
997
+ }
998
+ .mini-foot {
999
+ display: flex;
1000
+ flex-direction: column;
1001
+ align-items: center;
1002
+ flex: 0 0 auto;
1003
+ padding-top: 8px;
1004
+ }
1005
+ /* Icon button · 32×32 centered hit target. Stroke icons ride
1006
+ currentColor (text-soft idle → text on hover) via the shared
1007
+ mask-image vocabulary, matching the full sidebar's nav glyphs. */
1008
+ .mini-btn {
1009
+ position: relative;
1010
+ display: flex;
1011
+ align-items: center;
1012
+ justify-content: center;
1013
+ width: 32px;
1014
+ height: 32px;
1015
+ padding: 0;
1016
+ border: none;
976
1017
  background: transparent;
977
- border: 0;
978
1018
  color: var(--text-soft);
1019
+ border-radius: 8px;
979
1020
  cursor: pointer;
980
- width: 16px;
981
- height: 16px;
982
- padding: 0;
1021
+ text-decoration: none;
983
1022
  flex-shrink: 0;
984
- transition: color 0.12s;
985
- /* position: relative anchors the ::after hit-area expander so
986
- the cursor doesn't have to land precisely on the 16px icon. */
987
- position: relative;
988
- display: none;
1023
+ transition: background 0.12s, color 0.12s;
1024
+ appearance: none;
1025
+ }
1026
+ /* Hover tooltip · CSS-only via ::after + data-tip, mirroring the
1027
+ `.ib-action::after` pattern. Pops to the RIGHT of the icon (the
1028
+ rail hugs the window's left edge, so above/left would clip) after
1029
+ ~300ms dwell. data-tip is populated from data-i18n-tip by i18n.js,
1030
+ with a static English fallback for the pre-i18n frame. */
1031
+ .mini-btn[data-tip]::after {
1032
+ content: attr(data-tip);
1033
+ position: absolute;
1034
+ left: calc(100% + 10px);
1035
+ top: 50%;
1036
+ transform: translateY(-50%) translateX(-3px);
1037
+ background: var(--panel-2);
1038
+ border: 0.5px solid var(--line-strong);
1039
+ padding: 5px 9px;
1040
+ font-family: var(--mono);
1041
+ font-size: 10px;
1042
+ letter-spacing: 0.04em;
1043
+ color: var(--text);
1044
+ white-space: nowrap;
1045
+ pointer-events: none;
1046
+ opacity: 0;
1047
+ visibility: hidden;
1048
+ box-shadow: 0 4px 14px -6px rgba(0, 0, 0, 0.55);
1049
+ transition: opacity 0.14s ease, transform 0.14s ease, visibility 0s linear 0.18s;
1050
+ z-index: 250;
1051
+ }
1052
+ .mini-btn[data-tip]:hover::after,
1053
+ .mini-btn[data-tip]:focus-visible::after {
1054
+ opacity: 1;
1055
+ visibility: visible;
1056
+ transform: translateY(-50%) translateX(0);
1057
+ transition: opacity 0.14s ease 0.3s, transform 0.14s ease 0.3s, visibility 0s linear 0.3s;
989
1058
  }
990
- .room-head-expand::before {
1059
+ .mini-btn:hover {
1060
+ background: var(--panel-2);
1061
+ color: var(--text);
1062
+ }
1063
+ /* Selected state · the icon for the current destination (reports /
1064
+ notes / new-room / new-agent via shared trigger attrs, rooms /
1065
+ agents via setMiniRailContext). Settled `--panel-3` chip + full
1066
+ --text glyph, matching the full sidebar's `.new-btn.active`
1067
+ language (background-only, no accent — per project CSS rules).
1068
+ Placed after :hover so an active icon keeps its chip on hover
1069
+ instead of dropping to the lighter hover tone. */
1070
+ .mini-btn.active,
1071
+ .mini-btn.active:hover {
1072
+ background: var(--panel-3);
1073
+ color: var(--text);
1074
+ }
1075
+ /* Glyph · same 18px Lucide mask the full sidebar nav buttons use.
1076
+ `--icon` is supplied per-button by the shared rules below (data-
1077
+ attribute scoped so both the .new-btn and .mini-btn carriers pick
1078
+ it up from one definition). */
1079
+ .mini-btn::before {
991
1080
  content: "";
992
- display: block;
993
- width: 16px;
994
- height: 16px;
1081
+ width: 18px;
1082
+ height: 18px;
1083
+ flex-shrink: 0;
995
1084
  background-color: currentColor;
996
- -webkit-mask-image: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='black' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'><rect x='3' y='3' width='18' height='18' rx='2'/><path d='M9 3v18'/><path d='M5 8h2'/><path d='M5 12h2'/><path d='M5 16h2'/></svg>");
997
- mask-image: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='black' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'><rect x='3' y='3' width='18' height='18' rx='2'/><path d='M9 3v18'/><path d='M5 8h2'/><path d='M5 12h2'/><path d='M5 16h2'/></svg>");
1085
+ -webkit-mask-image: var(--icon, none);
1086
+ mask-image: var(--icon, none);
998
1087
  -webkit-mask-repeat: no-repeat;
999
1088
  mask-repeat: no-repeat;
1000
1089
  -webkit-mask-position: center;
1001
1090
  mask-position: center;
1002
- -webkit-mask-size: 16px 16px;
1003
- mask-size: 16px 16px;
1004
- /* Same scaleX(-1) the sidebar-collapse-btn uses when
1005
- body.sidebar-collapsed · since this button is ONLY visible
1006
- in the collapsed state, the flip is unconditional here. */
1007
- transform: scaleX(-1);
1091
+ -webkit-mask-size: 18px 18px;
1092
+ mask-size: 18px 18px;
1093
+ transition: color 0.12s;
1094
+ }
1095
+ /* Tab + user buttons render real children (.ic / avatar), not a mask
1096
+ glyph. Suppress the shared ::before — without an `--icon` its
1097
+ `currentColor` fill would paint a solid square block next to the
1098
+ real content. (The logo keeps its ::before for the hover fold-icon
1099
+ below.) */
1100
+ .mini-tab::before,
1101
+ .mini-user::before { content: none; }
1102
+ .mini-tab .ic { width: 18px; height: 18px; }
1103
+ /* Logo → fold glyph on hover · telegraphs "click to expand" using the
1104
+ same Lucide PanelLeft + 3-rows icon as the in-sidebar collapse
1105
+ button. At rest the brand mark shows; on hover it fades out and the
1106
+ fold glyph (overlaid, currentColor) fades in. The explicit fold
1107
+ mask here also overrides the generic .mini-btn::before, which —
1108
+ lacking an --icon — would otherwise paint a solid square. */
1109
+ .mini-logo { position: relative; }
1110
+ /* The chair avatar is a two-layer stack: open-eye frame always
1111
+ visible, closed-eye frame flashed in by the blink keyframe. The
1112
+ wrapper carries an occasional idle hop. */
1113
+ .mini-logo-av {
1114
+ position: relative;
1115
+ width: 26px;
1116
+ height: 26px;
1117
+ transition: opacity 0.12s;
1118
+ animation: chair-hop 7s ease-in-out infinite;
1008
1119
  }
1009
- /* Invisible hit-area expander · matches `.sidebar-collapse-btn::after`
1010
- so the click rectangle grows to 48×48 around the 16px icon. */
1011
- .room-head-expand::after {
1120
+ .mini-logo-av img {
1121
+ position: absolute;
1122
+ inset: 0;
1123
+ width: 100%;
1124
+ height: 100%;
1125
+ image-rendering: pixelated;
1126
+ image-rendering: crisp-edges;
1127
+ }
1128
+ /* Closed-eye frame · hidden at rest, briefly opaque on the blink
1129
+ keyframe. A different cycle length from the hop keeps the two from
1130
+ locking into a robotic sync. */
1131
+ .mini-logo-av .cl-blink {
1132
+ opacity: 0;
1133
+ animation: chair-blink 5.7s ease-in-out infinite;
1134
+ }
1135
+ .mini-logo:hover .mini-logo-av { opacity: 0; }
1136
+ /* Occasional double-bounce hop · still ~85% of the cycle, then a
1137
+ quick two-step hop so the chair reads as "alive" without being
1138
+ busy. */
1139
+ @keyframes chair-hop {
1140
+ 0%, 60%, 100% { transform: translateY(0); }
1141
+ 66% { transform: translateY(-4px); }
1142
+ 72% { transform: translateY(0); }
1143
+ 77% { transform: translateY(-2px); }
1144
+ 82% { transform: translateY(0); }
1145
+ }
1146
+ /* Single crisp blink near the end of the cycle (~170ms closed). */
1147
+ @keyframes chair-blink {
1148
+ 0%, 93.5%, 100% { opacity: 0; }
1149
+ 94%, 96.5% { opacity: 1; }
1150
+ 97% { opacity: 0; }
1151
+ }
1152
+ /* Respect reduced-motion · hold the open-eye frame, no hop / blink. */
1153
+ @media (prefers-reduced-motion: reduce) {
1154
+ .mini-logo-av,
1155
+ .mini-logo-av .cl-blink { animation: none; }
1156
+ }
1157
+ .mini-logo::before {
1012
1158
  content: "";
1013
1159
  position: absolute;
1014
- inset: -16px;
1160
+ top: 50%;
1161
+ left: 50%;
1162
+ /* scaleX(-1) mirrors the fold so the rows flip to the right edge —
1163
+ the same direction the in-sidebar collapse button uses in its
1164
+ collapsed state to telegraph "the panel expands out from here". */
1165
+ transform: translate(-50%, -50%) scaleX(-1);
1166
+ -webkit-mask-image: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='black' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'><rect x='3' y='3' width='18' height='18' rx='2'/><path d='M9 3v18'/><path d='M5 8h2'/><path d='M5 12h2'/><path d='M5 16h2'/></svg>");
1167
+ mask-image: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='black' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'><rect x='3' y='3' width='18' height='18' rx='2'/><path d='M9 3v18'/><path d='M5 8h2'/><path d='M5 12h2'/><path d='M5 16h2'/></svg>");
1168
+ opacity: 0;
1169
+ transition: opacity 0.12s;
1015
1170
  }
1016
- body.sidebar-collapsed .room-head-expand {
1017
- display: block;
1018
- /* Extra 5px breathing room between the icon and the title cluster
1019
- on top of the room-head's own 12px column gap · the icon sat
1020
- too close to the kicker / subject otherwise. Scoped to the
1021
- collapsed state since the button itself only exists then. */
1022
- margin-right: 5px;
1171
+ .mini-logo:hover::before { opacity: 1; }
1172
+ /* Divider between the action group and the rooms/agents switchers. */
1173
+ .mini-sep {
1174
+ width: 20px;
1175
+ height: 1px;
1176
+ background: var(--line-bright);
1177
+ margin: 6px 0;
1178
+ flex-shrink: 0;
1179
+ }
1180
+ .mini-user { width: 32px; height: 32px; }
1181
+ .mini-user .mini-user-av { width: 26px; height: 26px; }
1182
+ .mini-user:hover { background: var(--panel-2); }
1183
+
1184
+ /* macOS Electron · clicks must reach the rail's buttons (the window
1185
+ drag region otherwise swallows them), and the traffic-light cluster
1186
+ is hidden while collapsed (see syncElectronTrafficLights), so the
1187
+ logo can sit near the top with normal padding. */
1188
+ html.is-electron-mac .mini-sidebar {
1189
+ -webkit-app-region: no-drag;
1023
1190
  }
1024
- .room-head-expand:hover { color: var(--lime); }
1025
1191
 
1026
1192
  /* ─── Sidebar tabs (Rooms / Agents) — bar-style nav ───
1027
1193
  Reference: `public/icons/bar.png`. Two layers:
@@ -1068,7 +1234,7 @@
1068
1234
  justify-content: center;
1069
1235
  gap: 7px;
1070
1236
  font-family: var(--sans);
1071
- font-size: 13px;
1237
+ font-size: 14px;
1072
1238
  font-weight: 500;
1073
1239
  color: var(--text-dim);
1074
1240
  cursor: pointer;
@@ -1388,7 +1554,7 @@
1388
1554
  position: relative;
1389
1555
  z-index: 1;
1390
1556
  font-family: var(--mono);
1391
- font-size: 13px;
1557
+ font-size: 14px;
1392
1558
  color: var(--lime);
1393
1559
  line-height: 1;
1394
1560
  }
@@ -1507,27 +1673,33 @@
1507
1673
  [data-convene-trigger]::before {
1508
1674
  --icon: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='black' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'><path d='M12 3H5a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2v-7'/><path d='M18.375 2.625a1 1 0 0 1 3 3l-9.013 9.014a2 2 0 0 1-.853.505l-2.873.84a.5.5 0 0 1-.62-.62l.84-2.873a2 2 0 0 1 .506-.852z'/></svg>");
1509
1675
  }
1510
- /* "New agent" · UserPlus (Lucide) — silhouette with a small plus
1511
- to its right, Claude's "add member" pattern. */
1676
+ /* "New agent" · UserRoundPlus (Lucide) — rounder, bigger-headed
1677
+ "add person" glyph. Replaces the thinner UserPlus, whose small
1678
+ low-left silhouette + floating plus read a size smaller than the
1679
+ box-filling neighbours (SquarePen / FileText) — most visible in
1680
+ the collapsed mini rail where icons stand alone without labels. */
1512
1681
  [data-agent-composer-trigger]::before {
1513
- --icon: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='black' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'><path d='M16 21v-2a4 4 0 0 0-4-4H6a4 4 0 0 0-4 4v2'/><circle cx='9' cy='7' r='4'/><line x1='19' x2='19' y1='8' y2='14'/><line x1='22' x2='16' y1='11' y2='11'/></svg>");
1682
+ --icon: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='black' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'><path d='M2 21a8 8 0 0 1 13.292-6'/><circle cx='10' cy='8' r='5'/><path d='M19 16v6'/><path d='M22 19h-6'/></svg>");
1514
1683
  }
1515
1684
  /* "All Reports" · FileText (Lucide) — single document with three
1516
1685
  text lines, cleaner than the previous gradient-stack glyph. */
1517
- .new-btn.nav-reports::before {
1686
+ .new-btn.nav-reports::before,
1687
+ .mini-reports::before {
1518
1688
  --icon: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='black' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'><path d='M15 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V7Z'/><path d='M14 2v4a2 2 0 0 0 2 2h4'/><path d='M10 9H8'/><path d='M16 13H8'/><path d='M16 17H8'/></svg>");
1519
1689
  }
1520
1690
  /* "All Notes" · Bookmark (Lucide) — pennant-shaped bookmark glyph
1521
1691
  mirroring the qcta save-button icon. Matches the chairman's-notes
1522
1692
  vocabulary across the app (sidebar entry, save action, in-room
1523
1693
  overlay all share the bookmark register). */
1524
- .new-btn.nav-notes::before {
1694
+ .new-btn.nav-notes::before,
1695
+ .mini-notes::before {
1525
1696
  --icon: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='black' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'><path d='M19 21l-7-5-7 5V5a2 2 0 0 1 2-2h10a2 2 0 0 1 2 2z'/></svg>");
1526
1697
  }
1527
1698
  /* "Search" · Search (Lucide) — circle + diagonal handle. Standard
1528
1699
  magnifying-glass glyph in the same Lucide line-icon vocabulary as
1529
1700
  the rest of the sidebar. */
1530
- .new-btn.nav-search::before {
1701
+ .new-btn.nav-search::before,
1702
+ .mini-search::before {
1531
1703
  --icon: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='black' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'><circle cx='11' cy='11' r='8'/><path d='m21 21-4.3-4.3'/></svg>");
1532
1704
  }
1533
1705
  /* All Reports / All Notes / Search nav-button shape · label-only. */
@@ -1888,11 +2060,12 @@
1888
2060
  overflow: hidden;
1889
2061
  }
1890
2062
  /* Pixel-art variant · when prefs.avatarSeed exists, app.renderUserBlock
1891
- swaps the initial-letter chip for an AvatarSkill SVG. Drop the
1892
- lime fill (the SVG is its own surface) and use the bg color
1893
- instead, keep the box border-less so the SVG bleeds edge-to-edge. */
2063
+ swaps the initial-letter chip for an AvatarSkill SVG. The pixel SVG
2064
+ is transparent, so we drop the fill entirely (was --lime, then
2065
+ --bg) and let it sit directly on the surface behind it — no boxed
2066
+ square around the character. */
1894
2067
  .user-av.has-pixel-av {
1895
- background: var(--bg);
2068
+ background: transparent;
1896
2069
  }
1897
2070
  .user-av.has-pixel-av svg {
1898
2071
  width: 100%;
@@ -2199,7 +2372,7 @@
2199
2372
  background: var(--panel-2);
2200
2373
  }
2201
2374
  .reports-load-more-arrow {
2202
- font-size: 13px;
2375
+ font-size: 14px;
2203
2376
  line-height: 1;
2204
2377
  }
2205
2378
 
@@ -2710,7 +2883,7 @@
2710
2883
  }
2711
2884
  .search-overlay-status-msg {
2712
2885
  font-family: var(--font-human);
2713
- font-size: 13px;
2886
+ font-size: 14px;
2714
2887
  color: var(--text-soft);
2715
2888
  line-height: 1.5;
2716
2889
  max-width: 360px;
@@ -3722,15 +3895,6 @@
3722
3895
  -webkit-backdrop-filter: none;
3723
3896
  }
3724
3897
  }
3725
- /* When the sidebar is collapsed the room-head gains a leading
3726
- auto-sized track for the in-header `.room-head-expand` button.
3727
- When expanded the button is display:none, so a 0-width track
3728
- would still leave a `gap` artifact — switching to a 3-track
3729
- template only in the collapsed state keeps the header visually
3730
- identical to before whenever the sidebar is open. */
3731
- body.sidebar-collapsed .room-head {
3732
- grid-template-columns: auto 1fr auto;
3733
- }
3734
3898
  /* `overflow: visible` so the tone-tag hover tooltip (positioned via
3735
3899
  ::after below the tag) can escape this container. The room-subject
3736
3900
  has its own overflow:hidden + text-overflow ellipsis rule, so the
@@ -4551,7 +4715,7 @@
4551
4715
  .brief-active-substage {
4552
4716
  font-family: var(--font-headline, "Charter", "Songti SC", Georgia, serif);
4553
4717
  font-style: italic;
4554
- font-size: 13px;
4718
+ font-size: 14px;
4555
4719
  line-height: 1.55;
4556
4720
  color: var(--text-soft);
4557
4721
  margin-top: 6px;
@@ -4858,7 +5022,7 @@
4858
5022
  color: var(--bg);
4859
5023
  }
4860
5024
  .brief-retry-mark {
4861
- font-size: 13px;
5025
+ font-size: 14px;
4862
5026
  line-height: 1;
4863
5027
  }
4864
5028
 
@@ -4922,7 +5086,7 @@
4922
5086
  border: none;
4923
5087
  color: var(--text-faint);
4924
5088
  cursor: pointer;
4925
- font-size: 13px;
5089
+ font-size: 14px;
4926
5090
  line-height: 1;
4927
5091
  padding: 2px 4px;
4928
5092
  }
@@ -4957,7 +5121,7 @@
4957
5121
  color: var(--text-faint);
4958
5122
  width: 18px;
4959
5123
  padding: 0 4px 0 0;
4960
- font-size: 13px;
5124
+ font-size: 14px;
4961
5125
  line-height: 1;
4962
5126
  cursor: pointer;
4963
5127
  opacity: 0;
@@ -5294,7 +5458,7 @@
5294
5458
  what the user is looking at before they get to the verdict. */
5295
5459
  .dv-purpose {
5296
5460
  font-family: var(--font-human);
5297
- font-size: 13px;
5461
+ font-size: 14px;
5298
5462
  line-height: 1.55;
5299
5463
  color: var(--text-soft);
5300
5464
  margin: 0;
@@ -5347,7 +5511,7 @@
5347
5511
  }
5348
5512
  .dv-verdict-body {
5349
5513
  font-family: var(--font-human);
5350
- font-size: 13px;
5514
+ font-size: 14px;
5351
5515
  line-height: 1.55;
5352
5516
  color: var(--text-soft);
5353
5517
  margin: 0;
@@ -5368,7 +5532,7 @@
5368
5532
  }
5369
5533
  .dv-unexplored-row {
5370
5534
  font-family: var(--font-human);
5371
- font-size: 13px;
5535
+ font-size: 14px;
5372
5536
  line-height: 1.5;
5373
5537
  color: var(--text);
5374
5538
  padding: 8px 12px;
@@ -5546,7 +5710,7 @@
5546
5710
  overflows. Mirrors the adjourn-overlay / room-settings clamp
5547
5711
  pattern so all three modals read identically. */
5548
5712
  .followup-parent-subject {
5549
- font-size: 13px;
5713
+ font-size: 14px;
5550
5714
  font-weight: 600;
5551
5715
  color: var(--text);
5552
5716
  line-height: 1.4;
@@ -6232,7 +6396,7 @@
6232
6396
  common case (textarea + one strip); tall textarea +
6233
6397
  multiple strips can still cover the bottom message, the
6234
6398
  user scrolls a touch — acceptable. */
6235
- padding: calc(14px + var(--room-head-h, 56px)) 50px 140px 40px;
6399
+ padding: calc(14px + var(--room-head-h, 56px)) 40px 140px 40px;
6236
6400
  transition: opacity 0.18s ease-out;
6237
6401
  }
6238
6402
  /* Note-jump loading state · the user clicked a note in the All
@@ -6551,7 +6715,7 @@
6551
6715
  color: var(--text);
6552
6716
  text-decoration: none;
6553
6717
  font-family: var(--font-human);
6554
- font-size: 13px;
6718
+ font-size: 14px;
6555
6719
  font-weight: 600;
6556
6720
  letter-spacing: -0.005em;
6557
6721
  line-height: 1.4;
@@ -6563,6 +6727,7 @@
6563
6727
  text-overflow: ellipsis;
6564
6728
  white-space: nowrap;
6565
6729
  min-width: 0;
6730
+ font-weight: 400;
6566
6731
  }
6567
6732
  .msg-tool-sources-title:hover {
6568
6733
  color: var(--lime);
@@ -7188,7 +7353,7 @@
7188
7353
  align-items: center;
7189
7354
  justify-content: center;
7190
7355
  font-weight: 700;
7191
- font-size: 13px;
7356
+ font-size: 14px;
7192
7357
  border: 0.5px solid var(--line-strong);
7193
7358
  font-family: var(--mono);
7194
7359
  }
@@ -7444,12 +7609,12 @@
7444
7609
  Used by quote-cta probe / second messages and any plain
7445
7610
  markdown blockquote a director / chair might emit. */
7446
7611
  .msg-bubble .msg-quote {
7447
- margin: 0 0 12px;
7448
- padding: 10px 14px;
7612
+ margin: 0;
7613
+ padding: 10px 0;
7449
7614
  background: var(--panel-2);
7450
7615
  font-family: var(--font-agent);
7451
7616
  font-style: italic;
7452
- font-size: 13px;
7617
+ font-size: 14px;
7453
7618
  line-height: 1.55;
7454
7619
  color: var(--text-soft);
7455
7620
  }
@@ -7465,7 +7630,6 @@
7465
7630
  font-style: normal;
7466
7631
  margin-bottom: 6px;
7467
7632
  }
7468
- .msg-bubble .msg-quote:last-child { margin-bottom: 0; }
7469
7633
  /* Markdown tables · editorial style. No outer box, no zebra, no
7470
7634
  uppercase data-grid headers — those read as "spreadsheet pasted
7471
7635
  into a conversation" and broke the bubble's literary register.
@@ -7482,7 +7646,7 @@
7482
7646
  width: 100%;
7483
7647
  font-family: var(--font-human);
7484
7648
  font-style: normal;
7485
- font-size: 13px;
7649
+ font-size: 14px;
7486
7650
  line-height: 1.55;
7487
7651
  color: var(--text-soft);
7488
7652
  letter-spacing: -0.003em;
@@ -7818,7 +7982,7 @@
7818
7982
  .kp-row:hover { border-color: var(--line-strong); }
7819
7983
  .kp-body {
7820
7984
  font-family: var(--font-human);
7821
- font-size: 13px;
7985
+ font-size: 14px;
7822
7986
  color: var(--text);
7823
7987
  line-height: 1.4;
7824
7988
  }
@@ -8634,6 +8798,25 @@
8634
8798
  .rt-avatar[data-agent]:hover {
8635
8799
  filter: brightness(1.18);
8636
8800
  }
8801
+ /* Speaking squash-blink · the avatar's eyes are baked into the
8802
+ sprite (img / inline pixel-SVG), so a quick vertical scaleY
8803
+ squash is the cheapest "blink while talking" cue. Only the
8804
+ speaking seat animates; the dip is brief + shallow so it reads
8805
+ as a blink, not a bounce. transform keeps the static
8806
+ translateX(-50%) so the sprite stays centered on the chair. */
8807
+ .rt-seat-speaking .rt-avatar {
8808
+ transform-origin: center 45%;
8809
+ animation: rt-blink-squash 4.2s ease-in-out infinite;
8810
+ }
8811
+ @keyframes rt-blink-squash {
8812
+ 0%, 88%, 100% { transform: translateX(-50%) scaleY(1); }
8813
+ 92% { transform: translateX(-50%) scaleY(0.88); }
8814
+ 96% { transform: translateX(-50%) scaleY(1); }
8815
+ }
8816
+ /* Respect reduced-motion · hold the avatar at full height. */
8817
+ @media (prefers-reduced-motion: reduce) {
8818
+ .rt-seat-speaking .rt-avatar { animation: none; }
8819
+ }
8637
8820
 
8638
8821
  /* Name plate · small mono caption ABOVE the avatar (was below,
8639
8822
  but back-row director seats had their names land on the table
@@ -9538,7 +9721,7 @@
9538
9721
  height: 16px;
9539
9722
  line-height: 1;
9540
9723
  font-family: var(--mono);
9541
- font-size: 13px;
9724
+ font-size: 14px;
9542
9725
  font-weight: 700;
9543
9726
  padding: 0;
9544
9727
  cursor: pointer;
@@ -11021,7 +11204,7 @@
11021
11204
  }
11022
11205
  .starter-deck {
11023
11206
  font-family: var(--font-human);
11024
- font-size: 13px;
11207
+ font-size: 14px;
11025
11208
  color: var(--text-soft);
11026
11209
  line-height: 1.5;
11027
11210
  margin: 0;
@@ -11273,15 +11456,6 @@
11273
11456
  align-content: initial !important;
11274
11457
  overflow-y: auto;
11275
11458
  }
11276
- .chat.chat--composer.chat--composer-overflow .cmp {
11277
- /* Overflow mode · the starters tray (now augmented with
11278
- topic recommendations + the "+ N more" button) can push
11279
- total content past one viewport, at which point the
11280
- composer can't stay centred. Pin the hero a fixed 120px
11281
- from the top so it sits comfortably above the fold and
11282
- the tray scrolls underneath. */
11283
- padding-top: 120px;
11284
- }
11285
11459
  .cmp-hero {
11286
11460
  text-align: center;
11287
11461
  margin-bottom: 22px;
@@ -11868,7 +12042,7 @@
11868
12042
  background: transparent;
11869
12043
  border: none;
11870
12044
  color: var(--text-faint);
11871
- font-size: 13px;
12045
+ font-size: 14px;
11872
12046
  line-height: 1;
11873
12047
  cursor: pointer;
11874
12048
  transition: color 0.1s;
@@ -12381,7 +12555,7 @@
12381
12555
  }
12382
12556
  .ag-gen-error-desc-body {
12383
12557
  font-family: var(--font-human);
12384
- font-size: 13px;
12558
+ font-size: 14px;
12385
12559
  line-height: 1.55;
12386
12560
  color: var(--text-soft);
12387
12561
  }
@@ -12412,7 +12586,7 @@
12412
12586
  color: var(--lime);
12413
12587
  }
12414
12588
  .ag-gen-error-retry-mark {
12415
- font-size: 13px;
12589
+ font-size: 14px;
12416
12590
  line-height: 1;
12417
12591
  }
12418
12592
  .ag-gen-error-discard {
@@ -12848,7 +13022,7 @@
12848
13022
  }
12849
13023
  .pb-done-sub {
12850
13024
  font-family: var(--font-human);
12851
- font-size: 13px;
13025
+ font-size: 14px;
12852
13026
  color: var(--text-soft);
12853
13027
  line-height: 1.5;
12854
13028
  margin: 0 auto 18px;
@@ -12890,7 +13064,7 @@
12890
13064
  background: transparent;
12891
13065
  color: var(--lime);
12892
13066
  }
12893
- .pb-done-open-mark { font-size: 13px; line-height: 1; }
13067
+ .pb-done-open-mark { font-size: 14px; line-height: 1; }
12894
13068
 
12895
13069
  /* When the manual overlay is in Full-mode reuse, surface the
12896
13070
  build's footer-meta override in lime (it carries the
@@ -12915,19 +13089,6 @@
12915
13089
  padding: 16px 0 32px;
12916
13090
  color: var(--text);
12917
13091
  }
12918
- /* Persona-builder layout · default behaviour is the SAME as the
12919
- new-room / new-agent hero: vertically centred by
12920
- `.chat.chat--composer { align-content: center }`. When the
12921
- dashboard's natural height exceeds 90% of the viewport (the
12922
- overflow trigger for pb-stage · `updateComposerOverflow` in
12923
- app.js switches its threshold from 70% of chat height to 90%
12924
- of `window.innerHeight` when a `.pb-stage` is present),
12925
- `.chat--composer-overflow` kicks in and we reduce the default
12926
- 120 px top offset to 50 px so the dashboard pins comfortably
12927
- near the top of the panel rather than getting shoved down. */
12928
- .chat.chat--composer.chat--composer-overflow:has(.pb-stage) .cmp {
12929
- padding-top: 50px !important;
12930
- }
12931
13092
  .pb-stage-head {
12932
13093
  display: grid;
12933
13094
  grid-template-columns: 1fr auto;
@@ -13472,7 +13633,7 @@
13472
13633
  background: color-mix(in srgb, var(--red) 5%, transparent);
13473
13634
  }
13474
13635
  .pb-cancel-mark {
13475
- font-size: 13px;
13636
+ font-size: 14px;
13476
13637
  line-height: 1;
13477
13638
  }
13478
13639
  .pb-foot-hint {
@@ -13531,7 +13692,7 @@
13531
13692
  border: 0.5px solid var(--line-bright);
13532
13693
  color: var(--text);
13533
13694
  font-family: var(--font-human);
13534
- font-size: 13px;
13695
+ font-size: 14px;
13535
13696
  padding: 7px 10px;
13536
13697
  width: 100%;
13537
13698
  box-sizing: border-box;
@@ -13666,7 +13827,7 @@
13666
13827
  gap: 12px;
13667
13828
  }
13668
13829
  .ag-persona-error-detail {
13669
- font-size: 13px;
13830
+ font-size: 14px;
13670
13831
  color: var(--text);
13671
13832
  line-height: 1.55;
13672
13833
  }
@@ -13972,7 +14133,7 @@
13972
14133
  border: 0.5px solid var(--line-strong);
13973
14134
  color: var(--text);
13974
14135
  font-family: var(--font-human);
13975
- font-size: 13px;
14136
+ font-size: 14px;
13976
14137
  line-height: 1.5;
13977
14138
  padding: 8px 10px;
13978
14139
  width: 100%;
@@ -14273,7 +14434,7 @@
14273
14434
  .brief-picker-title-head {
14274
14435
  font-family: var(--mono);
14275
14436
  font-size: 14px;
14276
- font-weight: 700;
14437
+ font-weight: 400;
14277
14438
  letter-spacing: 0.04em;
14278
14439
  color: var(--text);
14279
14440
  }
@@ -14291,7 +14452,7 @@
14291
14452
  }
14292
14453
  .brief-picker-row {
14293
14454
  display: grid;
14294
- grid-template-columns: 32px 1fr auto auto;
14455
+ grid-template-columns: 32px 1fr auto;
14295
14456
  gap: 10px;
14296
14457
  align-items: center;
14297
14458
  padding: 10px 12px;
@@ -14318,8 +14479,8 @@
14318
14479
  }
14319
14480
  .brief-picker-title {
14320
14481
  font-family: var(--font-human, system-ui, sans-serif);
14321
- font-size: 13px;
14322
- font-weight: 600;
14482
+ font-size: 14px;
14483
+ font-weight: 400;
14323
14484
  color: var(--text);
14324
14485
  line-height: 1.3;
14325
14486
  overflow: hidden;
@@ -14327,6 +14488,15 @@
14327
14488
  -webkit-line-clamp: 2;
14328
14489
  -webkit-box-orient: vertical;
14329
14490
  }
14491
+ /* Subtitle row · supplement label (left) + filed time (right) share
14492
+ one line beneath the title, so the title row gets the full main
14493
+ column width instead of competing with the time for space. */
14494
+ .brief-picker-subrow {
14495
+ display: flex;
14496
+ align-items: center;
14497
+ gap: 8px;
14498
+ min-width: 0;
14499
+ }
14330
14500
  .brief-picker-sub {
14331
14501
  font-family: var(--mono);
14332
14502
  font-size: 10px;
@@ -14335,6 +14505,8 @@
14335
14505
  overflow: hidden;
14336
14506
  text-overflow: ellipsis;
14337
14507
  white-space: nowrap;
14508
+ flex: 1 1 auto;
14509
+ min-width: 0;
14338
14510
  }
14339
14511
  .brief-picker-time {
14340
14512
  font-family: var(--mono);
@@ -14342,6 +14514,8 @@
14342
14514
  letter-spacing: 0.04em;
14343
14515
  color: var(--text-faint);
14344
14516
  white-space: nowrap;
14517
+ flex: 0 0 auto;
14518
+ margin-left: auto;
14345
14519
  }
14346
14520
  .brief-picker-arrow {
14347
14521
  font-family: var(--mono);
@@ -15437,7 +15611,7 @@
15437
15611
  flex: 1 1 auto;
15438
15612
  min-width: 0;
15439
15613
  font-family: var(--sans);
15440
- font-size: 13px;
15614
+ font-size: 14px;
15441
15615
  font-weight: 600;
15442
15616
  /* Theme accent · `--lime` is the per-theme primary token
15443
15617
  (swapped by every theme in themes.css). Sits on the
@@ -15612,6 +15786,9 @@
15612
15786
  <script src="app-updater.js" defer></script>
15613
15787
  <link rel="stylesheet" href="quote-cta.css">
15614
15788
  <script src="quote-cta.js" defer></script>
15789
+ <link rel="stylesheet" href="thread.css">
15790
+ <link rel="stylesheet" href="voice-clone.css">
15791
+ <script src="voice-clone.js" defer></script>
15615
15792
  <script src="typing-sfx.js" defer></script>
15616
15793
  <script src="agent-build-bgm.js" defer></script>
15617
15794
  <script src="share-cover-svg-creator.js" defer></script>
@@ -15888,6 +16065,45 @@
15888
16065
  <!-- Resizable handle: sidebar | main -->
15889
16066
  <div class="col-resizer" data-resize data-var="--sidebar-w" data-side="left" data-min="220" data-max="480" data-i18n-title="col_resize"></div>
15890
16067
 
16068
+ <!-- ─── Mini (collapsed) sidebar · ChatGPT-style icon rail ───
16069
+ Shown ONLY while body.sidebar-collapsed (the full .sidebar is
16070
+ display:none then). Source order matters: it sits between the
16071
+ (hidden) resizer and <main> so grid auto-placement drops it in
16072
+ column 1 and main in column 2 when collapsed.
16073
+ Icon order: logo (→ expand), new room, new agent, search,
16074
+ reports, notes, divider, rooms (→ expand+tab), agents
16075
+ (→ expand+tab). Foot: user avatar (→ settings). New room /
16076
+ agent / search / reports / notes keep the room collapsed (they
16077
+ reuse the same document-delegated data-* triggers the full
16078
+ sidebar uses); only the logo + rooms + agents expand. -->
16079
+ <aside class="mini-sidebar" aria-label="Collapsed sidebar navigation">
16080
+ <div class="mini-top">
16081
+ <button type="button" class="mini-btn mini-logo" data-sidebar-expand data-i18n-tip="sidebar_expand" data-tip="Expand sidebar" aria-label="Expand sidebar">
16082
+ <span class="mini-logo-av">
16083
+ <img class="cl-open" src="/avatars/chair.svg" alt="" aria-hidden="true">
16084
+ <img class="cl-blink" src="/avatars/chair-blink.svg" alt="" aria-hidden="true">
16085
+ </span>
16086
+ </button>
16087
+ <button type="button" class="mini-btn mini-new-room" data-convene-trigger data-i18n-tip="sidebar_new_room" data-tip="New room" aria-label="New room"></button>
16088
+ <button type="button" class="mini-btn mini-new-agent" data-agent-composer-trigger data-i18n-tip="sidebar_new_agent" data-tip="New agent" aria-label="New agent"></button>
16089
+ <button type="button" class="mini-btn mini-search" data-search-trigger data-tip="Search" aria-label="Search"></button>
16090
+ <button type="button" class="mini-btn mini-reports" data-reports-trigger data-i18n-tip="sidebar_all_reports" data-tip="Reports" aria-label="All Reports"></button>
16091
+ <button type="button" class="mini-btn mini-notes" data-notes-trigger data-i18n-tip="sidebar_all_notes" data-tip="Notes" aria-label="All Notes"></button>
16092
+ <span class="mini-sep" aria-hidden="true"></span>
16093
+ <button type="button" class="mini-btn mini-tab" data-mini-tab="rooms" data-i18n-tip="sidebar_tab_rooms" data-tip="Rooms" aria-label="Rooms">
16094
+ <i class="ic ic-rooms"></i>
16095
+ </button>
16096
+ <button type="button" class="mini-btn mini-tab" data-mini-tab="agents" data-i18n-tip="sidebar_tab_agents" data-tip="Agents" aria-label="Agents">
16097
+ <i class="ic ic-agents"></i>
16098
+ </button>
16099
+ </div>
16100
+ <div class="mini-foot">
16101
+ <a href="#" class="mini-btn mini-user" data-user-settings-trigger data-i18n-tip="settings_title" data-tip="Settings" aria-label="Settings">
16102
+ <div class="user-av mini-user-av" data-user-avatar>K</div>
16103
+ </a>
16104
+ </div>
16105
+ </aside>
16106
+
15891
16107
  <!-- ═══════════════ MAIN: room view + agent profile view ═══════════════ -->
15892
16108
  <main class="main">
15893
16109
 
@@ -16530,6 +16746,21 @@
16530
16746
  clearSidebarPeek();
16531
16747
  return;
16532
16748
  }
16749
+ // Mini-rail rooms / agents · expand the full sidebar. The tab
16750
+ // switch + first-room/agent default is owned by the sidebar-tab
16751
+ // IIFE's click handler (it has ROOMS_KEY / appFirstRoomId /
16752
+ // activate in scope — they're NOT visible here). Both handlers
16753
+ // fire on the same click: this one expands, that one navigates.
16754
+ // The other mini buttons (new room / agent / search / reports /
16755
+ // notes) deliberately do NOT expand — they fire their own
16756
+ // document-delegated triggers and the rail stays collapsed.
16757
+ if (e.target.closest("[data-mini-tab]")) {
16758
+ e.preventDefault();
16759
+ applySidebarCollapsed(false);
16760
+ writeSidebarCollapsed(false);
16761
+ clearSidebarPeek();
16762
+ return;
16763
+ }
16533
16764
  });
16534
16765
 
16535
16766
  // ─── Sidebar edge-peek · Arc-browser style ───
@@ -16579,6 +16810,10 @@
16579
16810
  // miss `.sidebar` during the open animation. Immediate close on
16580
16811
  // exit is handled by the in→out transition branch below, which
16581
16812
  // bypasses this lock.
16813
+ // The floated full sidebar is opaque (--sidebar-bg) at 280px and
16814
+ // pins to left:0, so it cleanly covers the 56px mini rail while
16815
+ // peeked — hovering the left edge reveals the full sidebar, and
16816
+ // moving off it drops back to the rail.
16582
16817
  setPeek(true);
16583
16818
  peekLockedUntil = Date.now() + PEEK_OPEN_LOCK_MS;
16584
16819
  }
@@ -16994,6 +17229,34 @@
16994
17229
  tracker. These don't conflict with stopPropagation because they
16995
17230
  match elements that no other capture handler swallows. */
16996
17231
  document.addEventListener("click", (e) => {
17232
+ // Mini-rail rooms / agents switcher · expand is handled by the
17233
+ // collapse IIFE; here we own the tab switch + default selection.
17234
+ // Seed the FIRST room / agent when coming from a non-room /
17235
+ // non-agent context (reports, notes, composer, the other tab) so
17236
+ // the icon actually lands you on content instead of restoring the
17237
+ // last sub-state (e.g. "new" / "reports"). If you're already
17238
+ // viewing a room / agent profile, leave it so expand keeps place.
17239
+ const miniTab = e.target.closest("[data-mini-tab]");
17240
+ if (miniTab) {
17241
+ e.preventDefault();
17242
+ const which = miniTab.getAttribute("data-mini-tab");
17243
+ if (which === "rooms") {
17244
+ if (!window.app || !window.app.currentRoomId) {
17245
+ const first = appFirstRoomId();
17246
+ if (first) lsSet(ROOMS_KEY, first);
17247
+ }
17248
+ } else if (which === "agents") {
17249
+ const agentView = document.querySelector('[data-main-view="agent"]');
17250
+ const inAgentProfile = agentView && !agentView.hasAttribute("hidden");
17251
+ if (!inAgentProfile) {
17252
+ const firstAgent = document.querySelector('[data-sidebar-panel="agents"] .agent-row[data-agent-profile]');
17253
+ const id = firstAgent && firstAgent.getAttribute("data-agent-profile");
17254
+ if (id) lsSet(AGENTS_KEY, id);
17255
+ }
17256
+ }
17257
+ activate(which, { fromTabClick: true });
17258
+ return;
17259
+ }
16997
17260
  // Tab click
16998
17261
  const tab = e.target.closest(".sidebar-tab[data-sidebar-tab]");
16999
17262
  if (tab) {