clay-server 2.39.0-beta.2 → 2.39.0-beta.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/project-connection.js +15 -0
- package/lib/project-sessions.js +18 -0
- package/lib/public/app.js +13 -0
- package/lib/public/css/tui-attention.css +317 -0
- package/lib/public/index.html +21 -0
- package/lib/public/modules/app-home-hub.js +41 -0
- package/lib/public/modules/app-messages.js +15 -0
- package/lib/public/modules/app-notifications.js +11 -2
- package/lib/public/modules/app-projects.js +2 -0
- package/lib/public/modules/session-tui-view.js +44 -68
- package/lib/public/modules/whats-new-article.js +70 -0
- package/lib/public/modules/whats-new.js +264 -0
- package/lib/users-preferences.js +72 -9
- package/lib/users.js +4 -0
- package/lib/whats-new-content.js +68 -0
- package/lib/whats-new.js +54 -0
- package/lib/ws-schema.js +7 -0
- package/package.json +1 -1
|
@@ -157,6 +157,21 @@ function attachConnection(ctx) {
|
|
|
157
157
|
var _comVal = _comUid ? usersModule.getClaudeOpenMode(_comUid) : "tui";
|
|
158
158
|
sendTo(ws, { type: "claude_open_mode_changed", claudeOpenMode: _comVal || "tui" });
|
|
159
159
|
}
|
|
160
|
+
|
|
161
|
+
// What's New: push the full entries list (for the home-page feed)
|
|
162
|
+
// plus the subset of unseen ids (for the auto-pop carousel). Content
|
|
163
|
+
// lives in lib/whats-new-content.js so adding an entry doesn't touch
|
|
164
|
+
// this file.
|
|
165
|
+
try {
|
|
166
|
+
var _wn = require("./whats-new");
|
|
167
|
+
var _wnUid = (wsUser && wsUser.id) || null;
|
|
168
|
+
var _wnState = _wnUid ? _wn.getStateForUser(_wnUid) : { entries: _wn.listEntries(), unseenIds: [] };
|
|
169
|
+
if (_wnState.entries.length > 0) {
|
|
170
|
+
sendTo(ws, { type: "whats_new_state", entries: _wnState.entries, unseenIds: _wnState.unseenIds });
|
|
171
|
+
}
|
|
172
|
+
} catch (e) {
|
|
173
|
+
if (debug) console.error("[project] whats_new send failed:", e && e.message);
|
|
174
|
+
}
|
|
160
175
|
_loop.sendConnectionState(ws);
|
|
161
176
|
if (_mcp) _mcp.sendConnectionState(ws);
|
|
162
177
|
if (_notifications) _notifications.sendConnectionState(ws, sendTo);
|
package/lib/project-sessions.js
CHANGED
|
@@ -1754,6 +1754,24 @@ function attachSessions(ctx) {
|
|
|
1754
1754
|
return true;
|
|
1755
1755
|
}
|
|
1756
1756
|
|
|
1757
|
+
if (msg.type === "whats_new_seen") {
|
|
1758
|
+
// Persist that the current user dismissed a What's New entry so it
|
|
1759
|
+
// is not shown again on future connects.
|
|
1760
|
+
var wnUserId = ws._clayUser ? ws._clayUser.id : null;
|
|
1761
|
+
if (!wnUserId) {
|
|
1762
|
+
sendTo(ws, { type: "whats_new_seen_result", ok: false, error: "no_user" });
|
|
1763
|
+
return true;
|
|
1764
|
+
}
|
|
1765
|
+
var wnSvc = require("./whats-new");
|
|
1766
|
+
var wnResult = wnSvc.markSeen(wnUserId, msg.id);
|
|
1767
|
+
if (wnResult && wnResult.ok) {
|
|
1768
|
+
sendTo(ws, { type: "whats_new_seen_result", ok: true, id: msg.id });
|
|
1769
|
+
} else {
|
|
1770
|
+
sendTo(ws, { type: "whats_new_seen_result", ok: false, error: (wnResult && wnResult.error) || "unknown" });
|
|
1771
|
+
}
|
|
1772
|
+
return true;
|
|
1773
|
+
}
|
|
1774
|
+
|
|
1757
1775
|
if (msg.type === "set_claude_open_mode") {
|
|
1758
1776
|
// Per-user preference: when Clay opens a Claude session, render it as
|
|
1759
1777
|
// the SDK-driven custom chat ("gui") or as an embedded `claude` TUI
|
package/lib/public/app.js
CHANGED
|
@@ -63,6 +63,8 @@ import { initDebateUi, showDebateConcludeConfirm as _debShowDebateConcludeConfir
|
|
|
63
63
|
import { initLoopUi, updateLoopInputVisibility as _loopUpdateLoopInputVisibility, updateLoopButton as _loopUpdateLoopButton, showLoopBanner as _loopShowLoopBanner, updateLoopBanner as _loopUpdateLoopBanner, updateRalphBars as _loopUpdateRalphBars, showRalphCraftingBar as _loopShowRalphCraftingBar, showRalphApprovalBar as _loopShowRalphApprovalBar, updateRalphApprovalStatus as _loopUpdateRalphApprovalStatus, openRalphPreviewModal as _loopOpenRalphPreviewModal, showExecModal as _loopShowExecModal, closeExecModal as _loopCloseExecModal, updateExecModalStatus as _loopUpdateExecModalStatus } from './modules/app-loop-ui.js';
|
|
64
64
|
import { initLoopWizard, openRalphWizard as _loopOpenRalphWizard, closeRalphWizard as _loopCloseRalphWizard, getWizardSource as _loopGetWizardSource } from './modules/app-loop-wizard.js';
|
|
65
65
|
import { initAppNotifications, handleNotificationsState as _notifHandleState, handleNotificationCreated as _notifHandleCreated, handleNotificationDismissed as _notifHandleDismissed, handleNotificationDismissedAll as _notifHandleDismissedAll } from './modules/app-notifications.js';
|
|
66
|
+
import { initWhatsNew, handleWhatsNewState as _wnHandleState, handleWhatsNewSeenResult as _wnHandleSeenResult } from './modules/whats-new.js';
|
|
67
|
+
import { initWhatsNewArticle, openArticle as openWhatsNewArticle } from './modules/whats-new-article.js';
|
|
66
68
|
import { createStore, store } from './modules/store.js';
|
|
67
69
|
import { initPanels, updateConfigChip as _panUpdateConfigChip, getModelEffortLevels as _panGetModelEffortLevels, accumulateUsage as _panAccumulateUsage, updateUsagePanel as _panUpdateUsagePanel, resetUsage as _panResetUsage, toggleUsagePanel as _panToggleUsagePanel, formatTokens as _panFormatTokens, updateStatusPanel as _panUpdateStatusPanel, requestProcessStats as _panRequestProcessStats, toggleStatusPanel as _panToggleStatusPanel, accumulateContext as _panAccumulateContext, updateContextPanel as _panUpdateContextPanel, resetContext as _panResetContext, resetContextData as _panResetContextData, minimizeContext as _panMinimizeContext, expandContext as _panExpandContext, toggleContextPanel as _panToggleContextPanel, getContextView as _panGetContextView, renderCtxPopover as _panRenderCtxPopover, hideCtxPopover as _panHideCtxPopover, formatBytes as _panFormatBytes, formatUptime as _panFormatUptime, getModelSupportsEffort as _panGetModelSupportsEffort, getSessionUsage, setSessionUsage, getContextData, setContextData, setContextView as _panSetContextView, applyContextView as _panApplyContextView } from './modules/app-panels.js';
|
|
68
70
|
import { initProjects, updateProjectList as _projUpdateProjectList, renderProjectList as _projRenderProjectList, renderTopbarPresence as _projRenderTopbarPresence, switchProject as _projSwitchProject, resetClientState as _projResetClientState, confirmRemoveProject as _projConfirmRemoveProject, handleRemoveProjectCheckResult as _projHandleRemoveProjectCheckResult, handleRemoveProjectResult as _projHandleRemoveProjectResult, openAddProjectModal as _projOpenAddProjectModal, closeAddProjectModal as _projCloseAddProjectModal, handleBrowseDirResult as _projHandleBrowseDirResult, handleAddProjectResult as _projHandleAddProjectResult, handleCloneProgress as _projHandleCloneProgress, showUpdateAvailable as _projShowUpdateAvailable, getCachedProjects, setCachedProjects, getCachedProjectCount, getCachedRemovedProjects, setCachedRemovedProjects } from './modules/app-projects.js';
|
|
@@ -590,6 +592,17 @@ import { initDebate, handleDebatePreparing, handleDebateStarted, handleDebateRes
|
|
|
590
592
|
// --- Notifications module ---
|
|
591
593
|
initAppNotifications();
|
|
592
594
|
|
|
595
|
+
// --- What's New viewer ---
|
|
596
|
+
initWhatsNewArticle();
|
|
597
|
+
initWhatsNew({
|
|
598
|
+
// "Read more" in the carousel opens the dedicated article viewer
|
|
599
|
+
// for the chosen entry, jumping straight to the body content
|
|
600
|
+
// (skipping the home list).
|
|
601
|
+
onReadMore: function (entryId) {
|
|
602
|
+
if (entryId) openWhatsNewArticle(entryId);
|
|
603
|
+
},
|
|
604
|
+
});
|
|
605
|
+
|
|
593
606
|
// --- Panels module ---
|
|
594
607
|
initPanels();
|
|
595
608
|
|
|
@@ -369,3 +369,320 @@
|
|
|
369
369
|
padding: 4px;
|
|
370
370
|
background: #000;
|
|
371
371
|
}
|
|
372
|
+
|
|
373
|
+
/* ============================================================
|
|
374
|
+
What's New carousel
|
|
375
|
+
============================================================ */
|
|
376
|
+
.whats-new-backdrop {
|
|
377
|
+
position: fixed;
|
|
378
|
+
inset: 0;
|
|
379
|
+
z-index: 9600;
|
|
380
|
+
background: rgba(0, 0, 0, 0.55);
|
|
381
|
+
display: flex;
|
|
382
|
+
align-items: center;
|
|
383
|
+
justify-content: center;
|
|
384
|
+
padding: 24px;
|
|
385
|
+
opacity: 0;
|
|
386
|
+
transition: opacity 160ms ease;
|
|
387
|
+
}
|
|
388
|
+
.whats-new-backdrop.show { opacity: 1; }
|
|
389
|
+
.whats-new-card {
|
|
390
|
+
position: relative;
|
|
391
|
+
width: min(440px, 100%);
|
|
392
|
+
max-height: min(80vh, 640px);
|
|
393
|
+
background: var(--sidebar-bg);
|
|
394
|
+
border: 1px solid var(--border);
|
|
395
|
+
border-radius: 14px;
|
|
396
|
+
overflow: hidden;
|
|
397
|
+
display: flex;
|
|
398
|
+
flex-direction: column;
|
|
399
|
+
box-shadow: 0 12px 48px rgba(var(--shadow-rgb), 0.4);
|
|
400
|
+
color: var(--text);
|
|
401
|
+
}
|
|
402
|
+
.whats-new-close {
|
|
403
|
+
position: absolute;
|
|
404
|
+
top: 10px;
|
|
405
|
+
right: 10px;
|
|
406
|
+
width: 28px;
|
|
407
|
+
height: 28px;
|
|
408
|
+
padding: 0;
|
|
409
|
+
border: none;
|
|
410
|
+
border-radius: 6px;
|
|
411
|
+
background: rgba(var(--overlay-rgb), 0.08);
|
|
412
|
+
color: var(--text-dimmer);
|
|
413
|
+
cursor: pointer;
|
|
414
|
+
display: flex;
|
|
415
|
+
align-items: center;
|
|
416
|
+
justify-content: center;
|
|
417
|
+
z-index: 2;
|
|
418
|
+
}
|
|
419
|
+
.whats-new-close:hover {
|
|
420
|
+
background: rgba(var(--overlay-rgb), 0.15);
|
|
421
|
+
color: var(--text);
|
|
422
|
+
}
|
|
423
|
+
.whats-new-image-slot {
|
|
424
|
+
width: 100%;
|
|
425
|
+
aspect-ratio: 16 / 9;
|
|
426
|
+
background: linear-gradient(135deg, var(--accent) 0%, var(--accent2, var(--accent)) 100%);
|
|
427
|
+
flex-shrink: 0;
|
|
428
|
+
overflow: hidden;
|
|
429
|
+
display: block;
|
|
430
|
+
}
|
|
431
|
+
.whats-new-image-slot img {
|
|
432
|
+
width: 100%;
|
|
433
|
+
height: 100%;
|
|
434
|
+
object-fit: cover;
|
|
435
|
+
display: block;
|
|
436
|
+
}
|
|
437
|
+
.whats-new-image-slot.empty {
|
|
438
|
+
aspect-ratio: 16 / 6;
|
|
439
|
+
}
|
|
440
|
+
.whats-new-body {
|
|
441
|
+
padding: 18px 20px 14px;
|
|
442
|
+
display: flex;
|
|
443
|
+
flex-direction: column;
|
|
444
|
+
gap: 8px;
|
|
445
|
+
overflow-y: auto;
|
|
446
|
+
}
|
|
447
|
+
.whats-new-eyebrow {
|
|
448
|
+
font-size: 11px;
|
|
449
|
+
font-weight: 600;
|
|
450
|
+
letter-spacing: 0.06em;
|
|
451
|
+
text-transform: uppercase;
|
|
452
|
+
color: var(--text-dimmer);
|
|
453
|
+
}
|
|
454
|
+
.whats-new-title {
|
|
455
|
+
margin: 0;
|
|
456
|
+
font-size: 18px;
|
|
457
|
+
font-weight: 600;
|
|
458
|
+
line-height: 1.3;
|
|
459
|
+
color: var(--text);
|
|
460
|
+
}
|
|
461
|
+
.whats-new-summary {
|
|
462
|
+
margin: 0;
|
|
463
|
+
font-size: 13px;
|
|
464
|
+
line-height: 1.55;
|
|
465
|
+
color: var(--text-secondary);
|
|
466
|
+
}
|
|
467
|
+
.whats-new-read-more {
|
|
468
|
+
align-self: flex-start;
|
|
469
|
+
margin-top: 6px;
|
|
470
|
+
padding: 7px 14px;
|
|
471
|
+
font-size: 13px;
|
|
472
|
+
font-weight: 500;
|
|
473
|
+
border: none;
|
|
474
|
+
border-radius: 7px;
|
|
475
|
+
background: var(--accent);
|
|
476
|
+
color: var(--accent-contrast, #fff);
|
|
477
|
+
cursor: pointer;
|
|
478
|
+
display: inline-flex;
|
|
479
|
+
align-items: center;
|
|
480
|
+
}
|
|
481
|
+
.whats-new-read-more:hover {
|
|
482
|
+
filter: brightness(1.08);
|
|
483
|
+
}
|
|
484
|
+
.whats-new-nav {
|
|
485
|
+
display: flex;
|
|
486
|
+
align-items: center;
|
|
487
|
+
justify-content: space-between;
|
|
488
|
+
padding: 8px 16px 14px;
|
|
489
|
+
border-top: 1px solid var(--border);
|
|
490
|
+
flex-shrink: 0;
|
|
491
|
+
}
|
|
492
|
+
.whats-new-nav.hidden { display: none; }
|
|
493
|
+
.whats-new-prev,
|
|
494
|
+
.whats-new-next {
|
|
495
|
+
width: 30px;
|
|
496
|
+
height: 30px;
|
|
497
|
+
padding: 0;
|
|
498
|
+
border: none;
|
|
499
|
+
border-radius: 6px;
|
|
500
|
+
background: transparent;
|
|
501
|
+
color: var(--text-dimmer);
|
|
502
|
+
cursor: pointer;
|
|
503
|
+
display: flex;
|
|
504
|
+
align-items: center;
|
|
505
|
+
justify-content: center;
|
|
506
|
+
}
|
|
507
|
+
.whats-new-prev:hover,
|
|
508
|
+
.whats-new-next:hover {
|
|
509
|
+
background: rgba(var(--overlay-rgb), 0.08);
|
|
510
|
+
color: var(--text);
|
|
511
|
+
}
|
|
512
|
+
.whats-new-prev:disabled,
|
|
513
|
+
.whats-new-next:disabled {
|
|
514
|
+
opacity: 0.35;
|
|
515
|
+
cursor: default;
|
|
516
|
+
}
|
|
517
|
+
.whats-new-dots {
|
|
518
|
+
display: flex;
|
|
519
|
+
gap: 6px;
|
|
520
|
+
align-items: center;
|
|
521
|
+
}
|
|
522
|
+
.whats-new-dot {
|
|
523
|
+
width: 7px;
|
|
524
|
+
height: 7px;
|
|
525
|
+
padding: 0;
|
|
526
|
+
border: none;
|
|
527
|
+
border-radius: 50%;
|
|
528
|
+
background: var(--text-dimmer);
|
|
529
|
+
opacity: 0.35;
|
|
530
|
+
cursor: pointer;
|
|
531
|
+
transition: opacity 120ms ease, transform 120ms ease;
|
|
532
|
+
}
|
|
533
|
+
.whats-new-dot.active {
|
|
534
|
+
opacity: 1;
|
|
535
|
+
background: var(--accent);
|
|
536
|
+
transform: scale(1.2);
|
|
537
|
+
}
|
|
538
|
+
|
|
539
|
+
/* ============================================================
|
|
540
|
+
Home page "What's New" index (title-only list, blog-style)
|
|
541
|
+
============================================================ */
|
|
542
|
+
.hub-whats-new {
|
|
543
|
+
display: flex;
|
|
544
|
+
flex-direction: column;
|
|
545
|
+
}
|
|
546
|
+
.hub-whats-new-item {
|
|
547
|
+
display: flex;
|
|
548
|
+
align-items: baseline;
|
|
549
|
+
gap: 12px;
|
|
550
|
+
padding: 10px 4px;
|
|
551
|
+
border: none;
|
|
552
|
+
background: transparent;
|
|
553
|
+
text-align: left;
|
|
554
|
+
cursor: pointer;
|
|
555
|
+
color: var(--text);
|
|
556
|
+
border-bottom: 1px solid var(--border);
|
|
557
|
+
transition: background 120ms ease;
|
|
558
|
+
width: 100%;
|
|
559
|
+
}
|
|
560
|
+
.hub-whats-new-item:last-child {
|
|
561
|
+
border-bottom: none;
|
|
562
|
+
}
|
|
563
|
+
.hub-whats-new-item:hover {
|
|
564
|
+
background: rgba(var(--overlay-rgb), 0.04);
|
|
565
|
+
}
|
|
566
|
+
.hub-whats-new-item-date {
|
|
567
|
+
font-size: 11px;
|
|
568
|
+
color: var(--text-dimmer);
|
|
569
|
+
flex-shrink: 0;
|
|
570
|
+
width: 84px;
|
|
571
|
+
font-variant-numeric: tabular-nums;
|
|
572
|
+
}
|
|
573
|
+
.hub-whats-new-item-title {
|
|
574
|
+
margin: 0;
|
|
575
|
+
font-size: 14px;
|
|
576
|
+
font-weight: 500;
|
|
577
|
+
line-height: 1.4;
|
|
578
|
+
color: var(--text);
|
|
579
|
+
flex: 1 1 auto;
|
|
580
|
+
min-width: 0;
|
|
581
|
+
}
|
|
582
|
+
|
|
583
|
+
/* ============================================================
|
|
584
|
+
Whats New article (blog-style reading view)
|
|
585
|
+
============================================================
|
|
586
|
+
Mirrors #home-hub positioning: absolute inset:0 inside
|
|
587
|
+
#main-area so the icon strip, top bar, and any other chrome
|
|
588
|
+
outside #main-area stay visible. Sidebar-column (inside
|
|
589
|
+
main-area) is covered, same as home-hub. */
|
|
590
|
+
#whats-new-article {
|
|
591
|
+
position: absolute;
|
|
592
|
+
inset: 0;
|
|
593
|
+
z-index: 210;
|
|
594
|
+
background: var(--bg);
|
|
595
|
+
overflow-y: auto;
|
|
596
|
+
display: flex;
|
|
597
|
+
flex-direction: column;
|
|
598
|
+
border-top-left-radius: 8px;
|
|
599
|
+
}
|
|
600
|
+
#whats-new-article.hidden { display: none; }
|
|
601
|
+
.wna-toolbar {
|
|
602
|
+
position: sticky;
|
|
603
|
+
top: 0;
|
|
604
|
+
display: flex;
|
|
605
|
+
align-items: center;
|
|
606
|
+
padding: 12px 20px;
|
|
607
|
+
background: var(--bg);
|
|
608
|
+
border-bottom: 1px solid var(--border);
|
|
609
|
+
z-index: 1;
|
|
610
|
+
}
|
|
611
|
+
.wna-back {
|
|
612
|
+
display: inline-flex;
|
|
613
|
+
align-items: center;
|
|
614
|
+
gap: 8px;
|
|
615
|
+
padding: 6px 12px;
|
|
616
|
+
border: none;
|
|
617
|
+
border-radius: 6px;
|
|
618
|
+
background: transparent;
|
|
619
|
+
color: var(--text-secondary);
|
|
620
|
+
font-size: 13px;
|
|
621
|
+
cursor: pointer;
|
|
622
|
+
}
|
|
623
|
+
.wna-back:hover {
|
|
624
|
+
background: rgba(var(--overlay-rgb), 0.06);
|
|
625
|
+
color: var(--text);
|
|
626
|
+
}
|
|
627
|
+
.wna-content {
|
|
628
|
+
width: 100%;
|
|
629
|
+
max-width: 680px;
|
|
630
|
+
margin: 0 auto;
|
|
631
|
+
padding: 56px 32px 96px;
|
|
632
|
+
box-sizing: border-box;
|
|
633
|
+
}
|
|
634
|
+
.wna-date {
|
|
635
|
+
font-size: 12px;
|
|
636
|
+
color: var(--text-dimmer);
|
|
637
|
+
letter-spacing: 0.04em;
|
|
638
|
+
margin-bottom: 12px;
|
|
639
|
+
font-variant-numeric: tabular-nums;
|
|
640
|
+
}
|
|
641
|
+
.wna-title {
|
|
642
|
+
margin: 0 0 32px;
|
|
643
|
+
font-size: 32px;
|
|
644
|
+
font-weight: 700;
|
|
645
|
+
line-height: 1.2;
|
|
646
|
+
color: var(--text);
|
|
647
|
+
letter-spacing: -0.01em;
|
|
648
|
+
}
|
|
649
|
+
.wna-body {
|
|
650
|
+
font-size: 15px;
|
|
651
|
+
line-height: 1.7;
|
|
652
|
+
color: var(--text);
|
|
653
|
+
}
|
|
654
|
+
.wna-body p {
|
|
655
|
+
margin: 0 0 18px;
|
|
656
|
+
}
|
|
657
|
+
.wna-body p:last-child { margin-bottom: 0; }
|
|
658
|
+
.wna-body h3 {
|
|
659
|
+
margin: 36px 0 12px;
|
|
660
|
+
font-size: 18px;
|
|
661
|
+
font-weight: 600;
|
|
662
|
+
color: var(--text);
|
|
663
|
+
letter-spacing: -0.005em;
|
|
664
|
+
}
|
|
665
|
+
.wna-body ul, .wna-body ol {
|
|
666
|
+
margin: 0 0 18px;
|
|
667
|
+
padding-left: 22px;
|
|
668
|
+
}
|
|
669
|
+
.wna-body li {
|
|
670
|
+
margin-bottom: 8px;
|
|
671
|
+
}
|
|
672
|
+
.wna-body code {
|
|
673
|
+
padding: 2px 6px;
|
|
674
|
+
border-radius: 4px;
|
|
675
|
+
background: var(--code-bg);
|
|
676
|
+
color: var(--accent);
|
|
677
|
+
font-size: 13px;
|
|
678
|
+
font-family: "Roboto Mono", monospace;
|
|
679
|
+
}
|
|
680
|
+
.wna-body strong { color: var(--text); font-weight: 600; }
|
|
681
|
+
.wna-body em { color: var(--text-secondary); }
|
|
682
|
+
.wna-body a { color: var(--accent2); }
|
|
683
|
+
.wna-body a:hover { text-decoration: underline; }
|
|
684
|
+
@media (max-width: 640px) {
|
|
685
|
+
.wna-content { padding: 32px 20px 80px; }
|
|
686
|
+
.wna-title { font-size: 26px; }
|
|
687
|
+
}
|
|
688
|
+
|
package/lib/public/index.html
CHANGED
|
@@ -150,6 +150,13 @@
|
|
|
150
150
|
<div class="hub-week-strip" id="hub-week-strip"></div>
|
|
151
151
|
</div>
|
|
152
152
|
|
|
153
|
+
<div class="hub-card hub-whats-new-card" id="hub-whats-new-card">
|
|
154
|
+
<div class="hub-card-header">
|
|
155
|
+
<span class="hub-card-title">What's New</span>
|
|
156
|
+
</div>
|
|
157
|
+
<div class="hub-whats-new" id="hub-whats-new"></div>
|
|
158
|
+
</div>
|
|
159
|
+
|
|
153
160
|
<div class="hub-card hub-playbooks" id="hub-playbooks">
|
|
154
161
|
<div class="hub-card-header">
|
|
155
162
|
<span class="hub-card-title">Quick Start</span>
|
|
@@ -175,6 +182,20 @@
|
|
|
175
182
|
</div>
|
|
176
183
|
</div>
|
|
177
184
|
</div>
|
|
185
|
+
|
|
186
|
+
<div id="whats-new-article" class="hidden">
|
|
187
|
+
<header class="wna-toolbar">
|
|
188
|
+
<button type="button" class="wna-back" id="wna-back">
|
|
189
|
+
<svg viewBox="0 0 24 24" width="16" height="16" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><line x1="19" y1="12" x2="5" y2="12"></line><polyline points="12 19 5 12 12 5"></polyline></svg>
|
|
190
|
+
<span>Back</span>
|
|
191
|
+
</button>
|
|
192
|
+
</header>
|
|
193
|
+
<article class="wna-content">
|
|
194
|
+
<div class="wna-date" id="wna-date"></div>
|
|
195
|
+
<h1 class="wna-title" id="wna-title"></h1>
|
|
196
|
+
<div class="wna-body" id="wna-body"></div>
|
|
197
|
+
</article>
|
|
198
|
+
</div>
|
|
178
199
|
<div id="sidebar-column">
|
|
179
200
|
<div class="title-bar-sidebar">
|
|
180
201
|
<button class="title-bar-project-dropdown" id="title-bar-project-dropdown">
|
|
@@ -9,6 +9,8 @@ import { openSchedulerToTab } from './scheduler.js';
|
|
|
9
9
|
import { getPlaybooks, openPlaybook, getPlaybookForTip } from './playbook.js';
|
|
10
10
|
import { mateAvatarUrl } from './avatar.js';
|
|
11
11
|
import { openDm, exitDmMode } from './app-dm.js';
|
|
12
|
+
import { getKnownEntries as getWhatsNewEntries } from './whats-new.js';
|
|
13
|
+
import { openArticle as openWhatsNewArticle } from './whats-new-article.js';
|
|
12
14
|
|
|
13
15
|
function $hub(id) { return document.getElementById(id); }
|
|
14
16
|
|
|
@@ -489,6 +491,45 @@ export function renderHomeHub(projects) {
|
|
|
489
491
|
|
|
490
492
|
// Render twemoji for all emoji in the hub
|
|
491
493
|
|
|
494
|
+
// --- What's New feed ---
|
|
495
|
+
renderHomeWhatsNew();
|
|
496
|
+
}
|
|
497
|
+
|
|
498
|
+
function renderHomeWhatsNew() {
|
|
499
|
+
var list = $hub("hub-whats-new");
|
|
500
|
+
var card = $hub("hub-whats-new-card");
|
|
501
|
+
if (!list || !card) return;
|
|
502
|
+
var entries = getWhatsNewEntries();
|
|
503
|
+
// Sort newest first by publishedAt (YYYY-MM-DD string compare).
|
|
504
|
+
entries = entries.slice().sort(function (a, b) {
|
|
505
|
+
return (b.publishedAt || "").localeCompare(a.publishedAt || "");
|
|
506
|
+
});
|
|
507
|
+
if (entries.length === 0) {
|
|
508
|
+
card.classList.add("hidden");
|
|
509
|
+
list.innerHTML = "";
|
|
510
|
+
return;
|
|
511
|
+
}
|
|
512
|
+
card.classList.remove("hidden");
|
|
513
|
+
list.innerHTML = "";
|
|
514
|
+
for (var i = 0; i < entries.length; i++) {
|
|
515
|
+
var e = entries[i];
|
|
516
|
+
var btn = document.createElement("button");
|
|
517
|
+
btn.type = "button";
|
|
518
|
+
btn.className = "hub-whats-new-item";
|
|
519
|
+
btn.setAttribute("data-whats-new-id", e.id);
|
|
520
|
+
var dateEl = document.createElement("div");
|
|
521
|
+
dateEl.className = "hub-whats-new-item-date";
|
|
522
|
+
dateEl.textContent = e.publishedAt || "";
|
|
523
|
+
var titleEl = document.createElement("h3");
|
|
524
|
+
titleEl.className = "hub-whats-new-item-title";
|
|
525
|
+
titleEl.textContent = e.title || "";
|
|
526
|
+
btn.appendChild(dateEl);
|
|
527
|
+
btn.appendChild(titleEl);
|
|
528
|
+
(function (id) {
|
|
529
|
+
btn.addEventListener("click", function () { openWhatsNewArticle(id); });
|
|
530
|
+
})(e.id);
|
|
531
|
+
list.appendChild(btn);
|
|
532
|
+
}
|
|
492
533
|
}
|
|
493
534
|
|
|
494
535
|
export function handleHubSchedules(msg) {
|
|
@@ -44,6 +44,8 @@ import { handleLoopRegistryUpdated, handleScheduleRunStarted, handleScheduleRunF
|
|
|
44
44
|
import { scrollToBottom, addToMessages, addUserMessage, addSystemMessage, removeMatePreThinking, appendDelta, finalizeAssistantBlock, addConflictMessage, addContextOverflowMessage, showSuggestionChips, armStickyBottom } from './app-rendering.js';
|
|
45
45
|
import { setActivity, startUrgentBlink, stopUrgentBlink, blinkSessionDot, updateCrossProjectBlink } from './app-favicon.js';
|
|
46
46
|
import { setStatus } from './app-connection.js';
|
|
47
|
+
import { handleWhatsNewState, handleWhatsNewSeenResult, setKnownEntries as setWhatsNewKnownEntries } from './whats-new.js';
|
|
48
|
+
import { closeArticle as closeWhatsNewArticle } from './whats-new-article.js';
|
|
47
49
|
import { getModelEffortLevels, accumulateUsage, updateUsagePanel, accumulateContext, updateContextPanel, renderCtxPopover, updateStatusPanel } from './app-panels.js';
|
|
48
50
|
import { updateProjectList, resetClientState, showUpdateAvailable, handleRemoveProjectCheckResult, handleRemoveProjectResult, handleBrowseDirResult, handleAddProjectResult, handleCloneProgress } from './app-projects.js';
|
|
49
51
|
import { updateHistorySentinel, prependOlderHistory } from './app-header.js';
|
|
@@ -589,6 +591,7 @@ export function processMessage(msg) {
|
|
|
589
591
|
|
|
590
592
|
case "session_switched":
|
|
591
593
|
hideHomeHub();
|
|
594
|
+
closeWhatsNewArticle();
|
|
592
595
|
// Save draft from outgoing session
|
|
593
596
|
var _prevSid = store.get('activeSessionId');
|
|
594
597
|
if (_prevSid && inputEl.value) {
|
|
@@ -1694,6 +1697,18 @@ export function processMessage(msg) {
|
|
|
1694
1697
|
handleAutoContinueChanged(msg);
|
|
1695
1698
|
break;
|
|
1696
1699
|
|
|
1700
|
+
case "whats_new_state":
|
|
1701
|
+
// Keep a known-entries cache for the home page feed (which shows
|
|
1702
|
+
// both seen and unseen entries), then queue unseen ones for the
|
|
1703
|
+
// carousel.
|
|
1704
|
+
if (msg && Array.isArray(msg.entries)) setWhatsNewKnownEntries(msg.entries);
|
|
1705
|
+
handleWhatsNewState(msg);
|
|
1706
|
+
break;
|
|
1707
|
+
|
|
1708
|
+
case "whats_new_seen_result":
|
|
1709
|
+
handleWhatsNewSeenResult(msg);
|
|
1710
|
+
break;
|
|
1711
|
+
|
|
1697
1712
|
case "set_claude_open_mode_result":
|
|
1698
1713
|
case "claude_open_mode_changed":
|
|
1699
1714
|
if (msg.claudeOpenMode === "tui" || msg.claudeOpenMode === "gui") {
|
|
@@ -640,9 +640,17 @@ export function showUpdateBanner(msg) {
|
|
|
640
640
|
pendingUpdateMsg = msg;
|
|
641
641
|
if (!bannerContainer) return;
|
|
642
642
|
|
|
643
|
-
//
|
|
643
|
+
// If an update banner is already showing for the same version, skip the
|
|
644
|
+
// re-render. The server pushes update_available every hour but we don't
|
|
645
|
+
// want to flash/refresh the banner if the user already sees it.
|
|
644
646
|
var existing = bannerContainer.querySelector('[data-notif-id="_update"]');
|
|
645
|
-
if (existing)
|
|
647
|
+
if (existing) {
|
|
648
|
+
var existingVersion = existing.getAttribute("data-update-version");
|
|
649
|
+
if (existingVersion === msg.version) return;
|
|
650
|
+
// Version changed (e.g. a newer release came in while old banner was
|
|
651
|
+
// still up): tear down and re-render with the new version.
|
|
652
|
+
removeBanner(existing);
|
|
653
|
+
}
|
|
646
654
|
|
|
647
655
|
var isHeadless = store.get('isHeadlessMode');
|
|
648
656
|
var updTag = msg.version.indexOf("-beta") !== -1 ? "beta" : "latest";
|
|
@@ -650,6 +658,7 @@ export function showUpdateBanner(msg) {
|
|
|
650
658
|
var banner = document.createElement("div");
|
|
651
659
|
banner.className = "notif-banner notif-banner-update";
|
|
652
660
|
banner.setAttribute("data-notif-id", "_update");
|
|
661
|
+
banner.setAttribute("data-update-version", msg.version);
|
|
653
662
|
|
|
654
663
|
var actionsHtml = '';
|
|
655
664
|
if (!isHeadless) {
|
|
@@ -16,6 +16,7 @@ import { spawnDustParticles } from './sidebar.js';
|
|
|
16
16
|
import { isSearchOpen, closeSearch } from './session-search.js';
|
|
17
17
|
import { exitDmMode } from './app-dm.js';
|
|
18
18
|
import { isHomeHubVisible, hideHomeHub, showHomeHub } from './app-home-hub.js';
|
|
19
|
+
import { closeArticle as closeWhatsNewArticle } from './whats-new-article.js';
|
|
19
20
|
import { resetFileBrowser } from './filebrowser.js';
|
|
20
21
|
import { closeArchive } from './sticky-notes.js';
|
|
21
22
|
import { hideMemory } from './mate-memory.js';
|
|
@@ -411,6 +412,7 @@ export function switchProject(slug) {
|
|
|
411
412
|
var wasDm = st.dmMode;
|
|
412
413
|
var wasMate = st.dmMode && st.dmTargetUser && st.dmTargetUser.isMate;
|
|
413
414
|
if (st.dmMode) exitDmMode(wasMate);
|
|
415
|
+
closeWhatsNewArticle();
|
|
414
416
|
if (isHomeHubVisible()) {
|
|
415
417
|
hideHomeHub();
|
|
416
418
|
if (slug === store.get('currentSlug')) return;
|