@codemem/server 0.0.0 → 0.20.0-alpha.2

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.
@@ -0,0 +1,1860 @@
1
+ <!doctype html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="utf-8" />
5
+ <meta name="viewport" content="width=device-width, initial-scale=1" />
6
+ <title>codemem viewer</title>
7
+ <link rel="icon" type="image/svg+xml" href="/assets/favicon.svg" />
8
+ <link rel="preconnect" href="https://fonts.googleapis.com" />
9
+ <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
10
+ <link href="https://fonts.googleapis.com/css2?family=DM+Sans:ital,opsz,wght@0,9..40,300..700;1,9..40,300..700&family=JetBrains+Mono:wght@400;500&display=swap" rel="stylesheet" />
11
+ <script src="https://cdn.jsdelivr.net/npm/marked@11.1.1/marked.min.js"></script>
12
+ <script src="https://unpkg.com/lucide@latest"></script>
13
+ <script>
14
+ (function() {
15
+ try {
16
+ var rawTheme = localStorage.getItem('codemem-theme') || '';
17
+ if (!rawTheme) return;
18
+ var mode = rawTheme.indexOf('dark') === 0 ? 'dark' : 'light';
19
+ document.documentElement.setAttribute('data-theme', mode);
20
+ document.documentElement.setAttribute('data-color-mode', mode);
21
+ } catch (_e) {}
22
+ })();
23
+ </script>
24
+ <style>
25
+ /* ── Design Tokens ─────────────────────────────────────── */
26
+
27
+ :root {
28
+ /* Surface */
29
+ --surface-0: #faf8f5;
30
+ --surface-1: #fff9f2;
31
+ --surface-2: #f5efe6;
32
+ --surface-3: #ede5d9;
33
+
34
+ /* Text — tinted warm greys */
35
+ --text-primary: #1a1917;
36
+ --text-secondary: #5c5549;
37
+ --text-tertiary: #9b9285;
38
+ --text-inverse: #faf8f5;
39
+
40
+ /* Accent */
41
+ --accent: #1a6b56;
42
+ --accent-hover: #15574a;
43
+ --accent-subtle: rgba(26, 107, 86, 0.10);
44
+ --accent-warm: #d4642e;
45
+ --accent-warm-subtle: rgba(212, 100, 46, 0.12);
46
+ --accent-cool: #2d4a7a;
47
+ --accent-cool-subtle: rgba(45, 74, 122, 0.10);
48
+
49
+ /* Status */
50
+ --status-healthy: #1a6b56;
51
+ --status-degraded: #d4642e;
52
+ --status-attention: #b64b2f;
53
+
54
+ /* Border & chrome */
55
+ --border: #e8dfd3;
56
+ --border-hover: #d4c8b8;
57
+ --focus-ring: rgba(26, 107, 86, 0.14);
58
+ --shadow-sm: 0 1px 3px rgba(24, 23, 18, 0.06);
59
+ --shadow-md: 0 8px 24px rgba(24, 23, 18, 0.08);
60
+ --shadow-lg: 0 18px 40px rgba(24, 23, 18, 0.12);
61
+
62
+ /* Controls */
63
+ --control-bg: rgba(26, 107, 86, 0.08);
64
+ --control-border: rgba(26, 107, 86, 0.22);
65
+ --control-hover-bg: rgba(26, 107, 86, 0.14);
66
+ --control-hover-border: rgba(26, 107, 86, 0.4);
67
+ --control-text: var(--accent);
68
+
69
+ /* Toggles */
70
+ --toggle-active-bg: rgba(26, 107, 86, 0.14);
71
+ --toggle-active-text: var(--accent);
72
+ --toggle-hover-bg: rgba(0, 0, 0, 0.04);
73
+
74
+ /* Input */
75
+ --input-bg: rgba(255, 255, 255, 0.7);
76
+
77
+ /* Feed */
78
+ --item-bg: rgba(255, 255, 255, 0.6);
79
+ --item-hover-bg: rgba(255, 255, 255, 0.85);
80
+ --feed-hover-border: var(--accent);
81
+
82
+ /* Background gradients */
83
+ --grad-1: rgba(26, 107, 86, 0.12);
84
+ --grad-2: rgba(212, 100, 46, 0.14);
85
+ --grad-3: rgba(45, 74, 122, 0.10);
86
+ --body-start: #faf6ee;
87
+ --body-mid: #f3eadc;
88
+ --body-end: #efe3d2;
89
+ --dot-color: rgba(0, 0, 0, 0.025);
90
+
91
+ /* Spacing scale (4px base) */
92
+ --sp-1: 4px;
93
+ --sp-2: 8px;
94
+ --sp-3: 12px;
95
+ --sp-4: 16px;
96
+ --sp-5: 20px;
97
+ --sp-6: 24px;
98
+ --sp-8: 32px;
99
+ --sp-10: 40px;
100
+ --sp-12: 48px;
101
+
102
+ /* Radii */
103
+ --radius-sm: 8px;
104
+ --radius-md: 12px;
105
+ --radius-lg: 16px;
106
+ --radius-xl: 20px;
107
+ --radius-pill: 999px;
108
+
109
+ /* Typography */
110
+ --font-sans: "DM Sans", "Avenir Next", "Avenir", system-ui, sans-serif;
111
+ --font-mono: "JetBrains Mono", ui-monospace, SFMono-Regular, Menlo, monospace;
112
+ }
113
+
114
+ /* ── Dark mode ─────────────────────────────────────────── */
115
+
116
+ @media (prefers-color-scheme: dark) {
117
+ :root:not([data-theme="light"]) {
118
+ --surface-0: #161514;
119
+ --surface-1: #201f1d;
120
+ --surface-2: #2a2826;
121
+ --surface-3: #343230;
122
+
123
+ --text-primary: #ede8e2;
124
+ --text-secondary: #b5aea5;
125
+ --text-tertiary: #7d756b;
126
+ --text-inverse: #161514;
127
+
128
+ --accent: #4dd4b4;
129
+ --accent-hover: #3cc4a4;
130
+ --accent-subtle: rgba(77, 212, 180, 0.14);
131
+ --accent-warm: #ff9e6a;
132
+ --accent-warm-subtle: rgba(255, 158, 106, 0.16);
133
+ --accent-cool: #7eaaeb;
134
+ --accent-cool-subtle: rgba(126, 170, 235, 0.14);
135
+
136
+ --status-healthy: #4dd4b4;
137
+ --status-degraded: #ff9e6a;
138
+ --status-attention: #ff7a50;
139
+
140
+ --border: #3a3735;
141
+ --border-hover: #4a4745;
142
+ --focus-ring: rgba(77, 212, 180, 0.18);
143
+ --shadow-sm: 0 1px 3px rgba(0, 0, 0, 0.2);
144
+ --shadow-md: 0 8px 24px rgba(0, 0, 0, 0.3);
145
+ --shadow-lg: 0 18px 40px rgba(0, 0, 0, 0.5);
146
+
147
+ --control-bg: rgba(255, 255, 255, 0.06);
148
+ --control-border: rgba(255, 255, 255, 0.14);
149
+ --control-hover-bg: rgba(255, 255, 255, 0.10);
150
+ --control-hover-border: rgba(255, 255, 255, 0.24);
151
+ --control-text: var(--accent);
152
+
153
+ --toggle-active-bg: rgba(77, 212, 180, 0.20);
154
+ --toggle-active-text: var(--accent);
155
+ --toggle-hover-bg: rgba(255, 255, 255, 0.08);
156
+
157
+ --input-bg: rgba(60, 58, 56, 1);
158
+ --item-bg: rgba(50, 48, 46, 1);
159
+ --item-hover-bg: rgba(60, 58, 56, 1);
160
+
161
+ --grad-1: rgba(77, 212, 180, 0.08);
162
+ --grad-2: rgba(255, 158, 106, 0.08);
163
+ --grad-3: rgba(126, 170, 235, 0.06);
164
+ --body-start: #161514;
165
+ --body-mid: #1b1a19;
166
+ --body-end: #201f1d;
167
+ --dot-color: rgba(255, 255, 255, 0.03);
168
+ }
169
+ }
170
+
171
+ [data-theme="dark"] {
172
+ --surface-0: #161514;
173
+ --surface-1: #201f1d;
174
+ --surface-2: #2a2826;
175
+ --surface-3: #343230;
176
+
177
+ --text-primary: #ede8e2;
178
+ --text-secondary: #b5aea5;
179
+ --text-tertiary: #7d756b;
180
+ --text-inverse: #161514;
181
+
182
+ --accent: #4dd4b4;
183
+ --accent-hover: #3cc4a4;
184
+ --accent-subtle: rgba(77, 212, 180, 0.14);
185
+ --accent-warm: #ff9e6a;
186
+ --accent-warm-subtle: rgba(255, 158, 106, 0.16);
187
+ --accent-cool: #7eaaeb;
188
+ --accent-cool-subtle: rgba(126, 170, 235, 0.14);
189
+
190
+ --status-healthy: #4dd4b4;
191
+ --status-degraded: #ff9e6a;
192
+ --status-attention: #ff7a50;
193
+
194
+ --border: #3a3735;
195
+ --border-hover: #4a4745;
196
+ --focus-ring: rgba(77, 212, 180, 0.18);
197
+ --shadow-sm: 0 1px 3px rgba(0, 0, 0, 0.2);
198
+ --shadow-md: 0 8px 24px rgba(0, 0, 0, 0.3);
199
+ --shadow-lg: 0 18px 40px rgba(0, 0, 0, 0.5);
200
+
201
+ --control-bg: rgba(255, 255, 255, 0.06);
202
+ --control-border: rgba(255, 255, 255, 0.14);
203
+ --control-hover-bg: rgba(255, 255, 255, 0.10);
204
+ --control-hover-border: rgba(255, 255, 255, 0.24);
205
+ --control-text: var(--accent);
206
+
207
+ --toggle-active-bg: rgba(77, 212, 180, 0.20);
208
+ --toggle-active-text: var(--accent);
209
+ --toggle-hover-bg: rgba(255, 255, 255, 0.08);
210
+
211
+ --input-bg: rgba(60, 58, 56, 1);
212
+ --item-bg: rgba(50, 48, 46, 1);
213
+ --item-hover-bg: rgba(60, 58, 56, 1);
214
+
215
+ --grad-1: rgba(77, 212, 180, 0.08);
216
+ --grad-2: rgba(255, 158, 106, 0.08);
217
+ --grad-3: rgba(126, 170, 235, 0.06);
218
+ --body-start: #161514;
219
+ --body-mid: #1b1a19;
220
+ --body-end: #201f1d;
221
+ --dot-color: rgba(255, 255, 255, 0.03);
222
+ }
223
+
224
+ /* ── Reset & base ──────────────────────────────────────── */
225
+
226
+ *, *::before, *::after { box-sizing: border-box; margin: 0; }
227
+
228
+ body {
229
+ font-family: var(--font-sans);
230
+ font-size: 14px;
231
+ line-height: 1.55;
232
+ color: var(--text-primary);
233
+ background:
234
+ radial-gradient(circle at 12% 12%, var(--grad-1), transparent 45%),
235
+ radial-gradient(circle at 82% 18%, var(--grad-2), transparent 42%),
236
+ radial-gradient(circle at 70% 85%, var(--grad-3), transparent 40%),
237
+ linear-gradient(180deg, var(--body-start) 0%, var(--body-mid) 65%, var(--body-end) 100%);
238
+ min-height: 100vh;
239
+ }
240
+
241
+ body::before {
242
+ content: "";
243
+ position: fixed;
244
+ inset: 0;
245
+ background-image: radial-gradient(var(--dot-color) 1px, transparent 0);
246
+ background-size: 20px 20px;
247
+ opacity: 0.4;
248
+ pointer-events: none;
249
+ z-index: 0;
250
+ }
251
+
252
+ [hidden] { display: none !important; }
253
+
254
+ /* ── Accessibility ─────────────────────────────────────── */
255
+
256
+ .sr-only {
257
+ position: absolute; width: 1px; height: 1px;
258
+ padding: 0; margin: -1px; overflow: hidden;
259
+ clip: rect(0, 0, 0, 0); white-space: nowrap; border: 0;
260
+ }
261
+
262
+ /* ── Header ────────────────────────────────────────────── */
263
+
264
+ header {
265
+ position: sticky;
266
+ top: 0;
267
+ z-index: 10;
268
+ padding: var(--sp-4) var(--sp-6);
269
+ border-bottom: 1px solid var(--border);
270
+ background: color-mix(in srgb, var(--surface-1) 86%, transparent);
271
+ backdrop-filter: blur(12px);
272
+ -webkit-backdrop-filter: blur(12px);
273
+ }
274
+
275
+ .header-row {
276
+ display: flex;
277
+ align-items: center;
278
+ justify-content: space-between;
279
+ gap: var(--sp-4);
280
+ flex-wrap: wrap;
281
+ }
282
+
283
+ .header-left {
284
+ display: flex;
285
+ align-items: center;
286
+ gap: var(--sp-3);
287
+ min-width: 0;
288
+ }
289
+
290
+ .header-brand {
291
+ display: flex;
292
+ align-items: center;
293
+ gap: var(--sp-3);
294
+ }
295
+
296
+ .logo {
297
+ display: inline-flex;
298
+ align-items: center;
299
+ justify-content: center;
300
+ width: 40px;
301
+ height: 40px;
302
+ border-radius: var(--radius-sm);
303
+ background: var(--accent-subtle);
304
+ color: var(--accent);
305
+ flex-shrink: 0;
306
+ }
307
+ .logo svg { display: block; }
308
+
309
+ .header-title {
310
+ font-size: 18px;
311
+ font-weight: 600;
312
+ letter-spacing: -0.01em;
313
+ white-space: nowrap;
314
+ }
315
+
316
+ .health-dot {
317
+ width: 8px;
318
+ height: 8px;
319
+ border-radius: 50%;
320
+ flex-shrink: 0;
321
+ }
322
+ .health-dot.status-healthy { background: var(--status-healthy); box-shadow: 0 0 0 3px rgba(26, 107, 86, 0.15); }
323
+ .health-dot.status-degraded { background: var(--status-degraded); box-shadow: 0 0 0 3px rgba(212, 100, 46, 0.15); }
324
+ .health-dot.status-attention { background: var(--status-attention); box-shadow: 0 0 0 3px rgba(182, 75, 47, 0.15); animation: pulse 2.4s ease infinite; }
325
+
326
+ .header-right {
327
+ display: flex;
328
+ align-items: center;
329
+ gap: var(--sp-2);
330
+ }
331
+
332
+ .refresh-status {
333
+ display: inline-flex;
334
+ align-items: center;
335
+ gap: 6px;
336
+ font-size: 12px;
337
+ color: var(--text-tertiary);
338
+ }
339
+
340
+ .meta-line {
341
+ font-size: 12px;
342
+ color: var(--text-tertiary);
343
+ white-space: nowrap;
344
+ overflow: hidden;
345
+ text-overflow: ellipsis;
346
+ max-width: 300px;
347
+ }
348
+
349
+ /* ── Tab bar ───────────────────────────────────────────── */
350
+
351
+ .tab-bar {
352
+ display: flex;
353
+ gap: 2px;
354
+ padding: 0 var(--sp-6);
355
+ background: color-mix(in srgb, var(--surface-0) 60%, transparent);
356
+ backdrop-filter: blur(8px);
357
+ border-bottom: 1px solid var(--border);
358
+ position: sticky;
359
+ top: 73px; /* header height */
360
+ z-index: 9;
361
+ }
362
+
363
+ .tab-btn {
364
+ position: relative;
365
+ padding: var(--sp-3) var(--sp-4);
366
+ border: none;
367
+ background: transparent;
368
+ color: var(--text-tertiary);
369
+ font-family: var(--font-sans);
370
+ font-size: 13px;
371
+ font-weight: 500;
372
+ cursor: pointer;
373
+ transition: color 0.15s ease;
374
+ }
375
+
376
+ .tab-btn:hover {
377
+ color: var(--text-primary);
378
+ }
379
+
380
+ .tab-btn.active {
381
+ color: var(--accent);
382
+ font-weight: 600;
383
+ }
384
+
385
+ .tab-btn.active::after {
386
+ content: "";
387
+ position: absolute;
388
+ bottom: -1px;
389
+ left: var(--sp-4);
390
+ right: var(--sp-4);
391
+ height: 2px;
392
+ background: var(--accent);
393
+ border-radius: 2px 2px 0 0;
394
+ }
395
+
396
+ /* ── Tab panels ────────────────────────────────────────── */
397
+
398
+ .tab-panel {
399
+ position: relative;
400
+ z-index: 1;
401
+ padding: var(--sp-6);
402
+ max-width: 1100px;
403
+ margin: 0 auto;
404
+ display: flex;
405
+ flex-direction: column;
406
+ gap: var(--sp-5);
407
+ }
408
+
409
+ /* ── Cards / sections ──────────────────────────────────── */
410
+
411
+ .card {
412
+ background: var(--surface-1);
413
+ border: 1px solid var(--border);
414
+ border-radius: var(--radius-xl);
415
+ padding: var(--sp-5);
416
+ box-shadow: var(--shadow-md);
417
+ animation: fadeUp 0.4s ease both;
418
+ }
419
+
420
+ .card h2 {
421
+ font-size: 16px;
422
+ font-weight: 600;
423
+ letter-spacing: -0.01em;
424
+ margin-bottom: var(--sp-3);
425
+ }
426
+
427
+ .section-header {
428
+ display: flex;
429
+ align-items: center;
430
+ justify-content: space-between;
431
+ gap: var(--sp-3);
432
+ margin-bottom: var(--sp-3);
433
+ }
434
+
435
+ .section-header h2 { margin-bottom: 0; }
436
+
437
+ .section-actions {
438
+ display: flex;
439
+ gap: var(--sp-2);
440
+ align-items: center;
441
+ }
442
+
443
+ .section-meta {
444
+ color: var(--text-tertiary);
445
+ font-size: 12px;
446
+ margin-bottom: var(--sp-3);
447
+ }
448
+
449
+ /* ── Grid ──────────────────────────────────────────────── */
450
+
451
+ .grid-2 {
452
+ display: grid;
453
+ grid-template-columns: repeat(auto-fit, minmax(180px, 1fr));
454
+ gap: var(--sp-3);
455
+ }
456
+
457
+ .summary-row {
458
+ display: grid;
459
+ grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
460
+ gap: var(--sp-5);
461
+ }
462
+
463
+ /* ── Stat cards ────────────────────────────────────────── */
464
+
465
+ .stat {
466
+ border: 1px solid var(--border);
467
+ border-radius: var(--radius-md);
468
+ padding: var(--sp-3);
469
+ background: var(--surface-2);
470
+ display: flex;
471
+ align-items: center;
472
+ gap: var(--sp-3);
473
+ }
474
+
475
+ .stat-icon {
476
+ width: 18px;
477
+ height: 18px;
478
+ flex-shrink: 0;
479
+ stroke: var(--accent);
480
+ opacity: 0.6;
481
+ }
482
+
483
+ .stat-content {
484
+ display: flex;
485
+ flex-direction: column;
486
+ min-width: 0;
487
+ }
488
+
489
+ .stat .value {
490
+ font-weight: 600;
491
+ font-size: 16px;
492
+ color: var(--accent-cool);
493
+ line-height: 1.2;
494
+ font-feature-settings: 'tnum' 1;
495
+ }
496
+
497
+ .stat .label {
498
+ color: var(--text-tertiary);
499
+ font-size: 11px;
500
+ line-height: 1.3;
501
+ }
502
+
503
+ .health-primary {
504
+ border-width: 2px;
505
+ }
506
+ .health-primary .value { font-size: 20px; letter-spacing: 0.2px; }
507
+ .health-primary.status-healthy .value { color: var(--status-healthy); }
508
+ .health-primary.status-degraded .value { color: var(--status-degraded); }
509
+ .health-primary.status-attention .value { color: var(--status-attention); }
510
+
511
+ /* ── Buttons ───────────────────────────────────────────── */
512
+
513
+ .settings-button {
514
+ border: 1px solid var(--control-border);
515
+ background: var(--control-bg);
516
+ color: var(--control-text);
517
+ padding: 5px 12px;
518
+ border-radius: var(--radius-pill);
519
+ font-size: 12px;
520
+ font-family: var(--font-sans);
521
+ cursor: pointer;
522
+ transition: background 0.15s ease, border-color 0.15s ease;
523
+ white-space: nowrap;
524
+ }
525
+ .settings-button:hover {
526
+ background: var(--control-hover-bg);
527
+ border-color: var(--control-hover-border);
528
+ }
529
+ .settings-button:focus-visible {
530
+ outline: 2px solid var(--accent);
531
+ outline-offset: 2px;
532
+ }
533
+
534
+ /* ── Selects / inputs ──────────────────────────────────── */
535
+
536
+ .project-filter,
537
+ .theme-select {
538
+ padding: 5px 10px;
539
+ border-radius: var(--radius-sm);
540
+ border: 1px solid var(--border);
541
+ background: var(--input-bg);
542
+ color: var(--text-primary);
543
+ font-family: var(--font-sans);
544
+ font-size: 12px;
545
+ cursor: pointer;
546
+ transition: border-color 0.15s ease;
547
+ }
548
+ .project-filter:hover, .theme-select:hover { border-color: var(--border-hover); }
549
+ .project-filter:focus, .theme-select:focus {
550
+ outline: none;
551
+ border-color: var(--accent);
552
+ box-shadow: 0 0 0 3px var(--focus-ring);
553
+ }
554
+ .theme-select { min-width: 150px; }
555
+
556
+ /* ── Feed section ──────────────────────────────────────── */
557
+
558
+ .feed-controls {
559
+ display: flex;
560
+ align-items: center;
561
+ justify-content: space-between;
562
+ gap: var(--sp-3);
563
+ flex-wrap: wrap;
564
+ margin-bottom: var(--sp-4);
565
+ }
566
+
567
+ .feed-controls-right {
568
+ display: flex;
569
+ align-items: center;
570
+ gap: var(--sp-3);
571
+ flex-wrap: wrap;
572
+ justify-content: flex-end;
573
+ }
574
+
575
+ .feed-search {
576
+ min-width: 200px;
577
+ flex: 1;
578
+ max-width: 400px;
579
+ padding: 8px var(--sp-3);
580
+ border-radius: var(--radius-md);
581
+ border: 1px solid var(--border);
582
+ background: var(--input-bg);
583
+ color: var(--text-primary);
584
+ font-family: var(--font-sans);
585
+ font-size: 13px;
586
+ outline: none;
587
+ transition: border-color 0.15s ease, box-shadow 0.15s ease;
588
+ }
589
+ .feed-search::placeholder { color: var(--text-tertiary); }
590
+ .feed-search:focus { border-color: var(--accent); box-shadow: 0 0 0 3px var(--focus-ring); }
591
+
592
+ .feed-toggle {
593
+ display: inline-flex;
594
+ gap: 2px;
595
+ padding: 3px;
596
+ border-radius: var(--radius-pill);
597
+ border: 1px solid var(--border);
598
+ background: var(--surface-2);
599
+ }
600
+
601
+ .toggle-button {
602
+ border: none;
603
+ background: transparent;
604
+ color: var(--text-tertiary);
605
+ font-family: var(--font-sans);
606
+ font-size: 12px;
607
+ padding: 4px 10px;
608
+ border-radius: var(--radius-pill);
609
+ cursor: pointer;
610
+ display: inline-flex;
611
+ align-items: center;
612
+ gap: 5px;
613
+ transition: background 0.15s ease, color 0.15s ease;
614
+ }
615
+ .toggle-button:hover { background: var(--toggle-hover-bg); color: var(--text-primary); }
616
+ .toggle-button.active { background: var(--toggle-active-bg); color: var(--toggle-active-text); font-weight: 600; }
617
+
618
+ .feed-list {
619
+ display: flex;
620
+ flex-direction: column;
621
+ gap: var(--sp-3);
622
+ }
623
+
624
+ /* ── Feed items ────────────────────────────────────────── */
625
+
626
+ .feed-item {
627
+ border: 1px solid var(--border);
628
+ border-left: 4px solid var(--accent);
629
+ border-radius: var(--radius-lg);
630
+ padding: var(--sp-4) var(--sp-5);
631
+ background: var(--surface-1);
632
+ display: flex;
633
+ flex-direction: column;
634
+ gap: var(--sp-2);
635
+ transition: transform 0.15s ease, border-color 0.15s ease, box-shadow 0.15s ease;
636
+ }
637
+ .feed-item:hover {
638
+ transform: translateY(-1px);
639
+ box-shadow: var(--shadow-sm);
640
+ border-color: var(--border-hover);
641
+ }
642
+ .feed-item.new-item { animation: newPulse 420ms ease-out 1; }
643
+
644
+ /* Kind border colors */
645
+ .feed-item.feature { border-left-color: var(--accent); }
646
+ .feed-item.change { border-left-color: var(--accent-cool); }
647
+ .feed-item.bugfix { border-left-color: var(--accent-warm); }
648
+ .feed-item.refactor { border-left-color: #8b5cc6; }
649
+ .feed-item.discovery { border-left-color: #5e81ac; }
650
+ .feed-item.decision { border-left-color: #8fadbd; }
651
+ .feed-item.session_summary { border-left-color: #4696c8; }
652
+ .feed-item.exploration { border-left-color: var(--text-tertiary); }
653
+
654
+ .feed-card-header {
655
+ display: flex;
656
+ align-items: flex-start;
657
+ justify-content: space-between;
658
+ gap: var(--sp-3);
659
+ }
660
+ .feed-card-header > .feed-header { flex: 1; overflow: hidden; }
661
+
662
+ .feed-header {
663
+ display: flex;
664
+ align-items: center;
665
+ gap: var(--sp-2);
666
+ flex-wrap: wrap;
667
+ min-width: 0;
668
+ }
669
+
670
+ .feed-actions {
671
+ display: flex;
672
+ align-items: center;
673
+ gap: var(--sp-2);
674
+ flex-shrink: 0;
675
+ }
676
+
677
+ .feed-title {
678
+ font-weight: 600;
679
+ font-size: 15px;
680
+ flex: 1;
681
+ min-width: 0;
682
+ display: -webkit-box;
683
+ -webkit-box-orient: vertical;
684
+ -webkit-line-clamp: 2;
685
+ overflow: hidden;
686
+ overflow-wrap: anywhere;
687
+ word-break: break-word;
688
+ }
689
+
690
+ .feed-meta {
691
+ color: var(--text-tertiary);
692
+ font-size: 12px;
693
+ }
694
+
695
+ .feed-provenance {
696
+ display: flex;
697
+ flex-wrap: wrap;
698
+ gap: 6px;
699
+ }
700
+
701
+ .provenance-chip {
702
+ display: inline-flex;
703
+ align-items: center;
704
+ gap: 4px;
705
+ padding: 3px 8px;
706
+ border-radius: var(--radius-pill);
707
+ border: 1px solid var(--border);
708
+ background: var(--surface-2);
709
+ color: var(--text-tertiary);
710
+ font-size: 11px;
711
+ line-height: 1.2;
712
+ white-space: nowrap;
713
+ }
714
+ .provenance-chip.mine { background: var(--accent-subtle); color: var(--accent); border-color: rgba(43, 122, 176, 0.18); }
715
+ .provenance-chip.author { color: var(--text-secondary); }
716
+ .provenance-chip.private { background: rgba(140, 140, 140, 0.12); color: var(--text-secondary); }
717
+ .provenance-chip.shared { background: rgba(64, 125, 88, 0.12); color: #407d58; }
718
+ .provenance-chip.workspace { background: rgba(143, 173, 189, 0.12); color: #6b8fa3; }
719
+ .provenance-chip.source { background: rgba(94, 129, 172, 0.12); color: #5e81ac; }
720
+ .provenance-chip.device { background: rgba(128, 110, 74, 0.12); color: #806e4a; }
721
+ .provenance-chip.trust { background: rgba(202, 138, 4, 0.12); color: #a16207; }
722
+
723
+ .feed-body {
724
+ font-size: 13px;
725
+ line-height: 1.55;
726
+ color: var(--text-secondary);
727
+ }
728
+ .feed-body.clamp { display: -webkit-box; -webkit-box-orient: vertical; overflow: hidden; }
729
+ .feed-body.clamp-3 { -webkit-line-clamp: 3; }
730
+ .feed-body.clamp-5 { -webkit-line-clamp: 5; }
731
+ .feed-body.facts { display: flex; flex-direction: column; gap: 6px; }
732
+ .feed-body p { margin: 0 0 0.5em; }
733
+ .feed-body p:last-child { margin-bottom: 0; }
734
+ .feed-body ul, .feed-body ol { margin: 0.3em 0; padding-left: 1.3em; list-style: revert; }
735
+ .feed-body li { margin: 0.15em 0; padding: 0; border: none; border-radius: 0; background: none; font-size: inherit; }
736
+ .feed-body li:hover { transform: none; border-color: transparent; background: none; }
737
+ .feed-body code { background: var(--surface-2); padding: 0.15em 0.35em; border-radius: 4px; font-family: var(--font-mono); font-size: 0.9em; }
738
+ .feed-body pre { background: var(--surface-2); padding: 0.6em 0.8em; border-radius: var(--radius-sm); overflow-x: auto; margin: 0.5em 0; }
739
+ .feed-body pre code { background: none; padding: 0; }
740
+ .feed-body strong { font-weight: 600; }
741
+ .feed-body em { font-style: italic; }
742
+ .feed-body a { color: var(--accent); text-decoration: underline; }
743
+ .feed-body h1, .feed-body h2, .feed-body h3, .feed-body h4 { font-size: 1em; font-weight: 600; margin: 0.6em 0 0.3em; }
744
+ .feed-body blockquote { border-left: 3px solid var(--border); margin: 0.5em 0; padding-left: 0.8em; color: var(--text-tertiary); }
745
+
746
+ .summary-section {
747
+ display: flex;
748
+ flex-direction: column;
749
+ gap: 4px;
750
+ padding: 6px 0;
751
+ border-bottom: 1px solid var(--border);
752
+ }
753
+ .summary-section:last-child { border-bottom: none; }
754
+ .summary-section-label { font-size: 11px; font-weight: 600; text-transform: uppercase; letter-spacing: 0.5px; color: var(--text-tertiary); }
755
+ .summary-section-content { font-size: 13px; line-height: 1.5; color: var(--text-primary); }
756
+
757
+ .feed-footer {
758
+ display: flex;
759
+ flex-wrap: wrap;
760
+ gap: var(--sp-2);
761
+ align-items: flex-start;
762
+ justify-content: space-between;
763
+ }
764
+ .feed-footer-left { display: flex; flex-direction: column; gap: 6px; }
765
+ .feed-footer-right { display: flex; align-items: center; gap: var(--sp-2); flex-shrink: 0; }
766
+ .feed-visibility-controls { display: flex; flex-wrap: wrap; align-items: center; gap: 8px; }
767
+ .feed-visibility-select {
768
+ min-width: 180px;
769
+ padding: 6px 10px;
770
+ border-radius: var(--radius-md);
771
+ border: 1px solid var(--border);
772
+ background: var(--input-bg);
773
+ color: var(--text-primary);
774
+ font-family: var(--font-sans);
775
+ font-size: 12px;
776
+ }
777
+ .feed-visibility-note { width: 100%; max-width: 60ch; font-size: 12px; color: var(--text-tertiary); }
778
+
779
+ .feed-expand {
780
+ border: 1px solid var(--border);
781
+ background: transparent;
782
+ color: var(--text-tertiary);
783
+ border-radius: var(--radius-pill);
784
+ padding: 3px 10px;
785
+ font-family: var(--font-sans);
786
+ font-size: 11px;
787
+ cursor: pointer;
788
+ transition: background 0.15s ease, color 0.15s ease;
789
+ }
790
+ .feed-expand:hover { background: var(--accent-subtle); color: var(--accent); }
791
+
792
+ .feed-tags { display: flex; flex-wrap: wrap; gap: 6px; }
793
+ .feed-files { display: flex; flex-wrap: wrap; gap: var(--sp-2); font-size: 10px; color: var(--text-tertiary); opacity: 0.85; }
794
+ .feed-file { white-space: nowrap; }
795
+ .feed-age { white-space: nowrap; }
796
+
797
+ /* ── Pills & badges ────────────────────────────────────── */
798
+
799
+ .kind-pill {
800
+ display: inline-flex;
801
+ align-items: center;
802
+ padding: 2px 8px;
803
+ border-radius: var(--radius-pill);
804
+ font-size: 11px;
805
+ font-weight: 500;
806
+ letter-spacing: 0.2px;
807
+ white-space: nowrap;
808
+ background: var(--accent-subtle);
809
+ color: var(--accent);
810
+ }
811
+ .kind-pill.feature { background: var(--accent-subtle); color: var(--accent); }
812
+ .kind-pill.change { background: var(--accent-cool-subtle); color: var(--accent-cool); }
813
+ .kind-pill.bugfix { background: var(--accent-warm-subtle); color: var(--accent-warm); }
814
+ .kind-pill.refactor { background: rgba(139, 92, 198, 0.12); color: #8b5cc6; }
815
+ .kind-pill.discovery { background: rgba(94, 129, 172, 0.12); color: #5e81ac; }
816
+ .kind-pill.decision { background: rgba(143, 173, 189, 0.12); color: #6b8fa3; }
817
+ .kind-pill.session_summary { background: rgba(70, 150, 200, 0.12); color: #4696c8; }
818
+ .kind-pill.exploration { background: rgba(140, 140, 140, 0.12); color: var(--text-tertiary); }
819
+
820
+ .tag-chip {
821
+ display: inline-flex;
822
+ align-items: center;
823
+ gap: 6px;
824
+ padding: 2px 10px;
825
+ border-radius: var(--radius-pill);
826
+ background: var(--accent-cool-subtle);
827
+ color: var(--accent-cool);
828
+ font-size: 11px;
829
+ font-weight: 500;
830
+ }
831
+
832
+ .badge {
833
+ display: inline-flex;
834
+ align-items: center;
835
+ gap: 6px;
836
+ padding: 3px 10px;
837
+ border-radius: var(--radius-pill);
838
+ background: var(--accent-cool-subtle);
839
+ color: var(--accent-cool);
840
+ font-size: 12px;
841
+ font-weight: 600;
842
+ }
843
+ .badge-online { background: rgba(26, 107, 86, 0.12); color: var(--accent); }
844
+ .badge-offline { background: rgba(182, 75, 47, 0.15); color: var(--accent-warm); }
845
+
846
+ .pill {
847
+ display: inline-block;
848
+ padding: 2px 10px;
849
+ border-radius: var(--radius-pill);
850
+ background: var(--accent-subtle);
851
+ color: var(--accent);
852
+ font-size: 12px;
853
+ font-weight: 600;
854
+ transition: background 0.3s ease, color 0.3s ease;
855
+ }
856
+ .pill.alt { background: var(--accent-warm-subtle); color: var(--accent-warm); }
857
+ .pill-success { background: var(--green-bg, #16a34a22); color: var(--green-text, #16a34a); }
858
+ .pill-warning { background: var(--yellow-bg, #ca8a0422); color: var(--yellow-text, #ca8a04); }
859
+ .pill-error { background: var(--red-bg, #dc262622); color: var(--red-text, #dc2626); }
860
+
861
+ .match {
862
+ padding: 0 2px;
863
+ border-radius: 3px;
864
+ background: var(--accent-subtle);
865
+ color: inherit;
866
+ }
867
+
868
+ /* ── Health actions / sync actions ──────────────────────── */
869
+
870
+ .health-actions, .sync-actions {
871
+ display: grid;
872
+ gap: var(--sp-2);
873
+ margin: var(--sp-2) 0 var(--sp-3);
874
+ }
875
+
876
+ .health-action, .sync-action {
877
+ display: flex;
878
+ align-items: center;
879
+ justify-content: space-between;
880
+ gap: var(--sp-3);
881
+ padding: var(--sp-3);
882
+ border: 1px solid var(--border);
883
+ border-radius: var(--radius-sm);
884
+ background: var(--surface-2);
885
+ }
886
+
887
+ .health-action-text, .sync-action-text { font-size: 13px; color: var(--text-primary); flex: 1; min-width: 0; }
888
+ .health-action-command, .sync-action-command { display: block; margin-top: 2px; font-family: var(--font-mono); font-size: 12px; color: var(--text-tertiary); }
889
+ .health-action-buttons { display: flex; gap: 6px; flex-shrink: 0; }
890
+
891
+ /* ── Diagnostics ───────────────────────────────────────── */
892
+
893
+ .diag-list { margin-top: var(--sp-3); display: flex; flex-direction: column; gap: var(--sp-2); }
894
+ .diag-line { border: 1px solid var(--border); border-radius: var(--radius-md); padding: var(--sp-3); background: var(--surface-2); display: flex; justify-content: space-between; gap: var(--sp-3); }
895
+ .diag-line .left { min-width: 0; }
896
+ .diag-line .right { flex-shrink: 0; color: var(--text-tertiary); }
897
+
898
+ .diag-group { border: 1px solid var(--border); border-radius: var(--radius-sm); padding: var(--sp-2) var(--sp-3); margin-top: var(--sp-3); background: var(--surface-2); }
899
+ .diag-group summary { cursor: pointer; color: var(--text-tertiary); font-size: 13px; }
900
+
901
+ /* ── Peers ─────────────────────────────────────────────── */
902
+
903
+ .peer-list { display: grid; gap: var(--sp-3); }
904
+ .peer-card { border: 1px solid var(--border); border-radius: var(--radius-lg); padding: var(--sp-4); background: var(--surface-1); display: grid; gap: var(--sp-2); }
905
+ .peer-title { display: flex; align-items: center; justify-content: space-between; gap: var(--sp-2); flex-wrap: wrap; }
906
+ .peer-title strong { font-size: 14px; font-weight: 600; }
907
+ .actor-list { display: grid; gap: var(--sp-3); margin-top: var(--sp-3); }
908
+ .actor-row { display: flex; align-items: center; justify-content: space-between; gap: var(--sp-3); border: 1px solid var(--border); border-radius: var(--radius-lg); padding: var(--sp-3); background: var(--surface-1); }
909
+ .actor-details { min-width: 0; display: grid; gap: 6px; }
910
+ .actor-title { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }
911
+ .actor-actions { display: flex; align-items: center; gap: var(--sp-2); flex-wrap: wrap; }
912
+ .actor-name-input { min-width: 220px; }
913
+ .actor-merge-controls { display: flex; align-items: center; gap: var(--sp-2); flex-wrap: wrap; }
914
+ .actor-merge-select { min-width: 200px; }
915
+ .actor-merge-note { width: 100%; }
916
+ .actor-create-row { display: flex; gap: var(--sp-2); flex-wrap: wrap; margin-top: var(--sp-3); }
917
+ .actor-create-row input { flex: 1 1 260px; }
918
+ .actor-badge.local { background: rgba(31, 111, 92, 0.12); color: var(--accent); }
919
+ .peer-scope { display: grid; gap: var(--sp-2); padding-top: var(--sp-2); border-top: 1px solid var(--border); }
920
+ .peer-scope-summary, .peer-scope-effective { font-size: 12px; color: var(--text-secondary); }
921
+ .peer-actor-row { display: flex; align-items: center; gap: var(--sp-2); flex-wrap: wrap; }
922
+ .peer-scope-row { display: grid; grid-template-columns: 1fr 1fr; gap: var(--sp-2); }
923
+ .peer-scope-editor { display: grid; gap: 8px; }
924
+ .peer-scope-chips { display: flex; flex-wrap: wrap; gap: 6px; min-height: 32px; align-items: flex-start; padding: 8px 10px; border: 1px solid var(--border); border-radius: var(--radius-sm); background: var(--surface-2); }
925
+ .peer-scope-chip { display: inline-flex; align-items: center; gap: 6px; padding: 4px 8px; border-radius: var(--radius-pill); background: rgba(43, 122, 176, 0.12); color: var(--accent); font-size: 12px; }
926
+ .peer-scope-chip.empty { background: transparent; color: var(--text-tertiary); border: 1px dashed var(--border); }
927
+ .peer-scope-chip-remove { border: none; background: transparent; color: inherit; cursor: pointer; padding: 0; font: inherit; opacity: 0.75; }
928
+ .peer-scope-chip-remove:hover { opacity: 1; }
929
+ .peer-scope-input { width: 100%; border: 1px solid var(--border); border-radius: var(--radius-sm); background: var(--surface-2); color: var(--text-primary); padding: 8px 10px; font: inherit; }
930
+ .peer-scope-actions { display: flex; gap: var(--sp-2); }
931
+ .peer-actions { display: flex; gap: 6px; }
932
+ .peer-actions button { border: 1px solid var(--border); background: transparent; border-radius: var(--radius-pill); padding: 5px 10px; font-family: var(--font-sans); font-size: 12px; cursor: pointer; color: var(--text-secondary); }
933
+ .peer-actions button:hover { border-color: var(--border-hover); background: var(--surface-2); }
934
+ .peer-scope-editor-wrap {
935
+ display: grid;
936
+ gap: var(--sp-2);
937
+ overflow: hidden;
938
+ transition: max-height 0.25s ease, opacity 0.2s ease;
939
+ max-height: 400px;
940
+ opacity: 1;
941
+ }
942
+ .peer-scope-editor-wrap.collapsed {
943
+ max-height: 0;
944
+ opacity: 0;
945
+ padding: 0;
946
+ }
947
+ @keyframes sync-shimmer {
948
+ 0% { background-position: -200px 0; }
949
+ 100% { background-position: 200px 0; }
950
+ }
951
+ .sync-skeleton { padding: 16px 0; }
952
+ .sync-skeleton-line {
953
+ height: 12px;
954
+ border-radius: 6px;
955
+ background: linear-gradient(90deg, var(--border) 25%, var(--surface-2) 50%, var(--border) 75%);
956
+ background-size: 400px 100%;
957
+ animation: sync-shimmer 1.5s ease-in-out infinite;
958
+ margin: 8px 0;
959
+ }
960
+ .sync-skeleton-line.short { width: 40%; }
961
+ .sync-skeleton-line.medium { width: 65%; }
962
+ .sync-skeleton-line.long { width: 85%; }
963
+ .sync-skeleton-block {
964
+ height: 56px;
965
+ border-radius: 8px;
966
+ background: linear-gradient(90deg, var(--border) 25%, var(--surface-2) 50%, var(--border) 75%);
967
+ background-size: 400px 100%;
968
+ animation: sync-shimmer 1.5s ease-in-out infinite;
969
+ margin: 8px 0;
970
+ }
971
+ .sync-empty-state {
972
+ padding: 20px;
973
+ text-align: center;
974
+ color: var(--text-dim, var(--text-tertiary));
975
+ font-size: 0.9rem;
976
+ border: 1px dashed var(--border);
977
+ border-radius: 8px;
978
+ margin: 8px 0;
979
+ }
980
+ .sync-field-error {
981
+ outline: 1.5px solid var(--red-text, #dc2626) !important;
982
+ outline-offset: -1px;
983
+ }
984
+ .sync-field-hint {
985
+ font-size: 0.78rem;
986
+ color: var(--red-text, #dc2626);
987
+ margin: 2px 0 0 2px;
988
+ }
989
+ @keyframes sync-shake {
990
+ 0%, 100% { transform: translateX(0); }
991
+ 20%, 60% { transform: translateX(-4px); }
992
+ 40%, 80% { transform: translateX(4px); }
993
+ }
994
+ .sync-shake { animation: sync-shake 0.3s ease-in-out; }
995
+ .settings-button:disabled {
996
+ opacity: 0.55;
997
+ cursor: not-allowed;
998
+ }
999
+ .settings-button {
1000
+ transition: opacity 0.15s ease, background 0.15s ease, color 0.15s ease;
1001
+ }
1002
+ .sync-toggle-admin {
1003
+ background: none;
1004
+ border: none;
1005
+ color: var(--text-dim, var(--text-tertiary));
1006
+ padding: 4px 0;
1007
+ cursor: pointer;
1008
+ text-align: left;
1009
+ margin-top: 16px;
1010
+ font-size: 0.8rem;
1011
+ text-decoration: underline;
1012
+ text-decoration-style: dashed;
1013
+ text-underline-offset: 3px;
1014
+ }
1015
+ .sync-toggle-admin:hover {
1016
+ color: var(--accent);
1017
+ }
1018
+ .sync-ttl-group {
1019
+ display: flex;
1020
+ align-items: center;
1021
+ gap: 6px;
1022
+ }
1023
+ .sync-ttl-group label {
1024
+ font-size: 0.8rem;
1025
+ color: var(--text-dim, var(--text-tertiary));
1026
+ white-space: nowrap;
1027
+ }
1028
+ .sync-team-summary {
1029
+ display: grid;
1030
+ gap: 8px;
1031
+ padding: 12px 14px;
1032
+ border: 1px solid var(--border);
1033
+ border-radius: var(--radius-lg);
1034
+ background: var(--surface-2);
1035
+ }
1036
+ .sync-team-status-row {
1037
+ display: flex;
1038
+ align-items: center;
1039
+ gap: 10px;
1040
+ flex-wrap: wrap;
1041
+ }
1042
+ .sync-team-status-label {
1043
+ font-size: 12px;
1044
+ font-weight: 600;
1045
+ letter-spacing: 0.04em;
1046
+ text-transform: uppercase;
1047
+ color: var(--text-tertiary);
1048
+ }
1049
+ .sync-team-metrics {
1050
+ font-size: 13px;
1051
+ color: var(--text-secondary);
1052
+ font-feature-settings: 'tnum' 1;
1053
+ }
1054
+ .sync-legacy-claim-row { margin-top: 12px; gap: 12px; align-items: center; flex-wrap: wrap; }
1055
+ .sync-legacy-select {
1056
+ min-width: 320px;
1057
+ max-width: 100%;
1058
+ flex: 1 1 320px;
1059
+ padding: 8px 12px;
1060
+ border-radius: var(--radius-md);
1061
+ border: 1px solid var(--border);
1062
+ background: var(--input-bg);
1063
+ color: var(--text-primary);
1064
+ font-family: var(--font-sans);
1065
+ font-size: 13px;
1066
+ cursor: pointer;
1067
+ transition: border-color 0.15s ease, box-shadow 0.15s ease;
1068
+ }
1069
+ .sync-legacy-select:hover { border-color: var(--border-hover); }
1070
+ .sync-legacy-select:focus {
1071
+ outline: none;
1072
+ border-color: var(--accent);
1073
+ box-shadow: 0 0 0 3px var(--focus-ring);
1074
+ }
1075
+ .sync-legacy-button {
1076
+ border: 1px solid var(--control-border);
1077
+ background: var(--control-bg);
1078
+ color: var(--control-text);
1079
+ border-radius: var(--radius-pill);
1080
+ padding: 8px 14px;
1081
+ font-family: var(--font-sans);
1082
+ font-size: 13px;
1083
+ font-weight: 600;
1084
+ cursor: pointer;
1085
+ transition: background 0.15s ease, border-color 0.15s ease, transform 0.15s ease;
1086
+ }
1087
+ .sync-legacy-button:hover {
1088
+ background: var(--control-hover-bg);
1089
+ border-color: var(--control-hover-border);
1090
+ }
1091
+ .sync-legacy-button:focus-visible {
1092
+ outline: 2px solid var(--accent);
1093
+ outline-offset: 2px;
1094
+ }
1095
+ .sync-legacy-button:disabled {
1096
+ opacity: 0.7;
1097
+ cursor: default;
1098
+ transform: none;
1099
+ }
1100
+ .sync-actor-select {
1101
+ min-width: 220px;
1102
+ max-width: 100%;
1103
+ flex: 1 1 220px;
1104
+ padding: 8px 12px;
1105
+ border-radius: var(--radius-md);
1106
+ border: 1px solid var(--border);
1107
+ background: var(--input-bg);
1108
+ color: var(--text-primary);
1109
+ font-family: var(--font-sans);
1110
+ font-size: 13px;
1111
+ cursor: pointer;
1112
+ transition: border-color 0.15s ease, box-shadow 0.15s ease;
1113
+ }
1114
+ .sync-actor-select:hover { border-color: var(--border-hover); }
1115
+ .sync-actor-select:focus {
1116
+ outline: none;
1117
+ border-color: var(--accent);
1118
+ box-shadow: 0 0 0 3px var(--focus-ring);
1119
+ }
1120
+ .peer-meta { font-size: 13px; color: var(--text-tertiary); }
1121
+ .peer-addresses { font-family: var(--font-mono); font-size: 12px; color: var(--text-secondary); opacity: 0.7; }
1122
+ .attempts-list { margin-top: var(--sp-3); display: grid; gap: 6px; font-size: 13px; color: var(--text-tertiary); }
1123
+
1124
+ /* ── Pairing ───────────────────────────────────────────── */
1125
+
1126
+ .pairing-card { margin-top: var(--sp-4); border: 1px dashed var(--border); border-radius: var(--radius-lg); padding: var(--sp-4); display: grid; gap: var(--sp-3); background: var(--surface-2); }
1127
+ .pairing-body { display: grid; gap: var(--sp-3); grid-template-columns: 1.4fr 1fr; align-items: start; }
1128
+ .pairing-body pre { margin: 0; font-size: 12px; background: var(--surface-0); padding: var(--sp-3); border-radius: var(--radius-md); white-space: pre-wrap; word-break: break-all; }
1129
+ @media (max-width: 900px) { .pairing-body { grid-template-columns: 1fr; } }
1130
+
1131
+ /* ── Modal ──────────────────────────────────────────────── */
1132
+
1133
+ .modal-backdrop {
1134
+ position: fixed; inset: 0;
1135
+ background: rgba(25, 24, 23, 0.4);
1136
+ backdrop-filter: blur(6px);
1137
+ z-index: 20;
1138
+ }
1139
+ .modal {
1140
+ position: fixed; inset: 0;
1141
+ display: flex; align-items: center; justify-content: center;
1142
+ z-index: 21;
1143
+ padding: var(--sp-6);
1144
+ }
1145
+ @media (max-height: 720px) { .modal { align-items: flex-start; } }
1146
+ .modal-backdrop[hidden], .modal[hidden] { display: none; }
1147
+
1148
+ .modal-card {
1149
+ width: min(520px, 100%);
1150
+ background: var(--surface-1);
1151
+ border: 1px solid var(--border);
1152
+ border-radius: var(--radius-xl);
1153
+ box-shadow: var(--shadow-lg);
1154
+ padding: var(--sp-5);
1155
+ display: flex;
1156
+ flex-direction: column;
1157
+ gap: var(--sp-4);
1158
+ max-height: min(84vh, 720px);
1159
+ overflow: hidden;
1160
+ }
1161
+ .modal-header { display: flex; align-items: center; justify-content: space-between; gap: var(--sp-3); }
1162
+ .modal-header h2 { margin: 0; font-size: 18px; }
1163
+ .modal-close { border: none; background: transparent; color: var(--text-tertiary); cursor: pointer; font-size: 12px; font-family: var(--font-sans); }
1164
+ .modal-body { display: flex; flex-direction: column; gap: var(--sp-3); flex: 1; min-height: 0; overflow-y: auto; padding-right: 6px; }
1165
+ .modal-footer { display: flex; align-items: center; justify-content: space-between; gap: var(--sp-3); flex-shrink: 0; padding-top: var(--sp-2); border-top: 1px solid var(--border); }
1166
+ @keyframes noticeSlideIn {
1167
+ from { transform: translateY(-12px); opacity: 0; }
1168
+ to { transform: translateY(0); opacity: 1; }
1169
+ }
1170
+ @keyframes noticeSlideOut {
1171
+ from { transform: translateY(0); opacity: 1; }
1172
+ to { transform: translateY(-12px); opacity: 0; }
1173
+ }
1174
+ .app-notice {
1175
+ margin: var(--sp-3) var(--sp-5) 0;
1176
+ padding: var(--sp-3) var(--sp-4);
1177
+ border: 1px solid var(--border);
1178
+ border-radius: var(--radius-md);
1179
+ background: var(--surface-1);
1180
+ color: var(--text-secondary);
1181
+ box-shadow: var(--shadow-sm);
1182
+ animation: noticeSlideIn 0.25s ease both;
1183
+ }
1184
+ .app-notice.hiding {
1185
+ animation: noticeSlideOut 0.2s ease both;
1186
+ }
1187
+ .app-notice.success {
1188
+ border-color: rgba(26, 107, 86, 0.24);
1189
+ background: rgba(26, 107, 86, 0.08);
1190
+ color: var(--accent);
1191
+ }
1192
+ .app-notice.warning {
1193
+ border-color: rgba(212, 100, 46, 0.28);
1194
+ background: rgba(212, 100, 46, 0.1);
1195
+ color: var(--accent-warm);
1196
+ }
1197
+
1198
+ .settings-tabs { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 6px; }
1199
+ .settings-tab {
1200
+ border: 1px solid var(--border);
1201
+ background: var(--surface-0);
1202
+ color: var(--text-secondary);
1203
+ border-radius: var(--radius-sm);
1204
+ padding: 7px 10px;
1205
+ font-family: var(--font-sans);
1206
+ font-size: 12px;
1207
+ cursor: pointer;
1208
+ }
1209
+ .settings-tab:hover { border-color: var(--border-hover); }
1210
+ .settings-tab.active {
1211
+ border-color: var(--control-hover-border);
1212
+ color: var(--accent);
1213
+ background: var(--accent-subtle);
1214
+ }
1215
+ .settings-panel { display: none; flex-direction: column; gap: var(--sp-3); }
1216
+ .settings-panel.active { display: flex; }
1217
+ .observer-status-banner {
1218
+ display: flex;
1219
+ flex-direction: column;
1220
+ gap: var(--sp-1);
1221
+ padding: var(--sp-2) var(--sp-3);
1222
+ border: 1px solid var(--border);
1223
+ border-radius: var(--radius-md);
1224
+ background: var(--surface-0);
1225
+ font-size: 12px;
1226
+ color: var(--text-secondary);
1227
+ line-height: 1.5;
1228
+ }
1229
+ .observer-status-banner .status-active {
1230
+ color: var(--text-primary);
1231
+ font-weight: 500;
1232
+ }
1233
+ .observer-status-banner .status-label {
1234
+ color: var(--text-tertiary);
1235
+ font-size: 11px;
1236
+ text-transform: uppercase;
1237
+ letter-spacing: 0.06em;
1238
+ }
1239
+ .observer-status-banner .status-cred {
1240
+ display: inline-flex;
1241
+ align-items: center;
1242
+ gap: 4px;
1243
+ }
1244
+ .observer-status-banner .status-issue {
1245
+ display: flex;
1246
+ flex-direction: column;
1247
+ gap: 4px;
1248
+ padding: 8px 10px;
1249
+ border: 1px solid rgba(248, 113, 113, 0.32);
1250
+ border-radius: var(--radius-sm);
1251
+ background: rgba(248, 113, 113, 0.08);
1252
+ }
1253
+ .observer-status-banner .status-issue-message {
1254
+ color: var(--text-primary);
1255
+ font-weight: 500;
1256
+ }
1257
+ .observer-status-banner .status-issue-meta {
1258
+ color: var(--text-secondary);
1259
+ }
1260
+ .observer-status-banner .status-issue-impact {
1261
+ color: #fbbf24;
1262
+ }
1263
+ .observer-status-banner .cred-ok { color: var(--semantic-success, #4ade80); }
1264
+ .observer-status-banner .cred-none { color: var(--text-tertiary); }
1265
+ .settings-group {
1266
+ display: flex;
1267
+ flex-direction: column;
1268
+ gap: var(--sp-2);
1269
+ padding: var(--sp-3);
1270
+ border: 1px solid var(--border);
1271
+ border-radius: var(--radius-md);
1272
+ background: var(--surface-0);
1273
+ }
1274
+ .settings-group-title {
1275
+ margin: 0;
1276
+ font-size: 12px;
1277
+ letter-spacing: 0.06em;
1278
+ text-transform: uppercase;
1279
+ color: var(--text-tertiary);
1280
+ }
1281
+
1282
+ .field-label {
1283
+ display: inline-flex;
1284
+ align-items: center;
1285
+ gap: 6px;
1286
+ }
1287
+
1288
+ .help-icon {
1289
+ width: 16px;
1290
+ height: 16px;
1291
+ border: 1px solid var(--border);
1292
+ border-radius: 999px;
1293
+ display: inline-flex;
1294
+ align-items: center;
1295
+ justify-content: center;
1296
+ font-size: 11px;
1297
+ color: var(--text-secondary);
1298
+ cursor: pointer;
1299
+ user-select: none;
1300
+ background: var(--surface-0);
1301
+ position: relative;
1302
+ padding: 0;
1303
+ }
1304
+
1305
+ .help-icon:focus-visible {
1306
+ outline: 2px solid var(--focus-ring);
1307
+ outline-offset: 1px;
1308
+ }
1309
+
1310
+ .help-tooltip {
1311
+ position: fixed;
1312
+ min-width: 190px;
1313
+ max-width: 280px;
1314
+ padding: 8px 10px;
1315
+ border: 1px solid var(--border);
1316
+ border-radius: var(--radius-sm);
1317
+ background: var(--surface-1);
1318
+ color: var(--text-secondary);
1319
+ font-size: 12px;
1320
+ line-height: 1.4;
1321
+ box-shadow: var(--shadow-sm);
1322
+ pointer-events: none;
1323
+ opacity: 0;
1324
+ transform: translateY(-2px);
1325
+ transition: opacity 0.08s ease, transform 0.08s ease;
1326
+ z-index: 1000;
1327
+ }
1328
+
1329
+ .help-tooltip.visible {
1330
+ opacity: 1;
1331
+ transform: translateY(0);
1332
+ }
1333
+
1334
+ .settings-advanced-toolbar {
1335
+ display: flex;
1336
+ align-items: center;
1337
+ gap: 8px;
1338
+ padding: var(--sp-2) var(--sp-3);
1339
+ border: 1px solid var(--border);
1340
+ border-radius: var(--radius-sm);
1341
+ background: var(--surface-0);
1342
+ }
1343
+
1344
+ .settings-advanced-toolbar input {
1345
+ width: 14px;
1346
+ height: 14px;
1347
+ }
1348
+
1349
+ .settings-advanced[hidden] {
1350
+ display: none !important;
1351
+ }
1352
+
1353
+ .field { display: flex; flex-direction: column; gap: 6px; font-size: 13px; }
1354
+ .field input:not([type="checkbox"]), .field select, .field textarea { padding: var(--sp-2) var(--sp-3); border-radius: var(--radius-sm); border: 1px solid var(--border); background: var(--input-bg); font-family: var(--font-sans); font-size: 13px; color: var(--text-primary); }
1355
+ .field-checkbox { flex-direction: row; align-items: center; gap: 8px; }
1356
+ .field-checkbox input[type="checkbox"] { width: 14px; height: 14px; margin: 0; }
1357
+ .field-checkbox label { margin: 0; }
1358
+ .cm-checkbox {
1359
+ appearance: none;
1360
+ width: 16px;
1361
+ height: 16px;
1362
+ border: 1px solid var(--border-hover);
1363
+ border-radius: 4px;
1364
+ background: var(--surface-1);
1365
+ position: relative;
1366
+ cursor: pointer;
1367
+ }
1368
+ .cm-checkbox:checked {
1369
+ background: var(--accent);
1370
+ border-color: var(--accent);
1371
+ }
1372
+ .cm-checkbox:checked::after {
1373
+ content: "";
1374
+ position: absolute;
1375
+ left: 4px;
1376
+ top: 1px;
1377
+ width: 4px;
1378
+ height: 8px;
1379
+ border: solid var(--text-inverse);
1380
+ border-width: 0 2px 2px 0;
1381
+ transform: rotate(45deg);
1382
+ }
1383
+ .cm-checkbox:focus-visible {
1384
+ outline: 2px solid var(--focus-ring);
1385
+ outline-offset: 1px;
1386
+ }
1387
+ .field textarea { resize: vertical; min-height: 84px; }
1388
+ .settings-save {
1389
+ border: 1px solid var(--control-border);
1390
+ background: var(--control-bg);
1391
+ color: var(--control-text);
1392
+ padding: var(--sp-2) var(--sp-4);
1393
+ border-radius: var(--radius-md);
1394
+ font-family: var(--font-sans);
1395
+ font-size: 12px;
1396
+ cursor: pointer;
1397
+ transition: background 0.15s ease;
1398
+ }
1399
+ .settings-save:hover { background: var(--control-hover-bg); }
1400
+ .settings-save:disabled { opacity: 0.6; cursor: not-allowed; }
1401
+ .settings-note { color: var(--text-tertiary); font-size: 12px; }
1402
+
1403
+ /* ── Utilities ─────────────────────────────────────────── */
1404
+
1405
+ .small { color: var(--text-tertiary); font-size: 12px; }
1406
+ .mono { font-family: var(--font-mono); font-size: 12px; }
1407
+
1408
+ /* ── Animations ────────────────────────────────────────── */
1409
+
1410
+ @keyframes fadeUp {
1411
+ from { transform: translateY(8px); opacity: 0; }
1412
+ to { transform: translateY(0); opacity: 1; }
1413
+ }
1414
+
1415
+ @keyframes pulse {
1416
+ 0%, 100% { transform: scale(1); opacity: 0.9; }
1417
+ 50% { transform: scale(1.3); opacity: 0.5; }
1418
+ }
1419
+
1420
+ @keyframes newPulse {
1421
+ 0% { box-shadow: 0 0 0 rgba(26, 107, 86, 0); }
1422
+ 40% { box-shadow: 0 0 0 3px var(--accent-subtle); }
1423
+ 100% { box-shadow: 0 0 0 rgba(26, 107, 86, 0); }
1424
+ }
1425
+
1426
+ /* ── Responsive ────────────────────────────────────────── */
1427
+
1428
+ @media (max-width: 900px) {
1429
+ .tab-panel { padding: var(--sp-4); }
1430
+ .summary-row { grid-template-columns: 1fr; }
1431
+ .feed-card-header { flex-wrap: wrap; }
1432
+ .feed-actions { width: 100%; justify-content: flex-end; }
1433
+ .actor-row { align-items: stretch; flex-direction: column; }
1434
+ .peer-scope-row { grid-template-columns: 1fr; }
1435
+ .feed-visibility-controls { align-items: stretch; }
1436
+ .feed-visibility-select { width: 100%; }
1437
+ }
1438
+
1439
+ @media (max-width: 520px) {
1440
+ header { padding: var(--sp-3) var(--sp-4); }
1441
+ .tab-bar { padding: 0 var(--sp-4); }
1442
+ .modal { padding: var(--sp-3); }
1443
+ .modal-card { padding: var(--sp-4); border-radius: var(--radius-lg); }
1444
+ }
1445
+
1446
+ @media (prefers-reduced-motion: reduce) {
1447
+ *, *::before, *::after {
1448
+ animation-duration: 0.01ms !important;
1449
+ animation-iteration-count: 1 !important;
1450
+ transition-duration: 0.01ms !important;
1451
+ }
1452
+ }
1453
+ </style>
1454
+ </head>
1455
+ <body>
1456
+ <!-- ── Header ──────────────────────────────────────────── -->
1457
+ <header>
1458
+ <div class="header-row">
1459
+ <div class="header-left">
1460
+ <div class="header-brand">
1461
+ <span class="logo" aria-hidden="true">
1462
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="24" height="24" shape-rendering="crispEdges" aria-hidden="true">
1463
+ <path d="M3 8h13v13H3z" fill="none" stroke="currentColor" stroke-width="2.8" opacity="0.18"/>
1464
+ <path d="M4.5 6.5h13v13h-13z" fill="none" stroke="currentColor" stroke-width="2.8" opacity="0.30"/>
1465
+ <path d="M6 5h13v13H6z" fill="none" stroke="currentColor" stroke-width="2.8" opacity="0.56"/>
1466
+ <path d="M7.5 3.5h13v13h-13z" fill="none" stroke="currentColor" stroke-width="2.8" opacity="0.82"/>
1467
+ </svg>
1468
+ </span>
1469
+ <span class="header-title">codemem</span>
1470
+ <span class="health-dot status-healthy" id="healthDot" title="Healthy"></span>
1471
+ </div>
1472
+ <span class="refresh-status" id="refreshStatus">refreshing…</span>
1473
+ <span class="sr-only" id="refreshAnnouncer" aria-live="polite"></span>
1474
+ </div>
1475
+ <div class="header-right">
1476
+ <span class="meta-line" id="metaLine"></span>
1477
+ <select class="project-filter" id="projectFilter" aria-label="Project filter">
1478
+ <option value="">All Projects</option>
1479
+ </select>
1480
+ <label class="sr-only" for="themeSelect">Viewer theme</label>
1481
+ <select class="theme-select" id="themeSelect" aria-label="Viewer theme"></select>
1482
+ <button class="settings-button" id="settingsButton">Settings</button>
1483
+ </div>
1484
+ </div>
1485
+ </header>
1486
+
1487
+ <!-- ── Tab bar ─────────────────────────────────────────── -->
1488
+ <nav class="tab-bar" aria-label="Main navigation">
1489
+ <button class="tab-btn active" id="tabBtn-feed">Feed</button>
1490
+ <button class="tab-btn" id="tabBtn-health">Health</button>
1491
+ <button class="tab-btn" id="tabBtn-sync">Sync</button>
1492
+ </nav>
1493
+ <div class="app-notice" id="globalNotice" hidden aria-live="polite"></div>
1494
+
1495
+ <!-- ── Settings modal ──────────────────────────────────── -->
1496
+ <div class="modal-backdrop" id="settingsBackdrop" hidden></div>
1497
+ <div class="modal" id="settingsModal" hidden role="dialog" aria-modal="true" aria-labelledby="settingsTitle" aria-describedby="settingsDescription" tabindex="-1">
1498
+ <div class="modal-card">
1499
+ <div class="modal-header">
1500
+ <h2 id="settingsTitle">Memory & model settings</h2>
1501
+ <button class="modal-close" id="settingsClose" aria-label="Close settings">close</button>
1502
+ </div>
1503
+ <div class="modal-body">
1504
+ <div class="small" id="settingsDescription">Configure connection, authentication, processing, and sync behavior.</div>
1505
+ <div class="settings-tabs" role="tablist" aria-label="Settings sections">
1506
+ <button class="settings-tab active" id="settingsTabObserver" data-settings-tab="observer" role="tab" aria-selected="true">Connection</button>
1507
+ <button class="settings-tab" id="settingsTabQueue" data-settings-tab="queue" role="tab" aria-selected="false">Processing</button>
1508
+ <button class="settings-tab" id="settingsTabSync" data-settings-tab="sync" role="tab" aria-selected="false">Device Sync</button>
1509
+ </div>
1510
+ <div class="settings-advanced-toolbar field-checkbox">
1511
+ <input class="cm-checkbox" id="settingsAdvancedToggle" type="checkbox" />
1512
+ <label for="settingsAdvancedToggle">Show advanced controls</label>
1513
+ <button class="help-icon" type="button" data-tooltip="Advanced controls include JSON fields, tuning values, and network overrides." aria-label="About advanced controls">?</button>
1514
+ </div>
1515
+
1516
+ <div class="settings-panel active" id="settingsPanelObserver" data-settings-panel="observer">
1517
+ <div id="observerStatusBanner" class="observer-status-banner" hidden></div>
1518
+ <div class="settings-group">
1519
+ <h3 class="settings-group-title">Connection</h3>
1520
+ <div class="field">
1521
+ <div class="field-label">
1522
+ <label for="observerProvider">Model provider</label>
1523
+ <button class="help-icon" type="button" data-tooltip="Choose where model requests are sent. Use auto for recommended defaults." aria-label="About model provider">?</button>
1524
+ </div>
1525
+ <select id="observerProvider"><option value="">auto (default)</option></select>
1526
+ <div class="small">`auto` uses recommended defaults for the selected connection mode.</div>
1527
+ </div>
1528
+ <div class="field">
1529
+ <div class="field-label">
1530
+ <label for="observerModel">Model</label>
1531
+ <button class="help-icon" type="button" data-tooltip="Leave blank to use a recommended model for your selected mode/provider." aria-label="About model defaults">?</button>
1532
+ </div>
1533
+ <input id="observerModel" placeholder="leave empty for default" />
1534
+ <div class="small">Default: `gpt-5.1-codex-mini` for Direct API; `claude-4.5-haiku` for Local Claude session.</div>
1535
+ <div class="small" id="observerModelHint"></div>
1536
+ </div>
1537
+ <div class="field">
1538
+ <div class="field-label">
1539
+ <label for="observerRuntime">Connection mode</label>
1540
+ <button class="help-icon" type="button" data-tooltip="Direct API uses provider credentials. Local Claude session uses local Claude runtime auth." aria-label="About connection mode">?</button>
1541
+ </div>
1542
+ <select id="observerRuntime">
1543
+ <option value="api_http">Direct API (default)</option>
1544
+ <option value="claude_sidecar">Local Claude session</option>
1545
+ </select>
1546
+ <div class="small">Switch between provider API credentials and local Claude session auth.</div>
1547
+ </div>
1548
+ <div class="field settings-advanced" hidden>
1549
+ <label for="claudeCommand">Claude command (JSON argv)</label>
1550
+ <textarea id="claudeCommand" rows="2" placeholder='["claude"]'></textarea>
1551
+ <div class="small">Used by `claude_sidecar` runtime. Example wrapper: `["wrapper", "claude", "--"]`.</div>
1552
+ </div>
1553
+ <div class="field settings-advanced" hidden>
1554
+ <label for="observerMaxChars">Request size limit (chars)</label>
1555
+ <input id="observerMaxChars" type="number" min="1" />
1556
+ <div class="small" id="observerMaxCharsHint"></div>
1557
+ </div>
1558
+ </div>
1559
+
1560
+ <div class="settings-group">
1561
+ <h3 class="settings-group-title">Authentication</h3>
1562
+ <div class="field">
1563
+ <div class="field-label">
1564
+ <label for="observerAuthSource">Authentication method</label>
1565
+ <button class="help-icon" type="button" data-tooltip="Choose how credentials are resolved: environment, file, command, or none." aria-label="About authentication method">?</button>
1566
+ </div>
1567
+ <select id="observerAuthSource">
1568
+ <option value="auto">auto (default)</option>
1569
+ <option value="env">env</option>
1570
+ <option value="file">file</option>
1571
+ <option value="command">command</option>
1572
+ <option value="none">none</option>
1573
+ </select>
1574
+ <div class="small">Use `auto` unless you need a specific token source.</div>
1575
+ </div>
1576
+ <div class="field" id="observerAuthFileField" hidden>
1577
+ <label for="observerAuthFile">Token file path</label>
1578
+ <input id="observerAuthFile" placeholder="~/.codemem/work-token.txt" />
1579
+ <div class="small">Reads token text from this file when method is `file`.</div>
1580
+ </div>
1581
+ <div class="field" id="observerAuthCommandField" hidden>
1582
+ <div class="field-label">
1583
+ <label for="observerAuthCommand">Token command</label>
1584
+ <button class="help-icon" type="button" data-tooltip="Runs this command and uses stdout as the token. JSON argv only, no shell parsing." aria-label="About token command">?</button>
1585
+ </div>
1586
+ <textarea id="observerAuthCommand" rows="3" placeholder='["iap-auth", "--audience", "gateway"]'></textarea>
1587
+ <div class="small">When method is `command`, command stdout is used as the token.</div>
1588
+ </div>
1589
+ <div class="small" id="observerAuthCommandNote" hidden>
1590
+ Command format: JSON string array, e.g. `["iap-auth", "--audience", "gateway"]`.
1591
+ </div>
1592
+ <div class="field settings-advanced" hidden>
1593
+ <label for="observerAuthTimeoutMs">Token command timeout (ms)</label>
1594
+ <input id="observerAuthTimeoutMs" type="number" min="1" />
1595
+ </div>
1596
+ <div class="field settings-advanced" hidden>
1597
+ <label for="observerAuthCacheTtlS">Token cache time (s)</label>
1598
+ <input id="observerAuthCacheTtlS" type="number" min="0" />
1599
+ </div>
1600
+ <div class="field settings-advanced" hidden>
1601
+ <div class="field-label">
1602
+ <label for="observerHeaders">Request headers (JSON)</label>
1603
+ <button class="help-icon" type="button" data-tooltip="Optional extra headers. Supports templates like ${auth.token}, ${auth.type}, ${auth.source}." aria-label="About request headers">?</button>
1604
+ </div>
1605
+ <textarea id="observerHeaders" rows="4" placeholder='{"Authorization":"Bearer ${auth.token}"}'></textarea>
1606
+ <div class="small">Template variables: `${auth.token}`, `${auth.type}`, `${auth.source}`.</div>
1607
+ </div>
1608
+ </div>
1609
+ </div>
1610
+
1611
+ <div class="settings-panel" id="settingsPanelQueue" data-settings-panel="queue">
1612
+ <div class="settings-group">
1613
+ <h3 class="settings-group-title">Processing</h3>
1614
+ <div class="field">
1615
+ <div class="field-label">
1616
+ <label for="rawEventsSweeperIntervalS">Background processing interval (seconds)</label>
1617
+ <button class="help-icon" type="button" data-tooltip="How often codemem checks for queued events to process in the background." aria-label="About background processing interval">?</button>
1618
+ </div>
1619
+ <input id="rawEventsSweeperIntervalS" type="number" min="1" />
1620
+ <div class="small">How often background flush checks pending raw events.</div>
1621
+ </div>
1622
+ </div>
1623
+ <div class="settings-group settings-advanced" hidden>
1624
+ <h3 class="settings-group-title">Context Pack Limits</h3>
1625
+ <div class="field settings-advanced" hidden>
1626
+ <label for="packObservationLimit">Observation limit</label>
1627
+ <input id="packObservationLimit" type="number" min="1" />
1628
+ <div class="small">Default number of observations to include in a pack.</div>
1629
+ </div>
1630
+ <div class="field settings-advanced" hidden>
1631
+ <label for="packSessionLimit">Session summary limit</label>
1632
+ <input id="packSessionLimit" type="number" min="1" />
1633
+ <div class="small">Default number of session summaries to include in a pack.</div>
1634
+ </div>
1635
+ </div>
1636
+ </div>
1637
+
1638
+ <div class="settings-panel" id="settingsPanelSync" data-settings-panel="sync">
1639
+ <div class="settings-group">
1640
+ <h3 class="settings-group-title">Device Sync</h3>
1641
+ <div class="field field-checkbox"><input class="cm-checkbox" id="syncEnabled" type="checkbox" /><label for="syncEnabled">Enable sync</label></div>
1642
+ <div class="field"><label for="syncInterval">Sync interval (seconds)</label><input id="syncInterval" type="number" min="10" /></div>
1643
+ <div class="field settings-advanced" hidden><label for="syncHost">Sync host</label><input id="syncHost" placeholder="127.0.0.1" /></div>
1644
+ <div class="field settings-advanced" hidden><label for="syncPort">Sync port</label><input id="syncPort" type="number" min="1" /></div>
1645
+ <div class="field field-checkbox settings-advanced" hidden><input class="cm-checkbox" id="syncMdns" type="checkbox" /><label for="syncMdns">Enable mDNS discovery</label></div>
1646
+ <div class="field">
1647
+ <label for="syncCoordinatorUrl">Coordinator URL</label>
1648
+ <input id="syncCoordinatorUrl" placeholder="https://coord.example.com" />
1649
+ <div class="small">Optional self-hosted/operator-run discovery service. Direct peer-to-peer sync remains the data path.</div>
1650
+ </div>
1651
+ <div class="field">
1652
+ <label for="syncCoordinatorGroup">Coordinator group</label>
1653
+ <input id="syncCoordinatorGroup" placeholder="nerdworld" />
1654
+ <div class="small">Discovery namespace for peers using the same coordinator.</div>
1655
+ </div>
1656
+ <div class="field settings-advanced" hidden>
1657
+ <label for="syncCoordinatorTimeout">Coordinator timeout (seconds)</label>
1658
+ <input id="syncCoordinatorTimeout" type="number" min="1" />
1659
+ </div>
1660
+ <div class="field settings-advanced" hidden>
1661
+ <label for="syncCoordinatorPresenceTtl">Presence TTL (seconds)</label>
1662
+ <input id="syncCoordinatorPresenceTtl" type="number" min="1" />
1663
+ </div>
1664
+ </div>
1665
+ </div>
1666
+
1667
+ <div class="small mono" id="settingsPath"></div>
1668
+ <div class="small" id="settingsEffective"></div>
1669
+ <div class="settings-note" id="settingsOverrides">Some values are controlled outside this screen and take priority.</div>
1670
+ </div>
1671
+ <div class="modal-footer">
1672
+ <div class="small" id="settingsStatus">Ready</div>
1673
+ <button class="settings-save" id="settingsSave">Save</button>
1674
+ </div>
1675
+ </div>
1676
+ </div>
1677
+
1678
+ <!-- ── Tab: Feed ───────────────────────────────────────── -->
1679
+ <div class="tab-panel" id="tab-feed">
1680
+ <div class="feed-controls">
1681
+ <div class="section-meta" id="feedMeta">Loading memories…</div>
1682
+ <div class="feed-controls-right">
1683
+ <input class="feed-search" id="feedSearch" placeholder="Search title, body, tags…" />
1684
+ <div class="feed-toggle" id="feedScopeToggle">
1685
+ <button class="toggle-button" data-filter="all">All</button>
1686
+ <button class="toggle-button" data-filter="mine">My memories</button>
1687
+ <button class="toggle-button" data-filter="theirs">Other actors</button>
1688
+ </div>
1689
+ <div class="feed-toggle" id="feedTypeToggle">
1690
+ <button class="toggle-button" data-filter="all">All</button>
1691
+ <button class="toggle-button" data-filter="observations">Observations</button>
1692
+ <button class="toggle-button" data-filter="summaries">Summaries</button>
1693
+ </div>
1694
+ </div>
1695
+ </div>
1696
+ <div class="feed-list" id="feedList"></div>
1697
+ </div>
1698
+
1699
+ <!-- ── Tab: Health ─────────────────────────────────────── -->
1700
+ <div class="tab-panel" id="tab-health" hidden>
1701
+ <div class="card" style="animation-delay: 0s">
1702
+ <div class="section-header">
1703
+ <h2>System health</h2>
1704
+ </div>
1705
+ <div class="section-meta" id="healthMeta">Assessing health signals…</div>
1706
+ <div class="health-actions" id="healthActions"></div>
1707
+ <div class="grid-2" id="healthGrid"></div>
1708
+ </div>
1709
+
1710
+ <div class="summary-row">
1711
+ <div class="card" style="animation-delay: 0.05s">
1712
+ <h2>Stats</h2>
1713
+ <div class="grid-2" id="statsGrid"></div>
1714
+ </div>
1715
+ <div class="card" style="animation-delay: 0.1s">
1716
+ <h2>Current session</h2>
1717
+ <div class="section-meta" id="sessionMeta">No injections yet</div>
1718
+ <div class="grid-2" id="sessionGrid"></div>
1719
+ </div>
1720
+ </div>
1721
+ </div>
1722
+
1723
+ <!-- ── Tab: Sync ───────────────────────────────────────── -->
1724
+ <div class="tab-panel" id="tab-sync" hidden>
1725
+
1726
+ <!-- 1. Team sync — primary, always visible -->
1727
+ <div class="card" id="syncTeamCard">
1728
+ <div class="section-header">
1729
+ <h2>Team sync</h2>
1730
+ <div class="section-actions">
1731
+ <button class="settings-button" id="syncNowButton">Sync now</button>
1732
+ </div>
1733
+ </div>
1734
+ <div class="section-meta" id="syncTeamMeta"></div>
1735
+ <div class="sync-skeleton" id="syncTeamSkeleton" role="status" aria-label="Loading team sync">
1736
+ <div class="sync-skeleton-line medium"></div>
1737
+ <div class="sync-skeleton-block"></div>
1738
+ <div class="sync-skeleton-line short"></div>
1739
+ </div>
1740
+ <div id="syncSetupPanel">
1741
+ <div id="syncJoinSection">
1742
+ <h3 class="settings-group-title">Join a team</h3>
1743
+ <div class="section-meta">Have an invite? Paste it here.</div>
1744
+ <div class="actor-create-row" id="syncJoinPanel">
1745
+ <label for="syncJoinInvite" class="sr-only">Team invite</label>
1746
+ <textarea class="feed-search" id="syncJoinInvite" placeholder="Paste team invite or codemem:// link"></textarea>
1747
+ <button class="settings-button" id="syncJoinButton">Join team</button>
1748
+ </div>
1749
+ </div>
1750
+ <div id="syncAdminSection">
1751
+ <button class="sync-toggle-admin" id="syncToggleAdmin" aria-expanded="false" aria-controls="syncInvitePanel">Set up a new team instead…</button>
1752
+ <div id="syncInvitePanel" hidden>
1753
+ <h3 class="settings-group-title" style="margin-top:12px">Create a team</h3>
1754
+ <div class="section-meta">Generate an invite to share with teammates.</div>
1755
+ <div class="actor-create-row">
1756
+ <label for="syncInviteGroup" class="sr-only">Team name</label>
1757
+ <input class="peer-scope-input" id="syncInviteGroup" placeholder="Team name (e.g. my-team)" />
1758
+ <label for="syncInvitePolicy" class="sr-only">Join policy</label>
1759
+ <select class="sync-actor-select" id="syncInvitePolicy">
1760
+ <option value="auto_admit">Auto-admit</option>
1761
+ <option value="approval_required">Approval required</option>
1762
+ </select>
1763
+ <div class="sync-ttl-group">
1764
+ <label for="syncInviteTtl">Expires in</label>
1765
+ <input class="peer-scope-input" id="syncInviteTtl" type="number" min="1" value="24" style="width:64px" />
1766
+ <label>hours</label>
1767
+ </div>
1768
+ <button class="settings-button" id="syncCreateInviteButton">Create invite</button>
1769
+ </div>
1770
+ <label for="syncInviteOutput" class="sr-only">Generated invite</label>
1771
+ <textarea class="feed-search" id="syncInviteOutput" placeholder="Invite will appear here" readonly hidden></textarea>
1772
+ </div>
1773
+ </div>
1774
+ </div>
1775
+ <div class="actor-list" id="syncTeamStatus" aria-live="polite"></div>
1776
+ <div class="actor-list" id="syncJoinRequests"></div>
1777
+ <div class="sync-actions" id="syncTeamActions" hidden></div>
1778
+ </div>
1779
+
1780
+ <!-- 2. People — actors, devices, sharing, legacy claims -->
1781
+ <div class="card" style="animation-delay: 0.04s">
1782
+ <h2>People</h2>
1783
+
1784
+ <h3 class="settings-group-title">Actors</h3>
1785
+ <div class="section-meta" id="syncActorsMeta">Create and rename actors here. Assign each device below.</div>
1786
+ <div class="actor-create-row">
1787
+ <label for="syncActorCreateInput" class="sr-only">Actor name</label>
1788
+ <input class="peer-scope-input" id="syncActorCreateInput" placeholder="Create a named actor" />
1789
+ <button class="settings-button" id="syncActorCreateButton">Create actor</button>
1790
+ </div>
1791
+ <div class="actor-list" id="syncActorsList"></div>
1792
+ <div class="sync-skeleton" id="syncActorsSkeleton" role="status" aria-label="Loading actors">
1793
+ <div class="sync-skeleton-line long"></div>
1794
+ <div class="sync-skeleton-line medium"></div>
1795
+ </div>
1796
+
1797
+ <h3 class="settings-group-title" style="margin-top: 20px">Devices</h3>
1798
+ <div class="peer-list" id="syncPeers"></div>
1799
+ <div class="sync-skeleton" id="syncPeersSkeleton" role="status" aria-label="Loading devices">
1800
+ <div class="sync-skeleton-block"></div>
1801
+ <div class="sync-skeleton-block"></div>
1802
+ </div>
1803
+
1804
+ <div id="syncSharingReview" hidden>
1805
+ <h3 class="settings-group-title" style="margin-top: 20px">What teammates will receive</h3>
1806
+ <div class="section-meta" id="syncSharingReviewMeta"></div>
1807
+ <div class="actor-list" id="syncSharingReviewList"></div>
1808
+ </div>
1809
+
1810
+ <div id="syncLegacyClaims" hidden>
1811
+ <h3 class="settings-group-title" style="margin-top: 20px">Legacy device history</h3>
1812
+ <div class="peer-meta" id="syncLegacyClaimsMeta"></div>
1813
+ <div class="peer-actions sync-legacy-claim-row">
1814
+ <label for="syncLegacyDeviceSelect" class="sr-only">Legacy device</label>
1815
+ <select class="sync-legacy-select" id="syncLegacyDeviceSelect"></select>
1816
+ <button class="sync-legacy-button" id="syncLegacyClaimButton">Attach device history</button>
1817
+ </div>
1818
+ </div>
1819
+ </div>
1820
+
1821
+ <!-- 3. Diagnostics — status, pairing, attempts -->
1822
+ <div class="card" style="animation-delay: 0.06s">
1823
+ <div class="section-header">
1824
+ <h2>Diagnostics</h2>
1825
+ <div class="section-actions">
1826
+ <button class="settings-button" id="syncPairingToggle">Show pairing</button>
1827
+ <label class="small" style="display:inline-flex;align-items:center;gap:6px;">
1828
+ <input id="syncRedact" type="checkbox" checked />
1829
+ Redact
1830
+ </label>
1831
+ </div>
1832
+ </div>
1833
+ <div class="section-meta" id="syncMeta">Loading sync status…</div>
1834
+ <div class="sync-skeleton" id="syncDiagSkeleton" role="status" aria-label="Loading diagnostics">
1835
+ <div class="sync-skeleton-line long"></div>
1836
+ <div class="sync-skeleton-line medium"></div>
1837
+ <div class="sync-skeleton-line short"></div>
1838
+ </div>
1839
+ <div class="sync-actions" id="syncActions"></div>
1840
+ <div class="grid-2" id="syncStatusGrid"></div>
1841
+
1842
+ <div class="pairing-card" id="syncPairing" hidden>
1843
+ <div class="peer-title">
1844
+ <strong>Pairing command</strong>
1845
+ <div class="peer-actions"><button id="pairingCopy">Copy command</button></div>
1846
+ </div>
1847
+ <div class="pairing-body">
1848
+ <pre id="pairingPayload" style="user-select:all">Loading…</pre>
1849
+ </div>
1850
+ <div class="peer-meta" id="pairingHint">Copy and run on the other device. Use --include/--exclude to control which projects sync.</div>
1851
+ </div>
1852
+
1853
+ <h3 class="settings-group-title" style="margin-top: 20px">Recent sync attempts</h3>
1854
+ <div class="attempts-list" id="syncAttempts"></div>
1855
+ </div>
1856
+ </div>
1857
+
1858
+ <script src="/assets/app.js"></script>
1859
+ </body>
1860
+ </html>