personal memory agent
at scratch/segment-sense-rd 675 lines 17 kB view raw
1<style> 2.dev-container { 3 max-width: 1200px; 4 margin: 0 auto; 5 padding: 2rem; 6} 7 8.dev-section { 9 background: white; 10 border: 1px solid #e5e7eb; 11 border-radius: 8px; 12 padding: 1.5rem; 13 margin-bottom: 1.5rem; 14} 15 16.dev-section h2 { 17 margin: 0 0 1rem 0; 18 font-size: 1.25rem; 19 font-weight: 600; 20 color: #1f2937; 21} 22 23.dev-section p { 24 margin: 0 0 1rem 0; 25 color: #6b7280; 26 font-size: 0.9rem; 27} 28 29.dev-controls { 30 display: flex; 31 flex-wrap: wrap; 32 gap: 0.75rem; 33} 34 35.dev-button { 36 padding: 0.5rem 1rem; 37 background: #667eea; 38 color: white; 39 border: none; 40 border-radius: 6px; 41 cursor: pointer; 42 font-size: 0.9rem; 43 font-weight: 500; 44 transition: background 0.2s, transform 0.1s; 45} 46 47.dev-button:hover { 48 background: #5568d3; 49 transform: translateY(-1px); 50} 51 52.dev-button:active { 53 transform: translateY(0); 54} 55 56.dev-button.secondary { 57 background: #6b7280; 58} 59 60.dev-button.secondary:hover { 61 background: #4b5563; 62} 63 64.dev-button.danger { 65 background: #ef4444; 66} 67 68.dev-button.danger:hover { 69 background: #dc2626; 70} 71 72.dev-form { 73 display: flex; 74 flex-direction: column; 75 gap: 1rem; 76 max-width: 500px; 77} 78 79.dev-form-row { 80 display: flex; 81 flex-direction: column; 82 gap: 0.25rem; 83} 84 85.dev-form-row label { 86 font-size: 0.85rem; 87 font-weight: 500; 88 color: #374151; 89} 90 91.dev-form-row input, 92.dev-form-row textarea, 93.dev-form-row select { 94 padding: 0.5rem; 95 border: 1px solid #d1d5db; 96 border-radius: 4px; 97 font-size: 0.9rem; 98} 99 100.dev-form-row textarea { 101 resize: vertical; 102 min-height: 60px; 103} 104 105.dev-output { 106 background: #f9fafb; 107 border: 1px solid #e5e7eb; 108 border-radius: 4px; 109 padding: 1rem; 110 font-family: monospace; 111 font-size: 0.85rem; 112 color: #374151; 113 max-height: 200px; 114 overflow-y: auto; 115} 116 117.dev-stats { 118 display: grid; 119 grid-template-columns: repeat(auto-fit, minmax(150px, 1fr)); 120 gap: 1rem; 121 margin-top: 1rem; 122} 123 124.dev-stat { 125 background: #f9fafb; 126 border: 1px solid #e5e7eb; 127 border-radius: 4px; 128 padding: 1rem; 129 text-align: center; 130} 131 132.dev-stat-value { 133 font-size: 2rem; 134 font-weight: 600; 135 color: #667eea; 136} 137 138.dev-stat-label { 139 font-size: 0.75rem; 140 color: #6b7280; 141 text-transform: uppercase; 142 letter-spacing: 0.05em; 143 margin-top: 0.25rem; 144} 145 146/* Events Card */ 147.events-card { 148 background: white; 149 border-radius: 12px; 150 padding: 1.5em; 151 box-shadow: 0 2px 8px rgba(0,0,0,0.1); 152} 153 154.events-card.paused { 155 border-left: 4px solid #f59e0b; 156} 157 158.events-header { 159 display: flex; 160 justify-content: space-between; 161 align-items: center; 162 margin-bottom: 1em; 163} 164 165.events-title { 166 font-size: 1.1em; 167 font-weight: 600; 168} 169 170.events-controls { 171 display: flex; 172 align-items: center; 173 gap: 1em; 174} 175 176.pause-btn { 177 background: #3b82f6; 178 color: white; 179 border: none; 180 padding: 0.5em 1em; 181 border-radius: 6px; 182 cursor: pointer; 183 font-size: 0.85em; 184 font-weight: 500; 185 display: flex; 186 align-items: center; 187 gap: 0.4em; 188 transition: background 0.2s; 189} 190 191.pause-btn:hover { 192 background: #2563eb; 193} 194 195.pause-btn.paused { 196 background: #f59e0b; 197} 198 199.pause-btn.paused:hover { 200 background: #d97706; 201} 202 203.missed-count { 204 font-size: 0.85em; 205 color: #f59e0b; 206 font-weight: 500; 207} 208 209.event-log { 210 height: 400px; 211 overflow-y: auto; 212 font-family: 'Monaco', 'Menlo', 'Consolas', monospace; 213 font-size: 12px; 214 border: 1px solid #e5e7eb; 215 border-radius: 6px; 216 padding: 0.5em; 217 background: #f9fafb; 218} 219 220.event-line { 221 white-space: nowrap; 222 overflow: hidden; 223 text-overflow: ellipsis; 224 padding: 3px 0; 225 border-bottom: 1px solid #f3f4f6; 226 color: #374151; 227} 228 229.event-line:last-child { 230 border-bottom: none; 231} 232 233.event-line.error { 234 color: #dc2626; 235 background: #fef2f2; 236} 237 238.event-log:empty::before { 239 content: 'Waiting for events...'; 240 color: #9ca3af; 241 font-style: italic; 242} 243</style> 244 245<div class="dev-container"> 246 <h1>🛠️ Dev Tools</h1> 247 248 <!-- Quick Tests --> 249 <div class="dev-section"> 250 <h2>Quick Tests</h2> 251 <p>Click buttons to trigger different notification types</p> 252 <div class="dev-controls"> 253 <button class="dev-button" onclick="testBasic()">Basic Notification</button> 254 <button class="dev-button" onclick="testWithBadge()">With Badge (5)</button> 255 <button class="dev-button" onclick="testAutoDismiss()">Auto-Dismiss (5s)</button> 256 <button class="dev-button" onclick="testNonDismissible()">Non-Dismissible</button> 257 <button class="dev-button" onclick="testLongMessage()">Long Message</button> 258 <button class="dev-button" onclick="testNoAction()">No Click Action</button> 259 <button class="dev-button" onclick="testWithFacet()">With Facet (work)</button> 260 </div> 261 </div> 262 263 <!-- Custom Notification --> 264 <div class="dev-section"> 265 <h2>Custom Notification</h2> 266 <p>Build your own notification with custom parameters</p> 267 <div class="dev-form"> 268 <div class="dev-form-row"> 269 <label>App Name</label> 270 <input type="text" id="custom-app" value="dev" /> 271 </div> 272 <div class="dev-form-row"> 273 <label>Icon (emoji)</label> 274 <input type="text" id="custom-icon" value="🛠️" maxlength="2" /> 275 </div> 276 <div class="dev-form-row"> 277 <label>Title</label> 278 <input type="text" id="custom-title" value="Custom Notification" /> 279 </div> 280 <div class="dev-form-row"> 281 <label>Message</label> 282 <textarea id="custom-message">This is a custom notification message</textarea> 283 </div> 284 <div class="dev-form-row"> 285 <label>Action URL</label> 286 <input type="text" id="custom-action" value="/app/dev" /> 287 </div> 288 <div class="dev-form-row"> 289 <label>Facet (optional)</label> 290 <select id="custom-facet"> 291 <option value="">None</option> 292 </select> 293 </div> 294 <div class="dev-form-row"> 295 <label>Badge Count (0 = none)</label> 296 <input type="number" id="custom-badge" value="0" min="0" /> 297 </div> 298 <div class="dev-form-row"> 299 <label>Auto-Dismiss (ms, 0 = never)</label> 300 <input type="number" id="custom-autodismiss" value="0" min="0" step="1000" /> 301 </div> 302 <div class="dev-form-row"> 303 <label> 304 <input type="checkbox" id="custom-dismissible" checked /> Dismissible (show X button) 305 </label> 306 </div> 307 <button class="dev-button" onclick="testCustom()">Show Custom Notification</button> 308 </div> 309 </div> 310 311 <!-- Notification Management --> 312 <div class="dev-section"> 313 <h2>Notification Management</h2> 314 <p>Test dismissing and managing notifications</p> 315 <div class="dev-controls"> 316 <button class="dev-button secondary" onclick="updateLast()">Update Last Notification</button> 317 <button class="dev-button secondary" onclick="dismissLast()">Dismiss Last</button> 318 <button class="dev-button danger" onclick="dismissAll()">Dismiss All</button> 319 <button class="dev-button secondary" onclick="showStats()">Show Stats</button> 320 </div> 321 <div class="dev-stats" id="stats"></div> 322 </div> 323 324 <!-- Stress Test --> 325 <div class="dev-section"> 326 <h2>Stress Test</h2> 327 <p>Generate multiple notifications to test stacking and performance</p> 328 <div class="dev-controls"> 329 <button class="dev-button" onclick="spawnThree()">Spawn 3 Notifications</button> 330 <button class="dev-button" onclick="spawnTen()">Spawn 10 Notifications</button> 331 <button class="dev-button" onclick="spawnSequential()">Spawn Sequential (5s intervals)</button> 332 </div> 333 </div> 334 335 <!-- Console Log --> 336 <div class="dev-section"> 337 <h2>Console Log</h2> 338 <p>Notification IDs and events</p> 339 <div class="dev-output" id="console"></div> 340 </div> 341 342 <!-- Callosum Event Viewer --> 343 <div class="dev-section"> 344 <h2>Callosum Event Viewer</h2> 345 <p>Raw callosum event stream with pause/resume</p> 346 <div class="events-card" id="eventsCard"> 347 <div class="events-header"> 348 <div class="events-title">EVENTS</div> 349 <div class="events-controls"> 350 <span class="missed-count" id="missedCount" style="display: none;"></span> 351 <button class="pause-btn" id="pauseBtn"> 352 <span>||</span> 353 <span>Pause</span> 354 </button> 355 </div> 356 </div> 357 <div id="eventLog" class="event-log"></div> 358 </div> 359 </div> 360</div> 361 362<script> 363let lastNotificationId = null; 364let sequentialTimer = null; 365 366// Helper to log to console 367function log(message) { 368 const consoleEl = document.getElementById('console'); 369 const timestamp = new Date().toLocaleTimeString(); 370 consoleEl.innerHTML = `[${timestamp}] ${message}<br>` + consoleEl.innerHTML; 371} 372 373// Quick Tests 374function testBasic() { 375 const id = window.AppServices.notifications.show({ 376 app: 'dev', 377 icon: '🛠️', 378 title: 'Basic Notification', 379 message: 'This is a basic notification with all default settings', 380 action: '/app/dev' 381 }); 382 lastNotificationId = id; 383 log(`Created basic notification (ID: ${id})`); 384} 385 386function testWithBadge() { 387 const id = window.AppServices.notifications.show({ 388 app: 'dev', 389 icon: '🔔', 390 title: 'Notification with Badge', 391 message: 'This notification has a badge count', 392 badge: 5, 393 action: '/app/dev' 394 }); 395 lastNotificationId = id; 396 log(`Created notification with badge (ID: ${id})`); 397} 398 399function testAutoDismiss() { 400 const id = window.AppServices.notifications.show({ 401 app: 'dev', 402 icon: '⏱️', 403 title: 'Auto-Dismiss Test', 404 message: 'This notification will disappear after 5 seconds', 405 action: '/app/dev', 406 autoDismiss: 5000 407 }); 408 lastNotificationId = id; 409 log(`Created auto-dismiss notification (ID: ${id})`); 410} 411 412function testNonDismissible() { 413 const id = window.AppServices.notifications.show({ 414 app: 'dev', 415 icon: '🔒', 416 title: 'Non-Dismissible', 417 message: 'This notification has no X button', 418 dismissible: false, 419 action: '/app/dev' 420 }); 421 lastNotificationId = id; 422 log(`Created non-dismissible notification (ID: ${id})`); 423} 424 425function testLongMessage() { 426 const id = window.AppServices.notifications.show({ 427 app: 'dev', 428 icon: '📝', 429 title: 'Long Message Test', 430 message: 'This is a very long notification message that should be truncated with ellipsis after two lines to prevent the card from becoming too tall and taking up too much space in the notification center.', 431 action: '/app/dev' 432 }); 433 lastNotificationId = id; 434 log(`Created long message notification (ID: ${id})`); 435} 436 437function testNoAction() { 438 const id = window.AppServices.notifications.show({ 439 app: 'dev', 440 icon: '🚫', 441 title: 'No Click Action', 442 message: 'Clicking this card does nothing (no action URL)', 443 action: null 444 }); 445 lastNotificationId = id; 446 log(`Created no-action notification (ID: ${id})`); 447} 448 449function testWithFacet() { 450 const id = window.AppServices.notifications.show({ 451 app: 'dev', 452 icon: '🎯', 453 title: 'Facet Navigation Test', 454 message: 'Clicking this will navigate to /app/home and select "work" facet', 455 action: '/app/home', 456 facet: 'work' 457 }); 458 lastNotificationId = id; 459 log(`Created facet notification (ID: ${id})`); 460} 461 462// Custom Notification 463function testCustom() { 464 const app = document.getElementById('custom-app').value; 465 const icon = document.getElementById('custom-icon').value; 466 const title = document.getElementById('custom-title').value; 467 const message = document.getElementById('custom-message').value; 468 const action = document.getElementById('custom-action').value || null; 469 const facet = document.getElementById('custom-facet').value || null; 470 const badge = parseInt(document.getElementById('custom-badge').value) || null; 471 const autoDismiss = parseInt(document.getElementById('custom-autodismiss').value) || null; 472 const dismissible = document.getElementById('custom-dismissible').checked; 473 474 const id = window.AppServices.notifications.show({ 475 app, 476 icon, 477 title, 478 message, 479 action, 480 facet, 481 badge: badge > 0 ? badge : null, 482 autoDismiss: autoDismiss > 0 ? autoDismiss : null, 483 dismissible 484 }); 485 lastNotificationId = id; 486 log(`Created custom notification (ID: ${id})`); 487} 488 489// Management 490function updateLast() { 491 if (!lastNotificationId) { 492 log('No notification to update'); 493 return; 494 } 495 496 window.AppServices.notifications.update(lastNotificationId, { 497 title: 'Updated Notification', 498 message: 'This notification was updated!', 499 badge: 99 500 }); 501 log(`Updated notification (ID: ${lastNotificationId})`); 502} 503 504function dismissLast() { 505 if (!lastNotificationId) { 506 log('No notification to dismiss'); 507 return; 508 } 509 510 window.AppServices.notifications.dismiss(lastNotificationId); 511 log(`Dismissed notification (ID: ${lastNotificationId})`); 512 lastNotificationId = null; 513} 514 515function dismissAll() { 516 window.AppServices.notifications.dismissAll(); 517 log('Dismissed all notifications'); 518 lastNotificationId = null; 519} 520 521function showStats() { 522 const count = window.AppServices.notifications.count(); 523 const statsEl = document.getElementById('stats'); 524 statsEl.innerHTML = ` 525 <div class="dev-stat"> 526 <div class="dev-stat-value">${count}</div> 527 <div class="dev-stat-label">Active Notifications</div> 528 </div> 529 <div class="dev-stat"> 530 <div class="dev-stat-value">${lastNotificationId || 'N/A'}</div> 531 <div class="dev-stat-label">Last ID</div> 532 </div> 533 `; 534 log(`Stats: ${count} active notifications`); 535} 536 537// Stress Tests 538function spawnThree() { 539 for (let i = 1; i <= 3; i++) { 540 setTimeout(() => { 541 const id = window.AppServices.notifications.show({ 542 app: 'dev', 543 icon: '🚀', 544 title: `Notification ${i}/3`, 545 message: `Testing notification stacking (${i} of 3)`, 546 action: '/app/dev', 547 badge: i 548 }); 549 log(`Spawned notification ${i}/3 (ID: ${id})`); 550 }, (i - 1) * 300); 551 } 552} 553 554function spawnTen() { 555 for (let i = 1; i <= 10; i++) { 556 setTimeout(() => { 557 const id = window.AppServices.notifications.show({ 558 app: 'dev', 559 icon: '💥', 560 title: `Batch ${i}/10`, 561 message: `Testing max visible limit (only last 5 should show)`, 562 action: '/app/dev', 563 badge: i 564 }); 565 log(`Spawned notification ${i}/10 (ID: ${id})`); 566 }, (i - 1) * 200); 567 } 568} 569 570function spawnSequential() { 571 if (sequentialTimer) { 572 clearInterval(sequentialTimer); 573 sequentialTimer = null; 574 log('Stopped sequential spawn'); 575 return; 576 } 577 578 let count = 1; 579 sequentialTimer = setInterval(() => { 580 const id = window.AppServices.notifications.show({ 581 app: 'dev', 582 icon: '⏰', 583 title: `Sequential #${count}`, 584 message: 'New notification every 5 seconds', 585 action: '/app/dev', 586 autoDismiss: 8000 587 }); 588 log(`Sequential spawn #${count} (ID: ${id})`); 589 count++; 590 591 if (count > 10) { 592 clearInterval(sequentialTimer); 593 sequentialTimer = null; 594 log('Sequential spawn complete'); 595 } 596 }, 5000); 597 598 log('Started sequential spawn (10 notifications, 5s intervals)'); 599} 600 601// Populate facet dropdown from window.facetsData 602function populateFacetDropdown() { 603 const facetSelect = document.getElementById('custom-facet'); 604 if (!facetSelect || !window.facetsData) return; 605 606 // Clear existing options except "None" 607 facetSelect.innerHTML = '<option value="">None</option>'; 608 609 // Add facet options 610 window.facetsData.forEach(facet => { 611 const option = document.createElement('option'); 612 option.value = facet.name; 613 option.textContent = `${facet.emoji} ${facet.title}`; 614 facetSelect.appendChild(option); 615 }); 616} 617 618// Callosum Event Viewer 619(function() { 620 const eventLog = document.getElementById('eventLog'); 621 const eventsCard = document.getElementById('eventsCard'); 622 const pauseBtn = document.getElementById('pauseBtn'); 623 const missedCountEl = document.getElementById('missedCount'); 624 let paused = false; 625 let missed = 0; 626 627 function togglePause() { 628 paused = !paused; 629 if (paused) { 630 pauseBtn.classList.add('paused'); 631 pauseBtn.innerHTML = '<span>|></span><span>Resume</span>'; 632 eventsCard.classList.add('paused'); 633 missed = 0; 634 missedCountEl.style.display = 'none'; 635 } else { 636 pauseBtn.classList.remove('paused'); 637 pauseBtn.innerHTML = '<span>||</span><span>Pause</span>'; 638 eventsCard.classList.remove('paused'); 639 missed = 0; 640 missedCountEl.style.display = 'none'; 641 } 642 } 643 644 function appendEvent(msg) { 645 if (paused) { 646 missed++; 647 missedCountEl.textContent = missed + ' missed'; 648 missedCountEl.style.display = 'inline'; 649 return; 650 } 651 652 const line = document.createElement('div'); 653 line.className = 'event-line'; 654 if (msg.event === 'error' || (msg.event === 'exit' && msg.exit_code !== 0)) { 655 line.className = 'event-line error'; 656 } 657 line.textContent = JSON.stringify(msg); 658 eventLog.appendChild(line); 659 eventLog.scrollTop = eventLog.scrollHeight; 660 661 while (eventLog.children.length > 100) { 662 eventLog.removeChild(eventLog.firstChild); 663 } 664 } 665 666 pauseBtn.addEventListener('click', togglePause); 667 if (window.appEvents) { 668 window.appEvents.listen('*', appendEvent); 669 } 670})(); 671 672// Initial log and setup 673populateFacetDropdown(); 674log('Dev Tools loaded - ready to test notifications'); 675</script>