Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
af5e19d
feat: show claim media in custom view
jwalkingjew Sep 19, 2026
855f102
feat: add claim record tabs and activity
jwalkingjew Sep 19, 2026
b4fcc83
fix: refine claim record interactions
jwalkingjew Sep 19, 2026
b10316c
fix: rename related claims tab
jwalkingjew Sep 19, 2026
c93ba36
fix: address claim UI review findings
jwalkingjew Sep 19, 2026
1669072
fix: move claim topics below activity
jwalkingjew Sep 19, 2026
8710e7d
feat: include debate-extracted related claims
jwalkingjew Sep 19, 2026
ab65f1e
fix: address claim UI review findings
jwalkingjew Sep 19, 2026
4aae287
fix: address follow-up claim review findings
jwalkingjew Sep 20, 2026
e3d2519
fix: bound claim records and restore direct editing
jwalkingjew Sep 20, 2026
c4ee19b
fix: preserve related claims and tab indicators
jwalkingjew Sep 20, 2026
9cba406
fix: harden claim record queries and deduplication
jwalkingjew Sep 20, 2026
bf88cde
fix: isolate claim record loading
jwalkingjew Sep 20, 2026
e0a1f73
fix: make claim record failures retryable
jwalkingjew Sep 20, 2026
f30356c
perf: speed up claim activity overview
jwalkingjew Sep 21, 2026
4964670
perf: load claim activity in one request
jwalkingjew Sep 21, 2026
e2334db
fix: restore working claim activity loading
jwalkingjew Sep 21, 2026
61729d6
perf: hydrate claim activity while ranking
jwalkingjew Sep 21, 2026
6ed7616
Merge remote-tracking branch 'origin/master' into preston/geo-2976-up…
jwalkingjew Sep 21, 2026
71e0201
fix: harden claim record loading
jwalkingjew Sep 21, 2026
8035f17
fix: preserve cached claim activity
jwalkingjew Sep 21, 2026
97658f6
fix: parse authored tab links exactly
jwalkingjew Sep 21, 2026
d09426c
fix: distinguish bounded record retries
jwalkingjew Sep 21, 2026
39605b1
fix: isolate claim record retries
jwalkingjew Sep 21, 2026
3694824
perf: page claim activity from graph
jwalkingjew Sep 21, 2026
355eca6
feat: add claim record filters and sorts
jwalkingjew Sep 22, 2026
5a8b843
fix: add exact claim record facets
jwalkingjew Sep 22, 2026
5e224bd
fix: preserve claim record correctness
jwalkingjew Sep 22, 2026
1c93502
fix: keep claim record filters recoverable
jwalkingjew Sep 22, 2026
e12726e
fix: preserve claim tab authorization and ordering
jwalkingjew Sep 22, 2026
f804907
Merge remote-tracking branch 'origin/master' into preston/geo-2976-up…
jwalkingjew Sep 22, 2026
ae62386
Merge remote-tracking branch 'origin/master' into preston/geo-2976-up…
jwalkingjew Sep 22, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 30 additions & 0 deletions apps/web/app/space/(entity)/[id]/[entityId]/claim-record-page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import { IdUtils } from '@geoprotocol/geo-sdk/lite';

import { notFound } from 'next/navigation';

import { CLAIM_TYPE_ID } from '~/core/claims/ontology';
import { ID } from '~/core/id';
import { isHiddenEntity } from '~/core/moderation/hidden';

import { cachedFetchEntityPage } from './cached-fetch-entity';
import DefaultEntityPage from './default-entity-page';

export type ClaimRecordPageProps = {
params: Promise<{ id: string; entityId: string }>;
searchParams: Promise<{ [key: string]: string | string[] | undefined }>;
};

/** The shared server guard and page shell behind the claim's three record routes. */
export async function ClaimRecordPage(props: ClaimRecordPageProps) {
const params = await props.params;
const searchParams = await props.searchParams;

if (!IdUtils.isValid(params.id) || !IdUtils.isValid(params.entityId)) notFound();

const result = await cachedFetchEntityPage(params.entityId, params.id);
if (isHiddenEntity(result?.entity) || !result?.entity?.types.some(type => ID.equals(type.id, CLAIM_TYPE_ID))) {
Comment thread
jwalkingjew marked this conversation as resolved.
notFound();
}

return <DefaultEntityPage params={params} searchParams={searchParams} />;
}
5 changes: 5 additions & 0 deletions apps/web/app/space/(entity)/[id]/[entityId]/claims/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import { ClaimRecordPage, type ClaimRecordPageProps } from '../claim-record-page';

export default function ClaimClaimsPage(props: ClaimRecordPageProps) {
return <ClaimRecordPage {...props} />;
}
5 changes: 5 additions & 0 deletions apps/web/app/space/(entity)/[id]/[entityId]/debates/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import { ClaimRecordPage, type ClaimRecordPageProps } from '../claim-record-page';

export default function ClaimDebatesPage(props: ClaimRecordPageProps) {
return <ClaimRecordPage {...props} />;
}
5 changes: 3 additions & 2 deletions apps/web/app/space/(entity)/[id]/[entityId]/layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import { EntityStoreProvider } from '~/core/state/entity-page-store/entity-store
import { TabEntity } from '~/core/types';
import { Entity, Relation } from '~/core/types';
import { Entities } from '~/core/utils/entity';
import { entityBrowseViewFromTypes } from '~/core/utils/entity-browse-view';
import { sortRelations } from '~/core/utils/utils';

