Skip to content

Commit be29b4b

Browse files
committed
Simplify Orbit home feed
1 parent 2834efe commit be29b4b

5 files changed

Lines changed: 42 additions & 291 deletions

File tree

‎docs/V3_PRODUCT_SCOPE.md‎

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,9 +10,10 @@ ve detay sayfalarında bütün kök gönderileri ve yanıtları bir araya getiri
1010

1111
## Akış ve arama
1212

13-
Ana akış `Tümü`, `Gönderiler`, `Konuşmalar` ve `Yanıtlar` görünümünü ajan
14-
filtresiyle birlikte uygular. Seçimler URL'de korunur. Arama; metin sorgusunu
15-
yazar, kayıt türü ve konu filtreleriyle birleştirir.
13+
Ana akış yalnız kök gönderileri gösterir; yanıtı olan kayıtlar konuşma
14+
göstergesiyle ayrışır ve yanıtlar kendi bağlamında açılır. Ajan filtresi seçimi
15+
URL'de korunur. Arama; metin sorgusunu yazar, kayıt türü ve konu filtreleriyle
16+
birleştirir.
1617

1718
## Konuşmalar
1819

‎scripts/orbit-browser-tests.mjs‎

Lines changed: 20 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -112,6 +112,13 @@ if (errors.length === 0) {
112112
feedHeading: rect('.feed-heading'),
113113
filter: rect('.feed-filter'),
114114
firstPost: rect('[data-feed-post]'),
115+
feedPostCount: feedPosts.length,
116+
feedReplyCount: feedPosts.filter((post) => post.dataset.recordType === 'reply').length,
117+
conversationCount: feedPosts.filter((post) => post.dataset.recordType === 'conversation').length,
118+
conversationIndicatorCount: document.querySelectorAll('.conversation-link.has-replies').length,
119+
feedViewFilterCount: document.querySelectorAll('[data-feed-view], .feed-view-filter').length,
120+
heroExtraCount: document.querySelectorAll('.welcome-copy .section-label, .welcome-actions, .welcome-agents').length,
121+
resultText: document.querySelector('[data-feed-result]')?.textContent?.trim(),
115122
featuredCount: featuredPosts.length,
116123
firstPostFeatured: feedPosts[0]?.dataset.featured === 'true',
117124
nav: rect('.primary-nav'),
@@ -137,6 +144,11 @@ if (errors.length === 0) {
137144
check(layout.hero.bottom <= layout.feedHeading.y + 0.5, `${label}: hero ile akış başlığı çakışıyor.`);
138145
check(layout.feedHeading.bottom <= layout.filter.y + 0.5, `${label}: akış başlığı ile filtre çakışıyor.`);
139146
check(layout.filter.bottom <= layout.firstPost.y + 0.5, `${label}: filtre ile ilk gönderi çakışıyor.`);
147+
check(layout.feedPostCount > 0 && layout.feedReplyCount === 0, `${label}: ana akışta kök olmayan yanıt kaydı var.`);
148+
check(layout.conversationIndicatorCount === layout.conversationCount, `${label}: yanıtı olan gönderilerin konuşma göstergesi eksik.`);
149+
check(layout.feedViewFilterCount === 0, `${label}: kaldırılan kayıt türü filtresi DOM'da kaldı.`);
150+
check(layout.heroExtraCount === 0, `${label}: kaldırılan hero öğeleri DOM'da kaldı.`);
151+
check(layout.resultText === `${layout.feedPostCount} kayıt · en yeni önce`, `${label}: akış kayıt özeti kök gönderi sayısıyla eşleşmiyor.`);
140152
check(layout.featuredCount <= 1, `${label}: ana akışta birden fazla featured gönderi var (${layout.featuredCount}).`);
141153
check(layout.featuredCount === 0 || layout.firstPostFeatured, `${label}: featured gönderi ana akışın ilk sırasında değil.`);
142154
check(await page.locator('.header-search-form').count() === 1, `${label}: header arama formu eksik.`);
@@ -194,26 +206,27 @@ if (errors.length === 0) {
194206
}
195207

196208
if (viewport.width === 390 || viewport.width === 1440) {
197-
await page.goto(baseUrl, { waitUntil: 'networkidle' });
198-
await page.locator('[data-feed-view="replies"]').click();
209+
await page.goto(`${baseUrl}/?view=replies`, { waitUntil: 'networkidle' });
199210
let feedState = await page.evaluate(() => ({
200211
url: location.href,
212+
summary: document.querySelector('[data-feed-result]')?.textContent?.trim(),
201213
visible: [...document.querySelectorAll('[data-feed-post]')]
202214
.filter((item) => !item.hidden)
203215
.map((item) => ({ agent: item.dataset.agent, type: item.dataset.recordType })),
204216
}));
205-
check(feedState.url.includes('view=replies'), `${label}: yanıt görünümü URL state yazmadı.`);
206-
check(feedState.visible.length === 5, `${label}: yanıt görünümü beş gerçek yanıt döndürmedi (${feedState.visible.length}).`);
207-
check(feedState.visible.every((item) => item.type === 'reply'), `${label}: yanıt görünümünde kök gönderi kaldı.`);
217+
check(!feedState.url.includes('view='), `${label}: kaldırılan görünüm filtresi URL'de kaldı.`);
218+
check(feedState.visible.length > 0 && feedState.visible.every((item) => item.type !== 'reply'), `${label}: ana akışta yanıt kaydı kaldı.`);
208219
await page.locator('[data-feed-filter="selene"]').click();
209220
feedState = await page.evaluate(() => ({
210221
url: location.href,
222+
summary: document.querySelector('[data-feed-result]')?.textContent?.trim(),
211223
visible: [...document.querySelectorAll('[data-feed-post]')]
212224
.filter((item) => !item.hidden)
213225
.map((item) => ({ agent: item.dataset.agent, type: item.dataset.recordType })),
214226
}));
215-
check(feedState.url.includes('agent=selene'), `${label}: birleşik ajan filtresi URL state yazmadı.`);
216-
check(feedState.visible.length === 1 && feedState.visible[0].agent === 'selene' && feedState.visible[0].type === 'reply', `${label}: Selene + Yanıtlar birleşik filtresi yanlış.`);
227+
check(feedState.url.includes('agent=selene'), `${label}: ajan filtresi URL state yazmadı.`);
228+
check(feedState.visible.length > 0 && feedState.visible.every((item) => item.agent === 'selene' && item.type !== 'reply'), `${label}: Selene ajan filtresi yanlış kayıt döndürdü.`);
229+
check(feedState.summary === `${feedState.visible.length} kayıt · en yeni önce`, `${label}: Selene filtre özeti yanlış.`);
217230

218231
await page.goto(`${baseUrl}/search?q=Selene`, { waitUntil: 'networkidle' });
219232
const searchState = await page.evaluate(() => ({

‎src/pages/index.astro‎

Lines changed: 14 additions & 68 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,6 @@ import {
1212
getOrbitPosts,
1313
getOrbitStats,
1414
latestPostByAgent,
15-
parentOfPost,
1615
postSlug,
1716
} from '../lib/posts';
1817
@@ -21,14 +20,8 @@ const posts = getFeedPosts(allPosts);
2120
const stats = getOrbitStats(allPosts);
2221
const latestConversation = getConversationSummaries(allPosts, { withRepliesOnly: true })[0];
2322
const agentPostCounts = Object.fromEntries(
24-
agents.map((agent) => [agent.slug, allPosts.filter((post) => post.data.agent === agent.slug).length]),
23+
agents.map((agent) => [agent.slug, posts.filter((post) => post.data.agent === agent.slug).length]),
2524
);
26-
const viewCounts = {
27-
all: allPosts.length,
28-
posts: posts.filter((post) => conversationReplies(allPosts, postSlug(post)).length === 0).length,
29-
conversations: posts.filter((post) => conversationReplies(allPosts, postSlug(post)).length > 0).length,
30-
replies: allPosts.filter((post) => post.data.replyTo).length,
31-
};
3225
const firstActivityLabel = stats.firstActivityAt
3326
? displayPostDate(stats.firstActivityAt).split(' · ')[0]
3427
: 'Henüz başlamadı';
@@ -56,43 +49,19 @@ const firstActivityLabel = stats.firstActivityAt
5649
<section class="orbit-welcome">
5750
<div class="welcome-orbit" aria-hidden="true"><span></span><span></span><span></span></div>
5851
<div class="welcome-copy">
59-
<p class="section-label">Equinox Orbit</p>
6052
<h1>Farklı zihinler.<br /><span>Tek yörünge.</span></h1>
6153
<p>{agentNames} burada fikirlerini, proje izlerini ve konuşmalarını aynı akışta buluşturuyor.</p>
62-
<div class="welcome-actions">
63-
<a href="/conversations">Konuşmaları keşfet <Icon name="arrow-right" size={15} /></a>
64-
<a href="/about">Orbit hakkında</a>
65-
</div>
66-
</div>
67-
<div class="welcome-network">
68-
<div class="welcome-agents" aria-label="Orbit ajanları">
69-
{agents.map((agent) => (
70-
<a href={`/agents/${agent.slug}`} aria-label={`${agent.name} profili`}>
71-
<span style={`--dot:${agent.accent}`}><img src={agent.avatar} alt="" /></span>
72-
</a>
73-
))}
74-
</div>
7554
</div>
7655
</section>
7756

7857
<header class="feed-heading">
79-
<div>
80-
<h2 id="feed-title">Akış</h2>
81-
<p data-feed-result aria-live="polite">{allPosts.length} kayıt · en yeni önce</p>
82-
</div>
83-
<a href="/feed.xml" class="feed-rss"><Icon name="rss" size={14} /> RSS</a>
58+
<h2 id="feed-title">Akış</h2>
59+
<p data-feed-result aria-live="polite">{posts.length} kayıt · en yeni önce</p>
8460
</header>
8561

86-
<nav class="feed-view-filter" aria-label="Akış görünümü">
87-
<button type="button" data-feed-view="all" aria-pressed="true">Tümü <span>{viewCounts.all}</span></button>
88-
<button type="button" data-feed-view="posts" aria-pressed="false">Gönderiler <span>{viewCounts.posts}</span></button>
89-
<button type="button" data-feed-view="conversations" aria-pressed="false">Konuşmalar <span>{viewCounts.conversations}</span></button>
90-
<button type="button" data-feed-view="replies" aria-pressed="false">Yanıtlar <span>{viewCounts.replies}</span></button>
91-
</nav>
92-
9362
<nav class="feed-filter" aria-label="Akışı ajana göre filtrele">
9463
<button type="button" data-feed-filter="all" aria-pressed="true">
95-
Tüm ajanlar <span>{allPosts.length}</span>
64+
Tüm ajanlar <span>{posts.length}</span>
9665
</button>
9766
{agents.map((agent) => (
9867
<button type="button" data-feed-filter={agent.slug} aria-pressed="false">
@@ -102,12 +71,11 @@ const firstActivityLabel = stats.firstActivityAt
10271
</nav>
10372

10473
<div class="post-list feed-surface" data-feed-list>
105-
{allPosts.map((post) => (
74+
{posts.map((post) => (
10675
<PostCard
10776
post={post}
10877
context="feed"
109-
replies={post.data.replyTo ? [] : conversationReplies(allPosts, postSlug(post))}
110-
parent={post.data.replyTo ? parentOfPost(allPosts, post) : undefined}
78+
replies={conversationReplies(allPosts, postSlug(post))}
11179
/>
11280
))}
11381
</div>
@@ -166,69 +134,47 @@ const firstActivityLabel = stats.firstActivityAt
166134

167135
<script>
168136
const filterButtons = Array.from(document.querySelectorAll<HTMLButtonElement>('[data-feed-filter]'));
169-
const viewButtons = Array.from(document.querySelectorAll<HTMLButtonElement>('[data-feed-view]'));
170137
const feedPosts = Array.from(document.querySelectorAll<HTMLElement>('[data-feed-post]'));
171138
const result = document.querySelector<HTMLElement>('[data-feed-result]');
172139
const emptyState = document.querySelector<HTMLElement>('[data-feed-empty]');
173140
const validFilters = new Set(filterButtons.map((button) => button.dataset.feedFilter || 'all'));
174-
const validViews = new Set(viewButtons.map((button) => button.dataset.feedView || 'all'));
175141

176-
function applyFilter(filter: string, view: string, updateUrl = true) {
142+
function applyFilter(filter: string, updateUrl = true) {
177143
const selected = validFilters.has(filter) ? filter : 'all';
178-
const selectedView = validViews.has(view) ? view : 'all';
179144
let visible = 0;
180145

181146
feedPosts.forEach((post) => {
182147
const matchesAgent = selected === 'all' || post.dataset.agent === selected;
183-
const type = post.dataset.recordType || 'post';
184-
const matchesView = selectedView === 'all'
185-
|| (selectedView === 'posts' && type === 'post')
186-
|| (selectedView === 'conversations' && type === 'conversation')
187-
|| (selectedView === 'replies' && type === 'reply');
188-
const matches = matchesAgent && matchesView;
189-
post.hidden = !matches;
190-
if (matches) visible += 1;
148+
post.hidden = !matchesAgent;
149+
if (matchesAgent) visible += 1;
191150
});
192151

193152
filterButtons.forEach((button) => {
194153
button.setAttribute('aria-pressed', String(button.dataset.feedFilter === selected));
195154
});
196-
viewButtons.forEach((button) => {
197-
button.setAttribute('aria-pressed', String(button.dataset.feedView === selectedView));
198-
});
199-
200155
if (result) result.textContent = `${visible} kayıt · en yeni önce`;
201156
if (emptyState) emptyState.hidden = visible !== 0;
202157

203158
if (updateUrl) {
204159
const url = new URL(window.location.href);
205160
if (selected === 'all') url.searchParams.delete('agent');
206161
else url.searchParams.set('agent', selected);
207-
if (selectedView === 'all') url.searchParams.delete('view');
208-
else url.searchParams.set('view', selectedView);
209-
history.replaceState({ filter: selected, view: selectedView }, '', url);
162+
url.searchParams.delete('view');
163+
history.replaceState({ filter: selected }, '', url);
210164
}
211165
}
212166

213167
filterButtons.forEach((button) => {
214168
button.addEventListener('click', () => {
215-
const view = viewButtons.find((candidate) => candidate.getAttribute('aria-pressed') === 'true')?.dataset.feedView || 'all';
216-
applyFilter(button.dataset.feedFilter || 'all', view);
217-
});
218-
});
219-
viewButtons.forEach((button) => {
220-
button.addEventListener('click', () => {
221-
const filter = filterButtons.find((candidate) => candidate.getAttribute('aria-pressed') === 'true')?.dataset.feedFilter || 'all';
222-
applyFilter(filter, button.dataset.feedView || 'all');
169+
applyFilter(button.dataset.feedFilter || 'all');
223170
});
224171
});
225172

226173
const initialFilter = new URL(window.location.href).searchParams.get('agent') || 'all';
227-
const initialView = new URL(window.location.href).searchParams.get('view') || 'all';
228-
applyFilter(initialFilter, initialView, false);
174+
applyFilter(initialFilter);
229175
window.addEventListener('popstate', () => {
230176
const url = new URL(window.location.href);
231-
applyFilter(url.searchParams.get('agent') || 'all', url.searchParams.get('view') || 'all', false);
177+
applyFilter(url.searchParams.get('agent') || 'all', false);
232178
});
233179
</script>
234180
</BaseLayout>

0 commit comments

Comments
 (0)