privateboard 0.1.0 → 0.1.3

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.
@@ -84,9 +84,9 @@
84
84
  {
85
85
  tag: "// pivot",
86
86
  text: "Six months of runway, real users but flat MRR — pivot the product, hold the line, or shut it down?",
87
- hint: "no-mercy room: force the load-bearing claim into the open",
88
- tone: "no-mercy",
89
- intensity: "brutal",
87
+ hint: "critique room: each director audits the plan blocker / major / minor",
88
+ tone: "critique",
89
+ intensity: "sharp",
90
90
  briefStyle: "auto",
91
91
  agents: ["socrates", "first-principles", "long-horizon"],
92
92
  },
@@ -112,9 +112,7 @@
112
112
  // ── Provider catalogue ─────────────────────────────────
113
113
  // Model providers shown on step 2. OpenRouter leads — it's the
114
114
  // universal router that unlocks every model from a single key, so
115
- // it's the lowest-friction first stop for new users. Anthropic
116
- // (Claude) is temporarily withheld; bring it back when the
117
- // direct-Anthropic flow is ready.
115
+ // it's the lowest-friction first stop for new users.
118
116
  // `slug` matches /api/keys/{slug} on the backend.
119
117
  const KEY_PROVIDERS = [
120
118
  {
@@ -125,6 +123,14 @@
125
123
  help: "openrouter.ai/keys",
126
124
  helpUrl: "https://openrouter.ai/keys",
127
125
  },
126
+ {
127
+ slug: "anthropic",
128
+ label: "Claude",
129
+ sub: "Anthropic",
130
+ placeholder: "sk-ant-…",
131
+ help: "console.anthropic.com",
132
+ helpUrl: "https://console.anthropic.com/settings/keys",
133
+ },
128
134
  {
129
135
  slug: "openai",
130
136
  label: "ChatGPT",
@@ -151,6 +157,7 @@
151
157
  * and the Next-button enable state. */
152
158
  let providerConfigured = {
153
159
  openrouter: false,
160
+ anthropic: false,
154
161
  openai: false,
155
162
  google: false,
156
163
  };
@@ -403,7 +410,7 @@
403
410
  <div class="onb-field">
404
411
  <div class="onb-field-label" data-onb-field-label>${escape(active.label)} API key</div>
405
412
  <div class="onb-input-wrap">
406
- <input class="onb-input" data-onb-key type="password" placeholder="${escape(active.placeholder)}" autocomplete="off" spellcheck="false" value="${escape(inputValue)}">
413
+ <input class="onb-input" data-onb-key type="password" placeholder="${escape(active.placeholder)}" autocomplete="one-time-code" data-lpignore="true" data-1p-ignore="true" data-form-type="other" spellcheck="false" value="${escape(inputValue)}">
407
414
  <button type="button" class="onb-input-reveal" data-onb-reveal aria-label="Show key" aria-pressed="false">show</button>
408
415
  </div>
409
416
  ${status}
@@ -598,7 +605,23 @@
598
605
  if (typeof window.boardroomModelsRefresh === "function") {
599
606
  refreshes.push(Promise.resolve(window.boardroomModelsRefresh()).catch(() => {}));
600
607
  }
601
- Promise.all(refreshes).finally(() => { if (continuation) continuation(); });
608
+ Promise.all(refreshes).finally(() => {
609
+ if (continuation) {
610
+ continuation();
611
+ } else {
612
+ // Default skip path · the user dismissed onboarding without
613
+ // picking a starter or convene-your-own. Explicitly land
614
+ // them on the new-room composer. Without this, whatever
615
+ // composer mode the dashboard happened to settle into during
616
+ // boot stays put — and on first-run flows that's
617
+ // occasionally "agent" instead of "room", since the order
618
+ // of restore() / app.init() / refreshAgents isn't strictly
619
+ // guaranteed and the agents-tab restorer can win the race.
620
+ if (window.app && typeof window.app.setComposerMode === "function") {
621
+ window.app.setComposerMode("room");
622
+ }
623
+ }
624
+ });
602
625
  }
603
626
 
604
627
  async function createDemoRoom(spec) {
@@ -630,11 +653,15 @@
630
653
  function openConveneAfter() {
631
654
  setTimeout(() => {
632
655
  // Convene-overlay was retired in favour of the inline composer.
633
- // Fall back to closing the active room so the composer shows; if
634
- // app.closeRoom isn't ready yet, no-op (the user is on the
635
- // dashboard already and will see it on next interaction).
656
+ // Use setComposerMode("room") (not closeRoom) so we explicitly
657
+ // pin the new-room composer · closeRoom inherits whatever
658
+ // composerMode is set, and during a boot race that flag can be
659
+ // "agent", which would land the user on the new-agent composer
660
+ // instead of the new-room one.
636
661
  try {
637
- if (window.app && typeof window.app.closeRoom === "function") {
662
+ if (window.app && typeof window.app.setComposerMode === "function") {
663
+ window.app.setComposerMode("room");
664
+ } else if (window.app && typeof window.app.closeRoom === "function") {
638
665
  window.app.closeRoom();
639
666
  } else if (typeof window.openConveneOverlay === "function") {
640
667
  window.openConveneOverlay();
@@ -729,7 +756,7 @@
729
756
 
730
757
  function show() {
731
758
  if (document.getElementById("onb-overlay")) return;
732
- // Claim the dashboard sub-state · prototype-dashboard.html runs a
759
+ // Claim the dashboard sub-state · the dashboard page runs a
733
760
  // restore tick (~2.5s of 250ms retries) that re-opens whatever
734
761
  // agent profile the user last viewed once refreshAgents mounts the
735
762
  // sidebar rows. Onboarding always lands the user on a fresh room
@@ -0,0 +1,269 @@
1
+ /* ═══════════════════════════════════════════
2
+ QUOTE CTA · selection-driven follow-up
3
+ ═══════════════════════════════════════════
4
+ When the user selects text inside a director's message bubble,
5
+ a small floating bar appears above the selection with three
6
+ actions: Probe (opens an overlay), Second (one-click), or Save
7
+ (bookmark to chairman's notes). Probe / Second produce a user
8
+ message that quotes the snippet via markdown blockquote; Save
9
+ is a personal bookmark with no room interaction.
10
+
11
+ Visual vocabulary mirrors the rest of the app: panel-2 surface,
12
+ lime accent, mono micro-type for the action labels. Per the
13
+ no-coloured-left-borders rule, callouts use top tags + indent
14
+ instead of border-left treatments. */
15
+
16
+ /* Floating action bar · two segmented buttons above the selection.
17
+ Restrained surface · panel-2 base, neutral hairline border, soft
18
+ shadow for elevation. No lime border, no notch · the bar is just
19
+ text-and-icon, not a feature. Inter-button divider is structural
20
+ border-right between adjacent items, not a callout treatment. */
21
+ .qcta {
22
+ position: absolute;
23
+ z-index: 1400;
24
+ display: none;
25
+ background: var(--panel-2, #1A1A18);
26
+ border: 0.5px solid var(--line-strong, #3A3A35);
27
+ font-family: var(--mono, "Inter", system-ui, sans-serif);
28
+ box-shadow: 0 4px 14px -6px rgba(0, 0, 0, 0.55);
29
+ white-space: nowrap;
30
+ user-select: none;
31
+ }
32
+ .qcta.open { display: inline-flex; align-items: stretch; }
33
+
34
+ .qcta-btn {
35
+ appearance: none;
36
+ background: transparent;
37
+ border: 0;
38
+ color: var(--text, #C8C5BE);
39
+ font-family: inherit;
40
+ font-size: 12px;
41
+ font-weight: 500;
42
+ letter-spacing: 0;
43
+ padding: 7px 13px;
44
+ cursor: pointer;
45
+ display: inline-flex;
46
+ align-items: center;
47
+ gap: 6px;
48
+ transition: color 0.12s;
49
+ }
50
+ /* Tighten the inner edge of each button so the two CTAs sit close
51
+ together. Outer edges keep their 13px breathing room against the
52
+ bar border. */
53
+ .qcta-btn:not(:last-child) { padding-right: 6px; }
54
+ .qcta-btn:not(:first-child) { padding-left: 6px; }
55
+ .qcta-btn:hover { color: var(--lime, #6FB572); }
56
+ .qcta-btn:hover .ico { color: var(--lime, #6FB572); }
57
+ .qcta-btn .ico {
58
+ display: inline-flex;
59
+ align-items: center;
60
+ font-size: 12.5px;
61
+ font-weight: 500;
62
+ line-height: 1;
63
+ color: var(--text-soft, #8E8B83);
64
+ transition: color 0.12s;
65
+ }
66
+ .qcta-btn .ico svg { display: block; }
67
+
68
+ /* Read-only hint · shown only when the bar is in `qcta-readonly`
69
+ state (adjourned room). The buttons collapse out of the row and
70
+ the hint takes their place — the bar still pops up to acknowledge
71
+ the user's selection, but explains why probe / second can't fire. */
72
+ .qcta-hint {
73
+ display: none;
74
+ font-family: var(--mono, "Inter", system-ui, sans-serif);
75
+ font-size: 11px;
76
+ font-weight: 500;
77
+ letter-spacing: 0.04em;
78
+ color: var(--text-soft, #8E8B83);
79
+ padding: 8px 13px;
80
+ white-space: nowrap;
81
+ }
82
+ /* Adjourned-room state · Probe / Second hide (they post to a
83
+ closed room), but Save stays — bookmarking from a finished
84
+ session is a primary use case. */
85
+ .qcta.qcta-readonly .qcta-btn:not(.qcta-btn-save) { display: none; }
86
+ .qcta.qcta-readonly .qcta-hint { display: inline-flex; align-items: center; }
87
+
88
+ /* ─── Save toast ─────────────────────────────────────────────
89
+ Lightweight feedback after a successful POST /api/notes (or a
90
+ failure). Bottom-center anchored, fades in/out. Lime for ok,
91
+ red-tinted for error. Click to dismiss early. */
92
+ .qcta-toast {
93
+ position: fixed;
94
+ bottom: 24px;
95
+ left: 50%;
96
+ transform: translate(-50%, 8px);
97
+ z-index: 1600;
98
+ background: var(--panel, #131312);
99
+ border: 0.5px solid var(--lime, #6FB572);
100
+ color: var(--text, #C8C5BE);
101
+ font-family: var(--mono, "Inter", system-ui, sans-serif);
102
+ font-size: 11.5px;
103
+ font-weight: 500;
104
+ letter-spacing: 0.04em;
105
+ padding: 9px 16px;
106
+ pointer-events: none;
107
+ opacity: 0;
108
+ transition: opacity 0.16s ease-out, transform 0.16s ease-out;
109
+ white-space: nowrap;
110
+ box-shadow: 0 14px 30px -14px rgba(0, 0, 0, 0.55);
111
+ }
112
+ .qcta-toast.open {
113
+ opacity: 1;
114
+ transform: translate(-50%, 0);
115
+ pointer-events: auto;
116
+ cursor: pointer;
117
+ }
118
+ .qcta-toast.kind-error {
119
+ border-color: #d36a6a;
120
+ color: #f0bdbd;
121
+ }
122
+ .qcta-toast.kind-ok::before {
123
+ content: "✓ ";
124
+ color: var(--lime, #6FB572);
125
+ font-weight: 700;
126
+ }
127
+
128
+ /* Ask-follow-up overlay · backdrop + modal. Same chrome family
129
+ as openSendChoiceModal (.pc-overlay) so the family stays coherent. */
130
+ .qask-overlay {
131
+ position: fixed;
132
+ inset: 0;
133
+ background: rgba(8, 8, 8, 0.5);
134
+ -webkit-backdrop-filter: blur(8px) saturate(1.05);
135
+ backdrop-filter: blur(8px) saturate(1.05);
136
+ z-index: 1500;
137
+ display: flex;
138
+ align-items: center;
139
+ justify-content: center;
140
+ padding: 24px;
141
+ animation: qask-fade 0.16s ease-out;
142
+ }
143
+ @keyframes qask-fade { from { opacity: 0; } to { opacity: 1; } }
144
+
145
+ .qask-modal {
146
+ width: 100%;
147
+ max-width: 560px;
148
+ background: var(--panel, #131312);
149
+ border: 0.5px solid var(--lime, #6FB572);
150
+ color: var(--text, #C8C5BE);
151
+ display: flex;
152
+ flex-direction: column;
153
+ box-shadow: 0 30px 60px -20px rgba(0, 0, 0, 0.65);
154
+ animation: qask-rise 0.2s ease-out;
155
+ }
156
+ @keyframes qask-rise {
157
+ from { transform: translateY(8px); opacity: 0; }
158
+ to { transform: translateY(0); opacity: 1; }
159
+ }
160
+
161
+ .qask-classification {
162
+ padding: 8px 14px;
163
+ border-bottom: 0.5px solid var(--line-bright, #2A2A26);
164
+ font-family: var(--mono, "Inter", system-ui, sans-serif);
165
+ font-size: 9px;
166
+ letter-spacing: 0.18em;
167
+ text-transform: uppercase;
168
+ color: var(--text-faint, #3A382F);
169
+ display: flex;
170
+ justify-content: space-between;
171
+ align-items: center;
172
+ background: var(--panel-2, #1A1A18);
173
+ }
174
+ .qask-classification .dot { color: var(--lime, #6FB572); }
175
+ .qask-classification .right { color: var(--text-faint, #3A382F); }
176
+
177
+ .qask-body { padding: 18px 18px 14px; display: flex; flex-direction: column; gap: 12px; }
178
+
179
+ /* Quote callout · top tag + indented italic body. NO left-border
180
+ treatment per the project-wide rule. */
181
+ .qask-quote {
182
+ background: var(--panel-2, #1A1A18);
183
+ padding: 12px 14px;
184
+ display: flex;
185
+ flex-direction: column;
186
+ gap: 6px;
187
+ max-height: 180px;
188
+ overflow-y: auto;
189
+ }
190
+ .qask-quote-tag {
191
+ font-family: var(--mono, "Inter", system-ui, sans-serif);
192
+ font-size: 9px;
193
+ letter-spacing: 0.18em;
194
+ text-transform: uppercase;
195
+ color: var(--text-faint, #3A382F);
196
+ font-weight: 700;
197
+ }
198
+ .qask-quote-body {
199
+ font-size: 12.5px;
200
+ line-height: 1.55;
201
+ color: var(--text-soft, #8E8B83);
202
+ font-style: italic;
203
+ white-space: pre-wrap;
204
+ word-break: break-word;
205
+ }
206
+
207
+ .qask-input-wrap {
208
+ border: 0.5px solid var(--line-strong, #3A3A35);
209
+ background: var(--bg, #0A0A0A);
210
+ display: flex;
211
+ align-items: stretch;
212
+ transition: border-color 0.12s;
213
+ }
214
+ .qask-input-wrap:focus-within { border-color: var(--lime, #6FB572); }
215
+ .qask-input-wrap::before {
216
+ content: ">";
217
+ color: var(--lime, #6FB572);
218
+ font-weight: 700;
219
+ font-size: 13px;
220
+ font-family: var(--mono, "Inter", system-ui, sans-serif);
221
+ padding: 9px 0 0 11px;
222
+ align-self: flex-start;
223
+ }
224
+ .qask-input {
225
+ flex: 1;
226
+ border: none;
227
+ background: transparent;
228
+ font-family: var(--font-human, var(--mono));
229
+ font-size: 13.5px;
230
+ color: var(--text, #C8C5BE);
231
+ outline: none;
232
+ padding: 9px 12px;
233
+ letter-spacing: -0.003em;
234
+ width: 100%;
235
+ resize: none;
236
+ min-height: 72px;
237
+ line-height: 1.5;
238
+ }
239
+ .qask-input::placeholder { color: var(--text-faint, #3A382F); }
240
+
241
+ .qask-foot {
242
+ padding: 10px 14px;
243
+ border-top: 0.5px solid var(--line-bright, #2A2A26);
244
+ background: var(--panel-2, #1A1A18);
245
+ display: flex;
246
+ justify-content: flex-end;
247
+ align-items: center;
248
+ gap: 8px;
249
+ }
250
+ .qask-btn {
251
+ font-family: var(--mono, "Inter", system-ui, sans-serif);
252
+ font-size: 10px;
253
+ font-weight: 700;
254
+ text-transform: uppercase;
255
+ letter-spacing: 0.1em;
256
+ padding: 7px 14px;
257
+ border: 0.5px solid var(--line-strong, #3A3A35);
258
+ background: transparent;
259
+ color: var(--text-soft, #8E8B83);
260
+ cursor: pointer;
261
+ transition: border-color 0.12s, color 0.12s, background 0.12s;
262
+ }
263
+ .qask-btn:hover { border-color: var(--lime, #6FB572); color: var(--lime, #6FB572); }
264
+ .qask-btn.primary {
265
+ border-color: var(--lime, #6FB572);
266
+ color: var(--lime, #6FB572);
267
+ }
268
+ .qask-btn.primary:hover { background: var(--lime, #6FB572); color: var(--bg, #0A0A0A); }
269
+ .qask-btn[disabled] { opacity: 0.4; cursor: not-allowed; }