import { Spacer } from '~/design-system/spacer';
Expand Down Expand Up @@ -77,9 +78,9 @@ export default async function ProfileLayout(props: Props) {

const { children } = props;
const result = await cachedFetchEntityPage(entityId, spaceId);
const typeIds = result?.entity?.types.map(t => t.id) ?? [];
const entityTypes = result?.entity?.types ?? [];

if (!typeIds.includes(SystemIds.PERSON_TYPE)) {
if (entityBrowseViewFromTypes(entityTypes) !== 'person') {
return <>{children}</>;
}

Expand Down
5 changes: 3 additions & 2 deletions apps/web/app/space/(entity)/[id]/[entityId]/page.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { IdUtils, SystemIds } from '@geoprotocol/geo-sdk/lite';
import { IdUtils } from '@geoprotocol/geo-sdk/lite';

import { notFound } from 'next/navigation';

Expand All @@ -8,6 +8,7 @@ import { getRecordingUrls } from '~/core/community-calls/recordings';
import { DebateEntityView } from '~/core/debates/browse/debate-entity-view';
import { isDebateEntity } from '~/core/debates/is-debate-entity';
import { isHiddenEntity } from '~/core/moderation/hidden';
import { entityBrowseViewFromTypes } from '~/core/utils/entity-browse-view';
import { entityHasOnlyPostType } from '~/core/utils/entity/entities';

import { BountyDetailHeader } from '~/partials/bounties/bounty-detail-header';
Expand Down Expand Up @@ -43,7 +44,7 @@ export default async function EntityTemplateStrategy(props: Props) {
notFound();
}

if (result?.entity?.types.map(t => t.id).includes(SystemIds.PERSON_TYPE)) {
if (result?.entity && entityBrowseViewFromTypes(result.entity.types) === 'person') {
return <ProfileEntityServerContainer params={params} searchParams={searchParams} />;
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,18 @@
import { SystemIds } from '@geoprotocol/geo-sdk/lite';
import '@testing-library/jest-dom/vitest';
import { cleanup, render } from '@testing-library/react';

import * as React from 'react';

import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';

import { CLAIM_TYPE_ID } from '~/core/claims/ontology';

// Statically, below the mocks vitest hoists above it. Importing this graph inside a test body ran
// it against the 5s test timeout instead, which the route's module tree exceeds under a parallel
// run — it is the layout, so it pulls in most of the entity page.
import ProfileLayout from './layout';

// The layout 404s on anything `IdUtils.isValid` rejects, so these are real ids rather than labels.
/** The route params. The fetched person's id is deliberately a different one. */
const ROUTE_ENTITY_ID = 'a1b2c3d4e5f6427a8b9c0d1e2f3a4b5c';
Expand All @@ -17,14 +25,16 @@ const mocks = vi.hoisted(() => ({
heading: null as Record<string, unknown> | null,
storeProvider: null as Record<string, unknown> | null,
metadataHeader: null as Record<string, unknown> | null,
entityTypes: [] as { id: string }[],
}));

vi.mock('./cached-fetch-entity', async () => {
const { SystemIds } = await import('@geoprotocol/geo-sdk/lite');
vi.mock('./cached-fetch-entity', () => {
const person = {
id: 'c3d4e5f6a7b8429c0d1e2f3a4b5c6d7e',
name: 'Ada Lovelace',
types: [{ id: SystemIds.PERSON_TYPE }],
get types() {
return mocks.entityTypes;
},
relations: [],
values: [],
spaces: [],
Expand Down Expand Up @@ -88,12 +98,8 @@ vi.mock('~/partials/entity-page/personal-profile-suggested-task-sync', () => ({
PersonalProfileSuggestedTaskSync: () => null,
}));

// Statically, below the mocks vitest hoists above it. Importing this graph inside a test body ran
// it against the 5s test timeout instead, which the route's module tree exceeds under a parallel
// run — it is the layout, so it pulls in most of the entity page.
import ProfileLayout from './layout';

beforeEach(() => {
mocks.entityTypes = [{ id: SystemIds.PERSON_TYPE }];
mocks.actions = null;
mocks.heading = null;
mocks.storeProvider = null;
Expand Down Expand Up @@ -140,4 +146,14 @@ describe('profile ProfileLayout', () => {
// above is what covers it — the space it scopes by is the one assertable here.
expect(mocks.metadataHeader).toMatchObject({ spaceId: SPACE_ID });
});

it('does not wrap a mixed Claim and Person entity in the profile shell', async () => {
mocks.entityTypes = [{ id: SystemIds.PERSON_TYPE }, { id: CLAIM_TYPE_ID }];

const view = await renderProfileLayout();

expect(view.getByTestId('page')).toBeInTheDocument();
expect(mocks.storeProvider).toBeNull();
expect(mocks.actions).toBeNull();
});
});
5 changes: 5 additions & 0 deletions apps/web/app/space/(entity)/[id]/[entityId]/sources/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import { ClaimRecordPage, type ClaimRecordPageProps } from '../claim-record-page';

export default function ClaimSourcesPage(props: ClaimRecordPageProps) {
return <ClaimRecordPage {...props} />;
}
Loading
Loading