From 8596f539c72825e3fb2e12319be33eac9ab7d8ea Mon Sep 17 00:00:00 2001 From: mikael-lovqvists-claude-agent Date: Sun, 24 May 2026 16:14:15 +0000 Subject: [PATCH] Fix status filter: strict tree filtering and URL param handling - render_tree_node now applies entry_matches_filter directly; parent entries no longer bleed through when only a descendant matches - Filter dropdowns use set_filter() to persist state in URL query params - apply_search() reads filter state from URL on init and popstate - build_url() encodes filter state; uses ?status=all for "all statuses" to distinguish from absent param (which defaults to open) - apply_search() treats absent or empty status param as 'open' Co-Authored-By: Claude Sonnet 4.6 --- public/app.mjs | 42 ++++++++++++++++++++++++++++++++++-------- 1 file changed, 34 insertions(+), 8 deletions(-) diff --git a/public/app.mjs b/public/app.mjs index e70f1d4..87484a6 100644 --- a/public/app.mjs +++ b/public/app.mjs @@ -306,8 +306,8 @@ function make_entry_row(entry, children_map) { return row; } -function render_tree_node(entry, children_map, is_child = false) { - if (!is_child && !node_or_descendants_match(entry, children_map)) { return null; } +function render_tree_node(entry, children_map) { + if (!entry_matches_filter(entry)) { return null; } const children = children_map.get(entry.id) ?? []; const node = document.createElement('div'); @@ -318,7 +318,8 @@ function render_tree_node(entry, children_map, is_child = false) { const children_el = document.createElement('div'); children_el.className = 'task-children'; for (const child of children) { - children_el.appendChild(render_tree_node(child, children_map, true)); + const child_node = render_tree_node(child, children_map); + if (child_node) { children_el.appendChild(child_node); } } node.appendChild(children_el); } @@ -409,7 +410,7 @@ function render_entries(container) { if (val === state.filter_status) { opt.selected = true; } status_sel.appendChild(opt); } - status_sel.addEventListener('change', () => { state.filter_status = status_sel.value; render(); }); + status_sel.addEventListener('change', () => { set_filter('filter_status', status_sel.value); }); filter_bar.appendChild(status_sel); const priority_sel = document.createElement('select'); @@ -419,7 +420,7 @@ function render_entries(container) { if (val === state.filter_priority) { opt.selected = true; } priority_sel.appendChild(opt); } - priority_sel.addEventListener('change', () => { state.filter_priority = priority_sel.value; render(); }); + priority_sel.addEventListener('change', () => { set_filter('filter_priority', priority_sel.value); }); filter_bar.appendChild(priority_sel); const tag_sel = document.createElement('select'); @@ -443,7 +444,7 @@ function render_entries(container) { search_input.type = 'text'; search_input.placeholder = 'Search…'; search_input.value = state.search; - search_input.addEventListener('input', () => { state.search = search_input.value; render(); }); + search_input.addEventListener('input', () => { set_filter('search', search_input.value); }); filter_bar.appendChild(search_input); container.appendChild(filter_bar); @@ -464,7 +465,7 @@ function render_entries(container) { list.appendChild(make_entry_row(entry, children_map)); } } else { - const visible = roots.filter(e => node_or_descendants_match(e, children_map)); + const visible = roots.filter(e => entry_matches_filter(e)); if (!visible.length) { const empty = document.createElement('div'); empty.className = 'empty-state'; @@ -646,14 +647,38 @@ function navigate_new(type, parent_id) { navigate(hash); } +function build_url(hash) { + const params = new URLSearchParams(); + if (state.filter_status !== 'open') { params.set('status', state.filter_status || 'all'); } + if (state.filter_priority) { params.set('priority', state.filter_priority); } + if (state.search) { params.set('search', state.search); } + const qs = params.toString(); + return (qs ? '?' + qs : '') + '#' + hash; +} + +function apply_search() { + const params = new URLSearchParams(location.search); + const s = params.get('status'); + state.filter_status = (s === null || s === '') ? 'open' : (s === 'all' ? '' : s); + state.filter_priority = params.get('priority') ?? ''; + state.search = params.get('search') ?? ''; +} + function navigate(hash) { - history.pushState(null, '', '#' + hash); + history.pushState(null, '', build_url(hash)); apply_hash(hash); render(); } +function set_filter(key, val) { + state[key] = val; + history.replaceState(null, '', build_url(location.hash.slice(1))); + render(); +} + window.addEventListener('popstate', () => { apply_hash(location.hash.slice(1)); + apply_search(); render(); }); @@ -1188,6 +1213,7 @@ async function init() { state.entry_types = types_res.entry_types; state.entries = entries_res.entries; + apply_search(); apply_hash(location.hash.slice(1)); render(); }