Skip to content

feat(tui): add People browser - #669

Merged
wesm merged 1 commit into
kenn-io:mainfrom
salmonumbrella:feat/tui-people-browser
Aug 24, 2026
Merged

feat(tui): add People browser#669
wesm merged 1 commit into
kenn-io:mainfrom
salmonumbrella:feat/tui-people-browser

Conversation

@salmonumbrella

@salmonumbrella salmonumbrella commented Aug 22, 2026

Copy link
Copy Markdown
Contributor

Adds People as a fourth, keyboard-first TUI mode. An observed contact is now the entry point for finding a person, understanding the relationship over time, adding profile context, and opening the original messages, meetings, and received files without leaving the terminal.

People workflow

  • Search names and typed contact facts, then open a dossier with Overview, Attributes, Inboxes, Meetings, Files, and Activity tabs.
  • View a graph-relative relationship calendar in the TUI and web app, with four activity levels, year navigation, and current and peak temperatures.
  • Promote observed contacts into profiles, create typed custom fields, edit historized attributes, and manage sensitive Notes through the TUI, CLI, and trusted MCP transport.

Boundaries

  • Local and remote TUI sessions share the same People facades, HTTP contracts, generated clients, cache revisions, and stale-response guards.
  • Notes remain sensitive and non-searchable. MCP profile mutations are disabled unless --allow-profile-writes is set; HTTP also requires --http-allow-writes. MCP Notes writes use enrichment provenance, which the read tool returns.
  • Switching TUI modes preserves parked conversation timelines, global-search results, and incomplete message details.
  • Text pagination returns each page and its revision from one consistent read, avoiding repeated full-result scans while preserving stale-page detection.

@salmonumbrella
salmonumbrella force-pushed the feat/tui-people-browser branch from 5775d13 to e654af1 Compare August 22, 2026 10:12
@salmonumbrella

salmonumbrella commented Aug 22, 2026

Copy link
Copy Markdown
Contributor Author

@wesm people tui just for you 🤣 can you please take it from here for testing?

@salmonumbrella salmonumbrella changed the title feat(tui): add the People browser feat(tui): add People browser Aug 22, 2026
@roborev-ci

roborev-ci Bot commented Aug 22, 2026

Copy link
Copy Markdown

roborev: Combined Review (e654af1)

Changes need revision: three medium-severity issues affect operation gating, Notes attribute resolution, and conflict retry guidance.

Medium

  • internal/api/operation_gate.go:386 — The read-only POST /api/v1/participants/completions route is missing from readOnlyPostRoutePatterns, causing autocomplete requests to be blocked or rejected during long archive mutations. Add the route to the read-only POST patterns and cover its classification with a behavioral test.

  • internal/store/person_notes.go:41 — Notes are resolved using the preferred notes slug, but seed reconciliation may assign a fallback slug when that slug is already owned by a custom attribute. Notes operations can therefore modify an unrelated attribute or miss the seeded Notes definition. Resolve Notes using AttributeUniversalIDNotes, use its archive-local slug for writes, and consistently identify it by universal ID across TUI, CLI, and MCP paths.

  • internal/daemonclient/people_browser.go:267 — The conflict decoder expects current_value_id or current_value, but the API’s attribute_value_conflict response provides neither. This produces stale-value retry guidance with invalid ID 0. Include current-value metadata in the API response or reload the value before returning retry instructions.


Reviewers: 2 done | Synthesis: codex, 12s | Total: 15m53s

@wesm

wesm commented Aug 22, 2026

Copy link
Copy Markdown
Member

On my radar

@salmonumbrella
salmonumbrella force-pushed the feat/tui-people-browser branch 2 times, most recently from 190f219 to 2cb6786 Compare August 22, 2026 19:44
@roborev-ci

roborev-ci Bot commented Aug 22, 2026

Copy link
Copy Markdown

roborev: Combined Review (2cb6786)

Review verdict: Three medium-severity issues require attention; no high or critical findings were reported.

Medium

  • Inconsistent normalization of @-prefixed usernamesinternal/query/people_completion.go:92, internal/query/people_completion.go:202
    Username completion does not strip a leading @, though the profile-store query does. For example, @alice misses analytical username rows, while matching curated rows are later discarded because @alice does not match alice. Normalize username queries consistently across analytical lookup, merge filtering, and result ranking.

  • Relationship requests can exceed the cache’s effective yearinternal/tui/people_relationship.go:38, web/src/lib/relationships/controller.svelte.ts:124, internal/mcp/people_relationship.go:73
    These clients request the current calendar year, but the query layer rejects years after the cache’s score-effective year. If the cache is valid but effective in the previous year, relationship views and MCP requests fail until refresh. Initialize or clamp requests to the score-effective year; MCP calls without an explicit year should fetch the summary without forcing a calendar year.

  • Returning to Texts can leave message detail permanently emptyinternal/tui/keys.go:162, internal/tui/text_commands.go:195
    Switching modes while message detail is loading invalidates and discards the response. Returning to Texts requests the conversation list while navigation remains at textLevelDetail, causing “Message not found” until the user backs out. Reload the selected message when re-entering detail without cached data, or retain the off-screen response in the parked text state.


