privateboard 0.1.7 → 0.1.9
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli.js +1423 -46
- package/dist/cli.js.map +1 -1
- package/package.json +1 -1
- package/public/adjourn-overlay.css +195 -0
- package/public/agent-profile.css +525 -0
- package/public/agent-profile.js +278 -1
- package/public/app.js +634 -118
- package/public/home.html +389 -17
- package/public/index.html +325 -130
- package/public/magazine.html +1685 -0
- package/public/newspaper.html +1892 -0
- package/public/ppt.html +2623 -0
- package/public/report.html +366 -52
- package/public/room-settings.css +40 -4
- package/public/room-settings.js +44 -2
- package/public/user-settings.css +117 -68
- package/public/user-settings.js +77 -46
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
|
|
|
@@ -647,24 +656,50 @@
|
|
|
647
656
|
align-items: center;
|
|
648
657
|
justify-content: center;
|
|
649
658
|
}
|
|
650
|
-
/* CHAIR badge ·
|
|
651
|
-
|
|
652
|
-
|
|
659
|
+
/* CHAIR badge · pure typographic kicker, no pill geometry.
|
|
660
|
+
Earlier this was an outlined hairline pill (cyan border + cyan
|
|
661
|
+
text + transparent fill) — the only colored hairline box
|
|
662
|
+
anywhere on the page, so it read as a sticker rather than part
|
|
663
|
+
of the design system. The refined treatment drops border /
|
|
664
|
+
background / padding / radius and keeps the chair's identity
|
|
665
|
+
signal in pure typography:
|
|
666
|
+
· mono uppercase, generous letter-spacing (matches section
|
|
667
|
+
headers, brief banner kickers, time tags)
|
|
668
|
+
· cyan color (the chair's accent stays)
|
|
669
|
+
· em-dash sigil prefix separates it from the title — soft
|
|
670
|
+
enough to feel like part of the same line, distinct enough
|
|
671
|
+
that the eye lands on it as a label, not a name fragment
|
|
672
|
+
· weight 600 (one notch below 700) so it doesn't compete
|
|
673
|
+
with the title's emphasis
|
|
674
|
+
The avatar already carries the `⊘` immutability cue and the
|
|
675
|
+
section header already says "Chair", so this badge is now the
|
|
676
|
+
quietest possible identity stamp instead of a third tier of
|
|
677
|
+
decoration. */
|
|
653
678
|
.agent-row .agent-row-chair-badge {
|
|
654
679
|
font-family: var(--mono);
|
|
655
|
-
font-size:
|
|
656
|
-
font-weight:
|
|
657
|
-
letter-spacing: 0.
|
|
680
|
+
font-size: 9px;
|
|
681
|
+
font-weight: 600;
|
|
682
|
+
letter-spacing: 0.2em;
|
|
658
683
|
text-transform: uppercase;
|
|
659
|
-
|
|
660
|
-
|
|
684
|
+
/* Track the active theme's primary accent — `--lime` remaps per
|
|
685
|
+
theme (gold in regent, blue in apple, red in pinterest,
|
|
686
|
+
magenta in amuse, etc.). The earlier `--cyan` was static and
|
|
687
|
+
clashed in themes whose palette didn't include teal. */
|
|
688
|
+
color: var(--lime);
|
|
661
689
|
background: transparent;
|
|
662
|
-
border:
|
|
663
|
-
|
|
690
|
+
border: none;
|
|
691
|
+
padding: 0;
|
|
664
692
|
white-space: nowrap;
|
|
665
693
|
align-self: center;
|
|
666
694
|
margin-left: auto;
|
|
667
695
|
}
|
|
696
|
+
.agent-row .agent-row-chair-badge::before {
|
|
697
|
+
content: "—";
|
|
698
|
+
color: var(--text-faint);
|
|
699
|
+
margin-right: 8px;
|
|
700
|
+
font-weight: 400;
|
|
701
|
+
letter-spacing: 0;
|
|
702
|
+
}
|
|
668
703
|
.agent-row .agent-row-chair-role { color: var(--text-soft); font-weight: 600; }
|
|
669
704
|
.agent-row .agent-row-chair-note { color: var(--text-faint); }
|
|
670
705
|
/* Hide the Chair section's count badge — there's only ever one. */
|
|
@@ -943,6 +978,13 @@
|
|
|
943
978
|
opacity: 1;
|
|
944
979
|
color: var(--lime);
|
|
945
980
|
}
|
|
981
|
+
/* Pinned · the lime pin glyph stays visible permanently, so the
|
|
982
|
+
in-flow time tag has to hide too — otherwise the two stack on
|
|
983
|
+
the right edge (pin + "7h" overlapping in the same 18px slot).
|
|
984
|
+
The hover rule above only handles hover; pinned rows live in
|
|
985
|
+
"always visible" state and need their own hide. */
|
|
986
|
+
.agent-row.pinned .agent-row-time,
|
|
987
|
+
.session-row.pinned .row-time { visibility: hidden; }
|
|
946
988
|
|
|
947
989
|
/* The shell wraps the link + delete button as siblings so the click on
|
|
948
990
|
the X button doesn't get absorbed by the anchor's navigation.
|
|
@@ -1262,6 +1304,7 @@
|
|
|
1262
1304
|
grid-template-rows: auto 1fr auto;
|
|
1263
1305
|
overflow: hidden;
|
|
1264
1306
|
min-height: 0;
|
|
1307
|
+
height: 100%;
|
|
1265
1308
|
}
|
|
1266
1309
|
.main-view[data-main-view="agent"] {
|
|
1267
1310
|
/* Switch from grid to block so the profile card flows naturally
|
|
@@ -1375,7 +1418,7 @@
|
|
|
1375
1418
|
.notes-filters {
|
|
1376
1419
|
display: flex;
|
|
1377
1420
|
gap: 2px;
|
|
1378
|
-
margin: 22px 0
|
|
1421
|
+
margin: 22px 0 2px;
|
|
1379
1422
|
flex-wrap: wrap;
|
|
1380
1423
|
}
|
|
1381
1424
|
.reports-filter-chip,
|
|
@@ -1422,8 +1465,8 @@
|
|
|
1422
1465
|
in favour of a single rule + flat list (chips already disclose
|
|
1423
1466
|
recency, no need for repeated section headers). */
|
|
1424
1467
|
.reports-list-wrap {
|
|
1425
|
-
margin-top:
|
|
1426
|
-
padding-top:
|
|
1468
|
+
margin-top: 8px;
|
|
1469
|
+
padding-top: 8px;
|
|
1427
1470
|
border-top: 1px solid var(--border);
|
|
1428
1471
|
}
|
|
1429
1472
|
|
|
@@ -1514,6 +1557,18 @@
|
|
|
1514
1557
|
color: var(--text-faint);
|
|
1515
1558
|
letter-spacing: 0.06em;
|
|
1516
1559
|
}
|
|
1560
|
+
/* Report-mode type chip in the kicker line · "REPORT" / "BENTO" /
|
|
1561
|
+
"MAGAZINE" / "NEWSPAPER". Mono register matching the kicker.
|
|
1562
|
+
Slight color shift per mode so the type is glanceable without
|
|
1563
|
+
being a heavy badge. */
|
|
1564
|
+
.reports-item-type {
|
|
1565
|
+
color: var(--text-soft);
|
|
1566
|
+
letter-spacing: 0.16em;
|
|
1567
|
+
font-weight: 700;
|
|
1568
|
+
}
|
|
1569
|
+
.reports-item-type[data-mode="magazine"] { color: var(--amber, #C8A36F); }
|
|
1570
|
+
.reports-item-type[data-mode="newspaper"] { color: var(--text); }
|
|
1571
|
+
.reports-item-type[data-mode="ppt"] { color: var(--cyan, #6FC3C8); }
|
|
1517
1572
|
.reports-item-sep {
|
|
1518
1573
|
color: var(--text-faint);
|
|
1519
1574
|
opacity: 0.7;
|
|
@@ -2090,8 +2145,8 @@
|
|
|
2090
2145
|
the chip strip → divider → list rhythm reads identically. Group
|
|
2091
2146
|
headers were dropped in favour of a single hair under the chips. */
|
|
2092
2147
|
.notes-list-wrap {
|
|
2093
|
-
margin-top:
|
|
2094
|
-
padding-top:
|
|
2148
|
+
margin-top: 8px;
|
|
2149
|
+
padding-top: 8px;
|
|
2095
2150
|
border-top: 1px solid var(--border);
|
|
2096
2151
|
}
|
|
2097
2152
|
|
|
@@ -2117,15 +2172,17 @@
|
|
|
2117
2172
|
.notes-item-subject { max-width: 160px; }
|
|
2118
2173
|
}
|
|
2119
2174
|
|
|
2120
|
-
/* Room head —
|
|
2121
|
-
|
|
2175
|
+
/* Room head — compact register · the title is the only
|
|
2176
|
+
vertical anchor users actually read here, so we keep the
|
|
2177
|
+
padding moderately tight while still leaving the kicker +
|
|
2178
|
+
title pair some breathing room. */
|
|
2122
2179
|
.room-head {
|
|
2123
2180
|
background: var(--panel-2);
|
|
2124
2181
|
border-bottom: 0.5px solid var(--line-bright);
|
|
2125
|
-
padding: 16px
|
|
2182
|
+
padding: 11px 16px;
|
|
2126
2183
|
display: grid;
|
|
2127
2184
|
grid-template-columns: 1fr auto;
|
|
2128
|
-
gap:
|
|
2185
|
+
gap: 12px;
|
|
2129
2186
|
align-items: center;
|
|
2130
2187
|
}
|
|
2131
2188
|
/* When the sidebar is collapsed the room-head gains a leading
|
|
@@ -2144,36 +2201,51 @@
|
|
|
2144
2201
|
at this level. */
|
|
2145
2202
|
.room-info { min-width: 0; overflow: visible; }
|
|
2146
2203
|
|
|
2147
|
-
|
|
2204
|
+
/* ─── Compact kicker · single mono line at top of room-head ───
|
|
2205
|
+
Replaces the prior `.room-id` row (`MEETING ROOM` badge +
|
|
2206
|
+
`// ROOM #N · TONE`) AND the `.room-meta` row (tone/intensity/
|
|
2207
|
+
report tagged pills + status stamp). Net: three stacked rows
|
|
2208
|
+
collapse into one. Tone / intensity / brief-style remain
|
|
2209
|
+
editable in the room-settings overlay. */
|
|
2210
|
+
.room-kicker {
|
|
2148
2211
|
display: flex;
|
|
2149
2212
|
align-items: center;
|
|
2150
|
-
gap:
|
|
2151
|
-
|
|
2152
|
-
}
|
|
2153
|
-
.room-name {
|
|
2213
|
+
gap: 6px;
|
|
2214
|
+
font-family: var(--mono);
|
|
2154
2215
|
font-size: 9.5px;
|
|
2155
|
-
|
|
2156
|
-
background: var(--lime);
|
|
2157
|
-
padding: 1px 7px;
|
|
2216
|
+
letter-spacing: 0.12em;
|
|
2158
2217
|
text-transform: uppercase;
|
|
2159
|
-
|
|
2218
|
+
color: var(--text-dim);
|
|
2219
|
+
margin-bottom: 5px;
|
|
2220
|
+
flex-wrap: wrap;
|
|
2160
2221
|
font-weight: 700;
|
|
2222
|
+
line-height: 1.3;
|
|
2161
2223
|
}
|
|
2162
|
-
.
|
|
2163
|
-
|
|
2164
|
-
|
|
2165
|
-
|
|
2166
|
-
|
|
2224
|
+
.kicker-num { color: var(--text-soft); }
|
|
2225
|
+
.kicker-tone { color: var(--lime); cursor: help; }
|
|
2226
|
+
.kicker-intensity { color: var(--amber); }
|
|
2227
|
+
.kicker-sep { color: var(--text-faint); opacity: 0.6; }
|
|
2228
|
+
.kicker-status { display: inline-flex; align-items: center; gap: 5px; }
|
|
2229
|
+
.kicker-status::before {
|
|
2230
|
+
font-size: 11px;
|
|
2231
|
+
line-height: 1;
|
|
2232
|
+
display: inline-block;
|
|
2167
2233
|
}
|
|
2234
|
+
.kicker-status.status-live { color: var(--lime); }
|
|
2235
|
+
.kicker-status.status-live::before { content: "●"; font-size: 7px; animation: pulse 1.5s infinite; }
|
|
2236
|
+
.kicker-status.status-paused { color: var(--amber); }
|
|
2237
|
+
.kicker-status.status-paused::before { content: "❚❚"; font-size: 9px; letter-spacing: -0.1em; }
|
|
2238
|
+
.kicker-status.status-adjourned { color: var(--text-dim); }
|
|
2239
|
+
.kicker-status.status-adjourned::before { content: "▣"; font-size: 10px; }
|
|
2168
2240
|
|
|
2169
2241
|
.room-subject {
|
|
2170
|
-
font-size:
|
|
2242
|
+
font-size: 14px;
|
|
2171
2243
|
font-weight: 600;
|
|
2172
2244
|
color: var(--text);
|
|
2173
2245
|
line-height: 1.3;
|
|
2174
|
-
margin-bottom:
|
|
2246
|
+
margin-bottom: 0;
|
|
2175
2247
|
font-family: var(--sans);
|
|
2176
|
-
letter-spacing: -0.
|
|
2248
|
+
letter-spacing: -0.005em;
|
|
2177
2249
|
overflow: hidden;
|
|
2178
2250
|
text-overflow: ellipsis;
|
|
2179
2251
|
white-space: nowrap;
|
|
@@ -2262,64 +2334,18 @@
|
|
|
2262
2334
|
to { opacity: 1; transform: translateY(0); }
|
|
2263
2335
|
}
|
|
2264
2336
|
|
|
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
|
-
}
|
|
2337
|
+
/* `.room-meta` / `.meta-stamp` — REMOVED. The tone/intensity/
|
|
2338
|
+
report tags + status stamp that lived in this row are now
|
|
2339
|
+
part of `.room-kicker` (single mono line above the subject).
|
|
2340
|
+
Tone / intensity / brief style remain editable in the
|
|
2341
|
+
room-settings overlay. */
|
|
2315
2342
|
|
|
2316
2343
|
.head-actions { display: flex; align-items: center; gap: 6px; }
|
|
2317
2344
|
.head-cast { display: flex; align-items: center; }
|
|
2318
|
-
/* Avatar size
|
|
2319
|
-
|
|
2320
|
-
one toolbar at consistent height. */
|
|
2345
|
+
/* Avatar size aligned with the new compact header rhythm (22px) ·
|
|
2346
|
+
keeps the toolbar height in proportion to the 14px title. */
|
|
2321
2347
|
.head-cast img {
|
|
2322
|
-
width:
|
|
2348
|
+
width: 22px; height: 22px;
|
|
2323
2349
|
image-rendering: pixelated;
|
|
2324
2350
|
image-rendering: crisp-edges;
|
|
2325
2351
|
border: 0.5px solid var(--line-strong);
|
|
@@ -2334,9 +2360,9 @@
|
|
|
2334
2360
|
/* Stacked count tile · sits at the end of the avatar cascade and
|
|
2335
2361
|
matches the avatar height so the row stays flush. */
|
|
2336
2362
|
.head-cast .cast-count {
|
|
2337
|
-
height:
|
|
2338
|
-
min-width:
|
|
2339
|
-
padding: 0
|
|
2363
|
+
height: 22px;
|
|
2364
|
+
min-width: 22px;
|
|
2365
|
+
padding: 0 5px;
|
|
2340
2366
|
background: var(--panel-3);
|
|
2341
2367
|
color: var(--text);
|
|
2342
2368
|
border: 0.5px solid var(--lime-dim);
|
|
@@ -2345,7 +2371,7 @@
|
|
|
2345
2371
|
align-items: center;
|
|
2346
2372
|
justify-content: center;
|
|
2347
2373
|
font-family: var(--mono);
|
|
2348
|
-
font-size:
|
|
2374
|
+
font-size: 10px;
|
|
2349
2375
|
font-weight: 700;
|
|
2350
2376
|
letter-spacing: 0;
|
|
2351
2377
|
}
|
|
@@ -2760,6 +2786,23 @@
|
|
|
2760
2786
|
text-transform: uppercase;
|
|
2761
2787
|
color: var(--lime);
|
|
2762
2788
|
}
|
|
2789
|
+
/* Mode chip · "Report" / "Magazine" / "Newspaper".
|
|
2790
|
+
Sits between the // report kicker and the FILED stamp, styled as
|
|
2791
|
+
a hairline mono pill so the user sees the report type at a glance
|
|
2792
|
+
without it competing with the kicker for primary emphasis.
|
|
2793
|
+
`margin-right: auto` pushes the stamp to the right edge while the
|
|
2794
|
+
chip stays anchored next to the kicker. */
|
|
2795
|
+
.brief-banner-type {
|
|
2796
|
+
font-size: 9px;
|
|
2797
|
+
font-weight: 600;
|
|
2798
|
+
letter-spacing: 0.12em;
|
|
2799
|
+
text-transform: uppercase;
|
|
2800
|
+
color: var(--text-soft);
|
|
2801
|
+
border: 0.5px solid var(--line-bright, var(--line));
|
|
2802
|
+
padding: 2px 8px;
|
|
2803
|
+
margin-right: auto;
|
|
2804
|
+
white-space: nowrap;
|
|
2805
|
+
}
|
|
2763
2806
|
.brief-banner-stamp {
|
|
2764
2807
|
font-size: 9px;
|
|
2765
2808
|
letter-spacing: 0.12em;
|
|
@@ -3588,13 +3631,56 @@
|
|
|
3588
3631
|
margin-bottom: 18px;
|
|
3589
3632
|
font-family: var(--mono);
|
|
3590
3633
|
}
|
|
3634
|
+
/* Subject · clamp to 2 lines by default. The Show more / less
|
|
3635
|
+
toggle is rendered hidden and unhidden by the follow-up
|
|
3636
|
+
overlay's post-mount measurement only when the text actually
|
|
3637
|
+
overflows. Mirrors the adjourn-overlay / room-settings clamp
|
|
3638
|
+
pattern so all three modals read identically. */
|
|
3591
3639
|
.followup-parent-subject {
|
|
3592
3640
|
font-size: 13px;
|
|
3593
3641
|
font-weight: 600;
|
|
3594
3642
|
color: var(--text);
|
|
3595
3643
|
line-height: 1.4;
|
|
3596
3644
|
margin-bottom: 4px;
|
|
3645
|
+
word-break: break-word;
|
|
3646
|
+
}
|
|
3647
|
+
.followup-parent-subject.is-clamped {
|
|
3648
|
+
display: -webkit-box;
|
|
3649
|
+
-webkit-line-clamp: 2;
|
|
3650
|
+
-webkit-box-orient: vertical;
|
|
3651
|
+
overflow: hidden;
|
|
3652
|
+
}
|
|
3653
|
+
/* Meta row · single line that pairs the Show more toggle (left,
|
|
3654
|
+
when present) with the adjourned-time / brief-count meta
|
|
3655
|
+
(right). `margin-left: auto` on the meta keeps it pinned to
|
|
3656
|
+
the right edge regardless of whether the toggle is shown, so
|
|
3657
|
+
the meta doesn't shift between states. */
|
|
3658
|
+
.followup-parent-meta-row {
|
|
3659
|
+
display: flex;
|
|
3660
|
+
align-items: baseline;
|
|
3661
|
+
gap: 12px;
|
|
3662
|
+
margin-bottom: 4px;
|
|
3663
|
+
}
|
|
3664
|
+
.followup-parent-meta-row .followup-parent-meta {
|
|
3665
|
+
margin-left: auto;
|
|
3666
|
+
margin-bottom: 0;
|
|
3597
3667
|
}
|
|
3668
|
+
.followup-parent-subject-toggle {
|
|
3669
|
+
appearance: none;
|
|
3670
|
+
background: transparent;
|
|
3671
|
+
border: none;
|
|
3672
|
+
font-family: var(--mono);
|
|
3673
|
+
font-size: 9.5px;
|
|
3674
|
+
letter-spacing: 0.14em;
|
|
3675
|
+
text-transform: uppercase;
|
|
3676
|
+
color: var(--text-soft);
|
|
3677
|
+
cursor: pointer;
|
|
3678
|
+
padding: 0;
|
|
3679
|
+
transition: color 0.12s;
|
|
3680
|
+
}
|
|
3681
|
+
.followup-parent-subject-toggle:hover { color: var(--lime); }
|
|
3682
|
+
.followup-parent-subject-toggle::before { content: "[ "; color: var(--text-faint); }
|
|
3683
|
+
.followup-parent-subject-toggle::after { content: " ]"; color: var(--text-faint); }
|
|
3598
3684
|
.followup-parent-meta {
|
|
3599
3685
|
font-size: 10px;
|
|
3600
3686
|
color: var(--text-soft);
|
|
@@ -4158,12 +4244,29 @@
|
|
|
4158
4244
|
html[data-status="adjourned"] .paused-bar { display: none; }
|
|
4159
4245
|
|
|
4160
4246
|
/* Body · single chat column. Live-notes panel was removed, so there's
|
|
4161
|
-
no right-side resizer or aside to apportion space for.
|
|
4247
|
+
no right-side resizer or aside to apportion space for.
|
|
4248
|
+
`grid-template-rows: 1fr` is required so the implicit row stretches
|
|
4249
|
+
to body's height instead of collapsing to content height. Without
|
|
4250
|
+
it, .chat-col would be only as tall as its visible children, and
|
|
4251
|
+
.chat (`flex: 1 1 auto`) would have nothing to grow into — making
|
|
4252
|
+
[data-chat-messages] sit flush at the top of a content-sized
|
|
4253
|
+
chat. With `1fr`, .chat-col fills body, .chat fills chat-col, and
|
|
4254
|
+
vertical centring of the composer inside .chat actually has free
|
|
4255
|
+
space to work with. */
|
|
4162
4256
|
.body {
|
|
4163
4257
|
display: grid;
|
|
4164
4258
|
grid-template-columns: 1fr;
|
|
4259
|
+
grid-template-rows: 1fr;
|
|
4165
4260
|
overflow: hidden;
|
|
4166
4261
|
min-height: 0;
|
|
4262
|
+
height: 100%;
|
|
4263
|
+
/* Pin to .main-view's 1fr row · without this, when room-head is
|
|
4264
|
+
:empty + display:none (composer mode), .body becomes the first
|
|
4265
|
+
non-hidden grid child and auto-flows into row 1 (auto), where
|
|
4266
|
+
it collapses to content height. Vertical centring then has no
|
|
4267
|
+
free space. Explicit grid-row: 2 keeps .body in the 1fr row
|
|
4268
|
+
regardless of whether room-head is visible. */
|
|
4269
|
+
grid-row: 2;
|
|
4167
4270
|
}
|
|
4168
4271
|
|
|
4169
4272
|
/* Chat (zen plain text) */
|
|
@@ -7005,14 +7108,76 @@
|
|
|
7005
7108
|
edge so the page has one clear focal point. Refined: generous
|
|
7006
7109
|
vertical rhythm, single-accent colour discipline, mono micro-type
|
|
7007
7110
|
paired with serif-leaning sans display. ─── */
|
|
7111
|
+
/* Composer (new-room AND new-agent) shared rhythm.
|
|
7112
|
+
Vertical centring is split across two modes:
|
|
7113
|
+
· Default (`.chat.chat--composer`, content fits viewport) ·
|
|
7114
|
+
the chat is `display: grid; align-content: center` so the
|
|
7115
|
+
whole .cmp block sits in the visual centre.
|
|
7116
|
+
· Overflow (`.chat.chat--composer.chat--composer-overflow`,
|
|
7117
|
+
JS-toggled when `cmp.scrollHeight > chat.clientHeight`) ·
|
|
7118
|
+
the chat switches to block flow + scroll, .cmp-fold (hero
|
|
7119
|
+
+ input) is min-height ≈ 100vh with content centred inside,
|
|
7120
|
+
and .cmp-starters flow below as scrollable extras. Title +
|
|
7121
|
+
input stay visually centred on initial paint regardless of
|
|
7122
|
+
viewport height; starter cards live below the fold.
|
|
7123
|
+
Padding stays modest because vertical position no longer comes
|
|
7124
|
+
from top padding; it's just breathing room around the cmp box.
|
|
7125
|
+
Keep both composers in lock-step by editing here only. */
|
|
7008
7126
|
.cmp {
|
|
7009
7127
|
max-width: 760px;
|
|
7010
7128
|
margin: 0 auto;
|
|
7011
|
-
padding:
|
|
7129
|
+
padding: 32px 32px;
|
|
7130
|
+
width: 100%;
|
|
7131
|
+
}
|
|
7132
|
+
/* Default composer mode (content fits viewport).
|
|
7133
|
+
Toggled by JS via `.chat--composer` (added in
|
|
7134
|
+
renderEmptyState; removed in renderChat). The
|
|
7135
|
+
`.chat--composer-overflow` variant below handles the case
|
|
7136
|
+
where the composer is taller than .chat. */
|
|
7137
|
+
.chat.chat--composer {
|
|
7138
|
+
/* Grid centring · `align-content: center` is the most reliable
|
|
7139
|
+
cross-browser way to vertically centre a grid track. Beats
|
|
7140
|
+
flex auto-margins (which can resolve to 0 if any parent in
|
|
7141
|
+
the chain doesn't propagate height correctly) and beats
|
|
7142
|
+
`justify-content: safe center` (the `safe` keyword can
|
|
7143
|
+
silently invalidate the whole declaration in older browsers).
|
|
7144
|
+
When content overflows, the grid item still scrolls naturally
|
|
7145
|
+
inside .chat's `overflow-y: auto`. */
|
|
7146
|
+
display: grid !important;
|
|
7147
|
+
align-content: center !important;
|
|
7148
|
+
justify-items: stretch !important;
|
|
7149
|
+
}
|
|
7150
|
+
.chat.chat--composer > [data-chat-messages] {
|
|
7151
|
+
width: 100% !important;
|
|
7152
|
+
}
|
|
7153
|
+
.chat.chat--composer > [data-brief-card] {
|
|
7154
|
+
/* In composer mode the brief-card is empty · hide it so it
|
|
7155
|
+
doesn't claim a grid row that throws off the centring. */
|
|
7156
|
+
display: none !important;
|
|
7157
|
+
}
|
|
7158
|
+
/* Overflow mode · when .cmp's natural height exceeds .chat's height,
|
|
7159
|
+
centring the entire composer block would clip the title above the
|
|
7160
|
+
scroll area. Instead, push the .cmp's top down with a viewport-
|
|
7161
|
+
relative padding-top so hero + input sit at the visual centre of
|
|
7162
|
+
the viewport on initial paint; .cmp-starters fall below the input
|
|
7163
|
+
in normal document flow with the SAME spacing as the fits case
|
|
7164
|
+
(no `justify-content: center` void zone). Approximation: hero +
|
|
7165
|
+
input combined ≈ 220px tall, so half ≈ 110px; padding-top of
|
|
7166
|
+
`50vh - 110px` puts the hero+input vertical midpoint at 50vh.
|
|
7167
|
+
`max(32px, …)` keeps the original padding floor on tiny viewports
|
|
7168
|
+
where the calc would go negative.
|
|
7169
|
+
Toggled by JS (updateComposerOverflow) on render + resize. */
|
|
7170
|
+
.chat.chat--composer.chat--composer-overflow {
|
|
7171
|
+
display: block !important;
|
|
7172
|
+
align-content: initial !important;
|
|
7173
|
+
overflow-y: auto;
|
|
7174
|
+
}
|
|
7175
|
+
.chat.chat--composer.chat--composer-overflow .cmp {
|
|
7176
|
+
padding-top: max(32px, calc(50vh - 110px));
|
|
7012
7177
|
}
|
|
7013
7178
|
.cmp-hero {
|
|
7014
7179
|
text-align: center;
|
|
7015
|
-
margin-bottom:
|
|
7180
|
+
margin-bottom: 22px;
|
|
7016
7181
|
}
|
|
7017
7182
|
.cmp-greet {
|
|
7018
7183
|
font-family: var(--mono);
|
|
@@ -7021,11 +7186,11 @@
|
|
|
7021
7186
|
text-transform: uppercase;
|
|
7022
7187
|
color: var(--text-faint);
|
|
7023
7188
|
font-weight: 700;
|
|
7024
|
-
margin-bottom:
|
|
7189
|
+
margin-bottom: 14px;
|
|
7025
7190
|
}
|
|
7026
7191
|
.cmp-prompt {
|
|
7027
7192
|
font-family: var(--font-human);
|
|
7028
|
-
font-size:
|
|
7193
|
+
font-size: 28px;
|
|
7029
7194
|
font-weight: 600;
|
|
7030
7195
|
color: var(--text);
|
|
7031
7196
|
line-height: 1.2;
|
|
@@ -7070,7 +7235,13 @@
|
|
|
7070
7235
|
}
|
|
7071
7236
|
|
|
7072
7237
|
/* Toolbar inside input frame · cast on left, tune in the middle,
|
|
7073
|
-
convene on the right. Hairline divider above.
|
|
7238
|
+
convene on the right. Hairline divider above. min-height pinned
|
|
7239
|
+
to the room composer's natural height (driven by the 22px cast
|
|
7240
|
+
avatars + button padding) so the agent composer's toolbar — which
|
|
7241
|
+
has no avatar-bearing button — grows to match. Without this the
|
|
7242
|
+
agent input frame is a few pixels shorter than the room frame.
|
|
7243
|
+
Box-sizing is border-box (universal in this project), so the
|
|
7244
|
+
min-height includes the 8px+8px vertical padding. */
|
|
7074
7245
|
.cmp-toolbar {
|
|
7075
7246
|
display: flex;
|
|
7076
7247
|
align-items: center;
|
|
@@ -7078,6 +7249,7 @@
|
|
|
7078
7249
|
padding: 8px 8px 8px 14px;
|
|
7079
7250
|
border-top: 0.5px solid var(--line);
|
|
7080
7251
|
background: var(--panel-2);
|
|
7252
|
+
min-height: 48px;
|
|
7081
7253
|
}
|
|
7082
7254
|
|
|
7083
7255
|
.cmp-cast-btn {
|
|
@@ -7413,7 +7585,7 @@
|
|
|
7413
7585
|
accent borders. Just hairline-separated rows that brighten on
|
|
7414
7586
|
hover, with the arrow turning lime — the only visual reward. */
|
|
7415
7587
|
.cmp-starters {
|
|
7416
|
-
margin-top:
|
|
7588
|
+
margin-top: 28px;
|
|
7417
7589
|
}
|
|
7418
7590
|
.cmp-starters-rule {
|
|
7419
7591
|
display: flex;
|
|
@@ -7444,7 +7616,7 @@
|
|
|
7444
7616
|
gap: 16px;
|
|
7445
7617
|
align-items: center;
|
|
7446
7618
|
width: 100%;
|
|
7447
|
-
padding:
|
|
7619
|
+
padding: 10px 4px;
|
|
7448
7620
|
background: transparent;
|
|
7449
7621
|
border: none;
|
|
7450
7622
|
border-bottom: 0.5px solid var(--line);
|
|
@@ -7490,7 +7662,10 @@
|
|
|
7490
7662
|
/* ─── New-agent composer · variant of the new-room composer.
|
|
7491
7663
|
Reuses .cmp / .cmp-* classes for the hero + input frame; adds
|
|
7492
7664
|
ag-cmp-* for agent-specific bits and ag-prev-* for the editable
|
|
7493
|
-
spec preview card that appears after AI generation.
|
|
7665
|
+
spec preview card that appears after AI generation. The vertical
|
|
7666
|
+
rhythm (padding, hero/starter margins, starter card density) is
|
|
7667
|
+
SHARED with the new-room composer via the base .cmp rules — so
|
|
7668
|
+
edits land in one place and both composers stay in sync. ─── */
|
|
7494
7669
|
.ag-cmp .cmp-toolbar {
|
|
7495
7670
|
/* Model + manual buttons sit at the left, Generate hugs the right
|
|
7496
7671
|
via margin-left: auto on .cmp-go. No justify-content override
|
|
@@ -8621,12 +8796,12 @@
|
|
|
8621
8796
|
|
|
8622
8797
|
/* Long-opener clamp · when the user wrote more than a sentence of
|
|
8623
8798
|
context, the card would otherwise dominate the viewport. We
|
|
8624
|
-
clamp the body to
|
|
8799
|
+
clamp the body to 2 lines with a fade-out overlay; a
|
|
8625
8800
|
`<button data-convene-toggle>` below toggles `.expanded` on the
|
|
8626
|
-
opener parent to reveal the rest. 2
|
|
8627
|
-
|
|
8801
|
+
opener parent to reveal the rest. 2 × line-height (1.32 × 19px
|
|
8802
|
+
≈ 25px) = 50px. */
|
|
8628
8803
|
.convene-opener-clamped:not(.expanded) .convene-body {
|
|
8629
|
-
max-height:
|
|
8804
|
+
max-height: 50px;
|
|
8630
8805
|
overflow: hidden;
|
|
8631
8806
|
position: relative;
|
|
8632
8807
|
}
|
|
@@ -8636,7 +8811,7 @@
|
|
|
8636
8811
|
left: 0;
|
|
8637
8812
|
right: 0;
|
|
8638
8813
|
bottom: 0;
|
|
8639
|
-
height:
|
|
8814
|
+
height: 24px;
|
|
8640
8815
|
background: linear-gradient(to bottom, transparent, var(--panel-2));
|
|
8641
8816
|
pointer-events: none;
|
|
8642
8817
|
}
|
|
@@ -8939,6 +9114,7 @@
|
|
|
8939
9114
|
min-height: 0;
|
|
8940
9115
|
overflow: hidden;
|
|
8941
9116
|
background: var(--panel);
|
|
9117
|
+
height: 100%;
|
|
8942
9118
|
}
|
|
8943
9119
|
|
|
8944
9120
|
/* Input bar — sits inside the chat column. Live state hosts a
|
|
@@ -9560,13 +9736,27 @@
|
|
|
9560
9736
|
io.observe(hint);
|
|
9561
9737
|
})();
|
|
9562
9738
|
|
|
9563
|
-
/* ─── Pin toggle
|
|
9739
|
+
/* ─── Pin toggle ───
|
|
9740
|
+
Two paths: agent rows persist via PATCH /api/agents/:id { isPinned }
|
|
9741
|
+
through `app.togglePinAgent`, which mutates local state and
|
|
9742
|
+
re-renders the sidebar so the row moves into the Pinned bucket.
|
|
9743
|
+
Session (room) rows fall through to a visual-only toggle for now —
|
|
9744
|
+
room pinning isn't a backend feature yet, so the class flip is a
|
|
9745
|
+
placeholder that will go away once it lands. */
|
|
9564
9746
|
(function () {
|
|
9565
9747
|
document.addEventListener("click", (e) => {
|
|
9566
9748
|
const btn = e.target.closest("[data-pin-toggle]");
|
|
9567
9749
|
if (!btn) return;
|
|
9568
9750
|
e.preventDefault();
|
|
9569
9751
|
e.stopPropagation();
|
|
9752
|
+
// Agent row · persist the pin via the API and let the sidebar
|
|
9753
|
+
// re-render rebuild the Pinned / Custom / Core buckets.
|
|
9754
|
+
const agentShell = btn.closest(".agent-row-shell[data-agent-id]");
|
|
9755
|
+
if (agentShell && window.app && typeof window.app.togglePinAgent === "function") {
|
|
9756
|
+
window.app.togglePinAgent(agentShell.dataset.agentId);
|
|
9757
|
+
return;
|
|
9758
|
+
}
|
|
9759
|
+
// Fallback · session-row (room pinning is visual-only for now).
|
|
9570
9760
|
const row = btn.closest(".session-row, .agent-row");
|
|
9571
9761
|
if (!row) return;
|
|
9572
9762
|
row.classList.toggle("pinned");
|
|
@@ -9715,35 +9905,36 @@
|
|
|
9715
9905
|
});
|
|
9716
9906
|
|
|
9717
9907
|
if (which === "rooms") {
|
|
9718
|
-
// Resolve which sub-state to apply.
|
|
9719
|
-
//
|
|
9720
|
-
//
|
|
9721
|
-
//
|
|
9722
|
-
//
|
|
9723
|
-
//
|
|
9724
|
-
//
|
|
9725
|
-
//
|
|
9908
|
+
// Resolve which sub-state to apply. ROOMS_KEY is the user's
|
|
9909
|
+
// explicit choice — preserve it on every nav (tab click,
|
|
9910
|
+
// refresh, deep-link). All four sub-states are first-class:
|
|
9911
|
+
// an explicit room id, "new" (composer), "reports", "notes".
|
|
9912
|
+
//
|
|
9913
|
+
// Earlier the fromTabClick path overrode saved="new" with
|
|
9914
|
+
// ROOMS_LAST_KEY, on the theory that "re-entering the Rooms
|
|
9915
|
+
// tab should feel continuous." In practice that broke the
|
|
9916
|
+
// user's expectation: pick "+ New Room", switch to Agents
|
|
9917
|
+
// tab, switch back, lands on a different room. The user's
|
|
9918
|
+
// mental model is "the sidebar remembers what I last clicked"
|
|
9919
|
+
// — so "new" is now sticky just like "reports" / "notes".
|
|
9920
|
+
// Fallback to the last-opened room (or first row) only fires
|
|
9921
|
+
// when there's no saved sub-state at all.
|
|
9726
9922
|
const saved = lsGet(ROOMS_KEY);
|
|
9727
9923
|
let target;
|
|
9728
9924
|
if (saved && saved !== "new" && saved !== "reports" && saved !== "notes") {
|
|
9729
9925
|
target = saved; // explicit room id
|
|
9730
|
-
} else if (saved === "reports" || saved === "notes") {
|
|
9731
|
-
target = saved; //
|
|
9926
|
+
} else if (saved === "reports" || saved === "notes" || saved === "new") {
|
|
9927
|
+
target = saved; // explicit destination preserved on any nav
|
|
9732
9928
|
} else if (fromTabClick) {
|
|
9733
|
-
//
|
|
9734
|
-
//
|
|
9735
|
-
//
|
|
9736
|
-
// resetting to the composer. The "new" intent only persists
|
|
9737
|
-
// within the rooms tab — once the user leaves and returns,
|
|
9738
|
-
// we surface their last room.
|
|
9929
|
+
// No saved sub-state · fresh user clicking the tab. Prefer
|
|
9930
|
+
// the last actually-opened room so the tab feels populated
|
|
9931
|
+
// instead of bouncing them to a blank composer.
|
|
9739
9932
|
const last = lsGet(ROOMS_LAST_KEY);
|
|
9740
9933
|
if (last && document.querySelector(`.session-row-shell[data-room-id="${last}"]`)) {
|
|
9741
9934
|
target = last;
|
|
9742
9935
|
} else {
|
|
9743
9936
|
target = firstRoomId() || "new";
|
|
9744
9937
|
}
|
|
9745
|
-
} else if (saved === "new") {
|
|
9746
|
-
target = "new"; // initial load · preserve composer intent
|
|
9747
9938
|
} else {
|
|
9748
9939
|
target = "new"; // initial load with no history → composer
|
|
9749
9940
|
}
|
|
@@ -9890,9 +10081,13 @@
|
|
|
9890
10081
|
const tab = (savedTab && VALID_TABS.has(savedTab)) ? savedTab : "rooms";
|
|
9891
10082
|
// Honor URL hash → if user came in with #/r/<id>, that's the
|
|
9892
10083
|
// intended room; promote it to the rooms sub-state and switch
|
|
9893
|
-
// to rooms tab
|
|
10084
|
+
// to rooms tab. BUT only when the saved tab isn't already
|
|
10085
|
+
// "agents" — opening an agent profile doesn't update the URL
|
|
10086
|
+
// hash, so a refresh on the agent page would otherwise get
|
|
10087
|
+
// bounced to rooms by a leftover hash from the prior room
|
|
10088
|
+
// view. Sticky agent tab beats stale-hash deep linking.
|
|
9894
10089
|
const m = (location.hash || "").match(/^#\/r\/([a-z0-9]+)/i);
|
|
9895
|
-
if (m && m[1]) {
|
|
10090
|
+
if (m && m[1] && tab !== "agents") {
|
|
9896
10091
|
lsSet(ROOMS_KEY, m[1]);
|
|
9897
10092
|
lsSet(ROOMS_LAST_KEY, m[1]);
|
|
9898
10093
|
activate("rooms", { persist: false });
|