personal memory agent
0
fork

Configure Feed

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

feat: add app bar input and native facet filtering to todos

Move the new todo input from workspace into the app bar alongside date
navigation controls. The input is enabled for today and future dates,
disabled for historical dates with appropriate placeholder text.

Add native facet selection support - when a specific facet is selected,
only that facet's todos are shown. The input placeholder dynamically
updates to indicate which facet new todos will be added to, and the
server uses the selected facet as the default when no #facet hashtag
is specified.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>

+118 -42
+74
apps/todos/app_bar.html
··· 1 + <style> 2 + .todo-add-input { 3 + flex: 1; 4 + min-width: 0; 5 + padding: 0.75em 1em; 6 + border: 1px solid #d1d5db; 7 + border-radius: 20px; 8 + font-family: inherit; 9 + font-size: 14px; 10 + line-height: 1.5; 11 + transition: border-color 0.15s, box-shadow 0.15s; 12 + } 13 + 14 + .todo-add-input:focus { 15 + outline: none; 16 + border-color: var(--facet-color, #2563eb); 17 + box-shadow: 0 0 0 3px var(--facet-bg, rgba(37, 99, 235, 0.1)); 18 + } 19 + 20 + .todo-add-input::placeholder { 21 + color: #9ca3af; 22 + } 23 + 24 + .todo-add-input:disabled { 25 + background: #f3f4f6; 26 + color: #9ca3af; 27 + cursor: not-allowed; 28 + opacity: 0.7; 29 + } 30 + 31 + .todo-add-input:disabled::placeholder { 32 + color: #d1d5db; 33 + } 34 + </style> 35 + 1 36 {% include 'date_nav.html' %} 37 + 38 + <!-- Use display:contents to make form invisible to flexbox --> 39 + <form method="post" style="display: contents;"> 40 + <input type="hidden" name="action" value="add"> 41 + {% set is_future_or_today = day >= today_day %} 42 + <input 43 + type="text" 44 + name="text" 45 + id="todo-add-input" 46 + class="todo-add-input" 47 + placeholder="{% if not is_future_or_today %}Can't add todos to past dates{% elif selected_facet %}Add a todo to {{ facets|selectattr('name', 'equalto', selected_facet)|map(attribute='title')|first|default(selected_facet) }}...{% else %}Add a todo... (use #facet){% endif %}" 48 + autocomplete="off" 49 + {% if not is_future_or_today %}disabled{% endif %} 50 + > 51 + </form> 52 + 53 + {% if is_future_or_today %} 54 + <script> 55 + (function() { 56 + const input = document.getElementById('todo-add-input'); 57 + if (!input || input.disabled) return; 58 + 59 + function updatePlaceholder(facetName) { 60 + if (facetName) { 61 + const facetData = window.facetsData?.find(f => f.name === facetName); 62 + const facetTitle = facetData?.title || facetName; 63 + input.placeholder = `Add a todo to ${facetTitle}...`; 64 + } else { 65 + input.placeholder = 'Add a todo... (use #facet)'; 66 + } 67 + } 68 + 69 + // Listen for facet selection changes 70 + window.addEventListener('facet.switch', (e) => { 71 + updatePlaceholder(e.detail.facet); 72 + }); 73 + })(); 74 + </script> 75 + {% endif %}
+4 -2
apps/todos/routes.py
··· 15 15 ) 16 16 17 17 from convey import state 18 + from convey.config import get_selected_facet 18 19 from convey.utils import DATE_RE, adjacent_days, format_date 19 20 from think.facets import get_facets 20 21 from think.todo import ( ··· 76 77 flash(f"Facet #{facet} does not exist", "error") 77 78 return redirect(url_for("app:todos.todos_day", day=day)) 78 79 else: 79 - # Default to personal if no hashtag 80 - facet = "personal" 80 + # Use selected facet as default, fall back to personal 81 + selected = get_selected_facet() 82 + facet = selected if selected else "personal" 81 83 82 84 if not text: 83 85 flash("Cannot add an empty todo", "error")
+40 -40
apps/todos/workspace.html
··· 8 8 border-bottom: 1px solid #e5e7eb; 9 9 } 10 10 11 - .add-form { 12 - margin-bottom: 2rem; 13 - } 14 - 15 - .add-form input[type="text"] { 16 - width: 100%; 17 - padding: 0.75rem 1rem; 18 - border: 1px solid #d1d5db; 19 - border-radius: 8px; 20 - font-size: 0.95rem; 21 - transition: border-color 0.15s, box-shadow 0.15s; 22 - } 23 - 24 - .add-form input[type="text"]:focus { 25 - outline: none; 26 - border-color: #2563eb; 27 - box-shadow: 0 0 0 3px rgba(37, 99, 235, 0.1); 28 - } 29 - 30 - .add-form input[type="text"]::placeholder { 31 - color: #9ca3af; 32 - } 33 - 34 11 .facet-section { 35 12 margin-bottom: 1.5rem; 36 13 } ··· 255 232 {% endif %} 256 233 {% endwith %} 257 234 258 - {% if day == today_day %} 259 - <form method="post" class="add-form"> 260 - <input type="hidden" name="action" value="add"> 261 - <input 262 - type="text" 263 - name="text" 264 - placeholder="Add a todo... (use #facet)" 265 - autocomplete="off" 266 - autofocus 267 - > 268 - </form> 269 - {% endif %} 270 - 271 235 {% if todos_by_facet %} 272 236 {% for facet_name, facet_todos in todos_by_facet.items() %} 273 237 {% set facet_info = facet_map.get(facet_name, {}) %} ··· 370 334 const moveEndpoint = {{ url_for('app:todos.move_todo', day=day) | tojson }}; 371 335 const currentDay = {{ day | tojson }}; 372 336 const storageKey = `todos_collapsed_${currentDay}`; 337 + 338 + // Facet filtering based on global selection 339 + function filterByFacet(facetName) { 340 + const sections = document.querySelectorAll('.facet-section'); 341 + const noTodosEl = document.querySelector('.no-todos'); 342 + 343 + if (facetName === null) { 344 + // All-facet mode - show all sections 345 + sections.forEach(s => s.style.display = ''); 346 + if (noTodosEl) { 347 + noTodosEl.querySelector('p').textContent = 'No todos for this day yet.'; 348 + } 349 + } else { 350 + // Specific facet mode - show only matching section 351 + let hasVisibleSection = false; 352 + sections.forEach(s => { 353 + if (s.dataset.facet === facetName) { 354 + s.style.display = ''; 355 + hasVisibleSection = true; 356 + } else { 357 + s.style.display = 'none'; 358 + } 359 + }); 360 + 361 + // Update no-todos message for specific facet 362 + if (noTodosEl && !hasVisibleSection) { 363 + const facetData = window.facetsData?.find(f => f.name === facetName); 364 + const facetTitle = facetData?.title || facetName; 365 + noTodosEl.querySelector('p').textContent = `No todos for ${facetTitle} yet.`; 366 + } 367 + } 368 + } 369 + 370 + // Initialize facet filtering 371 + filterByFacet(window.selectedFacet); 372 + 373 + // Listen for facet selection changes 374 + window.addEventListener('facet.switch', (e) => { 375 + filterByFacet(e.detail.facet); 376 + }); 373 377 374 378 // Load collapsed state from sessionStorage 375 379 function loadCollapsedState() { ··· 887 891 }); 888 892 }); 889 893 }); 890 - 891 - // Auto-focus add input 892 - const addInput = document.querySelector('.add-form input[name="text"]'); 893 - addInput?.focus(); 894 894 895 895 // Listen for cortex events (agent completion) 896 896 if (window.appEvents) {