fix(agentworld): sort Tiny Place feed newest first (#3868)

Co-authored-by: MackJack023 <141124084+MackJack023@users.noreply.github.com>
Co-authored-by: Steven Enamakel <enamakel@tinyhumans.ai>
This commit is contained in:
Felix
2026-06-22 17:29:23 +05:30
committed by GitHub
co-authored by MackJack023 Steven Enamakel
parent bc59bb9307
commit b8b69da776
2 changed files with 66 additions and 3 deletions
@@ -171,6 +171,44 @@ describe('Feed list', () => {
expect(screen.getByText(/3 comments/i)).toBeInTheDocument();
});
test('sorts populated feed items newest first', async () => {
const olderItem = {
...sampleFeedItem,
post: {
...samplePost,
postId: 'post-old',
body: 'Old update',
createdAt: '2026-06-01T12:00:00Z',
},
};
const newerItem = {
...sampleFeedItem,
post: {
...samplePost,
postId: 'post-new',
body: 'Newest update',
createdAt: '2026-06-02T12:00:00Z',
},
};
vi.mocked(apiClient.graphql.homeFeed).mockResolvedValue({
items: [olderItem, newerItem],
count: 2,
});
render(<FeedSection />);
await waitFor(() => {
expect(screen.getByText('Newest update')).toBeInTheDocument();
expect(screen.getByText('Old update')).toBeInTheDocument();
});
expect(
screen.getByText('Newest update').compareDocumentPosition(screen.getByText('Old update')) &
Node.DOCUMENT_POSITION_FOLLOWING
).toBeTruthy();
});
test('shows wallet-locked error when wallet is not configured', async () => {
vi.mocked(apiClient.graphql.homeFeed).mockRejectedValue(new Error('wallet is not configured'));
render(<FeedSection />);
+28 -3
View File
@@ -15,6 +15,7 @@
* Pattern mirrors ExploreSection / MarketplaceSection: useState + useEffect
* fetch, PanelScaffold wrapper, StatusBlock for loading/error/empty states.
*/
import debug from 'debug';
import { useCallback, useEffect, useRef, useState } from 'react';
import PanelScaffold from '../../components/layout/PanelScaffold';
@@ -28,6 +29,8 @@ import {
import { fetchWalletStatus } from '../../services/walletApi';
import { apiClient } from '../AgentWorldShell';
const log = debug('agentworld:feed');
// ── State types ───────────────────────────────────────────────────────────────
type FeedState =
@@ -57,6 +60,28 @@ function isWalletLocked(message: string): boolean {
);
}
function postCreatedAtMillis(item: GqlHomeFeedItem): number {
const millis = Date.parse(item.post.createdAt);
return Number.isFinite(millis) ? millis : 0;
}
function sortedHomeFeedItems(result: { items?: GqlHomeFeedItem[] } | null | undefined) {
const items = Array.isArray(result?.items) ? [...result.items] : [];
const originalOrder = items.map(item => item.post.postId).join('\0');
items.sort((left, right) => postCreatedAtMillis(right) - postCreatedAtMillis(left));
if (items.length > 1 && originalOrder !== items.map(item => item.post.postId).join('\0')) {
log('sorted home feed newest-first', {
count: items.length,
newestCreatedAt: items[0]?.post.createdAt,
oldestCreatedAt: items.at(-1)?.post.createdAt,
});
}
return items;
}
/** Centered status message for loading / error / info states. */
function StatusBlock({ tone, title, body }: { tone: string; title: string; body?: string }) {
return (
@@ -550,7 +575,7 @@ export default function FeedSection() {
.homeFeed({ limit: 50 })
.then(result => {
if (cancelled) return;
const items = Array.isArray(result?.items) ? result.items : [];
const items = sortedHomeFeedItems(result);
setFeedState({ status: 'ok', items });
})
.catch((err: unknown) => {
@@ -624,7 +649,7 @@ export default function FeedSection() {
.deletePost(post.postId)
.then(() => {
void apiClient.graphql.homeFeed({ limit: 50 }).then(result => {
const items = Array.isArray(result?.items) ? result.items : [];
const items = sortedHomeFeedItems(result);
setFeedState({ status: 'ok', items });
});
})
@@ -635,7 +660,7 @@ export default function FeedSection() {
const refetchFeed = () => {
void apiClient.graphql.homeFeed({ limit: 50 }).then(result => {
const items = Array.isArray(result?.items) ? result.items : [];
const items = sortedHomeFeedItems(result);
setFeedState({ status: 'ok', items });
});
};