privateboard 0.1.8 → 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 +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 +269 -113
- package/public/index.html +304 -126
- 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
|
|
|
@@ -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
|
|
@@ -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;
|
|
@@ -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,7 +2786,7 @@
|
|
|
2760
2786
|
text-transform: uppercase;
|
|
2761
2787
|
color: var(--lime);
|
|
2762
2788
|
}
|
|
2763
|
-
/* Mode chip · "Report" / "
|
|
2789
|
+
/* Mode chip · "Report" / "Magazine" / "Newspaper".
|
|
2764
2790
|
Sits between the // report kicker and the FILED stamp, styled as
|
|
2765
2791
|
a hairline mono pill so the user sees the report type at a glance
|
|
2766
2792
|
without it competing with the kicker for primary emphasis.
|
|
@@ -3605,13 +3631,56 @@
|
|
|
3605
3631
|
margin-bottom: 18px;
|
|
3606
3632
|
font-family: var(--mono);
|
|
3607
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. */
|
|
3608
3639
|
.followup-parent-subject {
|
|
3609
3640
|
font-size: 13px;
|
|
3610
3641
|
font-weight: 600;
|
|
3611
3642
|
color: var(--text);
|
|
3612
3643
|
line-height: 1.4;
|
|
3613
3644
|
margin-bottom: 4px;
|
|
3645
|
+
word-break: break-word;
|
|
3614
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;
|
|
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); }
|
|
3615
3684
|
.followup-parent-meta {
|
|
3616
3685
|
font-size: 10px;
|
|
3617
3686
|
color: var(--text-soft);
|
|
@@ -4175,12 +4244,29 @@
|
|
|
4175
4244
|
html[data-status="adjourned"] .paused-bar { display: none; }
|
|
4176
4245
|
|
|
4177
4246
|
/* Body · single chat column. Live-notes panel was removed, so there's
|
|
4178
|
-
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. */
|
|
4179
4256
|
.body {
|
|
4180
4257
|
display: grid;
|
|
4181
4258
|
grid-template-columns: 1fr;
|
|
4259
|
+
grid-template-rows: 1fr;
|
|
4182
4260
|
overflow: hidden;
|
|
4183
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;
|
|
4184
4270
|
}
|
|
4185
4271
|
|
|
4186
4272
|
/* Chat (zen plain text) */
|
|
@@ -7022,14 +7108,76 @@
|
|
|
7022
7108
|
edge so the page has one clear focal point. Refined: generous
|
|
7023
7109
|
vertical rhythm, single-accent colour discipline, mono micro-type
|
|
7024
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. */
|
|
7025
7126
|
.cmp {
|
|
7026
7127
|
max-width: 760px;
|
|
7027
7128
|
margin: 0 auto;
|
|
7028
|
-
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));
|
|
7029
7177
|
}
|
|
7030
7178
|
.cmp-hero {
|
|
7031
7179
|
text-align: center;
|
|
7032
|
-
margin-bottom:
|
|
7180
|
+
margin-bottom: 22px;
|
|
7033
7181
|
}
|
|
7034
7182
|
.cmp-greet {
|
|
7035
7183
|
font-family: var(--mono);
|
|
@@ -7038,11 +7186,11 @@
|
|
|
7038
7186
|
text-transform: uppercase;
|
|
7039
7187
|
color: var(--text-faint);
|
|
7040
7188
|
font-weight: 700;
|
|
7041
|
-
margin-bottom:
|
|
7189
|
+
margin-bottom: 14px;
|
|
7042
7190
|
}
|
|
7043
7191
|
.cmp-prompt {
|
|
7044
7192
|
font-family: var(--font-human);
|
|
7045
|
-
font-size:
|
|
7193
|
+
font-size: 28px;
|
|
7046
7194
|
font-weight: 600;
|
|
7047
7195
|
color: var(--text);
|
|
7048
7196
|
line-height: 1.2;
|
|
@@ -7087,7 +7235,13 @@
|
|
|
7087
7235
|
}
|
|
7088
7236
|
|
|
7089
7237
|
/* Toolbar inside input frame · cast on left, tune in the middle,
|
|
7090
|
-
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. */
|
|
7091
7245
|
.cmp-toolbar {
|
|
7092
7246
|
display: flex;
|
|
7093
7247
|
align-items: center;
|
|
@@ -7095,6 +7249,7 @@
|
|
|
7095
7249
|
padding: 8px 8px 8px 14px;
|
|
7096
7250
|
border-top: 0.5px solid var(--line);
|
|
7097
7251
|
background: var(--panel-2);
|
|
7252
|
+
min-height: 48px;
|
|
7098
7253
|
}
|
|
7099
7254
|
|
|
7100
7255
|
.cmp-cast-btn {
|
|
@@ -7430,7 +7585,7 @@
|
|
|
7430
7585
|
accent borders. Just hairline-separated rows that brighten on
|
|
7431
7586
|
hover, with the arrow turning lime — the only visual reward. */
|
|
7432
7587
|
.cmp-starters {
|
|
7433
|
-
margin-top:
|
|
7588
|
+
margin-top: 28px;
|
|
7434
7589
|
}
|
|
7435
7590
|
.cmp-starters-rule {
|
|
7436
7591
|
display: flex;
|
|
@@ -7461,7 +7616,7 @@
|
|
|
7461
7616
|
gap: 16px;
|
|
7462
7617
|
align-items: center;
|
|
7463
7618
|
width: 100%;
|
|
7464
|
-
padding:
|
|
7619
|
+
padding: 10px 4px;
|
|
7465
7620
|
background: transparent;
|
|
7466
7621
|
border: none;
|
|
7467
7622
|
border-bottom: 0.5px solid var(--line);
|
|
@@ -7507,7 +7662,10 @@
|
|
|
7507
7662
|
/* ─── New-agent composer · variant of the new-room composer.
|
|
7508
7663
|
Reuses .cmp / .cmp-* classes for the hero + input frame; adds
|
|
7509
7664
|
ag-cmp-* for agent-specific bits and ag-prev-* for the editable
|
|
7510
|
-
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. ─── */
|
|
7511
7669
|
.ag-cmp .cmp-toolbar {
|
|
7512
7670
|
/* Model + manual buttons sit at the left, Generate hugs the right
|
|
7513
7671
|
via margin-left: auto on .cmp-go. No justify-content override
|
|
@@ -8638,12 +8796,12 @@
|
|
|
8638
8796
|
|
|
8639
8797
|
/* Long-opener clamp · when the user wrote more than a sentence of
|
|
8640
8798
|
context, the card would otherwise dominate the viewport. We
|
|
8641
|
-
clamp the body to
|
|
8799
|
+
clamp the body to 2 lines with a fade-out overlay; a
|
|
8642
8800
|
`<button data-convene-toggle>` below toggles `.expanded` on the
|
|
8643
|
-
opener parent to reveal the rest. 2
|
|
8644
|
-
|
|
8801
|
+
opener parent to reveal the rest. 2 × line-height (1.32 × 19px
|
|
8802
|
+
≈ 25px) = 50px. */
|
|
8645
8803
|
.convene-opener-clamped:not(.expanded) .convene-body {
|
|
8646
|
-
max-height:
|
|
8804
|
+
max-height: 50px;
|
|
8647
8805
|
overflow: hidden;
|
|
8648
8806
|
position: relative;
|
|
8649
8807
|
}
|
|
@@ -8653,7 +8811,7 @@
|
|
|
8653
8811
|
left: 0;
|
|
8654
8812
|
right: 0;
|
|
8655
8813
|
bottom: 0;
|
|
8656
|
-
height:
|
|
8814
|
+
height: 24px;
|
|
8657
8815
|
background: linear-gradient(to bottom, transparent, var(--panel-2));
|
|
8658
8816
|
pointer-events: none;
|
|
8659
8817
|
}
|
|
@@ -8956,6 +9114,7 @@
|
|
|
8956
9114
|
min-height: 0;
|
|
8957
9115
|
overflow: hidden;
|
|
8958
9116
|
background: var(--panel);
|
|
9117
|
+
height: 100%;
|
|
8959
9118
|
}
|
|
8960
9119
|
|
|
8961
9120
|
/* Input bar — sits inside the chat column. Live state hosts a
|
|
@@ -9577,13 +9736,27 @@
|
|
|
9577
9736
|
io.observe(hint);
|
|
9578
9737
|
})();
|
|
9579
9738
|
|
|
9580
|
-
/* ─── 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. */
|
|
9581
9746
|
(function () {
|
|
9582
9747
|
document.addEventListener("click", (e) => {
|
|
9583
9748
|
const btn = e.target.closest("[data-pin-toggle]");
|
|
9584
9749
|
if (!btn) return;
|
|
9585
9750
|
e.preventDefault();
|
|
9586
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).
|
|
9587
9760
|
const row = btn.closest(".session-row, .agent-row");
|
|
9588
9761
|
if (!row) return;
|
|
9589
9762
|
row.classList.toggle("pinned");
|
|
@@ -9732,35 +9905,36 @@
|
|
|
9732
9905
|
});
|
|
9733
9906
|
|
|
9734
9907
|
if (which === "rooms") {
|
|
9735
|
-
// Resolve which sub-state to apply.
|
|
9736
|
-
//
|
|
9737
|
-
//
|
|
9738
|
-
//
|
|
9739
|
-
//
|
|
9740
|
-
//
|
|
9741
|
-
//
|
|
9742
|
-
//
|
|
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.
|
|
9743
9922
|
const saved = lsGet(ROOMS_KEY);
|
|
9744
9923
|
let target;
|
|
9745
9924
|
if (saved && saved !== "new" && saved !== "reports" && saved !== "notes") {
|
|
9746
9925
|
target = saved; // explicit room id
|
|
9747
|
-
} else if (saved === "reports" || saved === "notes") {
|
|
9748
|
-
target = saved; //
|
|
9926
|
+
} else if (saved === "reports" || saved === "notes" || saved === "new") {
|
|
9927
|
+
target = saved; // explicit destination preserved on any nav
|
|
9749
9928
|
} 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.
|
|
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.
|
|
9756
9932
|
const last = lsGet(ROOMS_LAST_KEY);
|
|
9757
9933
|
if (last && document.querySelector(`.session-row-shell[data-room-id="${last}"]`)) {
|
|
9758
9934
|
target = last;
|
|
9759
9935
|
} else {
|
|
9760
9936
|
target = firstRoomId() || "new";
|
|
9761
9937
|
}
|
|
9762
|
-
} else if (saved === "new") {
|
|
9763
|
-
target = "new"; // initial load · preserve composer intent
|
|
9764
9938
|
} else {
|
|
9765
9939
|
target = "new"; // initial load with no history → composer
|
|
9766
9940
|
}
|
|
@@ -9907,9 +10081,13 @@
|
|
|
9907
10081
|
const tab = (savedTab && VALID_TABS.has(savedTab)) ? savedTab : "rooms";
|
|
9908
10082
|
// Honor URL hash → if user came in with #/r/<id>, that's the
|
|
9909
10083
|
// intended room; promote it to the rooms sub-state and switch
|
|
9910
|
-
// 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.
|
|
9911
10089
|
const m = (location.hash || "").match(/^#\/r\/([a-z0-9]+)/i);
|
|
9912
|
-
if (m && m[1]) {
|
|
10090
|
+
if (m && m[1] && tab !== "agents") {
|
|
9913
10091
|
lsSet(ROOMS_KEY, m[1]);
|
|
9914
10092
|
lsSet(ROOMS_LAST_KEY, m[1]);
|
|
9915
10093
|
activate("rooms", { persist: false });
|