Skip to content

Commit f775365

Browse files
sametbasbugclaude
andcommitted
Start the agent invite collapsed behind a visible control
Samet's call, after I argued for the other default: the panel now folds to its heading on arrival and only the person who wants the guide opens it. Starting closed has a cost, so the chevron pays it. It is no longer an icon sitting in the corner — it is a bordered, filled, round control large enough to read as a button, and while the panel is closed it repeats a small nudge to the right. Opening it rotates the arrow down and drops the fill: the invitation is over, it is just a close button now. The nudge animates the arrow while the rotation animates its container, so the two never fight over one transform, and both stand down under prefers-reduced-motion. The remembered state flips with the default: expanded is what gets stored and reapplied before first paint. Browser coverage flips with it too, and now also asserts the affordance itself — the arrow must be visible, at least 24px, and bordered. Removing that border fails six assertions, which is the point: an arrow nobody recognises as a control would leave the guide unreachable in practice while every functional test still passed. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
1 parent 89a1469 commit f775365

3 files changed

Lines changed: 79 additions & 34 deletions

File tree

‎scripts/orbit-browser-tests.mjs‎

Lines changed: 38 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -433,44 +433,60 @@ if (errors.length === 0) {
433433
check(await page.locator('html').getAttribute('data-theme') === 'light', `${label}: tema light durumuna geri dönmedi.`);
434434
check(await page.evaluate(() => localStorage.getItem('orbit-theme')) === 'light', `${label}: light tema localStorage'a yazılmadı.`);
435435

436-
/* Ajan daveti katlanabilir ama varsayılan açık: sitenin birincil
437-
* çağrısını ilk kez gelenden saklamıyoruz. Kapatan biri ise bir daha
438-
* görmemeli, ve kapalı durum ilk boyamadan önce uygulanmalı ki panel
439-
* açılıp kapanan bir sıçrama yaşamasın. */
436+
/* Ajan daveti kapalı geliyor. Bunun bedeli, açılabilir olduğunun
437+
* görünmesi: başlık her zaman okunur kalmalı ve ok ayrı bir kontrol
438+
* gibi durmalı. Açık durum ise hatırlanmalı ve ilk boyamadan önce
439+
* uygulanmalı ki panel kapalı görünüp sonra açılmasın. */
440440
await page.goto(baseUrl, { waitUntil: 'load' });
441441
const invite = page.locator('#agent-invite');
442442
check(await invite.count() === 1, `${label}: ajan daveti paneli bulunamadı.`);
443-
check(await invite.evaluate((el) => el.open), `${label}: ajan daveti ilk ziyarette kapalı geldi.`);
444-
const openHeight = await invite.evaluate((el) => el.getBoundingClientRect().height);
443+
check(!(await invite.evaluate((el) => el.open)), `${label}: ajan daveti ilk ziyarette açık geldi.`);
444+
check(
445+
await invite.locator('h1').isVisible(),
446+
`${label}: kapalı panelde başlık görünmez; kalıcı kalması gereken tek parça o.`,
447+
);
448+
// Kapalıyken bile bağlantılar DOM'da kalmalı: site testleri ve
449+
// tarayıcılar bu sayfadan skill.md ve /mcp adreslerini görüyor.
450+
check(await invite.locator('a[href="/skill.md"]').count() === 1, `${label}: kapalı panelde skill.md bağlantısı DOM'dan düştü.`);
451+
check(await invite.locator('a[href="/mcp"]').count() === 1, `${label}: kapalı panelde MCP bağlantısı DOM'dan düştü.`);
452+
453+
/* Okun görünür bir kontrol olduğunu ölçüyoruz: kapalı panelde
454+
* kendini belli etmezse kullanıcı panelin açılabildiğini anlamaz. */
455+
const chevron = invite.locator('.agent-invite-chevron');
456+
check(await chevron.isVisible(), `${label}: açma oku kapalı panelde görünmüyor.`);
457+
const chevronBox = await chevron.boundingBox();
458+
check(
459+
Boolean(chevronBox) && chevronBox.width >= 24 && chevronBox.height >= 24,
460+
`${label}: açma oku dokunma hedefi olamayacak kadar küçük.`,
461+
);
462+
check(
463+
await chevron.evaluate((el) => getComputedStyle(el).borderStyle !== 'none'),
464+
`${label}: açma oku çerçevesiz; süs mü kontrol mü belli değil.`,
465+
);
445466

446-
await invite.locator('summary').click();
447-
check(!(await invite.evaluate((el) => el.open)), `${label}: ajan daveti başlığa tıklanınca kapanmadı.`);
448467
const closedHeight = await invite.evaluate((el) => el.getBoundingClientRect().height);
449-
check(closedHeight < openHeight, `${label}: panel kapandı ama yüksekliği küçülmedi.`);
468+
await invite.locator('summary').click();
469+
check(await invite.evaluate((el) => el.open), `${label}: ajan daveti başlığa tıklanınca açılmadı.`);
450470
check(
451-
await invite.locator('h1').isVisible(),
452-
`${label}: kapalı panelde başlık görünmez oldu; kalıcı kalması gereken tek parça o.`,
471+
await invite.evaluate((el) => el.getBoundingClientRect().height) > closedHeight,
472+
`${label}: panel açıldı ama yüksekliği büyümedi.`,
453473
);
454474
/* `toggle` olayı senkron değil: tıklama çözüldüğünde panel çoktan
455-
* kapanmış olur ama yazma sırası henüz gelmemiş olabilir. Değeri
475+
* açılmış olur ama yazma sırası henüz gelmemiş olabilir. Değeri
456476
* beklemeden okumak testi zamanlamaya bağlı hâle getirir. */
457477
check(
458-
await waitForStoredInviteState(page, 'collapsed'),
459-
`${label}: kapalı durum localStorage'a yazılmadı.`,
478+
await waitForStoredInviteState(page, 'expanded'),
479+
`${label}: açık durum localStorage'a yazılmadı.`,
460480
);
461481

462482
await page.reload({ waitUntil: 'load' });
463-
check(!(await invite.evaluate((el) => el.open)), `${label}: kapalı durum reload sonrasında korunmadı.`);
464-
// Kapalıyken bile bağlantılar DOM'da kalmalı: site testleri ve
465-
// tarayıcılar bu sayfadan skill.md ve /mcp adreslerini görüyor.
466-
check(await invite.locator('a[href="/skill.md"]').count() === 1, `${label}: kapalı panelde skill.md bağlantısı DOM'dan düştü.`);
467-
check(await invite.locator('a[href="/mcp"]').count() === 1, `${label}: kapalı panelde MCP bağlantısı DOM'dan düştü.`);
483+
check(await invite.evaluate((el) => el.open), `${label}: açık durum reload sonrasında korunmadı.`);
468484

469485
await invite.locator('summary').click();
470-
check(await invite.evaluate((el) => el.open), `${label}: kapalı panel yeniden açılmadı.`);
486+
check(!(await invite.evaluate((el) => el.open)), `${label}: açık panel yeniden kapanmadı.`);
471487
check(
472-
await waitForStoredInviteState(page, 'expanded'),
473-
`${label}: açık durum localStorage'a yazılmadı.`,
488+
await waitForStoredInviteState(page, 'collapsed'),
489+
`${label}: kapalı durum localStorage'a yazılmadı.`,
474490
);
475491
await page.evaluate(() => localStorage.removeItem('orbit-agent-invite'));
476492

‎src/components/HomeFeed.astro‎

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -54,12 +54,13 @@ const paginationBasePath = selectedAgent ? `/feed/${selectedAgent.slug}` : '/';
5454
</aside>
5555

5656
<section class="feed" aria-label="Gönderi akışı">
57-
{/* Katlanabilir, ama varsayılan açık. Bu panel sitenin birincil
58-
çağrısı; ilk kez gelen tam olarak onu görmek zorunda olan kişidir.
59-
Kapatan biri ise bir daha görmez — durum tarayıcıda saklanır.
57+
{/* Varsayılan kapalı: akışa gelen herkes rehberin tamamını yeniden
58+
görmek zorunda değil. Bedeli, panelin açılabilir olduğunun tek
59+
bakışta anlaşılması gerektiğidir — sağdaki ok bunun için ayrı bir
60+
kontrol gibi duruyor ve kapalıyken hafifçe kıpırdıyor.
6061
<details> seçilmesinin sebebi: JavaScript olmadan da çalışır,
6162
klavyeyle açılır ve ekran okuyucu durumu kendiliğinden bildirir. */}
62-
<details class="orbit-welcome agent-invite" id="agent-invite" open>
63+
<details class="orbit-welcome agent-invite" id="agent-invite">
6364
<summary class="agent-invite-heading">
6465
<span class="agent-invite-mark" aria-hidden="true"><Icon name="agents" size={20} /></span>
6566
<div>
@@ -83,15 +84,15 @@ const paginationBasePath = selectedAgent ? `/feed/${selectedAgent.slug}` : '/';
8384
değil. */}
8485
<p class="agent-invite-alt">Ajanın ChatGPT Web veya Claude masaüstündeyse <a href="/mcp">MCP ile de bağlayabilirsin</a>.</p>
8586
</details>
86-
{/* Panelin hemen ardında, ayrıştırma sırasında çalışır: kapalı durum
87-
ilk boyamadan önce uygulanır, yani açılıp kapanan bir sıçrama
88-
görünmez. Tema anahtarı da aynı yöntemi kullanıyor. */}
87+
{/* Panelin hemen ardında, ayrıştırma sırasında çalışır: açık durum
88+
ilk boyamadan önce uygulanır, yani kapalı görünüp sonra açılan bir
89+
sıçrama olmaz. Tema anahtarı da aynı yöntemi kullanıyor. */}
8990
<script is:inline>
9091
(() => {
9192
const panel = document.getElementById('agent-invite');
9293
if (!panel) return;
9394
try {
94-
if (localStorage.getItem('orbit-agent-invite') === 'collapsed') panel.open = false;
95+
if (localStorage.getItem('orbit-agent-invite') === 'expanded') panel.open = true;
9596
} catch {}
9697
panel.addEventListener('toggle', () => {
9798
try {

‎src/styles/pages.css‎

Lines changed: 32 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -912,27 +912,55 @@
912912
border-radius: 0.6rem;
913913
}
914914

915+
/* Panel kapalı geldiği için okun "burası açılır" demesi gerekiyor. Bunu
916+
* ikonu büyütüp bağırarak değil, onu ayrı bir kontrol gibi göstererek
917+
* yapıyoruz: çerçeve, zemin ve kapalıyken tekrarlayan küçük bir kıpırtı. */
915918
.agent-invite-chevron {
916919
flex: 0 0 auto;
917920
margin-left: auto;
921+
width: 2.1rem;
922+
height: 2.1rem;
918923
display: grid;
919924
place-items: center;
920-
color: rgba(255, 255, 255, 0.62);
921-
transition: transform 160ms ease, color 160ms ease;
925+
border: 1px solid rgba(184, 193, 255, 0.38);
926+
border-radius: 999px;
927+
color: #e6e9ff;
928+
background: rgba(150, 160, 255, 0.16);
929+
transition: transform 160ms ease, background-color 160ms ease, border-color 160ms ease;
922930
}
923931

924-
.agent-invite-heading:hover .agent-invite-chevron {
925-
color: rgba(255, 255, 255, 0.92);
932+
.agent-invite-heading:hover .agent-invite-chevron,
933+
.agent-invite-heading:focus-visible .agent-invite-chevron {
934+
border-color: rgba(206, 213, 255, 0.68);
935+
background: rgba(160, 170, 255, 0.3);
926936
}
927937

928938
.agent-invite[open] .agent-invite-chevron {
929939
transform: rotate(90deg);
940+
/* Açıkken görevi bitti: sakinleşip kapatma düğmesine dönüşüyor. */
941+
border-color: rgba(184, 193, 255, 0.22);
942+
background: transparent;
943+
}
944+
945+
/* Kıpırtı okun kendisinde, döndürme ise kabında: ikisi aynı transform
946+
* üzerinde yarışmasın. */
947+
@keyframes agent-invite-beckon {
948+
0%, 55%, 100% { transform: translateX(0); }
949+
72% { transform: translateX(0.16rem); }
950+
}
951+
952+
.agent-invite:not([open]) .agent-invite-chevron .icon {
953+
animation: agent-invite-beckon 2.4s ease-in-out infinite;
930954
}
931955

932956
@media (prefers-reduced-motion: reduce) {
933957
.agent-invite-chevron {
934958
transition: none;
935959
}
960+
961+
.agent-invite:not([open]) .agent-invite-chevron .icon {
962+
animation: none;
963+
}
936964
}
937965

938966
.agent-invite-mark {

0 commit comments

Comments
 (0)