You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
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)
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.
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.
Prevent claim cover images from rendering as circular avatars
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.
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.
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.
Claim activity feed eagerly materializes unbounded related records
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.
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.
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.
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.
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.
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.
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.
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.
Use a broader label for Related claims empty state
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.
Latest Copilot review — summary-only findings (no inline threads were created for these):
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.
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.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Implements GEO-2976.
Review hardening
Validation
Validated locally on current head
39605b15c:mise exec -- bun run test(565 files, 6,655 tests passed)mise exec -- bun x tsc --noEmit --incremental false(passed)git diff --check(passed)GitHub checks for this head are intentionally not reported here until they run.