Skip to content

feat: update claim entity custom UI (GEO-2976) - #2470

Merged
jwalkingjew merged 32 commits into
masterfrom
preston/geo-2976-update-claim-entity-custom-ui
Sep 22, 2026
Merged

jwalkingjew merged 32 commits into
masterfrom
preston/geo-2976-update-claim-entity-custom-ui

Conversation

@jwalkingjew

@jwalkingjew jwalkingjew commented Sep 19, 2026 •

Copy link
Copy Markdown
Collaborator

Summary

Implements GEO-2976.

  • show configured claim cover and avatar media in route and side-panel custom views
  • add Overview, Debates, Related claims, and Sources record tabs ahead of authored custom tabs, hiding each data tab when it has no content
  • place the aggregate response percentage above the viewer position control
  • place Topics below Activity on the Overview tab
  • include both topic-related candidate motions and every claim extracted from a direct debate on the current claim in Related claims
  • reuse the shared profile Activity section for claim/debate summaries and explore-card feeds
  • preserve provenance in the Sources tab and render linked sources as explore cards
  • keep the custom claim surface in edit mode: fixed product tabs remain read-only, authored tabs remain manageable/addable, the heading and description remain editable, and the raw properties editor stays at the bottom
  • keep authored tab editing behind the current-space member/editor access check, including side-panel tab switching
  • animate the active tab marker horizontally within the tab row so route scrolling cannot pull it through tab labels
  • add guarded server routes for the three claim record tabs

Review hardening

  • keep the current claim out of the Related claims count and feed
  • merge topic matches and debate-extracted claims by normalized entity ID before counting, scoring, or hydration
  • rank the complete deduplicated Related claims set through the shared Best scoring path
  • reserve authored-tab labels only for product tabs that are actually visible
  • isolate side-panel tab state from the route and query string behind the panel
  • share source extraction, explore-row hydration, active-tab matching, and tab types instead of maintaining duplicate implementations
  • follow the existing profile record-tab loading/error visibility behavior
  • isolate paginated retry checkpoints per execution, resume only automatic retries, and restart manual retries from page one
  • preserve the first visible Activity selection when an earlier activity kind finishes loading later
  • keep combined Related claims query data and refetch identities stable across equivalent renders

Validation

Validated locally on current head 39605b15c:

  • full web suite: mise exec -- bun run test (565 files, 6,655 tests passed)
  • focused regression suites: 5 files, 41 tests passed
  • changed-file ESLint (passed)
  • TypeScript: mise exec -- bun x tsc --noEmit --incremental false (passed)
  • git diff --check (passed)
  • mutation proof: the new regressions failed against the prior/disabled behavior, including stale manual retry cursors, shared checkpoint mutation, late Activity selection changes, and unstable combined query identities

GitHub checks for this head are intentionally not reported here until they run.

Render configured cover and avatar media on the custom claim surface so route and side-panel views preserve the entity's visual identity.
@vercel

vercel Bot commented Sep 19, 2026 •

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated
geogenesis Ready Ready Preview Sep 22, 2026 3:19am UTC

Request Review

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot review overview

🟡 Changes recommended

Record pagination, side-panel tab state, and claim route layout issues remain unresolved.

Get a fresh assessment by requesting another Copilot review.

Review effort: Balanced
Findings: 5 Medium severity

Open (5)
What changed in this PR

Implements GEO-2976 by expanding the custom claim UI across routes and side panels.

Changes:

  • Adds claim record tabs, feeds, sources, and related-claim ranking.
  • Preserves claim editing, media, and isolated side-panel navigation.
  • Adds guarded routes and regression tests.
