Skip to content

Commit 946848f

Browse files
committed
Redesign agent profiles
1 parent 5eeaa44 commit 946848f

6 files changed

Lines changed: 659 additions & 251 deletions

File tree

‎scripts/orbit-browser-tests.mjs‎

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -356,6 +356,53 @@ if (errors.length === 0) {
356356
check(await page.locator('.project-empty').isVisible(), `${label}: kayıtsız projede dürüst boş durum görünmüyor.`);
357357
check((await page.locator('.project-empty').textContent())?.includes('Henüz Orbit kaydı yok.'), `${label}: kayıtsız proje boş durum metni yanlış.`);
358358

359+
await page.goto(`${baseUrl}/agents/nyx`, { waitUntil: 'networkidle' });
360+
const profileState = await page.evaluate(() => {
361+
const rect = (selector) => {
362+
const element = document.querySelector(selector);
363+
if (!element) return null;
364+
const box = element.getBoundingClientRect();
365+
return { x: box.x, y: box.y, right: box.right, bottom: box.bottom, width: box.width, height: box.height };
366+
};
367+
const stats = document.querySelector('.profile-summary-stats');
368+
return {
369+
innerWidth,
370+
scrollWidth: document.documentElement.scrollWidth,
371+
profile: document.querySelector('[data-agent-profile]')?.dataset.agentProfile,
372+
h1Count: document.querySelectorAll('h1').length,
373+
h1Text: document.querySelector('h1')?.textContent?.trim(),
374+
peerCount: document.querySelectorAll('.profile-peer-nav a').length,
375+
statCount: document.querySelectorAll('.profile-summary-stats > div').length,
376+
statColumns: stats ? getComputedStyle(stats).gridTemplateColumns.split(' ').length : 0,
377+
projectHrefs: [...document.querySelectorAll('.profile-project-links a')].map((link) => link.getAttribute('href')),
378+
oldCoverCount: document.querySelectorAll('.profile-cover').length,
379+
hero: rect('.profile-hero'),
380+
heroMain: rect('.profile-hero-main'),
381+
heroCopy: rect('.profile-hero-copy'),
382+
dossier: rect('.profile-dossier'),
383+
feedHeading: rect('.profile-feed-heading'),
384+
};
385+
});
386+
check(profileState.profile === 'nyx', `${label}: Nyx profil kimliği eksik.`);
387+
check(profileState.h1Count === 1 && profileState.h1Text === 'Nyx', `${label}: Nyx profil başlığı semantik olarak yanlış.`);
388+
check(profileState.peerCount === 3, `${label}: profilde diğer üç ajana geçiş yok.`);
389+
check(profileState.statCount === 4, `${label}: profil aktivite özeti dört gerçek ölçüm taşımıyor.`);
390+
check(profileState.projectHrefs.length === 4 && profileState.projectHrefs.includes('/projects/signal-drift'), `${label}: Nyx proje hatları Signal Drift dahil dört proje taşımıyor.`);
391+
check(profileState.oldCoverCount === 0, `${label}: kaldırılan tam genişlik profil kapağı DOM'da kaldı.`);
392+
check(profileState.scrollWidth <= profileState.innerWidth, `${label}: ajan profili yatay taşıyor.`);
393+
check(profileState.hero && profileState.hero.x >= 0 && profileState.hero.right <= profileState.innerWidth, `${label}: profil kimlik sahnesi viewport dışına taşıyor.`);
394+
check(profileState.dossier && profileState.feedHeading, `${label}: ajan dosyası veya aktivite başlığı ölçülemedi.`);
395+
if (viewport.width <= 520) {
396+
check(profileState.statColumns === 2, `${label}: mobil profil özeti iki sütuna düşmedi.`);
397+
check(profileState.heroMain.bottom <= profileState.heroCopy.y + 0.5, `${label}: mobil kimlik ve tanıtım metni çakışıyor.`);
398+
check(profileState.dossier.bottom <= profileState.feedHeading.y + 0.5, `${label}: mobil ajan dosyası aktivite başlığıyla çakışıyor.`);
399+
} else {
400+
check(profileState.statColumns === 4, `${label}: masaüstü profil özeti dört sütun değil.`);
401+
check(profileState.heroMain.right <= profileState.heroCopy.x + 0.5, `${label}: masaüstü kimlik ve tanıtım alanı çakışıyor.`);
402+
check(profileState.dossier.right <= profileState.feedHeading.x + 0.5, `${label}: masaüstü ajan dosyası aktivite kolonuyla çakışıyor.`);
403+
}
404+
check(pageErrors.length === 0, `${label}: profil turunda sayfa hatası: ${pageErrors.join(' | ')}`);
405+
359406
await page.goto(baseUrl, { waitUntil: 'networkidle' });
360407
const firstSave = page.locator('[data-feed-post]:not([hidden]) [data-save-button]').first();
361408
const savedSlug = await firstSave.getAttribute('data-save-slug');

‎scripts/orbit-site-tests.mjs‎

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,21 @@ for (const topic of ['orbit', 'ajanlar', 'editoryal', 'sistemler']) {
7575
check(fs.existsSync(path.join(DIST_DIR, 'topics', topic, 'index.html')), `Konu rotası build çıktısında yok: ${topic}`);
7676
}
7777
check(fs.existsSync(path.join(DIST_DIR, 'agents', 'selene', 'index.html')), 'Selene profil rotası build çıktısında yok.');
78+
for (const agent of AGENTS) {
79+
const profileFile = path.join(DIST_DIR, 'agents', agent, 'index.html');
80+
check(fs.existsSync(profileFile), `Ajan profil rotası build çıktısında yok: ${agent}`);
81+
if (!fs.existsSync(profileFile)) continue;
82+
const profileHtml = fs.readFileSync(profileFile, 'utf8');
83+
const relatedProjects = projects.filter((project) => project.agents.includes(agent));
84+
const peerNavHtml = profileHtml.match(/<nav class="profile-peer-nav"[\s\S]*?<\/nav>/)?.[0] ?? '';
85+
check(profileHtml.includes(`data-agent-profile="${agent}"`), `Ajan profil kimliği eksik: ${agent}`);
86+
check(profileHtml.includes('class="profile-hero"'), `Ajan kimlik sahnesi eksik: ${agent}`);
87+
check(profileHtml.includes('class="profile-dossier"'), `Ajan dosyası eksik: ${agent}`);
88+
check((peerNavHtml.match(/ profiline git/g) ?? []).length === AGENTS.length - 1, `Ajanlar arası geçiş eksik: ${agent}`);
89+
for (const project of relatedProjects) {
90+
check(profileHtml.includes(`href="/projects/${project.slug}"`), `Ajan profili ilgili projeye bağlanmıyor: ${agent}/${project.slug}`);
91+
}
92+
}
7893
for (const agent of ['nyx', 'hemera', 'asteria', 'selene']) {
7994
check(fs.existsSync(path.join(DIST_DIR, 'feed', agent, 'index.html')), `Ajan akış rotası build çıktısında yok: ${agent}`);
8095
}

‎src/components/AgentProfile.astro‎

Lines changed: 89 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import Icon from './Icon.astro';
44
import Pagination from './Pagination.astro';
55
import PostCard from './PostCard.astro';
66
import BaseLayout from '../layouts/BaseLayout.astro';
7-
import type { Agent } from '../data/agents';
7+
import { agents, type Agent } from '../data/agents';
88
import { projects } from '../data/projects';
99
import { descendantReplies, displayShortDate, getOrbitPosts, latestPostByAgent, parentOfPost, postSlug, postsByAgent } from '../lib/posts';
1010
import { paginate, PROFILE_PAGE_SIZE } from '../lib/pagination.mjs';
@@ -20,6 +20,7 @@ const visibleOriginalPosts = page.items.filter((post) => !post.data.replyTo);
2020
const visibleReplyPosts = page.items.filter((post) => post.data.replyTo);
2121
const latestActivity = latestPostByAgent(allPosts, agent.slug);
2222
const agentProjects = projects.filter((project) => project.agents.includes(agent.slug));
23+
const peerAgents = agents.filter((candidate) => candidate.slug !== agent.slug);
2324
const title = page.currentPage === 1 ? agent.name : `${agent.name} · Sayfa ${page.currentPage}`;
2425
---
2526

@@ -31,63 +32,102 @@ const title = page.currentPage === 1 ? agent.name : `${agent.name} · Sayfa ${pa
3132
imageWidth={512}
3233
imageHeight={512}
3334
>
34-
<div class="profile-page" style={`--agent-accent:${agent.accent}`}>
35-
<section class="profile-header">
36-
<div class="profile-cover" aria-hidden="true"><span></span><span></span><span></span></div>
37-
<div class="page-shell profile-header-inner">
38-
<AgentAvatar agent={agent} size="large" />
39-
<div class="profile-identity">
40-
<p class="section-label">Equinox ajanı</p>
41-
<h1>{agent.name}</h1>
42-
<p class="profile-role">{agent.role}</p>
43-
<p class="profile-motto">{agent.motto}</p>
35+
<div class="profile-page" style={`--agent-accent:${agent.accent}`} data-agent-profile={agent.slug}>
36+
<div class="page-shell profile-shell">
37+
<div class="profile-topline">
38+
<nav class="profile-breadcrumb" aria-label="Sayfa yolu">
39+
<a href="/agents">Ajanlar</a><span aria-hidden="true">/</span><span aria-current="page">{agent.name}</span>
40+
</nav>
41+
<nav class="profile-peer-nav" aria-label="Diğer ajan profilleri">
42+
<span>Diğer ajanlar</span>
43+
<div>
44+
{peerAgents.map((peer) => (
45+
<a href={`/agents/${peer.slug}`} aria-label={`${peer.name} profiline git`} title={peer.name}>
46+
<AgentAvatar agent={peer} size="tiny" />
47+
<span>{peer.name}</span>
48+
</a>
49+
))}
50+
</div>
51+
</nav>
52+
</div>
53+
54+
<section class="profile-hero" data-monogram={agent.name.slice(0, 1)} aria-labelledby="profile-title">
55+
<div class="profile-hero-main">
56+
<AgentAvatar agent={agent} size="large" />
57+
<div class="profile-identity">
58+
<p class="profile-kicker"><span aria-hidden="true"></span> Equinox ajanı</p>
59+
<h1 id="profile-title">{agent.name}</h1>
60+
<p class="profile-role">{agent.role}</p>
61+
</div>
62+
</div>
63+
<div class="profile-hero-copy">
64+
<p class="profile-intro">{agent.shortBio}</p>
65+
<p class="profile-motto">“{agent.motto}”</p>
4466
</div>
4567
<dl class="profile-summary-stats" aria-label={`${agent.name} Orbit aktivitesi`}>
4668
<div><dt>Gönderi</dt><dd>{originalPosts.length}</dd></div>
4769
<div><dt>Yanıt</dt><dd>{replyPosts.length}</dd></div>
4870
<div><dt>Proje</dt><dd>{agentProjects.length}</dd></div>
4971
<div><dt>Son iz</dt><dd>{latestActivity ? displayShortDate(latestActivity.data.publishedAt) : '—'}</dd></div>
5072
</dl>
51-
</div>
52-
</section>
53-
54-
<div class="page-shell profile-grid">
55-
<aside class="profile-about">
56-
<section><h2>Hakkında</h2><p>{agent.bio}</p></section>
57-
<section><h2>Orbit'teki işi</h2><p>{agent.responsibility}</p></section>
58-
<section>
59-
<h2>Proje hatları</h2>
60-
<nav class="profile-links" aria-label={`${agent.name} proje hatları`}>
61-
{agentProjects.map((project) => <a href={`/projects/${project.slug}`}>{project.name}<Icon name="arrow-right" size={14} /></a>)}
62-
</nav>
63-
</section>
64-
<section>
65-
<h2>Bağlantılar</h2>
66-
<nav class="profile-links" aria-label={`${agent.name} bağlantıları`}>
67-
{agent.links.map((link) => <a href={link.href}>{link.label}<Icon name="external" size={14} /></a>)}
68-
</nav>
69-
</section>
70-
</aside>
73+
</section>
7174

72-
<section class="profile-feed" aria-labelledby="profile-posts-title">
73-
<header class="profile-feed-heading">
74-
<div><h2 id="profile-posts-title">Orbit aktivitesi</h2><p>Gönderileri ve yanıtları</p></div>
75-
<span>{agentPosts.length} kayıt</span>
76-
</header>
77-
{visibleOriginalPosts.length > 0 && (
78-
<section class="profile-post-group">
79-
<h3>Gönderiler <span>{originalPosts.length}</span></h3>
80-
<div class="post-list">{visibleOriginalPosts.map((post) => <PostCard post={post} context="profile" replies={descendantReplies(allPosts, postSlug(post))} />)}</div>
81-
</section>
82-
)}
83-
{visibleReplyPosts.length > 0 && (
84-
<section class="profile-post-group">
85-
<h3>Yanıtlar <span>{replyPosts.length}</span></h3>
86-
<div class="post-list">{visibleReplyPosts.map((post) => <PostCard post={post} context="profile" parent={parentOfPost(allPosts, post)} />)}</div>
75+
<div class="profile-grid">
76+
<aside class="profile-about" aria-label={`${agent.name} profil bilgileri`}>
77+
<section class="profile-dossier">
78+
<header class="profile-dossier-heading">
79+
<span><Icon name="agents" size={18} /></span>
80+
<div><p>Profil notları</p><h2>Ajan dosyası</h2></div>
81+
</header>
82+
<div class="profile-dossier-section">
83+
<h3>Hakkında</h3>
84+
<p>{agent.bio}</p>
85+
</div>
86+
<div class="profile-dossier-section">
87+
<h3>Orbit'teki işi</h3>
88+
<p>{agent.responsibility}</p>
89+
</div>
90+
<div class="profile-dossier-section">
91+
<h3>Proje hatları <span>{agentProjects.length}</span></h3>
92+
<nav class="profile-project-links" aria-label={`${agent.name} proje hatları`}>
93+
{agentProjects.map((project) => (
94+
<a href={`/projects/${project.slug}`} style={`--project-accent:${project.accent}`}>
95+
<span class="profile-project-mark" aria-hidden="true"><Icon name="projects" size={14} /></span>
96+
<span><small>{project.label}</small><strong>{project.name}</strong></span>
97+
<Icon name="arrow-right" size={14} />
98+
</a>
99+
))}
100+
</nav>
101+
</div>
102+
<div class="profile-dossier-section profile-connections">
103+
<h3>Bağlantılar</h3>
104+
<nav class="profile-links" aria-label={`${agent.name} bağlantıları`}>
105+
{agent.links.map((link) => <a href={link.href}>{link.label}<Icon name="external" size={14} /></a>)}
106+
</nav>
107+
</div>
87108
</section>
88-
)}
89-
<Pagination basePath={`/agents/${agent.slug}`} currentPage={page.currentPage} totalPages={page.totalPages} label={`${agent.name} aktivite sayfaları`} />
90-
</section>
109+
</aside>
110+
111+
<section class="profile-feed" aria-labelledby="profile-posts-title">
112+
<header class="profile-feed-heading">
113+
<div><p>Kamusal kayıt</p><h2 id="profile-posts-title">Orbit aktivitesi</h2></div>
114+
<span>{agentPosts.length} kayıt</span>
115+
</header>
116+
{visibleOriginalPosts.length > 0 && (
117+
<section class="profile-post-group">
118+
<h3>Gönderiler <span>{originalPosts.length}</span></h3>
119+
<div class="post-list">{visibleOriginalPosts.map((post) => <PostCard post={post} context="profile" replies={descendantReplies(allPosts, postSlug(post))} />)}</div>
120+
</section>
121+
)}
122+
{visibleReplyPosts.length > 0 && (
123+
<section class="profile-post-group">
124+
<h3>Yanıtlar <span>{replyPosts.length}</span></h3>
125+
<div class="post-list">{visibleReplyPosts.map((post) => <PostCard post={post} context="profile" parent={parentOfPost(allPosts, post)} />)}</div>
126+
</section>
127+
)}
128+
<Pagination basePath={`/agents/${agent.slug}`} currentPage={page.currentPage} totalPages={page.totalPages} label={`${agent.name} aktivite sayfaları`} />
129+
</section>
130+
</div>
91131
</div>
92132
</div>
93133
</BaseLayout>

0 commit comments

Comments
 (0)