@co0ontty/wand 1.22.0 → 1.24.0

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.
@@ -3268,159 +3268,190 @@
3268
3268
  background: linear-gradient(180deg, rgba(255, 252, 248, 0.5) 0%, transparent 100%);
3269
3269
  }
3270
3270
 
3271
- .chat-overlay-controls {
3271
+ /* ===== Telegram 风格的"回到最新"胶囊 =====
3272
+ 规则:
3273
+ - 用户贴在底部时 → 胶囊隐藏,新消息自然出现。
3274
+ - 用户滚上去时 → 胶囊浮现在 chat-container 右下角,正好压在输入框上方边缘。
3275
+ - 没有未读时 → 紧凑圆形小胶囊,只显示一个回到底部的箭头。
3276
+ - 有未读时 → 胶囊横向展开成"↓ N 条新消息",行内显示数字。
3277
+ - 点胶囊 → 平滑滚回最新消息,未读分割线消失。 */
3278
+ .chat-unread-bubble {
3272
3279
  position: absolute;
3273
- top: 10px;
3274
- right: 10px;
3275
- display: inline-flex;
3276
- align-items: center;
3277
- z-index: 12;
3278
- pointer-events: auto;
3279
- opacity: 0;
3280
- transform: translateY(-4px);
3281
- transition: opacity 0.22s ease, transform 0.26s var(--ease-out-expo);
3282
- }
3283
-
3284
- .chat-container:hover .chat-overlay-controls,
3285
- .chat-overlay-controls:focus-within {
3286
- opacity: 1;
3287
- transform: translateY(0);
3288
- }
3289
-
3290
- .chat-follow-toggle {
3280
+ right: 14px;
3281
+ bottom: 10px;
3291
3282
  display: inline-flex;
3292
3283
  align-items: center;
3293
3284
  justify-content: center;
3294
- min-width: unset;
3295
- padding: 6px 11px;
3296
- color: var(--text-tertiary);
3297
- background: rgba(255, 250, 242, 0.7);
3298
- backdrop-filter: blur(12px);
3299
- -webkit-backdrop-filter: blur(12px);
3300
- border: 1px solid rgba(125, 91, 57, 0.1);
3285
+ gap: 6px;
3286
+ height: 32px;
3287
+ min-width: 32px;
3288
+ padding: 0 10px;
3289
+ box-sizing: border-box;
3290
+ border: 1px solid rgba(255, 255, 255, 0.72);
3301
3291
  border-radius: 999px;
3302
- box-shadow: 0 1px 4px rgba(89, 58, 32, 0.06);
3303
- cursor: pointer;
3304
- transition: all 0.2s ease;
3305
- }
3306
-
3307
- .chat-follow-toggle svg {
3308
- flex-shrink: 0;
3309
- opacity: 0.7;
3310
- transition: opacity 0.18s ease, transform 0.2s var(--ease-out-expo);
3311
- }
3312
-
3313
- .chat-follow-toggle.active {
3314
- color: var(--accent);
3315
- background: rgba(255, 250, 242, 0.88);
3316
- border-color: rgba(197, 101, 61, 0.18);
3317
- box-shadow:
3318
- 0 1px 4px rgba(89, 58, 32, 0.08),
3319
- 0 0 0 1px rgba(197, 101, 61, 0.06);
3320
- }
3321
-
3322
- .chat-follow-toggle.active svg {
3323
- opacity: 0.85;
3324
- stroke: var(--accent);
3325
- }
3326
-
3327
- .chat-follow-toggle:hover {
3328
- color: var(--text-primary);
3329
3292
  background: rgba(255, 252, 248, 0.92);
3330
- border-color: rgba(125, 91, 57, 0.16);
3331
- box-shadow: 0 2px 8px rgba(89, 58, 32, 0.1);
3332
- }
3333
-
3334
- .chat-follow-toggle:hover svg {
3335
- opacity: 0.8;
3336
- }
3337
-
3338
- .chat-follow-toggle.active:hover {
3339
- color: var(--accent-hover);
3340
- background: rgba(255, 248, 240, 0.94);
3341
- border-color: rgba(197, 101, 61, 0.24);
3342
- box-shadow: 0 2px 8px rgba(184, 92, 55, 0.12);
3343
- }
3344
-
3345
- .chat-follow-toggle.active:hover svg {
3346
- stroke: var(--accent-hover);
3347
- transform: translateY(1px);
3348
- }
3349
-
3350
- .chat-follow-toggle:active {
3351
- transform: scale(0.94);
3352
- transition-duration: 0.06s;
3353
- }
3354
-
3355
- .chat-jump-bottom {
3356
- position: absolute;
3357
- right: 14px;
3358
- bottom: 16px;
3359
- display: inline-flex;
3360
- align-items: center;
3361
- justify-content: center;
3362
- width: 34px;
3363
- height: 34px;
3364
- padding: 0;
3365
- border: 1px solid rgba(255, 255, 255, 0.65);
3366
- border-radius: 50%;
3367
- background: var(--accent);
3368
- color: #fff;
3293
+ color: var(--accent);
3294
+ font-size: 0.78rem;
3295
+ font-weight: 600;
3296
+ line-height: 1;
3297
+ letter-spacing: 0.01em;
3298
+ white-space: nowrap;
3299
+ backdrop-filter: blur(14px);
3300
+ -webkit-backdrop-filter: blur(14px);
3369
3301
  box-shadow:
3370
- 0 1px 2px rgba(89, 58, 32, 0.18),
3371
- 0 8px 22px rgba(184, 92, 55, 0.28),
3372
- inset 0 1px 0 rgba(255, 255, 255, 0.25);
3302
+ 0 1px 3px rgba(89, 58, 32, 0.12),
3303
+ 0 10px 24px rgba(184, 92, 55, 0.18),
3304
+ inset 0 1px 0 rgba(255, 255, 255, 0.6);
3373
3305
  cursor: pointer;
3374
3306
  z-index: 13;
3375
3307
  opacity: 0;
3376
- transform: translateY(10px) scale(0.86);
3308
+ transform: translateY(8px) scale(0.9);
3377
3309
  pointer-events: none;
3378
3310
  transition:
3379
3311
  opacity 0.26s var(--ease-out-expo),
3380
3312
  transform 0.32s var(--ease-spring),
3381
3313
  background 0.18s ease,
3382
3314
  border-color 0.18s ease,
3383
- box-shadow 0.2s ease;
3315
+ box-shadow 0.2s ease,
3316
+ color 0.18s ease,
3317
+ padding 0.22s var(--ease-out-expo);
3384
3318
  }
3385
3319
 
3386
- .chat-jump-bottom.visible {
3320
+ .chat-unread-bubble.visible {
3387
3321
  opacity: 1;
3388
3322
  transform: translateY(0) scale(1);
3389
3323
  pointer-events: auto;
3390
3324
  }
3391
3325
 
3392
- .chat-jump-bottom svg {
3326
+ /* 有未读时升级成"高亮色"——胶囊变成主题色实心,更显眼。 */
3327
+ .chat-unread-bubble.has-unread {
3328
+ background: var(--accent);
3329
+ color: #fff;
3330
+ border-color: rgba(255, 255, 255, 0.78);
3331
+ padding: 0 12px 0 10px;
3332
+ box-shadow:
3333
+ 0 1px 3px rgba(89, 58, 32, 0.18),
3334
+ 0 12px 28px rgba(184, 92, 55, 0.32),
3335
+ inset 0 1px 0 rgba(255, 255, 255, 0.28);
3336
+ }
3337
+
3338
+ .chat-unread-bubble-icon {
3339
+ display: inline-flex;
3340
+ align-items: center;
3341
+ justify-content: center;
3342
+ flex-shrink: 0;
3393
3343
  transition: transform 0.2s var(--ease-out-expo);
3394
3344
  }
3395
3345
 
3396
- .chat-jump-bottom:hover {
3346
+ .chat-unread-bubble:hover {
3347
+ background: rgba(255, 250, 242, 1);
3348
+ box-shadow:
3349
+ 0 2px 4px rgba(89, 58, 32, 0.16),
3350
+ 0 14px 30px rgba(184, 92, 55, 0.24),
3351
+ inset 0 1px 0 rgba(255, 255, 255, 0.7);
3352
+ }
3353
+
3354
+ .chat-unread-bubble.has-unread:hover {
3397
3355
  background: var(--accent-hover);
3398
- border-color: rgba(255, 255, 255, 0.85);
3399
3356
  box-shadow:
3400
- 0 2px 4px rgba(89, 58, 32, 0.20),
3401
- 0 12px 28px rgba(184, 92, 55, 0.38),
3357
+ 0 2px 5px rgba(89, 58, 32, 0.22),
3358
+ 0 14px 32px rgba(184, 92, 55, 0.42),
3402
3359
  inset 0 1px 0 rgba(255, 255, 255, 0.32);
3403
3360
  }
3404
3361
 
3405
- .chat-jump-bottom:hover svg {
3362
+ .chat-unread-bubble:hover .chat-unread-bubble-icon {
3406
3363
  transform: translateY(1.5px);
3407
3364
  }
3408
3365
 
3409
- .chat-jump-bottom:active {
3410
- transform: translateY(0) scale(0.92);
3411
- background: var(--accent-active);
3366
+ .chat-unread-bubble:active {
3367
+ transform: translateY(0) scale(0.94);
3412
3368
  transition-duration: 0.08s;
3413
3369
  }
3414
3370
 
3415
- .chat-jump-bottom:focus-visible {
3371
+ .chat-unread-bubble:focus-visible {
3416
3372
  outline: none;
3417
3373
  box-shadow:
3418
3374
  0 2px 4px rgba(89, 58, 32, 0.20),
3419
- 0 12px 28px rgba(184, 92, 55, 0.38),
3375
+ 0 12px 28px rgba(184, 92, 55, 0.32),
3420
3376
  inset 0 1px 0 rgba(255, 255, 255, 0.32),
3421
3377
  0 0 0 3px rgba(197, 101, 61, 0.32);
3422
3378
  }
3423
3379
 
3380
+ /* 未读计数:胶囊里行内显示,"↓ 3 条新消息"那种。
3381
+ 没有未读时 (.visible 不挂) 直接占 0 宽度,胶囊退化成圆形。 */
3382
+ .chat-unread-bubble-count {
3383
+ display: none;
3384
+ align-items: center;
3385
+ font-size: 0.78rem;
3386
+ font-weight: 600;
3387
+ line-height: 1;
3388
+ letter-spacing: 0.01em;
3389
+ pointer-events: none;
3390
+ }
3391
+
3392
+ .chat-unread-bubble-count.visible {
3393
+ display: inline-flex;
3394
+ animation: chatUnreadCountPop 0.22s var(--ease-out-back, cubic-bezier(0.34, 1.56, 0.64, 1));
3395
+ }
3396
+
3397
+ .chat-unread-bubble-count::after {
3398
+ content: " 条新消息";
3399
+ margin-left: 2px;
3400
+ font-weight: 500;
3401
+ opacity: 0.95;
3402
+ }
3403
+
3404
+ @keyframes chatUnreadCountPop {
3405
+ from { transform: scale(0.6); opacity: 0; }
3406
+ to { transform: scale(1); opacity: 1; }
3407
+ }
3408
+
3409
+ /* ===== 未读消息分隔线 =====
3410
+ 在第一条未读消息的"上方"渲染一条带文字的横线。
3411
+ column-reverse 下:分隔线在 DOM 里挂在第一条已读消息的前面(DOM 顺序:
3412
+ 新→旧),视觉上自然落在未读和已读之间。 */
3413
+ .chat-unread-divider {
3414
+ display: flex;
3415
+ align-items: center;
3416
+ gap: 12px;
3417
+ margin: 6px 4px 2px;
3418
+ padding: 0 2px;
3419
+ font-size: 0.72rem;
3420
+ font-weight: 500;
3421
+ color: var(--accent);
3422
+ letter-spacing: 0.04em;
3423
+ user-select: none;
3424
+ pointer-events: none;
3425
+ opacity: 0;
3426
+ animation: chatUnreadDividerIn 0.32s var(--ease-out-expo) forwards;
3427
+ }
3428
+
3429
+ .chat-unread-divider-line {
3430
+ flex: 1;
3431
+ height: 1px;
3432
+ background: linear-gradient(
3433
+ to right,
3434
+ rgba(197, 101, 61, 0.05),
3435
+ rgba(197, 101, 61, 0.32),
3436
+ rgba(197, 101, 61, 0.05)
3437
+ );
3438
+ }
3439
+
3440
+ .chat-unread-divider-label {
3441
+ flex-shrink: 0;
3442
+ padding: 3px 10px;
3443
+ background: rgba(255, 248, 240, 0.95);
3444
+ border: 1px solid rgba(197, 101, 61, 0.18);
3445
+ border-radius: 999px;
3446
+ box-shadow: 0 1px 3px rgba(184, 92, 55, 0.1);
3447
+ white-space: nowrap;
3448
+ }
3449
+
3450
+ @keyframes chatUnreadDividerIn {
3451
+ from { opacity: 0; transform: translateY(-2px); }
3452
+ to { opacity: 1; transform: translateY(0); }
3453
+ }
3454
+
3424
3455
  .chat-container.active { display: flex; }
3425
3456
 
3426
3457
  .chat-container::after {
@@ -5345,10 +5376,12 @@
5345
5376
  .input-label { font-size: 0.6875rem; color: var(--text-muted); font-weight: 500; }
5346
5377
  .input-textarea-wrap { position: relative; width: 100%; }
5347
5378
 
5348
- /* Composer top row: holds todo collapse bar (left) + reply status bar (right) on one line */
5379
+ /* Composer top row: holds todo collapse bar (left) + reply status bar (right) on one line.
5380
+ position: relative anchors the upward-floating .todo-progress-body to the full row width. */
5349
5381
  .composer-top-row {
5382
+ position: relative;
5350
5383
  display: flex;
5351
- align-items: flex-start;
5384
+ align-items: center;
5352
5385
  gap: 8px;
5353
5386
  min-width: 0;
5354
5387
  }
@@ -5391,31 +5424,52 @@
5391
5424
  min-width: 0;
5392
5425
  flex: 1;
5393
5426
  }
5394
- .todo-progress-spinner {
5395
- width: 14px;
5396
- height: 14px;
5397
- min-width: 14px;
5398
- border: 2px solid var(--accent-muted);
5399
- border-top-color: var(--accent);
5400
- border-radius: 50%;
5401
- animation: spin 0.8s linear infinite;
5427
+ /* Circular progress ring (replaces the old plain spinner) */
5428
+ .todo-progress-ring {
5429
+ position: relative;
5430
+ display: inline-flex;
5431
+ align-items: center;
5432
+ justify-content: center;
5433
+ width: 16px;
5434
+ height: 16px;
5435
+ min-width: 16px;
5436
+ flex-shrink: 0;
5402
5437
  }
5403
- .todo-progress.all-done .todo-progress-spinner {
5404
- display: none;
5438
+ .todo-progress-ring svg {
5439
+ width: 100%;
5440
+ height: 100%;
5441
+ transform: rotate(-90deg);
5405
5442
  }
5406
- .todo-progress.all-done .todo-progress-left::before {
5407
- content: "✓";
5408
- font-size: 0.8125rem;
5409
- font-weight: 700;
5410
- color: #4a7a4f;
5411
- width: 14px;
5412
- text-align: center;
5443
+ .todo-ring-track {
5444
+ stroke: rgba(197, 101, 61, 0.18);
5445
+ }
5446
+ .todo-ring-fill {
5447
+ stroke: var(--accent);
5448
+ /* circumference = 2 * π * 15.5 ≈ 97.39 */
5449
+ stroke-dasharray: 97.39;
5450
+ stroke-dashoffset: calc(97.39 * (1 - var(--progress, 0)));
5451
+ transition: stroke-dashoffset 0.35s ease;
5452
+ }
5453
+ .todo-progress-ring::after {
5454
+ content: "";
5455
+ position: absolute;
5456
+ width: 4px;
5457
+ height: 4px;
5458
+ border-radius: 50%;
5459
+ background: var(--accent);
5460
+ opacity: 0.6;
5461
+ animation: ringPulse 1.4s ease-in-out infinite;
5462
+ }
5463
+ @keyframes ringPulse {
5464
+ 0%, 100% { opacity: 0.35; transform: scale(0.85); }
5465
+ 50% { opacity: 0.95; transform: scale(1.05); }
5413
5466
  }
5414
5467
  .todo-progress-counter {
5415
5468
  font-size: 0.75rem;
5416
5469
  font-weight: 600;
5417
5470
  color: var(--text-primary);
5418
5471
  white-space: nowrap;
5472
+ font-variant-numeric: tabular-nums;
5419
5473
  }
5420
5474
  .todo-progress-task {
5421
5475
  font-size: 0.75rem;
@@ -5432,44 +5486,84 @@
5432
5486
  .todo-progress.expanded .todo-progress-chevron {
5433
5487
  transform: rotate(180deg);
5434
5488
  }
5489
+
5490
+ /* Expanded body floats UPWARD above the composer, spans the full row width,
5491
+ and is decoupled from the flex row so the status bar on the right does not
5492
+ animate together with the expansion. */
5435
5493
  .todo-progress-body {
5436
- border-top: 1px solid var(--border-subtle);
5437
- padding: 6px 10px 8px;
5494
+ position: absolute;
5495
+ left: 0;
5496
+ right: 0;
5497
+ bottom: calc(100% + 6px);
5498
+ background: rgba(255, 251, 246, 0.98);
5499
+ border: 1px solid var(--border-default);
5500
+ border-radius: 12px;
5501
+ padding: 8px 10px;
5502
+ box-shadow: 0 8px 24px rgba(0, 0, 0, 0.08), 0 2px 6px rgba(0, 0, 0, 0.04);
5503
+ backdrop-filter: blur(8px);
5504
+ -webkit-backdrop-filter: blur(8px);
5505
+ max-height: 320px;
5506
+ overflow-y: auto;
5507
+ z-index: 30;
5508
+ opacity: 0;
5509
+ transform: translateY(6px);
5510
+ pointer-events: none;
5511
+ transition: opacity 0.18s ease, transform 0.18s ease;
5438
5512
  }
5439
5513
  .todo-progress-body.hidden { display: none; }
5514
+ .todo-progress-body.expanded {
5515
+ opacity: 1;
5516
+ transform: translateY(0);
5517
+ pointer-events: auto;
5518
+ }
5440
5519
  .todo-progress-list {
5441
5520
  list-style: none;
5442
5521
  margin: 0;
5443
5522
  padding: 0;
5444
5523
  display: flex;
5445
5524
  flex-direction: column;
5446
- gap: 3px;
5525
+ gap: 1px;
5447
5526
  }
5448
5527
  .todo-progress-item {
5449
5528
  display: flex;
5450
5529
  align-items: center;
5451
- gap: 8px;
5452
- padding: 3px 4px;
5453
- border-radius: 6px;
5454
- font-size: 0.75rem;
5530
+ gap: 10px;
5531
+ padding: 6px 8px;
5532
+ border-radius: 8px;
5533
+ font-size: 0.8125rem;
5455
5534
  color: var(--text-secondary);
5535
+ line-height: 1.35;
5456
5536
  transition: background var(--transition-fast);
5457
5537
  }
5538
+ .todo-progress-item + .todo-progress-item {
5539
+ border-top: 1px solid var(--border-subtle);
5540
+ border-radius: 0;
5541
+ }
5542
+ .todo-progress-item:first-child { border-top-left-radius: 8px; border-top-right-radius: 8px; }
5543
+ .todo-progress-item:last-child { border-bottom-left-radius: 8px; border-bottom-right-radius: 8px; }
5458
5544
  .todo-progress-item.active {
5459
5545
  color: var(--text-primary);
5460
5546
  font-weight: 500;
5461
- background: rgba(197, 101, 61, 0.06);
5547
+ background: rgba(197, 101, 61, 0.07);
5462
5548
  }
5463
5549
  .todo-progress-item.done {
5464
5550
  color: var(--text-muted);
5465
5551
  text-decoration: line-through;
5466
5552
  text-decoration-color: var(--text-muted);
5467
5553
  }
5554
+ .todo-progress-item > span:last-child {
5555
+ flex: 1;
5556
+ min-width: 0;
5557
+ overflow: hidden;
5558
+ text-overflow: ellipsis;
5559
+ white-space: nowrap;
5560
+ }
5468
5561
  .todo-item-icon {
5469
5562
  width: 16px;
5470
5563
  min-width: 16px;
5471
5564
  text-align: center;
5472
- font-size: 0.6875rem;
5565
+ font-size: 0.75rem;
5566
+ flex-shrink: 0;
5473
5567
  }
5474
5568
  .todo-item-icon.pending { color: var(--text-muted); }
5475
5569
  .todo-item-icon.active { color: var(--accent); }
@@ -8442,8 +8536,7 @@
8442
8536
  }
8443
8537
 
8444
8538
  /* 回到底部按钮 - 紧凑 */
8445
- .terminal-jump-bottom,
8446
- .chat-jump-bottom {
8539
+ .terminal-jump-bottom {
8447
8540
  width: 32px;
8448
8541
  height: 32px;
8449
8542
  padding: 0;
@@ -8451,6 +8544,23 @@
8451
8544
  bottom: 12px;
8452
8545
  }
8453
8546
 
8547
+ /* 移动端的未读胶囊 —— 略矮一点,贴紧 chat-container 的右下角,
8548
+ 离输入框边缘更近、避开虚拟键盘那一坨。 */
8549
+ .chat-unread-bubble {
8550
+ height: 30px;
8551
+ min-width: 30px;
8552
+ right: 10px;
8553
+ bottom: 8px;
8554
+ padding: 0 9px;
8555
+ font-size: 0.72rem;
8556
+ }
8557
+ .chat-unread-bubble.has-unread {
8558
+ padding: 0 11px 0 9px;
8559
+ }
8560
+ .chat-unread-bubble-count {
8561
+ font-size: 0.72rem;
8562
+ }
8563
+
8454
8564
  /* 小键盘 FAB */
8455
8565
  .mini-keyboard-fab {
8456
8566
  width: 40px;
@@ -12015,7 +12125,9 @@
12015
12125
  100% { background-position: 100% center; }
12016
12126
  }
12017
12127
 
12018
- /* ── 结构化会话状态条(与 todo 折叠栏共处一行,靠右) ── */
12128
+ /* ── 结构化会话状态条(与 todo 折叠栏共处一行,靠右) ──
12129
+ Only opacity/color transition — `transition: all` would cause this bar to
12130
+ animate together with the todo bar's expansion, which looks broken. */
12019
12131
  .structured-status-bar {
12020
12132
  display: flex;
12021
12133
  align-items: center;
@@ -12028,7 +12140,7 @@
12028
12140
  border: none;
12029
12141
  font-size: 0.6875rem;
12030
12142
  color: var(--text-muted);
12031
- transition: all 0.3s ease;
12143
+ transition: opacity 0.3s ease, color 0.3s ease;
12032
12144
  overflow: hidden;
12033
12145
  flex-shrink: 0;
12034
12146
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@co0ontty/wand",
3
- "version": "1.22.0",
3
+ "version": "1.24.0",
4
4
  "description": "A web terminal for local CLI tools like Claude.",
5
5
  "type": "module",
6
6
  "bin": {