Skip to content

feat(web): add person attachment galleries - #622

Open
salmonumbrella wants to merge 1 commit into
kenn-io:mainfrom
salmonumbrella:feat/person-attachment-gallery
Open

feat(web): add person attachment galleries#622
salmonumbrella wants to merge 1 commit into
kenn-io:mainfrom
salmonumbrella:feat/person-attachment-gallery

Conversation

@salmonumbrella

Copy link
Copy Markdown
Contributor

What changed

  • Add a person-scoped file search that spans every linked identity, source, conversation, and channel, selectable by direction: files sent by the person, sent to them, or exchanged in a shared group conversation.
  • Keep the attachment occurrence message-owned. Each row carries exact provenance — matched cluster members, participant roles, and the owning message, conversation, and source — and deep-links to the source message rather than copying ownership onto the person.
  • Expose the contract at POST /api/v1/people/{id}/files/search and regenerate the Go and web clients from the OpenAPI spec. SQLite and PostgreSQL behavior stays equivalent, and there is no schema or migration change.
  • Add a Media presentation to the person's Files view that separates visual media from other files, throttles thumbnail loading through a shared bounded queue, and keeps the existing archive-wide Files response contract untouched.

Why

A person page could not answer "what files did this contact send me?" without falling back to the archive-wide Files workspace and filtering by hand, which cannot follow one person across linked identities and channels. Scoping the existing file search to an identity cluster answers it directly, and because the attachment occurrence stays with its message, linking or splitting identities changes gallery membership without moving or duplicating blobs.

Usage

Open a person in the web UI and use the Files view. Results default to files received from that person; the direction controls add files sent to them or exchanged in group conversations, and the Media/Files toggle switches between the thumbnail gallery and the existing table.

Semantic search inside the person-scoped result set is deliberately out of scope here — it depends on the document and multimodal indexes in #608 and #609.

Refs #612

@roborev-ci

roborev-ci Bot commented Aug 15, 2026

Copy link
Copy Markdown

roborev: Combined Review (1fde3c7)

Code review found one medium-severity issue; no security vulnerabilities were identified.

Medium

  • internal/query/files.go:443 — Person-file searches unconditionally add NOT deleted_from_source, conflicting with the canonical deletion predicate. A deletion:deleted query becomes contradictory (deleted_from_source AND NOT deleted_from_source) and always returns zero results. Unrestricted queries also silently omit source-deleted files, unlike the normal file route and prior behavior.
    • Fix: Let exploreConditions handle source-deletion filtering, retaining only the dedup-hidden exclusion if needed. Add coverage for person-file searches using both deleted and unrestricted deletion predicates.

Reviewers: 2 done | Synthesis: codex, 11s | Total: 4m50s

@wesm

wesm commented Aug 18, 2026

Copy link
Copy Markdown
Member

looking

@wesm
wesm force-pushed the feat/person-attachment-gallery branch from 1fde3c7 to 5fe0be4 Compare August 18, 2026 16:13
@roborev-ci

roborev-ci Bot commented Aug 18, 2026

Copy link
Copy Markdown

roborev: Combined Review (5fe0be4)

Automatic thumbnail loading introduces two medium-severity browser resource-exhaustion risks.

Medium

  • Unbounded image decodingweb/src/lib/components/files/MediaThumbnail.svelte:89
    Compressed or animated images can consume excessive memory or CPU because the 5 MiB limit does not bound decoded pixels or frames. Validate dimensions and animation metadata, or serve resource-limited static thumbnails.

  • Repeated full-attachment downloadsweb/src/lib/components/files/MediaThumbnail.svelte:79
    Each thumbnail downloads and decodes the full attachment again after leaving and re-entering the viewport. Large galleries can cause substantial transfer and memory churn. Use a dedicated resized-thumbnail endpoint or bounded thumbnail caching.


Reviewers: 2 done | Synthesis: codex, 10s | Total: 4m49s

@roborev-ci

roborev-ci Bot commented Aug 18, 2026

Copy link
Copy Markdown

