Skip to content

Commit a341433

Browse files
sametbasbugclaude
andcommitted
Give every page the same left rail
The menu lived inside HomeFeed, so it existed on exactly one page. From /agents or /topics there was nothing on screen but the logo: reaching Konular meant going back to the home page or hunting through the footer. There were two lists, not one, and they had already drifted apart — "Akış" against "Ana akış", "Hakkında" against "Orbit hakkında", a different order, and İletişim missing from one of them. Nobody did that on purpose; the lists were written separately, so there was nowhere the drift could show up. Rail, mobile bar and footer read from shared/navigation.ts now. The rail sits in BaseLayout rather than in the pages, which means the worker gets it for nothing: dynamic routes build their HTML from the compiled Astro shell, so agent profiles, the directory, records and announcements pick it up with no server-side change at all. Three widths, one component: labelled rail above 1260, icon-only down to 781, bottom bar below that. The middle band is why the icons exist — dropping the rail outright left laptop widths with no menu on screen, which was already true before this change and is the part worth fixing. Hesabım and Kaydedilenler moved into the rail, so the header is down to brand, search, theme and the Equinox link. Their header twins only appear below 781 where there is no rail to hold them. Single-column pages sit left-aligned at 760px, the same width and the same x as the feed column, so switching pages does not slide the text sideways. Two real regressions caught on the way: the header search no longer lined up with the content column once the rail shifted it by 56px, and the scale lock caught an sr-only copy carrying a margin it did not need. The HTML budgets rose by the rail's measured 3219 bytes, not by a guess — what those budgets guard against is inlined content, and this is bounded chrome. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
1 parent 3e20ade commit a341433

10 files changed

Lines changed: 376 additions & 83 deletions

File tree

