Skip to content

feat(web): bring people and domains into one list and page - #1610

Closed
rodboev wants to merge 26 commits into
pr/redesign-5-filesfrom
pr/redesign-6-people
Closed

rodboev wants to merge 26 commits into
pr/redesign-5-filesfrom
pr/redesign-6-people

Conversation

@rodboev

@rodboev rodboev commented Oct 31, 2026 •

Copy link
Copy Markdown
Contributor

People now lists people and domains, each with one page. Before, a person's activity lived in Relationships and their profile in a separate Directory view.

Each person or domain with archived activity keeps the messages, files, and calendar Relationships showed. Full profiles still open in Directory until a later slice moves them into Details.

  • Messages uses the same table and reader as the Messages workspace.
  • Files shares the Messages filters.
  • Details holds Open in Directory and Same person… from the old header.
  • A calendar day narrows Messages and stays selected in Details.
  • Back restores the conversation that was open for that day.
  • Saved lists curated profiles, including people with no messages.
  • Domains gain a daily calendar of sent messages in your time zone.
  • The API adds a day grouping with timezone and sent_only fields to /api/v1/explore/groups.
  • One person's 25-row message list query returned in 1,038 ms in a single run on a synthetic archive of 2,000,000 emails and 2,000 chat messages.
people-navigation.webm
Before After
List
Messages
Messages, 1280x720
Files
Details
Dark
Domains list
Domain calendar
Selected day
Conversation restored by Back
Saved profile without messages

Refs #1552, slice 6 (People list and page)
Stacks on #1588

People need a single place to move between a contact's messages, files,
and saved profile. Keep the existing archive scopes and identity actions
while giving people and domains the same browsing flow.

Daily domain activity must count each sent message before chats collapse
into conversations. Otherwise a conversation spanning several days hides
activity from the calendar and disagrees with the messages for that day.
Searching for a person's name must leave their files and messages intact.
The identity endpoint already includes linked aliases; an extra filter on
one participant hid activity that the calendar still counted.

Message filters belong to the shared browsing history so Files, Back,
and reload keep the same context. A person page also needs to remain
usable when the old, undisplayed timeline request fails.

Saved-profile reads can finish after the user chooses another row.
That old result must leave the newer choice and its selection intact.
A selected day must survive a visit to Details without changing the date
filters shared with Files. Conversation readers need the same day bounds
so a chat opens on the activity the user selected.

Filtered activity dates can hide earlier calendar years. Keep lifetime
contact dates separate from contextual metrics so the calendar still
shows the full relationship.

An expired Saved People cursor leaves useful rows on screen. Offer Reload
there so the user can recover without leaving the workspace. Daily group
requests remain available without making day a saved grouping that the
browser cannot restore.
Meeting follow-ups must use the same person, domain, and selected day as
the messages beside them. The shared summary needs the existing direct
relationship scope because a detached table predicate has no identity.

Changing message filters and visiting Details should preserve the chosen
calendar year. Keep the open identity in place while its metrics refresh,
and give the domain year the same page lifetime as its selected day.

People name searches belong to the People workspace. Keeping that query
separate prevents a later Messages visit from searching message text for
the person's name.

Domain days now use the existing validated timezone conversion so evening
messages appear on the same date in the calendar and the message table.
An open person page must reflect the addresses it represents after a link
or profile merge. Keeping the page mounted left its message list on the
previous identity scope. A second bulk link could also start before the
first profile merge finished reconciling.

The shared message table must preserve the relationship page's attendance
rule, so opening a person does not add events the owner never attended.
Keep Tasks setup reachable from its reader and calendar shades consistent
with the shared activity scale.
A saved contact can have only drafts in the archive. Its profile must stay
reachable when there is no sent activity to summarize.

Choosing another calendar period ends the previous reader selection.
Initial history restoration still needs that selection, and calendar
revision changes must stop retrying when the archive keeps moving.

Filters shown on Domains must describe the domain request they affect.
Following another person and returning should reopen the same message,
including its original calendar-day window. A message opened beyond the
first result page needs the existing restoration walk to find it again.

A bound domain year can settle during initial rendering. Keep that setup
from fetching the same calendar twice.
Opening a conversation after choosing a calendar day must retain the
reader selection. Reading that selection while notifying a predicate
change subscribed the loader to it and triggered another load.

Keep regression checks at their owning components so browser journeys
cover navigation without repeating request-level assertions.
A compact window could shrink the Messages wrapper below its table while
the person page reported no scroll range. Mouse users could see the
calendar but could not reach the rows below it.

Let the embedded workspace keep its content height so the existing person
page scrollbar can reach it.
The replaced Relationships workspace can still pass its old tests while
the People page breaks. Remove those unreachable checks and keep reader
restoration, calendar requests, and identity refresh at their live owners.

