personal memory agent
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

add Pulse conversational integration (Phase 3)

Expose window.openConversation() as a generic public API for any app to
pre-fill the chat bar and optionally auto-send. Add data-conversation
attributes and delegated click handler to Pulse elements — narrative,
calendar events, activities, todos, entities, and attention items — so
clicking any element pre-fills a context-appropriate prompt.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

+59 -6
+40 -6
apps/home/workspace.html
··· 278 278 } 279 279 280 280 .pulse-network-link a:hover { text-decoration: underline; } 281 + 282 + /* Click-to-converse interactive elements */ 283 + [data-conversation] { 284 + cursor: pointer; 285 + transition: background 0.15s ease; 286 + border-radius: 4px; 287 + } 288 + [data-conversation]:hover { 289 + background: #f1f5f9; 290 + } 291 + .pulse-tell-more { 292 + display: inline-block; 293 + margin-top: 0.75rem; 294 + font-size: 0.8rem; 295 + color: #6366f1; 296 + text-decoration: none; 297 + } 298 + .pulse-tell-more:hover { 299 + text-decoration: underline; 300 + background: transparent; 301 + } 281 302 </style> 282 303 283 304 <div class="pulse-dashboard"> ··· 317 338 <div class="pulse-narrative" id="pulse-narrative"> 318 339 <div class="pulse-section-header">{{ narrative_header }}</div> 319 340 <div class="pulse-narrative-content" id="pulse-narrative-content"></div> 341 + <a href="#" class="pulse-tell-more" data-conversation="Tell me more about what's happening today">Tell me more →</a> 320 342 {% if narrative_updated_at %} 321 343 <div class="pulse-narrative-meta">Updated at {{ narrative_updated_at }}</div> 322 344 {% endif %} ··· 336 358 <div class="pulse-events"> 337 359 {% for event in events %} 338 360 {% set is_past = event.get('occurred', false) or (event.get('end', '') and event['end'] < now.strftime('%H:%M:%S')) %} 339 - <div class="pulse-event{% if is_past %} past{% endif %}"> 361 + <div class="pulse-event{% if is_past %} past{% endif %}" data-conversation="Tell me about {{ event.get('title', 'Untitled') }} at {{ event.get('start', '')[:5] }}"> 340 362 <span class="pulse-event-time">{{ event.get('start', '')[:5] }}</span> 341 363 <span class="pulse-event-title">{{ event.get('title', 'Untitled') }}</span> 342 364 </div> ··· 347 369 <div class="pulse-activities-label">Recent Activity</div> 348 370 <div class="pulse-activities"> 349 371 {% for act in activities[:6] %} 350 - <div class="pulse-activity"> 372 + <div class="pulse-activity" data-conversation="Tell me about {{ act.get('description', act.get('activity', '')) }}"> 351 373 <span class="pulse-activity-time">{{ act.get('display_time', '') }}</span> 352 374 <span>{{ act.get('description', act.get('activity', '')) }}</span> 353 375 </div> ··· 370 392 <div class="pulse-section-header">Needs You</div> 371 393 <div class="pulse-needs-list"> 372 394 {% if attention %} 373 - <div class="pulse-needs-item pulse-needs-attention">{{ attention.placeholder_text }}</div> 395 + <div class="pulse-needs-item pulse-needs-attention" data-conversation="What happened with {{ attention.placeholder_text }}?">{{ attention.placeholder_text }}</div> 374 396 {% endif %} 375 397 {% for item in pulse_needs %} 376 - <div class="pulse-needs-item">{{ item }}</div> 398 + <div class="pulse-needs-item" data-conversation="What happened with {{ item }}?">{{ item }}</div> 377 399 {% endfor %} 378 400 {% for todo in todos[:7] %} 379 - <div class="pulse-needs-item">{{ todo.get('text', '') }}{% if todo.get('facet') %} <span style="color:#94a3b8;font-size:0.75rem">({{ todo['facet'] }})</span>{% endif %}</div> 401 + <div class="pulse-needs-item" data-conversation="What's the context on: {{ todo.get('text', '') }}">{{ todo.get('text', '') }}{% if todo.get('facet') %} <span style="color:#94a3b8;font-size:0.75rem">({{ todo['facet'] }})</span>{% endif %}</div> 380 402 {% endfor %} 381 403 </div> 382 404 {% if todos|length > 7 %} ··· 391 413 <div class="pulse-section-header">Network Pulse</div> 392 414 <div class="pulse-entities"> 393 415 {% for ent in entities %} 394 - <div class="pulse-entity"> 416 + <div class="pulse-entity" data-conversation="What do you know about {{ ent.get('name', '') }}?"> 395 417 <span class="pulse-entity-type {{ ent.get('entity_type', 'unknown') }}">{{ ent.get('entity_type', '?')[:3] }}</span> 396 418 <span>{{ ent.get('name', '') }}</span> 397 419 </div> ··· 411 433 if (narrativeRaw) { 412 434 const el = document.getElementById('pulse-narrative-content'); 413 435 if (el) el.innerHTML = marked.parse(narrativeRaw, {breaks: true, gfm: true}); 436 + } 437 + 438 + // Click-to-converse: delegated handler for all [data-conversation] elements 439 + var dashboard = document.querySelector('.pulse-dashboard'); 440 + if (dashboard) { 441 + dashboard.addEventListener('click', function(e) { 442 + var el = e.target.closest('[data-conversation]'); 443 + if (el && window.openConversation) { 444 + e.preventDefault(); 445 + window.openConversation({ prompt: el.dataset.conversation }); 446 + } 447 + }); 414 448 } 415 449 416 450 // WebSocket live updates
+19
convey/templates/app.html
··· 190 190 // Expose for websocket navigate handler 191 191 window._closeConversationPanel = closePanel; 192 192 193 + // --- Public conversation API --- 194 + // Generic API for any app to open the conversation panel with a pre-filled prompt. 195 + window.openConversation = function(options) { 196 + options = options || {}; 197 + if (options.prompt) { 198 + input.value = options.prompt; 199 + input.dispatchEvent(new Event('input')); 200 + } 201 + if (options.autoSend) { 202 + form.requestSubmit(); 203 + } else { 204 + input.focus(); 205 + input.setSelectionRange(input.value.length, input.value.length); 206 + if (options.prompt && options.prompt.length > 40) { 207 + openPanel(); 208 + } 209 + } 210 + }; 211 + 193 212 // Save state before navigation 194 213 window.addEventListener('pagehide', save); 195 214