Reviewers: 2 done | Synthesis: codex, 11s | Total: 16m7s

@salmonumbrella
salmonumbrella force-pushed the feat/tui-people-browser branch from 2cb6786 to 66e711d Compare August 22, 2026 20:06
@roborev-ci

roborev-ci Bot commented Aug 22, 2026

Copy link
Copy Markdown

roborev: Combined Review (66e711d)

The change has one medium-severity calendar rollover issue; no critical or high-severity findings were identified.

Medium

  • internal/query/relationship_calendar.go:103 — Calendar years are capped by the cache’s effective year, while new clients initially request the current local year. An inactive archive may retain a valid prior-year cache indefinitely, causing the relationship view and MCP summary to fail with invalid_year after New Year. The earlier UTC-year check can also temporarily reject the new local year in UTC-positive time zones.
    • Fix: Permit the current year in the requested time zone and return data only through the cache watermark, or have clients select the cache-effective year before loading the calendar. Add coverage for stale caches and UTC-positive year boundaries.

Reviewers: 2 done | Synthesis: codex, 7s | Total: 12m56s

@salmonumbrella
salmonumbrella force-pushed the feat/tui-people-browser branch 2 times, most recently from ca599e4 to c9b5f2c Compare August 22, 2026 21:15
@roborev-ci

roborev-ci Bot commented Aug 22, 2026

Copy link
Copy Markdown

roborev: Combined Review (c9b5f2c)

Changes requested: two medium-severity issues could break inactive heatmaps and leave the People Overview stuck loading.

Medium

  • internal/identityindex/temperature.go:150AssignRelationshipHeatLevels returns empty strings when every day has no activity, violating the expected NONE heat-level contract and preventing inactive-day styling for all-zero years. Initialize every result to HeatNone before the early return and add a behavioral test for an all-zero year.

  • internal/tui/people_relationship.go:201 — Changing the relationship year increments the shared People request ID and can invalidate an in-flight attributes request. Its ignored response never clears attributesLoading, leaving the Overview panel stuck loading. Use independent request generations for attributes and calendar loads, or avoid invalidating attributes on year changes; test a year change before the attributes response arrives.


Reviewers: 2 done | Synthesis: codex, 8s | Total: 11m47s

@salmonumbrella
salmonumbrella force-pushed the feat/tui-people-browser branch 2 times, most recently from 34178e8 to 82d68c2 Compare August 22, 2026 21:58
@roborev-ci

roborev-ci Bot commented Aug 22, 2026

Copy link
Copy Markdown

roborev: Combined Review (82d68c2)

The PR has three medium-severity issues involving future-dated notes, alias-based relationship lookup, and filtered calendar navigation.

Medium

  • internal/store/person_notes.go:93 — Appending notes closes the current note at time.Now(). If that note has a future active_from, the resulting active_until precedes it, violating the database constraint and causing a storage error. Validate that the append timestamp is not earlier than active_from, or schedule the appended value no earlier than that timestamp.

  • internal/query/people.go:422 — Participant lookup using a noncanonical alias returns zero relationship temperatures. The legacy aggregation keeps the requested alias as person_id, while relationship subqueries match only relationship_people.canonical_id. Use the resolved cluster canonical ID for relationship lookups while retaining the requested ID where the response contract requires it.

  • web/src/lib/relationships/controller.svelte.ts:310 — Opening a filtered cluster overlays the filtered summary onto unfiltered participant details and then uses filtered first_at to determine the earliest relationship-calendar year. Since the calendar is unfiltered, this can prevent navigation to older years with valid activity. Preserve the unfiltered participant detail’s earliest year separately for calendar navigation.


Reviewers: 2 done | Synthesis: codex, 9s | Total: 17m8s

@salmonumbrella
salmonumbrella force-pushed the feat/tui-people-browser branch from 82d68c2 to 31b1d84 Compare August 22, 2026 22:24
@roborev-ci

roborev-ci Bot commented Aug 22, 2026

Copy link
Copy Markdown