roborev: Combined Review (2855d9b)

The changes introduce three medium-severity regressions involving default search behavior, query scalability, and thumbnail decoding safety.

Medium

  • internal/api/files.go:482 — Omitting directions now defaults to from_person, silently narrowing behavior for existing clients and excluding files sent to the person or found through conversations. Default omitted directions to all supported directions; callers such as the new UI can request from_person explicitly.

  • internal/query/files.go:437 — Every person-file search selects analytical_entries.*, causing archive-wide participant-list aggregation and creating production-scale memory and latency risks. Filter and paginate through the narrow scalar entry CTE, then enrich participant lists only for the bounded page, similar to buildFileSearchFastSQL.

  • web/src/lib/components/files/MediaThumbnail.svelte:107 — Automatically decoding remote images based only on encoded size and signatures permits image-decompression denial of service through extreme dimensions or animation frame counts. Generate bounded thumbnails server-side or enforce pixel, dimension, and frame-count limits before rendering.


Reviewers: 2 done | Synthesis: codex, 7s | Total: 5m46s

@roborev-ci

roborev-ci Bot commented Aug 18, 2026

Copy link
Copy Markdown

roborev: Combined Review (ed6ff93)

Code review found three medium-severity issues; no critical or high-severity findings.

Medium

  • internal/query/files.go:417 — Direct-chat roster membership is excluded from every direction. Attachments sent by another participant never match to_person, and directionless requests lose previously returned rows. Derive to_person for direct-chat members other than the sender and preserve that relationship in provenance.

  • internal/api/openapi.go:201 — The HTTP contract changed without incrementing APISchemaVersion, so incompatible daemons both report 1.43.0. Bump the schema to 1.44.0 and update pinned tests and generated specifications.

  • pkg/client/generated/types.go:5327PersonFileSearchRow uses required plain strings for filename and mime_type, though empty archive metadata is valid. Add this row type to the client-schema nullable override and regenerate the client.


Reviewers: 2 done | Synthesis: codex, 9s | Total: 7m49s

@roborev-ci

roborev-ci Bot commented Aug 18, 2026

Copy link
Copy Markdown

roborev: Combined Review (6d3a5c1)

Code needs a medium-severity fix to avoid silently omitting attachments from some conversation types.

Medium

  • internal/query/files.go:417 — Roster-only participants are matched only for direct_chat, group_chat, and channel. Previously, person scope matched conversation_participant_ids across all conversation types, so default-direction requests now omit meeting, calendar, and legacy-chat attachments. Preserve broad roster membership for the default request, or map every supported conversation type to an explicit provenance direction and add a roster-only regression test.

Reviewers: 2 done | Synthesis: codex, 9s | Total: 6m39s

@roborev-ci

roborev-ci Bot commented Aug 18, 2026

Copy link
Copy Markdown

roborev: Combined Review (3c31c78)

The PR needs one medium-severity fix before merging.

Medium

  • web/src/lib/components/files/FilesWorkspace.svelte:675 — Grid viewport measurement and its ResizeObserver are installed only during onMount, but media mode removes the grid. If media mode is restored initially or toggled off later, the newly created grid remains unmeasured, causing incorrect virtualization and blank regions. Reactively measure and observe each newly bound grid/headerElement, with cleanup when it unmounts.

Reviewers: 2 done | Synthesis: codex, 7s | Total: 5m18s

Person pages need a direct way to answer which attachments a contact sent,
received, or shared in a conversation without filtering the archive-wide
Files workspace by hand.

- Add bounded person-scoped attachment search with exact identity, role,
  conversation, source, and deletion provenance.
- Expose the search through the API and generated Go and web clients, with
  compatible defaults for clients that omit direction filters.
- Add separate Media and Files presentations with safe bounded previews and
  links back to each attachment's owning message.
- Page scalar entry data before participant enrichment and restrict person
  role aggregation to attachment-bearing messages.
- Preserve direct-chat and broad conversation-roster relationships across
  legacy conversation types.
- Publish schema version 1.45.0 and accept valid empty filename and MIME
  metadata in generated clients.
- Rebind virtual-grid measurement and resize observation whenever conditional
  Media and Files views replace their DOM elements.

Attachments remain message-owned, so identity linking and splitting changes
gallery membership without moving or duplicating blobs.

Refs kenn-io#612

Generated with Codex
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
Co-authored-by: Wes McKinney <wesmckinn+git@gmail.com>
Co-authored-by: Codex <noreply@openai.com>

# Conflicts:
#	internal/api/openapi.go
#	internal/api/openapi_test.go

# Please enter the commit message for your changes. Lines starting
# with '#' will be ignored, and an empty message aborts the commit.
#
# Author:    salmonumbrella <182032677+salmonumbrella@users.noreply.github.com>
#
# interactive rebase in progress; onto f07c23a
# Last command done (1 command done):
#    pick 207ef003 # feat(web): add person attachment galleries
# No commands remaining.
# You are currently rebasing branch 'feat/person-attachment-gallery' on 'f07c23a8'.
#
# Changes to be committed:
#	modified:   api/openapi.yaml
#	modified:   internal/api/explore_e2e_test.go
#	modified:   internal/api/files.go
#	modified:   internal/api/files_test.go
#	modified:   internal/api/openapi.go
#	modified:   internal/api/openapi_test.go
#	modified:   internal/codegenfix/fix.go
#	modified:   internal/codegenfix/fix_test.go
#	modified:   internal/query/explore_fastpath_test.go
#	modified:   internal/query/files.go
#	modified:   internal/query/files_test.go
#	modified:   pkg/client/client_test.go
#	modified:   pkg/client/generated/enums.go
#	modified:   pkg/client/generated/payloads.go
#	modified:   pkg/client/generated/responses.go
#	modified:   pkg/client/generated/types.go
#	modified:   pkg/client/openapi.yaml
#	modified:   web/src/lib/api/generated/schema.d.ts
#	modified:   web/src/lib/components/files/FilesWorkspace.svelte
#	modified:   web/src/lib/components/files/FilesWorkspace.test.ts
#	new file:   web/src/lib/components/files/MediaThumbnail.svelte
#	new file:   web/src/lib/components/files/MediaThumbnail.test.ts
#	new file:   web/src/lib/components/files/PersonMediaGallery.svelte
#	new file:   web/src/lib/components/files/PersonMediaGallery.test.ts
#	modified:   web/src/lib/components/files/preview-bytes.test.ts
#	modified:   web/src/lib/components/files/preview-bytes.ts
#	new file:   web/src/lib/components/files/thumbnail-queue.ts
#	modified:   web/src/lib/components/relationships/RelationshipsWorkspace.svelte
#	modified:   web/src/lib/components/saved-views/SavedViewsWorkspace.test.ts
#	modified:   web/src/lib/components/shell/AppShell.svelte
#	modified:   web/src/lib/explore/models.ts
#	modified:   web/src/lib/explore/state.svelte.ts
#	modified:   web/src/lib/explore/state.test.ts
#	modified:   web/tests/e2e/accessibility.spec.ts
#	modified:   web/tests/relationships.spec.ts
#
@wesm
wesm force-pushed the feat/person-attachment-gallery branch from 3c31c78 to 2538152 Compare August 19, 2026 01:35
@roborev-ci

roborev-ci Bot commented Aug 19, 2026

Copy link
Copy Markdown

roborev: Combined Review (2538152)

One medium-severity correctness issue found; no security vulnerabilities identified.

Medium

  • internal/query/files.go:435 — Direct-chat messages with a null sender_id are always inferred as to, even when a message_recipients from row identifies the person as the sender. Linked sender aliases can also be misclassified, causing incoming attachments to appear under “To them.”
    • Fix: Resolve the effective sender from sender_id, falling back to the from recipient role, then compare it against the entire person cluster before inferring to. Add coverage for null senders and linked aliases.

Reviewers: 2 done | Synthesis: codex, 5s | Total: 4m35s

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Development

Successfully merging this pull request may close these issues.

2 participants