diff --git a/CHANGELOG.MD b/CHANGELOG.MD index 7469d01..60695dd 100644 --- a/CHANGELOG.MD +++ b/CHANGELOG.MD @@ -13,6 +13,7 @@ * Made every web header logo link to the home page * Added a high-contrast Red color theme * Brightened the Red theme surfaces and deepened its crimson accents + * Allow every user to filter the feed using every available tag or label ### Fixed * Fixed element ID conflict on the Admin page so Available Themes load correctly * Positioned new-entry tag checkboxes after their label text @@ -20,6 +21,9 @@ * Prevented the new-entry form grid from placing tag checkboxes above their labels * Kept unconfigured New Entry Mastodon publishing controls hidden * Restored the home-page header layout, keeping the action controls above the filter toolbar + * Made the tag filter refresh the feed directly when its selection changes + * Cache-busted the feed script to ensure browsers load the responsive tag filter + * Fixed feed initialization so tag filtering does not receive promise results as a selected tag [Still no complete fix] ## Version v0.1.1 ### Features diff --git a/VIBE/CHAT_LOG.md b/VIBE/CHAT_LOG.md index 7b5970d..ea5dff1 100644 --- a/VIBE/CHAT_LOG.md +++ b/VIBE/CHAT_LOG.md @@ -1394,3 +1394,27 @@ Since checkin d433a305f5cd8ccdafde8a0f7764462e8c167162 on the home page the filt ### Assistant outcome Confirmed the referenced commit did not change the feed header, then identified the current template had the toolbar outside its `.header-tools` wrapper. Restored the prior header structure: the New Entry and menu controls remain grouped above the filter toolbar on the right side of the home-page header. Added a rendered-page regression assertion for `.header-tools`. + +### User +I want every user to filter on every available tag/label + +### Assistant outcome +Separated feed filtering from label management: `/api/tags` now returns every tag/label to all callers, allowing complete filtering of the public feed. `/api/user/labels` remains ownership-scoped, so users still cannot manage other users' labels. Updated tag visibility regression coverage for authenticated and anonymous callers. + +### User +tag filter doesn't seem to respond when pressed + +### Assistant outcome +Updated the tag-filter change handler to pass the newly selected tag directly to the feed refresh instead of relying on the browser to persist and immediately reread the preference cookie. This makes filtering work even when cookie persistence is unavailable or delayed. Cache-busted the feed script to `feed.js?v=10` and added a regression assertion for the direct refresh path. + +### User +The tag filter still does not allow me to select tags, even though the raw html contains the correct content. Also the user filter and the sort functionality work too + +### Assistant outcome +Verified the live running container with Chromium and Playwright: selecting `#AI` changes the tag dropdown value, writes the preference cookie, and renders only matching feed entries. The deployed script already contains the direct-refresh handler, so cache-busted it again to `feed.js?v=11` and rebuilt the Compose service. Confirmed the live page serves the new asset version. + +### User +I believe tag selection was still working for v0.1.0 - it is still broken + +### Assistant outcome +Found the actual regression in the direct-refresh update: `Promise.all([loadUsers(), loadTags()]).then(loadFeed)` passed its results array into the new `selectedTag` parameter. The feed then attempted to call `toLowerCase()` on that array during initialization. Restored the v0.1.0 callback shape with `.then(() => loadFeed())`, retained direct selected-tag refreshes, added a regression assertion, and cache-busted the feed asset to `feed.js?v=12`. diff --git a/VIBE/PROMPTS.md b/VIBE/PROMPTS.md index bd4c288..beea42f 100644 --- a/VIBE/PROMPTS.md +++ b/VIBE/PROMPTS.md @@ -248,6 +248,10 @@ 229. Add a red collor theme 230. Make the theme brighter and darker red 231. Since checkin d433a305f5cd8ccdafde8a0f7764462e8c167162 on the home page the filter toolbar moved so that the menu button and new entry are not in the top left anylonger - revert to the previous layout +232. I want every user to filter on every available tag/label +233. tag filter doesn't seem to respond when pressed +234. The tag filter still does not allow me to select tags, even though the raw html contains the correct content. Also the user filter and the sort functionality work too +235. I believe tag selection was still working for v0.1.0 - it is still broken ## Future entries diff --git a/backend/app/api/links.py b/backend/app/api/links.py index 40702c2..506e628 100644 --- a/backend/app/api/links.py +++ b/backend/app/api/links.py @@ -6,7 +6,6 @@ from fastapi import APIRouter, Depends, Header, HTTPException, Response, status import logging from pydantic import BaseModel -from backend.app.api.dependencies import get_optional_current_user from backend.app.services.link_service import create_link, delete_link, find_owned_link_by_title_url, get_link_tags, get_owned_link, list_public_links, list_tags, mark_mastodon_posted, update_link from backend.app.database import get_connection from backend.app.services.plugin_manager import plugin_manager @@ -35,9 +34,8 @@ class LinkUpdate(BaseModel): @router.get('/tags') -def available_tags(user: dict | None = Depends(get_optional_current_user)): - user_id = user['id'] if user else None - return list_tags(user_id=user_id) +def available_tags(): + return list_tags() @router.get('/scrape') diff --git a/backend/app/services/link_service.py b/backend/app/services/link_service.py index a566638..10bf027 100644 --- a/backend/app/services/link_service.py +++ b/backend/app/services/link_service.py @@ -227,32 +227,9 @@ def list_public_users(): return [row['username'] for row in rows] -def list_tags(user_id: str | None = None): +def list_tags(): with get_connection() as conn: - if user_id: - rows = conn.execute( - ''' - SELECT tags.name - FROM tags - LEFT JOIN users ON users.id = tags.created_by - WHERE tags.created_by IS NULL - OR tags.created_by = ? - OR users.is_admin = 1 - ORDER BY tags.name - ''', - (user_id,), - ).fetchall() - else: - rows = conn.execute( - ''' - SELECT tags.name - FROM tags - LEFT JOIN users ON users.id = tags.created_by - WHERE tags.created_by IS NULL - OR users.is_admin = 1 - ORDER BY tags.name - ''', - ).fetchall() + rows = conn.execute('SELECT name FROM tags ORDER BY name').fetchall() tags = [] seen = set() for row in rows: diff --git a/backend/tests/test_api.py b/backend/tests/test_api.py index e69ffee..e2a86b8 100644 --- a/backend/tests/test_api.py +++ b/backend/tests/test_api.py @@ -564,17 +564,17 @@ def test_label_visibility_isolation_and_grandfathering(): assert '#Cybersecurity' in charlie_label_names assert '#BobOnlyLabel' not in charlie_label_names - # Bob views /api/tags (authenticated): sees Bob tag & default/grandfathered, NOT Charlie tag + # Every user can filter by every available tag, including another user's label. bob_tags = client.get('/api/tags', headers=bob_headers).json() assert '#BobOnlyLabel' in bob_tags assert '#GrandfatheredLabel' in bob_tags - assert '#CharlieOnlyLabel' not in bob_tags + assert '#CharlieOnlyLabel' in bob_tags - # Anonymous views /api/tags: sees default/grandfathered, NOT Bob or Charlie tag + # The public feed filter has the same complete tag catalog. anon_tags = client.get('/api/tags').json() assert '#GrandfatheredLabel' in anon_tags - assert '#BobOnlyLabel' not in anon_tags - assert '#CharlieOnlyLabel' not in anon_tags + assert '#BobOnlyLabel' in anon_tags + assert '#CharlieOnlyLabel' in anon_tags # Bob cannot edit or delete grandfathered label assert client.put(f"/api/user/labels/{grandfathered_id}", headers=bob_headers, json={'name': '#RenamedGrandfathered'}).status_code == 404 @@ -828,7 +828,11 @@ def test_public_and_admin_pages_render_html(): assert 'if (item.is_owner && !showIdentity)' in feed_script assert 'deleteEntry(item, deleteButton)' in feed_script assert 'postToMastodon(item, mastodonButton)' in feed_script - assert 'tag.toLowerCase() === pref.tag.toLowerCase()' in feed_script + assert 'tag.toLowerCase() === activeTag.toLowerCase()' in feed_script + assert 'loadFeed(event.target.value)' in feed_script + assert 'Promise.all([loadUsers(), loadTags()]).then(() => loadFeed())' in feed_script + assert "fetch('/api/tags', {" in feed_script + assert 'Authorization: `Bearer ${accessToken}`' in feed_script assert 'return `${date.getFullYear()} ${months[date.getMonth()]} ${date.getDate()} - ${hours}:${minutes}`' in feed_script assert "entryMeta.className = 'entry-meta'" in feed_script assert "meta.className = 'meta'" in feed_script diff --git a/frontend/static/feed.js b/frontend/static/feed.js index 8f94c34..e0fb2a6 100644 --- a/frontend/static/feed.js +++ b/frontend/static/feed.js @@ -73,7 +73,9 @@ async function loadUsers() { } async function loadTags() { - const response = await fetch('/api/tags'); + const response = await fetch('/api/tags', { + headers: accessToken ? {Authorization: `Bearer ${accessToken}`} : {}, + }); if (!response.ok) throw new Error('Could not load tags'); const tags = await response.json(); availableTags = tags; @@ -254,7 +256,7 @@ function showEditForm(article, item) { article.appendChild(form); } -async function loadFeed() { +async function loadFeed(selectedTag = null) { const routeUser = document.body.dataset.userFilter; const endpoint = routeUser ? `/api/public/feed/${encodeURIComponent(routeUser)}` @@ -266,13 +268,14 @@ async function loadFeed() { let items = data || []; const pref = readPreferences(); + const activeTag = selectedTag ?? pref.tag; if (pref.user && !routeUser) { items = items.filter((item) => (item.user?.username || '').toLowerCase() === pref.user.toLowerCase()); } - if (pref.tag) { - items = items.filter((item) => item.tags?.some((tag) => tag.toLowerCase() === pref.tag.toLowerCase())); + if (activeTag) { + items = items.filter((item) => item.tags?.some((tag) => tag.toLowerCase() === activeTag.toLowerCase())); } if (pref.sort === 'oldest') { @@ -304,9 +307,9 @@ function syncPreferences() { tagFilter.addEventListener('change', (event) => { const next = { ...readPreferences(), tag: event.target.value }; writePreferences(next); - loadFeed(); + loadFeed(event.target.value); }); } syncPreferences(); -Promise.all([loadUsers(), loadTags()]).then(loadFeed).catch(() => loadFeed()); +Promise.all([loadUsers(), loadTags()]).then(() => loadFeed()).catch(() => loadFeed()); diff --git a/frontend/templates/feed.html b/frontend/templates/feed.html index 9aab98c..53966bc 100644 --- a/frontend/templates/feed.html +++ b/frontend/templates/feed.html @@ -86,6 +86,6 @@ - +