privateboard 0.1.9 → 0.1.11
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 +3732 -2160
- package/dist/cli.js.map +1 -1
- package/package.json +1 -1
- package/public/agent-overlay.js +59 -36
- package/public/agent-profile.css +392 -100
- package/public/agent-profile.js +551 -59
- package/public/app.js +1341 -681
- package/public/i18n.js +1990 -0
- package/public/index.html +251 -94
- package/public/keys-store.js +63 -0
- package/public/new-agent.css +60 -0
- package/public/new-agent.js +121 -53
- package/public/room-settings.js +2 -1
- package/public/user-settings.css +68 -0
- package/public/user-settings.js +241 -87
- package/public/voice-replay.css +607 -0
- package/public/voice-replay.js +533 -0
package/public/index.html
CHANGED
|
@@ -158,6 +158,7 @@
|
|
|
158
158
|
}
|
|
159
159
|
.brand-mark .bg { fill: var(--lime); }
|
|
160
160
|
.brand-mark .fg { fill: var(--bg); }
|
|
161
|
+
|
|
161
162
|
@keyframes pulse { 0%, 60% { opacity: 1; } 80% { opacity: 0.4; } }
|
|
162
163
|
|
|
163
164
|
/* ═══════════════════════════════════════════
|
|
@@ -568,19 +569,9 @@
|
|
|
568
569
|
overflow: hidden;
|
|
569
570
|
text-overflow: ellipsis;
|
|
570
571
|
}
|
|
571
|
-
/* Time tag ·
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
visually a different "kind of metadata" from the rooms time. */
|
|
575
|
-
.agent-row .agent-row-time {
|
|
576
|
-
color: var(--text-dim);
|
|
577
|
-
font-size: 10.5px;
|
|
578
|
-
font-family: var(--mono);
|
|
579
|
-
letter-spacing: 0;
|
|
580
|
-
text-transform: none;
|
|
581
|
-
white-space: nowrap;
|
|
582
|
-
flex-shrink: 0;
|
|
583
|
-
}
|
|
572
|
+
/* Time tag removed · the agent-row no longer surfaces creation
|
|
573
|
+
time. The space is given back to the title (which can now use
|
|
574
|
+
the full row width minus the hover-revealed pin button). */
|
|
584
575
|
.agent-row .agent-row-subtitle {
|
|
585
576
|
font-family: var(--mono);
|
|
586
577
|
font-size: 9.5px;
|
|
@@ -934,13 +925,10 @@
|
|
|
934
925
|
.agents-section-header.pinned { color: var(--text-soft); }
|
|
935
926
|
|
|
936
927
|
/* ─── Pin toggle (per-row · hover-revealed for non-pinned) ───
|
|
937
|
-
Positioned absolutely so the
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
the agent's "7h" tag inward compared to the rooms list and made
|
|
942
|
-
the two columns of timestamps look mis-aligned. The row-shell
|
|
943
|
-
ancestor needs `position: relative` for this to anchor correctly. */
|
|
928
|
+
Positioned absolutely so it overlays the right edge of the row
|
|
929
|
+
without taking in-flow width (opacity:0 alone would still
|
|
930
|
+
reserve 22px and shrink the title). The row-shell ancestor
|
|
931
|
+
needs `position: relative` for this to anchor correctly. */
|
|
944
932
|
.agent-row-shell, .session-row-shell { position: relative; }
|
|
945
933
|
.row-top-line .pin-toggle,
|
|
946
934
|
.agent-row-top-line .pin-toggle {
|
|
@@ -968,23 +956,12 @@
|
|
|
968
956
|
}
|
|
969
957
|
.session-row:hover .pin-toggle,
|
|
970
958
|
.agent-row:hover .pin-toggle { opacity: 0.55; }
|
|
971
|
-
/* Hover · hide the in-flow time so the absolutely-positioned pin
|
|
972
|
-
button has the right edge to itself. Same pattern as the rooms
|
|
973
|
-
list (`.session-row-shell:hover .row-time { visibility: hidden }`). */
|
|
974
|
-
.agent-row-shell:hover .agent-row-time { visibility: hidden; }
|
|
975
959
|
.pin-toggle:hover { opacity: 1 !important; color: var(--lime); }
|
|
976
960
|
.session-row.pinned .pin-toggle,
|
|
977
961
|
.agent-row.pinned .pin-toggle {
|
|
978
962
|
opacity: 1;
|
|
979
963
|
color: var(--lime);
|
|
980
964
|
}
|
|
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; }
|
|
988
965
|
|
|
989
966
|
/* The shell wraps the link + delete button as siblings so the click on
|
|
990
967
|
the X button doesn't get absorbed by the anchor's navigation.
|
|
@@ -1038,10 +1015,8 @@
|
|
|
1038
1015
|
z-index: 2;
|
|
1039
1016
|
}
|
|
1040
1017
|
.session-row-shell:hover .row-delete { display: flex; }
|
|
1041
|
-
/*
|
|
1042
|
-
|
|
1043
|
-
before the X. */
|
|
1044
|
-
.session-row-shell:hover .row-time { visibility: hidden; }
|
|
1018
|
+
/* Subtitle reserves right padding so its ellipsis stops before
|
|
1019
|
+
the absolutely-positioned X delete button on hover. */
|
|
1045
1020
|
.session-row-shell .row-subtitle { padding-right: 26px; }
|
|
1046
1021
|
.row-delete:hover {
|
|
1047
1022
|
border-color: var(--red);
|
|
@@ -1130,17 +1105,14 @@
|
|
|
1130
1105
|
font-family: var(--sans);
|
|
1131
1106
|
letter-spacing: -0.005em;
|
|
1132
1107
|
}
|
|
1133
|
-
.row-time
|
|
1134
|
-
|
|
1135
|
-
|
|
1136
|
-
|
|
1137
|
-
|
|
1138
|
-
}
|
|
1139
|
-
.session-row-shell[data-status="live"] .row-time {
|
|
1108
|
+
/* Live indicator · was a `● ` prefix on the (now-removed) .row-time
|
|
1109
|
+
tag. Migrated to a title prefix so live rooms still read at a
|
|
1110
|
+
glance. The rest of the title stays its usual colour. */
|
|
1111
|
+
.session-row-shell[data-status="live"] .row-title::before {
|
|
1112
|
+
content: "● ";
|
|
1140
1113
|
color: var(--lime);
|
|
1141
1114
|
font-weight: 700;
|
|
1142
1115
|
}
|
|
1143
|
-
.session-row-shell[data-status="live"] .row-time::before { content: "● "; }
|
|
1144
1116
|
|
|
1145
1117
|
.row-subtitle {
|
|
1146
1118
|
font-size: 11.5px;
|
|
@@ -1845,8 +1817,7 @@
|
|
|
1845
1817
|
.notes-item-passage { padding-right: 30px; }
|
|
1846
1818
|
/* On hover · hide the trailing time tag so the delete button
|
|
1847
1819
|
occupies the right-edge slot cleanly. visibility:hidden keeps
|
|
1848
|
-
the layout box (the meta row doesn't reflow either).
|
|
1849
|
-
pattern as `.session-row-shell:hover .row-time` in rooms. */
|
|
1820
|
+
the layout box (the meta row doesn't reflow either). */
|
|
1850
1821
|
.notes-item:hover .notes-item-time { visibility: hidden; }
|
|
1851
1822
|
.notes-item-link {
|
|
1852
1823
|
display: block;
|
|
@@ -2841,6 +2812,11 @@
|
|
|
2841
2812
|
text-transform: uppercase;
|
|
2842
2813
|
margin-bottom: 4px;
|
|
2843
2814
|
}
|
|
2815
|
+
.brief-kicker-sep {
|
|
2816
|
+
color: var(--text-faint);
|
|
2817
|
+
opacity: 0.5;
|
|
2818
|
+
margin: 0 2px;
|
|
2819
|
+
}
|
|
2844
2820
|
.brief-title {
|
|
2845
2821
|
font-family: var(--font-human);
|
|
2846
2822
|
font-size: 15px;
|
|
@@ -2896,6 +2872,25 @@
|
|
|
2896
2872
|
.brief-meta-words.is-long { color: var(--red); opacity: 0.85; }
|
|
2897
2873
|
.brief-meta-words.is-too-long { color: var(--red); font-weight: 700; }
|
|
2898
2874
|
|
|
2875
|
+
/* Mode chip · folded into the brief-meta-row alongside the
|
|
2876
|
+
authors / word-count pieces. The standalone .brief-banner
|
|
2877
|
+
strip that used to host this was retired (too tall for the
|
|
2878
|
+
info it carried). Renders as a tiny mono pill so the report
|
|
2879
|
+
type ("REPORT" / "MAGAZINE" / "NEWSPAPER" / "SLIDES") is
|
|
2880
|
+
visible at a glance without competing for emphasis with the
|
|
2881
|
+
title or kicker. */
|
|
2882
|
+
.brief-meta-type {
|
|
2883
|
+
font-family: var(--mono);
|
|
2884
|
+
font-size: 8.5px;
|
|
2885
|
+
font-weight: 700;
|
|
2886
|
+
letter-spacing: 0.14em;
|
|
2887
|
+
text-transform: uppercase;
|
|
2888
|
+
color: var(--text-soft);
|
|
2889
|
+
border: 0.5px solid var(--line-bright, var(--line));
|
|
2890
|
+
padding: 1px 6px;
|
|
2891
|
+
white-space: nowrap;
|
|
2892
|
+
}
|
|
2893
|
+
|
|
2899
2894
|
/* Signatures · folded into the meta line, no top border */
|
|
2900
2895
|
.brief-signed {
|
|
2901
2896
|
display: inline-flex;
|
|
@@ -3206,6 +3201,98 @@
|
|
|
3206
3201
|
}
|
|
3207
3202
|
.brief-stat-fact.brief-stat-live { color: var(--lime); }
|
|
3208
3203
|
|
|
3204
|
+
.brief-llm-trace {
|
|
3205
|
+
border-top: 1px solid var(--line);
|
|
3206
|
+
padding-top: 10px;
|
|
3207
|
+
}
|
|
3208
|
+
.brief-llm-toggle {
|
|
3209
|
+
width: 100%;
|
|
3210
|
+
display: flex;
|
|
3211
|
+
align-items: center;
|
|
3212
|
+
gap: 8px;
|
|
3213
|
+
padding: 7px 0;
|
|
3214
|
+
border: 0;
|
|
3215
|
+
background: transparent;
|
|
3216
|
+
color: var(--text-soft);
|
|
3217
|
+
font-family: var(--mono);
|
|
3218
|
+
font-size: 10px;
|
|
3219
|
+
letter-spacing: 0.08em;
|
|
3220
|
+
text-transform: uppercase;
|
|
3221
|
+
text-align: left;
|
|
3222
|
+
cursor: pointer;
|
|
3223
|
+
}
|
|
3224
|
+
.brief-llm-toggle:hover { color: var(--lime); }
|
|
3225
|
+
.brief-llm-toggle-mark {
|
|
3226
|
+
color: var(--lime);
|
|
3227
|
+
font-weight: 700;
|
|
3228
|
+
}
|
|
3229
|
+
.brief-llm-toggle-meta {
|
|
3230
|
+
margin-left: auto;
|
|
3231
|
+
color: var(--text-faint);
|
|
3232
|
+
font-size: 10px;
|
|
3233
|
+
letter-spacing: 0.04em;
|
|
3234
|
+
text-transform: none;
|
|
3235
|
+
}
|
|
3236
|
+
.brief-llm-panel {
|
|
3237
|
+
display: flex;
|
|
3238
|
+
flex-direction: column;
|
|
3239
|
+
gap: 10px;
|
|
3240
|
+
padding-top: 8px;
|
|
3241
|
+
}
|
|
3242
|
+
.brief-llm-row {
|
|
3243
|
+
padding: 10px 0;
|
|
3244
|
+
border-top: 1px solid var(--line-bright);
|
|
3245
|
+
}
|
|
3246
|
+
.brief-llm-row:first-child { border-top: 0; }
|
|
3247
|
+
.brief-llm-row-head {
|
|
3248
|
+
display: flex;
|
|
3249
|
+
align-items: baseline;
|
|
3250
|
+
justify-content: space-between;
|
|
3251
|
+
gap: 12px;
|
|
3252
|
+
margin-bottom: 7px;
|
|
3253
|
+
}
|
|
3254
|
+
.brief-llm-row-title {
|
|
3255
|
+
font-family: var(--mono);
|
|
3256
|
+
font-size: 10px;
|
|
3257
|
+
letter-spacing: 0.08em;
|
|
3258
|
+
text-transform: uppercase;
|
|
3259
|
+
color: var(--text);
|
|
3260
|
+
}
|
|
3261
|
+
.brief-llm-row.is-running .brief-llm-row-title { color: var(--lime); }
|
|
3262
|
+
.brief-llm-row.is-failed .brief-llm-row-title { color: var(--red); }
|
|
3263
|
+
.brief-llm-row-meta {
|
|
3264
|
+
font-family: var(--mono);
|
|
3265
|
+
font-size: 10px;
|
|
3266
|
+
letter-spacing: 0.04em;
|
|
3267
|
+
color: var(--text-faint);
|
|
3268
|
+
white-space: nowrap;
|
|
3269
|
+
}
|
|
3270
|
+
.brief-llm-error {
|
|
3271
|
+
margin-bottom: 8px;
|
|
3272
|
+
color: var(--red);
|
|
3273
|
+
font-family: var(--mono);
|
|
3274
|
+
font-size: 10px;
|
|
3275
|
+
line-height: 1.5;
|
|
3276
|
+
}
|
|
3277
|
+
.brief-llm-output {
|
|
3278
|
+
max-height: 220px;
|
|
3279
|
+
overflow: auto;
|
|
3280
|
+
margin: 0;
|
|
3281
|
+
padding: 10px;
|
|
3282
|
+
background: var(--panel);
|
|
3283
|
+
border: 1px solid var(--line);
|
|
3284
|
+
color: var(--text-soft);
|
|
3285
|
+
font-family: var(--mono);
|
|
3286
|
+
font-size: 11px;
|
|
3287
|
+
line-height: 1.55;
|
|
3288
|
+
white-space: pre-wrap;
|
|
3289
|
+
word-break: break-word;
|
|
3290
|
+
}
|
|
3291
|
+
.brief-llm-empty {
|
|
3292
|
+
color: var(--text-faint);
|
|
3293
|
+
font-style: italic;
|
|
3294
|
+
}
|
|
3295
|
+
|
|
3209
3296
|
/* Brief error / interrupted state · retry button matches the
|
|
3210
3297
|
supplement-row treatment but uses red accent for "something went
|
|
3211
3298
|
wrong, click to retry" affordance. */
|
|
@@ -7558,6 +7645,12 @@
|
|
|
7558
7645
|
border-color: var(--lime-dim);
|
|
7559
7646
|
}
|
|
7560
7647
|
|
|
7648
|
+
/* Voice mode toggle now reuses `.ap-skill-row-toggle .cmp-ws-toggle`
|
|
7649
|
+
vocabulary (see agent-profile.css). The previous `.cmp-voice-
|
|
7650
|
+
switch` rules are gone — the toolbar fitting in `.cmp-toolbar
|
|
7651
|
+
.cmp-ws-toggle` above already covers the voice toggle since
|
|
7652
|
+
they share the same class set. */
|
|
7653
|
+
|
|
7561
7654
|
.cmp-go {
|
|
7562
7655
|
flex-shrink: 0;
|
|
7563
7656
|
width: 36px; height: 36px;
|
|
@@ -7575,6 +7668,17 @@
|
|
|
7575
7668
|
transform: translateX(1px);
|
|
7576
7669
|
}
|
|
7577
7670
|
.cmp-go.busy { opacity: 0.55; pointer-events: none; }
|
|
7671
|
+
/* SVG icon · both the room composer's "Convene" button and the
|
|
7672
|
+
agent composer's "Generate director" button use the same
|
|
7673
|
+
Lucide `arrow-right` (clean stroke, consistent across fonts).
|
|
7674
|
+
Stroke colour inherits from the button's `color` (var(--bg)),
|
|
7675
|
+
so the icon reads as ink on the lime fill. The legacy
|
|
7676
|
+
`.cmp-go-arrow` text-glyph rule below stays for the `…` busy
|
|
7677
|
+
state and any other text fallbacks. */
|
|
7678
|
+
.cmp-go-icon {
|
|
7679
|
+
display: block;
|
|
7680
|
+
flex-shrink: 0;
|
|
7681
|
+
}
|
|
7578
7682
|
.cmp-go-arrow {
|
|
7579
7683
|
font-size: 16px;
|
|
7580
7684
|
line-height: 1;
|
|
@@ -8119,6 +8223,25 @@
|
|
|
8119
8223
|
text-transform: uppercase;
|
|
8120
8224
|
color: var(--text-faint);
|
|
8121
8225
|
}
|
|
8226
|
+
/* Stop button · sits at the right edge of the generating header.
|
|
8227
|
+
Shares the bracketed mono register the rest of the composer
|
|
8228
|
+
uses ([ Generate ] / [ Cancel ]) so it reads as a peer action.
|
|
8229
|
+
Clicking aborts the in-flight controller via discardAgentSpec. */
|
|
8230
|
+
.ag-gen-stop {
|
|
8231
|
+
font-family: var(--mono);
|
|
8232
|
+
font-size: 10px;
|
|
8233
|
+
font-weight: 700;
|
|
8234
|
+
letter-spacing: 0.04em;
|
|
8235
|
+
color: var(--text-soft);
|
|
8236
|
+
background: transparent;
|
|
8237
|
+
border: 0;
|
|
8238
|
+
padding: 4px 8px;
|
|
8239
|
+
cursor: pointer;
|
|
8240
|
+
align-self: center;
|
|
8241
|
+
transition: color 0.12s;
|
|
8242
|
+
}
|
|
8243
|
+
.ag-gen-stop:hover { color: var(--red, #C85A5A); }
|
|
8244
|
+
.ag-gen-stop:active { color: var(--red, #C85A5A); }
|
|
8122
8245
|
.ag-gen-stages {
|
|
8123
8246
|
list-style: none;
|
|
8124
8247
|
margin: 0;
|
|
@@ -9291,6 +9414,7 @@
|
|
|
9291
9414
|
<link rel="stylesheet" href="agent-overlay.css">
|
|
9292
9415
|
<!-- Shared /api/models cache · loads first so every picker reads the
|
|
9293
9416
|
same source. See models-cache.js for the API surface. -->
|
|
9417
|
+
<script src="i18n.js" defer></script>
|
|
9294
9418
|
<script src="models-cache.js" defer></script>
|
|
9295
9419
|
<script src="agent-overlay.js" defer></script>
|
|
9296
9420
|
<link rel="stylesheet" href="room-settings.css">
|
|
@@ -9303,7 +9427,10 @@
|
|
|
9303
9427
|
<link rel="stylesheet" href="new-agent.css">
|
|
9304
9428
|
<script src="new-agent.js" defer></script>
|
|
9305
9429
|
<link rel="stylesheet" href="user-settings.css">
|
|
9430
|
+
<script type="module" src="keys-store.js"></script>
|
|
9306
9431
|
<script src="user-settings.js" defer></script>
|
|
9432
|
+
<link rel="stylesheet" href="voice-replay.css">
|
|
9433
|
+
<script src="voice-replay.js" defer></script>
|
|
9307
9434
|
<script src="auto-hide-scroll.js" defer></script>
|
|
9308
9435
|
<link rel="stylesheet" href="onboarding.css">
|
|
9309
9436
|
<script src="onboarding.js" defer></script>
|
|
@@ -9336,7 +9463,7 @@
|
|
|
9336
9463
|
<div class="sys-notice" data-sys-notice hidden>
|
|
9337
9464
|
<span class="sys-notice-mark">▸</span>
|
|
9338
9465
|
<span class="sys-notice-text" data-sys-notice-text></span>
|
|
9339
|
-
<button type="button" class="sys-notice-close" data-sys-notice-close aria-label="Dismiss">✕</button>
|
|
9466
|
+
<button type="button" class="sys-notice-close" data-sys-notice-close data-i18n-aria="sys_notice_dismiss" aria-label="Dismiss">✕</button>
|
|
9340
9467
|
</div>
|
|
9341
9468
|
|
|
9342
9469
|
<div class="control">
|
|
@@ -9364,20 +9491,20 @@
|
|
|
9364
9491
|
<rect class="fg" x="2" y="10" width="1" height="4"/>
|
|
9365
9492
|
<rect class="fg" x="13" y="10" width="1" height="4"/>
|
|
9366
9493
|
</svg>
|
|
9367
|
-
<span class="sidebar-brand-name">PRIVATEBOARD</span>
|
|
9494
|
+
<span class="sidebar-brand-name" data-i18n="brand_privateboard">PRIVATEBOARD</span>
|
|
9368
9495
|
</a>
|
|
9369
|
-
<button type="button" class="sidebar-collapse-btn" data-sidebar-collapse aria-label="Collapse sidebar" title="Collapse sidebar"></button>
|
|
9496
|
+
<button type="button" class="sidebar-collapse-btn" data-sidebar-collapse data-i18n-title="sidebar_collapse" aria-label="Collapse sidebar" title="Collapse sidebar"></button>
|
|
9370
9497
|
</div>
|
|
9371
9498
|
|
|
9372
9499
|
<div class="sidebar-tabs" role="tablist">
|
|
9373
9500
|
<a href="#" class="sidebar-tab active" data-sidebar-tab="rooms" role="tab" aria-selected="true">
|
|
9374
9501
|
<i class="ic ic-rooms ico"></i>
|
|
9375
|
-
<span>Rooms</span>
|
|
9502
|
+
<span data-i18n="sidebar_tab_rooms">Rooms</span>
|
|
9376
9503
|
<span class="count" data-sidebar-tab-count="rooms">0</span>
|
|
9377
9504
|
</a>
|
|
9378
9505
|
<a href="#" class="sidebar-tab" data-sidebar-tab="agents" role="tab" aria-selected="false">
|
|
9379
9506
|
<i class="ic ic-agents ico"></i>
|
|
9380
|
-
<span>Agents</span>
|
|
9507
|
+
<span data-i18n="sidebar_tab_agents">Agents</span>
|
|
9381
9508
|
<span class="count" data-sidebar-tab-count="agents">0</span>
|
|
9382
9509
|
</a>
|
|
9383
9510
|
</div>
|
|
@@ -9385,14 +9512,14 @@
|
|
|
9385
9512
|
<!-- ─── ROOMS PANEL ─── -->
|
|
9386
9513
|
<div class="sidebar-panel" data-sidebar-panel="rooms">
|
|
9387
9514
|
|
|
9388
|
-
<a href="#convene" class="new-btn" data-convene-trigger>New room</a>
|
|
9515
|
+
<a href="#convene" class="new-btn" data-convene-trigger data-i18n="sidebar_new_room">New room</a>
|
|
9389
9516
|
<!-- "All Reports" cross-room index · opens a Perplexity-Discover-
|
|
9390
9517
|
style grid of every brief any room has produced. Sits below
|
|
9391
9518
|
"New room" so it reads as a peer destination, not a list
|
|
9392
9519
|
item, with a square glyph echoing the dashboard glyphs
|
|
9393
9520
|
elsewhere in the sidebar. -->
|
|
9394
9521
|
<a href="#" class="new-btn nav-reports" data-reports-trigger>
|
|
9395
|
-
<span class="nav-label">All Reports</span>
|
|
9522
|
+
<span class="nav-label" data-i18n="sidebar_all_reports">All Reports</span>
|
|
9396
9523
|
<span class="nav-count" data-reports-count hidden></span>
|
|
9397
9524
|
</a>
|
|
9398
9525
|
<!-- "All Notes" · chairman's-notes index. Sits directly below
|
|
@@ -9402,7 +9529,7 @@
|
|
|
9402
9529
|
Reports' FileText glyph. The data-notes-count badge updates
|
|
9403
9530
|
live as new notes are saved or deleted. -->
|
|
9404
9531
|
<a href="#" class="new-btn nav-notes" data-notes-trigger>
|
|
9405
|
-
<span class="nav-label">All Notes</span>
|
|
9532
|
+
<span class="nav-label" data-i18n="sidebar_all_notes">All Notes</span>
|
|
9406
9533
|
<span class="nav-count" data-notes-count hidden></span>
|
|
9407
9534
|
</a>
|
|
9408
9535
|
|
|
@@ -9415,7 +9542,7 @@
|
|
|
9415
9542
|
<!-- ─── AGENTS PANEL ─── -->
|
|
9416
9543
|
<div class="sidebar-panel" data-sidebar-panel="agents" hidden>
|
|
9417
9544
|
|
|
9418
|
-
<a href="#" class="new-btn" data-agent-composer-trigger>New agent</a>
|
|
9545
|
+
<a href="#" class="new-btn" data-agent-composer-trigger data-i18n="sidebar_new_agent">New agent</a>
|
|
9419
9546
|
|
|
9420
9547
|
<div class="agents-scroll">
|
|
9421
9548
|
<!-- Rendered dynamically by app.renderSidebarAgents() from
|
|
@@ -9429,16 +9556,16 @@
|
|
|
9429
9556
|
<div class="user-av" data-user-avatar>K</div>
|
|
9430
9557
|
<div class="user-info">
|
|
9431
9558
|
<div class="user-name" data-user-name>—</div>
|
|
9432
|
-
<div class="user-meta" data-user-meta>// host</div>
|
|
9559
|
+
<div class="user-meta" data-user-meta data-i18n="sidebar_host">// host</div>
|
|
9433
9560
|
</div>
|
|
9434
9561
|
</a>
|
|
9435
|
-
<a href="#" class="icon-btn" title="
|
|
9562
|
+
<a href="#" class="icon-btn" data-i18n-title="settings_title" data-user-settings-trigger>⚙</a>
|
|
9436
9563
|
</div>
|
|
9437
9564
|
|
|
9438
9565
|
</aside>
|
|
9439
9566
|
|
|
9440
9567
|
<!-- Resizable handle: sidebar | main -->
|
|
9441
|
-
<div class="col-resizer" data-resize data-var="--sidebar-w" data-side="left" data-min="220" data-max="480" title="
|
|
9568
|
+
<div class="col-resizer" data-resize data-var="--sidebar-w" data-side="left" data-min="220" data-max="480" data-i18n-title="col_resize"></div>
|
|
9442
9569
|
|
|
9443
9570
|
<!-- ═══════════════ MAIN: room view + agent profile view ═══════════════ -->
|
|
9444
9571
|
<main class="main">
|
|
@@ -9461,20 +9588,20 @@
|
|
|
9461
9588
|
<div class="speaking-queue" id="speaking-queue">
|
|
9462
9589
|
<div class="queue-head">
|
|
9463
9590
|
<div class="queue-head-expanded">
|
|
9464
|
-
<span class="label">speaking queue</span>
|
|
9465
|
-
<span class="mode-tag">mode
|
|
9591
|
+
<span class="label" data-i18n="queue_speaking_queue">speaking queue</span>
|
|
9592
|
+
<span class="mode-tag"><span data-i18n="queue_mode">mode:</span> <span class="v">constructive</span></span>
|
|
9466
9593
|
</div>
|
|
9467
9594
|
<div class="queue-head-collapsed" data-queue-collapsed>
|
|
9468
9595
|
<!-- populated by app.renderQueue() · summary follows
|
|
9469
9596
|
the real queue: speaking, pending, or idle. -->
|
|
9470
9597
|
</div>
|
|
9471
|
-
<button type="button" class="queue-toggle" title="
|
|
9598
|
+
<button type="button" class="queue-toggle" data-i18n-title="queue_toggle" aria-expanded="true">
|
|
9472
9599
|
<span class="chev">▼</span>
|
|
9473
9600
|
</button>
|
|
9474
9601
|
</div>
|
|
9475
9602
|
<ul class="queue-list" data-queue-list></ul>
|
|
9476
9603
|
<div class="queue-foot" data-queue-foot>
|
|
9477
|
-
<span class="queue-foot-hint" data-queue-foot-hint>type to interject · <kbd>@</kbd> to direct · the chair files the round-end vote in chat</span>
|
|
9604
|
+
<span class="queue-foot-hint" data-queue-foot-hint data-i18n-html="queue_foot_hint">type to interject · <kbd>@</kbd> to direct · the chair files the round-end vote in chat</span>
|
|
9478
9605
|
</div>
|
|
9479
9606
|
</div>
|
|
9480
9607
|
|
|
@@ -9487,12 +9614,12 @@
|
|
|
9487
9614
|
brackets) but at a tighter scale. Adjourn turns warn
|
|
9488
9615
|
on hover to flag its destructiveness. -->
|
|
9489
9616
|
<div class="input-bar-actions">
|
|
9490
|
-
<button type="button" class="ib-action ib-pause" data-pause
|
|
9491
|
-
<button type="button" class="ib-action ib-adjourn" data-adjourn
|
|
9617
|
+
<button type="button" class="ib-action ib-pause" data-pause data-i18n-title="ib_pause_tip" data-i18n-aria="ib_pause_label" data-i18n-tip="ib_pause_tip"></button>
|
|
9618
|
+
<button type="button" class="ib-action ib-adjourn" data-adjourn data-i18n-title="ib_adjourn_tip" data-i18n-aria="ib_adjourn_label" data-i18n-tip="ib_adjourn_tip"></button>
|
|
9492
9619
|
</div>
|
|
9493
9620
|
<div class="input-wrap">
|
|
9494
|
-
<input type="text" placeholder="interject anytime · @first_p to direct a director..." data-send-input>
|
|
9495
|
-
<button class="send-btn" data-send-button>[ Send ]</button>
|
|
9621
|
+
<input type="text" data-i18n-placeholder="send_placeholder" placeholder="interject anytime · @first_p to direct a director..." data-send-input>
|
|
9622
|
+
<button class="send-btn" data-send-button data-i18n="send_button">[ Send ]</button>
|
|
9496
9623
|
</div>
|
|
9497
9624
|
</footer>
|
|
9498
9625
|
|
|
@@ -9512,6 +9639,7 @@
|
|
|
9512
9639
|
</div>
|
|
9513
9640
|
<div class="adjourned-bar-actions">
|
|
9514
9641
|
<a href="#" class="ghost-btn" data-room-export>[↓] Export</a>
|
|
9642
|
+
<a href="#" class="ghost-btn" data-room-replay>[♪] Voice Replay</a>
|
|
9515
9643
|
<a href="#" class="ghost-btn" data-room-followup>[→] Convene Follow-up</a>
|
|
9516
9644
|
</div>
|
|
9517
9645
|
</footer>
|
|
@@ -9546,7 +9674,7 @@
|
|
|
9546
9674
|
below the topbar / brand logo, where the sidebar's own header
|
|
9547
9675
|
sat before collapse. Frosted black glass with a 3-line
|
|
9548
9676
|
hamburger glyph. -->
|
|
9549
|
-
<button type="button" class="sidebar-expand-btn" data-sidebar-expand
|
|
9677
|
+
<button type="button" class="sidebar-expand-btn" data-sidebar-expand data-i18n-title="sidebar_expand">
|
|
9550
9678
|
<svg viewBox="0 0 16 16" width="16" height="16" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" aria-hidden="true">
|
|
9551
9679
|
<line x1="2.5" y1="4.25" x2="13.5" y2="4.25"/>
|
|
9552
9680
|
<line x1="2.5" y1="8" x2="13.5" y2="8"/>
|
|
@@ -9795,17 +9923,37 @@
|
|
|
9795
9923
|
const lsGet = (k) => { try { return localStorage.getItem(k); } catch (_) { return null; } };
|
|
9796
9924
|
const lsSet = (k, v) => { try { localStorage.setItem(k, v); } catch (_) {} };
|
|
9797
9925
|
|
|
9798
|
-
/*
|
|
9799
|
-
|
|
9800
|
-
|
|
9801
|
-
|
|
9802
|
-
|
|
9803
|
-
|
|
9804
|
-
|
|
9805
|
-
|
|
9926
|
+
/* Truth source for "does this room exist?" is `window.app.rooms`,
|
|
9927
|
+
not the DOM. The sidebar may not have rendered yet on first
|
|
9928
|
+
restore (app.init's loadInitial is async and runs concurrently
|
|
9929
|
+
with our DOMContentLoaded restore), and a DOM-empty sidebar
|
|
9930
|
+
does NOT mean "user has no rooms." Querying the in-memory
|
|
9931
|
+
array avoids the race entirely. Returns null until app data is
|
|
9932
|
+
loaded, in which case the caller defers. */
|
|
9933
|
+
function appRoomsLoaded() {
|
|
9934
|
+
return !!(window.app && Array.isArray(window.app.rooms));
|
|
9935
|
+
}
|
|
9936
|
+
function appRoomExists(id) {
|
|
9937
|
+
return appRoomsLoaded() && window.app.rooms.some((r) => r && r.id === id);
|
|
9938
|
+
}
|
|
9939
|
+
function appFirstRoomId() {
|
|
9940
|
+
if (!appRoomsLoaded()) return null;
|
|
9941
|
+
const live = window.app.rooms.filter((r) => r.status === "live");
|
|
9942
|
+
const paused = window.app.rooms.filter((r) => r.status === "paused");
|
|
9943
|
+
const adj = window.app.rooms.filter((r) => r.status === "adjourned");
|
|
9944
|
+
const ordered = [...live, ...paused, ...adj];
|
|
9945
|
+
return ordered.length > 0 ? ordered[0].id : null;
|
|
9806
9946
|
}
|
|
9807
9947
|
|
|
9948
|
+
/* applyRoomsSubState may run BEFORE app.init's loadInitial fetch
|
|
9949
|
+
completes — in that case window.app.rooms is undefined and any
|
|
9950
|
+
existence check is a lie. Defer with a short retry until data
|
|
9951
|
+
lands; only then apply the sub-state. The retry is gated by a
|
|
9952
|
+
generation token so a later applyRoomsSubState call cancels
|
|
9953
|
+
any in-flight retry from an earlier one. */
|
|
9954
|
+
let _applyGen = 0;
|
|
9808
9955
|
function applyRoomsSubState(sub) {
|
|
9956
|
+
const gen = ++_applyGen;
|
|
9809
9957
|
// Make sure the room main view is visible (in case the agent
|
|
9810
9958
|
// profile main view was up). setComposerMode also does this when
|
|
9811
9959
|
// sub === "new"; doing it here covers the roomId branch too.
|
|
@@ -9833,22 +9981,30 @@
|
|
|
9833
9981
|
}
|
|
9834
9982
|
return;
|
|
9835
9983
|
}
|
|
9836
|
-
//
|
|
9837
|
-
//
|
|
9838
|
-
//
|
|
9839
|
-
//
|
|
9840
|
-
|
|
9841
|
-
|
|
9842
|
-
|
|
9843
|
-
|
|
9984
|
+
// Saved sub is a roomId. We must consult the in-memory data
|
|
9985
|
+
// (not DOM); if it isn't loaded yet, defer rather than blow
|
|
9986
|
+
// away ROOMS_KEY with "new" — that's the bug that was eating
|
|
9987
|
+
// the user's last selection on every refresh.
|
|
9988
|
+
if (!appRoomsLoaded()) {
|
|
9989
|
+
let attempts = 0;
|
|
9990
|
+
const tick = () => {
|
|
9991
|
+
if (gen !== _applyGen) return; // a newer call superseded us
|
|
9992
|
+
if (appRoomsLoaded()) { applyRoomsSubState(sub); return; }
|
|
9993
|
+
if (++attempts < 40) setTimeout(tick, 100); // up to ~4s
|
|
9994
|
+
};
|
|
9995
|
+
setTimeout(tick, 100);
|
|
9996
|
+
return;
|
|
9997
|
+
}
|
|
9998
|
+
// Data loaded · resolve existence against window.app.rooms.
|
|
9999
|
+
// Stale id (deleted, never-existed) falls back to last-opened,
|
|
10000
|
+
// then first row, before resorting to the composer.
|
|
10001
|
+
let resolved = sub;
|
|
10002
|
+
if (!appRoomExists(resolved)) {
|
|
9844
10003
|
const last = lsGet(ROOMS_LAST_KEY);
|
|
9845
|
-
const candidate = (last &&
|
|
9846
|
-
? last
|
|
9847
|
-
: firstRoomId();
|
|
10004
|
+
const candidate = (last && appRoomExists(last)) ? last : appFirstRoomId();
|
|
9848
10005
|
if (candidate) {
|
|
9849
|
-
|
|
10006
|
+
resolved = candidate;
|
|
9850
10007
|
lsSet(ROOMS_KEY, candidate);
|
|
9851
|
-
exists = true;
|
|
9852
10008
|
} else {
|
|
9853
10009
|
lsSet(ROOMS_KEY, "new");
|
|
9854
10010
|
if (window.app && typeof window.app.setComposerMode === "function") {
|
|
@@ -9857,17 +10013,17 @@
|
|
|
9857
10013
|
return;
|
|
9858
10014
|
}
|
|
9859
10015
|
}
|
|
9860
|
-
const want = "#/r/" +
|
|
10016
|
+
const want = "#/r/" + resolved;
|
|
9861
10017
|
if (location.hash !== want) {
|
|
9862
10018
|
location.hash = want;
|
|
9863
|
-
} else if (window.app && window.app.currentRoomId !==
|
|
9864
|
-
window.app.openRoom(
|
|
10019
|
+
} else if (window.app && window.app.currentRoomId !== resolved && typeof window.app.openRoom === "function") {
|
|
10020
|
+
window.app.openRoom(resolved);
|
|
9865
10021
|
} else if (window.app && typeof window.app.markActiveRoom === "function") {
|
|
9866
10022
|
// Both hash and currentRoomId already match — but the sidebar
|
|
9867
10023
|
// session-row .active highlight may have been cleared by an
|
|
9868
10024
|
// intervening agent-profile open (markActiveAgent wipes it).
|
|
9869
10025
|
// Re-apply so the row reads as selected on tab return.
|
|
9870
|
-
window.app.markActiveRoom(
|
|
10026
|
+
window.app.markActiveRoom(resolved);
|
|
9871
10027
|
}
|
|
9872
10028
|
}
|
|
9873
10029
|
|
|
@@ -9928,12 +10084,13 @@
|
|
|
9928
10084
|
} else if (fromTabClick) {
|
|
9929
10085
|
// No saved sub-state · fresh user clicking the tab. Prefer
|
|
9930
10086
|
// the last actually-opened room so the tab feels populated
|
|
9931
|
-
// instead of bouncing them to a blank composer.
|
|
10087
|
+
// instead of bouncing them to a blank composer. Truth source
|
|
10088
|
+
// is window.app.rooms (DOM may not have rendered yet).
|
|
9932
10089
|
const last = lsGet(ROOMS_LAST_KEY);
|
|
9933
|
-
if (last &&
|
|
10090
|
+
if (last && appRoomExists(last)) {
|
|
9934
10091
|
target = last;
|
|
9935
10092
|
} else {
|
|
9936
|
-
target =
|
|
10093
|
+
target = appFirstRoomId() || "new";
|
|
9937
10094
|
}
|
|
9938
10095
|
} else {
|
|
9939
10096
|
target = "new"; // initial load with no history → composer
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* keys-store.js · canonical key-state module
|
|
3
|
+
*
|
|
4
|
+
* Loaded as <script type="module"> so it can be imported by vitest
|
|
5
|
+
* directly for unit-testing without a browser. The module also writes
|
|
6
|
+
* itself onto window.keysStore so the non-module user-settings.js IIFE
|
|
7
|
+
* can delegate to it at runtime.
|
|
8
|
+
*
|
|
9
|
+
* State lives HERE — user-settings.js must not maintain a parallel copy.
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
export let keysMeta = {};
|
|
13
|
+
|
|
14
|
+
/** Rebuild keysMeta from the server. Replaces the whole object so callers
|
|
15
|
+
* that hold a reference to the old snapshot see stale data — always read
|
|
16
|
+
* via the exported binding, not a captured variable. */
|
|
17
|
+
export async function fetchKeyMeta() {
|
|
18
|
+
try {
|
|
19
|
+
const r = await fetch("/api/keys");
|
|
20
|
+
if (!r.ok) return;
|
|
21
|
+
const data = await r.json();
|
|
22
|
+
const next = {};
|
|
23
|
+
for (const row of (data.keys || [])) next[row.provider] = row;
|
|
24
|
+
keysMeta = next;
|
|
25
|
+
} catch { /* keep last snapshot on network failure */ }
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/** PUT (non-empty value) or DELETE (empty/whitespace) a single key.
|
|
29
|
+
* Mirrors the resulting meta into keysMeta and returns it, or null on
|
|
30
|
+
* error. */
|
|
31
|
+
export async function setProviderKey(provider, value) {
|
|
32
|
+
try {
|
|
33
|
+
const trimmed = (value || "").trim();
|
|
34
|
+
const r = await fetch("/api/keys/" + encodeURIComponent(provider), {
|
|
35
|
+
method: trimmed ? "PUT" : "DELETE",
|
|
36
|
+
headers: trimmed ? { "content-type": "application/json" } : undefined,
|
|
37
|
+
body: trimmed ? JSON.stringify({ key: trimmed }) : undefined,
|
|
38
|
+
});
|
|
39
|
+
if (!r.ok) return null;
|
|
40
|
+
const meta = await r.json();
|
|
41
|
+
keysMeta[provider] = meta;
|
|
42
|
+
return meta;
|
|
43
|
+
} catch { return null; }
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/** Returns only the configured providers (existence-check safe). */
|
|
47
|
+
export function getConfiguredKeys() {
|
|
48
|
+
const out = {};
|
|
49
|
+
for (const [p, m] of Object.entries(keysMeta)) {
|
|
50
|
+
if (m && m.configured) out[p] = m;
|
|
51
|
+
}
|
|
52
|
+
return out;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// Expose globally for non-module scripts (user-settings.js IIFE).
|
|
56
|
+
if (typeof window !== "undefined") {
|
|
57
|
+
window.keysStore = {
|
|
58
|
+
get keysMeta() { return keysMeta; },
|
|
59
|
+
fetchKeyMeta,
|
|
60
|
+
setProviderKey,
|
|
61
|
+
getConfiguredKeys,
|
|
62
|
+
};
|
|
63
|
+
}
|