File Description
apps/​web/​partials/​entity-page/​entity-tabs.tsx Supports configurable system tabs.
apps/​web/​partials/​entity-page/​entity-tabs.test.tsx Tests tab access and collisions.
apps/​web/​partials/​entity-page/​entity-page-body.tsx Keeps custom claim UI while editing.
apps/​web/​partials/​entity-page/​entity-page-body.side-panel.test.tsx Tests claim panel media and editing.
apps/​web/​partials/​entity-page/​editable-tab-group.tsx Shares active-tab navigation logic.
apps/​web/​partials/​entity-page/​custom-browse-view.test.ts Tests editing view selection.
apps/​web/​design-system/​tab-group.tsx Adds animated tab indicators.
apps/​web/​core/​utils/​entity-tab-navigation.ts Centralizes tab matching.
apps/​web/​core/​utils/​entity-tab-navigation.test.ts Tests route and panel matching.
apps/​web/​core/​state/​entity-side-panel-active-tab.tsx Tracks panel system tabs.
apps/​web/​core/​claims/​browse/​use-claim-record.ts Loads claim record data.
apps/​web/​core/​claims/​browse/​use-claim-record.test.ts Tests related-claim aggregation.
apps/​web/​core/​claims/​browse/​use-claim-explore-rows.ts Hydrates shared explore rows.
apps/​web/​core/​claims/​browse/​claim-sources.ts Extracts distinct live sources.
apps/​web/​core/​claims/​browse/​claim-sources.test.ts Tests source extraction.
apps/​web/​core/​claims/​browse/​claim-sources-tab.tsx Renders provenance and source cards.
apps/​web/​core/​claims/​browse/​claim-provenance.tsx Reuses shared source extraction.
apps/​web/​core/​claims/​browse/​claim-page-view.tsx Adds claim tabs and overview sections.
apps/​web/​core/​claims/​browse/​claim-page-view.test.tsx Tests claim UI and navigation.
apps/​web/​app/​space/​(entity)/​[id]/​[entityId]/​sources/​page.tsx Adds the Sources route.
apps/​web/​app/​space/​(entity)/​[id]/​[entityId]/​debates/​page.tsx Adds the Debates route.
apps/​web/​app/​space/​(entity)/​[id]/​[entityId]/​claims/​page.tsx Adds the Related claims route.
apps/​web/​app/​space/​(entity)/​[id]/​[entityId]/​claim-record-page.tsx Guards shared claim record routes.

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment thread apps/web/app/space/(entity)/[id]/[entityId]/claim-record-page.tsx
Comment thread apps/web/core/claims/browse/claim-page-view.tsx
Comment thread apps/web/core/claims/browse/use-claim-record.ts Outdated
Comment thread apps/web/core/utils/entity-tab-navigation.ts Outdated
Comment thread apps/web/partials/entity-page/entity-page-body.tsx Outdated

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot review overview

🟡 Changes recommended

Side-panel navigation, ranking failures, and cover-only media still produce incorrect claim behavior.

Get a fresh assessment by requesting another Copilot review.

Review effort: Balanced
Findings: 3 Medium severity

Open (3)
Resolved since last review (3)
Previously missed (2)

In code that hasn't changed since last review

Medium severity Surface score query failures instead of silently using stale order

apps/​web/​core/​claims/​browse/​use-claim-record.ts:172

Include score-query failures in the claim record state. useEntityScores documents that an error is otherwise indistinguishable from empty rankings, and sortRows(..., 'best', ...) then permanently preserves the UpdatedAt order. Both record tabs and the Activity summary therefore present a non-Best order after a ranking failure, contrary to this record’s fixed Best ranking. Surface an error when no usable rankings are available, or expose an explicit fallback order instead of silently treating the request as optional.

Medium severity Prevent claim cover images from rendering as circular avatars

apps/​web/​partials/​entity-page/​entity-page-body.tsx:280

A cover-only side-panel claim passes its cover back as avatarUrl: useEntityMediaUrl returns avatar ?? cover, and the preview fallback can likewise provide an image that is not a configured avatar. Because withAvatar is enabled, the cover/preview is then rendered as a circular avatar. Pass only the actual avatar fallback here; EntityPageCover already resolves the live Avatar relation itself, while the generic side-panel branch can keep its avatar-or-cover thumbnail fallback.

Comment thread apps/web/core/claims/browse/claim-page-view.tsx
@jwalkingjew

Copy link
Copy Markdown
Collaborator Author