Use valid calendar responses so refresh coverage follows the same revision
checks as the application.
@roborev-ci

roborev-ci Bot commented Oct 31, 2026

Copy link
Copy Markdown

roborev: Combined Review (daa81b4)

Verdict: Changes require fixes for 4 findings.

Medium

  • web/src/lib/components/people/PersonMessages.svelte:59: Restoring a day-bounded chat can use the conversation’s latest anchor outside the saved day because only explicitly changed anchors are persisted, causing conversation_anchor_outside_range.

    Fix: Persist and restore the effective anchor: the explicit anchor or the selected row’s anchor_message_id.

  • web/src/lib/components/people/PersonMessages.svelte:23: Back/Forward between history entries for the same person leaves the current message open because the reader snapshot is read only when PersonMessages mounts.

    Fix: React to history restoration and restore the matching selection, anchor, and bounds before updating the history snapshot.

  • web/src/lib/components/people/PeopleWorkspace.svelte:80; web/src/lib/components/people/PeopleWorkspace.svelte:94: People reuses Directory’s selection without synchronizing relationshipTarget. Returning from Directory can highlight Bob while showing Alice’s content, or show only a profile name and Open in Directory button despite archived messages because participant_ids are ignored.

    Fix: Separate Directory and People selections, or synchronize the selected profile and relationshipTarget using the participant-to-target resolution used by open().

  • web/src/lib/components/people/DomainCalendar.svelte:17: A domain with a next-year calendar event initially opens on a blank future year. Its last_at includes future event starts, but calendar days are generated only through Date.now(), hiding historical activity.

    Fix: Clamp the initial domain year to the current year, matching the person calendar.


Reviewers: 2x codex, codex (security), codex (test-value) | Synthesis: codex, 19s | Total: 13m57s

Back and Forward should reopen the message saved in that history entry,
including a conversation's original day and anchor. Returning from
Directory should show the selected person's archived messages.

Future events must leave historical domain activity visible on first open.
Embedded Messages also needs to preserve one main landmark while keeping
the reader available to assistive technology.
Escape should dismiss an open menu before closing the message or person
beneath it.

Earlier links remain committed when a later pair fails or a profile merge
is cancelled. Keep their count visible so the user can see what completed.
A later successful refresh also replaces the earlier stale-cache warning.
Returning to Messages after Files shows the full message table. Its chat
reader must use that same range, so messages from other days remain
available. Keep the earlier history entry's day window for Back.
A searched People list must follow the selected Name or Most recent order
before pagination. Search without an explicit sort keeps relevance order.

Typing a profile filter should keep Back on complete navigation steps.
Clearing filters is one such step, so Back restores the complete prior set.

Remove checks for the inactive list and timeline while their production
retirement remains part of the later workspace change.
Shared setup keeps the existing calendar and linking cases together.
Task setup forwarding belongs beside the person reader so its check can
identify a broken callback without navigating through the shell.

Use complete message rows and sent/unsent activity so request mistakes
change the result. Exact timeline keys distinguish linked alias messages
from an owner-absent event.
@roborev-ci

roborev-ci Bot commented Oct 31, 2026

Copy link
Copy Markdown

roborev: Combined Review (a0664e5)

Verdict: Changes require fixes for 1 finding.

Medium

  • internal/query/identity_activity.go:404: For /participants/search requests with identity_query and no explicit sort, prepareIdentitySearch sets Sort.Field to activity_count, preventing the new empty-field guard from enabling default relevance. High-volume substring matches can displace exact matches from the first page. The engine-only test bypasses API preparation.

    Fix: Preserve whether the caller omitted sorting through API preparation, use relevance for omitted sorts, and honor explicit sorts. Add coverage through the HTTP endpoint.


Reviewers: 2x codex, codex (security), codex (test-value) | Synthesis: codex, 9s | Total: 16m56s

Closest sends an explicit activity sort, and HTTP preparation supplies it
when callers omit sorting. Both forms must keep text relevance so a busy
partial match cannot displace a better match from the first page.

Name and Most recent continue to use their requested order.
Saved People uses Directory's list but displays its own selected profile.
Passing that selection into Directory starts a second profile read and
requests for sections the People page never displays.

Keep those detail requests with an actual Directory visit.
Opening an observed person clears the saved profile selection. The shell
check must use that same navigation contract so it can exercise promotion
conflicts without reopening the prior profile.

Keep the saved-view rejection check on request-only day grouping, and make
the documented promotion path follow the visible People controls.
An empty domain list should identify the selected facet so users can tell
which archive results are missing. Keep the shared list layout and search
empty state.

