@jhizzard/termdeck 0.9.0 → 0.10.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.
@@ -3223,3 +3223,430 @@
3223
3223
  white-space: pre-wrap;
3224
3224
  margin: 0 0 12px;
3225
3225
  }
3226
+
3227
+ /* ===== Knowledge graph view (Sprint 38 T4) ===== */
3228
+ /* Standalone /graph.html. The body opts in via .graph-page so dashboard
3229
+ styles aren't inadvertently applied (no terminal grid, no panel chrome). */
3230
+
3231
+ body.graph-page {
3232
+ margin: 0;
3233
+ padding: 0;
3234
+ background: var(--tg-bg);
3235
+ color: var(--tg-text);
3236
+ font-family: var(--tg-sans);
3237
+ overflow: hidden;
3238
+ display: flex;
3239
+ flex-direction: column;
3240
+ height: 100vh;
3241
+ }
3242
+
3243
+ .graph-topbar {
3244
+ display: flex;
3245
+ align-items: center;
3246
+ gap: 16px;
3247
+ padding: 8px 16px;
3248
+ background: var(--tg-surface);
3249
+ border-bottom: 1px solid var(--tg-border);
3250
+ flex-shrink: 0;
3251
+ min-height: 44px;
3252
+ }
3253
+ .graph-tb-left {
3254
+ display: flex;
3255
+ align-items: center;
3256
+ gap: 8px;
3257
+ }
3258
+ .graph-tb-back {
3259
+ display: inline-flex;
3260
+ align-items: center;
3261
+ gap: 6px;
3262
+ color: var(--tg-text-bright);
3263
+ text-decoration: none;
3264
+ font-weight: 600;
3265
+ padding: 4px 8px;
3266
+ border-radius: var(--tg-radius-sm);
3267
+ transition: background 120ms;
3268
+ }
3269
+ .graph-tb-back:hover { background: var(--tg-surface-hover); }
3270
+ .graph-tb-divider {
3271
+ color: var(--tg-text-dim);
3272
+ }
3273
+ .graph-tb-title {
3274
+ color: var(--tg-text);
3275
+ font-weight: 500;
3276
+ }
3277
+ .graph-tb-controls {
3278
+ display: flex;
3279
+ align-items: center;
3280
+ gap: 12px;
3281
+ flex: 1;
3282
+ justify-content: center;
3283
+ }
3284
+ .graph-tb-control {
3285
+ display: inline-flex;
3286
+ align-items: center;
3287
+ gap: 6px;
3288
+ font-size: 12px;
3289
+ color: var(--tg-text-dim);
3290
+ }
3291
+ .graph-tb-control select,
3292
+ .graph-tb-control input {
3293
+ background: var(--tg-bg);
3294
+ color: var(--tg-text);
3295
+ border: 1px solid var(--tg-border);
3296
+ border-radius: var(--tg-radius-sm);
3297
+ padding: 5px 8px;
3298
+ font-family: var(--tg-mono);
3299
+ font-size: 12px;
3300
+ min-width: 140px;
3301
+ }
3302
+ .graph-tb-control select:focus,
3303
+ .graph-tb-control input:focus {
3304
+ outline: none;
3305
+ border-color: var(--tg-accent);
3306
+ }
3307
+ .graph-tb-search-wrap input {
3308
+ min-width: 220px;
3309
+ }
3310
+ .graph-tb-btn {
3311
+ background: var(--tg-bg);
3312
+ color: var(--tg-text);
3313
+ border: 1px solid var(--tg-border);
3314
+ border-radius: var(--tg-radius-sm);
3315
+ padding: 5px 10px;
3316
+ font-size: 12px;
3317
+ font-family: var(--tg-mono);
3318
+ cursor: pointer;
3319
+ transition: all 120ms;
3320
+ }
3321
+ .graph-tb-btn:hover {
3322
+ background: var(--tg-surface-hover);
3323
+ border-color: var(--tg-border-active);
3324
+ }
3325
+ .graph-tb-right {
3326
+ display: flex;
3327
+ gap: 12px;
3328
+ align-items: center;
3329
+ }
3330
+ .graph-tb-stat {
3331
+ font-size: 11px;
3332
+ font-family: var(--tg-mono);
3333
+ color: var(--tg-text-dim);
3334
+ padding: 3px 8px;
3335
+ background: var(--tg-bg);
3336
+ border: 1px solid var(--tg-border);
3337
+ border-radius: 999px;
3338
+ }
3339
+
3340
+ .graph-filters {
3341
+ display: flex;
3342
+ gap: 6px;
3343
+ padding: 8px 16px;
3344
+ background: var(--tg-surface);
3345
+ border-bottom: 1px solid var(--tg-border);
3346
+ flex-wrap: wrap;
3347
+ flex-shrink: 0;
3348
+ align-items: center;
3349
+ }
3350
+ .gf-chip {
3351
+ display: inline-flex;
3352
+ align-items: center;
3353
+ gap: 6px;
3354
+ padding: 3px 9px 3px 7px;
3355
+ font-size: 11px;
3356
+ font-family: var(--tg-mono);
3357
+ color: var(--tg-text-dim);
3358
+ background: var(--tg-bg);
3359
+ border: 1px solid var(--tg-border);
3360
+ border-radius: 999px;
3361
+ cursor: pointer;
3362
+ transition: all 150ms;
3363
+ opacity: 0.55;
3364
+ }
3365
+ .gf-chip:hover { opacity: 1; }
3366
+ .gf-chip.active {
3367
+ opacity: 1;
3368
+ color: var(--tg-text-bright);
3369
+ background: var(--tg-surface-hover);
3370
+ }
3371
+ .gf-chip-dot {
3372
+ width: 8px;
3373
+ height: 8px;
3374
+ border-radius: 50%;
3375
+ flex-shrink: 0;
3376
+ }
3377
+ .gf-chip-count {
3378
+ color: var(--tg-text-dim);
3379
+ font-size: 10px;
3380
+ padding: 0 4px;
3381
+ background: rgba(255, 255, 255, 0.04);
3382
+ border-radius: 999px;
3383
+ }
3384
+ .gf-chip.active .gf-chip-count {
3385
+ color: var(--tg-text);
3386
+ background: rgba(255, 255, 255, 0.08);
3387
+ }
3388
+
3389
+ .graph-stage {
3390
+ position: relative;
3391
+ flex: 1;
3392
+ overflow: hidden;
3393
+ background: var(--tg-bg);
3394
+ }
3395
+ .graph-svg {
3396
+ display: block;
3397
+ width: 100%;
3398
+ height: 100%;
3399
+ cursor: grab;
3400
+ }
3401
+ .graph-svg:active {
3402
+ cursor: grabbing;
3403
+ }
3404
+ .graph-zoom-root {
3405
+ will-change: transform;
3406
+ }
3407
+ .graph-nodes circle {
3408
+ transition: stroke 200ms, opacity 200ms;
3409
+ }
3410
+ .graph-nodes circle:hover {
3411
+ stroke: #eef1ff;
3412
+ stroke-width: 2;
3413
+ }
3414
+ .graph-edges line {
3415
+ transition: stroke-opacity 200ms;
3416
+ }
3417
+
3418
+ @keyframes graph-node-pulse {
3419
+ 0% { stroke-width: 1.2; }
3420
+ 50% { stroke-width: 4; stroke: #eef1ff; }
3421
+ 100% { stroke-width: 1.2; }
3422
+ }
3423
+ .graph-node-pulse {
3424
+ animation: graph-node-pulse 1.4s ease-in-out infinite;
3425
+ }
3426
+
3427
+ .graph-loading,
3428
+ .graph-empty {
3429
+ position: absolute;
3430
+ inset: 0;
3431
+ display: flex;
3432
+ flex-direction: column;
3433
+ align-items: center;
3434
+ justify-content: center;
3435
+ gap: 12px;
3436
+ color: var(--tg-text-dim);
3437
+ font-family: var(--tg-mono);
3438
+ pointer-events: none;
3439
+ padding: 24px;
3440
+ text-align: center;
3441
+ }
3442
+ .graph-loading-spinner {
3443
+ width: 28px;
3444
+ height: 28px;
3445
+ border-radius: 50%;
3446
+ border: 2px solid var(--tg-border);
3447
+ border-top-color: var(--tg-accent);
3448
+ animation: graph-spin 720ms linear infinite;
3449
+ }
3450
+ @keyframes graph-spin {
3451
+ to { transform: rotate(360deg); }
3452
+ }
3453
+ .graph-empty h3 {
3454
+ color: var(--tg-text);
3455
+ margin: 0;
3456
+ font-size: 16px;
3457
+ font-family: var(--tg-sans);
3458
+ }
3459
+ .graph-empty p {
3460
+ max-width: 540px;
3461
+ line-height: 1.55;
3462
+ color: var(--tg-text-dim);
3463
+ margin: 0;
3464
+ }
3465
+ .graph-empty code {
3466
+ background: var(--tg-surface);
3467
+ padding: 1px 5px;
3468
+ border-radius: 3px;
3469
+ font-size: 11px;
3470
+ }
3471
+
3472
+ .graph-tooltip {
3473
+ position: fixed;
3474
+ pointer-events: none;
3475
+ background: rgba(15, 17, 23, 0.95);
3476
+ color: var(--tg-text);
3477
+ border: 1px solid var(--tg-border-active);
3478
+ border-radius: var(--tg-radius-sm);
3479
+ padding: 6px 10px;
3480
+ font-family: var(--tg-mono);
3481
+ font-size: 11px;
3482
+ backdrop-filter: blur(6px);
3483
+ box-shadow: 0 4px 14px rgba(0, 0, 0, 0.35);
3484
+ z-index: 50;
3485
+ max-width: 320px;
3486
+ }
3487
+
3488
+ /* Drawer — right-side overlay shown when a node is clicked. */
3489
+ .graph-drawer {
3490
+ position: absolute;
3491
+ top: 0;
3492
+ right: 0;
3493
+ width: 420px;
3494
+ max-width: 90vw;
3495
+ height: 100%;
3496
+ background: var(--tg-surface);
3497
+ border-left: 1px solid var(--tg-border);
3498
+ box-shadow: -8px 0 24px rgba(0, 0, 0, 0.35);
3499
+ display: flex;
3500
+ flex-direction: column;
3501
+ transform: translateX(100%);
3502
+ transition: transform 220ms ease-out;
3503
+ z-index: 30;
3504
+ }
3505
+ .graph-drawer.open {
3506
+ transform: translateX(0);
3507
+ }
3508
+ .graph-drawer-header {
3509
+ display: flex;
3510
+ align-items: center;
3511
+ justify-content: space-between;
3512
+ padding: 12px 14px;
3513
+ border-bottom: 1px solid var(--tg-border);
3514
+ flex-shrink: 0;
3515
+ }
3516
+ .gd-h-meta {
3517
+ display: flex;
3518
+ gap: 8px;
3519
+ align-items: center;
3520
+ font-family: var(--tg-mono);
3521
+ font-size: 11px;
3522
+ color: var(--tg-text-dim);
3523
+ }
3524
+ .gd-project {
3525
+ font-weight: 600;
3526
+ color: var(--tg-text-bright);
3527
+ }
3528
+ .gd-source-type {
3529
+ padding: 1px 6px;
3530
+ background: var(--tg-bg);
3531
+ border-radius: 3px;
3532
+ border: 1px solid var(--tg-border);
3533
+ }
3534
+ .gd-created::before {
3535
+ content: '·';
3536
+ margin-right: 6px;
3537
+ color: var(--tg-border-active);
3538
+ }
3539
+ .graph-drawer-close {
3540
+ background: transparent;
3541
+ border: 0;
3542
+ color: var(--tg-text-dim);
3543
+ font-size: 20px;
3544
+ line-height: 1;
3545
+ cursor: pointer;
3546
+ padding: 4px 8px;
3547
+ border-radius: var(--tg-radius-sm);
3548
+ }
3549
+ .graph-drawer-close:hover {
3550
+ color: var(--tg-text-bright);
3551
+ background: var(--tg-surface-hover);
3552
+ }
3553
+ .graph-drawer-body {
3554
+ flex: 1;
3555
+ overflow: auto;
3556
+ padding: 14px;
3557
+ }
3558
+ .gd-content {
3559
+ background: var(--tg-bg);
3560
+ border: 1px solid var(--tg-border);
3561
+ border-radius: var(--tg-radius-sm);
3562
+ padding: 12px;
3563
+ font-family: var(--tg-mono);
3564
+ font-size: 12px;
3565
+ color: var(--tg-text);
3566
+ white-space: pre-wrap;
3567
+ word-break: break-word;
3568
+ max-height: 40vh;
3569
+ overflow: auto;
3570
+ margin: 0 0 16px;
3571
+ }
3572
+ .gd-section {
3573
+ margin: 0 0 8px;
3574
+ font-size: 11px;
3575
+ text-transform: uppercase;
3576
+ letter-spacing: 0.5px;
3577
+ color: var(--tg-text-dim);
3578
+ font-family: var(--tg-mono);
3579
+ }
3580
+ .gd-neighbors {
3581
+ display: flex;
3582
+ flex-direction: column;
3583
+ gap: 4px;
3584
+ }
3585
+ .gd-neighbor {
3586
+ display: grid;
3587
+ grid-template-columns: 10px 1fr auto;
3588
+ align-items: center;
3589
+ gap: 8px;
3590
+ padding: 7px 8px;
3591
+ background: var(--tg-bg);
3592
+ border: 1px solid var(--tg-border);
3593
+ border-radius: var(--tg-radius-sm);
3594
+ cursor: pointer;
3595
+ text-align: left;
3596
+ transition: border-color 120ms, background 120ms;
3597
+ font-family: var(--tg-sans);
3598
+ color: var(--tg-text);
3599
+ }
3600
+ .gd-neighbor:hover {
3601
+ border-color: var(--tg-border-active);
3602
+ background: var(--tg-surface-hover);
3603
+ }
3604
+ .gd-neighbor-dot {
3605
+ width: 8px;
3606
+ height: 8px;
3607
+ border-radius: 50%;
3608
+ }
3609
+ .gd-neighbor-label {
3610
+ font-size: 12px;
3611
+ overflow: hidden;
3612
+ text-overflow: ellipsis;
3613
+ white-space: nowrap;
3614
+ }
3615
+ .gd-neighbor-meta {
3616
+ font-size: 10px;
3617
+ color: var(--tg-text-dim);
3618
+ font-family: var(--tg-mono);
3619
+ }
3620
+ .gd-empty {
3621
+ padding: 20px 12px;
3622
+ text-align: center;
3623
+ color: var(--tg-text-dim);
3624
+ font-size: 12px;
3625
+ background: var(--tg-bg);
3626
+ border: 1px dashed var(--tg-border);
3627
+ border-radius: var(--tg-radius-sm);
3628
+ }
3629
+ .graph-drawer-footer {
3630
+ display: flex;
3631
+ gap: 8px;
3632
+ padding: 10px 14px;
3633
+ border-top: 1px solid var(--tg-border);
3634
+ flex-shrink: 0;
3635
+ }
3636
+ .graph-drawer-action {
3637
+ flex: 1;
3638
+ background: var(--tg-bg);
3639
+ color: var(--tg-text);
3640
+ border: 1px solid var(--tg-border);
3641
+ border-radius: var(--tg-radius-sm);
3642
+ padding: 6px 10px;
3643
+ font-size: 12px;
3644
+ font-family: var(--tg-mono);
3645
+ cursor: pointer;
3646
+ transition: all 120ms;
3647
+ }
3648
+ .graph-drawer-action:hover {
3649
+ background: var(--tg-surface-hover);
3650
+ border-color: var(--tg-border-active);
3651
+ color: var(--tg-text-bright);
3652
+ }
@@ -0,0 +1,51 @@
1
+ // Flashback diagnostic ring buffer (Sprint 39 T1).
2
+ //
3
+ // Six decision points along the Flashback pipeline write structured events
4
+ // here so production-flow regressions surface as a readable timeline instead
5
+ // of a silent gate failure. The ring is in-memory and lost on restart by
6
+ // design — persistence is a Sprint-40+ concern. Public surface:
7
+ //
8
+ // log({ sessionId, event, ...fields }) — append one event
9
+ // snapshot({ sessionId?, eventType?, limit? }) — read back filtered tail
10
+ // _resetForTest() — test-only ring clear
11
+ //
12
+ // Event shape (all events): { ts, sessionId, event, ...event-specific fields }.
13
+ //
14
+ // Event types and their producers:
15
+ // pattern_match — session.js _detectErrors (PATTERNS.error /
16
+ // errorLineStart / shellError matched)
17
+ // error_detected — session.js _detectErrors at onErrorDetected
18
+ // entry, before rate-limit check
19
+ // rate_limit_blocked — session.js _detectErrors when 30s limiter rejects
20
+ // bridge_query — mnestra-bridge queryMnestra at call return
21
+ // bridge_result — mnestra-bridge queryMnestra at call return
22
+ // proactive_memory_emit — index.js onErrorDetected WS send block
23
+ //
24
+ // The route GET /api/flashback/diag (registered in index.js) returns
25
+ // snapshot() output as JSON for ad-hoc inspection by Joshua and consumption
26
+ // by T4's production-flow e2e test.
27
+
28
+ const RING_SIZE = 200;
29
+
30
+ let ring = [];
31
+
32
+ function log(event) {
33
+ ring.push({ ts: new Date().toISOString(), ...event });
34
+ if (ring.length > RING_SIZE) {
35
+ ring = ring.slice(-RING_SIZE);
36
+ }
37
+ }
38
+
39
+ function snapshot({ sessionId, eventType, limit = RING_SIZE } = {}) {
40
+ let out = ring;
41
+ if (sessionId) out = out.filter((e) => e.sessionId === sessionId);
42
+ if (eventType) out = out.filter((e) => e.event === eventType);
43
+ const cap = Math.max(1, Math.min(RING_SIZE, Number(limit) || RING_SIZE));
44
+ return out.slice(-cap);
45
+ }
46
+
47
+ function _resetForTest() {
48
+ ring = [];
49
+ }
50
+
51
+ module.exports = { log, snapshot, _resetForTest, RING_SIZE };