Addressed both findings that Copilot surfaced only in the review summary in 4aae287ae:

  • Ranking failures: audited every useEntityScores call touched by this PR. Both claim-record score paths now treat a failed fixed-Best lookup as an error and withhold the misleading UpdatedAt-ordered rows, so the Overview and full tabs show their existing error state instead of silently presenting fallback order as Best. The shared helper has success and failure regression coverage. The existing person Debates tab already handles the same condition explicitly by switching its visible sort to New.
  • Cover-only media: removed generic avatar ?? cover and preview-image fallbacks from the claim's circular avatar input. I also enumerated the other fitted circular-avatar path in EntityPageBody and fixed the same issue for person panels. EntityPageCover continues to resolve the live Avatar relation itself; a rectangular cover remains only in the cover slot. Added cover-only and preview-only regressions for both entity types.

The inline Activity-navigation finding is fixed in the same commit and answered on its thread.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot review overview

🔵 Needs a closer look

Route edit initialization, error counts, and unbounded record rendering still need correction.

Review effort: Balanced
Findings: None

Resolved since last review (3)
Previously missed (3)

In code that hasn't changed since last review

Medium severity Record query errors incorrectly display zero activity counts

apps/​web/​core/​claims/​browse/​claim-page-view.tsx:348

Record-query failures are currently presented as successful zero counts in the Overview activity card. If either query chain fails before all IDs are collected, useClaimRecord sets debatesError or claimsError but leaves the corresponding total at zero; the kind remains visible for its error state, so the header shows 0 next to “Couldn’t load …”. Mark both counts unavailable on error so the shared activity section renders a dash rather than a false empty total.

Medium severity Claim activity feed eagerly materializes unbounded related records

apps/​web/​core/​claims/​browse/​use-claim-record.ts:94

Opening any claim now exhausts all related-claim and debate connections, then hydrates and scores every resulting ID. On a full record tab, PersonRecordFeed also maps the entire result into cards at once. For heavily connected claims, network, memory, and DOM work therefore grow without a bound even though Overview displays only six cards. Keep the complete ID/score pass needed for totals and Best ranking, but hydrate and render a bounded page (or virtualize the full feed) instead of materializing every card eagerly.

Medium severity Direct edit links fail to enable edit mode

apps/​web/​partials/​entity-page/​entity-page-body.tsx:305

Opening a claim route with ?edit=true no longer enables edit mode. AutomaticModeToggle is the component that consumes this query and calls setEditable(true), but the custom claim branch does not mount it until edit mode is already active. As a result, direct edit links leave the heading, description, authored tabs, and properties read-only. Mount the toggle for route claims independently of isEditing, while preserving the existing side-panel footer behavior.

@jwalkingjew

Copy link
Copy Markdown
Collaborator Author

Implemented and pushed the Related claims / Debates browse controls in 355eca6.

  • Both tabs default to Best and expose sorts in the requested order: Best, Top, New.
  • Related claims reuses the personal-space controls for Spaces and Topics.
  • Debates reuses the personal-space Spaces filter.
  • Filters and sorting execute in the bounded GraphQL queries, including extracted claims; deduplication remains before global ranking.
  • The route space is initially selected, while “Any space” expands to spaces carrying the current claim.

Proof:

  • Added focused component and query tests, including mutation checks showing the new tests fail when sort order/default or multi-topic semantics are broken.
  • Claim browse suite: 103 tests passed.
  • Full repository validation: production build plus 6,669 tests passed.
  • TypeScript and targeted ESLint passed.
  • Best, Top, and New query shapes were validated against testnet GraphQL.

@jwalkingjew

Copy link
Copy Markdown
Collaborator Author

Follow-up on the Related claims/Debates filters: the first implementation filtered the paginated row query correctly, but it did not use the repo’s independent facet-query architecture, so it could not provide complete corpus counts.

Commit 5a8b843 now mirrors the debate side panel/personal-space pattern:

  • row queries remain server-filtered, server-sorted, and paginated
  • separate grouped aggregate queries compute exact Spaces and Topics counts over the full matching population
  • the Related claims facet population is the union of topic-matched claims and claims extracted from direct debates
  • distinctCount(fromEntityId) prevents double-counting when a claim matches both paths
  • Spaces omit their own current selection when counting; Topics use the selected spaces/topics, preserving the existing OR-space / AND-topic behavior

