Skip to content
This repository was archived by the owner on Jul 15, 2026. It is now read-only.

Media filter bar: multiple-sender filter + single-line layout - #25

Merged
joestump-agent merged 4 commits into
mainfrom
feature/6-7-media-filter-bar
Jul 11, 2026
Merged

Media filter bar: multiple-sender filter + single-line layout#25
joestump-agent merged 4 commits into
mainfrom
feature/6-7-media-filter-bar

Conversation

@joestump-agent

Copy link
Copy Markdown
Owner

Part of #6, #7. Closes #6, #7.

Issue #6 — filter by multiple senders

  • store.GalleryFilter.ConversationID (int64) → ConversationIDs []int64. Clause builders bind the set as conversation_id IN (?,?,…) with bound parameters (new inPlaceholders helper, never string-interpolated); ListAttachments, ListLinks, CountMedia all share them. listAttachmentsSQL still pins idx_attachments_conv_kind, so the keyset walk stays index-driven (query-plan test extended to prove no scan).
  • parseGalleryFilter reads every repeated ?conversation= param (dedup, drop garbage/non-positive, capped to guard SQLITE_MAX_VARIABLE_NUMBER); filterValues re-emits each id, so the selection survives tab switches and infinite-scroll pagination. Empty set = all conversations.
  • Multi-select UI: a CSS-only <details>/<summary> dropdown of checkboxes — zero-JS (CSP script-src 'self' clean), keyboard-accessible, form-native (checked boxes submit as repeated GET params). Collapsed summary shows "All conversations", the single name, or "N conversations". Known tradeoff: no close-on-outside-click without JS.

Issue #7 — filter bar layout

  • Conversation control capped at 13rem with ellipsis (long names never widen the bar); the dropdown panel is absolutely positioned so opening never reflows the row.
  • At ≥1024px the Media bar is flex-wrap: nowrap (Conversation, Source, From, To, Sort, Apply on one line, conversation shrinking first), scoped to a new .filter-row-media class so the Search page's bar keeps its existing wrap.

Carried-over nit from PR #16

On the Links tab the sort <select> is hidden; it now renders a hidden sort input so Apply from Links preserves sort=asc.

CSS

internal/web/static/app.css regenerated from input.css with the pinned Tailwind toolchain (not hand-edited).

🤖 Generated with Claude Code


Generated by Claude Code

claude added 4 commits July 11, 2026 08:19
Replace GalleryFilter.ConversationID (int64) with ConversationIDs
([]int64): attachment/link/count clauses bind the set as an IN(...)
parameter list (never string-interpolated), and listAttachmentsSQL keeps
pinning idx_attachments_conv_kind whenever the set is non-empty — SQLite
runs the IN over the index's leading column as one seek per id, so the
walk stays index-driven. MCP's list_media/list_links wrap their single
resolved conversation in a one-element set.

TestGalleryQueryPlans gains a multi-conversation case proving no table
scan crept in, and TestGalleryMultiConversationFilter covers union
semantics (lists + counts, exclusion of unselected conversations, empty
set = all).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01YWWaS4f9mr6Yp43S4GXHE7
…sues #6, #7)

The Media conversation filter becomes a CSS-only multi-select: a
<details> dropdown of checkboxes (no inline JS, CSP script-src 'self'
clean; Enter/Space toggles the summary, Tab walks the checkboxes). Every
checked conversation submits a repeated ?conversation= param;
parseGalleryFilter reads the whole set (deduped, garbage dropped) and
filterValues re-emits it, so the selection rides tab links and the
infinite-scroll load-more URLs. Empty selection = all conversations.
The collapsed control labels itself with the one selected name, a count,
or "All conversations".

Filter bar layout (issue #7): the summary is capped at 13rem and
ellipsizes, so long conversation names never widen the bar; at >=1024px
the Media bar (scoped .filter-row-media — Search keeps wrapping) stays
on one line with the conversation control shrinking first, and narrower
viewports fall back to the default wrap. The dropdown panel is
absolutely positioned so opening it never reflows the bar.

Also fixes the PR #16 review nit: the Links tab hides the inert Sort
select, so a previously chosen sort=asc now travels as a hidden input —
Apply from Links no longer silently resets the order.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01YWWaS4f9mr6Yp43S4GXHE7
Review fixes for the media filter bar (issues #6, #7):

- parseGalleryFilter clamps the distinct ?conversation= id set to 200
  (maxConversationFilterIDs). Each id becomes one bound IN(...) parameter,
  so an unbounded crafted URL could exceed SQLite's 32766-parameter limit
  and 500 every gallery query at prepare. The UI can never produce more
  than one id per conversation, so the cap only bites hand-crafted URLs.
- The multi-select summary now uses
  aria-labelledby="conv-filter-label conv-filter-value" with an id on the
  value span, so screen readers announce both the label and the current
  selection ("All conversations" / a name / "N conversations") instead of
  just "Conversation".

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01YWWaS4f9mr6Yp43S4GXHE7
…ter-bar

# Conflicts:
#	internal/web/static/app.css
@joestump-agent
joestump-agent merged commit 0f98003 into main Jul 11, 2026
3 checks passed
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Media: allow filtering by multiple senders

2 participants