The usage guides also need the live People meeting flow and the day-grouping
request contract so readers can follow the controls and interpret counts.
People and domain calendars need one shading rule. Assign day levels with
the existing server calendar policy so clients render the supplied levels
and cannot drift from that policy.

Levels rank the returned day rows, which cover the domain calendar's year.
Other groupings omit them. Qualify the new client enum names to preserve
the existing calendar exports.
Default People search must distinguish a strong relationship from a busy
contact. Separate those rankings in the fixture before removing the
repeated HTTP check.

Keep person and domain meeting scopes beside the person message view.
Use calendar responses the server can produce, wait for loading to settle,
and leave quartile calculation assertions with their existing owner.
@roborev-ci

roborev-ci Bot commented Oct 31, 2026

Copy link
Copy Markdown

roborev: Combined Review (2d5af80)

Verdict: Changes require fixes for 4 findings.

Medium

  • web/src/lib/explore/api.ts:155: Person activity now uses participant-filtered Explore, which disables the narrow listing path and rebuilds archive-wide participant lists before pagination on each person or day selection, degrading large-archive browsing.

    Fix: Preserve indexed relationship filtering and page-only enrichment while returning the shared Messages table's EntryRow shape and logical-row behavior.

  • web/src/lib/components/people/PersonPage.svelte:65: When the base detail request succeeds but the filtered summary fails, the page hides timelineError because detail exists and presents unfiltered counts alongside filtered activity without warning or retry.

    Fix: Display partial detail failures even when detail exists, provide Retry, and avoid presenting fallback counts as successfully filtered metrics.

  • web/src/lib/components/people/PersonPage.svelte:87: After an identity link or unlink returns cache_state='stale', switching from Details to Messages and back destroys RelationshipHeader, losing its stale warning and saved retry mutation while the cache can remain outdated.

    Fix: Move mutation recovery state to an owner that survives tab changes, or retain the header instance while hidden.

  • web/src/lib/components/people/PeopleList.svelte:260: Clicking another person's checkbox changes selection without updating focusKey, so subsequent Enter, arrow, or Space actions operate on the previously focused person.

    Fix: Set focusKey to row.key before toggling checkbox selection, and cover clicking a nonfocused row's checkbox followed by keyboard navigation.


Reviewers: 2x codex, codex (security), codex (test-value) | Synthesis: codex, 10s | Total: 15m34s

@rodboev rodboev closed this Oct 31, 2026
@rodboev rodboev reopened this Oct 31, 2026
Opening a person should not rebuild every archived message's participant
list. Reuse indexed membership and enrich the selected page so identity
clusters and chat membership keep their existing meaning at archive scale.

A filtered summary failure needs a visible recovery action even when the
person is loaded. Identity cache recovery must also survive switching tabs.
Keyboard actions should follow the person whose checkbox was clicked.
A saved profile can retain addresses from disconnected identity clusters.
Its Files tab must include those bindings, as Directory already does;
using the first observed cluster silently drops the other addresses' files.

Keep this file scope tied to the selected saved profile. Activity keeps the
observed cluster chosen by the existing Directory relationship entry.
A saved profile can span disconnected identity clusters. Readers need to
know why Files includes every binding while activity follows the existing
Directory entry into one observed cluster.
Returning to Saved after a merge could leave the absorbed profile available
to open. People shares Directory rows across visits, so a completed merge
must refresh that list along with the open relationship.
API checks need to distinguish forwarded options and linked identities,
while query tests own day counts and participant membership. Keep the
workflow recovery checks without repeating their lower-layer details.
@roborev-ci

roborev-ci Bot commented Oct 31, 2026

Copy link
Copy Markdown

roborev: Combined Review (422eb68)

Verdict: Changes require fixes for 1 finding.

Medium

  • web/src/lib/components/people/PeopleWorkspace.svelte:218: After unlinking, a saved profile can span separate identity clusters. If its first bound participant belongs to a draft-only cluster, opening the profile receives a detail 404 and removes all tabs, hiding Files even when another bound cluster has archived files.

    Fix: Keep the saved profile’s Files tab available independently of the first cluster’s activity, or resolve a bound cluster with sent activity before falling back. Add coverage for a draft-only first binding and a later binding with files.


Reviewers: 2x codex, codex (security), codex (test-value) | Synthesis: codex, 10s | Total: 16m58s

@rodboev rodboev closed this Oct 31, 2026
@rodboev

rodboev commented Oct 31, 2026

Copy link
Copy Markdown
Contributor Author

Replaced by #1611.

@rodboev
rodboev removed this pull request from stack #1561 October 31, 2026 16:18
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.

1 participant