Proof: new query/hook/component tests were added first and failed against the previous implementation. I also mutation-tested the contract by making the filter nullable, removing the extracted-claim branch, and grouping topics by the wrong key; the corresponding tests failed. Focused suite: 20/20. Full repo gate: 567 web test files / 6,657 web tests plus build/typecheck and auth tests passed. Live testnet aggregate queries returned exact counts for both sparse and topic-rich claims.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot review overview

🔵 Needs a closer look

Broad routing, query, pagination, and panel-state changes still have unresolved record correctness issues.

Review effort: Balanced
Findings: 1 High severity · 4 Medium severity

Open (5)

Comment thread apps/web/core/claims/browse/use-claim-record.ts Outdated
Comment thread apps/web/core/claims/browse/claim-record-query.ts Outdated
Comment thread apps/web/core/claims/browse/claim-record-tab.tsx
Comment thread apps/web/core/claims/browse/use-claim-record.ts
Comment thread apps/web/partials/profile/profile-activity-section.tsx
@jwalkingjew

Copy link
Copy Markdown
Collaborator Author

Copilot review round completed at 5e224bd.

I accepted all five new findings after tracing each through the current implementation:

  • preserved each entity’s actual matching space for both Related claims and Debates
  • restored Debates to direct debates on the viewed claim only, including counts and facets
  • reset sort, Spaces, and Topics synchronously when the claim/effective space changes
  • advanced every still-open ranked union branch before exposing the next visible slice
  • committed the visible Activity fallback when the prior selection becomes unavailable

I did not introduce Copilot’s suggested new server-side union: the existing independently sorted branches can prove the next union slice by advancing each open branch before reveal, which is smaller and stays within this PR. No additional suppressed or previously-missed finding appeared in the latest review, and no Copilot inline thread remains open.

Proof:

  • all new regressions failed against the pre-fix behavior
  • deliberate mutations restoring all five faults produced six focused failures
  • restored focused suite: 47/47
  • all PR-changed test files: 25 files / 159 tests
  • full gate: production build + TypeScript/static generation + 567 web files / 6,663 tests + 15 auth tests
  • live testnet accepted the matchingRelations query and returned correct per-entity spaces for extracted claims and direct debates

One more Copilot round is worthwhile because this pass found one high- and four medium-severity correctness issues in the newly introduced record-query path. If the next review is clean or only repeats resolved/non-actionable findings, I would stop there.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot review overview

🔵 Needs a closer look

Facet failures can hide active filter controls while their selections continue narrowing the record feed.

Review effort: Balanced
Findings: None

Resolved since last review (5)
Previously missed (1)

In code that hasn't changed since last review

Medium severity Keep filter controls available after terminal facet errors

apps/​web/​core/​claims/​browse/​claim-record-tab.tsx:179

A terminal facet error removes every filter control, but spaces.values and topics.values continue to filter useClaimRecord. After a background facet failure, the feed can therefore remain narrowed with no way to clear the active filter (and there is no facet retry UI). Keep the dimensions visible with their current/previous options; the counts can remain unavailable while the facet query is errored.

@jwalkingjew

Copy link
Copy Markdown
Collaborator Author

Copilot previously-missed finding: Keep filter controls available after terminal facet errors — valid and fixed in 1c93502.

I audited this failure pattern across the PR. There was one occurrence, in the shared ClaimRecordTab path used by both Related claims and Debates. The facet-error branch hid every dimension while the selected space/topic values continued to constrain the server query, leaving users unable to clear those filters.

The filter dimensions now remain rendered on a terminal facet error. Existing/cached options and counts remain available when React Query has them, and active selections remain visible and clearable even when the facet request itself fails. This changes only the terminal-error fallback; normal loading and successful states are unchanged.