roborev: Combined Review (31b1d84)

Two medium-severity issues should be addressed; no critical or high-severity findings were reported.

Medium

  • Inconsistent contact view after revision driftweb/src/lib/relationships/controller.svelte.ts:473
    When the calendar reports a newer cache or identity revision, the controller reloads only the calendar. The person header and timeline remain from the older snapshot, producing an internally inconsistent view after a sync or identity change.
    Fix: Restart the entire target load once on revision drift while preserving the selected year.

  • People mode exposed for incompatible daemon schemascmd/msgvault/cmd/tui.go:85
    PeopleBackend is enabled unconditionally, but same-major API compatibility allows connections to daemons older than schema 2.8.0 that lack required People endpoints. The TUI consequently exposes a mode that fails with endpoint errors.
    Fix: Enable PeopleBackend only when SupportsAPISchemaVersion confirms the required schema; otherwise leave it nil so the mode is omitted.


Reviewers: 2 done | Synthesis: codex, 9s | Total: 20m25s

@roborev-ci

roborev-ci Bot commented Aug 23, 2026

Copy link
Copy Markdown

roborev: Combined Review (bf7ff73)

Changes need fixes for four medium-severity issues involving username completion, relationship metadata, revision handling, and API compatibility.

Medium

  • internal/query/people_completion.go:202@-prefixed username queries never reach the API response. The store strips @, but PeopleCompletionMatchRank compares @alice with alice, causing valid results to be discarded. Normalize username queries consistently before matching/ranking, including the DuckDB candidate predicate, and add an API completion test for @-prefixed queries.

  • internal/api/people_completion.go:199 — Replacing an observed completion with its curated duplicate discards CurrentRelationshipTemperature and LastAt, causing promoted/profile-backed contacts to sort as cold and stale. Merge curated display/source fields into the observed candidate while preserving relationship temperature and recency metadata.

  • web/src/lib/relationships/controller.svelte.ts:482 — When the timeline request fails, identityRevision remains null, so a successful calendar response is incorrectly treated as revision drift. This can trigger repeated reopening and prevent a valid calendar from loading. Compare revisions only when a baseline exists, or initialize the baseline from the first successful calendar response.

  • cmd/msgvault/cmd/mcp.go:85 — Daemon-backed MCP advertises People tools when connected to older supported API versions that lack the required endpoints, so failures occur only at invocation time. Gate PeopleBackend with SupportsAPISchemaVersion(ctx, "2.8.0"), consistent with the TUI.


Reviewers: 2 done | Synthesis: codex, 11s | Total: 15m27s

@roborev-ci

roborev-ci Bot commented Aug 23, 2026

Copy link
Copy Markdown

roborev: Combined Review (de9d40c)

Medium-severity issue found: curated profiles are not discoverable through MCP people search.

Medium

  • internal/mcp/people_notes.go:116search_people claims to search durable profiles but delegates exclusively to the observed /participants/search backend. Curated display names are therefore not searchable, and results omit the profile IDs that read-only MCP clients need to call get_person_notes.

    Suggested fix: Merge curated profiles into search results and expose their profile linkage/ID. Add a behavioral test covering a curated-only display name.


Reviewers: 2 done | Synthesis: codex, 8s | Total: 14m48s

@roborev-ci

roborev-ci Bot commented Aug 23, 2026

Copy link
Copy Markdown

roborev: Combined Review (f34335f)

Code review found one medium-severity pagination and result-omission issue; no security vulnerabilities were identified.

Medium

  • internal/mcp/people_notes.go:258 — Curated-result deduplication tokenizes across concatenated fields, but the backend matches the complete query within a single search value. This can wrongly add IDs absent from observed results to excludedIDs, undercount total_count, suppress the observed-phase cursor, and omit valid contacts.
    • Fix: Use the same full-query, per-field matching semantics as SearchPeople, or derive exclusions from actual observed results before adjusting totals and pagination.

Reviewers: 2 done | Synthesis: codex, 8s | Total: 16m33s

@roborev-ci

roborev-ci Bot commented Aug 23, 2026

Copy link
Copy Markdown

roborev: Combined Review (bc5657c)

Changes need fixes for three medium-severity functional regressions.

Medium

  • Participant filtering can hide direct conversationsinternal/query/duckdb_text.go:70, internal/query/sqlite_text.go:229
    Filtering relies exclusively on conversation_participants. Direct chats without a roster may appear in the inbox rollup through message sender/recipient edges, but opening that inbox returns no conversations. Add a conversation-level sender/recipient fallback in both engines, preserving the entire conversation once any message establishes participation.

  • Timeline search misses body text beyond truncated snippetsinternal/tui/text_keys.go:223
    Timeline search filters locally through BodyText, but timeline rows now omit bodies. Searches therefore inspect only truncated snippets and silently miss terms outside the preview. Use conversation-scoped full-text search or another explicit body-search path, with coverage for matches beyond the snippet limit.

  • People overview may remain permanently unloaded after re-entryinternal/tui/keys.go:207
    Leaving People mode cancels an in-flight relationship-calendar load, but re-entering with an existing contact does not restart it. The displayed “Press r to retry” action also does nothing when no error is set. On re-entry to an overview without a calendar, call beginPeopleRelationshipLoad and restore the loading state.


Reviewers: 2 done | Synthesis: codex, 11s | Total: 22m16s

@roborev-ci

roborev-ci Bot commented Aug 23, 2026

Copy link
Copy Markdown

roborev: Combined Review (79ef39c)

Medium-severity issue found: a stale completion handler can corrupt restored People-mode state.

Medium

  • internal/tui/model.go:1170 — Text completion handlers mutate the active textState even when their presentation generation is stale. Because People mode uses its own text state, a delayed Text-mode request can overwrite conversations or messages in a restored People inbox.
    • Fix: Reject stale or mode-mismatched completions before mutation, or explicitly route them to the parked Text state.

Reviewers: 2 done | Synthesis: codex, 6s | Total: 19m18s

@roborev-ci

roborev-ci Bot commented Aug 23, 2026

Copy link
Copy Markdown

roborev: Combined Review (b7daeaa)

The change has two medium-severity state-management issues that can leave relationship calendars permanently unloaded; no security issues were identified.

Medium

  • internal/tui/people_keys.go:79, internal/tui/people_keys.go:324, internal/tui/people_attributes.go:265 — These paths increment the shared requestID while a relationship-calendar request may still be pending, causing its response to be rejected as stale while relationshipLoading remains true. This leaves the relationship panel and global spinner stuck. Give relationship requests an independent generation, or explicitly settle/restart them whenever the shared request ID changes; also reset relationship state when leaving a contact.

  • web/src/lib/relationships/controller.svelte.ts:321 — After revision drift, the refreshed first_at can move later than the preserved calendar year, but the selected year is not clamped before loadRelationshipYear. The loader silently rejects the year, and navigation cannot recover because rejected changes do not update relationshipCalendarYear. Future-only contacts have the same failure mode. Clamp the first year to the supported range, then clamp the selected year between it and the current year before loading.


Reviewers: 2 done | Synthesis: codex, 9s | Total: 25m37s

@roborev-ci

roborev-ci Bot commented Aug 23, 2026

Copy link
Copy Markdown

roborev: Combined Review (8629410)

Verdict: One medium-severity issue requires attention; no critical or high-severity findings.

Medium

  • Global search refinement uses the wrong search scopeinternal/tui/text_keys.go:223
    Global search results use textLevelTimeline, so refining them with / calls loadTextMessages with a zero or stale selectedConvID, potentially producing empty or unrelated results. Track whether the timeline is global or conversation-scoped, and call loadTextSearch when refining global results.

Reviewers: 2 done | Synthesis: codex, 8s | Total: 17m32s

@roborev-ci

roborev-ci Bot commented Aug 23, 2026

Copy link
Copy Markdown

roborev: Combined Review (35a16d4)

No Medium, High, or Critical findings were identified.


Reviewers: 2 done | Synthesis: codex, 5s | Total: 21m55s

@wesm wesm self-assigned this Aug 23, 2026
@wesm

wesm commented Aug 23, 2026

Copy link
Copy Markdown
Member

rebasing

@wesm
wesm force-pushed the feat/tui-people-browser branch from 35a16d4 to c5bcb4d Compare August 23, 2026 17:45
@roborev-ci

roborev-ci Bot commented Aug 23, 2026

Copy link
Copy Markdown

roborev: Combined Review (c5bcb4d)

One medium-severity performance issue should be addressed before merging.

Medium

  • internal/query/sqlite_text.go:26TextSnapshotRevision materializes and hashes the entire matching result, and stableTextRead calls it twice per page. Loading a 100-row inbox or conversation page therefore performs two full scans of every matching message, causing increasingly poor performance for large archives and long conversations.
    • Suggested fix: Use an inexpensive database/archive mutation revision, or fetch the page and revision within a single consistent read transaction.

Reviewers: 2 done | Synthesis: codex, 8s | Total: 14m37s

@wesm
wesm force-pushed the feat/tui-people-browser branch from c5bcb4d to bf493d5 Compare August 23, 2026 19:23
@roborev-ci

roborev-ci Bot commented Aug 23, 2026

Copy link
Copy Markdown

roborev: Combined Review (bf493d5)

Code review found two medium-severity issues affecting attribute history and restored Texts views.

Medium

  • internal/tui/people_attributes.go:337 — Adding a multi-valued attribute uses the first unused current ordinal, bypassing history-aware allocation. This can reuse an ordinal from a superseded value and merge unrelated values into the same supersession lineage. Set Ordinal only when editing an existing slot; omit it for new values so the store allocates across the full history.

  • internal/tui/keys.go:180 — Returning to Texts mode always reloads conversations, even if the preserved state is a timeline or detail view whose pending response was discarded in another mode. The timeline may remain empty or the detail view permanently unloaded until reopened. Reload data appropriate to the preserved Texts level, including the selected message for an incomplete detail view, or retain pending results in the parked state.


Reviewers: 2 done | Synthesis: codex, 8s | Total: 18m46s

@wesm
wesm force-pushed the feat/tui-people-browser branch from bf493d5 to b5f1660 Compare August 23, 2026 20:44
@roborev-ci

roborev-ci Bot commented Aug 23, 2026

Copy link
Copy Markdown

roborev: Combined Review (b5f1660)

No Medium, High, or Critical findings; the reviewers found no significant issues.


Reviewers: 2 done | Synthesis: codex, 5s | Total: 18m44s

@wesm
wesm force-pushed the feat/tui-people-browser branch from b5f1660 to 349b8c4 Compare August 24, 2026 08:48
@roborev-ci

roborev-ci Bot commented Aug 24, 2026

Copy link
Copy Markdown

roborev: Combined Review (349b8c4)

Medium-severity issues found in global-search state handling and MCP profile-write authorization.

Medium

  • internal/tui/keys.go:188 — Returning to Texts mode from global search calls loadTextData(), which reloads a stale or zero selectedConvID, replaces search results, and clears globalSearchTimeline. Preserve the existing results or persist and reload the query with loadTextSearch; add a mode-cycle test for global-search timelines.

  • internal/mcp/people_notes.go:518update_person_notes persists model-supplied content without enforceable user authorization and labels it with user provenance, allowing message-borne prompt injection to become trusted, persistent context. Require a trusted per-mutation confirmation token or disable profile writes by default, and record model-originated writes with non-user provenance.


Reviewers: 2 done | Synthesis: codex, 10s | Total: 20m51s

@wesm
wesm force-pushed the feat/tui-people-browser branch from 349b8c4 to e9817ec Compare August 24, 2026 11:53
@roborev-ci

roborev-ci Bot commented Aug 24, 2026

Copy link
Copy Markdown

roborev: Combined Review (e9817ec)

No Medium, High, or Critical findings were identified.


Reviewers: 2 done | Synthesis: codex, 6s | Total: 28m18s

Give users a focused terminal workflow for finding people, viewing relationship context, and managing person details without leaving the TUI.

- Add relationship heatmaps.
- Refresh revision drift safely.
- Close People browser edge cases.
- Include curated people in MCP search.
- Preserve People cursor compatibility.
- Wait for relationship calendar capture in documentation tests.
- Satisfy People search lint checks.
- Align People deduplication matching in MCP.
- Resume the People calendar on reentry.
- Fall back to message participant edges in queries.
- Search complete conversation bodies from the TUI.
- Refresh web API types.
- Use local testify helpers in CI.
- Reject stale text completions.
- Isolate relationship calendar requests.
- Preserve global search scope.
- Satisfy embedded field lint checks.
- Read text pages and revisions in one consistent scan.
- Preserve attribute histories and restore parked Texts views.
- Treat completion wildcards literally and report People capability probe failures.
- Keep parked global-search timelines and require explicit MCP profile-write authorization.

Generated with Codex

Co-authored-by: Wes McKinney <wesmckinn+git@gmail.com>
Co-authored-by: Codex <codex@users.noreply.github.com>
@wesm
wesm force-pushed the feat/tui-people-browser branch from e9817ec to 1052ac6 Compare August 24, 2026 12:35
@roborev-ci

roborev-ci Bot commented Aug 24, 2026

Copy link
Copy Markdown

roborev: Combined Review (1052ac6)

Code review verdict: No Medium, High, or Critical findings.


Reviewers: 2 done | Synthesis: codex, 5s | Total: 25m5s

@wesm
wesm merged commit aaff9f8 into kenn-io:main Aug 24, 2026
21 checks passed
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