‎scripts/orbit-browser-tests.mjs‎

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -462,7 +462,11 @@ if (errors.length === 0) {
462462
navDisplay: getComputedStyle(navigation).display,
463463
headerSearch: rect('.header-search-form'),
464464
headerTopicCount: document.querySelectorAll('.header-topic').length,
465-
sideTopicVisible: (rect('.side-nav a[href="/topics"]')?.width || 0) > 0,
465+
/* Ray artık `HomeFeed` içinde değil, `BaseLayout`'ta ve her
466+
sayfada. Seçici de oraya taşındı; niyet aynı: masaüstünde menü
467+
ekranda, 1260 altında değil. */
468+
sideTopicVisible: (rect('.app-rail-nav a[href="/topics"]')?.width || 0) > 0,
469+
railLabelVisible: (rect('.app-rail-nav a[href="/topics"] span')?.width || 0) > 4,
466470
brandEyebrow: document.querySelector('.brand-copy small')?.textContent?.trim(),
467471
brandEyebrowDisplay: getComputedStyle(document.querySelector('.brand-copy small')).display,
468472
brandName: document.querySelector('.brand-copy strong')?.textContent?.trim(),
@@ -501,7 +505,15 @@ if (errors.length === 0) {
501505
check(layout.moderationControlCount === 0, `${label}: anonim ziyaretçiye yönetici silme kontrolü göründü.`);
502506
check(await page.locator('.header-search-form').count() === 1, `${label}: header arama formu eksik.`);
503507
check(layout.headerTopicCount === 0, `${label}: üst barda yinelenen Konular düğmesi kaldı.`);
504-
check(layout.sideTopicVisible === (viewport.width > 1260), `${label}: masaüstü sol rayındaki Konular bağlantısı yanlış.`);
508+
/* Ray 781px'ten itibaren ekranda. 1260 altında etiketleri düşüp
509+
ikona iniyor ama kaybolmuyor: o bantta rayı tamamen kaldırmak
510+
781–1260 arasında hiç ana menü bırakmıyordu ve dizüstü
511+
genişlikleri tam oraya düşüyor. 780 altında alt çubuk devralıyor. */
512+
check(layout.sideTopicVisible === (viewport.width > 780), `${label}: masaüstü sol rayındaki Konular bağlantısı yanlış.`);
513+
check(
514+
layout.railLabelVisible === (viewport.width > 1260),
515+
`${label}: ray etiketlerinin görünürlüğü yanlış (${layout.railLabelVisible}).`,
516+
);
505517
check(layout.brandEyebrow === 'Equinox' && layout.brandEyebrowDisplay !== 'none' && layout.brandName === 'Orbit', `${label}: marka adı Equinox Orbit olarak görünmüyor.`);
506518
check(pageErrors.length === 0, `${label}: sayfa hatası: ${pageErrors.join(' | ')}`);
507519

‎scripts/orbit-site-tests.mjs‎

Lines changed: 28 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -705,8 +705,15 @@ check(searchScript.includes('/v1/search?'), 'Arama sayfası cursor tabanlı publ
705705
check(searchHtml.includes('data-search-more'), 'Arama sayfası sonraki cursor sayfasını açan kontrolü taşımıyor.');
706706
check(!searchScript.includes('/search-index.json'), 'Arama sayfası statik indeksle sınırlı kalıyor.');
707707
check(!savedHtml.includes('data-saved-card='), 'Kaydedilenler bütün kayıt kartlarını yeniden HTML içine gömüyor.');
708-
check(searchHtml.length < 24_000, `Arama HTML bütçesi aşıldı: ${searchHtml.length} byte.`);
709-
check(savedHtml.length < 22_000, `Kaydedilenler HTML bütçesi aşıldı: ${savedHtml.length} byte.`);
708+
/* Bütçe kalıcı sol rayın maliyeti kadar yükseldi: ray her sayfaya 3219 byte
709+
* ekliyor (ölçüldü, tahmin değil). Bütçenin koruduğu şey bu değil — üstteki
710+
* iddialar kayıt metinlerinin ve kart gövdelerinin HTML'e gömülmesini
711+
* engelliyor. Ray sınırlı ve sabit bir kabuk maliyeti; içerik değil.
712+
*
713+
* Tavanlar eski değerlerin 3219 üstünde: raysız gövde hâlâ 21_000 ve
714+
* 19_000'in altında kalmalı. */
715+
check(searchHtml.length < 27_219, `Arama HTML bütçesi aşıldı: ${searchHtml.length} byte.`);
716+
check(savedHtml.length < 25_219, `Kaydedilenler HTML bütçesi aşıldı: ${savedHtml.length} byte.`);
710717

711718
for (const htmlFile of htmlFiles) {
712719
const html = fs.readFileSync(htmlFile, 'utf8');
@@ -936,11 +943,28 @@ check(
936943
'Onay kutusu ya kayıp ya da onayladığı metinlere bağlantı vermiyor.',
937944
);
938945
/* İletişim menüde. Footer'da kalması, bize ulaşmak isteyen insanın
939-
* ulaşamaması demekti — ve kayıt herkese açıkken o insan sayısı artıyor. */
946+
* ulaşamaması demekti — ve kayıt herkese açıkken o insan sayısı artıyor.
947+
*
948+
* Liste artık `src/shared/navigation.ts`'te; iddia oraya taşındı. Header'da
949+
* aramak, menü oradan çıktığı için hep geçen bir test bırakırdı. */
950+
const navigationSource = fs.readFileSync(path.join(ROOT, 'src', 'shared', 'navigation.ts'), 'utf8');
940951
check(
941-
/href: '\/iletisim'/u.test(fs.readFileSync(path.join(ROOT, 'src', 'components', 'Header.astro'), 'utf8')),
952+
/href: '\/iletisim'[^\n]*primary: true/u.test(navigationSource),
942953
'İletişim menüden düşmüş; yalnız footer’da kalan bir bağlantıyı kimse bulmaz.',
943954
);
955+
/* Ray her sayfada. Menünün yalnız ana sayfada durduğu bir dönem vardı ve
956+
* /agents üzerinde ekranda marka dışında hiçbir gezinme öğesi kalmıyordu. */
957+
for (const [label, html] of [
958+
['ana sayfa', homeHtml],
959+
['ajan dizini', fs.readFileSync(path.join(DIST_DIR, 'agents', 'index.html'), 'utf8')],
960+
['konular', fs.readFileSync(path.join(DIST_DIR, 'topics', 'index.html'), 'utf8')],
961+
['hakkında', fs.readFileSync(path.join(DIST_DIR, 'about', 'index.html'), 'utf8')],
962+
['ajan profili kabuğu', fs.readFileSync(path.join(DIST_DIR, 'orbit-runtime', 'agent', 'index.html'), 'utf8')],
963+
['gönderi kabuğu', fs.readFileSync(path.join(DIST_DIR, 'orbit-runtime', 'post', 'index.html'), 'utf8')],
964+
]) {
965+
check(html.includes('class="app-rail"'), `${label}: kalıcı sol ray basılmamış.`);
966+
check(html.includes('aria-label="Orbit menüsü"'), `${label}: ray ana menüyü taşımıyor.`);
967+
}
944968
/* Tavan artık İKİ yerde okunuyor ve ikisi de gerekli: callback'te, kişiyi
945969
* ad seçtirdikten sonra reddetmemek için; kaydın kendisinde, kapının
946970
* gerçekten kapalı olması için. Aradan dakikalar geçebiliyor — kayıt tek

‎src/components/AppRail.astro‎

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
---
2+
/**
3+
* Kalıcı sol ray — sitenin ana menüsü.
4+
*
5+
* Menü bir dönem yalnız ana sayfada vardı: `HomeFeed.astro`'nun içine
6+
* gömülüydü ve `/agents`, `/topics`, `/about` gibi sayfalarda ekranda marka
7+
* dışında hiçbir gezinme öğesi kalmıyordu. Oralardan Konular'a gitmenin yolu
8+
* ya logoya basıp ana sayfaya dönmek ya da footer'ı bulmaktı.
9+
*
10+
* Ray `BaseLayout` içinde duruyor, sayfaların içinde değil. Worker da sayfa
11+
* kabuğunu derlenmiş Astro çıktısından okuduğu için ajan profilleri, dizin,
12+
* gönderi ve duyuru sayfaları rayı worker tarafında hiçbir değişiklik
13+
* olmadan alıyor.
14+
*/
15+
import Icon from './Icon.astro';
16+
import { ACCOUNT_NAVIGATION, NAVIGATION, isActive } from '../shared/navigation';
17+
18+
type Props = { currentPath: string };
19+
const { currentPath } = Astro.props;
20+
---
21+
22+
<aside class="app-rail">
23+
<nav class="app-rail-nav" aria-label="Orbit menüsü">
24+
{NAVIGATION.map((link) => (
25+
<a href={link.href} title={link.label} aria-current={isActive(link, currentPath) ? 'page' : undefined}>
26+
<Icon name={link.icon} size={19} />
27+
<span>{link.label}</span>
28+
</a>
29+
))}
30+
</nav>
31+
32+
{/*
33+
Hesap bölgesi ana menüden bir çizgiyle ayrı: ötekiler Orbit'in içeriğine,
34+
bu kişinin kendi hesabına gidiyor.
35+
*/}
36+
<nav class="app-rail-nav app-rail-account" aria-label="Hesabın">
37+
{ACCOUNT_NAVIGATION.map((link) => (
38+
<a href={link.href} title={link.label} aria-current={isActive(link, currentPath) ? 'page' : undefined}>
39+
<Icon name={link.icon} size={19} />
40+
<span>{link.label}</span>
41+
</a>
42+
))}
43+
</nav>
44+
</aside>

‎src/components/Header.astro‎

Lines changed: 19 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,20 @@
11
---
2-
import Icon, { type IconName } from './Icon.astro';
2+
import Icon from './Icon.astro';
33
import ThemeToggle from './ThemeToggle.astro';
4+
import { MOBILE_NAVIGATION, isActive, normalizePath } from '../shared/navigation';
45
56
type Props = { currentPath: string };
67
const { currentPath } = Astro.props;
7-
const normalizedPath = currentPath === '/' ? '/' : currentPath.replace(/\/$/, '');
8+
const normalizedPath = normalizePath(currentPath);
89
9-
const links: Array<{ href: string; label: string; icon: IconName; active: boolean }> = [
10-
{ href: '/', label: 'Akış', icon: 'home', active: normalizedPath === '/' || normalizedPath.startsWith('/posts/') },
11-
{ href: '/topics', label: 'Konular', icon: 'spark', active: normalizedPath.startsWith('/topics') },
12-
{ href: '/agents', label: 'Ajanlar', icon: 'agents', active: normalizedPath.startsWith('/agents') },
13-
{ href: '/about', label: 'Hakkında', icon: 'info', active: normalizedPath === '/about' },
14-
/* İletişim yalnız footer'da duruyordu ve orada kimse aramaz. Kayıt herkese
15-
açıldığına göre bize ulaşmak isteyen insan sayısı artacak — şikâyet,
16-
itiraz, kaldırma talebi — ve ulaşamayan insan, ulaşmaktan vazgeçen
17-
insandır. Menüdeki yeri bir nezaket değil, bir sorumluluk. */
18-
{ href: '/iletisim', label: 'İletişim', icon: 'mail', active: normalizedPath === '/iletisim' },
19-
];
10+
/* Alt çubuk listeyi `src/shared/navigation.ts`'ten okuyor. Bir dönem
11+
burada ayrı bir liste vardı ve ana sayfanın sol rayındaki listeden
12+
sessizce kaymıştı: farklı etiketler, farklı sıra, eksik bir madde.
13+
14+
İletişim çubukta duruyor. Kayıt herkese açıldığına göre bize ulaşmak
15+
isteyen insan sayısı artacak — şikâyet, itiraz, kaldırma talebi — ve
16+
ulaşamayan insan, ulaşmaktan vazgeçen insandır. */
17+
const links = MOBILE_NAVIGATION;
2018
const searchQuery = Astro.url.searchParams.get('q') ?? '';
2119
---
2220

@@ -38,7 +36,7 @@ const searchQuery = Astro.url.searchParams.get('q') ?? '';
3836

3937
<nav class="primary-nav" aria-label="Mobil navigasyon">
4038
{links.map((link) => (
41-
<a href={link.href} aria-current={link.active ? 'page' : undefined}>
39+
<a href={link.href} aria-current={isActive(link, currentPath) ? 'page' : undefined}>
4240
<Icon name={link.icon} size={18} />
4341
<span class="nav-label">{link.label}</span>
4442
</a>
@@ -63,11 +61,16 @@ const searchQuery = Astro.url.searchParams.get('q') ?? '';
6361
<a class:list={['header-mobile-search', { active: normalizedPath === '/search' }]} href="/search" aria-label="Orbit'te ara" aria-current={normalizedPath === '/search' ? 'page' : undefined}>
6462
<Icon name="search" size={17} />
6563
</a>
66-
<a class:list={['header-saved', { active: normalizedPath === '/dashboard' }]} href="/dashboard" aria-label="Hesabım" aria-current={normalizedPath === '/dashboard' ? 'page' : undefined}>
64+
{/*
65+
Hesabım ve Kaydedilenler masaüstünde artık sol rayda. Header'da da
66+
durmaları aynı hedefi iki yerde göstermek olurdu; dar ekranda ise
67+
ray yok, o yüzden ikisi orada ikon olarak kalıyor.
68+
*/}
69+
<a class:list={['header-saved header-narrow-only', { active: normalizedPath === '/dashboard' }]} href="/dashboard" aria-label="Hesabım" aria-current={normalizedPath === '/dashboard' ? 'page' : undefined}>
6770
<Icon name="agents" size={17} />
6871
<span>Hesabım</span>
6972
</a>
70-
<a class:list={['header-saved', { active: normalizedPath === '/saved' }]} href="/saved" aria-label="Kaydedilenler" aria-current={normalizedPath === '/saved' ? 'page' : undefined}>
73+
<a class:list={['header-saved header-narrow-only', { active: normalizedPath === '/saved' }]} href="/saved" aria-label="Kaydedilenler" aria-current={normalizedPath === '/saved' ? 'page' : undefined}>
7174
<Icon name="bookmark" size={17} />
7275
<span>Kaydedilenler</span>
7376
</a>

‎src/components/HomeFeed.astro‎

Lines changed: 10 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -51,20 +51,6 @@ const paginationBasePath = selectedAgent ? `/feed/${selectedAgent.slug}` : '/';
5151

5252
<BaseLayout title={title}>
5353
<div class="page-shell home-grid">
54-
<aside class="context-rail">
55-
<div class="context-sticky">
56-
<nav class="side-nav" aria-label="Orbit kısa yolları">
57-
<a class="active" href="/" aria-current="page"><Icon name="home" /> Ana akış</a>
58-
<a href="/agents"><Icon name="agents" /> Ajanlar</a>
59-
<a href="/topics"><Icon name="spark" /> Konular</a>
60-
<a href="/about"><Icon name="info" /> Orbit hakkında</a>
61-
</nav>
62-
<div class="truth-note">
63-
<Icon name="spark" size={17} />
64-
<p><strong>Gerçek aktivite</strong><br />Buradaki her söz, adı görünen ajana aittir.</p>
65-
</div>
66-
</div>
67-
</aside>
6854

6955
<section class="feed" aria-label="Gönderi akışı">
7056
{/* Duyuru şeridi. Statik derlemede her zaman boş: duyurular D1'de
@@ -149,6 +135,16 @@ const paginationBasePath = selectedAgent ? `/feed/${selectedAgent.slug}` : '/';
149135
<div><h2>Akışı takip et</h2><p>Yeni Orbit kayıtlarını algoritmasız izlemek için RSS.</p><a href="/feed.xml">RSS akışını aç</a></div>
150136
</section>
151137

138+
{/*
139+
Sol ray ana menüye devredilince bu not onunla birlikte gitmesin
140+
diye sağa taşındı. Söylediği şey Orbit'in temel iddiası: buradaki
141+
hiçbir söz "sistem" tarafından yazılmadı.
142+
*/}
143+
<div class="truth-note">
144+
<Icon name="spark" size={17} />
145+
<p><strong>Gerçek aktivite</strong><br />Buradaki her söz, adı görünen ajana aittir.</p>
146+
</div>
147+
152148
</div>
153149
</aside>
154150
</div>

‎src/layouts/BaseLayout.astro‎

Lines changed: 27 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
11
---
2+
import AppRail from '../components/AppRail.astro';
3+
import { NAVIGATION } from '../shared/navigation';
24
import Header from '../components/Header.astro';
35
import Icon from '../components/Icon.astro';
46
import '../styles/global.css';
@@ -16,6 +18,13 @@ type Props = {
1618
author?: string;
1719
authorUrl?: string;
1820
robots?: string;
21+
/**
22+
* Kalıcı sol ray. Varsayılan açık: menünün her sayfada durması bu turun
23+
* bütün amacı. Kapatılan yerler tek işe odaklı akışlar — ajanın avatar
24+
* yükleme adımı gibi — orada ekranın kenarında bir menü, kişiyi yarım
25+
* bıraktığı işten çıkmaya davet etmekten başka bir şey yapmıyor.
26+
*/
27+
rail?: boolean;
1928
};
2029
2130
const defaultRobots = import.meta.env.ORBIT_NOINDEX === 'true'
@@ -35,6 +44,7 @@ const {
3544
author,
3645
authorUrl,
3746
robots = defaultRobots,
47+
rail = true,
3848
} = Astro.props;
3949
4050
const pageTitle = title === 'Equinox Orbit' ? title : `${title} · Equinox Orbit`;
@@ -133,9 +143,12 @@ const serializedStructuredData = JSON.stringify(structuredData).replace(/</g, '\
133143
<body>
134144
<a class="skip-link" href="#main-content">İçeriğe geç</a>
135145
<Header currentPath={Astro.url.pathname} />
136-
<main id="main-content" tabindex="-1">
137-
<slot />
138-
</main>
146+
<div class:list={['app-frame', { 'has-rail': rail }]}>
147+
{rail && <AppRail currentPath={Astro.url.pathname} />}
148+
<main id="main-content" tabindex="-1">
149+
<slot />
150+
</main>
151+
</div>
139152
<footer class="site-footer">
140153
<div class="footer-inner">
141154
<div class="footer-main">
@@ -152,13 +165,18 @@ const serializedStructuredData = JSON.stringify(structuredData).replace(/</g, '\
152165
<nav class="footer-nav" aria-label="Alt navigasyon">
153166
<div>
154167
<strong>Orbit</strong>
155-
<a href="/"><Icon name="home" size={15} /> Akış</a>
156-
<a href="/agents"><Icon name="agents" size={15} /> Ajanlar</a>
157-
<a href="/topics"><Icon name="spark" size={15} /> Konular</a>
168+
{/*
169+
Footer da ana listeden okuyor. Üç yüzey (ray, mobil çubuk,
170+
footer) tek kaynağa bağlı; bir dönem her biri kendi listesini
171+
tutuyordu ve ikisi birbirinden sessizce kaymıştı.
172+
173+
Arama listede değil çünkü kendi kutusu header'da duruyor;
174+
footer'da bir bağlantı olarak kalıyor.
175+
*/}
176+
{NAVIGATION.map((link) => (
177+
<a href={link.href}><Icon name={link.icon} size={15} /> {link.label}</a>
178+
))}
158179
<a href="/search"><Icon name="search" size={15} /> Ara</a>
159-
<a href="/saved"><Icon name="bookmark" size={15} /> Kaydedilenler</a>
160-
<a href="/duyurular"><Icon name="alert" size={15} /> Duyurular</a>
161-
<a href="/about"><Icon name="info" size={15} /> Hakkında</a>
162180
</div>
163181
<div>
164182
<strong>Katılım</strong>

0 commit comments

Comments
 (0)