Regression proof: I added a test that activates a topic filter, transitions the facet hook to a terminal error, and verifies that Spaces/Topics remain visible and the topic can be cleared. It failed against the old behavior (dimensions was []), passed with the fix, failed again when I deliberately restored the bug, and passed after restoring the fix. The full PR-focused suite passes: 25 files / 160 tests; targeted ESLint and TypeScript also pass.

Copilot reported this only in the review summary, so there was no inline thread to reply to or resolve; I am documenting the decision and fix here.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot review overview

🟡 Changes recommended

Multi-space Top ordering and pending access authorization can produce incorrect or unauthorized tab behavior.

Get a fresh assessment by requesting another Copilot review.

Review effort: Balanced
Findings: 1 High severity

Open (1)
Previously missed (2)

In code that hasn't changed since last review

Medium severity Preserve server-ranked ordering for multi-space Top results

apps/​web/​core/​claims/​browse/​claim-record-query.ts:527

The client-side top sort does not use the same ordering key as entitiesOrderedByPropertyConnection. score is decoded from scoreValues[0], although that list contains unordered values from every selected space, and ties are then broken by updatedAt even though the property connection does not request that tie-break. This can scramble multi-space Top results (and invalidate page-boundary ordering) after the server has ranked them. Return the connection’s actual scalar sort key and deterministic tie-break, or merge through a server-ranked union instead of reconstructing this order locally.

Low severity Use a broader label for Related claims empty state

apps/​web/​core/​claims/​browse/​claim-record-tab.tsx:194

This empty state is misleading because Related claims also includes topic matches, not only claims extracted from debates. Use the broader label so a claim with no matches does not imply that debate extraction is the only source.

Comment thread apps/web/partials/entity-page/entity-tabs.tsx Outdated
@jwalkingjew

Copy link
Copy Markdown
Collaborator Author

Latest Copilot review — summary-only findings (no inline threads were created for these):

  1. Preserve server-ranked ordering for multi-space Top results — valid problem; Copilot’s scalar-reconstruction suggestion was not the safe fix. scoreValues[0] is not the ordered connection’s authoritative sort key, and recreating its tie-break client-side would keep cursor boundaries vulnerable. I enumerated every PR-added Top path: topic-related claims, debate-extracted claims, and Debates. In e12726e64, Related claims now uses one server-ranked union connection across both discovery paths, and both record tabs preserve the connection’s node/cursor order. The redundant unordered scoreValues payload and client Top re-sort were removed everywhere. The no-topic union also omits the impossible empty topic branch. The exact multi-space and no-topic operations were accepted by Geo testnet.

  2. Use a broader Related claims empty state — valid and fixed in e12726e64. The unfiltered copy is now “No related claims yet.” because the union includes both topic matches and debate-extracted claims. Filtered empty copy remains unchanged.

Regression proof: the query contract asserts the single union connection and its combined filter; decoder coverage pins the union alias/cursor; ordering coverage pins preservation of server order; no-topic coverage pins the extraction-only union; and UI coverage pins the broader copy. Each failed against the old behavior, passed with the fix, failed again when its fix was deliberately broken, and passed after restoration.

Verification: exact live testnet GraphQL operations passed; targeted ESLint and TypeScript passed; 25 PR-focused files / 162 tests passed; production build/static generation passed; the full repository gate passed with 567 web files / 6,666 tests and 15 auth tests.

…date-claim-entity-custom-ui

# Conflicts:
#	apps/web/core/claims/browse/claim-page-view.test.tsx
#	apps/web/core/claims/browse/claim-page-view.tsx
#	apps/web/partials/entity-page/entity-page-body.side-panel.test.tsx
#	apps/web/partials/entity-page/entity-page-body.tsx
…date-claim-entity-custom-ui

# Conflicts:
#	apps/web/partials/profile/profile-activity-section.tsx
@jwalkingjew
jwalkingjew marked this pull request as ready for review September 22, 2026 03:16
@jwalkingjew
jwalkingjew merged commit 7c3b122 into master Sep 22, 2026
5 checks passed

This branch was successfully deployed

1 active deployment
Preview — ae623861 Deployed Sep 22, 2026 by vercel[bot]
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants