privateboard 0.1.8 → 0.1.10
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/dist/cli.js +879 -180
- package/dist/cli.js.map +1 -1
- package/package.json +1 -1
- package/public/adjourn-overlay.css +2 -1
- package/public/agent-profile.css +525 -0
- package/public/agent-profile.js +278 -1
- package/public/app.js +279 -120
- package/public/index.html +312 -164
- package/public/magazine.html +1257 -838
- package/public/newspaper.html +1389 -1267
- package/public/ppt.html +2623 -0
- package/public/room-settings.css +40 -4
- package/public/room-settings.js +44 -2
- package/public/user-settings.css +23 -21
- package/public/user-settings.js +1 -4
- package/public/bento.html +0 -1396
package/public/index.html
CHANGED
|
@@ -166,13 +166,22 @@
|
|
|
166
166
|
.body-grid {
|
|
167
167
|
display: grid;
|
|
168
168
|
grid-template-columns: var(--sidebar-w) 8px 1fr;
|
|
169
|
+
grid-template-rows: 1fr;
|
|
169
170
|
gap: 0;
|
|
170
171
|
overflow: hidden;
|
|
171
172
|
min-height: 0;
|
|
172
173
|
/* `position: relative` so the floating sidebar-expand-btn
|
|
173
174
|
(absolute-positioned) anchors to the body-grid's top-left
|
|
174
175
|
edge — which, after the topbar's retirement, is the viewport's
|
|
175
|
-
top-left under the 8px page padding.
|
|
176
|
+
top-left under the 8px page padding.
|
|
177
|
+
`grid-template-rows: 1fr` is the load-bearing fix that lets
|
|
178
|
+
the height chain reach all the way down to `[data-chat-messages]`.
|
|
179
|
+
Without it the implicit row is `auto` (= content height), so
|
|
180
|
+
`.main`/`.main-view`/`.body`/`.chat-col`/`.chat` all collapse
|
|
181
|
+
to content size — and any centring rule on `.chat` has no free
|
|
182
|
+
space to work with. With `1fr`, every flex/grid layer below
|
|
183
|
+
inherits a viewport-relative height and the composer can
|
|
184
|
+
actually centre vertically inside `.chat`. */
|
|
176
185
|
position: relative;
|
|
177
186
|
}
|
|
178
187
|
|
|
@@ -559,19 +568,9 @@
|
|
|
559
568
|
overflow: hidden;
|
|
560
569
|
text-overflow: ellipsis;
|
|
561
570
|
}
|
|
562
|
-
/* Time tag ·
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
visually a different "kind of metadata" from the rooms time. */
|
|
566
|
-
.agent-row .agent-row-time {
|
|
567
|
-
color: var(--text-dim);
|
|
568
|
-
font-size: 10.5px;
|
|
569
|
-
font-family: var(--mono);
|
|
570
|
-
letter-spacing: 0;
|
|
571
|
-
text-transform: none;
|
|
572
|
-
white-space: nowrap;
|
|
573
|
-
flex-shrink: 0;
|
|
574
|
-
}
|
|
571
|
+
/* Time tag removed · the agent-row no longer surfaces creation
|
|
572
|
+
time. The space is given back to the title (which can now use
|
|
573
|
+
the full row width minus the hover-revealed pin button). */
|
|
575
574
|
.agent-row .agent-row-subtitle {
|
|
576
575
|
font-family: var(--mono);
|
|
577
576
|
font-size: 9.5px;
|
|
@@ -647,24 +646,50 @@
|
|
|
647
646
|
align-items: center;
|
|
648
647
|
justify-content: center;
|
|
649
648
|
}
|
|
650
|
-
/* CHAIR badge ·
|
|
651
|
-
|
|
652
|
-
|
|
649
|
+
/* CHAIR badge · pure typographic kicker, no pill geometry.
|
|
650
|
+
Earlier this was an outlined hairline pill (cyan border + cyan
|
|
651
|
+
text + transparent fill) — the only colored hairline box
|
|
652
|
+
anywhere on the page, so it read as a sticker rather than part
|
|
653
|
+
of the design system. The refined treatment drops border /
|
|
654
|
+
background / padding / radius and keeps the chair's identity
|
|
655
|
+
signal in pure typography:
|
|
656
|
+
· mono uppercase, generous letter-spacing (matches section
|
|
657
|
+
headers, brief banner kickers, time tags)
|
|
658
|
+
· cyan color (the chair's accent stays)
|
|
659
|
+
· em-dash sigil prefix separates it from the title — soft
|
|
660
|
+
enough to feel like part of the same line, distinct enough
|
|
661
|
+
that the eye lands on it as a label, not a name fragment
|
|
662
|
+
· weight 600 (one notch below 700) so it doesn't compete
|
|
663
|
+
with the title's emphasis
|
|
664
|
+
The avatar already carries the `⊘` immutability cue and the
|
|
665
|
+
section header already says "Chair", so this badge is now the
|
|
666
|
+
quietest possible identity stamp instead of a third tier of
|
|
667
|
+
decoration. */
|
|
653
668
|
.agent-row .agent-row-chair-badge {
|
|
654
669
|
font-family: var(--mono);
|
|
655
|
-
font-size:
|
|
656
|
-
font-weight:
|
|
657
|
-
letter-spacing: 0.
|
|
670
|
+
font-size: 9px;
|
|
671
|
+
font-weight: 600;
|
|
672
|
+
letter-spacing: 0.2em;
|
|
658
673
|
text-transform: uppercase;
|
|
659
|
-
|
|
660
|
-
|
|
674
|
+
/* Track the active theme's primary accent — `--lime` remaps per
|
|
675
|
+
theme (gold in regent, blue in apple, red in pinterest,
|
|
676
|
+
magenta in amuse, etc.). The earlier `--cyan` was static and
|
|
677
|
+
clashed in themes whose palette didn't include teal. */
|
|
678
|
+
color: var(--lime);
|
|
661
679
|
background: transparent;
|
|
662
|
-
border:
|
|
663
|
-
|
|
680
|
+
border: none;
|
|
681
|
+
padding: 0;
|
|
664
682
|
white-space: nowrap;
|
|
665
683
|
align-self: center;
|
|
666
684
|
margin-left: auto;
|
|
667
685
|
}
|
|
686
|
+
.agent-row .agent-row-chair-badge::before {
|
|
687
|
+
content: "—";
|
|
688
|
+
color: var(--text-faint);
|
|
689
|
+
margin-right: 8px;
|
|
690
|
+
font-weight: 400;
|
|
691
|
+
letter-spacing: 0;
|
|
692
|
+
}
|
|
668
693
|
.agent-row .agent-row-chair-role { color: var(--text-soft); font-weight: 600; }
|
|
669
694
|
.agent-row .agent-row-chair-note { color: var(--text-faint); }
|
|
670
695
|
/* Hide the Chair section's count badge — there's only ever one. */
|
|
@@ -899,13 +924,10 @@
|
|
|
899
924
|
.agents-section-header.pinned { color: var(--text-soft); }
|
|
900
925
|
|
|
901
926
|
/* ─── Pin toggle (per-row · hover-revealed for non-pinned) ───
|
|
902
|
-
Positioned absolutely so the
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
the agent's "7h" tag inward compared to the rooms list and made
|
|
907
|
-
the two columns of timestamps look mis-aligned. The row-shell
|
|
908
|
-
ancestor needs `position: relative` for this to anchor correctly. */
|
|
927
|
+
Positioned absolutely so it overlays the right edge of the row
|
|
928
|
+
without taking in-flow width (opacity:0 alone would still
|
|
929
|
+
reserve 22px and shrink the title). The row-shell ancestor
|
|
930
|
+
needs `position: relative` for this to anchor correctly. */
|
|
909
931
|
.agent-row-shell, .session-row-shell { position: relative; }
|
|
910
932
|
.row-top-line .pin-toggle,
|
|
911
933
|
.agent-row-top-line .pin-toggle {
|
|
@@ -933,10 +955,6 @@
|
|
|
933
955
|
}
|
|
934
956
|
.session-row:hover .pin-toggle,
|
|
935
957
|
.agent-row:hover .pin-toggle { opacity: 0.55; }
|
|
936
|
-
/* Hover · hide the in-flow time so the absolutely-positioned pin
|
|
937
|
-
button has the right edge to itself. Same pattern as the rooms
|
|
938
|
-
list (`.session-row-shell:hover .row-time { visibility: hidden }`). */
|
|
939
|
-
.agent-row-shell:hover .agent-row-time { visibility: hidden; }
|
|
940
958
|
.pin-toggle:hover { opacity: 1 !important; color: var(--lime); }
|
|
941
959
|
.session-row.pinned .pin-toggle,
|
|
942
960
|
.agent-row.pinned .pin-toggle {
|
|
@@ -996,10 +1014,8 @@
|
|
|
996
1014
|
z-index: 2;
|
|
997
1015
|
}
|
|
998
1016
|
.session-row-shell:hover .row-delete { display: flex; }
|
|
999
|
-
/*
|
|
1000
|
-
|
|
1001
|
-
before the X. */
|
|
1002
|
-
.session-row-shell:hover .row-time { visibility: hidden; }
|
|
1017
|
+
/* Subtitle reserves right padding so its ellipsis stops before
|
|
1018
|
+
the absolutely-positioned X delete button on hover. */
|
|
1003
1019
|
.session-row-shell .row-subtitle { padding-right: 26px; }
|
|
1004
1020
|
.row-delete:hover {
|
|
1005
1021
|
border-color: var(--red);
|
|
@@ -1088,17 +1104,14 @@
|
|
|
1088
1104
|
font-family: var(--sans);
|
|
1089
1105
|
letter-spacing: -0.005em;
|
|
1090
1106
|
}
|
|
1091
|
-
.row-time
|
|
1092
|
-
|
|
1093
|
-
|
|
1094
|
-
|
|
1095
|
-
|
|
1096
|
-
}
|
|
1097
|
-
.session-row-shell[data-status="live"] .row-time {
|
|
1107
|
+
/* Live indicator · was a `● ` prefix on the (now-removed) .row-time
|
|
1108
|
+
tag. Migrated to a title prefix so live rooms still read at a
|
|
1109
|
+
glance. The rest of the title stays its usual colour. */
|
|
1110
|
+
.session-row-shell[data-status="live"] .row-title::before {
|
|
1111
|
+
content: "● ";
|
|
1098
1112
|
color: var(--lime);
|
|
1099
1113
|
font-weight: 700;
|
|
1100
1114
|
}
|
|
1101
|
-
.session-row-shell[data-status="live"] .row-time::before { content: "● "; }
|
|
1102
1115
|
|
|
1103
1116
|
.row-subtitle {
|
|
1104
1117
|
font-size: 11.5px;
|
|
@@ -1262,6 +1275,7 @@
|
|
|
1262
1275
|
grid-template-rows: auto 1fr auto;
|
|
1263
1276
|
overflow: hidden;
|
|
1264
1277
|
min-height: 0;
|
|
1278
|
+
height: 100%;
|
|
1265
1279
|
}
|
|
1266
1280
|
.main-view[data-main-view="agent"] {
|
|
1267
1281
|
/* Switch from grid to block so the profile card flows naturally
|
|
@@ -1514,6 +1528,18 @@
|
|
|
1514
1528
|
color: var(--text-faint);
|
|
1515
1529
|
letter-spacing: 0.06em;
|
|
1516
1530
|
}
|
|
1531
|
+
/* Report-mode type chip in the kicker line · "REPORT" / "BENTO" /
|
|
1532
|
+
"MAGAZINE" / "NEWSPAPER". Mono register matching the kicker.
|
|
1533
|
+
Slight color shift per mode so the type is glanceable without
|
|
1534
|
+
being a heavy badge. */
|
|
1535
|
+
.reports-item-type {
|
|
1536
|
+
color: var(--text-soft);
|
|
1537
|
+
letter-spacing: 0.16em;
|
|
1538
|
+
font-weight: 700;
|
|
1539
|
+
}
|
|
1540
|
+
.reports-item-type[data-mode="magazine"] { color: var(--amber, #C8A36F); }
|
|
1541
|
+
.reports-item-type[data-mode="newspaper"] { color: var(--text); }
|
|
1542
|
+
.reports-item-type[data-mode="ppt"] { color: var(--cyan, #6FC3C8); }
|
|
1517
1543
|
.reports-item-sep {
|
|
1518
1544
|
color: var(--text-faint);
|
|
1519
1545
|
opacity: 0.7;
|
|
@@ -1790,8 +1816,7 @@
|
|
|
1790
1816
|
.notes-item-passage { padding-right: 30px; }
|
|
1791
1817
|
/* On hover · hide the trailing time tag so the delete button
|
|
1792
1818
|
occupies the right-edge slot cleanly. visibility:hidden keeps
|
|
1793
|
-
the layout box (the meta row doesn't reflow either).
|
|
1794
|
-
pattern as `.session-row-shell:hover .row-time` in rooms. */
|
|
1819
|
+
the layout box (the meta row doesn't reflow either). */
|
|
1795
1820
|
.notes-item:hover .notes-item-time { visibility: hidden; }
|
|
1796
1821
|
.notes-item-link {
|
|
1797
1822
|
display: block;
|
|
@@ -2117,15 +2142,17 @@
|
|
|
2117
2142
|
.notes-item-subject { max-width: 160px; }
|
|
2118
2143
|
}
|
|
2119
2144
|
|
|
2120
|
-
/* Room head —
|
|
2121
|
-
|
|
2145
|
+
/* Room head — compact register · the title is the only
|
|
2146
|
+
vertical anchor users actually read here, so we keep the
|
|
2147
|
+
padding moderately tight while still leaving the kicker +
|
|
2148
|
+
title pair some breathing room. */
|
|
2122
2149
|
.room-head {
|
|
2123
2150
|
background: var(--panel-2);
|
|
2124
2151
|
border-bottom: 0.5px solid var(--line-bright);
|
|
2125
|
-
padding: 16px
|
|
2152
|
+
padding: 11px 16px;
|
|
2126
2153
|
display: grid;
|
|
2127
2154
|
grid-template-columns: 1fr auto;
|
|
2128
|
-
gap:
|
|
2155
|
+
gap: 12px;
|
|
2129
2156
|
align-items: center;
|
|
2130
2157
|
}
|
|
2131
2158
|
/* When the sidebar is collapsed the room-head gains a leading
|
|
@@ -2144,36 +2171,51 @@
|
|
|
2144
2171
|
at this level. */
|
|
2145
2172
|
.room-info { min-width: 0; overflow: visible; }
|
|
2146
2173
|
|
|
2147
|
-
|
|
2174
|
+
/* ─── Compact kicker · single mono line at top of room-head ───
|
|
2175
|
+
Replaces the prior `.room-id` row (`MEETING ROOM` badge +
|
|
2176
|
+
`// ROOM #N · TONE`) AND the `.room-meta` row (tone/intensity/
|
|
2177
|
+
report tagged pills + status stamp). Net: three stacked rows
|
|
2178
|
+
collapse into one. Tone / intensity / brief-style remain
|
|
2179
|
+
editable in the room-settings overlay. */
|
|
2180
|
+
.room-kicker {
|
|
2148
2181
|
display: flex;
|
|
2149
2182
|
align-items: center;
|
|
2150
|
-
gap:
|
|
2151
|
-
|
|
2152
|
-
}
|
|
2153
|
-
.room-name {
|
|
2183
|
+
gap: 6px;
|
|
2184
|
+
font-family: var(--mono);
|
|
2154
2185
|
font-size: 9.5px;
|
|
2155
|
-
|
|
2156
|
-
background: var(--lime);
|
|
2157
|
-
padding: 1px 7px;
|
|
2186
|
+
letter-spacing: 0.12em;
|
|
2158
2187
|
text-transform: uppercase;
|
|
2159
|
-
|
|
2188
|
+
color: var(--text-dim);
|
|
2189
|
+
margin-bottom: 5px;
|
|
2190
|
+
flex-wrap: wrap;
|
|
2160
2191
|
font-weight: 700;
|
|
2192
|
+
line-height: 1.3;
|
|
2161
2193
|
}
|
|
2162
|
-
.
|
|
2163
|
-
|
|
2164
|
-
|
|
2165
|
-
|
|
2166
|
-
|
|
2194
|
+
.kicker-num { color: var(--text-soft); }
|
|
2195
|
+
.kicker-tone { color: var(--lime); cursor: help; }
|
|
2196
|
+
.kicker-intensity { color: var(--amber); }
|
|
2197
|
+
.kicker-sep { color: var(--text-faint); opacity: 0.6; }
|
|
2198
|
+
.kicker-status { display: inline-flex; align-items: center; gap: 5px; }
|
|
2199
|
+
.kicker-status::before {
|
|
2200
|
+
font-size: 11px;
|
|
2201
|
+
line-height: 1;
|
|
2202
|
+
display: inline-block;
|
|
2167
2203
|
}
|
|
2204
|
+
.kicker-status.status-live { color: var(--lime); }
|
|
2205
|
+
.kicker-status.status-live::before { content: "●"; font-size: 7px; animation: pulse 1.5s infinite; }
|
|
2206
|
+
.kicker-status.status-paused { color: var(--amber); }
|
|
2207
|
+
.kicker-status.status-paused::before { content: "❚❚"; font-size: 9px; letter-spacing: -0.1em; }
|
|
2208
|
+
.kicker-status.status-adjourned { color: var(--text-dim); }
|
|
2209
|
+
.kicker-status.status-adjourned::before { content: "▣"; font-size: 10px; }
|
|
2168
2210
|
|
|
2169
2211
|
.room-subject {
|
|
2170
|
-
font-size:
|
|
2212
|
+
font-size: 14px;
|
|
2171
2213
|
font-weight: 600;
|
|
2172
2214
|
color: var(--text);
|
|
2173
2215
|
line-height: 1.3;
|
|
2174
|
-
margin-bottom:
|
|
2216
|
+
margin-bottom: 0;
|
|
2175
2217
|
font-family: var(--sans);
|
|
2176
|
-
letter-spacing: -0.
|
|
2218
|
+
letter-spacing: -0.005em;
|
|
2177
2219
|
overflow: hidden;
|
|
2178
2220
|
text-overflow: ellipsis;
|
|
2179
2221
|
white-space: nowrap;
|
|
@@ -2262,64 +2304,18 @@
|
|
|
2262
2304
|
to { opacity: 1; transform: translateY(0); }
|
|
2263
2305
|
}
|
|
2264
2306
|
|
|
2265
|
-
/*
|
|
2266
|
-
|
|
2267
|
-
|
|
2268
|
-
|
|
2269
|
-
|
|
2270
|
-
flex-wrap: wrap;
|
|
2271
|
-
gap: 6px 8px;
|
|
2272
|
-
font-family: var(--mono);
|
|
2273
|
-
font-size: 10px;
|
|
2274
|
-
color: var(--text-dim);
|
|
2275
|
-
line-height: 1.4;
|
|
2276
|
-
}
|
|
2277
|
-
.room-meta .meta-stamp {
|
|
2278
|
-
display: inline-flex;
|
|
2279
|
-
align-items: center;
|
|
2280
|
-
gap: 5px;
|
|
2281
|
-
padding: 3px 8px;
|
|
2282
|
-
border: 0.5px solid var(--lime-dim);
|
|
2283
|
-
background: var(--panel);
|
|
2284
|
-
color: var(--lime);
|
|
2285
|
-
font-weight: 700;
|
|
2286
|
-
font-size: 9.5px;
|
|
2287
|
-
letter-spacing: 0.06em;
|
|
2288
|
-
text-transform: lowercase;
|
|
2289
|
-
}
|
|
2290
|
-
.room-meta .meta-stamp::before {
|
|
2291
|
-
content: "●";
|
|
2292
|
-
font-size: 7px;
|
|
2293
|
-
line-height: 1;
|
|
2294
|
-
color: var(--lime);
|
|
2295
|
-
animation: pulse 1.5s infinite;
|
|
2296
|
-
}
|
|
2297
|
-
html[data-status="paused"] .room-meta .meta-stamp {
|
|
2298
|
-
color: var(--amber);
|
|
2299
|
-
border-color: var(--amber-dim);
|
|
2300
|
-
}
|
|
2301
|
-
html[data-status="paused"] .room-meta .meta-stamp::before {
|
|
2302
|
-
content: "❚❚";
|
|
2303
|
-
color: var(--amber);
|
|
2304
|
-
animation: none;
|
|
2305
|
-
}
|
|
2306
|
-
html[data-status="adjourned"] .room-meta .meta-stamp {
|
|
2307
|
-
color: var(--text-soft);
|
|
2308
|
-
border-color: var(--line-bright);
|
|
2309
|
-
}
|
|
2310
|
-
html[data-status="adjourned"] .room-meta .meta-stamp::before {
|
|
2311
|
-
content: "▣";
|
|
2312
|
-
color: var(--text-soft);
|
|
2313
|
-
animation: none;
|
|
2314
|
-
}
|
|
2307
|
+
/* `.room-meta` / `.meta-stamp` — REMOVED. The tone/intensity/
|
|
2308
|
+
report tags + status stamp that lived in this row are now
|
|
2309
|
+
part of `.room-kicker` (single mono line above the subject).
|
|
2310
|
+
Tone / intensity / brief style remain editable in the
|
|
2311
|
+
room-settings overlay. */
|
|
2315
2312
|
|
|
2316
2313
|
.head-actions { display: flex; align-items: center; gap: 6px; }
|
|
2317
2314
|
.head-cast { display: flex; align-items: center; }
|
|
2318
|
-
/* Avatar size
|
|
2319
|
-
|
|
2320
|
-
one toolbar at consistent height. */
|
|
2315
|
+
/* Avatar size aligned with the new compact header rhythm (22px) ·
|
|
2316
|
+
keeps the toolbar height in proportion to the 14px title. */
|
|
2321
2317
|
.head-cast img {
|
|
2322
|
-
width:
|
|
2318
|
+
width: 22px; height: 22px;
|
|
2323
2319
|
image-rendering: pixelated;
|
|
2324
2320
|
image-rendering: crisp-edges;
|
|
2325
2321
|
border: 0.5px solid var(--line-strong);
|
|
@@ -2334,9 +2330,9 @@
|
|
|
2334
2330
|
/* Stacked count tile · sits at the end of the avatar cascade and
|
|
2335
2331
|
matches the avatar height so the row stays flush. */
|
|
2336
2332
|
.head-cast .cast-count {
|
|
2337
|
-
height:
|
|
2338
|
-
min-width:
|
|
2339
|
-
padding: 0
|
|
2333
|
+
height: 22px;
|
|
2334
|
+
min-width: 22px;
|
|
2335
|
+
padding: 0 5px;
|
|
2340
2336
|
background: var(--panel-3);
|
|
2341
2337
|
color: var(--text);
|
|
2342
2338
|
border: 0.5px solid var(--lime-dim);
|
|
@@ -2345,7 +2341,7 @@
|
|
|
2345
2341
|
align-items: center;
|
|
2346
2342
|
justify-content: center;
|
|
2347
2343
|
font-family: var(--mono);
|
|
2348
|
-
font-size:
|
|
2344
|
+
font-size: 10px;
|
|
2349
2345
|
font-weight: 700;
|
|
2350
2346
|
letter-spacing: 0;
|
|
2351
2347
|
}
|
|
@@ -2760,7 +2756,7 @@
|
|
|
2760
2756
|
text-transform: uppercase;
|
|
2761
2757
|
color: var(--lime);
|
|
2762
2758
|
}
|
|
2763
|
-
/* Mode chip · "Report" / "
|
|
2759
|
+
/* Mode chip · "Report" / "Magazine" / "Newspaper".
|
|
2764
2760
|
Sits between the // report kicker and the FILED stamp, styled as
|
|
2765
2761
|
a hairline mono pill so the user sees the report type at a glance
|
|
2766
2762
|
without it competing with the kicker for primary emphasis.
|
|
@@ -3605,13 +3601,56 @@
|
|
|
3605
3601
|
margin-bottom: 18px;
|
|
3606
3602
|
font-family: var(--mono);
|
|
3607
3603
|
}
|
|
3604
|
+
/* Subject · clamp to 2 lines by default. The Show more / less
|
|
3605
|
+
toggle is rendered hidden and unhidden by the follow-up
|
|
3606
|
+
overlay's post-mount measurement only when the text actually
|
|
3607
|
+
overflows. Mirrors the adjourn-overlay / room-settings clamp
|
|
3608
|
+
pattern so all three modals read identically. */
|
|
3608
3609
|
.followup-parent-subject {
|
|
3609
3610
|
font-size: 13px;
|
|
3610
3611
|
font-weight: 600;
|
|
3611
3612
|
color: var(--text);
|
|
3612
3613
|
line-height: 1.4;
|
|
3613
3614
|
margin-bottom: 4px;
|
|
3615
|
+
word-break: break-word;
|
|
3616
|
+
}
|
|
3617
|
+
.followup-parent-subject.is-clamped {
|
|
3618
|
+
display: -webkit-box;
|
|
3619
|
+
-webkit-line-clamp: 2;
|
|
3620
|
+
-webkit-box-orient: vertical;
|
|
3621
|
+
overflow: hidden;
|
|
3622
|
+
}
|
|
3623
|
+
/* Meta row · single line that pairs the Show more toggle (left,
|
|
3624
|
+
when present) with the adjourned-time / brief-count meta
|
|
3625
|
+
(right). `margin-left: auto` on the meta keeps it pinned to
|
|
3626
|
+
the right edge regardless of whether the toggle is shown, so
|
|
3627
|
+
the meta doesn't shift between states. */
|
|
3628
|
+
.followup-parent-meta-row {
|
|
3629
|
+
display: flex;
|
|
3630
|
+
align-items: baseline;
|
|
3631
|
+
gap: 12px;
|
|
3632
|
+
margin-bottom: 4px;
|
|
3633
|
+
}
|
|
3634
|
+
.followup-parent-meta-row .followup-parent-meta {
|
|
3635
|
+
margin-left: auto;
|
|
3636
|
+
margin-bottom: 0;
|
|
3614
3637
|
}
|
|
3638
|
+
.followup-parent-subject-toggle {
|
|
3639
|
+
appearance: none;
|
|
3640
|
+
background: transparent;
|
|
3641
|
+
border: none;
|
|
3642
|
+
font-family: var(--mono);
|
|
3643
|
+
font-size: 9.5px;
|
|
3644
|
+
letter-spacing: 0.14em;
|
|
3645
|
+
text-transform: uppercase;
|
|
3646
|
+
color: var(--text-soft);
|
|
3647
|
+
cursor: pointer;
|
|
3648
|
+
padding: 0;
|
|
3649
|
+
transition: color 0.12s;
|
|
3650
|
+
}
|
|
3651
|
+
.followup-parent-subject-toggle:hover { color: var(--lime); }
|
|
3652
|
+
.followup-parent-subject-toggle::before { content: "[ "; color: var(--text-faint); }
|
|
3653
|
+
.followup-parent-subject-toggle::after { content: " ]"; color: var(--text-faint); }
|
|
3615
3654
|
.followup-parent-meta {
|
|
3616
3655
|
font-size: 10px;
|
|
3617
3656
|
color: var(--text-soft);
|
|
@@ -4175,12 +4214,29 @@
|
|
|
4175
4214
|
html[data-status="adjourned"] .paused-bar { display: none; }
|
|
4176
4215
|
|
|
4177
4216
|
/* Body · single chat column. Live-notes panel was removed, so there's
|
|
4178
|
-
no right-side resizer or aside to apportion space for.
|
|
4217
|
+
no right-side resizer or aside to apportion space for.
|
|
4218
|
+
`grid-template-rows: 1fr` is required so the implicit row stretches
|
|
4219
|
+
to body's height instead of collapsing to content height. Without
|
|
4220
|
+
it, .chat-col would be only as tall as its visible children, and
|
|
4221
|
+
.chat (`flex: 1 1 auto`) would have nothing to grow into — making
|
|
4222
|
+
[data-chat-messages] sit flush at the top of a content-sized
|
|
4223
|
+
chat. With `1fr`, .chat-col fills body, .chat fills chat-col, and
|
|
4224
|
+
vertical centring of the composer inside .chat actually has free
|
|
4225
|
+
space to work with. */
|
|
4179
4226
|
.body {
|
|
4180
4227
|
display: grid;
|
|
4181
4228
|
grid-template-columns: 1fr;
|
|
4229
|
+
grid-template-rows: 1fr;
|
|
4182
4230
|
overflow: hidden;
|
|
4183
4231
|
min-height: 0;
|
|
4232
|
+
height: 100%;
|
|
4233
|
+
/* Pin to .main-view's 1fr row · without this, when room-head is
|
|
4234
|
+
:empty + display:none (composer mode), .body becomes the first
|
|
4235
|
+
non-hidden grid child and auto-flows into row 1 (auto), where
|
|
4236
|
+
it collapses to content height. Vertical centring then has no
|
|
4237
|
+
free space. Explicit grid-row: 2 keeps .body in the 1fr row
|
|
4238
|
+
regardless of whether room-head is visible. */
|
|
4239
|
+
grid-row: 2;
|
|
4184
4240
|
}
|
|
4185
4241
|
|
|
4186
4242
|
/* Chat (zen plain text) */
|
|
@@ -7022,14 +7078,76 @@
|
|
|
7022
7078
|
edge so the page has one clear focal point. Refined: generous
|
|
7023
7079
|
vertical rhythm, single-accent colour discipline, mono micro-type
|
|
7024
7080
|
paired with serif-leaning sans display. ─── */
|
|
7081
|
+
/* Composer (new-room AND new-agent) shared rhythm.
|
|
7082
|
+
Vertical centring is split across two modes:
|
|
7083
|
+
· Default (`.chat.chat--composer`, content fits viewport) ·
|
|
7084
|
+
the chat is `display: grid; align-content: center` so the
|
|
7085
|
+
whole .cmp block sits in the visual centre.
|
|
7086
|
+
· Overflow (`.chat.chat--composer.chat--composer-overflow`,
|
|
7087
|
+
JS-toggled when `cmp.scrollHeight > chat.clientHeight`) ·
|
|
7088
|
+
the chat switches to block flow + scroll, .cmp-fold (hero
|
|
7089
|
+
+ input) is min-height ≈ 100vh with content centred inside,
|
|
7090
|
+
and .cmp-starters flow below as scrollable extras. Title +
|
|
7091
|
+
input stay visually centred on initial paint regardless of
|
|
7092
|
+
viewport height; starter cards live below the fold.
|
|
7093
|
+
Padding stays modest because vertical position no longer comes
|
|
7094
|
+
from top padding; it's just breathing room around the cmp box.
|
|
7095
|
+
Keep both composers in lock-step by editing here only. */
|
|
7025
7096
|
.cmp {
|
|
7026
7097
|
max-width: 760px;
|
|
7027
7098
|
margin: 0 auto;
|
|
7028
|
-
padding:
|
|
7099
|
+
padding: 32px 32px;
|
|
7100
|
+
width: 100%;
|
|
7101
|
+
}
|
|
7102
|
+
/* Default composer mode (content fits viewport).
|
|
7103
|
+
Toggled by JS via `.chat--composer` (added in
|
|
7104
|
+
renderEmptyState; removed in renderChat). The
|
|
7105
|
+
`.chat--composer-overflow` variant below handles the case
|
|
7106
|
+
where the composer is taller than .chat. */
|
|
7107
|
+
.chat.chat--composer {
|
|
7108
|
+
/* Grid centring · `align-content: center` is the most reliable
|
|
7109
|
+
cross-browser way to vertically centre a grid track. Beats
|
|
7110
|
+
flex auto-margins (which can resolve to 0 if any parent in
|
|
7111
|
+
the chain doesn't propagate height correctly) and beats
|
|
7112
|
+
`justify-content: safe center` (the `safe` keyword can
|
|
7113
|
+
silently invalidate the whole declaration in older browsers).
|
|
7114
|
+
When content overflows, the grid item still scrolls naturally
|
|
7115
|
+
inside .chat's `overflow-y: auto`. */
|
|
7116
|
+
display: grid !important;
|
|
7117
|
+
align-content: center !important;
|
|
7118
|
+
justify-items: stretch !important;
|
|
7119
|
+
}
|
|
7120
|
+
.chat.chat--composer > [data-chat-messages] {
|
|
7121
|
+
width: 100% !important;
|
|
7122
|
+
}
|
|
7123
|
+
.chat.chat--composer > [data-brief-card] {
|
|
7124
|
+
/* In composer mode the brief-card is empty · hide it so it
|
|
7125
|
+
doesn't claim a grid row that throws off the centring. */
|
|
7126
|
+
display: none !important;
|
|
7127
|
+
}
|
|
7128
|
+
/* Overflow mode · when .cmp's natural height exceeds .chat's height,
|
|
7129
|
+
centring the entire composer block would clip the title above the
|
|
7130
|
+
scroll area. Instead, push the .cmp's top down with a viewport-
|
|
7131
|
+
relative padding-top so hero + input sit at the visual centre of
|
|
7132
|
+
the viewport on initial paint; .cmp-starters fall below the input
|
|
7133
|
+
in normal document flow with the SAME spacing as the fits case
|
|
7134
|
+
(no `justify-content: center` void zone). Approximation: hero +
|
|
7135
|
+
input combined ≈ 220px tall, so half ≈ 110px; padding-top of
|
|
7136
|
+
`50vh - 110px` puts the hero+input vertical midpoint at 50vh.
|
|
7137
|
+
`max(32px, …)` keeps the original padding floor on tiny viewports
|
|
7138
|
+
where the calc would go negative.
|
|
7139
|
+
Toggled by JS (updateComposerOverflow) on render + resize. */
|
|
7140
|
+
.chat.chat--composer.chat--composer-overflow {
|
|
7141
|
+
display: block !important;
|
|
7142
|
+
align-content: initial !important;
|
|
7143
|
+
overflow-y: auto;
|
|
7144
|
+
}
|
|
7145
|
+
.chat.chat--composer.chat--composer-overflow .cmp {
|
|
7146
|
+
padding-top: max(32px, calc(50vh - 110px));
|
|
7029
7147
|
}
|
|
7030
7148
|
.cmp-hero {
|
|
7031
7149
|
text-align: center;
|
|
7032
|
-
margin-bottom:
|
|
7150
|
+
margin-bottom: 22px;
|
|
7033
7151
|
}
|
|
7034
7152
|
.cmp-greet {
|
|
7035
7153
|
font-family: var(--mono);
|
|
@@ -7038,11 +7156,11 @@
|
|
|
7038
7156
|
text-transform: uppercase;
|
|
7039
7157
|
color: var(--text-faint);
|
|
7040
7158
|
font-weight: 700;
|
|
7041
|
-
margin-bottom:
|
|
7159
|
+
margin-bottom: 14px;
|
|
7042
7160
|
}
|
|
7043
7161
|
.cmp-prompt {
|
|
7044
7162
|
font-family: var(--font-human);
|
|
7045
|
-
font-size:
|
|
7163
|
+
font-size: 28px;
|
|
7046
7164
|
font-weight: 600;
|
|
7047
7165
|
color: var(--text);
|
|
7048
7166
|
line-height: 1.2;
|
|
@@ -7087,7 +7205,13 @@
|
|
|
7087
7205
|
}
|
|
7088
7206
|
|
|
7089
7207
|
/* Toolbar inside input frame · cast on left, tune in the middle,
|
|
7090
|
-
convene on the right. Hairline divider above.
|
|
7208
|
+
convene on the right. Hairline divider above. min-height pinned
|
|
7209
|
+
to the room composer's natural height (driven by the 22px cast
|
|
7210
|
+
avatars + button padding) so the agent composer's toolbar — which
|
|
7211
|
+
has no avatar-bearing button — grows to match. Without this the
|
|
7212
|
+
agent input frame is a few pixels shorter than the room frame.
|
|
7213
|
+
Box-sizing is border-box (universal in this project), so the
|
|
7214
|
+
min-height includes the 8px+8px vertical padding. */
|
|
7091
7215
|
.cmp-toolbar {
|
|
7092
7216
|
display: flex;
|
|
7093
7217
|
align-items: center;
|
|
@@ -7095,6 +7219,7 @@
|
|
|
7095
7219
|
padding: 8px 8px 8px 14px;
|
|
7096
7220
|
border-top: 0.5px solid var(--line);
|
|
7097
7221
|
background: var(--panel-2);
|
|
7222
|
+
min-height: 48px;
|
|
7098
7223
|
}
|
|
7099
7224
|
|
|
7100
7225
|
.cmp-cast-btn {
|
|
@@ -7430,7 +7555,7 @@
|
|
|
7430
7555
|
accent borders. Just hairline-separated rows that brighten on
|
|
7431
7556
|
hover, with the arrow turning lime — the only visual reward. */
|
|
7432
7557
|
.cmp-starters {
|
|
7433
|
-
margin-top:
|
|
7558
|
+
margin-top: 28px;
|
|
7434
7559
|
}
|
|
7435
7560
|
.cmp-starters-rule {
|
|
7436
7561
|
display: flex;
|
|
@@ -7461,7 +7586,7 @@
|
|
|
7461
7586
|
gap: 16px;
|
|
7462
7587
|
align-items: center;
|
|
7463
7588
|
width: 100%;
|
|
7464
|
-
padding:
|
|
7589
|
+
padding: 10px 4px;
|
|
7465
7590
|
background: transparent;
|
|
7466
7591
|
border: none;
|
|
7467
7592
|
border-bottom: 0.5px solid var(--line);
|
|
@@ -7507,7 +7632,10 @@
|
|
|
7507
7632
|
/* ─── New-agent composer · variant of the new-room composer.
|
|
7508
7633
|
Reuses .cmp / .cmp-* classes for the hero + input frame; adds
|
|
7509
7634
|
ag-cmp-* for agent-specific bits and ag-prev-* for the editable
|
|
7510
|
-
spec preview card that appears after AI generation.
|
|
7635
|
+
spec preview card that appears after AI generation. The vertical
|
|
7636
|
+
rhythm (padding, hero/starter margins, starter card density) is
|
|
7637
|
+
SHARED with the new-room composer via the base .cmp rules — so
|
|
7638
|
+
edits land in one place and both composers stay in sync. ─── */
|
|
7511
7639
|
.ag-cmp .cmp-toolbar {
|
|
7512
7640
|
/* Model + manual buttons sit at the left, Generate hugs the right
|
|
7513
7641
|
via margin-left: auto on .cmp-go. No justify-content override
|
|
@@ -8638,12 +8766,12 @@
|
|
|
8638
8766
|
|
|
8639
8767
|
/* Long-opener clamp · when the user wrote more than a sentence of
|
|
8640
8768
|
context, the card would otherwise dominate the viewport. We
|
|
8641
|
-
clamp the body to
|
|
8769
|
+
clamp the body to 2 lines with a fade-out overlay; a
|
|
8642
8770
|
`<button data-convene-toggle>` below toggles `.expanded` on the
|
|
8643
|
-
opener parent to reveal the rest. 2
|
|
8644
|
-
|
|
8771
|
+
opener parent to reveal the rest. 2 × line-height (1.32 × 19px
|
|
8772
|
+
≈ 25px) = 50px. */
|
|
8645
8773
|
.convene-opener-clamped:not(.expanded) .convene-body {
|
|
8646
|
-
max-height:
|
|
8774
|
+
max-height: 50px;
|
|
8647
8775
|
overflow: hidden;
|
|
8648
8776
|
position: relative;
|
|
8649
8777
|
}
|
|
@@ -8653,7 +8781,7 @@
|
|
|
8653
8781
|
left: 0;
|
|
8654
8782
|
right: 0;
|
|
8655
8783
|
bottom: 0;
|
|
8656
|
-
height:
|
|
8784
|
+
height: 24px;
|
|
8657
8785
|
background: linear-gradient(to bottom, transparent, var(--panel-2));
|
|
8658
8786
|
pointer-events: none;
|
|
8659
8787
|
}
|
|
@@ -8956,6 +9084,7 @@
|
|
|
8956
9084
|
min-height: 0;
|
|
8957
9085
|
overflow: hidden;
|
|
8958
9086
|
background: var(--panel);
|
|
9087
|
+
height: 100%;
|
|
8959
9088
|
}
|
|
8960
9089
|
|
|
8961
9090
|
/* Input bar — sits inside the chat column. Live state hosts a
|
|
@@ -9577,13 +9706,27 @@
|
|
|
9577
9706
|
io.observe(hint);
|
|
9578
9707
|
})();
|
|
9579
9708
|
|
|
9580
|
-
/* ─── Pin toggle
|
|
9709
|
+
/* ─── Pin toggle ───
|
|
9710
|
+
Two paths: agent rows persist via PATCH /api/agents/:id { isPinned }
|
|
9711
|
+
through `app.togglePinAgent`, which mutates local state and
|
|
9712
|
+
re-renders the sidebar so the row moves into the Pinned bucket.
|
|
9713
|
+
Session (room) rows fall through to a visual-only toggle for now —
|
|
9714
|
+
room pinning isn't a backend feature yet, so the class flip is a
|
|
9715
|
+
placeholder that will go away once it lands. */
|
|
9581
9716
|
(function () {
|
|
9582
9717
|
document.addEventListener("click", (e) => {
|
|
9583
9718
|
const btn = e.target.closest("[data-pin-toggle]");
|
|
9584
9719
|
if (!btn) return;
|
|
9585
9720
|
e.preventDefault();
|
|
9586
9721
|
e.stopPropagation();
|
|
9722
|
+
// Agent row · persist the pin via the API and let the sidebar
|
|
9723
|
+
// re-render rebuild the Pinned / Custom / Core buckets.
|
|
9724
|
+
const agentShell = btn.closest(".agent-row-shell[data-agent-id]");
|
|
9725
|
+
if (agentShell && window.app && typeof window.app.togglePinAgent === "function") {
|
|
9726
|
+
window.app.togglePinAgent(agentShell.dataset.agentId);
|
|
9727
|
+
return;
|
|
9728
|
+
}
|
|
9729
|
+
// Fallback · session-row (room pinning is visual-only for now).
|
|
9587
9730
|
const row = btn.closest(".session-row, .agent-row");
|
|
9588
9731
|
if (!row) return;
|
|
9589
9732
|
row.classList.toggle("pinned");
|
|
@@ -9732,35 +9875,36 @@
|
|
|
9732
9875
|
});
|
|
9733
9876
|
|
|
9734
9877
|
if (which === "rooms") {
|
|
9735
|
-
// Resolve which sub-state to apply.
|
|
9736
|
-
//
|
|
9737
|
-
//
|
|
9738
|
-
//
|
|
9739
|
-
//
|
|
9740
|
-
//
|
|
9741
|
-
//
|
|
9742
|
-
//
|
|
9878
|
+
// Resolve which sub-state to apply. ROOMS_KEY is the user's
|
|
9879
|
+
// explicit choice — preserve it on every nav (tab click,
|
|
9880
|
+
// refresh, deep-link). All four sub-states are first-class:
|
|
9881
|
+
// an explicit room id, "new" (composer), "reports", "notes".
|
|
9882
|
+
//
|
|
9883
|
+
// Earlier the fromTabClick path overrode saved="new" with
|
|
9884
|
+
// ROOMS_LAST_KEY, on the theory that "re-entering the Rooms
|
|
9885
|
+
// tab should feel continuous." In practice that broke the
|
|
9886
|
+
// user's expectation: pick "+ New Room", switch to Agents
|
|
9887
|
+
// tab, switch back, lands on a different room. The user's
|
|
9888
|
+
// mental model is "the sidebar remembers what I last clicked"
|
|
9889
|
+
// — so "new" is now sticky just like "reports" / "notes".
|
|
9890
|
+
// Fallback to the last-opened room (or first row) only fires
|
|
9891
|
+
// when there's no saved sub-state at all.
|
|
9743
9892
|
const saved = lsGet(ROOMS_KEY);
|
|
9744
9893
|
let target;
|
|
9745
9894
|
if (saved && saved !== "new" && saved !== "reports" && saved !== "notes") {
|
|
9746
9895
|
target = saved; // explicit room id
|
|
9747
|
-
} else if (saved === "reports" || saved === "notes") {
|
|
9748
|
-
target = saved; //
|
|
9896
|
+
} else if (saved === "reports" || saved === "notes" || saved === "new") {
|
|
9897
|
+
target = saved; // explicit destination preserved on any nav
|
|
9749
9898
|
} else if (fromTabClick) {
|
|
9750
|
-
//
|
|
9751
|
-
//
|
|
9752
|
-
//
|
|
9753
|
-
// resetting to the composer. The "new" intent only persists
|
|
9754
|
-
// within the rooms tab — once the user leaves and returns,
|
|
9755
|
-
// we surface their last room.
|
|
9899
|
+
// No saved sub-state · fresh user clicking the tab. Prefer
|
|
9900
|
+
// the last actually-opened room so the tab feels populated
|
|
9901
|
+
// instead of bouncing them to a blank composer.
|
|
9756
9902
|
const last = lsGet(ROOMS_LAST_KEY);
|
|
9757
9903
|
if (last && document.querySelector(`.session-row-shell[data-room-id="${last}"]`)) {
|
|
9758
9904
|
target = last;
|
|
9759
9905
|
} else {
|
|
9760
9906
|
target = firstRoomId() || "new";
|
|
9761
9907
|
}
|
|
9762
|
-
} else if (saved === "new") {
|
|
9763
|
-
target = "new"; // initial load · preserve composer intent
|
|
9764
9908
|
} else {
|
|
9765
9909
|
target = "new"; // initial load with no history → composer
|
|
9766
9910
|
}
|
|
@@ -9907,9 +10051,13 @@
|
|
|
9907
10051
|
const tab = (savedTab && VALID_TABS.has(savedTab)) ? savedTab : "rooms";
|
|
9908
10052
|
// Honor URL hash → if user came in with #/r/<id>, that's the
|
|
9909
10053
|
// intended room; promote it to the rooms sub-state and switch
|
|
9910
|
-
// to rooms tab
|
|
10054
|
+
// to rooms tab. BUT only when the saved tab isn't already
|
|
10055
|
+
// "agents" — opening an agent profile doesn't update the URL
|
|
10056
|
+
// hash, so a refresh on the agent page would otherwise get
|
|
10057
|
+
// bounced to rooms by a leftover hash from the prior room
|
|
10058
|
+
// view. Sticky agent tab beats stale-hash deep linking.
|
|
9911
10059
|
const m = (location.hash || "").match(/^#\/r\/([a-z0-9]+)/i);
|
|
9912
|
-
if (m && m[1]) {
|
|
10060
|
+
if (m && m[1] && tab !== "agents") {
|
|
9913
10061
|
lsSet(ROOMS_KEY, m[1]);
|
|
9914
10062
|
lsSet(ROOMS_LAST_KEY, m[1]);
|
|
9915
10063
|
activate("rooms", { persist: false });
|